mirror of
https://github.com/LucasDower/ObjToSchematic.git
synced 2024-11-27 02:19:58 +08:00
Major UI refactor
This commit is contained in:
parent
b04f381de5
commit
a34089d339
@ -11,7 +11,6 @@ import { ExporterFactory, TExporters } from './exporters/exporters';
|
||||
import { MaterialMap, MaterialType, SolidMaterial, TexturedMaterial } from './mesh';
|
||||
import { Renderer } from './renderer';
|
||||
import { StatusHandler, StatusMessage } from './status';
|
||||
import { TextureFiltering } from './texture';
|
||||
import { SolidMaterialUIElement, TextureMaterialUIElement } from './ui/elements/material';
|
||||
import { OutputStyle } from './ui/elements/output';
|
||||
import { UI } from './ui/layout';
|
||||
@ -185,7 +184,7 @@ export class AppContext {
|
||||
const payload: TToWorkerMessage = {
|
||||
action: 'Import',
|
||||
params: {
|
||||
filepath: uiElements.input.getCachedValue(),
|
||||
filepath: uiElements.input.getValue(),
|
||||
},
|
||||
};
|
||||
|
||||
@ -443,12 +442,12 @@ export class AppContext {
|
||||
const payload: TToWorkerMessage = {
|
||||
action: 'Voxelise',
|
||||
params: {
|
||||
constraintAxis: uiElements.constraintAxis.getCachedValue(),
|
||||
voxeliser: uiElements.voxeliser.getCachedValue(),
|
||||
size: uiElements.size.getCachedValue(),
|
||||
useMultisampleColouring: uiElements.multisampleColouring.getCachedValue(),
|
||||
enableAmbientOcclusion: uiElements.ambientOcclusion.getCachedValue(),
|
||||
voxelOverlapRule: uiElements.voxelOverlapRule.getCachedValue(),
|
||||
constraintAxis: uiElements.constraintAxis.getValue(),
|
||||
voxeliser: uiElements.voxeliser.getValue(),
|
||||
size: uiElements.size.getValue(),
|
||||
useMultisampleColouring: uiElements.multisampleColouring.getValue(),
|
||||
enableAmbientOcclusion: uiElements.ambientOcclusion.getValue(),
|
||||
voxelOverlapRule: uiElements.voxelOverlapRule.getValue(),
|
||||
},
|
||||
};
|
||||
|
||||
@ -471,8 +470,8 @@ export class AppContext {
|
||||
const payload: TToWorkerMessage = {
|
||||
action: 'RenderNextVoxelMeshChunk',
|
||||
params: {
|
||||
enableAmbientOcclusion: uiElements.ambientOcclusion.getCachedValue(),
|
||||
desiredHeight: uiElements.size.getCachedValue(),
|
||||
enableAmbientOcclusion: uiElements.ambientOcclusion.getValue(),
|
||||
desiredHeight: uiElements.size.getValue(),
|
||||
},
|
||||
};
|
||||
|
||||
@ -522,21 +521,21 @@ export class AppContext {
|
||||
this._ui.getActionOutput(EAction.Assign)
|
||||
.setTaskInProgress('action', '[Block Mesh]: Loading...');
|
||||
|
||||
Renderer.Get.setLightingAvailable(uiElements.calculateLighting.getCachedValue());
|
||||
Renderer.Get.setLightingAvailable(uiElements.calculateLighting.getValue());
|
||||
|
||||
const payload: TToWorkerMessage = {
|
||||
action: 'Assign',
|
||||
params: {
|
||||
textureAtlas: uiElements.textureAtlas.getCachedValue(),
|
||||
blockPalette: uiElements.blockPalette.getCachedValue(),
|
||||
dithering: uiElements.dithering.getCachedValue(),
|
||||
textureAtlas: uiElements.textureAtlas.getValue(),
|
||||
blockPalette: uiElements.blockPalette.getValue(),
|
||||
dithering: uiElements.dithering.getValue(),
|
||||
colourSpace: ColourSpace.RGB,
|
||||
fallable: uiElements.fallable.getCachedValue() as FallableBehaviour,
|
||||
resolution: Math.pow(2, uiElements.colourAccuracy.getCachedValue()),
|
||||
calculateLighting: uiElements.calculateLighting.getCachedValue(),
|
||||
lightThreshold: uiElements.lightThreshold.getCachedValue(),
|
||||
contextualAveraging: uiElements.contextualAveraging.getCachedValue(),
|
||||
errorWeight: uiElements.errorWeight.getCachedValue() / 10,
|
||||
fallable: uiElements.fallable.getValue() as FallableBehaviour,
|
||||
resolution: Math.pow(2, uiElements.colourAccuracy.getValue()),
|
||||
calculateLighting: uiElements.calculateLighting.getValue(),
|
||||
lightThreshold: uiElements.lightThreshold.getValue(),
|
||||
contextualAveraging: uiElements.contextualAveraging.getValue(),
|
||||
errorWeight: uiElements.errorWeight.getValue() / 10,
|
||||
},
|
||||
};
|
||||
|
||||
@ -560,7 +559,7 @@ export class AppContext {
|
||||
const payload: TToWorkerMessage = {
|
||||
action: 'RenderNextBlockMeshChunk',
|
||||
params: {
|
||||
textureAtlas: uiElements.textureAtlas.getCachedValue(),
|
||||
textureAtlas: uiElements.textureAtlas.getValue(),
|
||||
},
|
||||
};
|
||||
|
||||
@ -605,7 +604,7 @@ export class AppContext {
|
||||
}
|
||||
|
||||
private _export(): (TWorkerJob | undefined) {
|
||||
const exporterID: TExporters = this._ui.layout.export.elements.export.getCachedValue();
|
||||
const exporterID: TExporters = this._ui.layout.export.elements.export.getValue();
|
||||
const exporter: IExporter = ExporterFactory.GetExporter(exporterID);
|
||||
|
||||
const filepath = remote.dialog.showSaveDialogSync({
|
||||
|
@ -1,49 +0,0 @@
|
||||
import { ASSERT } from '../../util/error_util';
|
||||
|
||||
export abstract class BaseUIElement<Type> {
|
||||
protected _id: string;
|
||||
protected _label: string;
|
||||
protected _isEnabled: boolean;
|
||||
protected _value?: Type;
|
||||
protected _cachedValue?: any;
|
||||
|
||||
constructor(label: string) {
|
||||
this._id = '_' + Math.random().toString(16);
|
||||
this._label = label;
|
||||
this._isEnabled = true;
|
||||
}
|
||||
|
||||
public setEnabled(isEnabled: boolean, isGroupEnable: boolean = true) {
|
||||
if (isEnabled && isGroupEnable && !this._obeyGroupEnables) {
|
||||
return;
|
||||
}
|
||||
this._isEnabled = isEnabled;
|
||||
this._onEnabledChanged();
|
||||
}
|
||||
|
||||
public getCachedValue(): Type {
|
||||
ASSERT(this._cachedValue !== undefined, 'Attempting to access value before cached');
|
||||
return this._cachedValue as Type;
|
||||
}
|
||||
|
||||
protected getValue(): Type {
|
||||
ASSERT(this._value !== undefined);
|
||||
return this._value;
|
||||
}
|
||||
|
||||
public cacheValue() {
|
||||
this._cachedValue = this.getValue();
|
||||
}
|
||||
|
||||
public abstract generateHTML(): string;
|
||||
public abstract registerEvents(): void;
|
||||
|
||||
|
||||
protected abstract _onEnabledChanged(): void;
|
||||
|
||||
private _obeyGroupEnables: boolean = true;
|
||||
public setObeyGroupEnables(shouldListen: boolean) {
|
||||
this._obeyGroupEnables = shouldListen;
|
||||
return this;
|
||||
}
|
||||
}
|
85
src/ui/elements/base_element.ts
Normal file
85
src/ui/elements/base_element.ts
Normal file
@ -0,0 +1,85 @@
|
||||
import { getRandomID } from '../../util';
|
||||
import { UIUtil } from '../../util/ui_util';
|
||||
|
||||
/**
|
||||
* The base UI class from which user interactable DOM elements are built from.
|
||||
* Each `BaseUIElement` can be enabled/disabled.
|
||||
*/
|
||||
export abstract class BaseUIElement<T> {
|
||||
private _id: string;
|
||||
private _isEnabled: boolean;
|
||||
private _obeyGroupEnables: boolean;
|
||||
|
||||
public constructor() {
|
||||
this._id = getRandomID();
|
||||
this._isEnabled = true;
|
||||
this._obeyGroupEnables = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether or not this UI element is interactable.
|
||||
*/
|
||||
public getEnabled() {
|
||||
return this._isEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether or not this UI element is interactable.
|
||||
*/
|
||||
public setEnabled(isEnabled: boolean, isGroupEnable: boolean = true) {
|
||||
if (isGroupEnable && !this._obeyGroupEnables) {
|
||||
return;
|
||||
}
|
||||
this._isEnabled = isEnabled;
|
||||
this._onEnabledChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether or not this element should be enabled when the group
|
||||
* is it apart of becomes enabled. This is useful if an element should
|
||||
* only be enabled if another element has a particular value. If this is
|
||||
* false then there needs to be a some event added to manually enable this
|
||||
* element.
|
||||
*/
|
||||
public setShouldObeyGroupEnables(obey: boolean) {
|
||||
this._obeyGroupEnables = obey;
|
||||
}
|
||||
|
||||
/**
|
||||
* The actual HTML that represents this UI element. It is recommended to
|
||||
* give the outermost element that ID generated for this BaseUIElement so
|
||||
* that `getElement()` returns all elements created here.
|
||||
*/
|
||||
public abstract generateHTML(): string;
|
||||
|
||||
/**
|
||||
* A delegate that is called after the UI element has been added to the DOM.
|
||||
* Calls to `addEventListener` should be placed here.
|
||||
*/
|
||||
public abstract registerEvents(): void;
|
||||
|
||||
public finalise(): void {
|
||||
this._onEnabledChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the actual DOM element that this BaseUIElement refers to.
|
||||
* Calling this before the element is created (i.e. before `generateHTML`)
|
||||
* is called will throw an error.
|
||||
*/
|
||||
protected _getElement() {
|
||||
return UIUtil.getElementById(this._id) as T;
|
||||
}
|
||||
|
||||
/**
|
||||
* Each BaseUIElement is assignd an ID that can be used a DOM element with.
|
||||
*/
|
||||
protected _getId() {
|
||||
return this._id;
|
||||
}
|
||||
|
||||
/**
|
||||
* A delegate that is called when the enabled status is changed.
|
||||
*/
|
||||
protected abstract _onEnabledChanged(): void;
|
||||
}
|
@ -1,59 +1,53 @@
|
||||
import { ASSERT } from '../../util/error_util';
|
||||
import { BaseUIElement } from './base';
|
||||
import { UIUtil } from '../../util/ui_util';
|
||||
import { BaseUIElement } from './base_element';
|
||||
|
||||
export class ButtonElement extends BaseUIElement<any> {
|
||||
export class ButtonElement extends BaseUIElement<HTMLDivElement> {
|
||||
private _label: string;
|
||||
private _onClick: () => void;
|
||||
|
||||
public constructor(label: string, onClick: () => void) {
|
||||
super(label);
|
||||
this._onClick = onClick;
|
||||
this._isEnabled = true;
|
||||
public constructor() {
|
||||
super();
|
||||
this._label = 'Unknown';
|
||||
this._onClick = () => { };
|
||||
}
|
||||
|
||||
public generateHTML() {
|
||||
return `
|
||||
<div class="button" id="${this._id}">
|
||||
<div class="button-label">${this._label}</div>
|
||||
<div class="button-progress" id="${this._id}-progress"></div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
public registerEvents(): void {
|
||||
const element = document.getElementById(this._id) as HTMLDivElement;
|
||||
ASSERT(element !== null);
|
||||
|
||||
element.addEventListener('click', () => {
|
||||
if (this._isEnabled) {
|
||||
this._onClick();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected _onEnabledChanged() {
|
||||
const element = document.getElementById(this._id) as HTMLDivElement;
|
||||
ASSERT(element !== null);
|
||||
|
||||
if (this._isEnabled) {
|
||||
element.classList.remove('button-disabled');
|
||||
} else {
|
||||
element.classList.add('button-disabled');
|
||||
}
|
||||
}
|
||||
|
||||
public setLabelOverride(label: string) {
|
||||
const element = document.getElementById(this._id) as HTMLDivElement;
|
||||
ASSERT(element !== null, 'Updating label override of element that does not exist');
|
||||
|
||||
element.innerHTML = label;
|
||||
/**
|
||||
* Sets the delegate that is called when this button is clicked.
|
||||
*/
|
||||
public setOnClick(delegate: () => void) {
|
||||
this._onClick = delegate;
|
||||
return this;
|
||||
}
|
||||
|
||||
public removeLabelOverride() {
|
||||
const element = document.getElementById(this._id) as HTMLDivElement;
|
||||
ASSERT(element !== null, 'Removing label override of element that does not exist');
|
||||
/**
|
||||
* Sets the label of this button.
|
||||
*/
|
||||
public setLabel(label: string) {
|
||||
this._label = label;
|
||||
return this;
|
||||
}
|
||||
|
||||
element.innerHTML = this._label;
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
@ -62,26 +56,48 @@ export class ButtonElement extends BaseUIElement<any> {
|
||||
* @param progress A number between 0.0 and 1.0 inclusive.
|
||||
*/
|
||||
public setProgress(progress: number) {
|
||||
const element = document.getElementById(this._id + '-progress') as HTMLDivElement;
|
||||
ASSERT(element !== null);
|
||||
|
||||
element.style.width = `${progress * 100}%`;
|
||||
return this;
|
||||
}
|
||||
|
||||
public startLoading() {
|
||||
const element = document.getElementById(this._id) as HTMLDivElement;
|
||||
ASSERT(element !== null);
|
||||
|
||||
element.classList.add('button-loading');
|
||||
const progressBarElement = UIUtil.getElementById(this._getProgressBarId());
|
||||
progressBarElement.style.width = `${progress * 100}%`;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the loading animation
|
||||
*/
|
||||
public stopLoading() {
|
||||
const element = document.getElementById(this._id) as HTMLDivElement;
|
||||
ASSERT(element !== null);
|
||||
|
||||
element.classList.remove('button-loading');
|
||||
this._getElement().classList.remove('button-loading');
|
||||
return this;
|
||||
}
|
||||
|
||||
public override generateHTML() {
|
||||
return `
|
||||
<div class="button" id="${this._getId()}">
|
||||
<div class="button-label">${this._label}</div>
|
||||
<div class="button-progress" id="${this._getProgressBarId()}"></div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
public override registerEvents(): void {
|
||||
this._getElement().addEventListener('click', () => {
|
||||
if (this.getEnabled()) {
|
||||
this._onClick?.();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected override _onEnabledChanged() {
|
||||
if (this.getEnabled()) {
|
||||
this._getElement().classList.remove('button-disabled');
|
||||
} else {
|
||||
this._getElement().classList.add('button-disabled');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the ID of the DOM element for the button's progress bar.
|
||||
*/
|
||||
private _getProgressBarId() {
|
||||
return this._getId() + '-progress';
|
||||
}
|
||||
}
|
||||
|
@ -1,95 +1,86 @@
|
||||
import { getRandomID } from '../../util';
|
||||
import { ASSERT } from '../../util/error_util';
|
||||
import { LabelledElement } from './labelled_element';
|
||||
import { UIUtil } from '../../util/ui_util';
|
||||
import { ConfigUIElement } from './config_element';
|
||||
|
||||
export class CheckboxElement extends LabelledElement<boolean> {
|
||||
private _checkboxId: string;
|
||||
private _checkboxPipId: string;
|
||||
private _checkboxTextId: string;
|
||||
private _onText: string;
|
||||
private _offText: string;
|
||||
export class CheckboxElement extends ConfigUIElement<boolean, HTMLSelectElement> {
|
||||
private _labelChecked: string;
|
||||
private _labelUnchecked: string;
|
||||
|
||||
public constructor(label: string, value: boolean, onText: string, offText: string) {
|
||||
super(label);
|
||||
this._checkboxId = getRandomID();
|
||||
this._checkboxPipId = getRandomID();
|
||||
this._checkboxTextId = getRandomID();
|
||||
this._value = value;
|
||||
this._onText = onText;
|
||||
this._offText = offText;
|
||||
public constructor() {
|
||||
super(false);
|
||||
this._labelChecked = 'On';
|
||||
this._labelUnchecked = 'Off';
|
||||
}
|
||||
|
||||
protected generateInnerHTML(): string {
|
||||
return `
|
||||
<div class="checkbox" id="${this._checkboxId}">
|
||||
<svg id="${this._checkboxPipId}" xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-check" width="44" height="44" viewBox="0 0 24 24" stroke-width="1.5" stroke="#2c3e50" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M5 12l5 5l10 -10" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="checkbox-text" id="${this._checkboxTextId}">${this.getValue() ? this._onText : this._offText}</div>
|
||||
`;
|
||||
public setCheckedText(label: string) {
|
||||
this._labelChecked = label;
|
||||
return this;
|
||||
}
|
||||
|
||||
public registerEvents(): void {
|
||||
const checkboxElement = document.getElementById(this._checkboxId);
|
||||
const checkboxPipElement = document.getElementById(this._checkboxPipId);
|
||||
ASSERT(checkboxElement !== null && checkboxPipElement !== null);
|
||||
public setUncheckedText(label: string) {
|
||||
this._labelUnchecked = label;
|
||||
return this;
|
||||
}
|
||||
|
||||
checkboxElement?.addEventListener('mouseenter', () => {
|
||||
if (this._isEnabled) {
|
||||
public override registerEvents(): void {
|
||||
const checkboxElement = this._getElement();
|
||||
const checkboxPipElement = UIUtil.getElementById(this._getPipId());
|
||||
|
||||
checkboxElement.addEventListener('mouseenter', () => {
|
||||
if (this.getEnabled()) {
|
||||
checkboxElement.classList.add('checkbox-hover');
|
||||
checkboxPipElement.classList.add('checkbox-pip-hover');
|
||||
}
|
||||
});
|
||||
|
||||
checkboxElement?.addEventListener('mouseleave', () => {
|
||||
if (this._isEnabled) {
|
||||
checkboxElement.addEventListener('mouseleave', () => {
|
||||
if (this.getEnabled()) {
|
||||
checkboxElement.classList.remove('checkbox-hover');
|
||||
checkboxPipElement.classList.remove('checkbox-pip-hover');
|
||||
}
|
||||
});
|
||||
|
||||
checkboxElement.addEventListener('click', () => {
|
||||
if (this._isEnabled) {
|
||||
this._value = !this._value;
|
||||
this._onValueChanged();
|
||||
if (this.getEnabled()) {
|
||||
this._setValue(!this.getValue());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private _onValueChanged() {
|
||||
const checkboxElement = document.getElementById(this._checkboxId);
|
||||
const checkboxPipElement = document.getElementById(this._checkboxPipId);
|
||||
ASSERT(checkboxElement !== null && checkboxPipElement !== null);
|
||||
const checkboxTextElement = document.getElementById(this._checkboxTextId);
|
||||
ASSERT(checkboxTextElement !== null);
|
||||
protected override _generateInnerHTML(): string {
|
||||
return `
|
||||
<div class="checkbox" id="${this._getId()}">
|
||||
<svg id="${this._getPipId()}" xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-check" width="44" height="44" viewBox="0 0 24 24" stroke-width="1.5" stroke="#2c3e50" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M5 12l5 5l10 -10" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="checkbox-text" id="${this._getLabelId()}">${this.getValue() ? this._labelChecked : this._labelUnchecked}</div>
|
||||
`;
|
||||
}
|
||||
|
||||
checkboxTextElement.innerHTML = this.getValue() ? this._onText : this._offText;
|
||||
protected override _onValueChanged(): void {
|
||||
const checkboxElement = this._getElement();
|
||||
const checkboxPipElement = UIUtil.getElementById(this._getPipId());
|
||||
const checkboxTextElement = UIUtil.getElementById(this._getLabelId());
|
||||
|
||||
checkboxTextElement.innerHTML = this.getValue() ? this._labelChecked : this._labelUnchecked;
|
||||
checkboxPipElement.style.visibility = this.getValue() ? 'visible' : 'hidden';
|
||||
|
||||
if (this._isEnabled) {
|
||||
if (this.getEnabled()) {
|
||||
checkboxElement.classList.remove('checkbox-disabled');
|
||||
} else {
|
||||
checkboxElement.classList.add('checkbox-disabled');
|
||||
}
|
||||
|
||||
this._onValueChangedDelegate?.(this._value!);
|
||||
}
|
||||
|
||||
protected _onEnabledChanged(): void {
|
||||
protected override _onEnabledChanged(): void {
|
||||
super._onEnabledChanged();
|
||||
|
||||
const checkboxElement = document.getElementById(this._checkboxId);
|
||||
const checkboxPipElement = document.getElementById(this._checkboxPipId);
|
||||
ASSERT(checkboxElement !== null && checkboxPipElement !== null);
|
||||
const checkboxTextElement = document.getElementById(this._checkboxTextId);
|
||||
ASSERT(checkboxTextElement !== null);
|
||||
const checkboxElement = this._getElement();
|
||||
const checkboxPipElement = UIUtil.getElementById(this._getPipId());
|
||||
const checkboxTextElement = UIUtil.getElementById(this._getLabelId());
|
||||
|
||||
checkboxTextElement.innerHTML = this.getValue() ? this._onText : this._offText;
|
||||
checkboxPipElement.style.visibility = this.getValue() ? 'visible' : 'hidden';
|
||||
|
||||
if (this._isEnabled) {
|
||||
if (this.getEnabled()) {
|
||||
checkboxElement.classList.remove('checkbox-disabled');
|
||||
checkboxTextElement.classList.remove('checkbox-text-disabled');
|
||||
checkboxPipElement.classList.remove('checkbox-pip-disabled');
|
||||
@ -98,13 +89,13 @@ export class CheckboxElement extends LabelledElement<boolean> {
|
||||
checkboxTextElement.classList.add('checkbox-text-disabled');
|
||||
checkboxPipElement.classList.add('checkbox-pip-disabled');
|
||||
}
|
||||
|
||||
this._onValueChangedDelegate?.(this._value!);
|
||||
}
|
||||
|
||||
private _onValueChangedDelegate?: (value: boolean) => void;
|
||||
public onValueChanged(delegate: (value: boolean) => void) {
|
||||
this._onValueChangedDelegate = delegate;
|
||||
return this;
|
||||
private _getPipId() {
|
||||
return this._getId() + '-pip';
|
||||
}
|
||||
|
||||
private _getLabelId() {
|
||||
return this._getId() + '-label';
|
||||
}
|
||||
}
|
||||
|
@ -1,65 +1,61 @@
|
||||
import { EAppEvent, EventManager } from '../../event';
|
||||
import { ASSERT } from '../../util/error_util';
|
||||
import { LabelledElement } from './labelled_element';
|
||||
import { ConfigUIElement } from './config_element';
|
||||
|
||||
export type ComboBoxItem<T> = {
|
||||
id: T;
|
||||
payload: T;
|
||||
displayText: string;
|
||||
tooltip?: string;
|
||||
}
|
||||
|
||||
export class ComboBoxElement<T> extends LabelledElement<T> {
|
||||
export class ComboBoxElement<T> extends ConfigUIElement<T, HTMLSelectElement> {
|
||||
private _items: ComboBoxItem<T>[];
|
||||
|
||||
public constructor(id: string, items: ComboBoxItem<T>[]) {
|
||||
super(id);
|
||||
this._items = items;
|
||||
public constructor() {
|
||||
super();
|
||||
this._items = [];
|
||||
}
|
||||
|
||||
public generateInnerHTML() {
|
||||
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('onchange', (e: Event) => {
|
||||
const selectedValue = this._items[this._getElement().selectedIndex].payload;
|
||||
this._setValue(selectedValue);
|
||||
});
|
||||
}
|
||||
|
||||
protected override _generateInnerHTML() {
|
||||
ASSERT(this._items.length > 0);
|
||||
|
||||
let itemsHTML = '';
|
||||
for (const item of this._items) {
|
||||
itemsHTML += `<option value="${item.id}" title="${item.tooltip || ''}">${item.displayText}</option>`;
|
||||
itemsHTML += `<option value="${item.payload}" title="${item.tooltip || ''}">${item.displayText}</option>`;
|
||||
}
|
||||
|
||||
return `
|
||||
<select name="${this._id}" id="${this._id}">
|
||||
<select name="${this._getId()}" id="${this._getId()}">
|
||||
${itemsHTML}
|
||||
</select>
|
||||
`;
|
||||
}
|
||||
|
||||
public registerEvents(): void {
|
||||
const element = document.getElementById(this._id) as HTMLSelectElement;
|
||||
ASSERT(element !== null);
|
||||
|
||||
element.addEventListener('change', () => {
|
||||
if (this._onValueChangedDelegate) {
|
||||
//EventManager.Get.broadcast(EAppEvent.onComboBoxChanged, element.value);
|
||||
this._onValueChangedDelegate(element.value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected getValue() {
|
||||
const element = document.getElementById(this._id) as HTMLSelectElement;
|
||||
ASSERT(element !== null);
|
||||
return this._items[element.selectedIndex].id;
|
||||
}
|
||||
|
||||
protected _onEnabledChanged() {
|
||||
protected override _onEnabledChanged() {
|
||||
super._onEnabledChanged();
|
||||
|
||||
const element = document.getElementById(this._id) as HTMLSelectElement;
|
||||
ASSERT(element !== null);
|
||||
element.disabled = !this._isEnabled;
|
||||
|
||||
this._onValueChangedDelegate?.(element.value);
|
||||
this._getElement().disabled = !this.getEnabled();
|
||||
}
|
||||
|
||||
private _onValueChangedDelegate?: (value: any) => void;
|
||||
public onValueChanged(delegate: (value: any) => void) {
|
||||
this._onValueChangedDelegate = delegate;
|
||||
return this;
|
||||
protected override _onValueChanged(): void {
|
||||
}
|
||||
}
|
||||
|
114
src/ui/elements/config_element.ts
Normal file
114
src/ui/elements/config_element.ts
Normal file
@ -0,0 +1,114 @@
|
||||
import { ASSERT } from '../../util/error_util';
|
||||
import { BaseUIElement } from './base_element';
|
||||
import { LabelElement } from './label';
|
||||
|
||||
/**
|
||||
* A `ConfigUIElement` is a UI element that has a value the user can change.
|
||||
* For example, sliders, comboboxes and checkboxes are `ConfigUIElement`.
|
||||
*/
|
||||
export abstract class ConfigUIElement<T, F> extends BaseUIElement<F> {
|
||||
private _label: string;
|
||||
private _description?: string;
|
||||
private _labelElement: LabelElement;
|
||||
private _value?: T;
|
||||
private _cachedValue?: T;
|
||||
private _onValueChangedListeners: Array<(newValue: T) => void>;
|
||||
|
||||
public constructor(defaultValue?: T) {
|
||||
super();
|
||||
this._value = defaultValue;
|
||||
this._label = 'unknown';
|
||||
this._labelElement = new LabelElement(this._label, this._description);
|
||||
this._onValueChangedListeners = [];
|
||||
}
|
||||
|
||||
public setDefaultValue(value: T) {
|
||||
this._value = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public setLabel(label: string) {
|
||||
this._label = label;
|
||||
this._labelElement = new LabelElement(this._label, this._description);
|
||||
return this;
|
||||
}
|
||||
|
||||
public setDescription(text: string) {
|
||||
this._labelElement = new LabelElement(this._label, text);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Caches the current value.
|
||||
*/
|
||||
public cacheValue() {
|
||||
this._cachedValue = this._value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the value stored is different from the cached value.
|
||||
*/
|
||||
public hasChanged() {
|
||||
return this._cachedValue !== this._value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the currently set value of this UI element.
|
||||
*/
|
||||
public getValue(): T {
|
||||
ASSERT(this._value !== undefined, 'this._value is undefined');
|
||||
return this._value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a delegate that will be called when the value changes.
|
||||
*/
|
||||
public addValueChangedListener(delegate: (newValue: T) => void) {
|
||||
this._onValueChangedListeners.push(delegate);
|
||||
return this;
|
||||
}
|
||||
|
||||
public override finalise(): void {
|
||||
super.finalise();
|
||||
|
||||
this._onValueChanged();
|
||||
this._onValueChangedListeners.forEach((listener) => {
|
||||
listener(this._value!);
|
||||
});
|
||||
}
|
||||
|
||||
public override generateHTML() {
|
||||
return `
|
||||
${this._labelElement.generateHTML()}
|
||||
<div class="prop-value-container">
|
||||
${this._generateInnerHTML()}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* The UI element that this label is describing.
|
||||
*/
|
||||
protected abstract _generateInnerHTML(): string;
|
||||
|
||||
protected override _onEnabledChanged() {
|
||||
this._labelElement.setEnabled(this.getEnabled());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of this UI element.
|
||||
*/
|
||||
protected _setValue(value: T) {
|
||||
this._value = value;
|
||||
|
||||
this._onValueChanged();
|
||||
this._onValueChangedListeners.forEach((listener) => {
|
||||
listener(this._value!);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* A delegate that is called when the value of this element changes.
|
||||
*/
|
||||
protected abstract _onValueChanged(): void;
|
||||
}
|
@ -1,43 +1,49 @@
|
||||
import { remote } from 'electron';
|
||||
import * as path from 'path';
|
||||
|
||||
import { ASSERT } from '../../util/error_util';
|
||||
import { LabelledElement } from './labelled_element';
|
||||
import { ConfigUIElement } from './config_element';
|
||||
|
||||
export class FileInputElement extends LabelledElement<string> {
|
||||
private _fileExtension: string;
|
||||
export class FileInputElement extends ConfigUIElement<string, HTMLDivElement> {
|
||||
private _fileExtensions: string[];
|
||||
private _loadedFilePath: string;
|
||||
private _hovering: boolean;
|
||||
|
||||
public constructor(label: string, fileExtension: string) {
|
||||
super(label);
|
||||
this._fileExtension = fileExtension;
|
||||
public constructor() {
|
||||
super();
|
||||
this._fileExtensions = [];
|
||||
this._loadedFilePath = '';
|
||||
this._hovering = false;
|
||||
}
|
||||
|
||||
public generateInnerHTML() {
|
||||
/**
|
||||
* Set the allow list of file extensions that can be uploaded.
|
||||
*/
|
||||
public setFileExtensions(extensions: string[]) {
|
||||
this._fileExtensions = extensions;
|
||||
return this;
|
||||
}
|
||||
|
||||
protected override _generateInnerHTML() {
|
||||
return `
|
||||
<div class="input-file" id="${this._id}">
|
||||
<div class="input-file" id="${this._getId()}">
|
||||
${this._loadedFilePath}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
public registerEvents(): void {
|
||||
const element = document.getElementById(this._id) as HTMLDivElement;
|
||||
ASSERT(element !== null);
|
||||
|
||||
element.onmouseenter = () => {
|
||||
public override registerEvents(): void {
|
||||
this._getElement().addEventListener('mouseenter', () => {
|
||||
this._hovering = true;
|
||||
};
|
||||
this._updateStyle();
|
||||
});
|
||||
|
||||
element.onmouseleave = () => {
|
||||
this._getElement().addEventListener('mouseleave', () => {
|
||||
this._hovering = false;
|
||||
};
|
||||
this._updateStyle();
|
||||
});
|
||||
|
||||
element.onclick = () => {
|
||||
if (!this._isEnabled) {
|
||||
this._getElement().addEventListener('click', () => {
|
||||
if (!this.getEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -45,43 +51,48 @@ export class FileInputElement extends LabelledElement<string> {
|
||||
title: 'Load file',
|
||||
buttonLabel: 'Load',
|
||||
filters: [{
|
||||
name: 'Waveform obj file',
|
||||
extensions: [`${this._fileExtension}`],
|
||||
name: 'Model file',
|
||||
extensions: this._fileExtensions,
|
||||
}],
|
||||
});
|
||||
if (files && files.length === 1) {
|
||||
|
||||
if (files && files[0] !== undefined) {
|
||||
const filePath = files[0];
|
||||
this._loadedFilePath = filePath;
|
||||
this._value = filePath;
|
||||
this._setValue(filePath);
|
||||
}
|
||||
const parsedPath = path.parse(this._loadedFilePath);
|
||||
element.innerHTML = parsedPath.name + parsedPath.ext;
|
||||
};
|
||||
});
|
||||
|
||||
document.onmousemove = () => {
|
||||
element.classList.remove('input-file-disabled');
|
||||
element.classList.remove('input-file-hover');
|
||||
|
||||
if (this._isEnabled) {
|
||||
if (this._hovering) {
|
||||
element.classList.add('input-file-hover');
|
||||
}
|
||||
} else {
|
||||
element.classList.add('input-file-disabled');
|
||||
}
|
||||
};
|
||||
this._getElement().addEventListener('mousemove', () => {
|
||||
this._updateStyle();
|
||||
});
|
||||
}
|
||||
|
||||
protected _onEnabledChanged() {
|
||||
protected override _onEnabledChanged() {
|
||||
super._onEnabledChanged();
|
||||
|
||||
const element = document.getElementById(this._id) as HTMLDivElement;
|
||||
ASSERT(element !== null);
|
||||
|
||||
if (this._isEnabled) {
|
||||
element.classList.remove('input-file-disabled');
|
||||
if (this.getEnabled()) {
|
||||
this._getElement().classList.remove('input-file-disabled');
|
||||
} else {
|
||||
element.classList.add('input-file-disabled');
|
||||
this._getElement().classList.add('input-file-disabled');
|
||||
}
|
||||
}
|
||||
|
||||
protected override _onValueChanged(): void {
|
||||
const parsedPath = path.parse(this._loadedFilePath);
|
||||
this._getElement().innerHTML = parsedPath.name + parsedPath.ext;
|
||||
}
|
||||
|
||||
private _updateStyle() {
|
||||
this._getElement().classList.remove('input-file-disabled');
|
||||
this._getElement().classList.remove('input-file-hover');
|
||||
|
||||
if (this.getEnabled()) {
|
||||
if (this._hovering) {
|
||||
this._getElement().classList.add('input-file-hover');
|
||||
}
|
||||
} else {
|
||||
this._getElement().classList.add('input-file-disabled');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,12 @@
|
||||
import { BaseUIElement } from './base';
|
||||
import { BaseUIElement } from './base_element';
|
||||
import { LabelElement } from './label';
|
||||
|
||||
export abstract class LabelledElement<Type> extends BaseUIElement<Type> {
|
||||
private _label: string;
|
||||
private _labelElement: LabelElement;
|
||||
|
||||
public constructor(label: string) {
|
||||
super(label);
|
||||
super();
|
||||
this._label = label;
|
||||
this._labelElement = new LabelElement(label);
|
||||
}
|
||||
@ -22,7 +23,7 @@ export abstract class LabelledElement<Type> extends BaseUIElement<Type> {
|
||||
protected abstract generateInnerHTML(): string;
|
||||
|
||||
protected _onEnabledChanged() {
|
||||
this._labelElement.setEnabled(this._isEnabled);
|
||||
this._labelElement.setEnabled(this.getEnabled());
|
||||
}
|
||||
|
||||
public addDescription(text: string) {
|
||||
|
@ -18,20 +18,6 @@ export class OutputElement {
|
||||
`;
|
||||
}
|
||||
|
||||
public registerEvents() {
|
||||
const toggler = document.getElementsByClassName('caret');
|
||||
|
||||
for (let i = 0; i < toggler.length; i++) {
|
||||
const temp = toggler[i];
|
||||
temp.addEventListener('click', function () {
|
||||
temp.parentElement?.querySelector('.nested')?.classList.toggle('active');
|
||||
temp.classList.toggle('caret-down');
|
||||
});
|
||||
}
|
||||
|
||||
this._message.postBuild();
|
||||
}
|
||||
|
||||
private _message: UIMessageBuilder;
|
||||
|
||||
public setMessage(message: UIMessageBuilder, style?: OutputStyle) {
|
||||
@ -74,7 +60,6 @@ export class OutputElement {
|
||||
ASSERT(element !== null);
|
||||
|
||||
element.innerHTML = this._message.toString();
|
||||
this.registerEvents();
|
||||
}
|
||||
|
||||
public setStyle(style: OutputStyle) {
|
||||
|
@ -1,46 +1,82 @@
|
||||
import { clamp, mapRange, wayThrough } from '../../math';
|
||||
import { ASSERT } from '../../util/error_util';
|
||||
import { LabelledElement } from './labelled_element';
|
||||
import { UIUtil } from '../../util/ui_util';
|
||||
import { ConfigUIElement } from './config_element';
|
||||
|
||||
export class SliderElement extends LabelledElement<number> {
|
||||
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 _dragging: boolean;
|
||||
private _step: number;
|
||||
private _dragging: boolean;
|
||||
private _hovering: boolean;
|
||||
private _internalValue: number;
|
||||
|
||||
public constructor(label: string, min: number, max: number, decimals: number, value: number, step: number) {
|
||||
super(label);
|
||||
this._min = min;
|
||||
this._max = max;
|
||||
this._decimals = decimals;
|
||||
this._value = value;
|
||||
this._step = step;
|
||||
public constructor() {
|
||||
super();
|
||||
this._min = 0;
|
||||
this._max = 1;
|
||||
this._decimals = 1;
|
||||
this._step = 0.1;
|
||||
this._internalValue = 0.5;
|
||||
this._dragging = false;
|
||||
this._hovering = false;
|
||||
}
|
||||
|
||||
public generateInnerHTML() {
|
||||
const norm = (this.getValue() - this._min) / (this._max - this._min);
|
||||
return `
|
||||
<input type="number" id="${this._id}-value" min="${this._min}" max="${this._max}" step="${this._step}" value="${this.getValue().toFixed(this._decimals)}">
|
||||
<div class="new-slider" id="${this._id}" style="flex-grow: 1;">
|
||||
<div class="new-slider-bar" id="${this._id}-bar"style="width: ${norm * 100}%;">
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
public override setDefaultValue(value: number) {
|
||||
super.setDefaultValue(value);
|
||||
this._internalValue = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public registerEvents() {
|
||||
const element = document.getElementById(this._id) as HTMLDivElement;
|
||||
const elementBar = document.getElementById(this._id + '-bar') as HTMLDivElement;
|
||||
const elementValue = document.getElementById(this._id + '-value') as HTMLInputElement;
|
||||
ASSERT(element !== null);
|
||||
/**
|
||||
* 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._hovering = true;
|
||||
if (this._isEnabled) {
|
||||
if (this.getEnabled()) {
|
||||
element.classList.add('new-slider-hover');
|
||||
elementBar.classList.add('new-slider-bar-hover');
|
||||
}
|
||||
@ -76,7 +112,7 @@ export class SliderElement extends LabelledElement<number> {
|
||||
});
|
||||
|
||||
element.addEventListener('wheel', (e: WheelEvent) => {
|
||||
if (!this._dragging && this._isEnabled) {
|
||||
if (!this._dragging && this.getEnabled()) {
|
||||
e.preventDefault();
|
||||
this._onScrollSlider(e);
|
||||
}
|
||||
@ -87,75 +123,26 @@ export class SliderElement extends LabelledElement<number> {
|
||||
});
|
||||
}
|
||||
|
||||
public setMax(value: number) {
|
||||
this._max = value;
|
||||
this._value = clamp(this._value!, this._min, this._max);
|
||||
this._onValueUpdated();
|
||||
protected override _generateInnerHTML() {
|
||||
const norm = (this._internalValue - this._min) / (this._max - this._min);
|
||||
|
||||
return `
|
||||
<input type="number" id="${this._getSliderValueId()}" min="${this._min}" max="${this._max}" step="${this._step}" value="${this.getValue().toFixed(this._decimals)}">
|
||||
<div class="new-slider" id="${this._getId()}" style="flex-grow: 1;">
|
||||
<div class="new-slider-bar" id="${this._getSliderBarId()}" style="width: ${norm * 100}%;">
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _onTypedValue() {
|
||||
const elementValue = document.getElementById(this._id + '-value') as HTMLInputElement;
|
||||
const typedNumber = parseFloat(elementValue.value);
|
||||
if (!isNaN(typedNumber)) {
|
||||
this._value = clamp(typedNumber, this._min, this._max);
|
||||
}
|
||||
|
||||
this._onValueUpdated();
|
||||
}
|
||||
|
||||
private _onScrollSlider(e: WheelEvent) {
|
||||
if (!this._isEnabled) {
|
||||
return;
|
||||
}
|
||||
ASSERT(this._value !== undefined);
|
||||
|
||||
this._value -= (e.deltaY / 150) * this._step;
|
||||
this._value = clamp(this._value, this._min, this._max);
|
||||
|
||||
this._onValueUpdated();
|
||||
}
|
||||
|
||||
private _onDragSlider(e: MouseEvent) {
|
||||
if (!this._isEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const element = document.getElementById(this._id) as HTMLDivElement;
|
||||
ASSERT(element !== null);
|
||||
|
||||
const box = element.getBoundingClientRect();
|
||||
const left = box.x;
|
||||
const right = box.x + box.width;
|
||||
|
||||
this._value = mapRange(e.clientX, left, right, this._min, this._max);
|
||||
this._value = clamp(this._value, this._min, this._max);
|
||||
|
||||
this._onValueUpdated();
|
||||
}
|
||||
|
||||
private _onValueUpdated() {
|
||||
const elementBar = document.getElementById(this._id + '-bar') as HTMLDivElement;
|
||||
const elementValue = document.getElementById(this._id + '-value') as HTMLInputElement;
|
||||
ASSERT(elementBar !== null && elementValue !== null);
|
||||
|
||||
const norm = wayThrough(this.getValue(), this._min, this._max);
|
||||
elementBar.style.width = `${norm * 100}%`;
|
||||
elementValue.value = this.getValue().toFixed(this._decimals);
|
||||
}
|
||||
|
||||
public getDisplayValue() {
|
||||
return parseFloat(this.getValue().toFixed(this._decimals));
|
||||
}
|
||||
|
||||
protected _onEnabledChanged() {
|
||||
protected override _onEnabledChanged() {
|
||||
super._onEnabledChanged();
|
||||
|
||||
const element = document.getElementById(this._id) as HTMLDivElement;
|
||||
const elementBar = document.getElementById(this._id + '-bar') as HTMLDivElement;
|
||||
const elementValue = document.getElementById(this._id + '-value') as HTMLInputElement;
|
||||
ASSERT(element !== null && elementBar !== null && elementValue !== null);
|
||||
const element = this._getElement();
|
||||
const elementBar = UIUtil.getElementById(this._getSliderBarId());
|
||||
const elementValue = UIUtil.getElementById(this._getSliderValueId()) as HTMLInputElement;
|
||||
|
||||
if (this._isEnabled) {
|
||||
if (this.getEnabled()) {
|
||||
element.classList.remove('new-slider-disabled');
|
||||
elementBar.classList.remove('new-slider-bar-disabled');
|
||||
elementValue.disabled = false;
|
||||
@ -165,4 +152,67 @@ export class SliderElement extends LabelledElement<number> {
|
||||
elementValue.disabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
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';
|
||||
}
|
||||
}
|
||||
|
@ -98,10 +98,8 @@ export class ToolbarItemElement {
|
||||
|
||||
element.classList.remove('toolbar-item-disabled');
|
||||
element.classList.remove('toolbar-item-active');
|
||||
element.classList.remove('toolbar-item-disabled-active');
|
||||
svgElement.classList.remove('icon-disabled');
|
||||
svgElement.classList.remove('icon-active');
|
||||
svgElement.classList.remove('icon-disabled-active');
|
||||
|
||||
if (this._isEnabled) {
|
||||
if (this._isActive) {
|
||||
@ -109,13 +107,8 @@ export class ToolbarItemElement {
|
||||
svgElement.classList.add('icon-active');
|
||||
}
|
||||
} else {
|
||||
if (this._isActive) {
|
||||
element.classList.add('toolbar-item-disabled-active');
|
||||
svgElement.classList.add('icon-disabled-active');
|
||||
} else {
|
||||
element.classList.add('toolbar-item-disabled');
|
||||
svgElement.classList.add('icon-disabled');
|
||||
}
|
||||
element.classList.add('toolbar-item-disabled');
|
||||
svgElement.classList.add('icon-disabled');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,64 +1,55 @@
|
||||
/*
|
||||
import { ASSERT } from '../../util/error_util';
|
||||
import { LOG } from '../../util/log_util';
|
||||
import { Vector3 } from '../../vector';
|
||||
import { LabelledElement } from './labelled_element';
|
||||
import { ConfigElement } from './config_element';
|
||||
|
||||
/* eslint-disable */
|
||||
enum EAxis {
|
||||
None = 'none',
|
||||
X = 'x',
|
||||
Y = 'y',
|
||||
Z = 'z',
|
||||
};
|
||||
/* eslint-enable */
|
||||
|
||||
export class VectorSpinboxElement extends LabelledElement<Vector3> {
|
||||
export class VectorSpinboxElement extends ConfigElement<Vector3> {
|
||||
private _mouseover: EAxis;
|
||||
private _dragging: EAxis;
|
||||
private _lastClientX: number;
|
||||
private _showY: boolean;
|
||||
|
||||
public constructor(label: string, decimals: number, value: Vector3, showY: boolean) {
|
||||
public constructor(label: string, decimals: number, value: Vector3) {
|
||||
super(label);
|
||||
this._value = value;
|
||||
this._mouseover = EAxis.None;
|
||||
this._dragging = EAxis.None;
|
||||
this._lastClientX = 0.0;
|
||||
this._showY = showY;
|
||||
}
|
||||
|
||||
public generateInnerHTML() {
|
||||
ASSERT(this._value !== undefined, 'Value not found');
|
||||
let html = '';
|
||||
html += '<div class="spinbox-main-container">';
|
||||
html += `
|
||||
<div class="spinbox-element-container">
|
||||
<div class="spinbox-key" id="${this._id}-kx">X</div>
|
||||
<div class="spinbox-value" id="${this._id}-vx">
|
||||
${this._value.x}
|
||||
ASSERT(this._value, 'Value not found');
|
||||
return `
|
||||
<div style="display: flex; flex-direction: row;">
|
||||
<div style="display: flex; flex-direction: row; width: 33%">
|
||||
<div class="spinbox-key" id="${this._id}-kx" style="background-color: #FF4C4C;">X</div>
|
||||
<div class="spinbox-value" id="${this._id}-vx">
|
||||
${this._value.x}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
if (this._showY) {
|
||||
html += `
|
||||
<div class="spinbox-element-container">
|
||||
<div class="spinbox-key" id="${this._id}-ky">Y</div>
|
||||
<div class="invis-divider"></div>
|
||||
<div style="display: flex; flex-direction: row; width: 33%"">
|
||||
<div class="spinbox-key" id="${this._id}-ky" style="background-color: #34BF49;">Y</div>
|
||||
<div class="spinbox-value" id="${this._id}-vy">
|
||||
${this._value.y}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
html += `
|
||||
<div class="spinbox-element-container">
|
||||
<div class="spinbox-key" id="${this._id}-kz">Z</div>
|
||||
<div class="spinbox-value" id="${this._id}-vz">
|
||||
${this._value.z}
|
||||
<div class="invis-divider"></div>
|
||||
<div style="display: flex; flex-direction: row; width: 33%"">
|
||||
<div class="spinbox-key" id="${this._id}-kz" style="background-color: #0099E5;">Z</div>
|
||||
<div class="spinbox-value" id="${this._id}-vz">
|
||||
${this._value.z}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
html += '</div>';
|
||||
return html;
|
||||
}
|
||||
|
||||
private _registerAxis(axis: EAxis) {
|
||||
@ -103,9 +94,7 @@ export class VectorSpinboxElement extends LabelledElement<Vector3> {
|
||||
|
||||
public registerEvents() {
|
||||
this._registerAxis(EAxis.X);
|
||||
if (this._showY) {
|
||||
this._registerAxis(EAxis.Y);
|
||||
}
|
||||
this._registerAxis(EAxis.Y);
|
||||
this._registerAxis(EAxis.Z);
|
||||
|
||||
document.addEventListener('mousedown', (e: any) => {
|
||||
@ -123,20 +112,20 @@ export class VectorSpinboxElement extends LabelledElement<Vector3> {
|
||||
|
||||
document.addEventListener('mouseup', () => {
|
||||
const elementXK = document.getElementById(this._id + '-kx') as HTMLDivElement;
|
||||
const elementYK = document.getElementById(this._id + '-ky') as (HTMLDivElement | undefined);
|
||||
const elementYK = document.getElementById(this._id + '-ky') as HTMLDivElement;
|
||||
const elementZK = document.getElementById(this._id + '-kz') as HTMLDivElement;
|
||||
const elementXV = document.getElementById(this._id + '-vx') as HTMLDivElement;
|
||||
const elementYV = document.getElementById(this._id + '-vy') as (HTMLDivElement | undefined);
|
||||
const elementYV = document.getElementById(this._id + '-vy') as HTMLDivElement;
|
||||
const elementZV = document.getElementById(this._id + '-vz') as HTMLDivElement;
|
||||
|
||||
switch (this._dragging) {
|
||||
case EAxis.X:
|
||||
elementXK?.classList.remove('spinbox-key-hover');
|
||||
elementXK.classList.remove('spinbox-key-hover');
|
||||
elementXV.classList.remove('spinbox-value-hover');
|
||||
break;
|
||||
case EAxis.Y:
|
||||
elementYK?.classList.remove('spinbox-key-hover');
|
||||
elementYV?.classList.remove('spinbox-value-hover');
|
||||
elementYK.classList.remove('spinbox-key-hover');
|
||||
elementYV.classList.remove('spinbox-value-hover');
|
||||
break;
|
||||
case EAxis.Z:
|
||||
elementZK.classList.remove('spinbox-key-hover');
|
||||
@ -150,7 +139,7 @@ export class VectorSpinboxElement extends LabelledElement<Vector3> {
|
||||
private _updateValue(e: MouseEvent) {
|
||||
ASSERT(this._isEnabled, 'Not enabled');
|
||||
ASSERT(this._dragging !== EAxis.None, 'Dragging nothing');
|
||||
ASSERT(this._value !== undefined, 'No value to update');
|
||||
ASSERT(this._value, 'No value to update');
|
||||
|
||||
const deltaX = e.clientX - this._lastClientX;
|
||||
this._lastClientX = e.clientX;
|
||||
@ -168,12 +157,10 @@ export class VectorSpinboxElement extends LabelledElement<Vector3> {
|
||||
}
|
||||
|
||||
const elementXV = document.getElementById(this._id + '-vx') as HTMLDivElement;
|
||||
const elementYV = document.getElementById(this._id + '-vy') as (HTMLDivElement | undefined);
|
||||
const elementYV = document.getElementById(this._id + '-vy') as HTMLDivElement;
|
||||
const elementZV = document.getElementById(this._id + '-vz') as HTMLDivElement;
|
||||
elementXV.innerHTML = this._value.x.toString();
|
||||
if (elementYV) {
|
||||
elementYV.innerHTML = this._value.y.toString();
|
||||
}
|
||||
elementYV.innerHTML = this._value.y.toString();
|
||||
elementZV.innerHTML = this._value.z.toString();
|
||||
}
|
||||
|
||||
@ -184,29 +171,30 @@ export class VectorSpinboxElement extends LabelledElement<Vector3> {
|
||||
|
||||
const keyElements = [
|
||||
document.getElementById(this._id + '-kx') as HTMLDivElement,
|
||||
document.getElementById(this._id + '-ky') as (HTMLDivElement | undefined),
|
||||
document.getElementById(this._id + '-ky') as HTMLDivElement,
|
||||
document.getElementById(this._id + '-kz') as HTMLDivElement,
|
||||
];
|
||||
const valueElements = [
|
||||
document.getElementById(this._id + '-vx') as HTMLDivElement,
|
||||
document.getElementById(this._id + '-vy') as (HTMLDivElement | undefined),
|
||||
document.getElementById(this._id + '-vy') as HTMLDivElement,
|
||||
document.getElementById(this._id + '-vz') as HTMLDivElement,
|
||||
];
|
||||
|
||||
if (this._isEnabled) {
|
||||
for (const keyElement of keyElements) {
|
||||
keyElement?.classList.remove('spinbox-key-disabled');
|
||||
keyElement.classList.remove('spinbox-key-disabled');
|
||||
}
|
||||
for (const valueElement of valueElements) {
|
||||
valueElement?.classList.remove('spinbox-value-disabled');
|
||||
valueElement.classList.remove('spinbox-value-disabled');
|
||||
}
|
||||
} else {
|
||||
for (const keyElement of keyElements) {
|
||||
keyElement?.classList.add('spinbox-key-disabled');
|
||||
keyElement.classList.add('spinbox-key-disabled');
|
||||
}
|
||||
for (const valueElement of valueElements) {
|
||||
valueElement?.classList.add('spinbox-value-disabled');
|
||||
valueElement.classList.add('spinbox-value-disabled');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
303
src/ui/layout.ts
303
src/ui/layout.ts
@ -1,6 +1,7 @@
|
||||
import fs from 'fs';
|
||||
|
||||
import { AppContext } from '../app_context';
|
||||
import { FallableBehaviour } from '../block_mesh';
|
||||
import { ArcballCamera } from '../camera';
|
||||
import { AppConfig } from '../config';
|
||||
import { EAppEvent, EventManager } from '../event';
|
||||
@ -15,10 +16,11 @@ import { TAxis, TTexelExtension } from '../util/type_util';
|
||||
import { TDithering } from '../util/type_util';
|
||||
import { TVoxelOverlapRule } from '../voxel_mesh';
|
||||
import { TVoxelisers } from '../voxelisers/voxelisers';
|
||||
import { BaseUIElement } from './elements/base';
|
||||
import { BaseUIElement } from './elements/base_element';
|
||||
import { ButtonElement } from './elements/button';
|
||||
import { CheckboxElement } from './elements/checkbox';
|
||||
import { ComboBoxElement, ComboBoxItem } from './elements/combobox';
|
||||
import { ConfigUIElement } from './elements/config_element';
|
||||
import { FileInputElement } from './elements/file_input';
|
||||
import { OutputElement } from './elements/output';
|
||||
import { SliderElement } from './elements/slider';
|
||||
@ -26,11 +28,11 @@ import { ToolbarItemElement } from './elements/toolbar_item';
|
||||
|
||||
export interface Group {
|
||||
label: string;
|
||||
elements: { [key: string]: BaseUIElement<any> };
|
||||
elements: { [key: string]: ConfigUIElement<any, any> };
|
||||
elementsOrder: string[];
|
||||
submitButton: ButtonElement;
|
||||
output: OutputElement;
|
||||
postElements?: { [key: string]: BaseUIElement<any> };
|
||||
postElements?: { [key: string]: ConfigUIElement<any, any> };
|
||||
postElementsOrder?: string[];
|
||||
}
|
||||
|
||||
@ -45,148 +47,222 @@ export class UI {
|
||||
'import': {
|
||||
label: 'Import',
|
||||
elements: {
|
||||
'input': new FileInputElement('Wavefront .obj file', 'obj'),
|
||||
'input': new FileInputElement()
|
||||
.setFileExtensions(['obj'])
|
||||
.setLabel('Wavefront .obj file'),
|
||||
},
|
||||
elementsOrder: ['input'],
|
||||
submitButton: new ButtonElement('Load mesh', () => {
|
||||
this._appContext.do(EAction.Import);
|
||||
}),
|
||||
submitButton: new ButtonElement()
|
||||
.setOnClick(() => {
|
||||
this._appContext.do(EAction.Import);
|
||||
})
|
||||
.setLabel('Load mesh'),
|
||||
output: new OutputElement(),
|
||||
},
|
||||
'voxelise': {
|
||||
label: 'Voxelise',
|
||||
elements: {
|
||||
'constraintAxis': new ComboBoxElement<TAxis>('Constraint axis', [
|
||||
{
|
||||
id: 'y',
|
||||
displayText: 'Y (height) (green)',
|
||||
},
|
||||
{
|
||||
id: 'x',
|
||||
displayText: 'X (width) (red)',
|
||||
},
|
||||
{
|
||||
id: 'z',
|
||||
displayText: 'Z (depth) (blue)',
|
||||
},
|
||||
]).onValueChanged((value: string) => {
|
||||
if (value === 'x') {
|
||||
this._ui.voxelise.elements.size.setMax(this._appContext.maxConstraint?.x ?? 400);
|
||||
} else if (value === 'y') {
|
||||
this._ui.voxelise.elements.size.setMax(this._appContext.maxConstraint?.y ?? AppConfig.Get.CONSTRAINT_MAXIMUM_HEIGHT);
|
||||
} else {
|
||||
this._ui.voxelise.elements.size.setMax(this._appContext.maxConstraint?.z ?? 400);
|
||||
}
|
||||
}),
|
||||
'size': new SliderElement('Size', 3, AppConfig.Get.CONSTRAINT_MAXIMUM_HEIGHT, 0, 80, 1),
|
||||
'voxeliser': new ComboBoxElement<TVoxelisers>('Algorithm', [
|
||||
{
|
||||
id: 'bvh-ray',
|
||||
displayText: 'BVH Ray-based',
|
||||
},
|
||||
{
|
||||
id: 'bvh-ray-plus-thickness',
|
||||
displayText: 'BVH Ray-based (thicker walls)',
|
||||
},
|
||||
{
|
||||
id: 'ncrb',
|
||||
displayText: 'NCRB',
|
||||
},
|
||||
{
|
||||
id: 'ray-based',
|
||||
displayText: 'Ray-based (legacy)',
|
||||
},
|
||||
]),
|
||||
'ambientOcclusion': new CheckboxElement('Ambient occlusion', true, 'On (recommended)', 'Off (faster)'),
|
||||
'multisampleColouring': new CheckboxElement('Multisampling', true, 'On (recommended)', 'Off (faster)'),
|
||||
'voxelOverlapRule': new ComboBoxElement<TVoxelOverlapRule>('Voxel overlap', [
|
||||
{
|
||||
id: 'average',
|
||||
'constraintAxis': new ComboBoxElement<TAxis>()
|
||||
.addItem({ payload: 'y', displayText: 'Y (height) (green)' })
|
||||
.addItem({ payload: 'x', displayText: 'X (width) (red)' })
|
||||
.addItem({ payload: 'z', displayText: 'Z (depth) (blue)' })
|
||||
.setLabel('Constraint axis')
|
||||
.addValueChangedListener((value: TAxis) => {
|
||||
/*
|
||||
switch (value) {
|
||||
case 'x':
|
||||
this._ui.voxelise.elements.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);
|
||||
break;
|
||||
case 'z':
|
||||
this._ui.voxelise.elements.size.setMax(this._appContext.maxConstraint?.z ?? 400);
|
||||
break;
|
||||
}
|
||||
*/
|
||||
}),
|
||||
'size': new SliderElement()
|
||||
.setMin(3)
|
||||
.setMax(380)
|
||||
.setDefaultValue(80)
|
||||
.setDecimals(0)
|
||||
.setStep(1)
|
||||
.setLabel('Size'),
|
||||
'voxeliser': new ComboBoxElement<TVoxelisers>()
|
||||
.addItem({ payload: 'bvh-ray', displayText: 'BVH Ray-based' })
|
||||
.addItem({ payload: 'ncrb', displayText: 'NCRB' })
|
||||
.addItem({ payload: 'ray-based', displayText: 'Ray-based (legacy)' })
|
||||
.setLabel('Algorithm'),
|
||||
'ambientOcclusion': new CheckboxElement()
|
||||
.setCheckedText('On (recommended)')
|
||||
.setUncheckedText('Off (faster)')
|
||||
.setDefaultValue(true)
|
||||
.setLabel('Ambient occlusion'),
|
||||
'multisampleColouring': new CheckboxElement()
|
||||
.setCheckedText('On (recommended)')
|
||||
.setUncheckedText('Off (faster)')
|
||||
.setDefaultValue(true)
|
||||
.setLabel('Multisampling'),
|
||||
'voxelOverlapRule': new ComboBoxElement<TVoxelOverlapRule>()
|
||||
.addItem({
|
||||
displayText: 'Average (recommended)',
|
||||
payload: 'average',
|
||||
tooltip: 'If multiple voxels are placed in the same location, take the average of their colours',
|
||||
},
|
||||
{
|
||||
id: 'first',
|
||||
})
|
||||
.addItem({
|
||||
displayText: 'First',
|
||||
payload: 'first',
|
||||
tooltip: 'If multiple voxels are placed in the same location, use the first voxel\'s colour',
|
||||
},
|
||||
]),
|
||||
})
|
||||
.setLabel('Voxel overlap'),
|
||||
},
|
||||
elementsOrder: ['constraintAxis', 'size', 'voxeliser', 'ambientOcclusion', 'multisampleColouring', 'voxelOverlapRule'],
|
||||
submitButton: new ButtonElement('Voxelise mesh', () => {
|
||||
this._appContext.do(EAction.Voxelise);
|
||||
}),
|
||||
elementsOrder: [
|
||||
'constraintAxis',
|
||||
'size',
|
||||
'voxeliser',
|
||||
'ambientOcclusion',
|
||||
'multisampleColouring',
|
||||
'voxelOverlapRule',
|
||||
],
|
||||
submitButton: new ButtonElement()
|
||||
.setOnClick(() => {
|
||||
this._appContext.do(EAction.Voxelise);
|
||||
})
|
||||
.setLabel('Voxelise mesh'),
|
||||
output: new OutputElement(),
|
||||
},
|
||||
'assign': {
|
||||
label: 'Assign',
|
||||
elements: {
|
||||
'textureAtlas': new ComboBoxElement('Texture atlas', this._getTextureAtlases()),
|
||||
'blockPalette': new ComboBoxElement('Block palette', this._getBlockPalettes()),
|
||||
'dithering': new ComboBoxElement<TDithering>('Dithering', [
|
||||
{ id: 'ordered', displayText: 'Ordered' },
|
||||
{ id: 'random', displayText: 'Random' },
|
||||
{ id: 'off', displayText: 'Off' },
|
||||
]),
|
||||
'fallable': new ComboBoxElement('Fallable blocks', [
|
||||
'textureAtlas': new ComboBoxElement<string>()
|
||||
.addItems(this._getTextureAtlases())
|
||||
.setLabel('Texture atlas'),
|
||||
'blockPalette': new ComboBoxElement<string>()
|
||||
.addItems(this._getBlockPalettes())
|
||||
.setLabel('Block palette'),
|
||||
'dithering': new ComboBoxElement<TDithering>()
|
||||
.addItems([{
|
||||
displayText: 'Ordered',
|
||||
payload: 'ordered',
|
||||
},
|
||||
{
|
||||
id: 'replace-falling',
|
||||
displayText: 'Random',
|
||||
payload: 'random',
|
||||
},
|
||||
{
|
||||
displayText: 'Off',
|
||||
payload: 'off',
|
||||
}])
|
||||
.setLabel('Dithering'),
|
||||
'fallable': new ComboBoxElement<FallableBehaviour>()
|
||||
.addItems([{
|
||||
displayText: 'Replace falling with solid',
|
||||
payload: 'replace-falling',
|
||||
tooltip: 'Replace all blocks that will fall with solid blocks',
|
||||
},
|
||||
{
|
||||
displayText: 'Replace fallable with solid',
|
||||
payload: 'replace-fallable',
|
||||
tooltip: 'Replace all blocks that can fall with solid blocks',
|
||||
},
|
||||
{
|
||||
id: 'replace-fallable',
|
||||
displayText: 'Replace fallable with solid',
|
||||
tooltip: 'Replace all blocks that will fall with solid blocks',
|
||||
},
|
||||
/*
|
||||
{
|
||||
id: 'place-string',
|
||||
displayText: 'Place string under',
|
||||
tooltip: 'Place string blocks under all blocks that would fall otherwise',
|
||||
},
|
||||
*/
|
||||
{
|
||||
id: 'do-nothing',
|
||||
displayText: 'Do nothing',
|
||||
payload: 'do-nothing',
|
||||
tooltip: 'Let the block fall',
|
||||
},
|
||||
]),
|
||||
'colourAccuracy': new SliderElement('Colour accuracy', 1, 8, 1, 5, 0.1),
|
||||
'contextualAveraging': new CheckboxElement('Smart averaging', true, 'On (recommended)', 'Off (faster)'),
|
||||
'errorWeight': new SliderElement('Smoothness', 0.0, AppConfig.Get.SMOOTHNESS_MAX, 2, 0.2, 0.01),
|
||||
'calculateLighting': new CheckboxElement('Calculate lighting', false, 'On', 'Off')
|
||||
.onValueChanged((value: boolean) => {
|
||||
}])
|
||||
.setLabel('Fallable blocks'),
|
||||
'colourAccuracy': new SliderElement()
|
||||
.setMin(1)
|
||||
.setMax(8)
|
||||
.setDefaultValue(5)
|
||||
.setDecimals(1)
|
||||
.setStep(0.1)
|
||||
.setLabel('Colour accuracy'),
|
||||
'contextualAveraging': new CheckboxElement()
|
||||
.setCheckedText('On (recommended)')
|
||||
.setUncheckedText('Off (faster)')
|
||||
.setDefaultValue(true)
|
||||
.setLabel('Smart averaging'),
|
||||
'errorWeight': new SliderElement()
|
||||
.setMin(0.0)
|
||||
.setMax(2.0)
|
||||
.setDefaultValue(0.2)
|
||||
.setDecimals(2)
|
||||
.setStep(0.01)
|
||||
.setLabel('Smoothness'),
|
||||
'calculateLighting': new CheckboxElement()
|
||||
.setCheckedText('On')
|
||||
.setUncheckedText('Off')
|
||||
.setDefaultValue(false)
|
||||
.setLabel('Calculate lighting')
|
||||
.addValueChangedListener((value: boolean) => {
|
||||
if (value) {
|
||||
this._ui.assign.elements.lightThreshold.setEnabled(true, false);
|
||||
} else {
|
||||
this._ui.assign.elements.lightThreshold.setEnabled(false, false);
|
||||
}
|
||||
}),
|
||||
'lightThreshold': new SliderElement('Light threshold', 0, 14, 0, 0, 1)
|
||||
.setObeyGroupEnables(false),
|
||||
'lightThreshold': new SliderElement()
|
||||
.setMin(0)
|
||||
.setMax(14)
|
||||
.setDefaultValue(1)
|
||||
.setDecimals(0)
|
||||
.setStep(1)
|
||||
.setLabel('Light threshold'),
|
||||
},
|
||||
elementsOrder: ['textureAtlas', 'blockPalette', 'dithering', 'fallable', 'colourAccuracy', 'contextualAveraging', 'errorWeight', 'calculateLighting', 'lightThreshold'],
|
||||
submitButton: new ButtonElement('Assign blocks', () => {
|
||||
this._appContext.do(EAction.Assign);
|
||||
}),
|
||||
elementsOrder: [
|
||||
'textureAtlas',
|
||||
'blockPalette',
|
||||
'dithering',
|
||||
'fallable',
|
||||
'colourAccuracy',
|
||||
'contextualAveraging',
|
||||
'errorWeight',
|
||||
'calculateLighting',
|
||||
'lightThreshold',
|
||||
],
|
||||
submitButton: new ButtonElement()
|
||||
.setOnClick(() => {
|
||||
this._appContext.do(EAction.Assign);
|
||||
})
|
||||
.setLabel('Assign blocks'),
|
||||
output: new OutputElement(),
|
||||
},
|
||||
'export': {
|
||||
label: 'Export',
|
||||
elements: {
|
||||
'export': new ComboBoxElement<TExporters>('File format', [
|
||||
{ id: 'litematic', displayText: 'Litematic (.litematic)' },
|
||||
{ id: 'schematic', displayText: 'Schematic (.schematic)' },
|
||||
{ id: 'obj', displayText: 'Wavefront OBJ (.obj)' },
|
||||
{ id: 'schem', displayText: 'Sponge Schematic (.schem)' },
|
||||
{ id: 'nbt', displayText: 'Structure blocks (.nbt)' },
|
||||
]),
|
||||
'export': new ComboBoxElement<TExporters>()
|
||||
.addItems([
|
||||
{
|
||||
displayText: 'Litematic (.litematic)',
|
||||
payload: 'litematic',
|
||||
},
|
||||
{
|
||||
displayText: 'Schematic (.schematic)',
|
||||
payload: 'schematic',
|
||||
},
|
||||
{
|
||||
displayText: 'Wavefront OBJ (.obj)',
|
||||
payload: 'obj',
|
||||
},
|
||||
{
|
||||
displayText: 'Sponge Schematic (.schem)',
|
||||
payload: 'schem',
|
||||
},
|
||||
{
|
||||
displayText: 'Structure blocks (.nbt)',
|
||||
payload: 'nbt',
|
||||
},
|
||||
])
|
||||
.setLabel('Exporter'),
|
||||
},
|
||||
elementsOrder: ['export'],
|
||||
submitButton: new ButtonElement('Export structure', () => {
|
||||
this._appContext.do(EAction.Export);
|
||||
}),
|
||||
submitButton: new ButtonElement()
|
||||
.setLabel('Export structure')
|
||||
.setOnClick(() => {
|
||||
this._appContext.do(EAction.Export);
|
||||
}),
|
||||
output: new OutputElement(),
|
||||
},
|
||||
};
|
||||
@ -326,9 +402,6 @@ export class UI {
|
||||
|
||||
constructor(appContext: AppContext) {
|
||||
this._appContext = appContext;
|
||||
|
||||
this._ui.assign.elements.textureAtlas.addDescription('Textures to use and colour-match with');
|
||||
this._ui.assign.elements.fallable.addDescription('Read tooltips for more info');
|
||||
}
|
||||
|
||||
public tick(isBusy: boolean) {
|
||||
@ -426,6 +499,7 @@ export class UI {
|
||||
let groupHTML = '';
|
||||
for (const elementName of group.elementsOrder) {
|
||||
const element = group.elements[elementName];
|
||||
ASSERT(element !== undefined, `No element for: ${elementName}`);
|
||||
groupHTML += this._buildSubcomponent(element);
|
||||
}
|
||||
|
||||
@ -434,6 +508,7 @@ export class UI {
|
||||
ASSERT(group.postElementsOrder, 'No post elements order');
|
||||
for (const elementName of group.postElementsOrder) {
|
||||
const element = group.postElements[elementName];
|
||||
ASSERT(element !== undefined, `No element for: ${elementName}`);
|
||||
postGroupHTML += this._buildSubcomponent(element);
|
||||
}
|
||||
}
|
||||
@ -452,7 +527,7 @@ export class UI {
|
||||
`;
|
||||
}
|
||||
|
||||
private _buildSubcomponent(element: BaseUIElement<any>) {
|
||||
private _buildSubcomponent(element: ConfigUIElement<any, any>) {
|
||||
return `
|
||||
<div class="property">
|
||||
${element.generateHTML()}
|
||||
@ -476,6 +551,7 @@ export class UI {
|
||||
for (const elementName in group.elements) {
|
||||
const element = group.elements[elementName];
|
||||
element.registerEvents();
|
||||
element.finalise();
|
||||
}
|
||||
group.submitButton.registerEvents();
|
||||
if (group.postElements) {
|
||||
@ -483,6 +559,7 @@ export class UI {
|
||||
for (const elementName in group.postElements) {
|
||||
const element = group.postElements[elementName];
|
||||
element.registerEvents();
|
||||
element.finalise();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -590,7 +667,7 @@ export class UI {
|
||||
const paletteID = file.split('.')[0];
|
||||
let paletteName = paletteID.replace('-', ' ').toLowerCase();
|
||||
paletteName = paletteName.charAt(0).toUpperCase() + paletteName.slice(1);
|
||||
textureAtlases.push({ id: paletteID, displayText: paletteName });
|
||||
textureAtlases.push({ payload: paletteID, displayText: paletteName });
|
||||
}
|
||||
});
|
||||
|
||||
@ -603,7 +680,7 @@ export class UI {
|
||||
const palettes = PaletteManager.getPalettesInfo();
|
||||
for (const palette of palettes) {
|
||||
blockPalettes.push({
|
||||
id: palette.paletteID,
|
||||
payload: palette.paletteID,
|
||||
displayText: palette.paletteDisplayName,
|
||||
});
|
||||
}
|
||||
|
9
src/util/ui_util.ts
Normal file
9
src/util/ui_util.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { ASSERT } from './error_util';
|
||||
|
||||
export namespace UIUtil {
|
||||
export function getElementById(id: string) {
|
||||
const element = document.getElementById(id);
|
||||
ASSERT(element !== null, `Attempting to getElement of nonexistent element: ${id}`);
|
||||
return element as HTMLElement;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user