2022-04-15 01:51:09 +00:00
|
|
|
import { Component } from /**/"./Component.js";
|
|
|
|
let Toolkit;
|
2021-08-26 19:23:18 +00:00
|
|
|
|
|
|
|
|
2022-04-15 01:51:09 +00:00
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
// Window //
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
// Standalone movable dialog
|
|
|
|
class Window extends Component {
|
|
|
|
static Component = Component;
|
|
|
|
|
|
|
|
///////////////////////// Initialization Methods //////////////////////////
|
|
|
|
|
|
|
|
constructor(gui, options) {
|
|
|
|
super(gui, options, {
|
|
|
|
className : "tk tk-window",
|
|
|
|
focusable : true,
|
|
|
|
role : "dialog",
|
|
|
|
tabStop : false,
|
|
|
|
tagName : "div",
|
|
|
|
visibility: true,
|
|
|
|
style : {
|
|
|
|
position: "absolute"
|
|
|
|
}
|
|
|
|
});
|
2021-08-26 19:23:18 +00:00
|
|
|
|
|
|
|
// Configure instance fields
|
2022-04-15 01:51:09 +00:00
|
|
|
this.firstShown = false;
|
|
|
|
this.lastFocus = null;
|
|
|
|
|
|
|
|
// DOM container
|
|
|
|
this.contents = document.createElement("div");
|
|
|
|
this.contents.style.display = "flex";
|
|
|
|
this.contents.style.flexDirection = "column";
|
|
|
|
this.element.append(this.contents);
|
|
|
|
|
|
|
|
// Sizing borders
|
|
|
|
this.borders = {}
|
|
|
|
this.border("n" ); this.border("w" );
|
|
|
|
this.border("e" ); this.border("s" );
|
|
|
|
this.border("nw"); this.border("ne");
|
|
|
|
this.border("sw"); this.border("se");
|
|
|
|
|
|
|
|
// Title bar
|
|
|
|
this.titleBar = document.createElement("div");
|
|
|
|
this.titleBar.className = "tk tk-title";
|
|
|
|
this.titleBar.style.display = "flex";
|
|
|
|
this.contents.append(this.titleBar);
|
|
|
|
this.titleBar.addEventListener(
|
|
|
|
"pointerdown", e=>this.onTitlePointerDown(e));
|
|
|
|
this.titleBar.addEventListener(
|
|
|
|
"pointermove", e=>this.onTitlePointerMove(e));
|
|
|
|
this.titleBar.addEventListener(
|
|
|
|
"pointerup" , e=>this.onTitlePointerUp (e));
|
|
|
|
|
|
|
|
// Title bar text
|
|
|
|
this.titleText = document.createElement("div");
|
|
|
|
this.titleText.className = "tk tk-text";
|
|
|
|
this.titleText.id = Toolkit.id();
|
|
|
|
this.titleText.style.flexGrow = "1";
|
|
|
|
this.titleText.style.position = "relative";
|
|
|
|
this.titleBar.append(this.titleText);
|
|
|
|
this.setAttribute("aria-labelledby", this.titleText.id);
|
|
|
|
|
|
|
|
// Close button
|
|
|
|
this.titleClose = document.createElement("div");
|
|
|
|
this.titleClose.className = "tk tk-close";
|
|
|
|
this.titleClose.setAttribute("aria-hidden", "true");
|
|
|
|
this.titleBar.append(this.titleClose);
|
|
|
|
this.titleClose.addEventListener(
|
|
|
|
"pointerdown", e=>this.onClosePointerDown(e));
|
|
|
|
this.titleClose.addEventListener(
|
|
|
|
"pointermove", e=>this.onClosePointerMove(e));
|
|
|
|
this.titleClose.addEventListener(
|
|
|
|
"pointerup" , e=>this.onClosePointerUp (e));
|
|
|
|
|
|
|
|
// Window client area
|
|
|
|
this.client = document.createElement("div");
|
|
|
|
this.client.className = "tk tk-client";
|
|
|
|
this.client.style.flexGrow = "1";
|
|
|
|
this.client.style.minHeight = "0";
|
|
|
|
this.client.style.minWidth = "0";
|
|
|
|
this.client.style.overflow = "hidden";
|
|
|
|
this.client.style.position = "relative";
|
|
|
|
this.contents.append(this.client);
|
|
|
|
|
|
|
|
// User agent behavior override
|
|
|
|
let observer = new ResizeObserver(
|
|
|
|
()=>this.titleBar.style.width =
|
|
|
|
this.client.getBoundingClientRect().width + "px"
|
|
|
|
);
|
|
|
|
observer.observe(this.client);
|
2021-08-26 19:23:18 +00:00
|
|
|
|
|
|
|
// Configure element
|
2022-04-15 01:51:09 +00:00
|
|
|
this.setAttribute("aria-modal", "false");
|
|
|
|
this.setBounds(
|
|
|
|
options.x , options.y,
|
|
|
|
options.width, options.height
|
|
|
|
);
|
2021-08-26 19:23:18 +00:00
|
|
|
|
2022-04-15 01:51:09 +00:00
|
|
|
// Configure component
|
|
|
|
this.gui.localize(this);
|
|
|
|
this.setTitle (options.title );
|
|
|
|
this.setCloseToolTip(options.closeToolTip);
|
|
|
|
this.addEventListener("focus" , e=>this.onFocus(e), true);
|
|
|
|
this.addEventListener("keydown" , e=>this.onWindowKeyDown (e));
|
|
|
|
this.addEventListener("pointerdown", e=>this.onWindowPointerDown(e));
|
2021-08-26 19:23:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2022-04-15 01:51:09 +00:00
|
|
|
///////////////////////////// Event Handlers //////////////////////////////
|
2021-08-26 19:23:18 +00:00
|
|
|
|
2022-04-15 01:51:09 +00:00
|
|
|
// Border pointer down
|
|
|
|
onBorderPointerDown(e, edge) {
|
|
|
|
if (e.target.hasPointerCapture(e.pointerId) || e.button != 0)
|
|
|
|
return;
|
|
|
|
e.target.setPointerCapture(e.pointerId);
|
|
|
|
e.preventDefault();
|
|
|
|
let bndClient = this.client.getBoundingClientRect();
|
|
|
|
let bndWindow = this .getBounds ();
|
|
|
|
let bndDesktop = this.parent ? this.parent.getBounds() : bndWindow;
|
|
|
|
let coords = Toolkit.screenCoords(e);
|
|
|
|
this.drag = {
|
|
|
|
clickX : coords.x,
|
|
|
|
clickY : coords.y,
|
|
|
|
mode : "resize",
|
|
|
|
pointerId : e.pointerId,
|
|
|
|
startHeight: bndClient.height,
|
|
|
|
startWidth : bndClient.width,
|
|
|
|
startX : bndWindow.x - bndDesktop.x,
|
|
|
|
startY : bndWindow.y - bndDesktop.y,
|
|
|
|
target : e.target
|
|
|
|
};
|
2021-08-30 02:14:06 +00:00
|
|
|
}
|
|
|
|
|
2022-04-15 01:51:09 +00:00
|
|
|
// Border pointer move
|
|
|
|
onBorderPointerMove(e, edge) {
|
|
|
|
if (!e.target.hasPointerCapture(e.pointerId))
|
|
|
|
return;
|
|
|
|
e.stopPropagation();
|
|
|
|
e.preventDefault();
|
|
|
|
let bndWindow = this.getBounds();
|
|
|
|
this["resize" + edge.toUpperCase()](
|
|
|
|
Toolkit.screenCoords(e),
|
|
|
|
this.client .getBoundingClientRect(),
|
|
|
|
this.parent ? this.parent.getBounds() : bndWindow,
|
|
|
|
bndWindow,
|
|
|
|
this.titleBar.getBoundingClientRect()
|
|
|
|
);
|
2021-09-06 00:09:15 +00:00
|
|
|
}
|
|
|
|
|
2022-04-15 01:51:09 +00:00
|
|
|
// Border pointer up
|
|
|
|
onBorderPointerUp(e, edge) {
|
|
|
|
if (!e.target.hasPointerCapture(e.pointerId) || e.button != 0)
|
|
|
|
return;
|
|
|
|
e.target.releasePointerCapture(e.pointerId);
|
|
|
|
e.stopPropagation();
|
|
|
|
e.preventDefault();
|
2021-09-02 00:16:22 +00:00
|
|
|
}
|
|
|
|
|
2022-04-15 01:51:09 +00:00
|
|
|
// Close pointer down
|
|
|
|
onClosePointerDown(e) {
|
|
|
|
if (this.titleClose.hasPointerCapture(e.pointerId) || e.button != 0)
|
|
|
|
return;
|
|
|
|
this.titleClose.setPointerCapture(e.pointerId);
|
|
|
|
e.stopPropagation();
|
|
|
|
e.preventDefault();
|
|
|
|
this.titleClose.classList.add("active");
|
|
|
|
this.drag = {
|
|
|
|
mode: "close",
|
|
|
|
x : e.offsetX,
|
|
|
|
y : e.offsetY
|
|
|
|
};
|
2021-08-26 19:23:18 +00:00
|
|
|
}
|
|
|
|
|
2022-04-15 01:51:09 +00:00
|
|
|
// Close pointer move
|
|
|
|
onClosePointerMove(e) {
|
|
|
|
if (!this.titleClose.hasPointerCapture(e.pointerId))
|
|
|
|
return;
|
|
|
|
e.stopPropagation();
|
|
|
|
e.preventDefault();
|
|
|
|
if (Toolkit.isInside(this.titleClose, e))
|
|
|
|
this.titleClose.classList.add("active");
|
|
|
|
else this.titleClose.classList.remove("active");
|
2021-08-26 19:23:18 +00:00
|
|
|
}
|
|
|
|
|
2022-04-15 01:51:09 +00:00
|
|
|
// Close pointer up
|
|
|
|
onClosePointerUp(e) {
|
|
|
|
if (!this.titleClose.hasPointerCapture(e.pointerId) || e.button != 0)
|
|
|
|
return;
|
|
|
|
this.titleClose.releasePointerCapture(e.pointerId);
|
|
|
|
e.stopPropagation();
|
|
|
|
e.preventDefault();
|
|
|
|
this.titleClose.classList.remove("active");
|
|
|
|
if (Toolkit.isInside(this.titleClose, e))
|
|
|
|
this.element.dispatchEvent(Toolkit.event("close", this));
|
|
|
|
this.drag = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Focus capture
|
|
|
|
onFocus(e) {
|
|
|
|
|
|
|
|
// Bring this window to the foreground of its siblings
|
|
|
|
if (!this.contains(e.relatedTarget) && this.parent)
|
|
|
|
this.parent.bringToFront(this);
|
|
|
|
|
|
|
|
// The target is not the window itself
|
|
|
|
if (e.target != this.element) {
|
|
|
|
this.lastFocus = e.target;
|
2021-09-06 00:09:15 +00:00
|
|
|
return;
|
|
|
|
}
|
2022-04-15 01:51:09 +00:00
|
|
|
|
|
|
|
// Select the first focusable child
|
|
|
|
if (this.lastFocus == null)
|
|
|
|
this.lastFocus = Toolkit.listFocusables(this.element)[0] || null;
|
|
|
|
|
|
|
|
// Send focus to the most recently focused element
|
|
|
|
if (this.lastFocus)
|
|
|
|
this.lastFocus.focus();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Title pointer down
|
|
|
|
onTitlePointerDown(e) {
|
|
|
|
if (this.titleBar.hasPointerCapture(e.pointerId) || e.button != 0)
|
2021-08-30 02:14:06 +00:00
|
|
|
return;
|
2022-04-15 01:51:09 +00:00
|
|
|
this.titleBar.setPointerCapture(e.pointerId);
|
|
|
|
e.preventDefault();
|
|
|
|
let bndWindow = this.getBounds();
|
|
|
|
let bndDesktop = this.parent ? this.parent.getBounds() : bndWindow;
|
|
|
|
let coords = Toolkit.screenCoords(e);
|
|
|
|
this.drag = {
|
|
|
|
clickX : coords.x,
|
|
|
|
clickY : coords.y,
|
|
|
|
mode : "move",
|
|
|
|
pointerId: e.pointerId,
|
|
|
|
startX : bndWindow.x - bndDesktop.x,
|
|
|
|
startY : bndWindow.y - bndDesktop.y
|
|
|
|
};
|
2021-08-30 02:14:06 +00:00
|
|
|
}
|
|
|
|
|
2022-04-15 01:51:09 +00:00
|
|
|
// Title pointer move
|
|
|
|
onTitlePointerMove(e) {
|
|
|
|
if (!this.titleBar.hasPointerCapture(e.pointerId))
|
|
|
|
return;
|
|
|
|
e.stopPropagation();
|
|
|
|
e.preventDefault();
|
|
|
|
let coords = Toolkit.screenCoords(e);
|
|
|
|
let valid = this.getValidLocations(
|
|
|
|
this.drag.startX + coords.x - this.drag.clickX,
|
|
|
|
this.drag.startY + coords.y - this.drag.clickY
|
|
|
|
);
|
|
|
|
this.setLocation(valid.x, valid.y);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Title pointer up
|
|
|
|
onTitlePointerUp(e) {
|
|
|
|
if (!this.titleBar.hasPointerCapture(e.pointerId) || e.button != 0)
|
|
|
|
return;
|
|
|
|
this.titleBar.releasePointerCapture(e.pointerId);
|
|
|
|
e.stopPropagation();
|
|
|
|
e.preventDefault();
|
|
|
|
this.drag = null;
|
2021-09-02 00:16:22 +00:00
|
|
|
}
|
|
|
|
|
2022-04-15 01:51:09 +00:00
|
|
|
// Window key press
|
|
|
|
onWindowKeyDown(e) {
|
2021-08-26 19:23:18 +00:00
|
|
|
|
2022-04-15 01:51:09 +00:00
|
|
|
// Process by key
|
|
|
|
switch (e.key) {
|
2021-08-26 19:23:18 +00:00
|
|
|
|
2022-04-15 01:51:09 +00:00
|
|
|
// Undo un-committed bounds modifications
|
|
|
|
case "Escape":
|
|
|
|
|
|
|
|
// Not dragging
|
|
|
|
if (this.drag == null)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Moving
|
|
|
|
if (this.drag.mode == "move") {
|
|
|
|
this.titleBar.releasePointerCapture(this.drag.pointerId);
|
|
|
|
this.setLocation(this.drag.startX, this.drag.startY);
|
|
|
|
this.drag = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Resizing
|
|
|
|
else if (this.drag.mode == "resize") {
|
|
|
|
this.drag.target
|
|
|
|
.releasePointerCapture(this.drag.pointerId);
|
|
|
|
this.setBounds(
|
|
|
|
this.drag.startX , this.drag.startY,
|
|
|
|
this.drag.startWidth, this.drag.startHeight
|
|
|
|
);
|
|
|
|
this.drag = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
// Transfer focus to another element
|
|
|
|
case "Tab":
|
|
|
|
|
|
|
|
default: return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// The event was handled
|
|
|
|
e.stopPropagation();
|
|
|
|
e.preventDefault();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Window pointer down
|
|
|
|
onWindowPointerDown(e) {
|
|
|
|
this.focus(e);
|
|
|
|
e.stopPropagation();
|
|
|
|
e.preventDefault();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
///////////////////////////// Public Methods //////////////////////////////
|
|
|
|
|
|
|
|
// Add a DOM element to this component's element
|
|
|
|
append(child) {
|
|
|
|
let element = child instanceof Element ? child : child.element;
|
|
|
|
this.client.append(element);
|
|
|
|
}
|
2021-08-26 19:23:18 +00:00
|
|
|
|
2022-04-15 01:51:09 +00:00
|
|
|
// Position the window in the center of the parent Desktop
|
2021-09-02 00:16:22 +00:00
|
|
|
center() {
|
2022-04-15 01:51:09 +00:00
|
|
|
if (!this.parent)
|
|
|
|
return;
|
|
|
|
let bndParent = this.parent.getBounds();
|
|
|
|
let bndWindow = this .getBounds();
|
|
|
|
this.setLocation(
|
|
|
|
Math.max(Math.floor((bndParent.width - bndWindow.width ) / 2), 0),
|
|
|
|
Math.max(Math.floor((bndParent.height - bndWindow.height) / 2), 0)
|
2021-09-02 00:16:22 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-04-15 01:51:09 +00:00
|
|
|
// Programmatically close the window
|
|
|
|
close() {
|
|
|
|
this.event("close");
|
|
|
|
}
|
2021-08-26 19:23:18 +00:00
|
|
|
|
2022-04-15 01:51:09 +00:00
|
|
|
// Add a DOM element to the beginning of this component's children
|
|
|
|
prepend(child) {
|
|
|
|
let element = child instanceof Element ? child : child.element;
|
|
|
|
this.element.prepend(element);
|
|
|
|
}
|
2021-08-26 19:23:18 +00:00
|
|
|
|
2022-04-15 01:51:09 +00:00
|
|
|
// Specify a new position and size for the window
|
|
|
|
setBounds(x, y, width, height) {
|
|
|
|
this.setSize(width, height);
|
2021-08-26 19:23:18 +00:00
|
|
|
this.setLocation(x, y);
|
|
|
|
}
|
|
|
|
|
2022-04-15 01:51:09 +00:00
|
|
|
// Specify the over text for the close button
|
|
|
|
setCloseToolTip(key) {
|
|
|
|
this.closeToolTip = key;
|
|
|
|
this.translate();
|
2021-08-26 19:23:18 +00:00
|
|
|
}
|
|
|
|
|
2022-04-15 01:51:09 +00:00
|
|
|
// Specify a new position for the window
|
|
|
|
setLocation(x, y) {
|
|
|
|
Object.assign(this.element.style, {
|
|
|
|
left: Math.round(parseFloat(x) || 0) + "px",
|
|
|
|
top : Math.round(parseFloat(y) || 0) + "px"
|
|
|
|
});
|
2021-08-26 19:23:18 +00:00
|
|
|
}
|
|
|
|
|
2022-04-15 01:51:09 +00:00
|
|
|
// Specify a new size for the window
|
|
|
|
setSize(width, height) {
|
|
|
|
Object.assign(this.client.style, {
|
|
|
|
width : Math.max(Math.round(parseFloat(width ) || 0, 32)) + "px",
|
|
|
|
height: Math.max(Math.round(parseFloat(height) || 0, 32)) + "px"
|
|
|
|
});
|
2021-08-26 19:23:18 +00:00
|
|
|
}
|
|
|
|
|
2022-04-15 01:51:09 +00:00
|
|
|
// Specify the window title text
|
|
|
|
setTitle(key) {
|
|
|
|
this.title = key;
|
|
|
|
this.translate();
|
2021-08-30 02:14:06 +00:00
|
|
|
}
|
|
|
|
|
2022-04-15 01:51:09 +00:00
|
|
|
// Specify whether the component is visible
|
|
|
|
setVisible(visible) {
|
|
|
|
super.setVisible(visible);
|
|
|
|
if (!visible || this.firstShown)
|
|
|
|
return;
|
|
|
|
this.firstShown = true;
|
|
|
|
this.event("firstshow", this);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
///////////////////////////// Package Methods /////////////////////////////
|
|
|
|
|
|
|
|
// Ensure the window is partially visible within its desktop
|
|
|
|
contain() {
|
|
|
|
let valid = this.getValidLocations();
|
|
|
|
this.setLocation(valid.x, valid.y);
|
2021-08-26 19:23:18 +00:00
|
|
|
}
|
|
|
|
|
2022-04-15 01:51:09 +00:00
|
|
|
// Determine the range of valid window coordinates
|
|
|
|
getValidLocations(x, y) {
|
|
|
|
|
|
|
|
// Measure the bounding boxes of the relevant elements
|
|
|
|
let bndClient = this.client .getBoundingClientRect();
|
|
|
|
let bndWindow = this .getBounds ();
|
|
|
|
let bndTitleBar = this.titleBar.getBoundingClientRect();
|
|
|
|
let bndDesktop = this.parent ? this.parent.getBounds() : bndWindow;
|
|
|
|
|
|
|
|
// Compute the minimum and maximum valid window coordinates
|
|
|
|
let ret = {
|
|
|
|
maxX: bndDesktop .width - bndTitleBar.height -
|
|
|
|
bndTitleBar.x + bndWindow .x,
|
|
|
|
maxY: bndDesktop .height - bndClient .y +
|
|
|
|
bndWindow .y,
|
|
|
|
minX: bndTitleBar.height - bndWindow .width +
|
|
|
|
bndWindow .right - bndTitleBar.right,
|
|
|
|
minY: 0
|
|
|
|
};
|
|
|
|
|
|
|
|
// Compute the effective "best" window coordinates
|
|
|
|
ret.x = Math.max(ret.minX, Math.min(ret.maxX,
|
|
|
|
x === undefined ? bndWindow.x - bndDesktop.x : x));
|
|
|
|
ret.y = Math.max(ret.minY, Math.min(ret.maxY,
|
|
|
|
y === undefined ? bndWindow.y - bndDesktop.y : y));
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update the global Toolkit object
|
|
|
|
static setToolkit(toolkit) {
|
|
|
|
Toolkit = toolkit;
|
|
|
|
}
|
2021-08-26 19:23:18 +00:00
|
|
|
|
2022-04-15 01:51:09 +00:00
|
|
|
// Regenerate localized display text
|
|
|
|
translate() {
|
|
|
|
if (!this.titleText)
|
2021-09-02 00:16:22 +00:00
|
|
|
return;
|
2022-04-15 01:51:09 +00:00
|
|
|
this.titleText.innerText = this.gui.translate(this.title, this);
|
|
|
|
if (this.closeToolTip)
|
|
|
|
this.titleClose.setAttribute("title",
|
|
|
|
this.gui.translate(this.closeToolTip, this));
|
|
|
|
else this.titleClose.removeAttribute("title");
|
|
|
|
}
|
2021-08-26 19:23:18 +00:00
|
|
|
|
2022-04-15 01:51:09 +00:00
|
|
|
|
|
|
|
|
|
|
|
///////////////////////////// Private Methods /////////////////////////////
|
|
|
|
|
|
|
|
// Produce a border element and add it to the window
|
|
|
|
border(edge) {
|
|
|
|
let border = this.borders[edge] = document.createElement("div");
|
|
|
|
border.className = "tk tk-" + edge;
|
|
|
|
border.style.cursor = edge + "-resize";
|
|
|
|
border.style.position = "absolute";
|
|
|
|
this.contents.append(border);
|
|
|
|
border.addEventListener(
|
|
|
|
"pointerdown", e=>this.onBorderPointerDown(e, edge));
|
|
|
|
border.addEventListener(
|
|
|
|
"pointermove", e=>this.onBorderPointerMove(e, edge));
|
|
|
|
border.addEventListener(
|
|
|
|
"pointerup" , e=>this.onBorderPointerUp (e, edge));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Compute client bounds when resizing on the east border
|
|
|
|
constrainE(coords, bndClient, bndDesktop, bndWindow, bndTitleBar) {
|
|
|
|
let w = this.drag.startWidth + coords.x - this.drag.clickX;
|
|
|
|
w = Math.max(w, bndTitleBar.height * 4);
|
|
|
|
if (bndClient.x - bndDesktop.x < 0)
|
|
|
|
w = Math.max(w, bndDesktop.x - bndClient.x + bndTitleBar.height);
|
|
|
|
return w;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Compute client bounds when resizing on the north border
|
|
|
|
constrainN(coords, bndClient, bndDesktop, bndWindow, bndTitleBar) {
|
|
|
|
let delta = coords.y - this.drag.clickY;
|
|
|
|
let y = this.drag.startY + delta;
|
|
|
|
let h = this.drag.startHeight - delta;
|
|
|
|
let min = Math.max(0, bndClient.bottom - bndDesktop.bottom);
|
|
|
|
if (h < min) {
|
|
|
|
delta = min - h;
|
|
|
|
h += delta;
|
|
|
|
y -= delta;
|
|
|
|
}
|
|
|
|
if (y < 0) {
|
|
|
|
h += y;
|
|
|
|
y = 0;
|
|
|
|
}
|
|
|
|
return {
|
|
|
|
height: h,
|
|
|
|
y : y
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
// Compute client bounds when resizing on the south border
|
|
|
|
constrainS(coords, bndClient, bndDesktop, bndWindow, bndTitleBar) {
|
|
|
|
return Math.max(0, this.drag.startHeight+coords.y-this.drag.clickY);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Compute client bounds when resizing on the west border
|
|
|
|
constrainW(coords, bndClient, bndDesktop, bndWindow, bndTitleBar) {
|
|
|
|
let delta = coords.x - this.drag.clickX;
|
|
|
|
let x = this.drag.startX + delta;
|
|
|
|
let w = this.drag.startWidth - delta;
|
|
|
|
let min = bndTitleBar.height * 4;
|
|
|
|
if (bndClient.right - bndDesktop.right > 0) {
|
|
|
|
min = Math.max(min, bndClient.right -
|
|
|
|
bndDesktop.right + bndTitleBar.height);
|
|
|
|
}
|
|
|
|
if (w < min) {
|
|
|
|
delta = min - w;
|
|
|
|
w += delta;
|
|
|
|
x -= delta;
|
|
|
|
}
|
|
|
|
return {
|
|
|
|
x : x,
|
|
|
|
width: w
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
// Resize on the east border
|
|
|
|
resizeE(coords, bndClient, bndDesktop, bndWindow, bndTitleBar) {
|
|
|
|
this.setSize(
|
|
|
|
this.constrainE(coords,bndClient,bndDesktop,bndWindow,bndTitleBar),
|
|
|
|
this.drag.startHeight
|
2021-09-02 00:16:22 +00:00
|
|
|
);
|
2022-04-15 01:51:09 +00:00
|
|
|
}
|
2021-09-02 00:16:22 +00:00
|
|
|
|
2022-04-15 01:51:09 +00:00
|
|
|
// Resize on the north border
|
|
|
|
resizeN(coords, bndClient, bndDesktop, bndWindow, bndTitleBar) {
|
|
|
|
let con = this.constrainN(coords,
|
|
|
|
bndClient, bndDesktop, bndWindow, bndTitleBar);
|
|
|
|
this.setBounds(
|
|
|
|
this.drag.startX , con.y,
|
|
|
|
this.drag.startWidth, con.height
|
|
|
|
);
|
|
|
|
}
|
2021-09-02 00:16:22 +00:00
|
|
|
|
2022-04-15 01:51:09 +00:00
|
|
|
// Resize on the northeast border
|
|
|
|
resizeNE(coords, bndClient, bndDesktop, bndWindow, bndTitleBar) {
|
|
|
|
let con = this.constrainN(coords,
|
|
|
|
bndClient, bndDesktop, bndWindow, bndTitleBar);
|
|
|
|
this.setBounds(
|
|
|
|
this.drag.startX, con.y,
|
|
|
|
this.constrainE(coords,bndClient,bndDesktop,bndWindow,bndTitleBar),
|
|
|
|
con.height
|
|
|
|
);
|
2021-08-26 19:23:18 +00:00
|
|
|
}
|
|
|
|
|
2022-04-15 01:51:09 +00:00
|
|
|
// Resize on the northwest border
|
|
|
|
resizeNW(coords, bndClient, bndDesktop, bndWindow, bndTitleBar) {
|
|
|
|
let conN = this.constrainN(coords,
|
|
|
|
bndClient, bndDesktop, bndWindow, bndTitleBar);
|
|
|
|
let conW = this.constrainW(coords,
|
|
|
|
bndClient, bndDesktop, bndWindow, bndTitleBar);
|
|
|
|
this.setBounds(conW.x, conN.y, conW.width, conN.height);
|
|
|
|
}
|
2021-08-26 19:23:18 +00:00
|
|
|
|
2022-04-15 01:51:09 +00:00
|
|
|
// Resize on the south border
|
|
|
|
resizeS(coords, bndClient, bndDesktop, bndWindow, bndTitleBar) {
|
|
|
|
this.setSize(
|
|
|
|
this.drag.startWidth,
|
|
|
|
this.constrainS(coords,bndClient,bndDesktop,bndWindow,bndTitleBar),
|
|
|
|
);
|
|
|
|
}
|
2021-08-26 19:23:18 +00:00
|
|
|
|
2022-04-15 01:51:09 +00:00
|
|
|
// Resize on the southeast border
|
|
|
|
resizeSE(coords, bndClient, bndDesktop, bndWindow, bndTitleBar) {
|
|
|
|
this.setSize(
|
|
|
|
this.constrainE(coords,bndClient,bndDesktop,bndWindow,bndTitleBar),
|
|
|
|
this.constrainS(coords,bndClient,bndDesktop,bndWindow,bndTitleBar)
|
|
|
|
);
|
|
|
|
}
|
2021-08-26 19:23:18 +00:00
|
|
|
|
2022-04-15 01:51:09 +00:00
|
|
|
// Resize on the southwest border
|
|
|
|
resizeSW(coords, bndClient, bndDesktop, bndWindow, bndTitleBar) {
|
|
|
|
let con = this.constrainW(coords,
|
|
|
|
bndClient, bndDesktop, bndWindow, bndTitleBar);
|
|
|
|
this.setBounds(
|
|
|
|
con.x , this.drag.startY,
|
|
|
|
con.width,
|
|
|
|
this.constrainS(coords,bndClient,bndDesktop,bndWindow,bndTitleBar)
|
|
|
|
);
|
|
|
|
}
|
2021-08-26 19:23:18 +00:00
|
|
|
|
2022-04-15 01:51:09 +00:00
|
|
|
// Resize on the west border
|
|
|
|
resizeW(coords, bndClient, bndDesktop, bndWindow, bndTitleBar) {
|
|
|
|
let con = this.constrainW(coords,
|
|
|
|
bndClient, bndDesktop, bndWindow, bndTitleBar);
|
|
|
|
this.setBounds(
|
|
|
|
con.x , this.drag.startY,
|
|
|
|
con.width, this.drag.startHeight
|
|
|
|
);
|
2021-08-26 19:23:18 +00:00
|
|
|
}
|
|
|
|
|
2022-04-15 01:51:09 +00:00
|
|
|
};
|
2021-08-26 19:23:18 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
2022-04-15 01:51:09 +00:00
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
// Desktop //
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
2021-08-26 19:23:18 +00:00
|
|
|
|
2022-04-15 01:51:09 +00:00
|
|
|
// Parent container for encapsulating groups of Windows
|
|
|
|
class Desktop extends Component {
|
2021-08-26 19:23:18 +00:00
|
|
|
|
2022-04-15 01:51:09 +00:00
|
|
|
///////////////////////// Initialization Methods //////////////////////////
|
|
|
|
|
|
|
|
constructor(gui, options) {
|
|
|
|
super(gui, options, {
|
|
|
|
className: "tk tk-desktop",
|
|
|
|
role : "group",
|
|
|
|
tagName : "div",
|
|
|
|
style : {
|
|
|
|
position: "relative"
|
2021-08-26 19:23:18 +00:00
|
|
|
}
|
2022-04-15 01:51:09 +00:00
|
|
|
});
|
2021-08-26 19:23:18 +00:00
|
|
|
|
2022-04-15 01:51:09 +00:00
|
|
|
// Configure event handlers
|
|
|
|
this.addEventListener("resize", e=>this.onResize(e));
|
|
|
|
}
|
2021-08-26 19:23:18 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
2022-04-15 01:51:09 +00:00
|
|
|
///////////////////////////// Event Handlers //////////////////////////////
|
2021-08-26 19:23:18 +00:00
|
|
|
|
2022-04-15 01:51:09 +00:00
|
|
|
// Element resized
|
|
|
|
onResize(e) {
|
|
|
|
for (let wnd of this.children)
|
|
|
|
wnd.contain();
|
|
|
|
}
|
2021-08-26 19:23:18 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
2022-04-15 01:51:09 +00:00
|
|
|
///////////////////////////// Public Methods //////////////////////////////
|
|
|
|
|
|
|
|
// Re-order windows to bring a particular one to the foreground
|
|
|
|
bringToFront(wnd) {
|
2021-08-26 19:23:18 +00:00
|
|
|
|
2022-04-15 01:51:09 +00:00
|
|
|
// The window is not a child of this Desktop
|
|
|
|
let index = this.children.indexOf(wnd);
|
|
|
|
if (index == -1)
|
|
|
|
return;
|
2021-08-26 19:23:18 +00:00
|
|
|
|
2022-04-15 01:51:09 +00:00
|
|
|
// The window is already in the foreground
|
|
|
|
let afters = this.children.slice(index + 1).map(c=>c.element);
|
|
|
|
if (afters.length == 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Record scroll pane positions
|
|
|
|
let scrolls = [];
|
|
|
|
for (let after of afters)
|
|
|
|
for (let scroll of
|
|
|
|
after.querySelectorAll(".tk-scrollpane > .tk-viewport")) {
|
|
|
|
scrolls.push({
|
|
|
|
element: scroll,
|
|
|
|
left : scroll.scrollLeft,
|
|
|
|
top : scroll.scrollTop
|
|
|
|
});
|
2021-08-26 19:23:18 +00:00
|
|
|
}
|
|
|
|
|
2022-04-15 01:51:09 +00:00
|
|
|
// Update window collection
|
|
|
|
wnd.element.before(... this.children.slice(index+1).map(c=>c.element));
|
|
|
|
this.children.splice(index, 1);
|
|
|
|
this.children.push(wnd);
|
|
|
|
|
|
|
|
// Restore scroll pane positions
|
|
|
|
for (let scroll of scrolls) {
|
|
|
|
Object.assign(scroll.element, {
|
|
|
|
scrollLeft: scroll.left,
|
|
|
|
scrollTop : scroll.top
|
|
|
|
});
|
|
|
|
}
|
2021-08-26 19:23:18 +00:00
|
|
|
|
2022-04-15 01:51:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Position a window in the center of the viewable area
|
|
|
|
center(wnd) {
|
2021-08-26 19:23:18 +00:00
|
|
|
|
2022-04-15 01:51:09 +00:00
|
|
|
// The window is not a child of the desktop pane
|
|
|
|
if (this.children.indexOf(wnd) == -1)
|
2021-08-26 19:23:18 +00:00
|
|
|
return;
|
|
|
|
|
2022-04-15 01:51:09 +00:00
|
|
|
let bndDesktop = this.getBounds();
|
|
|
|
let bndWindow = wnd .getBounds();
|
|
|
|
wnd.setLocation(
|
|
|
|
Math.max(0, Math.round((bndDesktop.width - bndWindow.width) / 2)),
|
|
|
|
Math.max(0, Math.round((bndDesktop.height-bndWindow.height) / 2))
|
|
|
|
);
|
2021-08-26 19:23:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
};
|
2022-04-15 01:51:09 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export { Desktop, Window };
|