Merge pull request #116 from LucasDower/0.8-web-ui

Merge 0.8-web-ui into 0.8-web
This commit is contained in:
Lucas Dower 2023-03-20 22:22:24 +00:00 committed by GitHub
commit af9dcd6ce2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 879 additions and 1037 deletions

View File

@ -9,6 +9,9 @@ import { MaterialMapManager } from './material-map';
import { MaterialType } from './mesh'; import { MaterialType } from './mesh';
import { MeshType, Renderer } from './renderer'; import { MeshType, Renderer } from './renderer';
import { AppConsole, TMessage } from './ui/console'; 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 { SolidMaterialElement } from './ui/elements/solid_material_element';
import { TexturedMaterialElement } from './ui/elements/textured_material_element'; import { TexturedMaterialElement } from './ui/elements/textured_material_element';
import { UI } from './ui/layout'; import { UI } from './ui/layout';
@ -48,6 +51,7 @@ export class AppContext {
this._ui.build(); this._ui.build();
this._ui.registerEvents(); this._ui.registerEvents();
this._ui.disable(EAction.Materials); this._ui.disable(EAction.Materials);
this._updateMaterialsAction();
this._workerController = new WorkerController(); this._workerController = new WorkerController();
this._workerController.addJob({ id: 'init', payload: { action: 'Init', params: {} } }); this._workerController.addJob({ id: 'init', payload: { action: 'Init', params: {} } });
@ -229,6 +233,10 @@ export class AppContext {
this._ui.layoutDull['materials'].elements = {}; this._ui.layoutDull['materials'].elements = {};
this._ui.layoutDull['materials'].elementsOrder = []; this._ui.layoutDull['materials'].elementsOrder = [];
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`);
} else {
this._materialManager.materials.forEach((material, materialName) => { this._materialManager.materials.forEach((material, materialName) => {
if (material.type === MaterialType.solid) { if (material.type === MaterialType.solid) {
this._ui.layoutDull['materials'].elements[`mat_${materialName}`] = new SolidMaterialElement(materialName, material) this._ui.layoutDull['materials'].elements[`mat_${materialName}`] = new SolidMaterialElement(materialName, material)
@ -241,10 +249,12 @@ export class AppContext {
this._ui.layoutDull['materials'].elements[`mat_${materialName}`] = new TexturedMaterialElement(materialName, material) this._ui.layoutDull['materials'].elements[`mat_${materialName}`] = new TexturedMaterialElement(materialName, material)
.setLabel(materialName) .setLabel(materialName)
.onChangeTypeDelegate(() => { .onChangeTypeDelegate(() => {
console.log('on change type');
this._materialManager.changeMaterialType(materialName, MaterialType.solid); this._materialManager.changeMaterialType(materialName, MaterialType.solid);
this._updateMaterialsAction(); this._updateMaterialsAction();
}) })
.onChangeTransparencyTypeDelegate((newTransparency) => { .onChangeTransparencyTypeDelegate((newTransparency) => {
console.log('on change trans');
this._materialManager.changeTransparencyType(materialName, newTransparency); this._materialManager.changeTransparencyType(materialName, newTransparency);
this._updateMaterialsAction(); this._updateMaterialsAction();
}); });
@ -252,6 +262,8 @@ export class AppContext {
this._ui.layoutDull['materials'].elementsOrder.push(`mat_${materialName}`); this._ui.layoutDull['materials'].elementsOrder.push(`mat_${materialName}`);
}); });
}
this._ui.refreshSubcomponents(this._ui.layoutDull['materials']); this._ui.refreshSubcomponents(this._ui.layoutDull['materials']);
} }

View File

@ -10,17 +10,10 @@ export class AppConfig {
public readonly RELEASE_MODE = true; public readonly RELEASE_MODE = true;
public readonly MAJOR_VERSION = 0; public readonly MAJOR_VERSION = 0;
public readonly MINOR_VERSION = 7; public readonly MINOR_VERSION = 8;
public readonly HOTFIX_VERSION = 12; public readonly HOTFIX_VERSION = 0;
public readonly VERSION_TYPE: 'd' | 'a' | 'r' = 'r'; // dev, alpha, or release build public readonly VERSION_TYPE: 'd' | 'a' | 'r' = 'r'; // dev, alpha, or release build
public readonly MINECRAFT_VERSION = '1.19.3'; public readonly MINECRAFT_VERSION = '1.19.3';
public readonly CHANGELOG = [
'+ Added customisable block palettes.',
'* Added a console log panel for persistent message logging.',
'+ Added support for importing/exporting block palettes.',
'* Improved UI for checkbox and material-type components.',
'- Removed the output component from action groups.',
];
public readonly VOXEL_BUFFER_CHUNK_SIZE = 5_000; public readonly VOXEL_BUFFER_CHUNK_SIZE = 5_000;
public readonly AMBIENT_OCCLUSION_OVERRIDE_CORNER = true; public readonly AMBIENT_OCCLUSION_OVERRIDE_CORNER = true;

View File

@ -1,18 +1,26 @@
import { getRandomID } from '../../util'; import { getRandomID } from '../../util';
import { UIUtil } from '../../util/ui_util'; import { UIUtil } from '../../util/ui_util';
export interface IInterfaceItem {
generateHTML: () => string;
registerEvents: () => void;
finalise: () => void;
}
/** /**
* The base UI class from which user interactable DOM elements are built from. * The base UI class from which user interactable DOM elements are built from.
* Each `BaseUIElement` can be enabled/disabled. * Each `BaseUIElement` can be enabled/disabled.
*/ */
export abstract class BaseUIElement<T> { export abstract class BaseUIElement<T> implements IInterfaceItem {
private _id: string; private _id: string;
private _isEnabled: boolean; private _isEnabled: boolean;
private _isHovered: boolean;
private _obeyGroupEnables: boolean; private _obeyGroupEnables: boolean;
public constructor() { public constructor() {
this._id = getRandomID(); this._id = getRandomID();
this._isEnabled = true; this._isEnabled = true;
this._isHovered = false;
this._obeyGroupEnables = true; this._obeyGroupEnables = true;
} }
@ -31,6 +39,10 @@ export abstract class BaseUIElement<T> {
return this._isEnabled; return this._isEnabled;
} }
public get disabled() {
return !this._isEnabled;
}
/** /**
* Set whether or not this UI element is interactable. * Set whether or not this UI element is interactable.
*/ */
@ -42,6 +54,14 @@ export abstract class BaseUIElement<T> {
this._onEnabledChanged(); this._onEnabledChanged();
} }
protected _setHovered(isHovered: boolean) {
this._isHovered = isHovered;
}
public get hovered() {
return this._isHovered;
}
/** /**
* Sets whether or not this element should be enabled when the group * 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 * is it apart of becomes enabled. This is useful if an element should
@ -69,6 +89,7 @@ export abstract class BaseUIElement<T> {
public finalise(): void { public finalise(): void {
this._onEnabledChanged(); this._onEnabledChanged();
this._updateStyles();
} }
/** /**
@ -91,4 +112,10 @@ export abstract class BaseUIElement<T> {
* A delegate that is called when the enabled status is changed. * A delegate that is called when the enabled status is changed.
*/ */
protected abstract _onEnabledChanged(): void; protected abstract _onEnabledChanged(): void;
/**
* Called after _onEnabledChanged() and _onValueChanged()
*/
protected _updateStyles(): void {
}
} }

View File

@ -71,27 +71,39 @@ export class ButtonElement extends BaseUIElement<HTMLDivElement> {
public override generateHTML() { public override generateHTML() {
return ` return `
<div class="button" id="${this._getId()}"> <div class="container-button">
<div class="struct-prop button" id="${this._getId()}">
<div class="button-label">${this._label}</div> <div class="button-label">${this._label}</div>
<div class="button-progress" id="${this._getProgressBarId()}"></div> <div class="button-progress" id="${this._getProgressBarId()}"></div>
</div> </div>
</div>
`; `;
} }
public override registerEvents(): void { public override registerEvents(): void {
this._getElement().addEventListener('click', () => { this._getElement().addEventListener('click', () => {
if (this.getEnabled()) { if (this.enabled) {
this._onClick?.(); this._onClick?.();
} }
}); });
this._getElement().addEventListener('mouseenter', () => {
this._setHovered(true);
this._updateStyles();
});
this._getElement().addEventListener('mouseleave', () => {
this._setHovered(false);
this._updateStyles();
});
} }
protected override _onEnabledChanged() { protected override _onEnabledChanged() {
if (this.getEnabled()) { this._updateStyles();
this._getElement().classList.remove('button-disabled');
} else {
this._getElement().classList.add('button-disabled');
} }
public override finalise(): void {
this._updateStyles();
} }
/** /**
@ -100,4 +112,12 @@ export class ButtonElement extends BaseUIElement<HTMLDivElement> {
private _getProgressBarId() { private _getProgressBarId() {
return this._getId() + '-progress'; return this._getId() + '-progress';
} }
protected _updateStyles(): void {
UIUtil.updateStyles(this._getElement(), {
isActive: true,
isEnabled: this.enabled,
isHovered: this.hovered,
});
}
} }

View File

@ -4,13 +4,11 @@ import { ConfigUIElement } from './config_element';
export class CheckboxElement extends ConfigUIElement<boolean, HTMLSelectElement> { export class CheckboxElement extends ConfigUIElement<boolean, HTMLSelectElement> {
private _labelChecked: string; private _labelChecked: string;
private _labelUnchecked: string; private _labelUnchecked: string;
private _hovering: boolean;
public constructor() { public constructor() {
super(false); super(false);
this._labelChecked = 'On'; this._labelChecked = 'On';
this._labelUnchecked = 'Off'; this._labelUnchecked = 'Off';
this._hovering = false;
} }
public setCheckedText(label: string) { public setCheckedText(label: string) {
@ -59,15 +57,14 @@ export class CheckboxElement extends ConfigUIElement<boolean, HTMLSelectElement>
} }
private _onMouseEnterLeave(isHovering: boolean) { private _onMouseEnterLeave(isHovering: boolean) {
this._hovering = isHovering; this._setHovered(isHovering);
this._updateStyles(); this._updateStyles();
} }
public override _generateInnerHTML(): string { public override _generateInnerHTML(): string {
return ` return `
<div class="checkbox" id="${this._getId()}"> <div class="struct-prop container-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"> <svg id="${this._getPipId()}" 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 stroke="none" d="M0 0h24v24H0z" fill="none"/>
<path d="M5 12l5 5l10 -10" /> <path d="M5 12l5 5l10 -10" />
</svg> </svg>
@ -86,7 +83,6 @@ export class CheckboxElement extends ConfigUIElement<boolean, HTMLSelectElement>
protected override _onEnabledChanged(): void { protected override _onEnabledChanged(): void {
super._onEnabledChanged(); super._onEnabledChanged();
this._updateStyles(); this._updateStyles();
} }
@ -106,36 +102,33 @@ export class CheckboxElement extends ConfigUIElement<boolean, HTMLSelectElement>
this._setValue(false); this._setValue(false);
} }
private _updateStyles() { protected _updateStyles() {
{
const checkboxElement = UIUtil.getElementById(this._getId()); const checkboxElement = UIUtil.getElementById(this._getId());
UIUtil.updateStyles(checkboxElement, {
isEnabled: this.enabled,
isHovered: this.hovered,
isActive: false,
});
}
const checkboxPipElement = UIUtil.getElementById(this._getPipId()); const checkboxPipElement = UIUtil.getElementById(this._getPipId());
const checkboxTextElement = UIUtil.getElementById(this._getTextId()); const checkboxTextElement = UIUtil.getElementById(this._getTextId());
checkboxElement.classList.remove('checkbox-disabled');
checkboxElement.classList.remove('checkbox-hover');
checkboxPipElement.classList.remove('checkbox-pip-disabled');
checkboxPipElement.classList.remove('checkbox-pip-hover');
checkboxTextElement.classList.remove('text-dark'); checkboxTextElement.classList.remove('text-dark');
checkboxTextElement.classList.remove('text-standard'); checkboxTextElement.classList.remove('text-standard');
checkboxTextElement.classList.remove('text-light'); checkboxTextElement.classList.remove('text-light');
checkboxTextElement.classList.remove('checkbox-text-hover');
checkboxTextElement.innerHTML = this.getValue() ? this._labelChecked : this._labelUnchecked; checkboxTextElement.innerHTML = this.getValue() ? this._labelChecked : this._labelUnchecked;
checkboxPipElement.style.visibility = this.getValue() ? 'visible' : 'hidden'; checkboxPipElement.style.visibility = this.getValue() ? 'visible' : 'hidden';
if (this.enabled) { if (this.enabled) {
if (this._hovering) { if (this.hovered) {
checkboxElement.classList.add('checkbox-hover');
checkboxPipElement.classList.add('checkbox-pip-hover');
checkboxTextElement.classList.add('text-light'); checkboxTextElement.classList.add('text-light');
checkboxTextElement.classList.add('checkbox-text-hover');
} else if (this.getValue()) { } else if (this.getValue()) {
checkboxTextElement.classList.add('text-standard'); checkboxTextElement.classList.add('text-standard');
} }
} else { } else {
checkboxElement.classList.add('checkbox-disabled');
checkboxTextElement.classList.add('text-dark'); checkboxTextElement.classList.add('text-dark');
checkboxPipElement.classList.add('checkbox-pip-disabled');
} }
} }
} }

View File

@ -0,0 +1,29 @@
import { RGBA, RGBAUtil } from '../../colour';
import { ConfigUIElement } from './config_element';
export class ColourElement extends ConfigUIElement<RGBA, HTMLInputElement> {
public constructor(colour: RGBA) {
super(colour);
}
protected override _generateInnerHTML(): string {
return `<input class="colour-swatch" type="color" id="${this._getId()}" value="${RGBAUtil.toHexString(this.getValue())}">`;
}
public override registerEvents(): void {
this._getElement().addEventListener('change', () => {
const newColour = RGBAUtil.fromHexString(this._getElement().value);
this._setValue(newColour);
});
}
protected _onEnabledChanged(): void {
super._onEnabledChanged();
if (this.enabled) {
this._getElement().classList.add('enabled');
} else {
this._getElement().classList.remove('enabled');
}
}
}

View File

@ -1,4 +1,6 @@
import { ASSERT } from '../../util/error_util'; import { UIUtil } from '../../util/ui_util';
import { AppIcons } from '../icons';
import { HTMLBuilder } from '../misc';
import { ConfigUIElement } from './config_element'; import { ConfigUIElement } from './config_element';
export type ComboBoxItem<T> = { export type ComboBoxItem<T> = {
@ -9,12 +11,10 @@ export type ComboBoxItem<T> = {
export class ComboBoxElement<T> extends ConfigUIElement<T, HTMLSelectElement> { export class ComboBoxElement<T> extends ConfigUIElement<T, HTMLSelectElement> {
private _items: ComboBoxItem<T>[]; private _items: ComboBoxItem<T>[];
private _small: boolean;
public constructor() { public constructor() {
super(); super();
this._items = []; this._items = [];
this._small = false;
} }
public addItems(items: ComboBoxItem<T>[]) { public addItems(items: ComboBoxItem<T>[]) {
@ -30,62 +30,83 @@ export class ComboBoxElement<T> extends ConfigUIElement<T, HTMLSelectElement> {
return this; return this;
} }
public setSmall() {
this._small = true;
return this;
}
public override registerEvents(): void { 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) => { this._getElement().addEventListener('change', (e: Event) => {
const selectedValue = this._items[this._getElement().selectedIndex].payload; const selectedValue = this._items[this._getElement().selectedIndex].payload;
this._setValue(selectedValue); this._setValue(selectedValue);
}); });
} }
/*
public override setDefaultValue(value: T): this {
super.setDefaultValue(value);
const element = this._getElement();
const newSelectedIndex = this._items.findIndex((item) => item.payload === value);
ASSERT(newSelectedIndex !== -1, 'Invalid selected index');
element.selectedIndex = newSelectedIndex;
return this;
}
*/
public override _generateInnerHTML() { public override _generateInnerHTML() {
//ASSERT(this._items.length > 0); const builder = new HTMLBuilder();
let itemsHTML = ''; 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) { for (const item of this._items) {
itemsHTML += `<option value="${item.payload}" title="${item.tooltip || ''}">${item.displayText}</option>`; 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();
} }
return ` protected _onValueChanged(): void {
<select class="${this._small ? 'height-small' : 'height-normal'}" name="${this._getId()}" id="${this._getId()}"> super._onValueChanged();
${itemsHTML}
</select> console.log('combo changed');
`;
} }
protected override _onEnabledChanged() { protected _onEnabledChanged(): void {
super._onEnabledChanged(); super._onEnabledChanged();
this._getElement().disabled = !this.getEnabled(); this._getElement().disabled = this.disabled;
this._updateStyles();
} }
// TODO: Subproperty combo boxes are not updating values when changed!!! protected override _updateStyles(): void {
UIUtil.updateStyles(this._getElement(), {
isHovered: this.hovered,
isEnabled: this.enabled,
isActive: false,
});
protected override _onValueChanged(): void { 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 { public override finalise(): void {
super.finalise();
const selectedIndex = this._items.findIndex((item) => item.payload === this.getValue()); const selectedIndex = this._items.findIndex((item) => item.payload === this.getValue());
const element = this._getElement(); const element = this._getElement();
//ASSERT(selectedIndex !== -1, 'Invalid selected index');
element.selectedIndex = selectedIndex; element.selectedIndex = selectedIndex;
this._updateStyles();
} }
} }

View File

@ -72,10 +72,12 @@ export abstract class ConfigUIElement<T, F> extends BaseUIElement<F> {
public override finalise(): void { public override finalise(): void {
super.finalise(); super.finalise();
/*
this._onValueChanged(); this._onValueChanged();
this._onValueChangedListeners.forEach((listener) => { this._onValueChangedListeners.forEach((listener) => {
listener(this._value!); listener(this._value!);
}); });
*/
} }
public override generateHTML() { public override generateHTML() {
@ -99,10 +101,12 @@ export abstract class ConfigUIElement<T, F> extends BaseUIElement<F> {
protected override _onEnabledChanged() { protected override _onEnabledChanged() {
const label = document.getElementById(this._getLabelId()) as (HTMLDivElement | null); const label = document.getElementById(this._getLabelId()) as (HTMLDivElement | null);
if (this.getEnabled()) { if (this.enabled) {
label?.classList.remove('text-disabled'); label?.classList.remove('text-dark');
label?.classList.add('text-standard');
} else { } else {
label?.classList.add('text-disabled'); label?.classList.add('text-dark');
label?.classList.remove('text-standard');
} }
this._onEnabledChangedListeners.forEach((listener) => { this._onEnabledChangedListeners.forEach((listener) => {
@ -125,7 +129,8 @@ export abstract class ConfigUIElement<T, F> extends BaseUIElement<F> {
/** /**
* A delegate that is called when the value of this element changes. * A delegate that is called when the value of this element changes.
*/ */
protected abstract _onValueChanged(): void; protected _onValueChanged(): void {
}
protected _getLabelId() { protected _getLabelId() {
return this._getId() + '_label'; return this._getId() + '_label';

View File

@ -4,29 +4,17 @@ import { ASSERT } from '../../util/error_util';
import { UIUtil } from '../../util/ui_util'; import { UIUtil } from '../../util/ui_util';
import { ConfigUIElement } from './config_element'; import { ConfigUIElement } from './config_element';
export class FileInputElement extends ConfigUIElement<Promise<string>, HTMLDivElement> { export class ObjFileInputElement extends ConfigUIElement<Promise<string>, HTMLDivElement> {
private _fileExtensions: string[];
private _loadedFilePath: string; private _loadedFilePath: string;
private _hovering: boolean;
public constructor() { public constructor() {
super(Promise.resolve('')); super(Promise.resolve(''));
this._fileExtensions = [];
this._loadedFilePath = ''; this._loadedFilePath = '';
this._hovering = false;
}
/**
* Set the allow list of file extensions that can be uploaded.
*/
public setFileExtensions(extensions: string[]) {
this._fileExtensions = extensions;
return this;
} }
protected override _generateInnerHTML() { protected override _generateInnerHTML() {
return ` return `
<div class="input-file" id="${this._getId()}"> <div class="input-file struct-prop" id="${this._getId()}">
<input type="file" accept=".obj" style="display: none;" id="${this._getId()}-input"> <input type="file" accept=".obj" style="display: none;" id="${this._getId()}-input">
${this._loadedFilePath} ${this._loadedFilePath}
</div> </div>
@ -35,13 +23,13 @@ export class FileInputElement extends ConfigUIElement<Promise<string>, HTMLDivEl
public override registerEvents(): void { public override registerEvents(): void {
this._getElement().addEventListener('mouseenter', () => { this._getElement().addEventListener('mouseenter', () => {
this._hovering = true; this._setHovered(true);
this._updateStyle(); this._updateStyles();
}); });
this._getElement().addEventListener('mouseleave', () => { this._getElement().addEventListener('mouseleave', () => {
this._hovering = false; this._setHovered(false);
this._updateStyle(); this._updateStyles();
}); });
const inputElement = UIUtil.getElementById(this._getId() + '-input') as HTMLInputElement; const inputElement = UIUtil.getElementById(this._getId() + '-input') as HTMLInputElement;
@ -57,43 +45,24 @@ export class FileInputElement extends ConfigUIElement<Promise<string>, HTMLDivEl
}); });
this._getElement().addEventListener('click', () => { this._getElement().addEventListener('click', () => {
if (!this.getEnabled()) { if (this.enabled) {
return;
}
inputElement.click(); inputElement.click();
}); }
this._getElement().addEventListener('mousemove', () => {
this._updateStyle();
}); });
} }
protected override _onEnabledChanged() { protected _onValueChanged(): void {
super._onEnabledChanged(); this._updateStyles();
if (this.getEnabled()) {
this._getElement().classList.remove('input-file-disabled');
} else {
this._getElement().classList.add('input-file-disabled');
}
} }
protected override _onValueChanged(): void { protected override _updateStyles() {
const parsedPath = path.parse(this._loadedFilePath); const parsedPath = path.parse(this._loadedFilePath);
this._getElement().innerHTML = parsedPath.name + parsedPath.ext; this._getElement().innerHTML = parsedPath.name + parsedPath.ext;
}
private _updateStyle() { UIUtil.updateStyles(this._getElement(), {
this._getElement().classList.remove('input-file-disabled'); isHovered: this.hovered,
this._getElement().classList.remove('input-file-hover'); isEnabled: this.enabled,
isActive: false,
if (this.getEnabled()) { });
if (this._hovering) {
this._getElement().classList.add('input-file-hover');
}
} else {
this._getElement().classList.add('input-file-disabled');
}
} }
} }

View File

@ -7,7 +7,7 @@ import { ConfigUIElement } from './config_element';
export abstract class FullConfigUIElement<T, F> extends ConfigUIElement<T, F> { export abstract class FullConfigUIElement<T, F> extends ConfigUIElement<T, F> {
public override generateHTML() { public override generateHTML() {
return ` return `
<div class="property full-width-property" style="flex-direction: column; align-items: start;"> <div class="property" style="flex-direction: column; align-items: start;">
<div class="prop-key-container" id="${this._getLabelId()}"> <div class="prop-key-container" id="${this._getLabelId()}">
${this._label} ${this._label}
</div> </div>

View File

@ -43,7 +43,6 @@ export class HeaderUIElement extends BaseUIElement<HTMLDivElement> {
public override generateHTML(): string { public override generateHTML(): string {
return ` return `
<div class="property">
<div class="col-container header-cols"> <div class="col-container header-cols">
<div class="col-container"> <div class="col-container">
<div class="col-item"> <div class="col-item">
@ -60,22 +59,12 @@ export class HeaderUIElement extends BaseUIElement<HTMLDivElement> {
</div> </div>
</div> </div>
</div> </div>
<div class="col-container"> <div class="toolbar-group">
<div class="col-item">
${this._githubButton.generateHTML()} ${this._githubButton.generateHTML()}
</div>
<div class="col-item">
${this._bugButton.generateHTML()} ${this._bugButton.generateHTML()}
</div>
<div class="col-item">
${this._discordButton.generateHTML()} ${this._discordButton.generateHTML()}
</div> </div>
</div> </div>
</div>
</div>
<div class="property changelog">
${AppConfig.Get.CHANGELOG.join('<br>')}
</div>
`; `;
} }
@ -86,6 +75,9 @@ export class HeaderUIElement extends BaseUIElement<HTMLDivElement> {
} }
public override finalise(): void { public override finalise(): void {
this._githubButton.finalise();
this._bugButton.finalise();
this._discordButton.finalise();
// const updateElement = UIUtil.getElementById('update-checker') as HTMLDivElement; // const updateElement = UIUtil.getElementById('update-checker') as HTMLDivElement;
// updateElement.style.animation = 'pulse-opacity 1.5s infinite'; // updateElement.style.animation = 'pulse-opacity 1.5s infinite';
// updateElement.innerHTML = '<i style="animation: pulse-opacity 1.5s infinite;">Checking for updates...</i>'; // updateElement.innerHTML = '<i style="animation: pulse-opacity 1.5s infinite;">Checking for updates...</i>';

View File

@ -15,7 +15,6 @@ export class ImageElement extends ConfigUIElement<Promise<TImageRawWrap>, HTMLIm
super(Promise.resolve(param ?? { raw: '', filetype: 'png' })); super(Promise.resolve(param ?? { raw: '', filetype: 'png' }));
this._switchElement = new ToolbarItemElement({ id: 'sw', iconSVG: AppIcons.UPLOAD }) this._switchElement = new ToolbarItemElement({ id: 'sw', iconSVG: AppIcons.UPLOAD })
.setSmall()
.setLabel('Choose') .setLabel('Choose')
.onClick(() => { .onClick(() => {
const inputElement = UIUtil.getElementById(this._getId() + '-input') as HTMLInputElement; const inputElement = UIUtil.getElementById(this._getId() + '-input') as HTMLInputElement;
@ -30,16 +29,18 @@ export class ImageElement extends ConfigUIElement<Promise<TImageRawWrap>, HTMLIm
<div class="row-container"> <div class="row-container">
<div class="row-item"> <div class="row-item">
<img id="${this._imageId}" alt="Texture Preview" class="texture-preview" loading="lazy"></img> <img id="${this._imageId}" alt="Texture Preview" class="texture-preview" loading="lazy"></img>
<div id="${this._imageId}-placeholder" class="texture-preview-placeholder">
<div class="row-container" style="align-items: center;">
<div class="row-item">${AppIcons.IMAGE_MISSING}</div>
<div class="row-item">No image loaded</div>
</div>
</div>
</div> </div>
<div class="row-item"> <div class="row-item">
<div class="col-container">
<div class="col-item">
<input type="file" accept="images/png" style="display: none;" id="${this._getId()}-input"> <input type="file" accept="images/png" style="display: none;" id="${this._getId()}-input">
${this._switchElement.generateHTML()} ${this._switchElement.generateHTML()}
</div> </div>
</div> </div>
</div>
</div>
`; `;
} }
@ -70,10 +71,15 @@ export class ImageElement extends ConfigUIElement<Promise<TImageRawWrap>, HTMLIm
} }
protected override _onEnabledChanged(): void { protected override _onEnabledChanged(): void {
super._onEnabledChanged();
this._switchElement.setEnabled(this.enabled);
} }
protected override _onValueChanged(): void { protected override _onValueChanged(): void {
const inputElement = UIUtil.getElementById(this._imageId) as HTMLImageElement; const inputElement = UIUtil.getElementById(this._imageId) as HTMLImageElement;
const placeholderElement = UIUtil.getElementById(this._imageId + '-placeholder');
this.getValue() this.getValue()
.then((res) => { .then((res) => {
if (res.raw === '') { if (res.raw === '') {
@ -82,11 +88,13 @@ export class ImageElement extends ConfigUIElement<Promise<TImageRawWrap>, HTMLIm
this._switchElement.setActive(false); this._switchElement.setActive(false);
inputElement.src = res.raw; inputElement.src = res.raw;
inputElement.style.display = 'unset'; inputElement.style.display = 'unset';
placeholderElement.style.display = 'none';
}) })
.catch((err) => { .catch((err) => {
this._switchElement.setActive(true); this._switchElement.setActive(true);
inputElement.src = ''; inputElement.src = '';
inputElement.style.display = 'none'; inputElement.style.display = 'none';
placeholderElement.style.display = 'flex';
}); });
} }
@ -94,5 +102,6 @@ export class ImageElement extends ConfigUIElement<Promise<TImageRawWrap>, HTMLIm
super.finalise(); super.finalise();
this._onValueChanged(); this._onValueChanged();
this._onEnabledChanged();
} }
} }

View File

@ -4,48 +4,58 @@ import { ConfigUIElement } from './config_element';
import { ToolbarItemElement } from './toolbar_item'; import { ToolbarItemElement } from './toolbar_item';
export class MaterialTypeElement extends ConfigUIElement<MaterialType, HTMLDivElement> { export class MaterialTypeElement extends ConfigUIElement<MaterialType, HTMLDivElement> {
private _switchElement: ToolbarItemElement; private _solidButton: ToolbarItemElement;
private _texturedButton: ToolbarItemElement;
private _material: SolidMaterial | TexturedMaterial; private _material: SolidMaterial | TexturedMaterial;
public constructor(material: SolidMaterial | TexturedMaterial) { public constructor(material: SolidMaterial | TexturedMaterial) {
super(material.type); super(material.type);
this._material = material; this._material = material;
this._switchElement = new ToolbarItemElement({ id: 'sw2', iconSVG: AppIcons.SWITCH })
.setSmall() this._solidButton = new ToolbarItemElement({ id: 'sw1', iconSVG: AppIcons.COLOUR_SWATCH })
.setLabel('Switch') .setLabel('Solid')
.onClick(() => { .onClick(() => {
if (this._material.type === MaterialType.textured) {
this._onClickChangeTypeDelegate?.(); this._onClickChangeTypeDelegate?.();
}
});
this._texturedButton = new ToolbarItemElement({ id: 'sw2', iconSVG: AppIcons.IMAGE })
.setLabel('Textured')
.onClick(() => {
if (this._material.type === MaterialType.solid) {
this._onClickChangeTypeDelegate?.();
}
}); });
} }
public override _generateInnerHTML() { public override _generateInnerHTML() {
const material = this.getValue();
return ` return `
<div class="row-container"> <div class="toolbar-group" style="width: 100%;">
<div class="row-item"> ${this._solidButton.generateHTML()}
${material === MaterialType.solid ? 'Solid' : 'Textured'} ${this._texturedButton.generateHTML()}
</div>
<div class="row-item">
<div class="col-container">
<div class="col-item">
${this._switchElement.generateHTML()}
</div>
</div>
</div>
</div> </div>
`; `;
} }
public override finalise(): void { public override finalise(): void {
this._switchElement.setActive(this._material.type === MaterialType.solid && this._material.canBeTextured); this._solidButton.finalise();
this._texturedButton.finalise();
this._solidButton.setActive(this._material.type === MaterialType.solid);
this._texturedButton.setActive(this._material.type === MaterialType.textured);
} }
public override registerEvents(): void { public override registerEvents(): void {
this._switchElement.registerEvents(); this._solidButton.registerEvents();
this._texturedButton.registerEvents();
} }
protected override _onEnabledChanged(): void { protected override _onEnabledChanged(): void {
super._onEnabledChanged();
this._solidButton.setEnabled(this.enabled);
this._texturedButton.setEnabled(this.enabled);
} }
protected override _onValueChanged(): void { protected override _onValueChanged(): void {

View File

@ -8,10 +8,11 @@ import { AppConsole } from '../console';
import { AppIcons } from '../icons'; import { AppIcons } from '../icons';
import { ButtonElement } from './button'; import { ButtonElement } from './button';
import { CheckboxElement } from './checkbox'; import { CheckboxElement } from './checkbox';
import { ConfigUIElement } from './config_element';
import { FullConfigUIElement } from './full_config_element'; import { FullConfigUIElement } from './full_config_element';
import { ToolbarItemElement } from './toolbar_item'; import { ToolbarItemElement } from './toolbar_item';
export class PaletteElement extends FullConfigUIElement<Palette, HTMLDivElement> { export class PaletteElement extends ConfigUIElement<Palette, HTMLDivElement> {
private _checkboxes: { block: string, element: CheckboxElement }[]; private _checkboxes: { block: string, element: CheckboxElement }[];
private _palette: Palette; private _palette: Palette;
private _selectAll: ToolbarItemElement; private _selectAll: ToolbarItemElement;
@ -133,15 +134,9 @@ export class PaletteElement extends FullConfigUIElement<Palette, HTMLDivElement>
checkboxesHTML += '</div>'; checkboxesHTML += '</div>';
}); });
/*
<select>
<option value="All">All</option>
</select>
*/
return ` return `
<div class="row-container" style="width: 100%; gap: 5px;"> <div class="row-container" style="width: 100%; gap: 5px;">
<input type="text" style="width: 100%;" placeholder="Search..." id="${this._getId() + '-search'}"></input> <input class="struct-prop" type="text" style="width: 100%; text-align: start;" placeholder="Search..." id="${this._getId() + '-search'}"></input>
<div class="col-container header-cols" style="padding-top: 0px;"> <div class="col-container header-cols" style="padding-top: 0px;">
<div class="col-container"> <div class="col-container">
<div class="col-item"> <div class="col-item">
@ -186,6 +181,7 @@ export class PaletteElement extends FullConfigUIElement<Palette, HTMLDivElement>
this._exportTo.setEnabled(this.enabled); this._exportTo.setEnabled(this.enabled);
this._onCountSelectedChanged(); this._onCountSelectedChanged();
this._updateStyles();
} }
public override registerEvents(): void { public override registerEvents(): void {
@ -193,6 +189,14 @@ export class PaletteElement extends FullConfigUIElement<Palette, HTMLDivElement>
searchElement.addEventListener('keyup', () => { searchElement.addEventListener('keyup', () => {
this._onSearchBoxChanged(searchElement.value); this._onSearchBoxChanged(searchElement.value);
}); });
searchElement.addEventListener('mouseenter', () => {
this._setHovered(true);
this._updateStyles();
});
searchElement.addEventListener('mouseleave', () => {
this._setHovered(false);
this._updateStyles();
});
this._checkboxes.forEach((checkbox) => { this._checkboxes.forEach((checkbox) => {
checkbox.element.registerEvents(); checkbox.element.registerEvents();
@ -218,7 +222,14 @@ export class PaletteElement extends FullConfigUIElement<Palette, HTMLDivElement>
checkbox.element.finalise(); checkbox.element.finalise();
}); });
this._selectAll.finalise();
this._deselectAll.finalise();
this._importFrom.finalise();
this._exportTo.finalise();
this._onCountSelectedChanged(); this._onCountSelectedChanged();
this._updateStyles();
//this._selectAll.finalise(); //this._selectAll.finalise();
//this._deselectAll.finalise(); //this._deselectAll.finalise();
//this._importFrom.finalise(); //this._importFrom.finalise();
@ -235,4 +246,12 @@ export class PaletteElement extends FullConfigUIElement<Palette, HTMLDivElement>
} }
}); });
} }
protected override _updateStyles(): void {
UIUtil.updateStyles(UIUtil.getElementById(this._getId() + '-search'), {
isActive: false,
isEnabled: this.enabled,
isHovered: this.hovered,
});
}
} }

View File

@ -0,0 +1,30 @@
import { ConfigUIElement } from './config_element';
export class PlaceholderElement extends ConfigUIElement<undefined, HTMLDivElement> {
private _placeholderText: string;
public constructor(placeholderText: string) {
super(undefined);
this._placeholderText = placeholderText;
}
public override generateHTML(): string {
return `
<div class="property" style="justify-content: center;">
${this._generateInnerHTML()}
</div>
`;
}
protected override _generateInnerHTML(): string {
return `
<div class="text-dark">
${this._placeholderText}
</div>
`;
}
public override registerEvents(): void {
}
}

View File

@ -17,9 +17,8 @@ export class SliderElement extends ConfigUIElement<number, HTMLDivElement> {
private _decimals: number; private _decimals: number;
private _step: number; private _step: number;
private _dragging: boolean; private _dragging: boolean;
private _hovering: boolean;
private _internalValue: number; private _internalValue: number;
private _small: boolean; private _valueHovered: boolean;
public constructor() { public constructor() {
super(); super();
@ -29,8 +28,7 @@ export class SliderElement extends ConfigUIElement<number, HTMLDivElement> {
this._step = 0.1; this._step = 0.1;
this._internalValue = 0.5; this._internalValue = 0.5;
this._dragging = false; this._dragging = false;
this._hovering = false; this._valueHovered = false;
this._small = false;
} }
public override setDefaultValue(value: number) { public override setDefaultValue(value: number) {
@ -39,11 +37,6 @@ export class SliderElement extends ConfigUIElement<number, HTMLDivElement> {
return this; return this;
} }
public setSmall() {
this._small = true;
return this;
}
/** /**
* Set the minimum value the slider can be set to. * Set the minimum value the slider can be set to.
*/ */
@ -82,19 +75,13 @@ export class SliderElement extends ConfigUIElement<number, HTMLDivElement> {
const elementValue = UIUtil.getElementById(this._getSliderValueId()) as HTMLInputElement; const elementValue = UIUtil.getElementById(this._getSliderValueId()) as HTMLInputElement;
element.onmouseenter = () => { element.onmouseenter = () => {
this._hovering = true; this._setHovered(true);
if (this.getEnabled()) { this._updateStyles();
element.classList.add('new-slider-hover');
elementBar.classList.add('new-slider-bar-hover');
}
}; };
element.onmouseleave = () => { element.onmouseleave = () => {
this._hovering = false; this._setHovered(false);
if (!this._dragging) { this._updateStyles();
element.classList.remove('new-slider-hover');
elementBar.classList.remove('new-slider-bar-hover');
}
}; };
element.onmousedown = () => { element.onmousedown = () => {
@ -111,10 +98,6 @@ export class SliderElement extends ConfigUIElement<number, HTMLDivElement> {
if (this._dragging) { if (this._dragging) {
this._onDragSlider(e); this._onDragSlider(e);
} }
if (!this._hovering) {
element.classList.remove('new-slider-hover');
elementBar.classList.remove('new-slider-bar-hover');
}
this._dragging = false; this._dragging = false;
}); });
@ -128,15 +111,25 @@ export class SliderElement extends ConfigUIElement<number, HTMLDivElement> {
elementValue.addEventListener('change', () => { elementValue.addEventListener('change', () => {
this._onTypedValue(); this._onTypedValue();
}); });
elementValue.addEventListener('mouseenter', () => {
this._valueHovered = true;
this._updateStyles();
});
elementValue.addEventListener('mouseleave', () => {
this._valueHovered = false;
this._updateStyles();
});
} }
public override _generateInnerHTML() { public override _generateInnerHTML() {
const norm = (this._internalValue - this._min) / (this._max - this._min); const norm = (this._internalValue - this._min) / (this._max - this._min);
return ` return `
<input class="${this._small ? 'slider-height-small' : 'slider-height-normal'}" type="number" id="${this._getSliderValueId()}" min="${this._min}" max="${this._max}" step="${this._step}" value="${this.getValue().toFixed(this._decimals)}"> <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="new-slider ${this._small ? 'slider-bar-height-small' : 'slider-bar-height-normal'} " id="${this._getId()}" style="flex-grow: 1;"> <div class="struct-prop comp-slider comp-slider-outer" id="${this._getId()}">
<div class="new-slider-bar" id="${this._getSliderBarId()}" style="width: ${norm * 100}%;"> <div class="struct-prop comp-slider comp-slider-inner" id="${this._getSliderBarId()}" style="width: ${norm * 100}%">
</div> </div>
</div> </div>
`; `;
@ -145,19 +138,20 @@ export class SliderElement extends ConfigUIElement<number, HTMLDivElement> {
protected override _onEnabledChanged() { protected override _onEnabledChanged() {
super._onEnabledChanged(); super._onEnabledChanged();
const element = this._getElement();
const elementBar = UIUtil.getElementById(this._getSliderBarId());
const elementValue = UIUtil.getElementById(this._getSliderValueId()) as HTMLInputElement; const elementValue = UIUtil.getElementById(this._getSliderValueId()) as HTMLInputElement;
elementValue.disabled = this.disabled;
if (this.getEnabled()) { const elementBar = UIUtil.getElementById(this._getSliderBarId());
element.classList.remove('new-slider-disabled'); const elementSlider = UIUtil.getElementById(this._getId());
elementBar.classList.remove('new-slider-bar-disabled'); if (this.enabled) {
elementValue.disabled = false; elementBar.classList.add('enabled');
elementSlider.classList.add('enabled');
} else { } else {
element.classList.add('new-slider-disabled'); elementBar.classList.remove('enabled');
elementBar.classList.add('new-slider-bar-disabled'); elementSlider.classList.remove('enabled');
elementValue.disabled = true;
} }
this._updateStyles();
} }
protected override _onValueChanged(): void { protected override _onValueChanged(): void {
@ -222,4 +216,27 @@ export class SliderElement extends ConfigUIElement<number, HTMLDivElement> {
private _getSliderBarId() { private _getSliderBarId() {
return this._getId() + '-bar'; 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,80 +1,64 @@
import { RGBAUtil } from '../../colour'; import { SolidMaterial } from '../../mesh';
import { MaterialType, SolidMaterial } from '../../mesh'; import { ColourElement } from './colour_element';
import { getRandomID } from '../../util';
import { UIUtil } from '../../util/ui_util';
import { ConfigUIElement } from './config_element'; import { ConfigUIElement } from './config_element';
import { MaterialTypeElement } from './material_type_element'; import { MaterialTypeElement } from './material_type_element';
import { SliderElement } from './slider'; import { SliderElement } from './slider';
export class SolidMaterialElement extends ConfigUIElement<SolidMaterial, HTMLDivElement> { export class SolidMaterialElement extends ConfigUIElement<SolidMaterial, HTMLDivElement> {
private _materialName: string;
private _colourId: string;
private _typeElement: MaterialTypeElement; private _typeElement: MaterialTypeElement;
private _colourElement: ColourElement;
private _alphaElement: SliderElement; private _alphaElement: SliderElement;
public constructor(materialName: string, material: SolidMaterial) { public constructor(materialName: string, material: SolidMaterial) {
super(material); super(material);
this._materialName = materialName;
this._colourId = getRandomID();
this._typeElement = new MaterialTypeElement(material); this._typeElement = new MaterialTypeElement(material)
.setLabel('Type');
this._colourElement = new ColourElement(material.colour)
.setLabel('Colour');
this._alphaElement = new SliderElement() this._alphaElement = new SliderElement()
.setLabel('Alpha')
.setMin(0.0) .setMin(0.0)
.setMax(1.0) .setMax(1.0)
.setDefaultValue(material.colour.a) .setDefaultValue(material.colour.a)
.setDecimals(2) .setDecimals(2)
.setStep(0.01) .setStep(0.01);
.setSmall();
} }
public override registerEvents(): void { public override registerEvents(): void {
this._typeElement.registerEvents(); this._typeElement.registerEvents();
this._colourElement.registerEvents();
this._alphaElement.registerEvents(); this._alphaElement.registerEvents();
this._typeElement.onClickChangeTypeDelegate(() => { this._typeElement.onClickChangeTypeDelegate(() => {
this._onChangeTypeDelegate?.(); this._onChangeTypeDelegate?.();
}); });
this._alphaElement.addValueChangedListener((newAlpha) => { this._colourElement.addValueChangedListener((newColour) => {
this.getValue().colour.a = newAlpha; this.getValue().colour.r = newColour.r;
this.getValue().colour.g = newColour.g;
this.getValue().colour.b = newColour.b;
}); });
const swatchElement = UIUtil.getElementById(this._colourId) as HTMLInputElement; this._alphaElement.addValueChangedListener((newAlpha) => {
swatchElement.addEventListener('change', () => { this.getValue().colour.a = newAlpha;
const material = this.getValue();
material.colour = RGBAUtil.fromHexString(swatchElement.value);
}); });
} }
public override finalise(): void { public override finalise(): void {
this._typeElement.finalise(); this._typeElement.finalise();
this._colourElement.finalise();
this._alphaElement.finalise();
} }
protected override _generateInnerHTML(): string { protected override _generateInnerHTML(): string {
const material = this.getValue();
const subproperties: string[] = [];
const addSubproperty = (key: string, value: string) => {
subproperties.push(`
<div class="subproperty">
<div class="subprop-key-container">
${key}
</div>
<div class="subprop-value-container">
${value}
</div>
</div>
`);
};
addSubproperty('Type', this._typeElement._generateInnerHTML());
addSubproperty('Colour', `<input class="colour-swatch" type="color" id="${this._colourId}" value="${RGBAUtil.toHexString(material.colour)}">`);
addSubproperty('Alpha', this._alphaElement._generateInnerHTML());
return ` return `
<div class="subproperty-container"> <div class="component-group">
${subproperties.join('')} ${this._typeElement.generateHTML()}
${this._colourElement.generateHTML()}
${this._alphaElement.generateHTML()}
</div> </div>
`; `;
} }
@ -84,6 +68,10 @@ export class SolidMaterialElement extends ConfigUIElement<SolidMaterial, HTMLDiv
protected override _onEnabledChanged(): void { protected override _onEnabledChanged(): void {
super._onEnabledChanged(); super._onEnabledChanged();
this._typeElement.setEnabled(this.enabled);
this._colourElement.setEnabled(this.enabled);
this._alphaElement.setEnabled(this.enabled);
} }
private _onChangeTypeDelegate?: () => void; private _onChangeTypeDelegate?: () => void;

View File

@ -4,6 +4,8 @@ import { MaterialType, TexturedMaterial } from '../../mesh';
import { EImageChannel, TTransparencyTypes } from '../../texture'; import { EImageChannel, TTransparencyTypes } from '../../texture';
import { getRandomID } from '../../util'; import { getRandomID } from '../../util';
import { ASSERT } from '../../util/error_util'; import { ASSERT } from '../../util/error_util';
import { TTexelInterpolation } from '../../util/type_util';
import { HTMLBuilder } from '../misc';
import { ComboBoxElement } from './combobox'; import { ComboBoxElement } from './combobox';
import { ConfigUIElement } from './config_element'; import { ConfigUIElement } from './config_element';
import { ImageElement } from './image_element'; import { ImageElement } from './image_element';
@ -11,64 +13,64 @@ import { MaterialTypeElement } from './material_type_element';
import { SliderElement } from './slider'; import { SliderElement } from './slider';
export class TexturedMaterialElement extends ConfigUIElement<TexturedMaterial, HTMLDivElement> { export class TexturedMaterialElement extends ConfigUIElement<TexturedMaterial, HTMLDivElement> {
private _materialName: string; private _typeElement: MaterialTypeElement;
private _colourId: string;
private _filteringElement: ComboBoxElement<'nearest' | 'linear'>; private _filteringElement: ComboBoxElement<'nearest' | 'linear'>;
private _wrapElement: ComboBoxElement<'clamp' | 'repeat'>; private _wrapElement: ComboBoxElement<'clamp' | 'repeat'>;
private _transparencyElement: ComboBoxElement<TTransparencyTypes>; private _transparencyElement: ComboBoxElement<TTransparencyTypes>;
private _imageElement: ImageElement; private _imageElement: ImageElement;
private _typeElement: MaterialTypeElement;
private _alphaValueElement?: SliderElement; private _alphaValueElement?: SliderElement;
private _alphaMapElement?: ImageElement; private _alphaMapElement?: ImageElement;
private _alphaChannelElement?: ComboBoxElement<EImageChannel>; private _alphaChannelElement?: ComboBoxElement<EImageChannel>;
public constructor(materialName: string, material: TexturedMaterial) { public constructor(materialName: string, material: TexturedMaterial) {
super(material); super(material);
this._materialName = materialName;
this._colourId = getRandomID();
this._filteringElement = new ComboBoxElement<'linear' | 'nearest'>() this._typeElement = new MaterialTypeElement(material)
.setLabel('Type');
this._filteringElement = new ComboBoxElement<TTexelInterpolation>()
.setLabel('Filtering')
.addItem({ payload: 'linear', displayText: 'Linear' }) .addItem({ payload: 'linear', displayText: 'Linear' })
.addItem({ payload: 'nearest', displayText: 'Nearest' }) .addItem({ payload: 'nearest', displayText: 'Nearest' })
.setSmall()
.setDefaultValue(material.interpolation); .setDefaultValue(material.interpolation);
this._wrapElement = new ComboBoxElement<'clamp' | 'repeat'>() this._wrapElement = new ComboBoxElement<'clamp' | 'repeat'>()
.setLabel('Wrap')
.addItem({ payload: 'clamp', displayText: 'Clamp' }) .addItem({ payload: 'clamp', displayText: 'Clamp' })
.addItem({ payload: 'repeat', displayText: 'Repeat' }) .addItem({ payload: 'repeat', displayText: 'Repeat' })
.setSmall()
.setDefaultValue(material.extension); .setDefaultValue(material.extension);
this._transparencyElement = new ComboBoxElement<TTransparencyTypes>() this._transparencyElement = new ComboBoxElement<TTransparencyTypes>()
.setLabel('Transparency')
.addItem({ payload: 'None', displayText: 'None' }) .addItem({ payload: 'None', displayText: 'None' })
.addItem({ payload: 'UseAlphaMap', displayText: 'Alpha map' }) .addItem({ payload: 'UseAlphaMap', displayText: 'Alpha map' })
.addItem({ payload: 'UseAlphaValue', displayText: 'Alpha constant' }) .addItem({ payload: 'UseAlphaValue', displayText: 'Alpha constant' })
.addItem({ payload: 'UseDiffuseMapAlphaChannel', displayText: 'Diffuse map alpha channel' }) .addItem({ payload: 'UseDiffuseMapAlphaChannel', displayText: 'Diffuse map alpha channel' })
.setSmall()
.setDefaultValue(material.transparency.type); .setDefaultValue(material.transparency.type);
this._imageElement = new ImageElement(material.diffuse); this._imageElement = new ImageElement(material.diffuse)
.setLabel('Diffuse map');
this._typeElement = new MaterialTypeElement(material);
switch (material.transparency.type) { switch (material.transparency.type) {
case 'UseAlphaValue': case 'UseAlphaValue':
this._alphaValueElement = new SliderElement() this._alphaValueElement = new SliderElement()
.setLabel('Alpha')
.setMin(0.0) .setMin(0.0)
.setMax(1.0) .setMax(1.0)
.setDefaultValue(material.transparency.alpha) .setDefaultValue(material.transparency.alpha)
.setDecimals(2) .setDecimals(2)
.setStep(0.01) .setStep(0.01);
.setSmall();
break; break;
case 'UseAlphaMap': case 'UseAlphaMap':
this._alphaMapElement = new ImageElement(material.transparency.alpha); this._alphaMapElement = new ImageElement(material.transparency.alpha)
.setLabel('Alpha map');
this._alphaChannelElement = new ComboBoxElement<EImageChannel>() this._alphaChannelElement = new ComboBoxElement<EImageChannel>()
.setLabel('Alpha channel')
.addItem({ payload: EImageChannel.R, displayText: 'Red' }) .addItem({ payload: EImageChannel.R, displayText: 'Red' })
.addItem({ payload: EImageChannel.G, displayText: 'Green' }) .addItem({ payload: EImageChannel.G, displayText: 'Green' })
.addItem({ payload: EImageChannel.B, displayText: 'Blue' }) .addItem({ payload: EImageChannel.B, displayText: 'Blue' })
.addItem({ payload: EImageChannel.A, displayText: 'Alpha' }) .addItem({ payload: EImageChannel.A, displayText: 'Alpha' })
.setSmall()
.setDefaultValue(material.transparency.channel); .setDefaultValue(material.transparency.channel);
break; break;
} }
@ -139,39 +141,27 @@ export class TexturedMaterialElement extends ConfigUIElement<TexturedMaterial, H
} }
protected override _generateInnerHTML(): string { protected override _generateInnerHTML(): string {
const subproperties: string[] = []; const builder = new HTMLBuilder();
const addSubproperty = (key: string, value: string) => {
subproperties.push(`
<div class="subproperty">
<div class="subprop-key-container">
${key}
</div>
<div class="subprop-value-container">
${value}
</div>
</div>
`);
};
addSubproperty('Type', this._typeElement._generateInnerHTML()); builder.add('<div class="component-group">');
addSubproperty('Diffuse map', this._imageElement._generateInnerHTML()); {
addSubproperty('Filtering', this._filteringElement._generateInnerHTML()); builder.add(this._typeElement.generateHTML());
addSubproperty('Wrap', this._wrapElement._generateInnerHTML()); builder.add(this._imageElement.generateHTML());
addSubproperty('Transparency', this._transparencyElement._generateInnerHTML()); builder.add(this._filteringElement.generateHTML());
builder.add(this._wrapElement.generateHTML());
builder.add(this._transparencyElement.generateHTML());
if (this._alphaMapElement !== undefined) { if (this._alphaMapElement !== undefined) {
ASSERT(this._alphaChannelElement !== undefined); ASSERT(this._alphaChannelElement !== undefined);
addSubproperty('Alpha map', this._alphaMapElement._generateInnerHTML()); builder.add(this._alphaMapElement.generateHTML());
addSubproperty('Channel', this._alphaChannelElement._generateInnerHTML()); builder.add(this._alphaChannelElement.generateHTML());
} }
if (this._alphaValueElement) { if (this._alphaValueElement !== undefined) {
addSubproperty('Alpha', this._alphaValueElement._generateInnerHTML()); builder.add(this._alphaValueElement.generateHTML());
} }
}
builder.add('</div>');
return ` return builder.toString();
<div class="subproperty-container">
${subproperties.join('')}
</div>
`;
} }
protected override _onValueChanged(): void { protected override _onValueChanged(): void {
@ -179,6 +169,15 @@ export class TexturedMaterialElement extends ConfigUIElement<TexturedMaterial, H
protected override _onEnabledChanged(): void { protected override _onEnabledChanged(): void {
super._onEnabledChanged(); super._onEnabledChanged();
this._imageElement.setEnabled(this.enabled);
this._typeElement.setEnabled(this.enabled);
this._filteringElement.setEnabled(this.enabled);
this._wrapElement.setEnabled(this.enabled);
this._transparencyElement.setEnabled(this.enabled);
this._alphaValueElement?.setEnabled(this.enabled);
this._alphaMapElement?.setEnabled(this.enabled);
this._alphaChannelElement?.setEnabled(this.enabled);
} }
public override finalise(): void { public override finalise(): void {

View File

@ -3,6 +3,7 @@ import { ASSERT } from '../../util/error_util';
import { AppPaths } from '../../util/path_util'; import { AppPaths } from '../../util/path_util';
import { PathUtil } from '../../util/path_util'; import { PathUtil } from '../../util/path_util';
import { UIUtil } from '../../util/ui_util'; import { UIUtil } from '../../util/ui_util';
import { BaseUIElement } from './base_element';
export type TToolbarBooleanProperty = 'enabled' | 'active'; export type TToolbarBooleanProperty = 'enabled' | 'active';
@ -11,18 +12,16 @@ export type TToolbarItemParams = {
iconSVG: string; iconSVG: string;
} }
export class ToolbarItemElement { export class ToolbarItemElement extends BaseUIElement<HTMLDivElement> {
private _id: string;
private _iconSVG: SVGSVGElement; private _iconSVG: SVGSVGElement;
private _isEnabled: boolean;
private _isActive: boolean;
private _isHovering: boolean;
private _small: boolean;
private _label: string; private _label: string;
private _onClick?: () => void; private _onClick?: () => void;
private _isActive: boolean;
public constructor(params: TToolbarItemParams) { public constructor(params: TToolbarItemParams) {
this._id = params.id + getRandomID(); super();
this._isActive = false;
{ {
const parser = new DOMParser(); const parser = new DOMParser();
@ -31,20 +30,16 @@ export class ToolbarItemElement {
ASSERT(svgs.length === 1, 'Missing SVG'); ASSERT(svgs.length === 1, 'Missing SVG');
this._iconSVG = svgs[0]; this._iconSVG = svgs[0];
this._iconSVG.id = this._id + '-svg'; this._iconSVG.id = this._getId() + '-svg';
} }
this._isEnabled = true;
this._isActive = false;
this._isHovering = false;
this._small = false;
this._label = ''; this._label = '';
} }
public setSmall() {
this._small = true; public setActive(isActive: boolean) {
return this; this._isActive = isActive;
this._updateStyles();
} }
public setLabel(label: string) { public setLabel(label: string) {
@ -55,18 +50,24 @@ export class ToolbarItemElement {
public tick() { public tick() {
if (this._isEnabledDelegate !== undefined) { if (this._isEnabledDelegate !== undefined) {
const newIsEnabled = this._isEnabledDelegate(); const newIsEnabled = this._isEnabledDelegate();
//if (newIsEnabled !== this._isEnabled) { if (newIsEnabled != this.enabled) {
this.setEnabled(newIsEnabled); this.setEnabled(newIsEnabled);
//} this._updateStyles();
}
} }
if (this._isActiveDelegate !== undefined) { if (this._isActiveDelegate !== undefined) {
const newIsActive = this._isActiveDelegate(); const newIsActive = this._isActiveDelegate();
//if (newIsActive !== this._isActive) { if (newIsActive !== this._isActive) {
this.setActive(newIsActive); this._isActive = newIsActive;
//} this._updateStyles();
} }
} }
}
protected _onEnabledChanged(): void {
this._updateStyles();
}
private _isActiveDelegate?: () => boolean; private _isActiveDelegate?: () => boolean;
public isActive(delegate: () => boolean) { public isActive(delegate: () => boolean) {
@ -88,84 +89,47 @@ export class ToolbarItemElement {
public generateHTML() { public generateHTML() {
return ` return `
<div class="toolbar-item ${this._small ? 'toolbar-item-small' : ''}" id="${this._id}"> <div class="struct-prop container-icon-button" id="${this._getId()}">
${this._iconSVG.outerHTML} ${this._label} ${this._iconSVG.outerHTML} ${this._label}
</div> </div>
`; `;
} }
public registerEvents(): void { public registerEvents(): void {
const element = document.getElementById(this._id) as HTMLDivElement; const element = document.getElementById(this._getId()) as HTMLDivElement;
ASSERT(element !== null); ASSERT(element !== null);
element.addEventListener('click', () => { element.addEventListener('click', () => {
if (this._isEnabled && this._onClick) { if (this.enabled && this._onClick) {
this._onClick(); this._onClick();
} }
}); });
element.addEventListener('mouseenter', () => { element.addEventListener('mouseenter', () => {
this._isHovering = true; this._setHovered(true);
this._updateElements(); this._updateStyles();
}); });
element.addEventListener('mouseleave', () => { element.addEventListener('mouseleave', () => {
this._isHovering = false; this._setHovered(false);
this._updateElements(); this._updateStyles();
}); });
}
this._updateElements(); public override finalise(): void {
this._updateStyles();
} }
private _getSVGElement() { private _getSVGElement() {
const svgId = this._id + '-svg'; const svgId = this._getId() + '-svg';
return UIUtil.getElementById(svgId); return UIUtil.getElementById(svgId);
} }
private _updateElements() { protected override _updateStyles() {
const element = document.getElementById(this._id) as HTMLDivElement; UIUtil.updateStyles(this._getElement(), {
const svgElement = this._getSVGElement(); isActive: this._isActive,
if (element !== null && svgElement !== null) { isEnabled: this.enabled,
element.classList.remove('toolbar-item-active-hover'); isHovered: this.hovered,
element.classList.remove('toolbar-item-disabled'); });
element.classList.remove('toolbar-item-active');
element.classList.remove('toolbar-item-hover');
if (this._isEnabled) {
if (this._isActive) {
if (this._isHovering) {
element.classList.add('toolbar-item-active-hover');
} else {
element.classList.add('toolbar-item-active');
}
} else {
if (this._isHovering) {
element.classList.add('toolbar-item-hover');
}
}
} else {
element.classList.add('toolbar-item-disabled');
}
svgElement.classList.remove('icon-disabled');
svgElement.classList.remove('icon-active');
if (this._isEnabled) {
if (this._isActive) {
svgElement.classList.add('icon-active');
}
} else {
svgElement.classList.add('icon-disabled');
}
}
}
public setEnabled(isEnabled: boolean) {
this._isEnabled = isEnabled;
this._updateElements();
}
public setActive(isActive: boolean) {
this._isActive = isActive;
this._updateElements();
} }
} }

View File

@ -46,7 +46,7 @@ export class VectorSpinboxElement extends ConfigUIElement<Vector3, HTMLDivElemen
html += ` html += `
<div class="spinbox-element-container"> <div class="spinbox-element-container">
<div class="spinbox-key" id="${this._getKeyId('x')}">X</div> <div class="spinbox-key" id="${this._getKeyId('x')}">X</div>
<div class="spinbox-value" id="${this._getValueId('x')}"> <div class="spinbox-value struct-prop" id="${this._getValueId('x')}">
${this.getValue().x} ${this.getValue().x}
</div> </div>
</div> </div>
@ -55,7 +55,7 @@ export class VectorSpinboxElement extends ConfigUIElement<Vector3, HTMLDivElemen
html += ` html += `
<div class="spinbox-element-container"> <div class="spinbox-element-container">
<div class="spinbox-key" id="${this._getKeyId('y')}">Y</div> <div class="spinbox-key" id="${this._getKeyId('y')}">Y</div>
<div class="spinbox-value" id="${this._getValueId('y')}"> <div class="spinbox-value struct-prop" id="${this._getValueId('y')}">
${this.getValue().y} ${this.getValue().y}
</div> </div>
</div> </div>
@ -64,7 +64,7 @@ export class VectorSpinboxElement extends ConfigUIElement<Vector3, HTMLDivElemen
html += ` html += `
<div class="spinbox-element-container"> <div class="spinbox-element-container">
<div class="spinbox-key" id="${this._getKeyId('z')}">Z</div> <div class="spinbox-key" id="${this._getKeyId('z')}">Z</div>
<div class="spinbox-value" id="${this._getValueId('z')}"> <div class="spinbox-value struct-prop" id="${this._getValueId('z')}">
${this.getValue().z} ${this.getValue().z}
</div> </div>
</div> </div>
@ -86,16 +86,12 @@ export class VectorSpinboxElement extends ConfigUIElement<Vector3, HTMLDivElemen
elementValue.onmouseenter = () => { elementValue.onmouseenter = () => {
this._mouseover = axis; this._mouseover = axis;
if (this.getEnabled()) { this._updateStyles();
elementValue.classList.add('spinbox-value-hover');
}
}; };
elementValue.onmouseleave = () => { elementValue.onmouseleave = () => {
this._mouseover = null; this._mouseover = null;
if (this._dragging !== axis) { this._updateStyles();
elementValue.classList.remove('spinbox-value-hover');
}
}; };
} }
@ -107,30 +103,26 @@ export class VectorSpinboxElement extends ConfigUIElement<Vector3, HTMLDivElemen
this._registerAxis('z'); this._registerAxis('z');
document.addEventListener('mousedown', (e: any) => { document.addEventListener('mousedown', (e: any) => {
if (this.getEnabled() && this._mouseover !== null) { if (this.enabled && this._mouseover !== null) {
this._dragging = this._mouseover; this._dragging = this._mouseover;
this._lastClientX = e.clientX; this._lastClientX = e.clientX;
} }
}); });
document.addEventListener('mousemove', (e: any) => { document.addEventListener('mousemove', (e: any) => {
if (this.getEnabled() && this._dragging !== null) { if (this.enabled && this._dragging !== null) {
this._updateValue(e); this._updateValue(e);
} }
}); });
document.addEventListener('mouseup', () => { document.addEventListener('mouseup', () => {
if (this._dragging !== null) {
const elementValue = UIUtil.getElementById(this._getValueId(this._dragging));
elementValue.classList.remove('spinbox-value-hover');
}
this._dragging = null; this._dragging = null;
this._updateStyles();
}); });
} }
private _updateValue(e: MouseEvent) { private _updateValue(e: MouseEvent) {
ASSERT(this.getEnabled(), 'Not enabled'); ASSERT(this.enabled, 'Not enabled');
ASSERT(this._dragging !== null, 'Dragging nothing'); ASSERT(this._dragging !== null, 'Dragging nothing');
const deltaX = e.clientX - this._lastClientX; const deltaX = e.clientX - this._lastClientX;
@ -152,42 +144,13 @@ export class VectorSpinboxElement extends ConfigUIElement<Vector3, HTMLDivElemen
this._setValue(current); this._setValue(current);
} }
protected override _onEnabledChanged() { protected override _updateStyles(): void {
super._onEnabledChanged();
const keyElements = [
UIUtil.getElementById(this._getKeyId('x')),
UIUtil.getElementById(this._getKeyId('y')),
UIUtil.getElementById(this._getKeyId('z')),
];
const valueElements = [
UIUtil.getElementById(this._getValueId('x')),
UIUtil.getElementById(this._getValueId('y')),
UIUtil.getElementById(this._getValueId('z')),
];
if (this.getEnabled()) {
for (const keyElement of keyElements) {
keyElement?.classList.remove('spinbox-key-disabled');
}
for (const valueElement of valueElements) {
valueElement?.classList.remove('spinbox-value-disabled');
}
} else {
for (const keyElement of keyElements) {
keyElement?.classList.add('spinbox-key-disabled');
}
for (const valueElement of valueElements) {
valueElement?.classList.add('spinbox-value-disabled');
}
}
}
protected override _onValueChanged(): void {
const elementXV = UIUtil.getElementById(this._getValueId('x')); const elementXV = UIUtil.getElementById(this._getValueId('x'));
const elementYV = UIUtil.getElementById(this._getValueId('y')); const elementYV = UIUtil.getElementById(this._getValueId('y'));
const elementZV = UIUtil.getElementById(this._getValueId('z')); const elementZV = UIUtil.getElementById(this._getValueId('z'));
// Update text
{
const current = this.getValue().copy(); const current = this.getValue().copy();
elementXV.innerHTML = current.x.toString() + this._units; elementXV.innerHTML = current.x.toString() + this._units;
@ -196,4 +159,35 @@ export class VectorSpinboxElement extends ConfigUIElement<Vector3, HTMLDivElemen
} }
elementZV.innerHTML = current.z.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

@ -1,3 +1,4 @@
// https://tablericons.com/
export namespace AppIcons { export namespace AppIcons {
export const MESH = ` export const MESH = `
@ -61,10 +62,11 @@ export namespace AppIcons {
`; `;
export const BULB = ` export const BULB = `
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-sun" width="44" height="44" viewBox="0 0 24 24" stroke-width="1.5" stroke="#00abfb" fill="none" stroke-linecap="round" stroke-linejoin="round" id="bulb-svg"> <svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-bulb" 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 stroke="none" d="M0 0h24v24H0z" fill="none"/>
<circle cx="12" cy="12" r="4" /> <path d="M3 12h1m8 -9v1m8 8h1m-15.4 -6.4l.7 .7m12.1 -.7l-.7 .7" />
<path d="M3 12h1m8 -9v1m8 8h1m-9 8v1m-6.4 -15.4l.7 .7m12.1 -.7l-.7 .7m0 11.4l.7 .7m-12.1 -.7l-.7 .7" /> <path d="M9 16a5 5 0 1 1 6 0a3.5 3.5 0 0 0 -1 3a2 2 0 0 1 -4 0a3.5 3.5 0 0 0 -1 -3" />
<line x1="9.7" y1="17" x2="14.3" y2="17" />
</svg> </svg>
`; `;
@ -209,4 +211,41 @@ export namespace AppIcons {
</svg> </svg>
`; `;
export const ARROW_DOWN = `
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-chevron-down" 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"/>
<polyline points="6 9 12 15 18 9" />
</svg>
`;
export const COLOUR_SWATCH = `
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-color-swatch" 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="M19 3h-4a2 2 0 0 0 -2 2v12a4 4 0 0 0 8 0v-12a2 2 0 0 0 -2 -2" />
<path d="M13 7.35l-2 -2a2 2 0 0 0 -2.828 0l-2.828 2.828a2 2 0 0 0 0 2.828l9 9" />
<path d="M7.3 13h-2.3a2 2 0 0 0 -2 2v4a2 2 0 0 0 2 2h12" />
<line x1="17" y1="17" x2="17" y2="17.01" />
</svg>
`;
export const IMAGE = `
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-photo" 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"/>
<line x1="15" y1="8" x2="15.01" y2="8" />
<rect x="4" y="4" width="16" height="16" rx="3" />
<path d="M4 15l4 -4a3 5 0 0 1 3 0l5 5" />
<path d="M14 14l1 -1a3 5 0 0 1 3 0l2 2" />
</svg>
`;
export const IMAGE_MISSING = `
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-photo-off" 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"/>
<line x1="3" y1="3" x2="21" y2="21" />
<line x1="15" y1="8" x2="15.01" y2="8" />
<path d="M19.121 19.122a3 3 0 0 1 -2.121 .878h-10a3 3 0 0 1 -3 -3v-10c0 -.833 .34 -1.587 .888 -2.131m3.112 -.869h9a3 3 0 0 1 3 3v9" />
<path d="M4 15l4 -4c.928 -.893 2.072 -.893 3 0l5 5" />
<path d="M16.32 12.34c.577 -.059 1.162 .162 1.68 .66l2 2" />
</svg>
`;
} }

View File

@ -11,6 +11,7 @@ import { ASSERT } from '../util/error_util';
import { LOG } from '../util/log_util'; import { LOG } from '../util/log_util';
import { TAxis } from '../util/type_util'; import { TAxis } from '../util/type_util';
import { TDithering } from '../util/type_util'; import { TDithering } from '../util/type_util';
import { UIUtil } from '../util/ui_util';
import { TVoxelOverlapRule } from '../voxel_mesh'; import { TVoxelOverlapRule } from '../voxel_mesh';
import { TVoxelisers } from '../voxelisers/voxelisers'; import { TVoxelisers } from '../voxelisers/voxelisers';
import { AppConsole } from './console'; import { AppConsole } from './console';
@ -18,14 +19,14 @@ import { ButtonElement } from './elements/button';
import { CheckboxElement } from './elements/checkbox'; import { CheckboxElement } from './elements/checkbox';
import { ComboBoxElement, ComboBoxItem } from './elements/combobox'; import { ComboBoxElement, ComboBoxItem } from './elements/combobox';
import { ConfigUIElement } from './elements/config_element'; import { ConfigUIElement } from './elements/config_element';
import { FileInputElement } from './elements/file_input'; import { ObjFileInputElement } from './elements/file_input';
import { HeaderUIElement } from './elements/header_element'; import { HeaderUIElement } from './elements/header_element';
import { PaletteElement } from './elements/palette_element'; import { PaletteElement } from './elements/palette_element';
import { SliderElement } from './elements/slider'; import { SliderElement } from './elements/slider';
import { ToolbarItemElement } from './elements/toolbar_item'; import { ToolbarItemElement } from './elements/toolbar_item';
import { VectorSpinboxElement } from './elements/vector_spinbox'; import { VectorSpinboxElement } from './elements/vector_spinbox';
import { AppIcons } from './icons'; import { AppIcons } from './icons';
import { HTMLBuilder } from './misc'; import { HTMLBuilder, MiscComponents } from './misc';
export interface Group { export interface Group {
label: string; label: string;
@ -43,10 +44,9 @@ export class UI {
public uiOrder = ['import', 'materials', 'voxelise', 'assign', 'export']; public uiOrder = ['import', 'materials', 'voxelise', 'assign', 'export'];
private _ui = { private _ui = {
'import': { 'import': {
label: 'Import', label: '1. Import',
elements: { elements: {
'input': new FileInputElement() 'input': new ObjFileInputElement()
.setFileExtensions(['obj'])
.setLabel('Wavefront .obj file'), .setLabel('Wavefront .obj file'),
'rotation': new VectorSpinboxElement() 'rotation': new VectorSpinboxElement()
.setLabel('Rotation') .setLabel('Rotation')
@ -61,7 +61,7 @@ export class UI {
.setLabel('Load mesh'), .setLabel('Load mesh'),
}, },
'materials': { 'materials': {
label: 'Materials', label: '2. Materials',
elements: { elements: {
}, },
elementsOrder: [], elementsOrder: [],
@ -72,7 +72,7 @@ export class UI {
.setLabel('Update materials'), .setLabel('Update materials'),
}, },
'voxelise': { 'voxelise': {
label: 'Voxelise', label: '3. Voxelise',
elements: { elements: {
'constraintAxis': new ComboBoxElement<TAxis>() 'constraintAxis': new ComboBoxElement<TAxis>()
.addItem({ payload: 'y', displayText: 'Y (height) (green)' }) .addItem({ payload: 'y', displayText: 'Y (height) (green)' })
@ -144,7 +144,7 @@ export class UI {
.setLabel('Voxelise mesh'), .setLabel('Voxelise mesh'),
}, },
'assign': { 'assign': {
label: 'Assign', label: '4. Assign',
elements: { elements: {
'textureAtlas': new ComboBoxElement<string>() 'textureAtlas': new ComboBoxElement<string>()
.addItems(this._getTextureAtlases()) .addItems(this._getTextureAtlases())
@ -242,7 +242,7 @@ export class UI {
.setLabel('Assign blocks'), .setLabel('Assign blocks'),
}, },
'export': { 'export': {
label: 'Export', label: '5. Export',
elements: { elements: {
'export': new ComboBoxElement<TExporters>() 'export': new ComboBoxElement<TExporters>()
.addItems([ .addItems([
@ -413,6 +413,13 @@ export class UI {
document.body.style.cursor = 'default'; document.body.style.cursor = 'default';
} }
const canvasColumn = UIUtil.getElementById('col-canvas');
if (ArcballCamera.Get.isUserRotating || ArcballCamera.Get.isUserTranslating) {
canvasColumn.style.cursor = 'grabbing';
} else {
canvasColumn.style.cursor = 'grab';
}
for (const groupName in this._toolbarLeftDull) { for (const groupName in this._toolbarLeftDull) {
const toolbarGroup = this._toolbarLeftDull[groupName]; const toolbarGroup = this._toolbarLeftDull[groupName];
for (const toolbarItem of toolbarGroup.elementsOrder) { for (const toolbarItem of toolbarGroup.elementsOrder) {
@ -433,7 +440,7 @@ export class UI {
{ {
const sidebarHTML = new HTMLBuilder(); const sidebarHTML = new HTMLBuilder();
sidebarHTML.add(`<div class="container">`); sidebarHTML.add(`<div class="container-properties">`);
{ {
sidebarHTML.add(HeaderUIElement.Get.generateHTML()); sidebarHTML.add(HeaderUIElement.Get.generateHTML());
@ -485,24 +492,16 @@ export class UI {
Split(['.column-sidebar', '.column-canvas'], { Split(['.column-sidebar', '.column-canvas'], {
sizes: [20, 80], sizes: [20, 80],
minSize: [400, 500], minSize: [600, 500],
gutterSize: 5,
}); });
Split(['.column-properties', '.column-console'], { Split(['.column-properties', '.column-console'], {
sizes: [90, 10], sizes: [85, 15],
minSize: [0, 0], minSize: [0, 0],
direction: 'vertical', direction: 'vertical',
gutterSize: 5,
}); });
const itemA = document.getElementsByClassName('gutter').item(1);
if (itemA !== null) {
itemA.innerHTML = `<div class='gutter-line'></div>`;
}
const itemB = document.getElementsByClassName('gutter').item(0);
if (itemB !== null) {
itemB.innerHTML = `<div class='gutter-line-horizontal'></div>`;
}
} }
public cacheValues(action: EAction) { public cacheValues(action: EAction) {
@ -539,25 +538,11 @@ export class UI {
private _getGroupHTML(group: Group) { private _getGroupHTML(group: Group) {
return ` return `
<div class="property" style="padding-top: 20px;"> ${MiscComponents.createGroupHeader(group.label.toUpperCase())}
<div style="flex-grow: 1"> <div class="component-group" id="subcomponents_${group.label}">
<div class="h-div">
</div>
</div>
<div class="group-heading">
${group.label.toUpperCase()}
</div>
<div style="flex-grow: 1">
<div class="h-div">
</div>
</div>
</div>
<div id="subcomponents_${group.label}">
${this._getGroupSubcomponentsHTML(group)} ${this._getGroupSubcomponentsHTML(group)}
</div> </div>
<div class="property">
${group.submitButton.generateHTML()} ${group.submitButton.generateHTML()}
</div>
`; `;
} }
@ -578,20 +563,25 @@ export class UI {
element.finalise(); element.finalise();
} }
group.submitButton.registerEvents(); group.submitButton.registerEvents();
group.submitButton.finalise();
} }
// Register toolbar left // Register toolbar left
for (const toolbarGroupName of this._toolbarLeft.groupsOrder) { for (const toolbarGroupName of this._toolbarLeft.groupsOrder) {
const toolbarGroup = this._toolbarLeftDull[toolbarGroupName]; const toolbarGroup = this._toolbarLeftDull[toolbarGroupName];
for (const groupElementName of toolbarGroup.elementsOrder) { for (const groupElementName of toolbarGroup.elementsOrder) {
toolbarGroup.elements[groupElementName].registerEvents(); const element = toolbarGroup.elements[groupElementName];
element.registerEvents();
element.finalise();
} }
} }
// Register toolbar right // Register toolbar right
for (const toolbarGroupName of this._toolbarRight.groupsOrder) { for (const toolbarGroupName of this._toolbarRight.groupsOrder) {
const toolbarGroup = this._toolbarRightDull[toolbarGroupName]; const toolbarGroup = this._toolbarRightDull[toolbarGroupName];
for (const groupElementName of toolbarGroup.elementsOrder) { for (const groupElementName of toolbarGroup.elementsOrder) {
toolbarGroup.elements[groupElementName].registerEvents(); const element = toolbarGroup.elements[groupElementName];
element.registerEvents();
element.finalise();
} }
} }
} }

View File

@ -22,3 +22,15 @@ export class HTMLBuilder {
element.innerHTML = this._html; element.innerHTML = this._html;
} }
} }
export namespace MiscComponents {
export function createGroupHeader(label: string) {
return `
<div class="container-group-heading">
<div class="group-heading">
${label}
</div>
</div>
`;
}
}

View File

@ -1,9 +1,44 @@
import { ASSERT } from './error_util'; import { ASSERT } from './error_util';
export type TStyleParams = {
isHovered: boolean,
isEnabled: boolean,
isActive: boolean,
}
export namespace UIUtil { export namespace UIUtil {
export function getElementById(id: string) { export function getElementById(id: string) {
const element = document.getElementById(id); const element = document.getElementById(id);
ASSERT(element !== null, `Attempting to getElement of nonexistent element: ${id}`); ASSERT(element !== null, `Attempting to getElement of nonexistent element: ${id}`);
return element as HTMLElement; return element as HTMLElement;
} }
export function clearStyles(element: HTMLElement) {
element.classList.remove('style-inactive-disabled');
element.classList.remove('style-inactive-enabled');
element.classList.remove('style-inactive-hover');
element.classList.remove('style-active-disabled');
element.classList.remove('style-active-enabled');
element.classList.remove('style-active-hover');
}
export function updateStyles(element: HTMLElement, style: TStyleParams) {
clearStyles(element);
let styleToApply = `style`;
styleToApply += style.isActive ? '-active' : '-inactive';
if (style.isEnabled) {
if (style.isHovered) {
styleToApply += '-hover';
} else {
styleToApply += '-enabled';
}
} else {
styleToApply += '-disabled';
}
element.classList.add(styleToApply);
}
} }

File diff suppressed because it is too large Load Diff

View File

@ -38,7 +38,7 @@
<div class="split column-console" id="console"> <div class="split column-console" id="console">
</div> </div>
</div> </div>
<div class="split column-canvas"> <div class="split column-canvas" id="col-canvas">
<canvas id="canvas"></canvas> <canvas id="canvas"></canvas>
<div class="toolbar" id="toolbar"></div> <div class="toolbar" id="toolbar"></div>
</div> </div>