@scidian/osui
Version:
Lightweight JavaScript UI library.
272 lines (253 loc) • 12.2 kB
JavaScript
// INTERACTION
// addCloseButton()
// addMaxButton()
// makeDraggable()
// makeResizeable()
import { Button } from '../input/Button.js';
import { Css } from './Css.js';
import { Div } from '../core/Div.js';
import { ShadowBox } from '../layout/ShadowBox.js';
import { GRID_SIZE, RESIZERS } from '../constants.js';
import { IMAGE_CLOSE, IMAGE_EXPAND } from '../constants.js';
export const CLOSE_SIDES = {
LEFT: 'left',
RIGHT: 'right',
BOTH: 'both',
NONE: 'none',
}
export const CORNER_BUTTONS = {
CLOSE: 'close',
MAX: 'max',
}
const MOUSE_SLOP = 2;
class Interaction {
static addCloseButton(element, closeSide = CLOSE_SIDES.BOTH, offset = 0, scale = 1.3) {
Interaction.addCornerButton(CORNER_BUTTONS.CLOSE, element, closeSide, offset, scale);
}
static addMaxButton(element, closeSide = CLOSE_SIDES.BOTH, offset = 0, scale = 1.3) {
Interaction.addCornerButton(CORNER_BUTTONS.MAX, element, closeSide, offset, scale);
}
static addCornerButton(type = CORNER_BUTTONS.CLOSE, element, closeSide, offset = 0, scale = 1.3) {
if (!element || !element.isElement) {
console.warn(`Interaction.addCornerButton: Missing element argument`);
return undefined;
}
const button = new Button();
button.setClass('osui-corner-button');
button.addClass('osui-panel-button');
let cornerImage, buttonTooltip, buttonOffset;
switch (type) {
case CORNER_BUTTONS.CLOSE:
button.setStyle('background-color', '#e24c4b');
cornerImage = IMAGE_CLOSE;
buttonTooltip = 'Close Panel';
buttonOffset = 0;
break;
case CORNER_BUTTONS.MAX:
button.setStyle('background-color', '#2bc840');
cornerImage = IMAGE_EXPAND;
buttonTooltip = 'Toggle Panel';
buttonOffset = 1.2;
break;
}
const imageBox = new ShadowBox(cornerImage).evenShadow().fullSize().addClass('osui-corner-image');
button.add(imageBox);
button.dom.setAttribute('tooltip', buttonTooltip);
button.setStyle('min-height', `${scale}em`, 'min-width', `${scale}em`);
const sideways = `${0.8 - ((scale + 0.28571) / 2) + offset + (buttonOffset * scale)}em`;
button.setStyle('top', `${0.8 - ((scale + 0.28571) / 2)}em`);
button.setStyle((closeSide === CLOSE_SIDES.LEFT) ? 'left' : 'right', sideways);
if (closeSide === CLOSE_SIDES.BOTH) {
let lastSide = CLOSE_SIDES.RIGHT;
element.dom.addEventListener('pointermove', function(event) {
const rect = element.dom.getBoundingClientRect();
const middle = rect.left + (rect.width / 2);
const x = event.pageX;
let changeSide = CLOSE_SIDES.NONE;
if (x > middle && lastSide !== CLOSE_SIDES.RIGHT) changeSide = CLOSE_SIDES.RIGHT;
else if (x < middle && lastSide !== CLOSE_SIDES.LEFT) changeSide = CLOSE_SIDES.LEFT;
if (changeSide !== CLOSE_SIDES.NONE) {
button.addClass('osui-item-hidden');
setTimeout(() => {
button.dom.style.removeProperty('left');
button.dom.style.removeProperty('right');
button.setStyle(changeSide, sideways);
button.removeClass('osui-item-hidden');
}, 100);
lastSide = changeSide;
}
});
}
switch (type) {
case CORNER_BUTTONS.CLOSE:
button.dom.addEventListener('click', () => { element.hide(); });
break;
case CORNER_BUTTONS.MAX:
button.dom.addEventListener('click', () => {
if (typeof element.toggleMinMax === 'function') {
element.toggleMinMax();
}
});
break;
}
element.dom.addEventListener('pointerenter', () => button.addClass('osui-item-shown'));
element.dom.addEventListener('pointerleave', () => button.removeClass('osui-item-shown'));
element.addToSelf(button);
}
static makeDraggable(element, parent = element, limitToWindow = false, onDown = () => {}, onMove = () => {}, onUp = () => {}) {
const eventElement = (element && element.isElement) ? element.dom : element;
const dragElement = (parent && parent.isElement) ? parent.dom : parent;
let downX, downY, rect = {};
let lastX, lastY;
let computed = getComputedStyle(dragElement);
let minDistance = 0;
function roundNearest(decimal, increment = GRID_SIZE) {
if (!element.snapToGrid) return decimal;
return Math.round(decimal / increment) * increment;
}
function dragPointerDown(event) {
if (event.button !== 0) return;
event.stopPropagation();
event.preventDefault();
eventElement.focus();
eventElement.setPointerCapture(event.pointerId);
minDistance = 0;
downX = event.pageX;
downY = event.pageY;
lastX = event.pageX;
lastY = event.pageY;
computed = getComputedStyle(dragElement);
rect.left = parseFloat(computed.left);
rect.top = parseFloat(computed.top);
rect.width = parseFloat(computed.width);
rect.height = parseFloat(computed.height);
eventElement.ownerDocument.addEventListener('pointermove', dragPointerMove);
eventElement.ownerDocument.addEventListener('pointerup', dragPointerUp);
document.dispatchEvent(new Event('closemenu'));
/* CUSTOM CALLBACK */
onDown();
}
function dragPointerUp(event) {
event.stopPropagation();
event.preventDefault();
eventElement.releasePointerCapture(event.pointerId);
eventElement.ownerDocument.removeEventListener('pointermove', dragPointerMove);
eventElement.ownerDocument.removeEventListener('pointerup', dragPointerUp);
eventElement.style.cursor = 'inherit';
/* CUSTOM CALLBACK */
onUp();
}
function dragPointerMove(event) {
event.stopPropagation();
event.preventDefault();
if (event.isTrusted) { /* not generated programmatically */
lastX = event.pageX;
lastY = event.pageY;
}
minDistance = Math.max(minDistance, Math.abs(downX - lastX));
minDistance = Math.max(minDistance, Math.abs(downY - lastY));
if (minDistance < MOUSE_SLOP) return;
eventElement.style.cursor = 'move';
const scale = ((element && element.getScale) ? element.getScale() : 1);
const diffX = (lastX - downX) * (1 / scale);
const diffY = (lastY - downY) * (1 / scale);
let newLeft = roundNearest(rect.left + diffX);
let newTop = roundNearest(rect.top + diffY);
if (limitToWindow) {
// // OPTION: Limit to Title Bar
const titleHeight = parseInt(Css.toPx('4em'));
newLeft = Math.min(window.innerWidth - (rect.width / 2), newLeft);
newTop = Math.min(window.innerHeight - titleHeight, newTop);
newLeft = Math.max(- (rect.width / 2), newLeft);
newTop = Math.max(0, newTop);
// // OPTION: Limit to Window
// const titleHeight = parseInt(Css.toPx('4em'));
// newLeft = Math.min(window.innerWidth - rect.width, newLeft);
// newTop = Math.min(window.innerHeight - rect.height, newTop);
// newLeft = Math.max(0, newLeft);
// newTop = Math.max(0, newTop);
}
dragElement.style.left = `${newLeft}px`;
dragElement.style.top = `${newTop}px`;
/* CUSTOM CALLBACK */
onMove(diffX, diffY);
}
eventElement.addEventListener('pointerdown', dragPointerDown);
}
static makeResizeable(resizeElement, addToElement = resizeElement, resizers = [], onDown = () => {}, onMove = () => {}, onUp = () => {}) {
if (!resizeElement || !resizeElement.isElement) return console.warning('Resizeable.enable: ResizeElement not defined');
if (!addToElement || !addToElement.isElement) return console.warning('Resizeable.enable: AddToElement not defined');
resizeElement.addClass('osui-resizeable');
// Build
const resizerDivs = {};
for (let key in RESIZERS) {
const resizerName = RESIZERS[key];
const className = `osui-resizer-${resizerName}`;
const resizer = new Div();
resizer.addClass('osui-resizer');
resizer.addClass(className);
let downX, downY, lastX, lastY;
function resizePointerDown(event) {
if (event.button !== 0) return;
event.stopPropagation();
event.preventDefault();
resizer.dom.setPointerCapture(event.pointerId);
downX = event.pageX;
downY = event.pageY;
lastX = event.pageX;
lastY = event.pageY;
resizeElement.dom.ownerDocument.addEventListener('pointermove', resizePointerMove);
resizeElement.dom.ownerDocument.addEventListener('pointerup', resizePointerUp);
document.dispatchEvent(new Event('closemenu'));
/* CUSTOM CALLBACK */
onDown();
}
function resizePointerUp(event) {
event.stopPropagation();
event.preventDefault();
resizer.dom.releasePointerCapture(event.pointerId);
resizeElement.dom.ownerDocument.removeEventListener('pointermove', resizePointerMove);
resizeElement.dom.ownerDocument.removeEventListener('pointerup', resizePointerUp);
/* CUSTOM CALLBACK */
onUp();
}
function resizePointerMove(event) {
event.stopPropagation();
event.preventDefault();
if (event.isTrusted) { /* not generated programmatically */
lastX = event.pageX;
lastY = event.pageY;
}
const diffX = lastX - downX;
const diffY = lastY - downY;
/* CUSTOM CALLBACK */
onMove(resizer, diffX, diffY);
}
resizer.dom.addEventListener('pointerdown', resizePointerDown);
resizerDivs[resizerName] = resizer;
addToElement.addToSelf(resizer);
}
// Toggle
const resizerEnabled = {};
resizeElement.toggleResize = function(resizerName, enable = true) {
if (!resizerName) return;
resizerEnabled[resizerName] = enable;
function toggleDisplay(element, display) {
if (!element) return;
element.setStyle('display', display ? '' : 'none');
}
toggleDisplay(resizerDivs[resizerName], enable);
toggleDisplay(resizerDivs[RESIZERS.TOP_LEFT], resizerEnabled[RESIZERS.TOP] && resizerEnabled[RESIZERS.LEFT]);
toggleDisplay(resizerDivs[RESIZERS.TOP_RIGHT], resizerEnabled[RESIZERS.TOP] && resizerEnabled[RESIZERS.RIGHT]);
toggleDisplay(resizerDivs[RESIZERS.BOTTOM_LEFT], resizerEnabled[RESIZERS.BOTTOM] && resizerEnabled[RESIZERS.LEFT]);
toggleDisplay(resizerDivs[RESIZERS.BOTTOM_RIGHT], resizerEnabled[RESIZERS.BOTTOM] && resizerEnabled[RESIZERS.RIGHT]);
return resizeElement;
};
// Enable
for (let key in RESIZERS) {
const resizerName = RESIZERS[key];
resizeElement.toggleResize(resizerName, resizers.includes(resizerName));
}
}
}
export { Interaction };