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

View File

@ -10,17 +10,10 @@ export class AppConfig {
public readonly RELEASE_MODE = true;
public readonly MAJOR_VERSION = 0;
public readonly MINOR_VERSION = 7;
public readonly HOTFIX_VERSION = 12;
public readonly MINOR_VERSION = 8;
public readonly HOTFIX_VERSION = 0;
public readonly VERSION_TYPE: 'd' | 'a' | 'r' = 'r'; // dev, alpha, or release build
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 AMBIENT_OCCLUSION_OVERRIDE_CORNER = true;

View File

@ -1,18 +1,26 @@
import { getRandomID } from '../../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.
* Each `BaseUIElement` can be enabled/disabled.
*/
export abstract class BaseUIElement<T> {
export abstract class BaseUIElement<T> implements IInterfaceItem {
private _id: string;
private _isEnabled: boolean;
private _isHovered: boolean;
private _obeyGroupEnables: boolean;
public constructor() {
this._id = getRandomID();
this._isEnabled = true;
this._isHovered = false;
this._obeyGroupEnables = true;
}
@ -31,6 +39,10 @@ export abstract class BaseUIElement<T> {
return this._isEnabled;
}
public get disabled() {
return !this._isEnabled;
}
/**
* Set whether or not this UI element is interactable.
*/
@ -42,6 +54,14 @@ export abstract class BaseUIElement<T> {
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
* 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 {
this._onEnabledChanged();
this._updateStyles();
}
/**
@ -91,4 +112,10 @@ export abstract class BaseUIElement<T> {
* A delegate that is called when the enabled status is changed.
*/
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() {
return `
<div class="button" id="${this._getId()}">
<div class="button-label">${this._label}</div>
<div class="button-progress" id="${this._getProgressBarId()}"></div>
<div class="container-button">
<div class="struct-prop button" id="${this._getId()}">
<div class="button-label">${this._label}</div>
<div class="button-progress" id="${this._getProgressBarId()}"></div>
</div>
</div>
`;
}
public override registerEvents(): void {
this._getElement().addEventListener('click', () => {
if (this.getEnabled()) {
if (this.enabled) {
this._onClick?.();
}
});
this._getElement().addEventListener('mouseenter', () => {
this._setHovered(true);
this._updateStyles();
});
this._getElement().addEventListener('mouseleave', () => {
this._setHovered(false);
this._updateStyles();
});
}
protected override _onEnabledChanged() {
if (this.getEnabled()) {
this._getElement().classList.remove('button-disabled');
} else {
this._getElement().classList.add('button-disabled');
}
this._updateStyles();
}
public override finalise(): void {
this._updateStyles();
}
/**
@ -100,4 +112,12 @@ export class ButtonElement extends BaseUIElement<HTMLDivElement> {
private _getProgressBarId() {
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> {
private _labelChecked: string;
private _labelUnchecked: string;
private _hovering: boolean;
public constructor() {
super(false);
this._labelChecked = 'On';
this._labelUnchecked = 'Off';
this._hovering = false;
}
public setCheckedText(label: string) {
@ -59,15 +57,14 @@ export class CheckboxElement extends ConfigUIElement<boolean, HTMLSelectElement>
}
private _onMouseEnterLeave(isHovering: boolean) {
this._hovering = isHovering;
this._setHovered(isHovering);
this._updateStyles();
}
public 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">
<div class="struct-prop container-checkbox" id="${this._getId()}">
<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 d="M5 12l5 5l10 -10" />
</svg>
@ -86,7 +83,6 @@ export class CheckboxElement extends ConfigUIElement<boolean, HTMLSelectElement>
protected override _onEnabledChanged(): void {
super._onEnabledChanged();
this._updateStyles();
}
@ -106,36 +102,33 @@ export class CheckboxElement extends ConfigUIElement<boolean, HTMLSelectElement>
this._setValue(false);
}
private _updateStyles() {
const checkboxElement = UIUtil.getElementById(this._getId());
protected _updateStyles() {
{
const checkboxElement = UIUtil.getElementById(this._getId());
UIUtil.updateStyles(checkboxElement, {
isEnabled: this.enabled,
isHovered: this.hovered,
isActive: false,
});
}
const checkboxPipElement = UIUtil.getElementById(this._getPipId());
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-standard');
checkboxTextElement.classList.remove('text-light');
checkboxTextElement.classList.remove('checkbox-text-hover');
checkboxTextElement.innerHTML = this.getValue() ? this._labelChecked : this._labelUnchecked;
checkboxPipElement.style.visibility = this.getValue() ? 'visible' : 'hidden';
if (this.enabled) {
if (this._hovering) {
checkboxElement.classList.add('checkbox-hover');
checkboxPipElement.classList.add('checkbox-pip-hover');
if (this.hovered) {
checkboxTextElement.classList.add('text-light');
checkboxTextElement.classList.add('checkbox-text-hover');
} else if (this.getValue()) {
checkboxTextElement.classList.add('text-standard');
}
} else {
checkboxElement.classList.add('checkbox-disabled');
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';
export type ComboBoxItem<T> = {
@ -9,12 +11,10 @@ export type ComboBoxItem<T> = {
export class ComboBoxElement<T> extends ConfigUIElement<T, HTMLSelectElement> {
private _items: ComboBoxItem<T>[];
private _small: boolean;
public constructor() {
super();
this._items = [];
this._small = false;
}
public addItems(items: ComboBoxItem<T>[]) {
@ -30,62 +30,83 @@ export class ComboBoxElement<T> extends ConfigUIElement<T, HTMLSelectElement> {
return this;
}
public setSmall() {
this._small = true;
return this;
}
public override registerEvents(): void {
this._getElement().addEventListener('mouseenter', () => {
this._setHovered(true);
this._updateStyles();
});
this._getElement().addEventListener('mouseleave', () => {
this._setHovered(false);
this._updateStyles();
});
this._getElement().addEventListener('change', (e: Event) => {
const selectedValue = this._items[this._getElement().selectedIndex].payload;
this._setValue(selectedValue);
});
}
/*
public override 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() {
//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) {
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>');
return `
<select class="${this._small ? 'height-small' : 'height-normal'}" name="${this._getId()}" id="${this._getId()}">
${itemsHTML}
</select>
`;
builder.add(`<div id="${this._getId()}-arrow" class="checkbox-arrow">`);
builder.add(AppIcons.ARROW_DOWN);
builder.add(`</div>`);
builder.add('</div>');
return builder.toString();
}
protected override _onEnabledChanged() {
protected _onValueChanged(): void {
super._onValueChanged();
console.log('combo changed');
}
protected _onEnabledChanged(): void {
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 {
super.finalise();
const selectedIndex = this._items.findIndex((item) => item.payload === this.getValue());
const element = this._getElement();
//ASSERT(selectedIndex !== -1, 'Invalid selected index');
element.selectedIndex = selectedIndex;
this._updateStyles();
}
}

View File

@ -72,10 +72,12 @@ export abstract class ConfigUIElement<T, F> extends BaseUIElement<F> {
public override finalise(): void {
super.finalise();
/*
this._onValueChanged();
this._onValueChangedListeners.forEach((listener) => {
listener(this._value!);
});
*/
}
public override generateHTML() {
@ -99,10 +101,12 @@ export abstract class ConfigUIElement<T, F> extends BaseUIElement<F> {
protected override _onEnabledChanged() {
const label = document.getElementById(this._getLabelId()) as (HTMLDivElement | null);
if (this.getEnabled()) {
label?.classList.remove('text-disabled');
if (this.enabled) {
label?.classList.remove('text-dark');
label?.classList.add('text-standard');
} else {
label?.classList.add('text-disabled');
label?.classList.add('text-dark');
label?.classList.remove('text-standard');
}
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.
*/
protected abstract _onValueChanged(): void;
protected _onValueChanged(): void {
}
protected _getLabelId() {
return this._getId() + '_label';

View File

@ -4,29 +4,17 @@ import { ASSERT } from '../../util/error_util';
import { UIUtil } from '../../util/ui_util';
import { ConfigUIElement } from './config_element';
export class FileInputElement extends ConfigUIElement<Promise<string>, HTMLDivElement> {
private _fileExtensions: string[];
export class ObjFileInputElement extends ConfigUIElement<Promise<string>, HTMLDivElement> {
private _loadedFilePath: string;
private _hovering: boolean;
public constructor() {
super(Promise.resolve(''));
this._fileExtensions = [];
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() {
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">
${this._loadedFilePath}
</div>
@ -35,13 +23,13 @@ export class FileInputElement extends ConfigUIElement<Promise<string>, HTMLDivEl
public override registerEvents(): void {
this._getElement().addEventListener('mouseenter', () => {
this._hovering = true;
this._updateStyle();
this._setHovered(true);
this._updateStyles();
});
this._getElement().addEventListener('mouseleave', () => {
this._hovering = false;
this._updateStyle();
this._setHovered(false);
this._updateStyles();
});
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', () => {
if (!this.getEnabled()) {
return;
if (this.enabled) {
inputElement.click();
}
inputElement.click();
});
this._getElement().addEventListener('mousemove', () => {
this._updateStyle();
});
}
protected override _onEnabledChanged() {
super._onEnabledChanged();
if (this.getEnabled()) {
this._getElement().classList.remove('input-file-disabled');
} else {
this._getElement().classList.add('input-file-disabled');
}
protected _onValueChanged(): void {
this._updateStyles();
}
protected override _onValueChanged(): void {
protected override _updateStyles() {
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');
}
UIUtil.updateStyles(this._getElement(), {
isHovered: this.hovered,
isEnabled: this.enabled,
isActive: false,
});
}
}

View File

@ -7,7 +7,7 @@ import { ConfigUIElement } from './config_element';
export abstract class FullConfigUIElement<T, F> extends ConfigUIElement<T, F> {
public override generateHTML() {
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()}">
${this._label}
</div>

View File

@ -43,38 +43,27 @@ export class HeaderUIElement extends BaseUIElement<HTMLDivElement> {
public override generateHTML(): string {
return `
<div class="property">
<div class="col-container header-cols">
<div class="col-container">
<div class="col-item">
<img class="logo" alt="Logo" src="${IMAGE_LOGO}">
</div>
<div class="col-item">
<div class="row-container">
<div class="row-item title">
ObjToSchematic
</div>
<div class="row-item subtitle">
v${AppConfig.Get.MAJOR_VERSION}.${AppConfig.Get.MINOR_VERSION}.${AppConfig.Get.HOTFIX_VERSION}${AppConfig.Get.VERSION_TYPE} Minecraft ${AppConfig.Get.MINECRAFT_VERSION}
</div>
<div class="col-container header-cols">
<div class="col-container">
<div class="col-item">
<img class="logo" alt="Logo" src="${IMAGE_LOGO}">
</div>
<div class="col-item">
<div class="row-container">
<div class="row-item title">
ObjToSchematic
</div>
<div class="row-item subtitle">
v${AppConfig.Get.MAJOR_VERSION}.${AppConfig.Get.MINOR_VERSION}.${AppConfig.Get.HOTFIX_VERSION}${AppConfig.Get.VERSION_TYPE} Minecraft ${AppConfig.Get.MINECRAFT_VERSION}
</div>
</div>
</div>
<div class="col-container">
<div class="col-item">
${this._githubButton.generateHTML()}
</div>
<div class="col-item">
${this._bugButton.generateHTML()}
</div>
<div class="col-item">
${this._discordButton.generateHTML()}
</div>
</div>
</div>
</div>
<div class="property changelog">
${AppConfig.Get.CHANGELOG.join('<br>')}
<div class="toolbar-group">
${this._githubButton.generateHTML()}
${this._bugButton.generateHTML()}
${this._discordButton.generateHTML()}
</div>
</div>
`;
}
@ -86,6 +75,9 @@ export class HeaderUIElement extends BaseUIElement<HTMLDivElement> {
}
public override finalise(): void {
this._githubButton.finalise();
this._bugButton.finalise();
this._discordButton.finalise();
// const updateElement = UIUtil.getElementById('update-checker') as HTMLDivElement;
// updateElement.style.animation = 'pulse-opacity 1.5s infinite';
// 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' }));
this._switchElement = new ToolbarItemElement({ id: 'sw', iconSVG: AppIcons.UPLOAD })
.setSmall()
.setLabel('Choose')
.onClick(() => {
const inputElement = UIUtil.getElementById(this._getId() + '-input') as HTMLInputElement;
@ -30,15 +29,17 @@ export class ImageElement extends ConfigUIElement<Promise<TImageRawWrap>, HTMLIm
<div class="row-container">
<div class="row-item">
<img id="${this._imageId}" alt="Texture Preview" class="texture-preview" loading="lazy"></img>
</div>
<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">
${this._switchElement.generateHTML()}
<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 class="row-item">
<input type="file" accept="images/png" style="display: none;" id="${this._getId()}-input">
${this._switchElement.generateHTML()}
</div>
</div>
`;
}
@ -70,10 +71,15 @@ export class ImageElement extends ConfigUIElement<Promise<TImageRawWrap>, HTMLIm
}
protected override _onEnabledChanged(): void {
super._onEnabledChanged();
this._switchElement.setEnabled(this.enabled);
}
protected override _onValueChanged(): void {
const inputElement = UIUtil.getElementById(this._imageId) as HTMLImageElement;
const placeholderElement = UIUtil.getElementById(this._imageId + '-placeholder');
this.getValue()
.then((res) => {
if (res.raw === '') {
@ -82,11 +88,13 @@ export class ImageElement extends ConfigUIElement<Promise<TImageRawWrap>, HTMLIm
this._switchElement.setActive(false);
inputElement.src = res.raw;
inputElement.style.display = 'unset';
placeholderElement.style.display = 'none';
})
.catch((err) => {
this._switchElement.setActive(true);
inputElement.src = '';
inputElement.style.display = 'none';
placeholderElement.style.display = 'flex';
});
}
@ -94,5 +102,6 @@ export class ImageElement extends ConfigUIElement<Promise<TImageRawWrap>, HTMLIm
super.finalise();
this._onValueChanged();
this._onEnabledChanged();
}
}

View File

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

View File

@ -8,10 +8,11 @@ import { AppConsole } from '../console';
import { AppIcons } from '../icons';
import { ButtonElement } from './button';
import { CheckboxElement } from './checkbox';
import { ConfigUIElement } from './config_element';
import { FullConfigUIElement } from './full_config_element';
import { ToolbarItemElement } from './toolbar_item';
export class PaletteElement extends FullConfigUIElement<Palette, HTMLDivElement> {
export class PaletteElement extends ConfigUIElement<Palette, HTMLDivElement> {
private _checkboxes: { block: string, element: CheckboxElement }[];
private _palette: Palette;
private _selectAll: ToolbarItemElement;
@ -133,15 +134,9 @@ export class PaletteElement extends FullConfigUIElement<Palette, HTMLDivElement>
checkboxesHTML += '</div>';
});
/*
<select>
<option value="All">All</option>
</select>
*/
return `
<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">
<div class="col-item">
@ -186,6 +181,7 @@ export class PaletteElement extends FullConfigUIElement<Palette, HTMLDivElement>
this._exportTo.setEnabled(this.enabled);
this._onCountSelectedChanged();
this._updateStyles();
}
public override registerEvents(): void {
@ -193,6 +189,14 @@ export class PaletteElement extends FullConfigUIElement<Palette, HTMLDivElement>
searchElement.addEventListener('keyup', () => {
this._onSearchBoxChanged(searchElement.value);
});
searchElement.addEventListener('mouseenter', () => {
this._setHovered(true);
this._updateStyles();
});
searchElement.addEventListener('mouseleave', () => {
this._setHovered(false);
this._updateStyles();
});
this._checkboxes.forEach((checkbox) => {
checkbox.element.registerEvents();
@ -218,7 +222,14 @@ export class PaletteElement extends FullConfigUIElement<Palette, HTMLDivElement>
checkbox.element.finalise();
});
this._selectAll.finalise();
this._deselectAll.finalise();
this._importFrom.finalise();
this._exportTo.finalise();
this._onCountSelectedChanged();
this._updateStyles();
//this._selectAll.finalise();
//this._deselectAll.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 _step: number;
private _dragging: boolean;
private _hovering: boolean;
private _internalValue: number;
private _small: boolean;
private _valueHovered: boolean;
public constructor() {
super();
@ -29,8 +28,7 @@ export class SliderElement extends ConfigUIElement<number, HTMLDivElement> {
this._step = 0.1;
this._internalValue = 0.5;
this._dragging = false;
this._hovering = false;
this._small = false;
this._valueHovered = false;
}
public override setDefaultValue(value: number) {
@ -39,11 +37,6 @@ export class SliderElement extends ConfigUIElement<number, HTMLDivElement> {
return this;
}
public setSmall() {
this._small = true;
return this;
}
/**
* 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;
element.onmouseenter = () => {
this._hovering = true;
if (this.getEnabled()) {
element.classList.add('new-slider-hover');
elementBar.classList.add('new-slider-bar-hover');
}
this._setHovered(true);
this._updateStyles();
};
element.onmouseleave = () => {
this._hovering = false;
if (!this._dragging) {
element.classList.remove('new-slider-hover');
elementBar.classList.remove('new-slider-bar-hover');
}
this._setHovered(false);
this._updateStyles();
};
element.onmousedown = () => {
@ -111,10 +98,6 @@ export class SliderElement extends ConfigUIElement<number, HTMLDivElement> {
if (this._dragging) {
this._onDragSlider(e);
}
if (!this._hovering) {
element.classList.remove('new-slider-hover');
elementBar.classList.remove('new-slider-bar-hover');
}
this._dragging = false;
});
@ -128,15 +111,25 @@ export class SliderElement extends ConfigUIElement<number, HTMLDivElement> {
elementValue.addEventListener('change', () => {
this._onTypedValue();
});
elementValue.addEventListener('mouseenter', () => {
this._valueHovered = true;
this._updateStyles();
});
elementValue.addEventListener('mouseleave', () => {
this._valueHovered = false;
this._updateStyles();
});
}
public override _generateInnerHTML() {
const norm = (this._internalValue - this._min) / (this._max - this._min);
return `
<input class="${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)}">
<div class="new-slider ${this._small ? 'slider-bar-height-small' : 'slider-bar-height-normal'} " id="${this._getId()}" style="flex-grow: 1;">
<div class="new-slider-bar" id="${this._getSliderBarId()}" style="width: ${norm * 100}%;">
<input class="struct-prop comp-slider-value" type="number" id="${this._getSliderValueId()}" min="${this._min}" max="${this._max}" step="${this._step}" value="${this.getValue().toFixed(this._decimals)}">
<div class="struct-prop comp-slider comp-slider-outer" id="${this._getId()}">
<div class="struct-prop comp-slider comp-slider-inner" id="${this._getSliderBarId()}" style="width: ${norm * 100}%">
</div>
</div>
`;
@ -145,19 +138,20 @@ export class SliderElement extends ConfigUIElement<number, HTMLDivElement> {
protected override _onEnabledChanged() {
super._onEnabledChanged();
const element = this._getElement();
const elementBar = UIUtil.getElementById(this._getSliderBarId());
const elementValue = UIUtil.getElementById(this._getSliderValueId()) as HTMLInputElement;
elementValue.disabled = this.disabled;
if (this.getEnabled()) {
element.classList.remove('new-slider-disabled');
elementBar.classList.remove('new-slider-bar-disabled');
elementValue.disabled = false;
const elementBar = UIUtil.getElementById(this._getSliderBarId());
const elementSlider = UIUtil.getElementById(this._getId());
if (this.enabled) {
elementBar.classList.add('enabled');
elementSlider.classList.add('enabled');
} else {
element.classList.add('new-slider-disabled');
elementBar.classList.add('new-slider-bar-disabled');
elementValue.disabled = true;
elementBar.classList.remove('enabled');
elementSlider.classList.remove('enabled');
}
this._updateStyles();
}
protected override _onValueChanged(): void {
@ -222,4 +216,27 @@ export class SliderElement extends ConfigUIElement<number, HTMLDivElement> {
private _getSliderBarId() {
return this._getId() + '-bar';
}
protected override _updateStyles(): void {
const elementValue = UIUtil.getElementById(this._getSliderValueId()) as HTMLInputElement;
UIUtil.updateStyles(elementValue, {
isHovered: this._valueHovered,
isActive: false,
isEnabled: this.enabled,
});
const elementBar = UIUtil.getElementById(this._getSliderBarId()) as HTMLInputElement;
UIUtil.updateStyles(elementBar, {
isHovered: this.hovered,
isActive: true,
isEnabled: this.enabled,
});
const elementSlider = UIUtil.getElementById(this._getId()) as HTMLInputElement;
UIUtil.updateStyles(elementSlider, {
isHovered: this.hovered,
isActive: false,
isEnabled: this.enabled,
});
}
}

View File

@ -1,80 +1,64 @@
import { RGBAUtil } from '../../colour';
import { MaterialType, SolidMaterial } from '../../mesh';
import { getRandomID } from '../../util';
import { UIUtil } from '../../util/ui_util';
import { SolidMaterial } from '../../mesh';
import { ColourElement } from './colour_element';
import { ConfigUIElement } from './config_element';
import { MaterialTypeElement } from './material_type_element';
import { SliderElement } from './slider';
export class SolidMaterialElement extends ConfigUIElement<SolidMaterial, HTMLDivElement> {
private _materialName: string;
private _colourId: string;
private _typeElement: MaterialTypeElement;
private _colourElement: ColourElement;
private _alphaElement: SliderElement;
public constructor(materialName: string, material: SolidMaterial) {
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()
.setLabel('Alpha')
.setMin(0.0)
.setMax(1.0)
.setDefaultValue(material.colour.a)
.setDecimals(2)
.setStep(0.01)
.setSmall();
.setStep(0.01);
}
public override registerEvents(): void {
this._typeElement.registerEvents();
this._colourElement.registerEvents();
this._alphaElement.registerEvents();
this._typeElement.onClickChangeTypeDelegate(() => {
this._onChangeTypeDelegate?.();
});
this._alphaElement.addValueChangedListener((newAlpha) => {
this.getValue().colour.a = newAlpha;
this._colourElement.addValueChangedListener((newColour) => {
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;
swatchElement.addEventListener('change', () => {
const material = this.getValue();
material.colour = RGBAUtil.fromHexString(swatchElement.value);
this._alphaElement.addValueChangedListener((newAlpha) => {
this.getValue().colour.a = newAlpha;
});
}
public override finalise(): void {
this._typeElement.finalise();
this._colourElement.finalise();
this._alphaElement.finalise();
}
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 `
<div class="subproperty-container">
${subproperties.join('')}
<div class="component-group">
${this._typeElement.generateHTML()}
${this._colourElement.generateHTML()}
${this._alphaElement.generateHTML()}
</div>
`;
}
@ -84,6 +68,10 @@ export class SolidMaterialElement extends ConfigUIElement<SolidMaterial, HTMLDiv
protected override _onEnabledChanged(): void {
super._onEnabledChanged();
this._typeElement.setEnabled(this.enabled);
this._colourElement.setEnabled(this.enabled);
this._alphaElement.setEnabled(this.enabled);
}
private _onChangeTypeDelegate?: () => void;

View File

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

View File

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

View File

@ -46,7 +46,7 @@ export class VectorSpinboxElement extends ConfigUIElement<Vector3, HTMLDivElemen
html += `
<div class="spinbox-element-container">
<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}
</div>
</div>
@ -55,7 +55,7 @@ export class VectorSpinboxElement extends ConfigUIElement<Vector3, HTMLDivElemen
html += `
<div class="spinbox-element-container">
<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}
</div>
</div>
@ -64,7 +64,7 @@ export class VectorSpinboxElement extends ConfigUIElement<Vector3, HTMLDivElemen
html += `
<div class="spinbox-element-container">
<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}
</div>
</div>
@ -86,16 +86,12 @@ export class VectorSpinboxElement extends ConfigUIElement<Vector3, HTMLDivElemen
elementValue.onmouseenter = () => {
this._mouseover = axis;
if (this.getEnabled()) {
elementValue.classList.add('spinbox-value-hover');
}
this._updateStyles();
};
elementValue.onmouseleave = () => {
this._mouseover = null;
if (this._dragging !== axis) {
elementValue.classList.remove('spinbox-value-hover');
}
this._updateStyles();
};
}
@ -107,30 +103,26 @@ export class VectorSpinboxElement extends ConfigUIElement<Vector3, HTMLDivElemen
this._registerAxis('z');
document.addEventListener('mousedown', (e: any) => {
if (this.getEnabled() && this._mouseover !== null) {
if (this.enabled && this._mouseover !== null) {
this._dragging = this._mouseover;
this._lastClientX = e.clientX;
}
});
document.addEventListener('mousemove', (e: any) => {
if (this.getEnabled() && this._dragging !== null) {
if (this.enabled && this._dragging !== null) {
this._updateValue(e);
}
});
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._updateStyles();
});
}
private _updateValue(e: MouseEvent) {
ASSERT(this.getEnabled(), 'Not enabled');
ASSERT(this.enabled, 'Not enabled');
ASSERT(this._dragging !== null, 'Dragging nothing');
const deltaX = e.clientX - this._lastClientX;
@ -152,48 +144,50 @@ export class VectorSpinboxElement extends ConfigUIElement<Vector3, HTMLDivElemen
this._setValue(current);
}
protected override _onEnabledChanged() {
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 {
protected override _updateStyles(): void {
const elementXV = UIUtil.getElementById(this._getValueId('x'));
const elementYV = UIUtil.getElementById(this._getValueId('y'));
const elementZV = UIUtil.getElementById(this._getValueId('z'));
const current = this.getValue().copy();
// Update text
{
const current = this.getValue().copy();
elementXV.innerHTML = current.x.toString() + this._units;
if (elementYV) {
elementYV.innerHTML = current.y.toString() + this._units;
elementXV.innerHTML = current.x.toString() + this._units;
if (elementYV) {
elementYV.innerHTML = current.y.toString() + this._units;
}
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 const MESH = `
@ -61,10 +62,11 @@ export namespace AppIcons {
`;
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"/>
<circle cx="12" cy="12" r="4" />
<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="M3 12h1m8 -9v1m8 8h1m-15.4 -6.4l.7 .7m12.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>
`;
@ -209,4 +211,41 @@ export namespace AppIcons {
</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 { TAxis } from '../util/type_util';
import { TDithering } from '../util/type_util';
import { UIUtil } from '../util/ui_util';
import { TVoxelOverlapRule } from '../voxel_mesh';
import { TVoxelisers } from '../voxelisers/voxelisers';
import { AppConsole } from './console';
@ -18,14 +19,14 @@ 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 { ObjFileInputElement } from './elements/file_input';
import { HeaderUIElement } from './elements/header_element';
import { PaletteElement } from './elements/palette_element';
import { SliderElement } from './elements/slider';
import { ToolbarItemElement } from './elements/toolbar_item';
import { VectorSpinboxElement } from './elements/vector_spinbox';
import { AppIcons } from './icons';
import { HTMLBuilder } from './misc';
import { HTMLBuilder, MiscComponents } from './misc';
export interface Group {
label: string;
@ -43,10 +44,9 @@ export class UI {
public uiOrder = ['import', 'materials', 'voxelise', 'assign', 'export'];
private _ui = {
'import': {
label: 'Import',
label: '1. Import',
elements: {
'input': new FileInputElement()
.setFileExtensions(['obj'])
'input': new ObjFileInputElement()
.setLabel('Wavefront .obj file'),
'rotation': new VectorSpinboxElement()
.setLabel('Rotation')
@ -61,7 +61,7 @@ export class UI {
.setLabel('Load mesh'),
},
'materials': {
label: 'Materials',
label: '2. Materials',
elements: {
},
elementsOrder: [],
@ -72,7 +72,7 @@ export class UI {
.setLabel('Update materials'),
},
'voxelise': {
label: 'Voxelise',
label: '3. Voxelise',
elements: {
'constraintAxis': new ComboBoxElement<TAxis>()
.addItem({ payload: 'y', displayText: 'Y (height) (green)' })
@ -144,7 +144,7 @@ export class UI {
.setLabel('Voxelise mesh'),
},
'assign': {
label: 'Assign',
label: '4. Assign',
elements: {
'textureAtlas': new ComboBoxElement<string>()
.addItems(this._getTextureAtlases())
@ -242,7 +242,7 @@ export class UI {
.setLabel('Assign blocks'),
},
'export': {
label: 'Export',
label: '5. Export',
elements: {
'export': new ComboBoxElement<TExporters>()
.addItems([
@ -413,6 +413,13 @@ export class UI {
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) {
const toolbarGroup = this._toolbarLeftDull[groupName];
for (const toolbarItem of toolbarGroup.elementsOrder) {
@ -433,7 +440,7 @@ export class UI {
{
const sidebarHTML = new HTMLBuilder();
sidebarHTML.add(`<div class="container">`);
sidebarHTML.add(`<div class="container-properties">`);
{
sidebarHTML.add(HeaderUIElement.Get.generateHTML());
@ -485,24 +492,16 @@ export class UI {
Split(['.column-sidebar', '.column-canvas'], {
sizes: [20, 80],
minSize: [400, 500],
minSize: [600, 500],
gutterSize: 5,
});
Split(['.column-properties', '.column-console'], {
sizes: [90, 10],
sizes: [85, 15],
minSize: [0, 0],
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) {
@ -539,25 +538,11 @@ export class UI {
private _getGroupHTML(group: Group) {
return `
<div class="property" style="padding-top: 20px;">
<div style="flex-grow: 1">
<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}">
${MiscComponents.createGroupHeader(group.label.toUpperCase())}
<div class="component-group" id="subcomponents_${group.label}">
${this._getGroupSubcomponentsHTML(group)}
</div>
<div class="property">
${group.submitButton.generateHTML()}
</div>
${group.submitButton.generateHTML()}
`;
}
@ -578,20 +563,25 @@ export class UI {
element.finalise();
}
group.submitButton.registerEvents();
group.submitButton.finalise();
}
// Register toolbar left
for (const toolbarGroupName of this._toolbarLeft.groupsOrder) {
const toolbarGroup = this._toolbarLeftDull[toolbarGroupName];
for (const groupElementName of toolbarGroup.elementsOrder) {
toolbarGroup.elements[groupElementName].registerEvents();
const element = toolbarGroup.elements[groupElementName];
element.registerEvents();
element.finalise();
}
}
// Register toolbar right
for (const toolbarGroupName of this._toolbarRight.groupsOrder) {
const toolbarGroup = this._toolbarRightDull[toolbarGroupName];
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;
}
}
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';
export type TStyleParams = {
isHovered: boolean,
isEnabled: boolean,
isActive: boolean,
}
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;
}
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>
</div>
<div class="split column-canvas">
<div class="split column-canvas" id="col-canvas">
<canvas id="canvas"></canvas>
<div class="toolbar" id="toolbar"></div>
</div>