pvbemu/app/toolkit/Window.js

383 lines
12 KiB
JavaScript
Raw Normal View History

2021-08-26 19:23:18 +00:00
"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.dragBounds = null;
this.dragCursor = { x: 0, y: 0 };
this.dragEdge = null;
this.lastFocus = this.element;
this.title = options.title || "";
// Configure element
this.setLayout("flex", {
alignCross: "stretch",
direction : "column",
overflowX : "visible",
overflowY : "visible"
});
this.setRole("dialog");
this.element.style.position = "absolute";
this.element.setAttribute("aria-modal", "false");
this.element.setAttribute("focus" , "false");
this.element.setAttribute("tabindex" , "-1" );
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));
// Configure body container
this.body = this.add(this.newPanel({
layout : "flex",
alignCross: "stretch",
direction : "column",
overflowX : "visible",
overflowY : "visible"
}));
this.body.element.style.flexGrow = "1";
this.body.element.style.margin = "3px";
this.body.element.setAttribute("name", "body");
// Configure title bar
this.titleBar = this.body.add(this.newPanel({
layout : "flex",
alignCross: "center",
direction : "row",
overflowX : "visible",
overflowY : "visible"
}));
this.titleBar.element.setAttribute("name", "title-bar");
// Configure title icon element
this.titleIcon = this.titleBar.add(this.newPanel({}));
this.titleIcon.element.setAttribute("name", "title-icon");
this.titleIcon.element.style.removeProperty("min-width");
// Configure title text element
this.titleElement = this.titleBar.add(this.newPanel({}));
this.titleElement.element.setAttribute("name", "title");
this.titleElement.element.style.cursor = "default";
this.titleElement.element.style.flexGrow = "1";
this.titleElement.element.style.userSelect = "none";
this.element.setAttribute("aria-labelledby", this.titleElement.id);
// Configure title close element
this.titleCloseBox = this.titleBar.add(this.newPanel({}));
this.titleCloseBox.element.setAttribute("name", "title-close-box");
this.titleCloseBox.element.style.removeProperty("min-width");
this.titleClose = this.titleCloseBox.add(this.newButton({
focusable: false,
name : "{app.close}",
toolTip : "{app.close}"
}));
this.titleClose.element.setAttribute("name", "title-close");
// Configure client area
this.client = this.body.add(this.newPanel({}));
this.client.element.style.flexGrow = "1";
this.client.element.setAttribute("name", "client");
this.client.element.addEventListener(
"pointerdown", e=>this.onclientdown(e));
// Configure properties
this.setTitle(this.title);
application.addComponent(this);
}
///////////////////////////// Public Methods //////////////////////////////
// Specify the size of the client rectangle in pixels
setClientSize(width, height) {
let bounds = this.getBounds();
let client = this.client.getBounds();
this.setSize(
width + bounds.width - client.width,
height + bounds.height - client.height
);
}
// Specify the window's title text
setTitle(title) {
this.title = title || "";
this.localize();
}
///////////////////////////// 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 using a tentative location in the desktop
contain(desktop, bounds, x, y, client) {
bounds = bounds || this.getBounds();
client = client || this.client.getBounds();
// 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;
}
// Update display text with localized strings
localize() {
let title = this.title;
if (this.application)
title = this.application.translate(title, this);
this.titleElement.element.innerText = title;
}
// Focus lost event capture
onblur(e) {
if (!this.contains(e.relatedTarget))
this.element.setAttribute("focus", "false");
}
// Client pointer down event handler
onclientdown(e) {
e.preventDefault();
e.stopPropagation();
this.focus();
}
// Focus gained event capture
onfocus(e) {
// Configure element
this.element.setAttribute("focus", "true");
// Working variables
let target = e.target;
// Delegate focus to the most recently focused component
if (target == this.element)
target = this.lastFocus;
// The component is not visible: focus on self instead
if ("component" in target && !target.component.isVisible())
target = this.element;
// Configure instance fields
this.lastFocus = target;
// Transfer focus to the correct component
if (target != e.target)
target.focus();
}
// Key down event handler
onkeydown(e) {
// Processing by key
switch (e.key) {
default: return;
}
// Configure event
e.preventDefault();
e.stopPropagation();
}
// Pointer down event handler
onpointerdown(e) {
// Configure event
e.preventDefault();
e.stopPropagation();
// Configure element
this.focus();
// Error checking
if (
e.button != 0 ||
this.element.hasPointerCapture(e.pointerId)
) return;
// Configure instance fields
this.dragBounds = this.getBounds();
this.dragEdge = this.edge(e, this.dragBounds);
this.dragCursor.x = e.x;
this.dragCursor.y = e.y;
// Configure element
this.element.setPointerCapture(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();
let minHeight = bounds.height - client.height;
// Move the window
if (this.dragEdge == null) {
this.contain(
desktop,
bounds,
this.dragBounds.x - desktop.x + rX,
this.dragBounds.y - desktop.y + rY,
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.dragBounds.height - rY;
// Restrict window bounds
if (top > maxTop) {
height -= maxTop - top;
top = maxTop;
}
if (top < 0) {
height += top;
top = 0;
}
if (height < minHeight) {
top -= minHeight - height;
height = minHeight;
}
// 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.dragBounds.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.dragBounds.width + rX;
// Restrict window bounds
width = Math.max(64, width);
width = Math.max(width, -this.dragBounds.x + 16);
// Configure element
this.setWidth(width);
}
// Resizing on the south edge
if (this.dragEdge.startsWith("s")) {
let height = this.dragBounds.height + rY;
// Restrict window bounds
height = Math.max(minHeight, 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);
}
};