403 lines
12 KiB
JavaScript
403 lines
12 KiB
JavaScript
"use strict";
|
|
|
|
// Movable, sizeable child window
|
|
Toolkit.Window = class Window extends Toolkit.Panel {
|
|
|
|
// Object constructor
|
|
constructor(application, options) {
|
|
super(application, options);
|
|
options = options || {};
|
|
|
|
// Configure instance fields
|
|
this.closeListeners = [];
|
|
this.dragBounds = null;
|
|
this.dragClient = null;
|
|
this.dragCursor = { x: 0, y: 0 };
|
|
this.dragEdge = null;
|
|
this.dragPointer = null;
|
|
this.initialCenter = "center" in options ? !!options.center : false;
|
|
this.lastFocus = this.element;
|
|
this.shown = this.visible;
|
|
|
|
// Configure element
|
|
this.setLayout("grid", { columns: "auto" });
|
|
this.setRole("dialog");
|
|
this.setLocation(0, 0);
|
|
this.element.style.position = "absolute";
|
|
this.element.setAttribute("aria-modal", "false");
|
|
this.element.setAttribute("focus" , "false");
|
|
this.element.setAttribute("tabindex" , "0" );
|
|
this.element.addEventListener(
|
|
"blur" , e=>this.onblur (e), { capture: true });
|
|
this.element.addEventListener(
|
|
"focus", e=>this.onfocus(e), { capture: true });
|
|
this.element.addEventListener("keydown" , e=>this.onkeydown (e));
|
|
this.element.addEventListener("pointerdown", e=>this.onpointerdown(e));
|
|
this.element.addEventListener("pointermove", e=>this.onpointermove(e));
|
|
this.element.addEventListener("pointerup" , e=>this.onpointerup (e));
|
|
|
|
// Primary visible container
|
|
this.body = this.add(this.newPanel({
|
|
layout : "grid",
|
|
overflowX: "visible",
|
|
overflowY: "visible",
|
|
rows : "max-content auto"
|
|
}));
|
|
this.body.element.setAttribute("name", "body");
|
|
|
|
// Title bar
|
|
this.titleBar = this.body.add(this.newPanel({
|
|
layout : "grid",
|
|
columns: "max-content auto max-content",
|
|
hollow : false
|
|
}));
|
|
this.titleBar.element.setAttribute("name", "title-bar");
|
|
|
|
// Title bar icon
|
|
this.titleIcon = this.titleBar.add(this.newPanel());
|
|
this.titleIcon.element.setAttribute("name", "icon");
|
|
this.titleIcon.element.setAttribute("aria-hidden", "true");
|
|
|
|
// Title bar title
|
|
this.title = this.titleBar.add(this.newLabel({
|
|
localized: true,
|
|
text : options.title
|
|
}));
|
|
this.title.element.setAttribute("name", "title");
|
|
this.title.setProperty("sim", "");
|
|
|
|
// Title bar close
|
|
this.titleClose = this.titleBar.add(this.newButton({
|
|
toolTip: "{app.close}"
|
|
}));
|
|
this.titleClose.element.setAttribute("name", "close");
|
|
this.titleClose.element.setAttribute("aria-hidden", "true");
|
|
this.titleClose.addClickListener(e=>this.onclose(e));
|
|
|
|
// Client area
|
|
this.client = this.body.add(this.newPanel());
|
|
this.client.element.setAttribute("name", "client");
|
|
this.setSize(options.width, options.height);
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////// Public Methods //////////////////////////////
|
|
|
|
// Add a callback for close events
|
|
addCloseListener(listener) {
|
|
if (this.closeListeners.indexOf(listener) == -1)
|
|
this.closeListeners.push(listener);
|
|
}
|
|
|
|
// Retrieve the window's title text
|
|
getTitle() {
|
|
return this.title.getText();
|
|
}
|
|
|
|
// Specify the height of the component
|
|
setHeight(height, minimum) {
|
|
this.client && this.client.setHeight(height, minimum);
|
|
}
|
|
|
|
// Specify the window's title text
|
|
setTitle(title) {
|
|
this.title.setText(title);
|
|
}
|
|
|
|
// Specify whether the component is visible
|
|
setVisible(visible, focus) {
|
|
super.setVisible(visible);
|
|
if (this.client === undefined)
|
|
return;
|
|
if (visible)
|
|
this.contain();
|
|
if (focus)
|
|
this.focus();
|
|
if (!this.shown)
|
|
this.firstShow();
|
|
}
|
|
|
|
// Specify the width of the component
|
|
setWidth(width, minimum) {
|
|
this.client && this.client.setWidth(
|
|
Math.max(64, width || 0), minimum);
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////// Package Methods /////////////////////////////
|
|
|
|
// Request focus on the appropriate element
|
|
focus() {
|
|
if (this.lastFocus != this)
|
|
this.lastFocus.focus();
|
|
else this.element.focus();
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////// Private Methods /////////////////////////////
|
|
|
|
// Position the window in the center of the desktop
|
|
center() {
|
|
let bounds = this.getBounds();
|
|
let desktop = this.parent.getBounds();
|
|
this.contain(
|
|
Math.floor((desktop.width - bounds.width ) / 2),
|
|
Math.ceil ((desktop.height - bounds.height) / 2),
|
|
desktop, bounds
|
|
);
|
|
}
|
|
|
|
// Position the window using a tentative location in the desktop
|
|
contain(x, y, desktop, bounds, client) {
|
|
desktop = desktop || this.parent.getBounds();
|
|
bounds = bounds || this.getBounds();
|
|
client = client || this.client.getBounds();
|
|
if (x === undefined)
|
|
x = bounds.x - desktop.x;
|
|
if (y === undefined)
|
|
y = bounds.y - desktop.y;
|
|
|
|
// Restrict window position
|
|
x = Math.min(x, desktop.width - 16);
|
|
x = Math.max(x, -bounds.width + 16);
|
|
y = Math.min(y, desktop.height - (client.y - bounds.y));
|
|
y = Math.max(y, 0);
|
|
|
|
// Configure element
|
|
this.setLocation(x, y);
|
|
}
|
|
|
|
// Detect where in the window the pointer is
|
|
edge(e, bounds) {
|
|
bounds = bounds || this.getBounds();
|
|
let x = e.x - bounds.x;
|
|
let y = e.y - bounds.y;
|
|
if (y < 3) {
|
|
if (x < 8) return "nw";
|
|
if (x < bounds.width - 8) return "n" ;
|
|
return "ne";
|
|
}
|
|
if (y >= bounds.height - 3) {
|
|
if (x < 8) return "sw";
|
|
if (x < bounds.width - 8) return "s" ;
|
|
return "se";
|
|
}
|
|
if (x < 3) {
|
|
if (y < 8) return "nw";
|
|
if (y < bounds.height - 8) return "w" ;
|
|
return "sw";
|
|
}
|
|
if (x >= bounds.width - 3) {
|
|
if (y < 8) return "ne";
|
|
if (y < bounds.height - 8) return "e" ;
|
|
return "se";
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// The window is being displayed for the first time
|
|
firstShow() {
|
|
this.shown = true;
|
|
}
|
|
|
|
// Focus lost event capture
|
|
onblur(e) {
|
|
if (!this.contains(e.relatedTarget))
|
|
this.element.setAttribute("focus", "false");
|
|
}
|
|
|
|
// Window close
|
|
onclose(e) {
|
|
for (let listener of this.closeListeners)
|
|
listener(e);
|
|
}
|
|
|
|
// Focus gained event capture
|
|
onfocus(e) {
|
|
this.element.setAttribute("focus", "true");
|
|
let target = e.target;
|
|
if (target == this.element)
|
|
target = this.lastFocus;
|
|
this.lastFocus = target;
|
|
if (target != e.target)
|
|
target.focus();
|
|
this.parent.bringToFront(this);
|
|
}
|
|
|
|
// Key pressed event handler
|
|
onkeydown(e) {
|
|
|
|
// Only listening for Escape while dragging
|
|
if (e.key != "Escape" || this.dragPointer === null)
|
|
return;
|
|
|
|
// Restore the window's position
|
|
let desktop = this.parent.getBounds();
|
|
this.setLocation(
|
|
this.dragBounds.x - desktop.x,
|
|
this.dragBounds.y - desktop.y
|
|
);
|
|
|
|
// Restore the window's size
|
|
if (this.dragEdge != null)
|
|
this.setSize(this.dragClient.width, this.dragClient.height);
|
|
|
|
// Configure instance fields
|
|
this.element.releasePointerCapture(this.dragPointer);
|
|
this.dragPointer = null;
|
|
}
|
|
|
|
// Pointer down event handler
|
|
onpointerdown(e) {
|
|
|
|
// Configure event
|
|
e.stopPropagation();
|
|
|
|
// Error checking
|
|
if (
|
|
e.button != 0 ||
|
|
this.element.hasPointerCapture(e.pointerId)
|
|
) return;
|
|
|
|
// Configure instance fields
|
|
this.dragBounds = this.getBounds();
|
|
this.dragClient = this.client.getBounds();
|
|
this.dragCursor.x = e.x;
|
|
this.dragCursor.y = e.y;
|
|
this.dragEdge = this.edge(e, this.dragBounds);
|
|
|
|
// Don't perform a move if the cursor isn't in the title bar
|
|
let title = this.titleBar.getBounds();
|
|
if (this.dragEdge == null && e.y >= title.y + title.height)
|
|
return;
|
|
|
|
// Configure element
|
|
this.element.setPointerCapture(e.pointerId);
|
|
this.dragPointer = e.pointerId;
|
|
}
|
|
|
|
// Pointer move event handler
|
|
onpointermove(e) {
|
|
|
|
// Configure event
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
|
|
// Not dragging: set the cursor based on pointer location
|
|
if (!this.element.hasPointerCapture(e.pointerId)) {
|
|
let region = this.edge(e);
|
|
if (region == null)
|
|
this.element.style.removeProperty("cursor");
|
|
else this.element.style.cursor = region + "-resize";
|
|
return;
|
|
}
|
|
|
|
// Working variables
|
|
let rX = e.x - this.dragCursor.x;
|
|
let rY = e.y - this.dragCursor.y;
|
|
let bounds = this.getBounds();
|
|
let desktop = this.parent.getBounds();
|
|
let client = this.client.getBounds();
|
|
|
|
// Move the window
|
|
if (this.dragEdge == null) {
|
|
this.contain(
|
|
this.dragBounds.x - desktop.x + rX,
|
|
this.dragBounds.y - desktop.y + rY,
|
|
desktop, bounds, client
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Resizing on the north edge
|
|
if (this.dragEdge.startsWith("n")) {
|
|
let maxTop = desktop.height - client.y + bounds.y;
|
|
let top = this.dragBounds.y - desktop.y + rY;
|
|
let height = this.dragClient.height - rY;
|
|
|
|
// Restrict window bounds
|
|
if (top > maxTop) {
|
|
height -= maxTop - top;
|
|
top = maxTop;
|
|
}
|
|
if (top < 0) {
|
|
height += top;
|
|
top = 0;
|
|
}
|
|
if (height < 0) {
|
|
top += height;
|
|
height = 0;
|
|
}
|
|
|
|
// Configure element
|
|
this.setTop (top );
|
|
this.setHeight(height);
|
|
}
|
|
|
|
// Resizing on the west edge
|
|
if (this.dragEdge.endsWith("w")) {
|
|
let maxLeft = desktop.width - 16;
|
|
let left = this.dragBounds.x - desktop.x + rX;
|
|
let width = this.dragClient.width - rX;
|
|
|
|
// Restrict window bounds
|
|
if (left > maxLeft) {
|
|
width -= maxLeft - left;
|
|
left = maxLeft;
|
|
}
|
|
if (width < 64) {
|
|
left -= 64 - width;
|
|
width = 64;
|
|
}
|
|
|
|
// Configure element
|
|
this.setLeft (left );
|
|
this.setWidth(width);
|
|
}
|
|
|
|
// Resizing on the east edge
|
|
if (this.dragEdge.endsWith("e")) {
|
|
let width = this.dragClient.width + rX;
|
|
|
|
// Restrict window bounds
|
|
width = Math.max(64, width);
|
|
width = Math.max(width, -this.dragClient.x + 16);
|
|
|
|
// Configure element
|
|
this.setWidth(width);
|
|
}
|
|
|
|
// Resizing on the south edge
|
|
if (this.dragEdge.startsWith("s")) {
|
|
let height = this.dragClient.height + rY;
|
|
|
|
// Restrict window bounds
|
|
height = Math.max(0, height);
|
|
|
|
// Configure element
|
|
this.setHeight(height);
|
|
}
|
|
|
|
}
|
|
|
|
// Pointer up event handler
|
|
onpointerup(e) {
|
|
|
|
// Configure event
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
|
|
// Error checking
|
|
if (!this.element.hasPointerCapture(e.pointerId))
|
|
return;
|
|
|
|
// Configure element
|
|
this.element.releasePointerCapture(e.pointerId);
|
|
this.dragPointer = null;
|
|
}
|
|
|
|
};
|