pvbemu/app/toolkit/Panel.js

350 lines
11 KiB
JavaScript

"use strict";
// Box that can contain other components
Toolkit.Panel = class Panel extends Toolkit.Component {
// Object constructor
constructor(application, options) {
super(application, "div", options);
options = options || {};
// Configure instance fields
this.alignCross = "start";
this.alignMain = "start";
this.application = application;
this.children = [];
this.columns = null;
this.direction = "row";
this.focusable = "focusable" in options ? !!options.focusable:false;
this.hollow = "hollow" in options ? !!options.hollow : true;
this.layout = null;
this.name = options.name || "";
this.overflowX = options.overflowX || "hidden";
this.overflowY = options.overflowY || "hidden";
this.rows = null;
this.wrap = false;
// Configure properties
this.setFocusable(this.focusable);
this.setHollow (this.hollow);
this.setLayout (options.layout || null, options);
this.setName (this.name);
this.setOverflow (this.overflowX, this.overflowY);
if (this.application && this.focusable)
this.application.addComponent(this);
}
///////////////////////////// Public Methods //////////////////////////////
// Add a component as a child of this container
add(component, index) {
// Determine the ordinal position of the element within the container
index = !(typeof index == "number") ? this.children.length :
Math.floor(Math.min(Math.max(0, index), this.children.length));
// Add the component to the container
let ref = this.children[index] || null;
component.parent = this;
this.element.insertBefore(component.element,
ref == null ? null : ref.element);
this.children.splice(index, 0, component);
return component;
}
// Request focus on the appropriate element
focus() {
if (this.focusable)
this.element.focus();
}
// Retrieve the component's accessible name
getName() {
return this.name;
}
// Determine whether the component is focusable
isFocusable() {
return this.focusable;
}
// Determine whether the component is hollow
isHollow() {
return this.hollow;
}
// Create a Button and associate it with the application
newButton(options) {
return new Toolkit.Button(this.application, options);
}
// Create a CheckBox and associate it with the application
newCheckBox(options) {
return new Toolkit.CheckBox(this.application, options);
}
// Create a Label and associate it with the application
newLabel(options) {
return new Toolkit.Label(this.application, options);
}
// Create a MenuBar and associate it with the application
newMenuBar(options) {
return new Toolkit.MenuBar(this.application, options);
}
// Create a Panel and associate it with the application
newPanel(options) {
return new Toolkit.Panel(this.application, options);
}
// Create a RadioButton and associate it with the application
newRadioButton(options) {
return new Toolkit.RadioButton(this.application, options);
}
// Create a Splitter and associate it with the application
newSplitter(options) {
return new Toolkit.Splitter(this.application, options);
}
// Create a TextBox and associate it with the application
newTextBox(options) {
return new Toolkit.TextBox(this.application, options);
}
// Create a Window and associate it with the application
newWindow(options) {
return new Toolkit.Window(this.application, options);
}
// Determine the index of the next visible child
nextChild(index) {
for (let x = 0; x <= this.children.length; x++) {
index = (index + 1) % this.children.length;
let comp = this.children[index];
if (comp.isVisible())
return index;
}
return -1;
}
// Determine the index of the previous visible child
previousChild(index) {
for (let x = 0; x <= this.children.length; x++) {
index = (index + this.children.length - 1) % this.children.length;
let comp = this.children[index];
if (comp.isVisible())
return index;
}
return -1;
}
// Remove a component from the container
remove(component) {
// Locate the component in the children
let index = this.children.indexOf(component);
if (index == -1)
return;
// Remove the component
component.parent = null;
component.element.remove();
this.children.splice(index, 1);
}
// Specify whether the component is focusable
setFocusable(focusable) {
this.focusable = focusable = !!focusable;
if (focusable) {
this.element.setAttribute("tabindex", "0");
this.application && this.application.addComponent(this);
} else {
this.element.removeAttribute("aria-label");
this.element.removeAttribute("tabindex");
this.application && this.application.removeComponent(this);
}
}
// Specify whether the component is hollow
setHollow(hollow) {
this.hollow = hollow = !!hollow;
if (hollow) {
this.element.style.minHeight = "0";
this.element.style.minWidth = "0";
} else {
this.element.style.removeProperty("min-height");
this.element.style.removeProperty("min-width" );
}
}
// Configure the element's layout
setLayout(layout, options) {
// Configure instance fields
this.layout = layout;
// Processing by layout
options = options || {};
switch (layout) {
case "block" : this.setBlockLayout (options); break;
case "desktop": this.setDesktopLayout(options); break;
case "flex" : this.setFlexLayout (options); break;
case "grid" : this.setGridLayout (options); break;
default : this.setNullLayout (options); break;
}
}
// Specify the component's accessible name
setName(name) {
this.name = name || "";
if (this.focusable)
this.localize();
}
// Configure the panel's overflow scrolling behavior
setOverflow(x, y) {
this.element.style.overflowX = this.overflowX = x || "hidden";
this.element.style.overflowY = this.overflowY = y || this.overflowX;
}
// Specify the semantic role of the panel
setRole(role) {
if (!role)
this.element.removeAttribute("role");
else this.element.setAttribute("role", "" + role);
}
///////////////////////////// Package Methods /////////////////////////////
// Move a window to the foreground
bringToFront(wnd) {
for (let child of this.children)
child.element.style.zIndex = child == wnd ? "1" : "0";
}
// Update display text with localized strings
localize() {
let name = this.name;
if (this.application)
name = this.application.translate(name, this);
this.element.setAttribute("aria-label", name);
}
///////////////////////////// Private Methods /////////////////////////////
// Resize event handler
onresize(desktop) {
// Error checking
if (this.layout != "desktop")
return;
// Ensure all child windows are visible in the viewport
for (let wnd of this.children) {
if (!wnd.isVisible())
continue;
let bounds = wnd.getBounds();
wnd.contain(
bounds.x - desktop.x,
bounds.y - desktop.y,
desktop, bounds
);
}
}
// Configure a block layout
setBlockLayout(options) {
// Configure instance fields
this.layout = "block";
// Configure element
this.setDisplay("block");
}
// Configure a desktop layout
setDesktopLayout(options) {
// Configure instance fields
this.layout = "desktop";
// Configure element
this.setDisplay("block");
this.element.style.position = "relative";
if (this.resizeObserver == null)
this.addResizeListener(b=>this.onresize(b));
}
// Configure a flex layout
setFlexLayout(options) {
// Configure instance fields
this.alignCross = options.alignCross || "start";
this.alignMain = options.alignMain || "start";
this.direction = options.direction || this.direction;
this.layout = "flex";
this.wrap = !!options.wrap;
// Working variables
let alignCross = this.alignCross;
let alignMain = this.alignMain;
if (alignCross == "start" || alignCross == "end")
alignCross = "flex-" + alignCross;
if (alignMain == "start" || alignMain == "end")
alignMain = "flex-" + alignMain;
// Configure element
this.setDisplay("flex");
this.element.style.alignItems = alignCross;
this.element.style.flexDirection = this.direction;
this.element.style.justifyContent = alignMain;
this.element.style.flexWrap = this.wrap ? "wrap" : "nowrap";
}
// Configure a grid layout
setGridLayout(options) {
// Configure instance fields
this.columns = options.columns || null;
this.layout = "grid";
this.rows = options.rows || null;
// Configure element
this.setDisplay("grid");
if (this.columns == null)
this.element.style.removeProperty("grid-template-columns");
else this.element.style.gridTemplateColumns = this.columns;
if (this.rows == null)
this.element.style.removeProperty("grid-template-rows");
else this.element.style.gridTemplateRows = this.rows;
}
// Configure a null layout
setNullLayout(options) {
// Configure instance fields
this.layout = null;
// Configure element
this.setDisplay(null);
this.element.style.removeProperty("align-items" );
this.element.style.removeProperty("flex-wrap" );
this.element.style.removeProperty("grid-template-columns");
this.element.style.removeProperty("grid-template-rows" );
this.element.style.removeProperty("justify-content" );
this.element.style.removeProperty("flex-direction" );
}
};