Renamed elements to components

This commit is contained in:
Lucas Dower 2023-03-21 18:42:59 +00:00
parent ac3bcc637c
commit dd5227ad6e
No known key found for this signature in database
GPG Key ID: B3EE6B8499593605
21 changed files with 1159 additions and 1192 deletions

View File

@ -8,12 +8,10 @@ import { TExporters } from './exporters/exporters';
import { MaterialMapManager } from './material-map';
import { MaterialType } from './mesh';
import { MeshType, Renderer } from './renderer';
import { PlaceholderComponent } from './ui/components/placeholder';
import { SolidMaterialComponent } from './ui/components/solid_material';
import { TexturedMaterialComponent } from './ui/components/textured_material';
import { AppConsole, TMessage } from './ui/console';
import { ButtonElement } from './ui/elements/button';
import { CheckboxElement } from './ui/elements/checkbox';
import { PlaceholderElement } from './ui/elements/placeholder_element';
import { SolidMaterialElement } from './ui/elements/solid_material_element';
import { TexturedMaterialElement } from './ui/elements/textured_material_element';
import { UI } from './ui/layout';
import { ColourSpace, EAction } from './util';
import { ASSERT } from './util/error_util';
@ -152,7 +150,7 @@ export class AppContext {
}
private async _import(): Promise<TWorkerJob> {
const uiElements = this._ui.layout.import.elements;
const uiElements = this._ui.layout.import.components;
AppConsole.info('Importing mesh...');
const payload: TToWorkerMessage = {
@ -230,41 +228,39 @@ export class AppContext {
}
private _updateMaterialsAction() {
this._ui.layoutDull['materials'].elements = {};
this._ui.layoutDull['materials'].elementsOrder = [];
this._ui.layoutDull['materials'].components = {};
this._ui.layoutDull['materials'].componentOrder = [];
if (this._materialManager.materials.size == 0) {
this._ui.layoutDull['materials'].elements[`placeholder_element`] = new PlaceholderElement('No materials loaded');
this._ui.layoutDull['materials'].elementsOrder.push(`placeholder_element`);
this._ui.layoutDull['materials'].components[`placeholder_element`] = new PlaceholderComponent('No materials loaded');
this._ui.layoutDull['materials'].componentOrder.push(`placeholder_element`);
} else {
this._materialManager.materials.forEach((material, materialName) => {
if (material.type === MaterialType.solid) {
this._ui.layoutDull['materials'].elements[`mat_${materialName}`] = new SolidMaterialElement(materialName, material)
this._ui.layoutDull['materials'].components[`mat_${materialName}`] = new SolidMaterialComponent(materialName, material)
.setLabel(materialName)
.onChangeTypeDelegate(() => {
this._materialManager.changeMaterialType(materialName, MaterialType.textured);
this._updateMaterialsAction();
});
} else {
this._ui.layoutDull['materials'].elements[`mat_${materialName}`] = new TexturedMaterialElement(materialName, material)
this._ui.layoutDull['materials'].components[`mat_${materialName}`] = new TexturedMaterialComponent(materialName, material)
.setLabel(materialName)
.onChangeTypeDelegate(() => {
console.log('on change type');
this._materialManager.changeMaterialType(materialName, MaterialType.solid);
this._updateMaterialsAction();
})
.onChangeTransparencyTypeDelegate((newTransparency) => {
console.log('on change trans');
this._materialManager.changeTransparencyType(materialName, newTransparency);
this._updateMaterialsAction();
});
}
this._ui.layoutDull['materials'].elementsOrder.push(`mat_${materialName}`);
this._ui.layoutDull['materials'].componentOrder.push(`mat_${materialName}`);
});
}
this._ui.refreshSubcomponents(this._ui.layoutDull['materials']);
this._ui.refreshComponents(EAction.Materials);
}
private _renderMesh(): TWorkerJob {
@ -302,7 +298,7 @@ export class AppContext {
private _voxelise(): TWorkerJob {
AppConsole.info('Loading voxel mesh...');
const uiElements = this._ui.layout.voxelise.elements;
const uiElements = this._ui.layout.voxelise.components;
const payload: TToWorkerMessage = {
action: 'Voxelise',
@ -333,7 +329,7 @@ export class AppContext {
AppConsole.info('Rendering voxel mesh...');
}
const uiElements = this._ui.layout.voxelise.elements;
const uiElements = this._ui.layout.voxelise.components;
const payload: TToWorkerMessage = {
action: 'RenderNextVoxelMeshChunk',
@ -374,7 +370,7 @@ export class AppContext {
}
private _assign(): (TWorkerJob | undefined) {
const uiElements = this._ui.layout.assign.elements;
const uiElements = this._ui.layout.assign.components;
if (uiElements.blockPalette.getValue().count() <= 0) {
AppConsole.error('No blocks selected');
@ -417,7 +413,7 @@ export class AppContext {
AppConsole.info('Rendering block mesh...');
}
const uiElements = this._ui.layout.assign.elements;
const uiElements = this._ui.layout.assign.components;
const payload: TToWorkerMessage = {
action: 'RenderNextBlockMeshChunk',
@ -459,7 +455,7 @@ export class AppContext {
private _export(): (TWorkerJob | undefined) {
AppConsole.info('Exporting structure...');
const exporterID: TExporters = this._ui.layout.export.elements.export.getValue();
const exporterID: TExporters = this._ui.layout.export.components.export.getValue();
const filepath = '';
const payload: TToWorkerMessage = {

View File

@ -9,9 +9,9 @@ export interface IInterfaceItem {
/**
* The base UI class from which user interactable DOM elements are built from.
* Each `BaseUIElement` can be enabled/disabled.
* Each `BaseComponent` can be enabled/disabled.
*/
export abstract class BaseUIElement<T> implements IInterfaceItem {
export abstract class BaseComponent<T> implements IInterfaceItem {
private _id: string;
private _isEnabled: boolean;
private _isHovered: boolean;
@ -76,7 +76,7 @@ export abstract class BaseUIElement<T> implements IInterfaceItem {
/**
* The actual HTML that represents this UI element. It is recommended to
* give the outermost element that ID generated for this BaseUIElement so
* give the outermost element that ID generated for this BaseComponent so
* that `getElement()` returns all elements created here.
*/
public abstract generateHTML(): string;
@ -93,7 +93,7 @@ export abstract class BaseUIElement<T> implements IInterfaceItem {
}
/**
* Returns the actual DOM element that this BaseUIElement refers to.
* Returns the actual DOM element that this BaseComponent refers to.
* Calling this before the element is created (i.e. before `generateHTML`)
* is called will throw an error.
*/
@ -102,7 +102,7 @@ export abstract class BaseUIElement<T> implements IInterfaceItem {
}
/**
* Each BaseUIElement is assignd an ID that can be used a DOM element with.
* Each BaseComponent is assignd an ID that can be used a DOM element with.
*/
protected _getId() {
return this._id;

View File

@ -1,123 +1,123 @@
import { UIUtil } from '../../util/ui_util';
import { BaseUIElement } from './base_element';
export class ButtonElement extends BaseUIElement<HTMLDivElement> {
private _label: string;
private _onClick: () => void;
public constructor() {
super();
this._label = 'Unknown';
this._onClick = () => { };
}
/**
* Sets the delegate that is called when this button is clicked.
*/
public setOnClick(delegate: () => void) {
this._onClick = delegate;
return this;
}
/**
* Sets the label of this button.
*/
public setLabel(label: string) {
this._label = label;
return this;
}
/**
* Override the current label with a new value.
*/
public setLabelOverride(label: string) {
this._getElement().innerHTML = label;
return this;
}
/**
* Remove the label override and set the label back to its default
*/
public removeLabelOverride() {
this._getElement().innerHTML = this._label;
return this;
}
/**
* Start the loading animation
*/
public startLoading() {
this._getElement().classList.add('button-loading');
return this;
}
/**
* Set the progress bar progress.
* @param progress A number between 0.0 and 1.0 inclusive.
*/
public setProgress(progress: number) {
const progressBarElement = UIUtil.getElementById(this._getProgressBarId());
progressBarElement.style.width = `${progress * 100}%`;
return this;
}
/**
* Stop the loading animation
*/
public stopLoading() {
this._getElement().classList.remove('button-loading');
return this;
}
public override generateHTML() {
return `
<div class="container-button">
<div class="struct-prop button" id="${this._getId()}">
<div class="button-label">${this._label}</div>
<div class="button-progress" id="${this._getProgressBarId()}"></div>
</div>
</div>
`;
}
public override registerEvents(): void {
this._getElement().addEventListener('click', () => {
if (this.enabled) {
this._onClick?.();
}
});
this._getElement().addEventListener('mouseenter', () => {
this._setHovered(true);
this._updateStyles();
});
this._getElement().addEventListener('mouseleave', () => {
this._setHovered(false);
this._updateStyles();
});
}
protected override _onEnabledChanged() {
this._updateStyles();
}
public override finalise(): void {
this._updateStyles();
}
/**
* Gets the ID of the DOM element for the button's progress bar.
*/
private _getProgressBarId() {
return this._getId() + '-progress';
}
protected _updateStyles(): void {
UIUtil.updateStyles(this._getElement(), {
isActive: true,
isEnabled: this.enabled,
isHovered: this.hovered,
});
}
}
import { UIUtil } from '../../util/ui_util';
import { BaseComponent } from './base';
export class ButtonComponent extends BaseComponent<HTMLDivElement> {
private _label: string;
private _onClick: () => void;
public constructor() {
super();
this._label = 'Unknown';
this._onClick = () => { };
}
/**
* Sets the delegate that is called when this button is clicked.
*/
public setOnClick(delegate: () => void) {
this._onClick = delegate;
return this;
}
/**
* Sets the label of this button.
*/
public setLabel(label: string) {
this._label = label;
return this;
}
/**
* Override the current label with a new value.
*/
public setLabelOverride(label: string) {
this._getElement().innerHTML = label;
return this;
}
/**
* Remove the label override and set the label back to its default
*/
public removeLabelOverride() {
this._getElement().innerHTML = this._label;
return this;
}
/**
* Start the loading animation
*/
public startLoading() {
this._getElement().classList.add('button-loading');
return this;
}
/**
* Set the progress bar progress.
* @param progress A number between 0.0 and 1.0 inclusive.
*/
public setProgress(progress: number) {
const progressBarElement = UIUtil.getElementById(this._getProgressBarId());
progressBarElement.style.width = `${progress * 100}%`;
return this;
}
/**
* Stop the loading animation
*/
public stopLoading() {
this._getElement().classList.remove('button-loading');
return this;
}
public override generateHTML() {
return `
<div class="container-button">
<div class="struct-prop button" id="${this._getId()}">
<div class="button-label">${this._label}</div>
<div class="button-progress" id="${this._getProgressBarId()}"></div>
</div>
</div>
`;
}
public override registerEvents(): void {
this._getElement().addEventListener('click', () => {
if (this.enabled) {
this._onClick?.();
}
});
this._getElement().addEventListener('mouseenter', () => {
this._setHovered(true);
this._updateStyles();
});
this._getElement().addEventListener('mouseleave', () => {
this._setHovered(false);
this._updateStyles();
});
}
protected override _onEnabledChanged() {
this._updateStyles();
}
public override finalise(): void {
this._updateStyles();
}
/**
* Gets the ID of the DOM element for the button's progress bar.
*/
private _getProgressBarId() {
return this._getId() + '-progress';
}
protected _updateStyles(): void {
UIUtil.updateStyles(this._getElement(), {
isActive: true,
isEnabled: this.enabled,
isHovered: this.hovered,
});
}
}

View File

@ -1,7 +1,7 @@
import { UIUtil } from '../../util/ui_util';
import { ConfigUIElement } from './config_element';
import { ConfigComponent } from './config';
export class CheckboxElement extends ConfigUIElement<boolean, HTMLSelectElement> {
export class CheckboxComponent extends ConfigComponent<boolean, HTMLSelectElement> {
private _labelChecked: string;
private _labelUnchecked: string;
@ -22,14 +22,14 @@ export class CheckboxElement extends ConfigUIElement<boolean, HTMLSelectElement>
}
public override registerEvents(): void {
const checkboxElement = this._getElement();
const CheckboxComponent = this._getElement();
const textElement = UIUtil.getElementById(this._getTextId());
checkboxElement.addEventListener('mouseenter', () => {
CheckboxComponent.addEventListener('mouseenter', () => {
this._onMouseEnterLeave(true);
});
checkboxElement.addEventListener('mouseleave', () => {
CheckboxComponent.addEventListener('mouseleave', () => {
this._onMouseEnterLeave(false);
});
@ -41,7 +41,7 @@ export class CheckboxElement extends ConfigUIElement<boolean, HTMLSelectElement>
this._onMouseEnterLeave(false);
});
checkboxElement.addEventListener('click', () => {
CheckboxComponent.addEventListener('click', () => {
this._onClick();
});
@ -104,11 +104,11 @@ export class CheckboxElement extends ConfigUIElement<boolean, HTMLSelectElement>
protected _updateStyles() {
{
const checkboxElement = UIUtil.getElementById(this._getId());
UIUtil.updateStyles(checkboxElement, {
const CheckboxComponent = UIUtil.getElementById(this._getId());
UIUtil.updateStyles(CheckboxComponent, {
isEnabled: this.enabled,
isHovered: this.hovered,
isActive: false,
isActive: this.getValue(),
});
}
const checkboxPipElement = UIUtil.getElementById(this._getPipId());

View File

@ -1,7 +1,7 @@
import { RGBA, RGBAUtil } from '../../colour';
import { ConfigUIElement } from './config_element';
import { ConfigComponent } from './config';
export class ColourElement extends ConfigUIElement<RGBA, HTMLInputElement> {
export class ColourComponent extends ConfigComponent<RGBA, HTMLInputElement> {
public constructor(colour: RGBA) {
super(colour);
}

View File

@ -1,112 +1,112 @@
import { UIUtil } from '../../util/ui_util';
import { AppIcons } from '../icons';
import { HTMLBuilder } from '../misc';
import { ConfigUIElement } from './config_element';
export type ComboBoxItem<T> = {
payload: T;
displayText: string;
tooltip?: string;
}
export class ComboBoxElement<T> extends ConfigUIElement<T, HTMLSelectElement> {
private _items: ComboBoxItem<T>[];
public constructor() {
super();
this._items = [];
}
public addItems(items: ComboBoxItem<T>[]) {
items.forEach((item) => {
this.addItem(item);
});
return this;
}
public addItem(item: ComboBoxItem<T>) {
this._items.push(item);
this._setValue(this._items[0].payload);
return this;
}
public override registerEvents(): void {
this._getElement().addEventListener('mouseenter', () => {
this._setHovered(true);
this._updateStyles();
});
this._getElement().addEventListener('mouseleave', () => {
this._setHovered(false);
this._updateStyles();
});
this._getElement().addEventListener('change', (e: Event) => {
const selectedValue = this._items[this._getElement().selectedIndex].payload;
this._setValue(selectedValue);
});
}
public override _generateInnerHTML() {
const builder = new HTMLBuilder();
builder.add('<div style="position: relative; width: 100%;">');
builder.add(`<select class="struct-prop" name="${this._getId()}" id="${this._getId()}">`);
for (const item of this._items) {
builder.add(`<option value="${item.payload}" title="${item.tooltip || ''}">${item.displayText}</option>`);
}
builder.add('</select>');
builder.add(`<div id="${this._getId()}-arrow" class="checkbox-arrow">`);
builder.add(AppIcons.ARROW_DOWN);
builder.add(`</div>`);
builder.add('</div>');
return builder.toString();
}
protected _onValueChanged(): void {
super._onValueChanged();
console.log('combo changed');
}
protected _onEnabledChanged(): void {
super._onEnabledChanged();
this._getElement().disabled = this.disabled;
this._updateStyles();
}
protected override _updateStyles(): void {
UIUtil.updateStyles(this._getElement(), {
isHovered: this.hovered,
isEnabled: this.enabled,
isActive: false,
});
const arrowElement = UIUtil.getElementById(this._getId() + '-arrow');
arrowElement.classList.remove('text-dark');
arrowElement.classList.remove('text-standard');
arrowElement.classList.remove('text-light');
if (this.enabled) {
if (this.hovered) {
arrowElement.classList.add('text-light');
} else {
arrowElement.classList.add('text-standard');
}
} else {
arrowElement.classList.add('text-dark');
}
}
public override finalise(): void {
super.finalise();
const selectedIndex = this._items.findIndex((item) => item.payload === this.getValue());
const element = this._getElement();
element.selectedIndex = selectedIndex;
this._updateStyles();
}
}
import { UIUtil } from '../../util/ui_util';
import { AppIcons } from '../icons';
import { HTMLBuilder } from '../misc';
import { ConfigComponent } from './config';
export type ComboBoxItem<T> = {
payload: T;
displayText: string;
tooltip?: string;
}
export class ComboboxComponent<T> extends ConfigComponent<T, HTMLSelectElement> {
private _items: ComboBoxItem<T>[];
public constructor() {
super();
this._items = [];
}
public addItems(items: ComboBoxItem<T>[]) {
items.forEach((item) => {
this.addItem(item);
});
return this;
}
public addItem(item: ComboBoxItem<T>) {
this._items.push(item);
this._setValue(this._items[0].payload);
return this;
}
public override registerEvents(): void {
this._getElement().addEventListener('mouseenter', () => {
this._setHovered(true);
this._updateStyles();
});
this._getElement().addEventListener('mouseleave', () => {
this._setHovered(false);
this._updateStyles();
});
this._getElement().addEventListener('change', (e: Event) => {
const selectedValue = this._items[this._getElement().selectedIndex].payload;
this._setValue(selectedValue);
});
}
public override _generateInnerHTML() {
const builder = new HTMLBuilder();
builder.add('<div style="position: relative; width: 100%;">');
builder.add(`<select class="struct-prop" name="${this._getId()}" id="${this._getId()}">`);
for (const item of this._items) {
builder.add(`<option value="${item.payload}" title="${item.tooltip || ''}">${item.displayText}</option>`);
}
builder.add('</select>');
builder.add(`<div id="${this._getId()}-arrow" class="checkbox-arrow">`);
builder.add(AppIcons.ARROW_DOWN);
builder.add(`</div>`);
builder.add('</div>');
return builder.toString();
}
protected _onValueChanged(): void {
super._onValueChanged();
console.log('combo changed');
}
protected _onEnabledChanged(): void {
super._onEnabledChanged();
this._getElement().disabled = this.disabled;
this._updateStyles();
}
protected override _updateStyles(): void {
UIUtil.updateStyles(this._getElement(), {
isHovered: this.hovered,
isEnabled: this.enabled,
isActive: false,
});
const arrowElement = UIUtil.getElementById(this._getId() + '-arrow');
arrowElement.classList.remove('text-dark');
arrowElement.classList.remove('text-standard');
arrowElement.classList.remove('text-light');
if (this.enabled) {
if (this.hovered) {
arrowElement.classList.add('text-light');
} else {
arrowElement.classList.add('text-standard');
}
} else {
arrowElement.classList.add('text-dark');
}
}
public override finalise(): void {
super.finalise();
const selectedIndex = this._items.findIndex((item) => item.payload === this.getValue());
const element = this._getElement();
element.selectedIndex = selectedIndex;
this._updateStyles();
}
}

View File

@ -1,12 +1,11 @@
import { ASSERT } from '../../util/error_util';
import { UIUtil } from '../../util/ui_util';
import { BaseUIElement } from './base_element';
import { BaseComponent } from './base';
/**
* A `ConfigUIElement` is a UI element that has a value the user can change.
* For example, sliders, comboboxes and checkboxes are `ConfigUIElement`.
* A `ConfigComponent` is a UI element that has a value the user can change.
* For example, sliders, comboboxes and checkboxes are `ConfigComponent`.
*/
export abstract class ConfigUIElement<T, F> extends BaseUIElement<F> {
export abstract class ConfigComponent<T, F> extends BaseComponent<F> {
protected _label: string;
private _value?: T;
private _cachedValue?: T;

View File

@ -1,68 +1,68 @@
import * as path from 'path';
import { ASSERT } from '../../util/error_util';
import { UIUtil } from '../../util/ui_util';
import { ConfigUIElement } from './config_element';
export class ObjFileInputElement extends ConfigUIElement<Promise<string>, HTMLDivElement> {
private _loadedFilePath: string;
public constructor() {
super(Promise.resolve(''));
this._loadedFilePath = '';
}
protected override _generateInnerHTML() {
return `
<div class="input-file struct-prop" id="${this._getId()}">
<input type="file" accept=".obj" style="display: none;" id="${this._getId()}-input">
${this._loadedFilePath}
</div>
`;
}
public override registerEvents(): void {
this._getElement().addEventListener('mouseenter', () => {
this._setHovered(true);
this._updateStyles();
});
this._getElement().addEventListener('mouseleave', () => {
this._setHovered(false);
this._updateStyles();
});
const inputElement = UIUtil.getElementById(this._getId() + '-input') as HTMLInputElement;
inputElement.addEventListener('change', () => {
const files = inputElement.files;
if (files?.length === 1) {
const file = files.item(0);
ASSERT(file !== null);
this._loadedFilePath = file.name;
this._setValue(file.text());
}
});
this._getElement().addEventListener('click', () => {
if (this.enabled) {
inputElement.click();
}
});
}
protected _onValueChanged(): void {
this._updateStyles();
}
protected override _updateStyles() {
const parsedPath = path.parse(this._loadedFilePath);
this._getElement().innerHTML = parsedPath.name + parsedPath.ext;
UIUtil.updateStyles(this._getElement(), {
isHovered: this.hovered,
isEnabled: this.enabled,
isActive: false,
});
}
}
import * as path from 'path';
import { ASSERT } from '../../util/error_util';
import { UIUtil } from '../../util/ui_util';
import { ConfigComponent } from './config';
export class ObjFileComponent extends ConfigComponent<Promise<string>, HTMLDivElement> {
private _loadedFilePath: string;
public constructor() {
super(Promise.resolve(''));
this._loadedFilePath = '';
}
protected override _generateInnerHTML() {
return `
<div class="input-file struct-prop" id="${this._getId()}">
<input type="file" accept=".obj" style="display: none;" id="${this._getId()}-input">
${this._loadedFilePath}
</div>
`;
}
public override registerEvents(): void {
this._getElement().addEventListener('mouseenter', () => {
this._setHovered(true);
this._updateStyles();
});
this._getElement().addEventListener('mouseleave', () => {
this._setHovered(false);
this._updateStyles();
});
const inputElement = UIUtil.getElementById(this._getId() + '-input') as HTMLInputElement;
inputElement.addEventListener('change', () => {
const files = inputElement.files;
if (files?.length === 1) {
const file = files.item(0);
ASSERT(file !== null);
this._loadedFilePath = file.name;
this._setValue(file.text());
}
});
this._getElement().addEventListener('click', () => {
if (this.enabled) {
inputElement.click();
}
});
}
protected _onValueChanged(): void {
this._updateStyles();
}
protected override _updateStyles() {
const parsedPath = path.parse(this._loadedFilePath);
this._getElement().innerHTML = parsedPath.name + parsedPath.ext;
UIUtil.updateStyles(this._getElement(), {
isHovered: this.hovered,
isEnabled: this.enabled,
isActive: false,
});
}
}

View File

@ -1,10 +1,10 @@
import { ConfigUIElement } from './config_element';
import { ConfigComponent } from './config';
/**
* A `FullConfigUIElement` is a UI element that has a value the user can change.
* For example, sliders, comboboxes and checkboxes are `ConfigUIElement`.
* A `FullConfigComponent` is a UI element that has a value the user can change.
* For example, sliders, comboboxes and checkboxes are `ConfigComponent`.
*/
export abstract class FullConfigUIElement<T, F> extends ConfigUIElement<T, F> {
export abstract class FullConfigComponent<T, F> extends ConfigComponent<T, F> {
public override generateHTML() {
return `
<div class="property" style="flex-direction: column; align-items: start;">

View File

@ -1,36 +1,33 @@
import IMAGE_LOGO from '../../../res/static/icon.png';
import { AppConfig } from '../../config';
import { LOG, LOG_ERROR } from '../../util/log_util';
import { AppPaths, PathUtil } from '../../util/path_util';
import { UIUtil } from '../../util/ui_util';
import { AppIcons } from '../icons';
import { BaseUIElement } from './base_element';
import { ToolbarItemElement } from './toolbar_item';
import { BaseComponent } from './base';
import { ToolbarItemComponent } from './toolbar_item';
export class HeaderUIElement extends BaseUIElement<HTMLDivElement> {
private static _instance: HeaderUIElement;
export class HeaderComponent extends BaseComponent<HTMLDivElement> {
private static _instance: HeaderComponent;
public static get Get() {
return this._instance || (this._instance = new this());
}
private _githubButton: ToolbarItemElement;
private _bugButton: ToolbarItemElement;
private _discordButton: ToolbarItemElement;
private _githubButton: ToolbarItemComponent;
private _bugButton: ToolbarItemComponent;
private _discordButton: ToolbarItemComponent;
private constructor() {
super();
this._githubButton = new ToolbarItemElement({ id: 'gh', iconSVG: AppIcons.GITHUB })
this._githubButton = new ToolbarItemComponent({ id: 'gh', iconSVG: AppIcons.GITHUB })
.onClick(() => {
window.open('https://github.com/LucasDower/ObjToSchematic');
});
this._bugButton = new ToolbarItemElement({ id: 'bug', iconSVG: AppIcons.BUG })
this._bugButton = new ToolbarItemComponent({ id: 'bug', iconSVG: AppIcons.BUG })
.onClick(() => {
window.open('https://github.com/LucasDower/ObjToSchematic/issues');
});
this._discordButton = new ToolbarItemElement({ id: 'disc', iconSVG: AppIcons.DISCORD })
this._discordButton = new ToolbarItemComponent({ id: 'disc', iconSVG: AppIcons.DISCORD })
.onClick(() => {
window.open('https://discord.gg/McS2VrBZPD');
});

View File

@ -1,20 +1,20 @@
import { TImageFiletype, TImageRawWrap } from '../../texture';
import { TImageRawWrap } from '../../texture';
import { getRandomID } from '../../util';
import { ASSERT } from '../../util/error_util';
import { UIUtil } from '../../util/ui_util';
import { AppIcons } from '../icons';
import { ConfigUIElement } from './config_element';
import { ToolbarItemElement } from './toolbar_item';
import { ConfigComponent } from './config';
import { ToolbarItemComponent } from './toolbar_item';
export class ImageElement extends ConfigUIElement<Promise<TImageRawWrap>, HTMLImageElement> {
private _switchElement: ToolbarItemElement;
export class ImageComponent extends ConfigComponent<Promise<TImageRawWrap>, HTMLImageElement> {
private _switchElement: ToolbarItemComponent;
private _imageId: string;
public constructor(param?: TImageRawWrap) {
super(Promise.resolve(param ?? { raw: '', filetype: 'png' }));
this._switchElement = new ToolbarItemElement({ id: 'sw', iconSVG: AppIcons.UPLOAD })
this._switchElement = new ToolbarItemComponent({ id: 'sw', iconSVG: AppIcons.UPLOAD })
.setLabel('Choose')
.onClick(() => {
const inputElement = UIUtil.getElementById(this._getId() + '-input') as HTMLInputElement;
@ -78,7 +78,7 @@ export class ImageElement extends ConfigUIElement<Promise<TImageRawWrap>, HTMLIm
protected override _onValueChanged(): void {
const inputElement = UIUtil.getElementById(this._imageId) as HTMLImageElement;
const placeholderElement = UIUtil.getElementById(this._imageId + '-placeholder');
const PlaceholderComponent = UIUtil.getElementById(this._imageId + '-placeholder');
this.getValue()
.then((res) => {
@ -88,13 +88,13 @@ export class ImageElement extends ConfigUIElement<Promise<TImageRawWrap>, HTMLIm
this._switchElement.setActive(false);
inputElement.src = res.raw;
inputElement.style.display = 'unset';
placeholderElement.style.display = 'none';
PlaceholderComponent.style.display = 'none';
})
.catch((err) => {
this._switchElement.setActive(true);
inputElement.src = '';
inputElement.style.display = 'none';
placeholderElement.style.display = 'flex';
PlaceholderComponent.style.display = 'flex';
});
}

View File

@ -1,18 +1,18 @@
import { MaterialType, SolidMaterial, TexturedMaterial } from '../../mesh';
import { AppIcons } from '../icons';
import { ConfigUIElement } from './config_element';
import { ToolbarItemElement } from './toolbar_item';
import { ConfigComponent } from './config';
import { ToolbarItemComponent } from './toolbar_item';
export class MaterialTypeElement extends ConfigUIElement<MaterialType, HTMLDivElement> {
private _solidButton: ToolbarItemElement;
private _texturedButton: ToolbarItemElement;
export class MaterialTypeComponent extends ConfigComponent<MaterialType, HTMLDivElement> {
private _solidButton: ToolbarItemComponent;
private _texturedButton: ToolbarItemComponent;
private _material: SolidMaterial | TexturedMaterial;
public constructor(material: SolidMaterial | TexturedMaterial) {
super(material.type);
this._material = material;
this._solidButton = new ToolbarItemElement({ id: 'sw1', iconSVG: AppIcons.COLOUR_SWATCH })
this._solidButton = new ToolbarItemComponent({ id: 'sw1', iconSVG: AppIcons.COLOUR_SWATCH })
.setLabel('Solid')
.onClick(() => {
if (this._material.type === MaterialType.textured) {
@ -20,7 +20,7 @@ export class MaterialTypeElement extends ConfigUIElement<MaterialType, HTMLDivEl
}
});
this._texturedButton = new ToolbarItemElement({ id: 'sw2', iconSVG: AppIcons.IMAGE })
this._texturedButton = new ToolbarItemComponent({ id: 'sw2', iconSVG: AppIcons.IMAGE })
.setLabel('Textured')
.onClick(() => {
if (this._material.type === MaterialType.solid) {

View File

@ -6,19 +6,17 @@ import { download } from '../../util/file_util';
import { UIUtil } from '../../util/ui_util';
import { AppConsole } from '../console';
import { AppIcons } from '../icons';
import { ButtonElement } from './button';
import { CheckboxElement } from './checkbox';
import { ConfigUIElement } from './config_element';
import { FullConfigUIElement } from './full_config_element';
import { ToolbarItemElement } from './toolbar_item';
import { CheckboxComponent } from './checkbox';
import { ConfigComponent } from './config';
import { ToolbarItemComponent } from './toolbar_item';
export class PaletteElement extends ConfigUIElement<Palette, HTMLDivElement> {
private _checkboxes: { block: string, element: CheckboxElement }[];
export class PaletteComponent extends ConfigComponent<Palette, HTMLDivElement> {
private _checkboxes: { block: string, element: CheckboxComponent }[];
private _palette: Palette;
private _selectAll: ToolbarItemElement;
private _deselectAll: ToolbarItemElement;
private _importFrom: ToolbarItemElement;
private _exportTo: ToolbarItemElement;
private _selectAll: ToolbarItemComponent;
private _deselectAll: ToolbarItemComponent;
private _importFrom: ToolbarItemComponent;
private _exportTo: ToolbarItemComponent;
public constructor() {
super();
@ -31,14 +29,14 @@ export class PaletteElement extends ConfigUIElement<Palette, HTMLDivElement> {
PALETTE_ALL_RELEASE.forEach((block) => {
this._checkboxes.push({
block: block,
element: new CheckboxElement()
element: new CheckboxComponent()
.setDefaultValue(true)
.setCheckedText(block)
.setUncheckedText(block),
});
});
this._selectAll = new ToolbarItemElement({ iconSVG: AppIcons.SELECT_ALL, id: 'select-all' })
this._selectAll = new ToolbarItemComponent({ iconSVG: AppIcons.SELECT_ALL, id: 'select-all' })
.onClick(() => {
this._checkboxes.forEach((checkbox) => {
checkbox.element.check();
@ -47,7 +45,7 @@ export class PaletteElement extends ConfigUIElement<Palette, HTMLDivElement> {
});
this._deselectAll = new ToolbarItemElement({ iconSVG: AppIcons.DESELECT_ALL, id: 'deselect-all' })
this._deselectAll = new ToolbarItemComponent({ iconSVG: AppIcons.DESELECT_ALL, id: 'deselect-all' })
.onClick(() => {
this._checkboxes.forEach((checkbox) => {
checkbox.element.uncheck();
@ -55,7 +53,7 @@ export class PaletteElement extends ConfigUIElement<Palette, HTMLDivElement> {
this._onCountSelectedChanged();
});
this._importFrom = new ToolbarItemElement({ iconSVG: AppIcons.IMPORT, id: 'import' })
this._importFrom = new ToolbarItemComponent({ iconSVG: AppIcons.IMPORT, id: 'import' })
.onClick(() => {
const a = document.createElement('input');
a.setAttribute('type', 'file');
@ -76,7 +74,7 @@ export class PaletteElement extends ConfigUIElement<Palette, HTMLDivElement> {
a.click();
});
this._exportTo = new ToolbarItemElement({ iconSVG: AppIcons.EXPORT, id: 'export' })
this._exportTo = new ToolbarItemComponent({ iconSVG: AppIcons.EXPORT, id: 'export' })
.onClick(() => {
const textPalette = this._checkboxes.filter((x) => x.element.getValue())
.map((x) => x.block)

View File

@ -1,6 +1,6 @@
import { ConfigUIElement } from './config_element';
import { ConfigComponent } from './config';
export class PlaceholderElement extends ConfigUIElement<undefined, HTMLDivElement> {
export class PlaceholderComponent extends ConfigComponent<undefined, HTMLDivElement> {
private _placeholderText: string;
public constructor(placeholderText: string) {

View File

@ -1,242 +1,242 @@
import { clamp, mapRange, wayThrough } from '../../math';
import { ASSERT } from '../../util/error_util';
import { UIUtil } from '../../util/ui_util';
import { ConfigUIElement } from './config_element';
export type TSliderParams = {
min: number,
max: number,
value: number,
decimals: number,
step: number,
}
export class SliderElement extends ConfigUIElement<number, HTMLDivElement> {
private _min: number;
private _max: number;
private _decimals: number;
private _step: number;
private _dragging: boolean;
private _internalValue: number;
private _valueHovered: boolean;
public constructor() {
super();
this._min = 0;
this._max = 1;
this._decimals = 1;
this._step = 0.1;
this._internalValue = 0.5;
this._dragging = false;
this._valueHovered = false;
}
public override setDefaultValue(value: number) {
super.setDefaultValue(value);
this._internalValue = value;
return this;
}
/**
* Set the minimum value the slider can be set to.
*/
public setMin(min: number) {
this._min = min;
return this;
}
/**
* Set the maximum value the slider can be set to.
*/
public setMax(max: number) {
this._max = max;
return this;
}
/**
* Set the number of decimals to display the value to.
*/
public setDecimals(decimals: number) {
this._decimals = decimals;
return this;
}
/**
* Set the step the value is increased/decreased by.
*/
public setStep(step: number) {
this._step = step;
return this;
}
public override registerEvents() {
const element = this._getElement();
const elementBar = UIUtil.getElementById(this._getSliderBarId());
const elementValue = UIUtil.getElementById(this._getSliderValueId()) as HTMLInputElement;
element.onmouseenter = () => {
this._setHovered(true);
this._updateStyles();
};
element.onmouseleave = () => {
this._setHovered(false);
this._updateStyles();
};
element.onmousedown = () => {
this._dragging = true;
};
document.addEventListener('mousemove', (e: MouseEvent) => {
if (this._dragging) {
this._onDragSlider(e);
}
});
document.addEventListener('mouseup', (e: MouseEvent) => {
if (this._dragging) {
this._onDragSlider(e);
}
this._dragging = false;
});
element.addEventListener('wheel', (e: WheelEvent) => {
if (!this._dragging && this.getEnabled()) {
e.preventDefault();
this._onScrollSlider(e);
}
});
elementValue.addEventListener('change', () => {
this._onTypedValue();
});
elementValue.addEventListener('mouseenter', () => {
this._valueHovered = true;
this._updateStyles();
});
elementValue.addEventListener('mouseleave', () => {
this._valueHovered = false;
this._updateStyles();
});
}
public override _generateInnerHTML() {
const norm = (this._internalValue - this._min) / (this._max - this._min);
return `
<input class="struct-prop comp-slider-value" type="number" id="${this._getSliderValueId()}" min="${this._min}" max="${this._max}" step="${this._step}" value="${this.getValue().toFixed(this._decimals)}">
<div class="struct-prop comp-slider comp-slider-outer" id="${this._getId()}">
<div class="struct-prop comp-slider comp-slider-inner" id="${this._getSliderBarId()}" style="width: ${norm * 100}%">
</div>
</div>
`;
}
protected override _onEnabledChanged() {
super._onEnabledChanged();
const elementValue = UIUtil.getElementById(this._getSliderValueId()) as HTMLInputElement;
elementValue.disabled = this.disabled;
const elementBar = UIUtil.getElementById(this._getSliderBarId());
const elementSlider = UIUtil.getElementById(this._getId());
if (this.enabled) {
elementBar.classList.add('enabled');
elementSlider.classList.add('enabled');
} else {
elementBar.classList.remove('enabled');
elementSlider.classList.remove('enabled');
}
this._updateStyles();
}
protected override _onValueChanged(): void {
const percentage = wayThrough(this.getValue(), this._min, this._max);
ASSERT(percentage >= 0.0 && percentage <= 1.0);
UIUtil.getElementById(this._getSliderBarId()).style.width = `${percentage * 100}%`;
(UIUtil.getElementById(this._getSliderValueId()) as HTMLInputElement).value = this.getValue().toFixed(this._decimals);
}
private _onScrollSlider(e: WheelEvent) {
if (!this.getEnabled()) {
return;
}
this._internalValue -= (e.deltaY / 150) * this._step;
this._internalValue = clamp(this._internalValue, this._min, this._max);
this._onInternalValueUpdated();
}
private _onDragSlider(e: MouseEvent) {
if (!this.getEnabled()) {
return;
}
const box = this._getElement().getBoundingClientRect();
const left = box.x;
const right = box.x + box.width;
this._internalValue = mapRange(e.clientX, left, right, this._min, this._max);
this._internalValue = clamp(this._internalValue, this._min, this._max);
this._onInternalValueUpdated();
}
private _onTypedValue() {
const elementValue = UIUtil.getElementById(this._getSliderValueId()) as HTMLInputElement;
const typedNumber = parseFloat(elementValue.value);
if (!isNaN(typedNumber)) {
this._internalValue = clamp(typedNumber, this._min, this._max);
}
this._onInternalValueUpdated();
}
private _onInternalValueUpdated() {
const displayString = this._internalValue!.toFixed(this._decimals);
this._setValue(parseFloat(displayString));
}
/**
* Gets the ID of the DOM element for the slider's value.
*/
private _getSliderValueId() {
return this._getId() + '-value';
}
/**
* Gets the ID of the DOM element for the slider's bar.
*/
private _getSliderBarId() {
return this._getId() + '-bar';
}
protected override _updateStyles(): void {
const elementValue = UIUtil.getElementById(this._getSliderValueId()) as HTMLInputElement;
UIUtil.updateStyles(elementValue, {
isHovered: this._valueHovered,
isActive: false,
isEnabled: this.enabled,
});
const elementBar = UIUtil.getElementById(this._getSliderBarId()) as HTMLInputElement;
UIUtil.updateStyles(elementBar, {
isHovered: this.hovered,
isActive: true,
isEnabled: this.enabled,
});
const elementSlider = UIUtil.getElementById(this._getId()) as HTMLInputElement;
UIUtil.updateStyles(elementSlider, {
isHovered: this.hovered,
isActive: false,
isEnabled: this.enabled,
});
}
}
import { clamp, mapRange, wayThrough } from '../../math';
import { ASSERT } from '../../util/error_util';
import { UIUtil } from '../../util/ui_util';
import { ConfigComponent } from './config';
export type TSliderParams = {
min: number,
max: number,
value: number,
decimals: number,
step: number,
}
export class SliderComponent extends ConfigComponent<number, HTMLDivElement> {
private _min: number;
private _max: number;
private _decimals: number;
private _step: number;
private _dragging: boolean;
private _internalValue: number;
private _valueHovered: boolean;
public constructor() {
super();
this._min = 0;
this._max = 1;
this._decimals = 1;
this._step = 0.1;
this._internalValue = 0.5;
this._dragging = false;
this._valueHovered = false;
}
public override setDefaultValue(value: number) {
super.setDefaultValue(value);
this._internalValue = value;
return this;
}
/**
* Set the minimum value the slider can be set to.
*/
public setMin(min: number) {
this._min = min;
return this;
}
/**
* Set the maximum value the slider can be set to.
*/
public setMax(max: number) {
this._max = max;
return this;
}
/**
* Set the number of decimals to display the value to.
*/
public setDecimals(decimals: number) {
this._decimals = decimals;
return this;
}
/**
* Set the step the value is increased/decreased by.
*/
public setStep(step: number) {
this._step = step;
return this;
}
public override registerEvents() {
const element = this._getElement();
const elementBar = UIUtil.getElementById(this._getSliderBarId());
const elementValue = UIUtil.getElementById(this._getSliderValueId()) as HTMLInputElement;
element.onmouseenter = () => {
this._setHovered(true);
this._updateStyles();
};
element.onmouseleave = () => {
this._setHovered(false);
this._updateStyles();
};
element.onmousedown = () => {
this._dragging = true;
};
document.addEventListener('mousemove', (e: MouseEvent) => {
if (this._dragging) {
this._onDragSlider(e);
}
});
document.addEventListener('mouseup', (e: MouseEvent) => {
if (this._dragging) {
this._onDragSlider(e);
}
this._dragging = false;
});
element.addEventListener('wheel', (e: WheelEvent) => {
if (!this._dragging && this.getEnabled()) {
e.preventDefault();
this._onScrollSlider(e);
}
});
elementValue.addEventListener('change', () => {
this._onTypedValue();
});
elementValue.addEventListener('mouseenter', () => {
this._valueHovered = true;
this._updateStyles();
});
elementValue.addEventListener('mouseleave', () => {
this._valueHovered = false;
this._updateStyles();
});
}
public override _generateInnerHTML() {
const norm = (this._internalValue - this._min) / (this._max - this._min);
return `
<input class="struct-prop comp-slider-value" type="number" id="${this._getSliderValueId()}" min="${this._min}" max="${this._max}" step="${this._step}" value="${this.getValue().toFixed(this._decimals)}">
<div class="struct-prop comp-slider comp-slider-outer" id="${this._getId()}">
<div class="struct-prop comp-slider comp-slider-inner" id="${this._getSliderBarId()}" style="width: ${norm * 100}%">
</div>
</div>
`;
}
protected override _onEnabledChanged() {
super._onEnabledChanged();
const elementValue = UIUtil.getElementById(this._getSliderValueId()) as HTMLInputElement;
elementValue.disabled = this.disabled;
const elementBar = UIUtil.getElementById(this._getSliderBarId());
const elementSlider = UIUtil.getElementById(this._getId());
if (this.enabled) {
elementBar.classList.add('enabled');
elementSlider.classList.add('enabled');
} else {
elementBar.classList.remove('enabled');
elementSlider.classList.remove('enabled');
}
this._updateStyles();
}
protected override _onValueChanged(): void {
const percentage = wayThrough(this.getValue(), this._min, this._max);
ASSERT(percentage >= 0.0 && percentage <= 1.0);
UIUtil.getElementById(this._getSliderBarId()).style.width = `${percentage * 100}%`;
(UIUtil.getElementById(this._getSliderValueId()) as HTMLInputElement).value = this.getValue().toFixed(this._decimals);
}
private _onScrollSlider(e: WheelEvent) {
if (!this.getEnabled()) {
return;
}
this._internalValue -= (e.deltaY / 150) * this._step;
this._internalValue = clamp(this._internalValue, this._min, this._max);
this._onInternalValueUpdated();
}
private _onDragSlider(e: MouseEvent) {
if (!this.getEnabled()) {
return;
}
const box = this._getElement().getBoundingClientRect();
const left = box.x;
const right = box.x + box.width;
this._internalValue = mapRange(e.clientX, left, right, this._min, this._max);
this._internalValue = clamp(this._internalValue, this._min, this._max);
this._onInternalValueUpdated();
}
private _onTypedValue() {
const elementValue = UIUtil.getElementById(this._getSliderValueId()) as HTMLInputElement;
const typedNumber = parseFloat(elementValue.value);
if (!isNaN(typedNumber)) {
this._internalValue = clamp(typedNumber, this._min, this._max);
}
this._onInternalValueUpdated();
}
private _onInternalValueUpdated() {
const displayString = this._internalValue!.toFixed(this._decimals);
this._setValue(parseFloat(displayString));
}
/**
* Gets the ID of the DOM element for the slider's value.
*/
private _getSliderValueId() {
return this._getId() + '-value';
}
/**
* Gets the ID of the DOM element for the slider's bar.
*/
private _getSliderBarId() {
return this._getId() + '-bar';
}
protected override _updateStyles(): void {
const elementValue = UIUtil.getElementById(this._getSliderValueId()) as HTMLInputElement;
UIUtil.updateStyles(elementValue, {
isHovered: this._valueHovered,
isActive: false,
isEnabled: this.enabled,
});
const elementBar = UIUtil.getElementById(this._getSliderBarId()) as HTMLInputElement;
UIUtil.updateStyles(elementBar, {
isHovered: this.hovered,
isActive: true,
isEnabled: this.enabled,
});
const elementSlider = UIUtil.getElementById(this._getId()) as HTMLInputElement;
UIUtil.updateStyles(elementSlider, {
isHovered: this.hovered,
isActive: false,
isEnabled: this.enabled,
});
}
}

View File

@ -1,24 +1,24 @@
import { SolidMaterial } from '../../mesh';
import { ColourElement } from './colour_element';
import { ConfigUIElement } from './config_element';
import { MaterialTypeElement } from './material_type_element';
import { SliderElement } from './slider';
import { ColourComponent } from './colour';
import { ConfigComponent } from './config';
import { MaterialTypeComponent } from './material_type';
import { SliderComponent } from './slider';
export class SolidMaterialElement extends ConfigUIElement<SolidMaterial, HTMLDivElement> {
private _typeElement: MaterialTypeElement;
private _colourElement: ColourElement;
private _alphaElement: SliderElement;
export class SolidMaterialComponent extends ConfigComponent<SolidMaterial, HTMLDivElement> {
private _typeElement: MaterialTypeComponent;
private _ColourComponent: ColourComponent;
private _alphaElement: SliderComponent;
public constructor(materialName: string, material: SolidMaterial) {
super(material);
this._typeElement = new MaterialTypeElement(material)
this._typeElement = new MaterialTypeComponent(material)
.setLabel('Type');
this._colourElement = new ColourElement(material.colour)
this._ColourComponent = new ColourComponent(material.colour)
.setLabel('Colour');
this._alphaElement = new SliderElement()
this._alphaElement = new SliderComponent()
.setLabel('Alpha')
.setMin(0.0)
.setMax(1.0)
@ -29,14 +29,14 @@ export class SolidMaterialElement extends ConfigUIElement<SolidMaterial, HTMLDiv
public override registerEvents(): void {
this._typeElement.registerEvents();
this._colourElement.registerEvents();
this._ColourComponent.registerEvents();
this._alphaElement.registerEvents();
this._typeElement.onClickChangeTypeDelegate(() => {
this._onChangeTypeDelegate?.();
});
this._colourElement.addValueChangedListener((newColour) => {
this._ColourComponent.addValueChangedListener((newColour) => {
this.getValue().colour.r = newColour.r;
this.getValue().colour.g = newColour.g;
this.getValue().colour.b = newColour.b;
@ -49,7 +49,7 @@ export class SolidMaterialElement extends ConfigUIElement<SolidMaterial, HTMLDiv
public override finalise(): void {
this._typeElement.finalise();
this._colourElement.finalise();
this._ColourComponent.finalise();
this._alphaElement.finalise();
}
@ -57,7 +57,7 @@ export class SolidMaterialElement extends ConfigUIElement<SolidMaterial, HTMLDiv
return `
<div class="component-group">
${this._typeElement.generateHTML()}
${this._colourElement.generateHTML()}
${this._ColourComponent.generateHTML()}
${this._alphaElement.generateHTML()}
</div>
`;
@ -70,7 +70,7 @@ export class SolidMaterialElement extends ConfigUIElement<SolidMaterial, HTMLDiv
super._onEnabledChanged();
this._typeElement.setEnabled(this.enabled);
this._colourElement.setEnabled(this.enabled);
this._ColourComponent.setEnabled(this.enabled);
this._alphaElement.setEnabled(this.enabled);
}

View File

@ -1,46 +1,43 @@
import path from 'path';
import { MaterialType, TexturedMaterial } from '../../mesh';
import { TexturedMaterial } from '../../mesh';
import { EImageChannel, TTransparencyTypes } from '../../texture';
import { getRandomID } from '../../util';
import { ASSERT } from '../../util/error_util';
import { TTexelInterpolation } from '../../util/type_util';
import { HTMLBuilder } from '../misc';
import { ComboBoxElement } from './combobox';
import { ConfigUIElement } from './config_element';
import { ImageElement } from './image_element';
import { MaterialTypeElement } from './material_type_element';
import { SliderElement } from './slider';
import { ComboboxComponent } from './combobox';
import { ConfigComponent } from './config';
import { ImageComponent } from './image_element';
import { MaterialTypeComponent } from './material_type';
import { SliderComponent } from './slider';
export class TexturedMaterialElement extends ConfigUIElement<TexturedMaterial, HTMLDivElement> {
private _typeElement: MaterialTypeElement;
private _filteringElement: ComboBoxElement<'nearest' | 'linear'>;
private _wrapElement: ComboBoxElement<'clamp' | 'repeat'>;
private _transparencyElement: ComboBoxElement<TTransparencyTypes>;
private _imageElement: ImageElement;
private _alphaValueElement?: SliderElement;
private _alphaMapElement?: ImageElement;
private _alphaChannelElement?: ComboBoxElement<EImageChannel>;
export class TexturedMaterialComponent extends ConfigComponent<TexturedMaterial, HTMLDivElement> {
private _typeElement: MaterialTypeComponent;
private _filteringElement: ComboboxComponent<'nearest' | 'linear'>;
private _wrapElement: ComboboxComponent<'clamp' | 'repeat'>;
private _transparencyElement: ComboboxComponent<TTransparencyTypes>;
private _ImageComponent: ImageComponent;
private _alphaValueElement?: SliderComponent;
private _alphaMapElement?: ImageComponent;
private _alphaChannelElement?: ComboboxComponent<EImageChannel>;
public constructor(materialName: string, material: TexturedMaterial) {
super(material);
this._typeElement = new MaterialTypeElement(material)
this._typeElement = new MaterialTypeComponent(material)
.setLabel('Type');
this._filteringElement = new ComboBoxElement<TTexelInterpolation>()
this._filteringElement = new ComboboxComponent<TTexelInterpolation>()
.setLabel('Filtering')
.addItem({ payload: 'linear', displayText: 'Linear' })
.addItem({ payload: 'nearest', displayText: 'Nearest' })
.setDefaultValue(material.interpolation);
this._wrapElement = new ComboBoxElement<'clamp' | 'repeat'>()
this._wrapElement = new ComboboxComponent<'clamp' | 'repeat'>()
.setLabel('Wrap')
.addItem({ payload: 'clamp', displayText: 'Clamp' })
.addItem({ payload: 'repeat', displayText: 'Repeat' })
.setDefaultValue(material.extension);
this._transparencyElement = new ComboBoxElement<TTransparencyTypes>()
this._transparencyElement = new ComboboxComponent<TTransparencyTypes>()
.setLabel('Transparency')
.addItem({ payload: 'None', displayText: 'None' })
.addItem({ payload: 'UseAlphaMap', displayText: 'Alpha map' })
@ -48,12 +45,12 @@ export class TexturedMaterialElement extends ConfigUIElement<TexturedMaterial, H
.addItem({ payload: 'UseDiffuseMapAlphaChannel', displayText: 'Diffuse map alpha channel' })
.setDefaultValue(material.transparency.type);
this._imageElement = new ImageElement(material.diffuse)
this._ImageComponent = new ImageComponent(material.diffuse)
.setLabel('Diffuse map');
switch (material.transparency.type) {
case 'UseAlphaValue':
this._alphaValueElement = new SliderElement()
this._alphaValueElement = new SliderComponent()
.setLabel('Alpha')
.setMin(0.0)
.setMax(1.0)
@ -62,10 +59,10 @@ export class TexturedMaterialElement extends ConfigUIElement<TexturedMaterial, H
.setStep(0.01);
break;
case 'UseAlphaMap':
this._alphaMapElement = new ImageElement(material.transparency.alpha)
this._alphaMapElement = new ImageComponent(material.transparency.alpha)
.setLabel('Alpha map');
this._alphaChannelElement = new ComboBoxElement<EImageChannel>()
this._alphaChannelElement = new ComboboxComponent<EImageChannel>()
.setLabel('Alpha channel')
.addItem({ payload: EImageChannel.R, displayText: 'Red' })
.addItem({ payload: EImageChannel.G, displayText: 'Green' })
@ -77,7 +74,7 @@ export class TexturedMaterialElement extends ConfigUIElement<TexturedMaterial, H
}
public override registerEvents(): void {
this._imageElement.registerEvents();
this._ImageComponent.registerEvents();
this._typeElement.registerEvents();
this._filteringElement.registerEvents();
this._wrapElement.registerEvents();
@ -86,7 +83,7 @@ export class TexturedMaterialElement extends ConfigUIElement<TexturedMaterial, H
this._alphaMapElement?.registerEvents();
this._alphaChannelElement?.registerEvents();
this._imageElement.addValueChangedListener((newPath) => {
this._ImageComponent.addValueChangedListener((newPath) => {
const material = this.getValue();
// TODO Unimplemented, promise should be resolved where it is used
newPath.then((res) => {
@ -146,7 +143,7 @@ export class TexturedMaterialElement extends ConfigUIElement<TexturedMaterial, H
builder.add('<div class="component-group">');
{
builder.add(this._typeElement.generateHTML());
builder.add(this._imageElement.generateHTML());
builder.add(this._ImageComponent.generateHTML());
builder.add(this._filteringElement.generateHTML());
builder.add(this._wrapElement.generateHTML());
builder.add(this._transparencyElement.generateHTML());
@ -170,7 +167,7 @@ export class TexturedMaterialElement extends ConfigUIElement<TexturedMaterial, H
protected override _onEnabledChanged(): void {
super._onEnabledChanged();
this._imageElement.setEnabled(this.enabled);
this._ImageComponent.setEnabled(this.enabled);
this._typeElement.setEnabled(this.enabled);
this._filteringElement.setEnabled(this.enabled);
this._wrapElement.setEnabled(this.enabled);
@ -183,7 +180,7 @@ export class TexturedMaterialElement extends ConfigUIElement<TexturedMaterial, H
public override finalise(): void {
super.finalise();
this._imageElement.finalise();
this._ImageComponent.finalise();
this._typeElement.finalise();
this._filteringElement.finalise();
this._wrapElement.finalise();

View File

@ -1,135 +1,132 @@
import { getRandomID } from '../../util';
import { ASSERT } from '../../util/error_util';
import { AppPaths } from '../../util/path_util';
import { PathUtil } from '../../util/path_util';
import { UIUtil } from '../../util/ui_util';
import { BaseUIElement } from './base_element';
export type TToolbarBooleanProperty = 'enabled' | 'active';
export type TToolbarItemParams = {
id: string,
iconSVG: string;
}
export class ToolbarItemElement extends BaseUIElement<HTMLDivElement> {
private _iconSVG: SVGSVGElement;
private _label: string;
private _onClick?: () => void;
private _isActive: boolean;
public constructor(params: TToolbarItemParams) {
super();
this._isActive = false;
{
const parser = new DOMParser();
const svgParse = parser.parseFromString(params.iconSVG, 'text/html');
const svgs = svgParse.getElementsByTagName('svg');
ASSERT(svgs.length === 1, 'Missing SVG');
this._iconSVG = svgs[0];
this._iconSVG.id = this._getId() + '-svg';
}
this._label = '';
}
public setActive(isActive: boolean) {
this._isActive = isActive;
this._updateStyles();
}
public setLabel(label: string) {
this._label = label;
return this;
}
public tick() {
if (this._isEnabledDelegate !== undefined) {
const newIsEnabled = this._isEnabledDelegate();
if (newIsEnabled != this.enabled) {
this.setEnabled(newIsEnabled);
this._updateStyles();
}
}
if (this._isActiveDelegate !== undefined) {
const newIsActive = this._isActiveDelegate();
if (newIsActive !== this._isActive) {
this._isActive = newIsActive;
this._updateStyles();
}
}
}
protected _onEnabledChanged(): void {
this._updateStyles();
}
private _isActiveDelegate?: () => boolean;
public isActive(delegate: () => boolean) {
this._isActiveDelegate = delegate;
return this;
}
private _isEnabledDelegate?: () => boolean;
public isEnabled(delegate: () => boolean) {
this._isEnabledDelegate = delegate;
return this;
}
public onClick(delegate: () => void) {
this._onClick = delegate;
return this;
}
public generateHTML() {
return `
<div class="struct-prop container-icon-button" id="${this._getId()}">
${this._iconSVG.outerHTML} ${this._label}
</div>
`;
}
public registerEvents(): void {
const element = document.getElementById(this._getId()) as HTMLDivElement;
ASSERT(element !== null);
element.addEventListener('click', () => {
if (this.enabled && this._onClick) {
this._onClick();
}
});
element.addEventListener('mouseenter', () => {
this._setHovered(true);
this._updateStyles();
});
element.addEventListener('mouseleave', () => {
this._setHovered(false);
this._updateStyles();
});
}
public override finalise(): void {
this._updateStyles();
}
private _getSVGElement() {
const svgId = this._getId() + '-svg';
return UIUtil.getElementById(svgId);
}
protected override _updateStyles() {
UIUtil.updateStyles(this._getElement(), {
isActive: this._isActive,
isEnabled: this.enabled,
isHovered: this.hovered,
});
}
}
import { ASSERT } from '../../util/error_util';
import { UIUtil } from '../../util/ui_util';
import { BaseComponent } from './base';
export type TToolbarBooleanProperty = 'enabled' | 'active';
export type TToolbarItemParams = {
id: string,
iconSVG: string;
}
export class ToolbarItemComponent extends BaseComponent<HTMLDivElement> {
private _iconSVG: SVGSVGElement;
private _label: string;
private _onClick?: () => void;
private _isActive: boolean;
public constructor(params: TToolbarItemParams) {
super();
this._isActive = false;
{
const parser = new DOMParser();
const svgParse = parser.parseFromString(params.iconSVG, 'text/html');
const svgs = svgParse.getElementsByTagName('svg');
ASSERT(svgs.length === 1, 'Missing SVG');
this._iconSVG = svgs[0];
this._iconSVG.id = this._getId() + '-svg';
}
this._label = '';
}
public setActive(isActive: boolean) {
this._isActive = isActive;
this._updateStyles();
}
public setLabel(label: string) {
this._label = label;
return this;
}
public tick() {
if (this._isEnabledDelegate !== undefined) {
const newIsEnabled = this._isEnabledDelegate();
if (newIsEnabled != this.enabled) {
this.setEnabled(newIsEnabled);
this._updateStyles();
}
}
if (this._isActiveDelegate !== undefined) {
const newIsActive = this._isActiveDelegate();
if (newIsActive !== this._isActive) {
this._isActive = newIsActive;
this._updateStyles();
}
}
}
protected _onEnabledChanged(): void {
this._updateStyles();
}
private _isActiveDelegate?: () => boolean;
public isActive(delegate: () => boolean) {
this._isActiveDelegate = delegate;
return this;
}
private _isEnabledDelegate?: () => boolean;
public isEnabled(delegate: () => boolean) {
this._isEnabledDelegate = delegate;
return this;
}
public onClick(delegate: () => void) {
this._onClick = delegate;
return this;
}
public generateHTML() {
return `
<div class="struct-prop container-icon-button" id="${this._getId()}">
${this._iconSVG.outerHTML} ${this._label}
</div>
`;
}
public registerEvents(): void {
const element = document.getElementById(this._getId()) as HTMLDivElement;
ASSERT(element !== null);
element.addEventListener('click', () => {
if (this.enabled && this._onClick) {
this._onClick();
}
});
element.addEventListener('mouseenter', () => {
this._setHovered(true);
this._updateStyles();
});
element.addEventListener('mouseleave', () => {
this._setHovered(false);
this._updateStyles();
});
}
public override finalise(): void {
this._updateStyles();
}
private _getSVGElement() {
const svgId = this._getId() + '-svg';
return UIUtil.getElementById(svgId);
}
protected override _updateStyles() {
UIUtil.updateStyles(this._getElement(), {
isActive: this._isActive,
isEnabled: this.enabled,
isHovered: this.hovered,
});
}
}

View File

@ -1,193 +1,193 @@
import { ASSERT } from '../../util/error_util';
import { TAxis } from '../../util/type_util';
import { UIUtil } from '../../util/ui_util';
import { Vector3 } from '../../vector';
import { ConfigUIElement } from './config_element';
export class VectorSpinboxElement extends ConfigUIElement<Vector3, HTMLDivElement> {
private _mouseover: TAxis | null;
private _dragging: TAxis | null;
private _lastClientX: number;
private _showY: boolean;
private _wrap: number;
private _units: string | null;
public constructor() {
super(new Vector3(0, 0, 0));
this._mouseover = null;
this._dragging = null;
this._lastClientX = 0.0;
this._showY = true;
this._wrap = Infinity;
this._units = null;
}
/**
* Set whether or not the Y axis has a UI element
*/
public setShowY(showY: boolean) {
this._showY = showY;
return this;
}
public setWrap(wrap: number) {
this._wrap = wrap;
return this;
}
public setUnits(units: string) {
this._units = units;
return this;
}
protected override _generateInnerHTML() {
let html = '';
html += '<div class="spinbox-main-container">';
html += `
<div class="spinbox-element-container">
<div class="spinbox-key" id="${this._getKeyId('x')}">X</div>
<div class="spinbox-value struct-prop" id="${this._getValueId('x')}">
${this.getValue().x}
</div>
</div>
`;
if (this._showY) {
html += `
<div class="spinbox-element-container">
<div class="spinbox-key" id="${this._getKeyId('y')}">Y</div>
<div class="spinbox-value struct-prop" id="${this._getValueId('y')}">
${this.getValue().y}
</div>
</div>
`;
}
html += `
<div class="spinbox-element-container">
<div class="spinbox-key" id="${this._getKeyId('z')}">Z</div>
<div class="spinbox-value struct-prop" id="${this._getValueId('z')}">
${this.getValue().z}
</div>
</div>
`;
html += '</div>';
return html;
}
private _getKeyId(axis: TAxis) {
return this._getId() + '-k' + axis;
}
private _getValueId(axis: TAxis) {
return this._getId() + '-v' + axis;
}
private _registerAxis(axis: TAxis) {
const elementValue = UIUtil.getElementById(this._getValueId(axis));
elementValue.onmouseenter = () => {
this._mouseover = axis;
this._updateStyles();
};
elementValue.onmouseleave = () => {
this._mouseover = null;
this._updateStyles();
};
}
public registerEvents() {
this._registerAxis('x');
if (this._showY) {
this._registerAxis('y');
}
this._registerAxis('z');
document.addEventListener('mousedown', (e: any) => {
if (this.enabled && this._mouseover !== null) {
this._dragging = this._mouseover;
this._lastClientX = e.clientX;
}
});
document.addEventListener('mousemove', (e: any) => {
if (this.enabled && this._dragging !== null) {
this._updateValue(e);
}
});
document.addEventListener('mouseup', () => {
this._dragging = null;
this._updateStyles();
});
}
private _updateValue(e: MouseEvent) {
ASSERT(this.enabled, 'Not enabled');
ASSERT(this._dragging !== null, 'Dragging nothing');
const deltaX = e.clientX - this._lastClientX;
this._lastClientX = e.clientX;
const current = this.getValue().copy();
switch (this._dragging) {
case 'x':
current.x = (current.x + deltaX) % this._wrap;
break;
case 'y':
current.y = (current.y + deltaX) % this._wrap;
break;
case 'z':
current.z = (current.z + deltaX) % this._wrap;
break;
}
this._setValue(current);
}
protected override _updateStyles(): void {
const elementXV = UIUtil.getElementById(this._getValueId('x'));
const elementYV = UIUtil.getElementById(this._getValueId('y'));
const elementZV = UIUtil.getElementById(this._getValueId('z'));
// Update text
{
const current = this.getValue().copy();
elementXV.innerHTML = current.x.toString() + this._units;
if (elementYV) {
elementYV.innerHTML = current.y.toString() + this._units;
}
elementZV.innerHTML = current.z.toString() + this._units;
}
// Update styles
{
UIUtil.updateStyles(elementXV, {
isActive: false,
isEnabled: this.enabled,
isHovered: this._dragging === 'x' || (this._mouseover === 'x' && this._dragging === null),
});
UIUtil.updateStyles(elementYV, {
isActive: false,
isEnabled: this.enabled,
isHovered: this._dragging === 'y' || (this._mouseover === 'y' && this._dragging === null),
});
UIUtil.updateStyles(elementZV, {
isActive: false,
isEnabled: this.enabled,
isHovered: this._dragging === 'z' || (this._mouseover === 'z' && this._dragging === null),
});
}
}
protected override _onEnabledChanged() {
super._onEnabledChanged();
this._updateStyles();
}
protected override _onValueChanged(): void {
this._updateStyles();
}
}
import { ASSERT } from '../../util/error_util';
import { TAxis } from '../../util/type_util';
import { UIUtil } from '../../util/ui_util';
import { Vector3 } from '../../vector';
import { ConfigComponent } from './config';
export class VectorSpinboxComponent extends ConfigComponent<Vector3, HTMLDivElement> {
private _mouseover: TAxis | null;
private _dragging: TAxis | null;
private _lastClientX: number;
private _showY: boolean;
private _wrap: number;
private _units: string | null;
public constructor() {
super(new Vector3(0, 0, 0));
this._mouseover = null;
this._dragging = null;
this._lastClientX = 0.0;
this._showY = true;
this._wrap = Infinity;
this._units = null;
}
/**
* Set whether or not the Y axis has a UI element
*/
public setShowY(showY: boolean) {
this._showY = showY;
return this;
}
public setWrap(wrap: number) {
this._wrap = wrap;
return this;
}
public setUnits(units: string) {
this._units = units;
return this;
}
protected override _generateInnerHTML() {
let html = '';
html += '<div class="spinbox-main-container">';
html += `
<div class="spinbox-element-container">
<div class="spinbox-key" id="${this._getKeyId('x')}">X</div>
<div class="spinbox-value struct-prop" id="${this._getValueId('x')}">
${this.getValue().x}
</div>
</div>
`;
if (this._showY) {
html += `
<div class="spinbox-element-container">
<div class="spinbox-key" id="${this._getKeyId('y')}">Y</div>
<div class="spinbox-value struct-prop" id="${this._getValueId('y')}">
${this.getValue().y}
</div>
</div>
`;
}
html += `
<div class="spinbox-element-container">
<div class="spinbox-key" id="${this._getKeyId('z')}">Z</div>
<div class="spinbox-value struct-prop" id="${this._getValueId('z')}">
${this.getValue().z}
</div>
</div>
`;
html += '</div>';
return html;
}
private _getKeyId(axis: TAxis) {
return this._getId() + '-k' + axis;
}
private _getValueId(axis: TAxis) {
return this._getId() + '-v' + axis;
}
private _registerAxis(axis: TAxis) {
const elementValue = UIUtil.getElementById(this._getValueId(axis));
elementValue.onmouseenter = () => {
this._mouseover = axis;
this._updateStyles();
};
elementValue.onmouseleave = () => {
this._mouseover = null;
this._updateStyles();
};
}
public registerEvents() {
this._registerAxis('x');
if (this._showY) {
this._registerAxis('y');
}
this._registerAxis('z');
document.addEventListener('mousedown', (e: any) => {
if (this.enabled && this._mouseover !== null) {
this._dragging = this._mouseover;
this._lastClientX = e.clientX;
}
});
document.addEventListener('mousemove', (e: any) => {
if (this.enabled && this._dragging !== null) {
this._updateValue(e);
}
});
document.addEventListener('mouseup', () => {
this._dragging = null;
this._updateStyles();
});
}
private _updateValue(e: MouseEvent) {
ASSERT(this.enabled, 'Not enabled');
ASSERT(this._dragging !== null, 'Dragging nothing');
const deltaX = e.clientX - this._lastClientX;
this._lastClientX = e.clientX;
const current = this.getValue().copy();
switch (this._dragging) {
case 'x':
current.x = (current.x + deltaX) % this._wrap;
break;
case 'y':
current.y = (current.y + deltaX) % this._wrap;
break;
case 'z':
current.z = (current.z + deltaX) % this._wrap;
break;
}
this._setValue(current);
}
protected override _updateStyles(): void {
const elementXV = UIUtil.getElementById(this._getValueId('x'));
const elementYV = UIUtil.getElementById(this._getValueId('y'));
const elementZV = UIUtil.getElementById(this._getValueId('z'));
// Update text
{
const current = this.getValue().copy();
elementXV.innerHTML = current.x.toString() + this._units;
if (elementYV) {
elementYV.innerHTML = current.y.toString() + this._units;
}
elementZV.innerHTML = current.z.toString() + this._units;
}
// Update styles
{
UIUtil.updateStyles(elementXV, {
isActive: false,
isEnabled: this.enabled,
isHovered: this._dragging === 'x' || (this._mouseover === 'x' && this._dragging === null),
});
UIUtil.updateStyles(elementYV, {
isActive: false,
isEnabled: this.enabled,
isHovered: this._dragging === 'y' || (this._mouseover === 'y' && this._dragging === null),
});
UIUtil.updateStyles(elementZV, {
isActive: false,
isEnabled: this.enabled,
isHovered: this._dragging === 'z' || (this._mouseover === 'z' && this._dragging === null),
});
}
}
protected override _onEnabledChanged() {
super._onEnabledChanged();
this._updateStyles();
}
protected override _onValueChanged(): void {
this._updateStyles();
}
}

View File

@ -14,30 +14,30 @@ import { TDithering } from '../util/type_util';
import { UIUtil } from '../util/ui_util';
import { TVoxelOverlapRule } from '../voxel_mesh';
import { TVoxelisers } from '../voxelisers/voxelisers';
import { ButtonComponent } from './components/button';
import { CheckboxComponent } from './components/checkbox';
import { ComboboxComponent, ComboBoxItem } from './components/combobox';
import { ConfigComponent } from './components/config';
import { ObjFileComponent } from './components/file_input';
import { HeaderComponent } from './components/header';
import { PaletteComponent } from './components/palette';
import { SliderComponent } from './components/slider';
import { ToolbarItemComponent } from './components/toolbar_item';
import { VectorSpinboxComponent } from './components/vector_spinbox';
import { AppConsole } from './console';
import { ButtonElement } from './elements/button';
import { CheckboxElement } from './elements/checkbox';
import { ComboBoxElement, ComboBoxItem } from './elements/combobox';
import { ConfigUIElement } from './elements/config_element';
import { ObjFileInputElement } from './elements/file_input';
import { HeaderUIElement } from './elements/header_element';
import { PaletteElement } from './elements/palette_element';
import { SliderElement } from './elements/slider';
import { ToolbarItemElement } from './elements/toolbar_item';
import { VectorSpinboxElement } from './elements/vector_spinbox';
import { AppIcons } from './icons';
import { HTMLBuilder, MiscComponents } from './misc';
export interface Group {
export type Group = {
label: string;
elements: { [key: string]: ConfigUIElement<any, any> };
elementsOrder: string[];
submitButton: ButtonElement;
components: { [key: string]: ConfigComponent<any, any> };
componentOrder: string[];
execButton: ButtonComponent;
}
export interface ToolbarGroup {
elements: { [key: string]: ToolbarItemElement };
elementsOrder: string[];
components: { [key: string]: ToolbarItemComponent };
componentOrder: string[];
}
export class UI {
@ -45,16 +45,16 @@ export class UI {
private _ui = {
'import': {
label: '1. Import',
elements: {
'input': new ObjFileInputElement()
components: {
'input': new ObjFileComponent()
.setLabel('Wavefront .obj file'),
'rotation': new VectorSpinboxElement()
'rotation': new VectorSpinboxComponent()
.setLabel('Rotation')
.setWrap(360)
.setUnits('°'),
},
elementsOrder: ['input', 'rotation'],
submitButton: new ButtonElement()
componentOrder: ['input', 'rotation'],
execButton: new ButtonComponent()
.setOnClick(() => {
this._appContext.do(EAction.Import);
})
@ -62,10 +62,10 @@ export class UI {
},
'materials': {
label: '2. Materials',
elements: {
components: {
},
elementsOrder: [],
submitButton: new ButtonElement()
componentOrder: [],
execButton: new ButtonComponent()
.setOnClick(() => {
this._appContext.do(EAction.Materials);
})
@ -73,8 +73,8 @@ export class UI {
},
'voxelise': {
label: '3. Voxelise',
elements: {
'constraintAxis': new ComboBoxElement<TAxis>()
components: {
'constraintAxis': new ComboboxComponent<TAxis>()
.addItem({ payload: 'y', displayText: 'Y (height) (green)' })
.addItem({ payload: 'x', displayText: 'X (width) (red)' })
.addItem({ payload: 'z', displayText: 'Z (depth) (blue)' })
@ -83,40 +83,40 @@ export class UI {
/*
switch (value) {
case 'x':
this._ui.voxelise.elements.size.setMax(this._appContext.maxConstraint?.x ?? 400);
this._ui.voxelise.components.size.setMax(this._appContext.maxConstraint?.x ?? 400);
break;
case 'y':
this._ui.voxelise.elements.size.setMax(this._appContext.maxConstraint?.y ?? AppConfig.Get.CONSTRAINT_MAXIMUM_HEIGHT);
this._ui.voxelise.components.size.setMax(this._appContext.maxConstraint?.y ?? AppConfig.Get.CONSTRAINT_MAXIMUM_HEIGHT);
break;
case 'z':
this._ui.voxelise.elements.size.setMax(this._appContext.maxConstraint?.z ?? 400);
this._ui.voxelise.components.size.setMax(this._appContext.maxConstraint?.z ?? 400);
break;
}
*/
}),
'size': new SliderElement()
'size': new SliderComponent()
.setMin(3)
.setMax(380)
.setDefaultValue(80)
.setDecimals(0)
.setStep(1)
.setLabel('Size'),
'voxeliser': new ComboBoxElement<TVoxelisers>()
'voxeliser': new ComboboxComponent<TVoxelisers>()
.addItem({ payload: 'ray-based', displayText: 'Ray-based' })
.addItem({ payload: 'bvh-ray', displayText: 'BVH Ray-based' })
.addItem({ payload: 'ncrb', displayText: 'NCRB' })
.setLabel('Algorithm'),
'ambientOcclusion': new CheckboxElement()
'ambientOcclusion': new CheckboxComponent()
.setCheckedText('On (recommended)')
.setUncheckedText('Off (faster)')
.setDefaultValue(true)
.setLabel('Ambient occlusion'),
'multisampleColouring': new CheckboxElement()
'multisampleColouring': new CheckboxComponent()
.setCheckedText('On (recommended)')
.setUncheckedText('Off (faster)')
.setDefaultValue(true)
.setLabel('Multisampling'),
'voxelOverlapRule': new ComboBoxElement<TVoxelOverlapRule>()
'voxelOverlapRule': new ComboboxComponent<TVoxelOverlapRule>()
.addItem({
displayText: 'Average (recommended)',
payload: 'average',
@ -129,7 +129,7 @@ export class UI {
})
.setLabel('Voxel overlap'),
},
elementsOrder: [
componentOrder: [
'constraintAxis',
'size',
'voxeliser',
@ -137,7 +137,7 @@ export class UI {
'multisampleColouring',
'voxelOverlapRule',
],
submitButton: new ButtonElement()
execButton: new ButtonComponent()
.setOnClick(() => {
this._appContext.do(EAction.Voxelise);
})
@ -145,14 +145,14 @@ export class UI {
},
'assign': {
label: '4. Assign',
elements: {
'textureAtlas': new ComboBoxElement<string>()
.addItems(this._getTextureAtlases())
components: {
'textureAtlas': new ComboboxComponent<string>()
.addItem({ displayText: 'Vanilla', payload: 'vanilla' })
.setLabel('Texture atlas')
.setShouldObeyGroupEnables(false),
'blockPalette': new PaletteElement()
'blockPalette': new PaletteComponent()
.setLabel('Block palette'),
'dithering': new ComboBoxElement<TDithering>()
'dithering': new ComboboxComponent<TDithering>()
.addItems([{
displayText: 'Ordered',
payload: 'ordered',
@ -166,7 +166,7 @@ export class UI {
payload: 'off',
}])
.setLabel('Dithering'),
'fallable': new ComboBoxElement<FallableBehaviour>()
'fallable': new ComboboxComponent<FallableBehaviour>()
.addItems([{
displayText: 'Replace falling with solid',
payload: 'replace-falling',
@ -183,39 +183,39 @@ export class UI {
tooltip: 'Let the block fall',
}])
.setLabel('Fallable blocks'),
'colourAccuracy': new SliderElement()
'colourAccuracy': new SliderComponent()
.setMin(1)
.setMax(8)
.setDefaultValue(5)
.setDecimals(1)
.setStep(0.1)
.setLabel('Colour accuracy'),
'contextualAveraging': new CheckboxElement()
'contextualAveraging': new CheckboxComponent()
.setCheckedText('On (recommended)')
.setUncheckedText('Off (faster)')
.setDefaultValue(true)
.setLabel('Smart averaging'),
'errorWeight': new SliderElement()
'errorWeight': new SliderComponent()
.setMin(0.0)
.setMax(2.0)
.setDefaultValue(0.2)
.setDecimals(2)
.setStep(0.01)
.setLabel('Smoothness'),
'calculateLighting': new CheckboxElement()
'calculateLighting': new CheckboxComponent()
.setCheckedText('On')
.setUncheckedText('Off')
.setDefaultValue(false)
.setLabel('Calculate lighting')
.addValueChangedListener((newValue: boolean) => {
const isEnabled = this._ui.assign.elements.calculateLighting.getEnabled();
this._ui.assign.elements.lightThreshold.setEnabled(newValue && isEnabled, false);
const isEnabled = this._ui.assign.components.calculateLighting.getEnabled();
this._ui.assign.components.lightThreshold.setEnabled(newValue && isEnabled, false);
})
.addEnabledChangedListener((isEnabled: boolean) => {
const value = this._ui.assign.elements.calculateLighting.getValue();
this._ui.assign.elements.lightThreshold.setEnabled(value && isEnabled, false);
const value = this._ui.assign.components.calculateLighting.getValue();
this._ui.assign.components.lightThreshold.setEnabled(value && isEnabled, false);
}),
'lightThreshold': new SliderElement()
'lightThreshold': new SliderComponent()
.setMin(0)
.setMax(14)
.setDefaultValue(1)
@ -224,7 +224,7 @@ export class UI {
.setLabel('Light threshold')
.setShouldObeyGroupEnables(false),
},
elementsOrder: [
componentOrder: [
'textureAtlas',
'blockPalette',
'dithering',
@ -235,7 +235,7 @@ export class UI {
'calculateLighting',
'lightThreshold',
],
submitButton: new ButtonElement()
execButton: new ButtonComponent()
.setOnClick(() => {
this._appContext.do(EAction.Assign);
})
@ -243,8 +243,8 @@ export class UI {
},
'export': {
label: '5. Export',
elements: {
'export': new ComboBoxElement<TExporters>()
components: {
'export': new ComboboxComponent<TExporters>()
.addItems([
{
displayText: 'Litematic (.litematic)',
@ -271,8 +271,8 @@ export class UI {
])
.setLabel('Exporter'),
},
elementsOrder: ['export'],
submitButton: new ButtonElement()
componentOrder: ['export'],
execButton: new ButtonComponent()
.setLabel('Export structure')
.setOnClick(() => {
this._appContext.do(EAction.Export);
@ -283,8 +283,8 @@ export class UI {
private _toolbarLeft = {
groups: {
'viewmode': {
elements: {
'mesh': new ToolbarItemElement({ id: 'mesh', iconSVG: AppIcons.MESH })
components: {
'mesh': new ToolbarItemComponent({ id: 'mesh', iconSVG: AppIcons.MESH })
.onClick(() => {
Renderer.Get.setModelToUse(MeshType.TriangleMesh);
})
@ -294,7 +294,7 @@ export class UI {
.isEnabled(() => {
return Renderer.Get.getModelsAvailable() >= MeshType.TriangleMesh;
}),
'voxelMesh': new ToolbarItemElement({ id: 'voxelMesh', iconSVG: AppIcons.VOXEL })
'voxelMesh': new ToolbarItemComponent({ id: 'voxelMesh', iconSVG: AppIcons.VOXEL })
.onClick(() => {
Renderer.Get.setModelToUse(MeshType.VoxelMesh);
})
@ -304,7 +304,7 @@ export class UI {
.isEnabled(() => {
return Renderer.Get.getModelsAvailable() >= MeshType.VoxelMesh;
}),
'blockMesh': new ToolbarItemElement({ id: 'blockMesh', iconSVG: AppIcons.BLOCK })
'blockMesh': new ToolbarItemComponent({ id: 'blockMesh', iconSVG: AppIcons.BLOCK })
.onClick(() => {
Renderer.Get.setModelToUse(MeshType.BlockMesh);
})
@ -315,11 +315,11 @@ export class UI {
return Renderer.Get.getModelsAvailable() >= MeshType.BlockMesh;
}),
},
elementsOrder: ['mesh', 'voxelMesh', 'blockMesh'],
componentOrder: ['mesh', 'voxelMesh', 'blockMesh'],
},
'debug': {
elements: {
'grid': new ToolbarItemElement({ id: 'grid', iconSVG: AppIcons.GRID })
components: {
'grid': new ToolbarItemComponent({ id: 'grid', iconSVG: AppIcons.GRID })
.onClick(() => {
Renderer.Get.toggleIsGridEnabled();
})
@ -329,14 +329,14 @@ export class UI {
.isEnabled(() => {
return Renderer.Get.getActiveMeshType() !== MeshType.None;
}),
'axes': new ToolbarItemElement({ id: 'axes', iconSVG: AppIcons.AXES })
'axes': new ToolbarItemComponent({ id: 'axes', iconSVG: AppIcons.AXES })
.onClick(() => {
Renderer.Get.toggleIsAxesEnabled();
})
.isActive(() => {
return Renderer.Get.isAxesEnabled();
}),
'night-vision': new ToolbarItemElement({ id: 'night', iconSVG: AppIcons.BULB })
'night-vision': new ToolbarItemComponent({ id: 'night', iconSVG: AppIcons.BULB })
.onClick(() => {
Renderer.Get.toggleIsNightVisionEnabled();
})
@ -347,7 +347,7 @@ export class UI {
return Renderer.Get.canToggleNightVision();
}),
},
elementsOrder: ['grid', 'axes', 'night-vision'],
componentOrder: ['grid', 'axes', 'night-vision'],
},
},
@ -357,32 +357,32 @@ export class UI {
private _toolbarRight = {
groups: {
'zoom': {
elements: {
'zoomOut': new ToolbarItemElement({ id: 'zout', iconSVG: AppIcons.MINUS })
components: {
'zoomOut': new ToolbarItemComponent({ id: 'zout', iconSVG: AppIcons.MINUS })
.onClick(() => {
ArcballCamera.Get.onZoomOut();
}),
'zoomIn': new ToolbarItemElement({ id: 'zin', iconSVG: AppIcons.PLUS })
'zoomIn': new ToolbarItemComponent({ id: 'zin', iconSVG: AppIcons.PLUS })
.onClick(() => {
ArcballCamera.Get.onZoomIn();
}),
'reset': new ToolbarItemElement({ id: 'reset', iconSVG: AppIcons.CENTRE })
'reset': new ToolbarItemComponent({ id: 'reset', iconSVG: AppIcons.CENTRE })
.onClick(() => {
ArcballCamera.Get.reset();
}),
},
elementsOrder: ['zoomOut', 'zoomIn', 'reset'],
componentOrder: ['zoomOut', 'zoomIn', 'reset'],
},
'camera': {
elements: {
'perspective': new ToolbarItemElement({ id: 'pers', iconSVG: AppIcons.PERSPECTIVE })
components: {
'perspective': new ToolbarItemComponent({ id: 'pers', iconSVG: AppIcons.PERSPECTIVE })
.onClick(() => {
ArcballCamera.Get.setCameraMode('perspective');
})
.isActive(() => {
return ArcballCamera.Get.isPerspective();
}),
'orthographic': new ToolbarItemElement({ id: 'orth', iconSVG: AppIcons.ORTHOGRAPHIC })
'orthographic': new ToolbarItemComponent({ id: 'orth', iconSVG: AppIcons.ORTHOGRAPHIC })
.onClick(() => {
ArcballCamera.Get.setCameraMode('orthographic');
})
@ -390,7 +390,7 @@ export class UI {
return ArcballCamera.Get.isOrthographic();
}),
},
elementsOrder: ['perspective', 'orthographic'],
componentOrder: ['perspective', 'orthographic'],
},
},
groupsOrder: ['camera', 'zoom'],
@ -422,15 +422,15 @@ export class UI {
for (const groupName in this._toolbarLeftDull) {
const toolbarGroup = this._toolbarLeftDull[groupName];
for (const toolbarItem of toolbarGroup.elementsOrder) {
toolbarGroup.elements[toolbarItem].tick();
for (const toolbarItem of toolbarGroup.componentOrder) {
toolbarGroup.components[toolbarItem].tick();
}
}
for (const groupName in this._toolbarRightDull) {
const toolbarGroup = this._toolbarRightDull[groupName];
for (const toolbarItem of toolbarGroup.elementsOrder) {
toolbarGroup.elements[toolbarItem].tick();
for (const toolbarItem of toolbarGroup.componentOrder) {
toolbarGroup.components[toolbarItem].tick();
}
}
}
@ -442,7 +442,7 @@ export class UI {
sidebarHTML.add(`<div class="container-properties">`);
{
sidebarHTML.add(HeaderUIElement.Get.generateHTML());
sidebarHTML.add(HeaderComponent.Get.generateHTML());
for (const groupName of this.uiOrder) {
const group = this._uiDull[groupName];
@ -463,8 +463,8 @@ export class UI {
for (const toolbarGroupName of this._toolbarLeft.groupsOrder) {
toolbarHTML.add('<div class="toolbar-group">');
const toolbarGroup = this._toolbarLeftDull[toolbarGroupName];
for (const groupElementName of toolbarGroup.elementsOrder) {
const groupElement = toolbarGroup.elements[groupElementName];
for (const groupElementName of toolbarGroup.componentOrder) {
const groupElement = toolbarGroup.components[groupElementName];
toolbarHTML.add(groupElement.generateHTML());
}
toolbarHTML.add('</div>');
@ -476,8 +476,8 @@ export class UI {
for (const toolbarGroupName of this._toolbarRight.groupsOrder) {
toolbarHTML.add('<div class="toolbar-group">');
const toolbarGroup = this._toolbarRightDull[toolbarGroupName];
for (const groupElementName of toolbarGroup.elementsOrder) {
const groupElement = toolbarGroup.elements[groupElementName];
for (const groupElementName of toolbarGroup.componentOrder) {
const groupElement = toolbarGroup.components[groupElementName];
toolbarHTML.add(groupElement.generateHTML());
}
toolbarHTML.add('</div>');
@ -504,32 +504,44 @@ export class UI {
});
}
public cacheValues(action: EAction) {
const group = this._getEActionGroup(action);
for (const elementName of group.elementsOrder) {
LOG(`[UI]: Caching ${elementName}`);
const element = group.elements[elementName];
element.cacheValue();
private _forEachComponent(action: EAction, functor: (component: ConfigComponent<unknown, unknown>) => void) {
const group = this._getGroup(action);
for (const elementName of group.componentOrder) {
const element = group.components[elementName];
functor(element);
}
}
public refreshSubcomponents(group: Group) {
/**
* Caches the current value of each component in an action group.
*/
public cacheValues(action: EAction) {
this._forEachComponent(action, (component) => {
component.cacheValue();
});
}
/**
* Rebuilds the HTML for all components in an action group.
*/
public refreshComponents(action: EAction) {
const group = this._getGroup(action);
const element = document.getElementById(`subcomponents_${group.label}`);
ASSERT(element !== null);
element.innerHTML = this._getGroupSubcomponentsHTML(group);
element.innerHTML = this._getComponentsHTML(group);
for (const elementName in group.elements) {
const element = group.elements[elementName];
element.registerEvents();
element.finalise();
}
this._forEachComponent(action, (component) => {
component.registerEvents();
component.finalise();
});
}
private _getGroupSubcomponentsHTML(group: Group) {
private _getComponentsHTML(group: Group) {
let groupHTML = '';
for (const elementName of group.elementsOrder) {
const element = group.elements[elementName];
for (const elementName of group.componentOrder) {
const element = group.components[elementName];
ASSERT(element !== undefined, `No element for: ${elementName}`);
groupHTML += element.generateHTML();
}
@ -540,37 +552,37 @@ export class UI {
return `
${MiscComponents.createGroupHeader(group.label.toUpperCase())}
<div class="component-group" id="subcomponents_${group.label}">
${this._getGroupSubcomponentsHTML(group)}
${this._getComponentsHTML(group)}
</div>
${group.submitButton.generateHTML()}
${group.execButton.generateHTML()}
`;
}
public getActionButton(action: EAction) {
const group = this._getEActionGroup(action);
return group.submitButton;
const group = this._getGroup(action);
return group.execButton;
}
public registerEvents() {
HeaderUIElement.Get.registerEvents();
HeaderUIElement.Get.finalise();
HeaderComponent.Get.registerEvents();
HeaderComponent.Get.finalise();
for (const groupName in this._ui) {
const group = this._uiDull[groupName];
for (const elementName in group.elements) {
const element = group.elements[elementName];
element.registerEvents();
element.finalise();
}
group.submitButton.registerEvents();
group.submitButton.finalise();
for (let action = 0; action < EAction.MAX; ++action) {
this._forEachComponent(action, (component) => {
component.registerEvents();
component.finalise();
});
const group = this._getGroup(action);
group.execButton.registerEvents();
group.execButton.finalise();
}
// Register toolbar left
for (const toolbarGroupName of this._toolbarLeft.groupsOrder) {
const toolbarGroup = this._toolbarLeftDull[toolbarGroupName];
for (const groupElementName of toolbarGroup.elementsOrder) {
const element = toolbarGroup.elements[groupElementName];
for (const groupElementName of toolbarGroup.componentOrder) {
const element = toolbarGroup.components[groupElementName];
element.registerEvents();
element.finalise();
}
@ -578,8 +590,8 @@ export class UI {
// Register toolbar right
for (const toolbarGroupName of this._toolbarRight.groupsOrder) {
const toolbarGroup = this._toolbarRightDull[toolbarGroupName];
for (const groupElementName of toolbarGroup.elementsOrder) {
const element = toolbarGroup.elements[groupElementName];
for (const groupElementName of toolbarGroup.componentOrder) {
const element = toolbarGroup.components[groupElementName];
element.registerEvents();
element.finalise();
}
@ -594,79 +606,50 @@ export class UI {
return this._uiDull;
}
/**
* Enable a specific action.
*/
public enable(action: EAction) {
this._forEachComponent(action, (component) => {
component.setEnabled(true);
});
this._getGroup(action).execButton.setEnabled(true);
}
/**
* Enable all actions up to and including a specific action.
*/
public enableTo(action: EAction) {
for (let i = 0; i <= action; ++i) {
this.enable(i);
}
}
public enable(action: EAction) {
if (action >= EAction.MAX) {
return;
}
/**
* Disable a specific action and its dependent actions.
*/
public disable(action: EAction) {
for (let i = action; i < EAction.MAX; ++i) {
this._forEachComponent(action, (component) => {
component.setEnabled(false);
});
LOG('[UI]: Enabling', action);
const group = this._getEActionGroup(action);
for (const compName in group.elements) {
group.elements[compName].setEnabled(true);
this._getGroup(action).execButton.setEnabled(false);
}
group.submitButton.setEnabled(true);
}
/**
* Disables all the actions.
*/
public disableAll() {
this.disable(EAction.Import);
}
public disable(action: EAction) {
if (action < 0) {
return;
}
for (let i = action; i < EAction.MAX; ++i) {
const group = this._getEActionGroup(i);
//LOG('[UI]: Disabling', group.label);
for (const compName in group.elements) {
group.elements[compName].setEnabled(false);
}
group.submitButton.setEnabled(false);
}
}
private _getEActionGroup(action: EAction): Group {
/**
* Util function to get the `Group` associated with an `EAction`.
*/
private _getGroup(action: EAction): Group {
const key = this.uiOrder[action];
return this._uiDull[key];
}
private _getTextureAtlases(): ComboBoxItem<string>[] {
const textureAtlases: ComboBoxItem<string>[] = [];
// TODO Unimplemented
/*
fs.readdirSync(AppPaths.Get.atlases).forEach((file) => {
if (file.endsWith('.atlas')) {
const paletteID = file.split('.')[0];
let paletteName = paletteID.replace('-', ' ').toLowerCase();
paletteName = paletteName.charAt(0).toUpperCase() + paletteName.slice(1);
textureAtlases.push({ payload: paletteID, displayText: paletteName });
}
});
*/
textureAtlases.push({ payload: 'vanilla', displayText: 'Vanilla ' });
return textureAtlases;
}
private _getBlockPalettes(): ComboBoxItem<TPalettes>[] {
const blockPalettes: ComboBoxItem<TPalettes>[] = [];
const palettes = PaletteManager.getPalettesInfo();
for (const palette of palettes) {
blockPalettes.push({
payload: palette.id,
displayText: palette.name,
});
}
return blockPalettes;
}
}

View File

@ -512,7 +512,7 @@ svg {
padding-left: 10px;
color: var(--text-dim)
}
.checkbox-text-hover {
.checkbox-text.text-light {
cursor: pointer;
}