From 4e303de0c8a8b3503f06decc35ae94fe23a76dd8 Mon Sep 17 00:00:00 2001 From: Lucas Dower Date: Mon, 20 Mar 2023 18:02:18 +0000 Subject: [PATCH 1/3] Major UI and CSS refactor --- src/config.ts | 7 - src/ui/elements/base_element.ts | 21 + src/ui/elements/button.ts | 38 +- src/ui/elements/checkbox.ts | 33 +- src/ui/elements/combobox.ts | 66 ++- src/ui/elements/config_element.ts | 9 +- src/ui/elements/file_input.ts | 65 +-- src/ui/elements/full_config_element.ts | 2 +- src/ui/elements/header_element.ts | 22 +- src/ui/elements/palette_element.ts | 29 +- src/ui/elements/slider.ts | 64 ++- src/ui/elements/toolbar_item.ts | 113 ++--- src/ui/elements/vector_spinbox.ts | 102 ++-- src/ui/icons.ts | 6 + src/ui/layout.ts | 52 +- src/ui/misc.ts | 12 + src/util/ui_util.ts | 35 ++ styles.css | 641 +++++++------------------ 18 files changed, 544 insertions(+), 773 deletions(-) diff --git a/src/config.ts b/src/config.ts index 94368ac..2e826a7 100644 --- a/src/config.ts +++ b/src/config.ts @@ -14,13 +14,6 @@ export class AppConfig { public readonly HOTFIX_VERSION = 12; 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; diff --git a/src/ui/elements/base_element.ts b/src/ui/elements/base_element.ts index ffccd1d..02b0d4b 100644 --- a/src/ui/elements/base_element.ts +++ b/src/ui/elements/base_element.ts @@ -8,11 +8,13 @@ import { UIUtil } from '../../util/ui_util'; export abstract class BaseUIElement { 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 +33,10 @@ export abstract class BaseUIElement { return this._isEnabled; } + public get disabled() { + return !this._isEnabled; + } + /** * Set whether or not this UI element is interactable. */ @@ -42,6 +48,14 @@ export abstract class BaseUIElement { 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 +83,7 @@ export abstract class BaseUIElement { public finalise(): void { this._onEnabledChanged(); + this._updateStyles(); } /** @@ -91,4 +106,10 @@ export abstract class BaseUIElement { * A delegate that is called when the enabled status is changed. */ protected abstract _onEnabledChanged(): void; + + /** + * Called after _onEnabledChanged() and _onValueChanged() + */ + protected _updateStyles(): void { + } } diff --git a/src/ui/elements/button.ts b/src/ui/elements/button.ts index ea6c4f5..c967eac 100644 --- a/src/ui/elements/button.ts +++ b/src/ui/elements/button.ts @@ -71,27 +71,39 @@ export class ButtonElement extends BaseUIElement { public override generateHTML() { return ` -
-
${this._label}
-
+
+
+
${this._label}
+
+
`; } 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 { private _getProgressBarId() { return this._getId() + '-progress'; } + + protected _updateStyles(): void { + UIUtil.updateStyles(this._getElement(), { + isActive: true, + isEnabled: this.enabled, + isHovered: this.hovered, + }); + } } diff --git a/src/ui/elements/checkbox.ts b/src/ui/elements/checkbox.ts index 665ae28..70585c0 100644 --- a/src/ui/elements/checkbox.ts +++ b/src/ui/elements/checkbox.ts @@ -4,13 +4,11 @@ import { ConfigUIElement } from './config_element'; export class CheckboxElement extends ConfigUIElement { 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 } private _onMouseEnterLeave(isHovering: boolean) { - this._hovering = isHovering; - + this._setHovered(isHovering); this._updateStyles(); } public override _generateInnerHTML(): string { return ` -
- +
+ @@ -86,7 +83,6 @@ export class CheckboxElement extends ConfigUIElement protected override _onEnabledChanged(): void { super._onEnabledChanged(); - this._updateStyles(); } @@ -106,36 +102,33 @@ export class CheckboxElement extends ConfigUIElement 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'); } } } diff --git a/src/ui/elements/combobox.ts b/src/ui/elements/combobox.ts index 09e3c96..3fd3019 100644 --- a/src/ui/elements/combobox.ts +++ b/src/ui/elements/combobox.ts @@ -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 = { @@ -36,6 +38,16 @@ export class ComboBoxElement extends ConfigUIElement { } 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); @@ -57,35 +69,59 @@ export class ComboBoxElement extends ConfigUIElement { */ public override _generateInnerHTML() { - //ASSERT(this._items.length > 0); + const builder = new HTMLBuilder(); - let itemsHTML = ''; + builder.add('
'); + builder.add(` - ${itemsHTML} - - `; + builder.add(''); + + builder.add(`
`); + builder.add(AppIcons.ARROW_DOWN); + builder.add(`
`); + builder.add('
'); + + return builder.toString(); } - protected override _onEnabledChanged() { + 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(); } } diff --git a/src/ui/elements/config_element.ts b/src/ui/elements/config_element.ts index fc2e38d..591f024 100644 --- a/src/ui/elements/config_element.ts +++ b/src/ui/elements/config_element.ts @@ -99,10 +99,10 @@ export abstract class ConfigUIElement extends BaseUIElement { 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-standard'); } else { - label?.classList.add('text-disabled'); + label?.classList.add('text-dark'); } this._onEnabledChangedListeners.forEach((listener) => { @@ -125,7 +125,8 @@ export abstract class ConfigUIElement extends BaseUIElement { /** * 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'; diff --git a/src/ui/elements/file_input.ts b/src/ui/elements/file_input.ts index 2f3b2d8..60fed56 100644 --- a/src/ui/elements/file_input.ts +++ b/src/ui/elements/file_input.ts @@ -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, HTMLDivElement> { - private _fileExtensions: string[]; +export class ObjFileInputElement extends ConfigUIElement, 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 ` -
+
${this._loadedFilePath}
@@ -35,13 +23,13 @@ export class FileInputElement extends ConfigUIElement, 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, 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, + }); } } diff --git a/src/ui/elements/full_config_element.ts b/src/ui/elements/full_config_element.ts index 0396898..f9bc870 100644 --- a/src/ui/elements/full_config_element.ts +++ b/src/ui/elements/full_config_element.ts @@ -7,7 +7,7 @@ import { ConfigUIElement } from './config_element'; export abstract class FullConfigUIElement extends ConfigUIElement { public override generateHTML() { return ` -
+
${this._label}
diff --git a/src/ui/elements/header_element.ts b/src/ui/elements/header_element.ts index cb9a2d1..3b89a4a 100644 --- a/src/ui/elements/header_element.ts +++ b/src/ui/elements/header_element.ts @@ -43,7 +43,7 @@ export class HeaderUIElement extends BaseUIElement { public override generateHTML(): string { return ` -
+
@@ -60,22 +60,13 @@ export class HeaderUIElement extends BaseUIElement {
-
-
- ${this._githubButton.generateHTML()} -
-
- ${this._bugButton.generateHTML()} -
-
- ${this._discordButton.generateHTML()} -
+
+ ${this._githubButton.generateHTML()} + ${this._bugButton.generateHTML()} + ${this._discordButton.generateHTML()}
-
- ${AppConfig.Get.CHANGELOG.join('
')} -
`; } @@ -86,6 +77,9 @@ export class HeaderUIElement extends BaseUIElement { } 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 = 'Checking for updates...'; diff --git a/src/ui/elements/palette_element.ts b/src/ui/elements/palette_element.ts index 9e79310..00c4227 100644 --- a/src/ui/elements/palette_element.ts +++ b/src/ui/elements/palette_element.ts @@ -133,15 +133,9 @@ export class PaletteElement extends FullConfigUIElement checkboxesHTML += '
'; }); - /* - - */ - return `
- +
@@ -193,6 +187,14 @@ export class PaletteElement extends FullConfigUIElement 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,6 +220,11 @@ export class PaletteElement extends FullConfigUIElement checkbox.element.finalise(); }); + this._selectAll.finalise(); + this._deselectAll.finalise(); + this._importFrom.finalise(); + this._exportTo.finalise(); + this._onCountSelectedChanged(); //this._selectAll.finalise(); //this._deselectAll.finalise(); @@ -235,4 +242,12 @@ export class PaletteElement extends FullConfigUIElement } }); } + + protected override _updateStyles(): void { + UIUtil.updateStyles(UIUtil.getElementById(this._getId() + '-search'), { + isActive: false, + isEnabled: this.enabled, + isHovered: this.hovered, + }); + } } diff --git a/src/ui/elements/slider.ts b/src/ui/elements/slider.ts index 5e2230c..c719ab2 100644 --- a/src/ui/elements/slider.ts +++ b/src/ui/elements/slider.ts @@ -17,7 +17,6 @@ export class SliderElement extends ConfigUIElement { private _decimals: number; private _step: number; private _dragging: boolean; - private _hovering: boolean; private _internalValue: number; private _small: boolean; @@ -29,7 +28,6 @@ export class SliderElement extends ConfigUIElement { this._step = 0.1; this._internalValue = 0.5; this._dragging = false; - this._hovering = false; this._small = false; } @@ -82,19 +80,13 @@ export class SliderElement extends ConfigUIElement { 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 +103,6 @@ export class SliderElement extends ConfigUIElement { 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; }); @@ -134,9 +122,9 @@ export class SliderElement extends ConfigUIElement { const norm = (this._internalValue - this._min) / (this._max - this._min); return ` - -
-
+ +
+
`; @@ -144,20 +132,7 @@ export class SliderElement extends ConfigUIElement { protected override _onEnabledChanged() { super._onEnabledChanged(); - - const element = this._getElement(); - const elementBar = UIUtil.getElementById(this._getSliderBarId()); - const elementValue = UIUtil.getElementById(this._getSliderValueId()) as HTMLInputElement; - - if (this.getEnabled()) { - element.classList.remove('new-slider-disabled'); - elementBar.classList.remove('new-slider-bar-disabled'); - elementValue.disabled = false; - } else { - element.classList.add('new-slider-disabled'); - elementBar.classList.add('new-slider-bar-disabled'); - elementValue.disabled = true; - } + this._updateStyles(); } protected override _onValueChanged(): void { @@ -222,4 +197,27 @@ export class SliderElement extends ConfigUIElement { private _getSliderBarId() { return this._getId() + '-bar'; } + + protected override _updateStyles(): void { + const elementValue = UIUtil.getElementById(this._getSliderValueId()) as HTMLInputElement; + UIUtil.updateStyles(elementValue, { + isHovered: false, + 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, + }); + } } diff --git a/src/ui/elements/toolbar_item.ts b/src/ui/elements/toolbar_item.ts index af78435..c460971 100644 --- a/src/ui/elements/toolbar_item.ts +++ b/src/ui/elements/toolbar_item.ts @@ -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,17 @@ export type TToolbarItemParams = { iconSVG: string; } -export class ToolbarItemElement { - private _id: string; +export class ToolbarItemElement extends BaseUIElement { 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,17 +31,19 @@ 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 setActive(isActive: boolean) { + this._isActive = isActive; + } + + public setSmall() { this._small = true; return this; @@ -55,19 +57,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 +96,47 @@ export class ToolbarItemElement { public generateHTML() { return ` -
+
${this._iconSVG.outerHTML} ${this._label}
`; } 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, + }); } } diff --git a/src/ui/elements/vector_spinbox.ts b/src/ui/elements/vector_spinbox.ts index 3f42ca2..9bd42ff 100644 --- a/src/ui/elements/vector_spinbox.ts +++ b/src/ui/elements/vector_spinbox.ts @@ -46,7 +46,7 @@ export class VectorSpinboxElement extends ConfigUIElement
X
-
+
${this.getValue().x}
@@ -55,7 +55,7 @@ export class VectorSpinboxElement extends ConfigUIElement
Y
-
+
${this.getValue().y}
@@ -64,7 +64,7 @@ export class VectorSpinboxElement extends ConfigUIElement
Z
-
+
${this.getValue().z}
@@ -86,16 +86,12 @@ export class VectorSpinboxElement extends ConfigUIElement { 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 { - 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 `; + export const ARROW_DOWN = ` + + + + + `; } diff --git a/src/ui/layout.ts b/src/ui/layout.ts index a244bd1..311a57d 100644 --- a/src/ui/layout.ts +++ b/src/ui/layout.ts @@ -18,14 +18,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 +43,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 +60,7 @@ export class UI { .setLabel('Load mesh'), }, 'materials': { - label: 'Materials', + label: '2. Materials', elements: { }, elementsOrder: [], @@ -72,7 +71,7 @@ export class UI { .setLabel('Update materials'), }, 'voxelise': { - label: 'Voxelise', + label: '3. Voxelise', elements: { 'constraintAxis': new ComboBoxElement() .addItem({ payload: 'y', displayText: 'Y (height) (green)' }) @@ -144,7 +143,7 @@ export class UI { .setLabel('Voxelise mesh'), }, 'assign': { - label: 'Assign', + label: '4. Assign', elements: { 'textureAtlas': new ComboBoxElement() .addItems(this._getTextureAtlases()) @@ -242,7 +241,7 @@ export class UI { .setLabel('Assign blocks'), }, 'export': { - label: 'Export', + label: '5. Export', elements: { 'export': new ComboBoxElement() .addItems([ @@ -433,7 +432,7 @@ export class UI { { const sidebarHTML = new HTMLBuilder(); - sidebarHTML.add(`
`); + sidebarHTML.add(`
`); { sidebarHTML.add(HeaderUIElement.Get.generateHTML()); @@ -485,11 +484,11 @@ export class UI { Split(['.column-sidebar', '.column-canvas'], { sizes: [20, 80], - minSize: [400, 500], + minSize: [450, 500], }); Split(['.column-properties', '.column-console'], { - sizes: [90, 10], + sizes: [85, 15], minSize: [0, 0], direction: 'vertical', }); @@ -539,25 +538,11 @@ export class UI { private _getGroupHTML(group: Group) { return ` -
-
-
-
-
-
- ${group.label.toUpperCase()} -
-
-
-
-
-
-
+ ${MiscComponents.createGroupHeader(group.label.toUpperCase())} +
${this._getGroupSubcomponentsHTML(group)}
-
- ${group.submitButton.generateHTML()} -
+ ${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(); } } } diff --git a/src/ui/misc.ts b/src/ui/misc.ts index 7f6818b..4221238 100644 --- a/src/ui/misc.ts +++ b/src/ui/misc.ts @@ -22,3 +22,15 @@ export class HTMLBuilder { element.innerHTML = this._html; } } + +export namespace MiscComponents { + export function createGroupHeader(label: string) { + return ` +
+
+ ${label} +
+
+ `; + } +} diff --git a/src/util/ui_util.ts b/src/util/ui_util.ts index 29aa512..06e8d32 100644 --- a/src/util/ui_util.ts +++ b/src/util/ui_util.ts @@ -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); + } } diff --git a/styles.css b/styles.css index 0686ab6..c111535 100644 --- a/styles.css +++ b/styles.css @@ -1,21 +1,25 @@ -@import url('https://fonts.googleapis.com/css2?family=Lexend:wght@300;400&display=swap'); +@import url('https://fonts.googleapis.com/css2?family=Rubik:wght@300;400;500&display=swap'); +@import url('https://fonts.googleapis.com/css2?family=Courier+Prime&display=swap'); :root { --pill-radius: 5px; - --prop-bg: rgb(26, 26, 26); - --prop-alt: hsl(0, 0%, 15%); - --prop-accent-border-hovered: #33AAC2; - --prop-accent-hovered: #0095b3; - --prop-accent-standard: #00738a; - --prop-accent-disabled: #00404d; + --gray-300: hsl(0, 0%, 10%); /* property background */ + --gray-350: hsl(0, 0%, 12%); - --prop-border-hovered: hsl(0, 0%, 38%); - --prop-hovered: hsl(0, 0%, 22%); - --prop-standard: hsl(0, 0%, 18%); - --prop-disabled: hsl(0, 0%, 14%); - --prop-sunken: hsl(0, 0%, 8%); + --gray-400: hsl(0, 0%, 14%); /* disabled property */ + --gray-500: hsl(0, 0%, 18%); /* standard property */ + --gray-600: hsl(0, 0%, 22%); /* standard border */ + --gray-700: hsl(0, 0%, 25%); /* hovered border */ + /*--gray-800: hsl(0, 0%, 38%);*/ + --blue-400: hsl(190, 100%, 15%); + --blue-450: hsl(190, 90%, 18%); + --blue-500: hsl(190, 85%, 27%); + --blue-600: hsl(190, 75%, 35%); + --blue-700: hsl(190, 58%, 48%); + + --text-bright: white; --text-light: hsl(0, 0%, 85%); --text-standard: hsl(0, 0%, 66%); --text-dim: hsl(0, 0%, 45%); @@ -24,22 +28,23 @@ --vertical-divider: hsl(0, 0%, 14%); --border-radius: 5px; - --property-height: 34px; + --property-height: 32px; --subproperty-height: 22px; --subprop-value-width: 125px; } * { - -webkit-box-sizing: border-box; /* Safari/Chrome, other WebKit */ - -moz-box-sizing: border-box; /* Firefox, other Gecko */ + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; box-sizing: border-box; } body { margin: 0px; - font-family: 'Lexend', sans-serif; + font-family: 'Rubik', sans-serif; overflow: hidden; user-select: none; + font-weight: 300; } canvas { @@ -67,33 +72,39 @@ canvas { .gutter { cursor: col-resize; height: 100%; - background: rgb(20, 20, 20); - border: 1px solid rgb(10, 10, 10); + background: var(--gray-500); display: flex; justify-content: center; align-items: center; + color: var(--gray-600); } +.gutter:hover { + background: var(--gray-600); + color: white; +} + .gutter.gutter-vertical { cursor: row-resize; } .gutter-line { - background-color: hsl(0, 0%, 20%); - width: 2px; + width: 3px; height: 20%; + background-color: currentColor; border-radius: 2px; } .gutter-line-horizontal { - background-color: hsl(0, 0%, 20%); width: 20%; - height: 2px; + height: 3px; + background-color: var(--gray-700); border-radius: 2px; } .column-properties { - background: var(--prop-bg); + background: var(--gray-300); overflow: auto; + padding: 10px; } .column-canvas { @@ -104,64 +115,111 @@ canvas { background: hsl(0, 0%, 5%); font-size: 85%; color: var(--text-dim); - font-family: monospace; + font-family: 'Courier Prime'; } .column-sidebar { } -.container { +.container-properties { display: flex; flex-direction: column; margin: 10px; } +.component-group { + padding: 5px 10px; + border: 1px solid var(--gray-400); + border-radius: 10px; + border-top-left-radius: 0px; +} + +.container-checkbox { + width: var(--property-height); + height: var(--property-height); +} + +.container-header { + display: flex; + flex-direction: row; + align-items: center; + color: var(--text-standard); + font-size: 85%; + padding: 5px; +} + .property { display: flex; flex-direction: row; align-items: center; color: var(--text-standard); font-size: 85%; - padding: 5px 10px; + padding: 5px 0px; + border-bottom: 2px solid var(--gray-350); +} +.property:first-child { +} +.property:last-child { + border-bottom: none; +} +/* +.property:nth-of-type(2n) { + border-radius: var(--border-radius); + background: var(--gray-350); + border-radius: 10px; +} +*/ + +.container-header { + display: flex; + flex-direction: row; + align-items: center; + color: var(--text-standard); + font-size: 85%; + padding: 5px 0px; } -.full-width-property { - +.container-button { + display: flex; + flex-direction: row; + align-items: center; + color: var(--text-standard); + font-size: 85%; + padding: 5px 0px; + margin: 0px; } -.full-width-property-container { -} - -.big-padding { - padding-bottom: 25px; -} -.big-padding:last-child { - padding-bottom: 5px; +.container-group-heading { + display: flex; + flex-direction: row; + align-items: flex-start; + padding-top: 30px; } .group-heading { - padding: 10px 10px; - color: var(--text-dark); + color: var(--gray-600); + letter-spacing: 4px; font-weight: 400; font-size: 85%; - letter-spacing: 4px; + padding: 12px 14px; + padding-bottom: 8px; + background: var(--gray-350); + border: 1px solid var(--gray-400); + border-bottom-width: 0px; + border-top-left-radius: 10px; + border-top-right-radius: 10px; } - .prop-key-container { display: flex; align-items: center; - padding: 0px 10px 0px 0px; + padding: 0px 5px; width: 150px; height: var(--property-height); overflow: auto; } -.text-disabled { - color: var(--text-dark) !important; -} - .prop-value-container { flex-grow: 1; display: flex; @@ -210,85 +268,16 @@ canvas { padding-left: 5px; } - - -.height-small { - height: var(--subproperty-height); - font-size: 100%; -} - -.height-normal { - height: var(--property-height); -} - - -.item-body-sunken { - background: var(--prop-sunken); - box-shadow: rgba(0, 0, 0, 0.2) 0px 3px 10px 0px inset; - border-radius: var(--border-radius); - color: var(--text-dark); - font-weight: 300; - font-size: 90%; - padding: 12px 18px; - line-height: 180%; - min-height: 22px; - width: 100%; - border: 1.5px solid var(--prop-sunken); - transition-duration: 1s; - overflow: hidden; -} - -.slider-height-normal { - height: var(--property-height); -} - -.slider-height-small { - height: calc(var(--subproperty-height) - 4px); - font-size: 100%; -} - input { - padding: 0px; - margin: 0px; - -moz-appearance: none; - font-size: 100%; - user-select: none; - margin-right: 3px; - border-radius: var(--border-radius); + font-family: 'Rubik', sans-serif; text-align: center; - background: var(--prop-standard); - font-family: 'Lexend', sans-serif; font-weight: 300; - - outline-color: var(--prop-accent-hovered); - border: 1px solid rgb(255, 255, 255, 0.0); -} -input:enabled { - color: var(--text-standard); -} -input:hover:enabled { - color: #C6C6C6; - border: 1px solid rgb(255, 255, 255, 0.1); - background: var(--prop-hovered); -} -input:disabled { - background: var(--prop-disabled); - color: var(--text-dark); } input::-webkit-outer-spin-button, input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; } -input[type=number] { - -moz-appearance:textfield; -} -input[type=text] { - -moz-appearance:textfield; - height: var(--property-height); - text-align: start; - padding-left: 10px; -} ::placeholder { color: var(--text-dark); @@ -299,102 +288,24 @@ input[type=text] { } select { - font-size: 100%; - width: 100%; - height: var(--property-height); - padding-left: 10px; - border-radius: var(--border-radius); - font-family: 'Lexend', sans-serif; - font-weight: 300; - color: var(--text-standard); - background: var(--prop-standard); - border: 1px solid rgb(255, 255, 255, 0.0); - max-width: 100%; - outline-color: var(--prop-accent-hovered); - opacity: 1.0; - cursor: inherit; - -webkit-appearance: none; -} -select:hover:enabled { - color: #C6C6C6; - background: var(--prop-hovered); - border: 1px solid rgb(255, 255, 255, 0.1); - cursor: pointer; -} -select:disabled { - background: var(--prop-disabled) !important; - color: var(--text-dark); + all: unset; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; } -.slider-bar-height-normal { - height: var(--property-height); +.checkbox-arrow { + position: absolute; + right: 8px; + top: 9px; + pointer-events: none; } -.slider-bar-height-small { - height: calc(var(--subproperty-height) - 2px); -} - -.new-slider { - border-radius: var(--border-radius); - font-family: 'Lexend', sans-serif; - font-weight: 300; - background: var(--prop-standard); - cursor: ew-resize; - border: 0px solid var(--prop-bg); - overflow: hidden; - flex-grow: 1; -} -.new-slider-hover { - border: 1px solid rgba(255, 255, 255, 0.1) !important; - background: var(--prop-hovered) !important; -} -.new-slider-disabled { - background: var(--prop-disabled) !important; - cursor: inherit !important; -} - -.new-slider-bar { - border-radius: var(--border-radius); - height: 100%; - background: var(--prop-accent-standard); - border: 1px solid rgb(255, 255, 255, 0.0); -} -.new-slider-bar-hover { - border: 1px solid rgb(255, 255, 255, 0.2) !important; - background: var(--prop-accent-hovered) !important; -} -.new-slider-bar-disabled { - background: var(--prop-accent-disabled) !important; - cursor: inherit !important; - border: 1px solid rgb(255, 255, 255, 0.0) !important; -} - - - .button { - width: calc(100% - 2px); - height: calc(var(--property-height) - 2px); - background: var(--prop-accent-standard); - border-radius: var(--border-radius); - color: white; - text-align: center; - border: 1px solid rgb(255, 255, 255, 0); - position: relative; - cursor: pointer; display: flex; align-items: center; justify-content: center; -} -.button:hover { - border: 1px solid rgb(255, 255, 255, 0.2); - background: var(--prop-accent-hovered); - cursor: pointer; -} -.button-disabled { - cursor: inherit !important; - background: var(--prop-accent-disabled) !important; - border: 1px solid rgb(255, 255, 255, 0) !important; - color: #808080 !important; + font-weight: 400; } .button-progress { @@ -412,41 +323,59 @@ select:disabled { transition: width 0.2s; } -.input-file { +.struct-prop { display: flex; align-items: center; width: 100%; - height: calc(var(--property-height) - 2px); - background: var(--prop-standard); + height: var(--property-height); border-radius: var(--border-radius); - font-weight: 300; padding: 0px 10px; - border: 1px solid rgb(255, 255, 255, 0); + border-width: 1px; + border-style: solid; } -.input-file-hover { - border: 1px solid rgb(255, 255, 255, 0.2); - background: var(--prop-hovered); + +.style-inactive-disabled { + border-color: var(--gray-500); + color: var(--text-dark); + background: var(--gray-400); + cursor: default; +} +.style-inactive-enabled { + border-color: var(--gray-600); + color: var(--text-standard); + background: var(--gray-500); +} +.style-inactive-hover { + border-color: var(--gray-700); + color: var(--text-light); + background: var(--gray-600); cursor: pointer; } -.input-file-disabled { - background: var(--prop-disabled) !important; - color: var(--text-dark) !important; + +.style-active-disabled { + border-color: var(--blue-450); + color: var(--text-dim); + background: var(--blue-400); + cursor: default; +} +.style-active-enabled { + border-color: var(--blue-600); + color: var(--text-bright); + background: var(--blue-500); +} +.style-active-hover { + border-color: var(--blue-700); + color: var(--text-bright); + background: var(--blue-600); + cursor: pointer; } .h-div { - height: 1px; + height: 0px; border-radius: 2px; background: var(--text-dark); } -.disabled { - opacity: 0.5; - cursor: inherit !important; -} - - - - ::-webkit-scrollbar { width: 10px; @@ -454,36 +383,18 @@ select:disabled { } ::-webkit-scrollbar-track { - background: rgb(20, 20, 20); + background: var(--gray-350); } ::-webkit-scrollbar-thumb { - background: rgb(50, 50, 50); + background: var(--gray-500); border-radius: 10px; + border: 1px solid var(--gray-600); } ::-webkit-scrollbar-thumb:hover { - background: rgb(60, 60, 60); -} - -.message-builder-item { - overflow: auto; - white-space: nowrap; -} - -.status-warning { - transition-duration: 0.5s; - color: orange; -} - -.status-success { - transition-duration: 0.5s; - color: var(--prop-accent-standard); -} - -.status-error { - transition-duration: 0.5s; - color: rgb(156, 27, 27); + background: var(--gray-600); + border: 1px solid var(--gray-700); } .spinbox-key { @@ -507,28 +418,7 @@ select:disabled { display: flex; align-items: center; justify-content: center; - height: calc(var(--property-height) - 2px); - background: var(--prop-standard); - border-radius: 5px; flex-grow: 1; - font-weight: 300; - border: 1px solid rgba(255, 255, 255, 0.0); - color: var(--text-standard); -} - -.spinbox-value-hover { - border: 1px solid rgba(255, 255, 255, 0.2) !important; - background: var(--prop-hovered) !important; - cursor: ew-resize; -} - -.spinbox-value-disabled { - color: var(--text-dark); - background: var(--prop-disabled) !important; -} - -.spinbox-divider { - width: 3px; } .toolbar { @@ -561,157 +451,32 @@ select:disabled { margin-right: 10px; } -.toolbar-item { +.container-icon-button { + width: var(--property-height); + height: var(--property-height); display: flex; align-items: center; justify-content: center; - height: 32px; padding: 0px 8px 0px 8px; text-align: center; - color: var(--text-dim); font-size: 85%; - font-weight: 300; - background-color: var(--prop-standard); - border: 1px solid var(--prop-standard); pointer-events: all; gap: 2px; } -.toolbar-item:only-of-type { - border-radius: 5px; -} - -.toolbar-item-small { - height: 24px !important; - padding: 0px 4px 0px 4px !important; -} - -.toolbar-item-hover { - background-color: var(--prop-hovered) !important; - border: 1px solid var(--prop-border-hovered) !important; - cursor: pointer; -} - -.toolbar-item-active-hover { - background-color: var(--prop-accent-hovered) !important; - border: 1px solid var(--prop-accent-border-hovered) !important; - cursor: pointer; - color: white; -} - -.toolbar-item-disabled-active { - background-color: var(--prop-accent-disabled) !important; - border: 1px solid var(--prop-accent-disabled) !important; -} - -.toolbar-item-disabled { - background-color: var(--prop-disabled) !important; - border: 1px solid var(--prop-disabled) !important; - cursor: default; -} - -.toolbar-item-active { - background-color: var(--prop-accent-standard) !important; - border-color: var(--prop-accent-border-hovered) !important; - color: white; -} - -.toolbar-item:first-child { - border-top-left-radius: 5px; - border-bottom-left-radius: 5px; -} - -.toolbar-item:last-child { - border-top-right-radius: 5px; - border-bottom-right-radius: 5px; -} - svg { width: 16px; height: 16px; - stroke: var(--text-standard); + stroke: currentColor; } -.icon-active { - stroke: white !important; +.container-checkbox { + justify-content: center; + padding: 0px; + width: calc(0.75 * var(--property-height)) !important; + height: calc(0.75 * var(--property-height)) !important; } -.icon-disabled { - stroke: var(--text-dark) !important; -} - -.icon-disabled-active { - stroke: #808080 !important; -} - -.palette-container { - width: 100%; - height: 200px; - display: flex; - flex-direction: row; - flex-wrap: wrap; - align-items: flex-start; - align-content: flex-start; - overflow: auto; - gap: 4px; -} - -.palette-item { - display: flex; - background: var(--prop-disabled); - width: 32px; - height: 32px; - border-radius: 8px; -} - - -.interactable-base { - display: flex; - border-radius: 8px; - padding: 5px; - border-width: 1px; - border-style: solid; -} - - -.interactable { - border-color: rgb(255, 255, 255, 0.0); - background-color: var(--prop-standard); -} - -.interactable-hover { - border-color: rgb(255, 255, 255, 0.2); - background-color: var(--prop-standard); -} - -.interactable-disabled { - border-color: rgb(255, 255, 255, 0.0); - background-color: var(--prop-disabled); -} - - -.interactable-active { - border-color: var(----prop-accent-standard); - background-color: var(--prop-accent-standard); -} - -.interactable-active-hover { - border-color: var(--prop-accent-border-hovered); - background-color: var(--prop-accent-hovered); -} - -.interactable-active-disabled { - border-color: var(--prop-accent-disabled); - background-color: var(--prop-accent-disabled); -} - - - -.toggleable-icon { - filter: drop-shadow(0px 0px 4px rgba(0, 0, 0, 0.125)); -} - - .button-loading { box-shadow: 0 0 0 0 rgba(0, 0, 0, 1); animation: pulse 2s infinite; @@ -731,15 +496,6 @@ svg { } } - - -.loader-circle { - width: 6px; - height: 6px; - border-radius: 50%; - background-color: currentColor; -} - .spin { animation: blinker 0.75s ease-in-out infinite; } @@ -764,7 +520,7 @@ svg { } .progress-bar { - background-color: var(--prop-accent-standard); + background-color: var(--blue-500); height: 100%; transition: width 0.1s; } @@ -812,26 +568,6 @@ svg { .material-container { } -.checkbox { - height: calc(var(--property-height) * 0.75); - aspect-ratio: 1/1; - background-color: var(--prop-standard); - border-radius: 5px; - border: 1px solid var(--prop-standard); - display: flex; - align-items: center; - justify-content: center; -} -.checkbox-hover { - background-color: var(--prop-hovered) !important; - border: 1px solid rgba(255, 255, 255, 0.2) !important; - cursor: pointer; -} -.checkbox-disabled { - background-color: var(--prop-disabled) !important; - border: 1px solid var(--prop-disabled) !important; -} - .checkbox-text { padding-left: 10px; font-weight: 300; @@ -841,18 +577,6 @@ svg { cursor: pointer; } -.checkbox-pip { - width: 50%; - height: 50%; - border-radius: 3px; -} -.checkbox-pip-hover { - stroke: white; -} -.checkbox-pip-disabled { - stroke: var(--text-dark); -} - .spinbox-main-container { display: flex; flex-direction: row; @@ -899,22 +623,13 @@ svg { align-items: center; } -.banner { - color: white; - font-weight: 300; - font-size: 75%; - padding: 6px; - text-align: center; - background-color: var(--prop-accent-disabled); - border: 1px solid rgba(255, 255, 255, 0.1); -} - .logo { width: 32px; margin-right: 10px; } .title { + font-weight: 400; font-size: 110%; } @@ -927,7 +642,19 @@ svg { .header-cols { justify-content: space-between; width: 100%; - padding-top: 4px; + padding-top: 7px; +} + +.struct-prop.container-icon-button { + border-radius: 0px; +} +.struct-prop.container-icon-button:first-child { + border-top-left-radius: 5px; + border-bottom-left-radius: 5px; +} +.struct-prop.container-icon-button:last-child { + border-top-right-radius: 5px; + border-bottom-right-radius: 5px; } .button-loading { @@ -950,7 +677,7 @@ svg { a { font-weight: 400; - color: var(--prop-accent-hovered) + color: var(--blue-600) } .text-dark { @@ -985,7 +712,3 @@ a { background: rgb(20, 20, 20); } -.changelog { - font-family: monospace; - color: var(--text-dim); -} \ No newline at end of file From e9a913a615867b32563140f49166c0b959b715cb Mon Sep 17 00:00:00 2001 From: Lucas Dower Date: Mon, 20 Mar 2023 19:08:38 +0000 Subject: [PATCH 2/3] Minor UI style changes --- src/app_context.ts | 54 ++++++++++++-------- src/ui/elements/header_element.ts | 36 +++++++------- src/ui/elements/placeholder_element.ts | 30 +++++++++++ src/ui/layout.ts | 20 ++++---- styles.css | 69 ++++---------------------- template.html | 2 +- 6 files changed, 101 insertions(+), 110 deletions(-) create mode 100644 src/ui/elements/placeholder_element.ts diff --git a/src/app_context.ts b/src/app_context.ts index f4c64f2..b676395 100644 --- a/src/app_context.ts +++ b/src/app_context.ts @@ -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,35 @@ 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(() => { + this._materialManager.changeMaterialType(materialName, MaterialType.solid); + this._updateMaterialsAction(); + }) + .onChangeTransparencyTypeDelegate((newTransparency) => { + 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']); } diff --git a/src/ui/elements/header_element.ts b/src/ui/elements/header_element.ts index 3b89a4a..c2bc170 100644 --- a/src/ui/elements/header_element.ts +++ b/src/ui/elements/header_element.ts @@ -43,28 +43,26 @@ export class HeaderUIElement extends BaseUIElement { public override generateHTML(): string { return ` -
-
-
-
- -
-
-
-
- ObjToSchematic -
-
- v${AppConfig.Get.MAJOR_VERSION}.${AppConfig.Get.MINOR_VERSION}.${AppConfig.Get.HOTFIX_VERSION}${AppConfig.Get.VERSION_TYPE} • Minecraft ${AppConfig.Get.MINECRAFT_VERSION} -
+
+
+
+ +
+
+
+
+ ObjToSchematic +
+
+ v${AppConfig.Get.MAJOR_VERSION}.${AppConfig.Get.MINOR_VERSION}.${AppConfig.Get.HOTFIX_VERSION}${AppConfig.Get.VERSION_TYPE} • Minecraft ${AppConfig.Get.MINECRAFT_VERSION}
-
- ${this._githubButton.generateHTML()} - ${this._bugButton.generateHTML()} - ${this._discordButton.generateHTML()} -
+
+
+ ${this._githubButton.generateHTML()} + ${this._bugButton.generateHTML()} + ${this._discordButton.generateHTML()}
`; diff --git a/src/ui/elements/placeholder_element.ts b/src/ui/elements/placeholder_element.ts new file mode 100644 index 0000000..32aeff5 --- /dev/null +++ b/src/ui/elements/placeholder_element.ts @@ -0,0 +1,30 @@ +import { ConfigUIElement } from './config_element'; + +export class PlaceholderElement extends ConfigUIElement { + private _placeholderText: string; + + public constructor(placeholderText: string) { + super(undefined); + + this._placeholderText = placeholderText; + } + + public override generateHTML(): string { + return ` +
+ ${this._generateInnerHTML()} +
+ `; + } + + protected override _generateInnerHTML(): string { + return ` +
+ ${this._placeholderText} +
+ `; + } + + public override registerEvents(): void { + } +} diff --git a/src/ui/layout.ts b/src/ui/layout.ts index 311a57d..728a4cc 100644 --- a/src/ui/layout.ts +++ b/src/ui/layout.ts @@ -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'; @@ -412,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) { @@ -485,23 +493,15 @@ export class UI { Split(['.column-sidebar', '.column-canvas'], { sizes: [20, 80], minSize: [450, 500], + gutterSize: 5, }); Split(['.column-properties', '.column-console'], { sizes: [85, 15], minSize: [0, 0], direction: 'vertical', + gutterSize: 5, }); - - const itemA = document.getElementsByClassName('gutter').item(1); - if (itemA !== null) { - itemA.innerHTML = `
`; - } - - const itemB = document.getElementsByClassName('gutter').item(0); - if (itemB !== null) { - itemB.innerHTML = `
`; - } } public cacheValues(action: EAction) { diff --git a/styles.css b/styles.css index c111535..5323f23 100644 --- a/styles.css +++ b/styles.css @@ -1,4 +1,4 @@ -@import url('https://fonts.googleapis.com/css2?family=Rubik:wght@300;400;500&display=swap'); +@import url('https://fonts.googleapis.com/css2?family=Rubik:wght@400&display=swap'); @import url('https://fonts.googleapis.com/css2?family=Courier+Prime&display=swap'); :root { @@ -11,7 +11,7 @@ --gray-500: hsl(0, 0%, 18%); /* standard property */ --gray-600: hsl(0, 0%, 22%); /* standard border */ --gray-700: hsl(0, 0%, 25%); /* hovered border */ - /*--gray-800: hsl(0, 0%, 38%);*/ + --gray-800: hsl(0, 0%, 38%); --blue-400: hsl(190, 100%, 15%); --blue-450: hsl(190, 90%, 18%); @@ -44,7 +44,6 @@ body { font-family: 'Rubik', sans-serif; overflow: hidden; user-select: none; - font-weight: 300; } canvas { @@ -80,27 +79,13 @@ canvas { } .gutter:hover { background: var(--gray-600); - color: white; + color: var(--gray-800); } .gutter.gutter-vertical { cursor: row-resize; } -.gutter-line { - width: 3px; - height: 20%; - background-color: currentColor; - border-radius: 2px; -} - -.gutter-line-horizontal { - width: 20%; - height: 3px; - background-color: var(--gray-700); - border-radius: 2px; -} - .column-properties { background: var(--gray-300); overflow: auto; @@ -108,7 +93,7 @@ canvas { } .column-canvas { - cursor: crosshair; + cursor: grab; } .column-console { @@ -132,7 +117,6 @@ canvas { padding: 5px 10px; border: 1px solid var(--gray-400); border-radius: 10px; - border-top-left-radius: 0px; } .container-checkbox { @@ -140,14 +124,6 @@ canvas { height: var(--property-height); } -.container-header { - display: flex; - flex-direction: row; - align-items: center; - color: var(--text-standard); - font-size: 85%; - padding: 5px; -} .property { display: flex; @@ -171,15 +147,6 @@ canvas { } */ -.container-header { - display: flex; - flex-direction: row; - align-items: center; - color: var(--text-standard); - font-size: 85%; - padding: 5px 0px; -} - .container-button { display: flex; flex-direction: row; @@ -194,21 +161,14 @@ canvas { display: flex; flex-direction: row; align-items: flex-start; - padding-top: 30px; + padding-top: 20px; } .group-heading { color: var(--gray-600); letter-spacing: 4px; - font-weight: 400; font-size: 85%; - padding: 12px 14px; - padding-bottom: 8px; - background: var(--gray-350); - border: 1px solid var(--gray-400); - border-bottom-width: 0px; - border-top-left-radius: 10px; - border-top-right-radius: 10px; + padding: 12px 0px; } .prop-key-container { @@ -263,15 +223,12 @@ canvas { min-height: var(--subproperty-height); width: var(--subprop-value-width); overflow: auto; - font-weight: 300; - /*border-left: 1px solid var(--vertical-divider);*/ padding-left: 5px; } input { font-family: 'Rubik', sans-serif; text-align: center; - font-weight: 300; } input::-webkit-outer-spin-button, input::-webkit-inner-spin-button { @@ -305,7 +262,6 @@ select { display: flex; align-items: center; justify-content: center; - font-weight: 400; } .button-progress { @@ -400,7 +356,6 @@ select { .spinbox-key { color: #ffffffb9; font-size: 85%; - font-weight: 300; padding-right: 5px; } @@ -570,7 +525,6 @@ svg { .checkbox-text { padding-left: 10px; - font-weight: 300; color: var(--text-dim) } .checkbox-text-hover { @@ -629,20 +583,21 @@ svg { } .title { - font-weight: 400; font-size: 110%; } .subtitle { font-size: 90%; - font-weight: 300; color: var(--text-dim); } .header-cols { + align-items: start; justify-content: space-between; width: 100%; - padding-top: 7px; + color: var(--text-standard); + font-size: 85%; + padding-top: 5px; } .struct-prop.container-icon-button { @@ -676,7 +631,6 @@ svg { } a { - font-weight: 400; color: var(--blue-600) } @@ -710,5 +664,4 @@ a { ::-webkit-scrollbar-corner { background: rgb(20, 20, 20); -} - +} \ No newline at end of file diff --git a/template.html b/template.html index df951a2..6b7e1f0 100644 --- a/template.html +++ b/template.html @@ -38,7 +38,7 @@
-
+
From 083dd8ded8576ba0385bde1433582e4a2d8eafbf Mon Sep 17 00:00:00 2001 From: Lucas Dower Date: Mon, 20 Mar 2023 22:21:43 +0000 Subject: [PATCH 3/3] Final touches to UI refactor --- src/app_context.ts | 2 + src/config.ts | 4 +- src/ui/elements/base_element.ts | 8 +- src/ui/elements/colour_element.ts | 29 +++++ src/ui/elements/combobox.ts | 31 ++---- src/ui/elements/config_element.ts | 6 +- src/ui/elements/header_element.ts | 2 +- src/ui/elements/image_element.ts | 23 ++-- src/ui/elements/material_type_element.ts | 50 +++++---- src/ui/elements/palette_element.ts | 8 +- src/ui/elements/slider.ts | 41 +++++-- src/ui/elements/solid_material_element.ts | 66 +++++------- src/ui/elements/textured_material_element.ts | 91 ++++++++-------- src/ui/elements/toolbar_item.ts | 9 +- src/ui/icons.ts | 39 ++++++- src/ui/layout.ts | 2 +- styles.css | 107 +++++++++---------- 17 files changed, 299 insertions(+), 219 deletions(-) create mode 100644 src/ui/elements/colour_element.ts diff --git a/src/app_context.ts b/src/app_context.ts index b676395..c98feaa 100644 --- a/src/app_context.ts +++ b/src/app_context.ts @@ -249,10 +249,12 @@ export class AppContext { 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(); }); diff --git a/src/config.ts b/src/config.ts index 2e826a7..d9777ee 100644 --- a/src/config.ts +++ b/src/config.ts @@ -10,8 +10,8 @@ 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'; diff --git a/src/ui/elements/base_element.ts b/src/ui/elements/base_element.ts index 02b0d4b..380abfe 100644 --- a/src/ui/elements/base_element.ts +++ b/src/ui/elements/base_element.ts @@ -1,11 +1,17 @@ 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 { +export abstract class BaseUIElement implements IInterfaceItem { private _id: string; private _isEnabled: boolean; private _isHovered: boolean; diff --git a/src/ui/elements/colour_element.ts b/src/ui/elements/colour_element.ts new file mode 100644 index 0000000..48a34a9 --- /dev/null +++ b/src/ui/elements/colour_element.ts @@ -0,0 +1,29 @@ +import { RGBA, RGBAUtil } from '../../colour'; +import { ConfigUIElement } from './config_element'; + +export class ColourElement extends ConfigUIElement { + public constructor(colour: RGBA) { + super(colour); + } + + protected override _generateInnerHTML(): string { + return ``; + } + + 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'); + } + } +} diff --git a/src/ui/elements/combobox.ts b/src/ui/elements/combobox.ts index 3fd3019..e29021a 100644 --- a/src/ui/elements/combobox.ts +++ b/src/ui/elements/combobox.ts @@ -11,12 +11,10 @@ export type ComboBoxItem = { export class ComboBoxElement extends ConfigUIElement { private _items: ComboBoxItem[]; - private _small: boolean; public constructor() { super(); this._items = []; - this._small = false; } public addItems(items: ComboBoxItem[]) { @@ -32,11 +30,6 @@ export class ComboBoxElement extends ConfigUIElement { return this; } - public setSmall() { - this._small = true; - return this; - } - public override registerEvents(): void { this._getElement().addEventListener('mouseenter', () => { this._setHovered(true); @@ -54,20 +47,6 @@ export class ComboBoxElement extends ConfigUIElement { }); } - /* - 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() { const builder = new HTMLBuilder(); @@ -77,15 +56,21 @@ export class ComboBoxElement extends ConfigUIElement { builder.add(``); } builder.add(''); - + builder.add(`
`); builder.add(AppIcons.ARROW_DOWN); builder.add(`
`); builder.add('
'); - + return builder.toString(); } + protected _onValueChanged(): void { + super._onValueChanged(); + + console.log('combo changed'); + } + protected _onEnabledChanged(): void { super._onEnabledChanged(); this._getElement().disabled = this.disabled; diff --git a/src/ui/elements/config_element.ts b/src/ui/elements/config_element.ts index 591f024..ab8bff8 100644 --- a/src/ui/elements/config_element.ts +++ b/src/ui/elements/config_element.ts @@ -72,10 +72,12 @@ export abstract class ConfigUIElement extends BaseUIElement { public override finalise(): void { super.finalise(); + /* this._onValueChanged(); this._onValueChangedListeners.forEach((listener) => { listener(this._value!); }); + */ } public override generateHTML() { @@ -100,9 +102,11 @@ export abstract class ConfigUIElement extends BaseUIElement { const label = document.getElementById(this._getLabelId()) as (HTMLDivElement | null); if (this.enabled) { - label?.classList.remove('text-standard'); + label?.classList.remove('text-dark'); + label?.classList.add('text-standard'); } else { label?.classList.add('text-dark'); + label?.classList.remove('text-standard'); } this._onEnabledChangedListeners.forEach((listener) => { diff --git a/src/ui/elements/header_element.ts b/src/ui/elements/header_element.ts index c2bc170..af018b6 100644 --- a/src/ui/elements/header_element.ts +++ b/src/ui/elements/header_element.ts @@ -59,7 +59,7 @@ export class HeaderUIElement extends BaseUIElement {
-
+
${this._githubButton.generateHTML()} ${this._bugButton.generateHTML()} ${this._discordButton.generateHTML()} diff --git a/src/ui/elements/image_element.ts b/src/ui/elements/image_element.ts index 5453880..20205d4 100644 --- a/src/ui/elements/image_element.ts +++ b/src/ui/elements/image_element.ts @@ -15,7 +15,6 @@ export class ImageElement extends ConfigUIElement, 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, HTMLIm
Texture Preview -
-
-
-
- - ${this._switchElement.generateHTML()} +
+
+
${AppIcons.IMAGE_MISSING}
+
No image loaded
+
+ + ${this._switchElement.generateHTML()} +
`; } @@ -70,10 +71,15 @@ export class ImageElement extends ConfigUIElement, 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, 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, HTMLIm super.finalise(); this._onValueChanged(); + this._onEnabledChanged(); } } diff --git a/src/ui/elements/material_type_element.ts b/src/ui/elements/material_type_element.ts index 29a7f3b..cd964e2 100644 --- a/src/ui/elements/material_type_element.ts +++ b/src/ui/elements/material_type_element.ts @@ -4,48 +4,58 @@ import { ConfigUIElement } from './config_element'; import { ToolbarItemElement } from './toolbar_item'; export class MaterialTypeElement extends ConfigUIElement { - 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 ` -
-
- ${material === MaterialType.solid ? 'Solid' : 'Textured'} -
-
-
-
- ${this._switchElement.generateHTML()} -
-
-
+
+ ${this._solidButton.generateHTML()} + ${this._texturedButton.generateHTML()}
`; } 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 { diff --git a/src/ui/elements/palette_element.ts b/src/ui/elements/palette_element.ts index 00c4227..407bc6a 100644 --- a/src/ui/elements/palette_element.ts +++ b/src/ui/elements/palette_element.ts @@ -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 { +export class PaletteElement extends ConfigUIElement { private _checkboxes: { block: string, element: CheckboxElement }[]; private _palette: Palette; private _selectAll: ToolbarItemElement; @@ -135,7 +136,7 @@ export class PaletteElement extends FullConfigUIElement return `
- +
@@ -180,6 +181,7 @@ export class PaletteElement extends FullConfigUIElement this._exportTo.setEnabled(this.enabled); this._onCountSelectedChanged(); + this._updateStyles(); } public override registerEvents(): void { @@ -226,6 +228,8 @@ export class PaletteElement extends FullConfigUIElement this._exportTo.finalise(); this._onCountSelectedChanged(); + + this._updateStyles(); //this._selectAll.finalise(); //this._deselectAll.finalise(); //this._importFrom.finalise(); diff --git a/src/ui/elements/slider.ts b/src/ui/elements/slider.ts index c719ab2..b57e4b2 100644 --- a/src/ui/elements/slider.ts +++ b/src/ui/elements/slider.ts @@ -18,7 +18,7 @@ export class SliderElement extends ConfigUIElement { private _step: number; private _dragging: boolean; private _internalValue: number; - private _small: boolean; + private _valueHovered: boolean; public constructor() { super(); @@ -28,7 +28,7 @@ export class SliderElement extends ConfigUIElement { this._step = 0.1; this._internalValue = 0.5; this._dragging = false; - this._small = false; + this._valueHovered = false; } public override setDefaultValue(value: number) { @@ -37,11 +37,6 @@ export class SliderElement extends ConfigUIElement { return this; } - public setSmall() { - this._small = true; - return this; - } - /** * Set the minimum value the slider can be set to. */ @@ -116,15 +111,25 @@ export class SliderElement extends ConfigUIElement { 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 ` - -
-
+ +
+
`; @@ -132,6 +137,20 @@ export class SliderElement extends ConfigUIElement { protected override _onEnabledChanged() { super._onEnabledChanged(); + + const elementValue = UIUtil.getElementById(this._getSliderValueId()) as HTMLInputElement; + elementValue.disabled = this.disabled; + + const elementBar = UIUtil.getElementById(this._getSliderBarId()); + const elementSlider = UIUtil.getElementById(this._getId()); + if (this.enabled) { + elementBar.classList.add('enabled'); + elementSlider.classList.add('enabled'); + } else { + elementBar.classList.remove('enabled'); + elementSlider.classList.remove('enabled'); + } + this._updateStyles(); } @@ -201,7 +220,7 @@ export class SliderElement extends ConfigUIElement { protected override _updateStyles(): void { const elementValue = UIUtil.getElementById(this._getSliderValueId()) as HTMLInputElement; UIUtil.updateStyles(elementValue, { - isHovered: false, + isHovered: this._valueHovered, isActive: false, isEnabled: this.enabled, }); diff --git a/src/ui/elements/solid_material_element.ts b/src/ui/elements/solid_material_element.ts index ab2893b..9b146d7 100644 --- a/src/ui/elements/solid_material_element.ts +++ b/src/ui/elements/solid_material_element.ts @@ -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 { - 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(` -
-
- ${key} -
-
- ${value} -
-
- `); - }; - - addSubproperty('Type', this._typeElement._generateInnerHTML()); - addSubproperty('Colour', ``); - addSubproperty('Alpha', this._alphaElement._generateInnerHTML()); - return ` -
- ${subproperties.join('')} +
+ ${this._typeElement.generateHTML()} + ${this._colourElement.generateHTML()} + ${this._alphaElement.generateHTML()}
`; } @@ -84,6 +68,10 @@ export class SolidMaterialElement extends ConfigUIElement void; diff --git a/src/ui/elements/textured_material_element.ts b/src/ui/elements/textured_material_element.ts index cada5ed..69fba8a 100644 --- a/src/ui/elements/textured_material_element.ts +++ b/src/ui/elements/textured_material_element.ts @@ -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 { - private _materialName: string; - private _colourId: string; + private _typeElement: MaterialTypeElement; private _filteringElement: ComboBoxElement<'nearest' | 'linear'>; private _wrapElement: ComboBoxElement<'clamp' | 'repeat'>; private _transparencyElement: ComboBoxElement; private _imageElement: ImageElement; - private _typeElement: MaterialTypeElement; private _alphaValueElement?: SliderElement; private _alphaMapElement?: ImageElement; private _alphaChannelElement?: ComboBoxElement; 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() + .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() + .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() + .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 { - subproperties.push(` -
-
- ${key} -
-
- ${value} -
-
- `); - }; + 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('
'); + { + 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('
'); - return ` -
- ${subproperties.join('')} -
- `; + return builder.toString(); } protected override _onValueChanged(): void { @@ -179,6 +169,15 @@ export class TexturedMaterialElement extends ConfigUIElement { private _iconSVG: SVGSVGElement; - private _small: boolean; private _label: string; private _onClick?: () => void; private _isActive: boolean; @@ -34,19 +33,13 @@ export class ToolbarItemElement extends BaseUIElement { this._iconSVG.id = this._getId() + '-svg'; } - this._small = false; this._label = ''; } public setActive(isActive: boolean) { this._isActive = isActive; - } - - - public setSmall() { - this._small = true; - return this; + this._updateStyles(); } public setLabel(label: string) { diff --git a/src/ui/icons.ts b/src/ui/icons.ts index 981ca24..527707f 100644 --- a/src/ui/icons.ts +++ b/src/ui/icons.ts @@ -1,3 +1,4 @@ +// https://tablericons.com/ export namespace AppIcons { export const MESH = ` @@ -61,10 +62,11 @@ export namespace AppIcons { `; export const BULB = ` - + - - + + + `; @@ -215,4 +217,35 @@ export namespace AppIcons { `; + + export const COLOUR_SWATCH = ` + + + + + + + + `; + + export const IMAGE = ` + + + + + + + + `; + + export const IMAGE_MISSING = ` + + + + + + + + + `; } diff --git a/src/ui/layout.ts b/src/ui/layout.ts index 728a4cc..971e2c5 100644 --- a/src/ui/layout.ts +++ b/src/ui/layout.ts @@ -492,7 +492,7 @@ export class UI { Split(['.column-sidebar', '.column-canvas'], { sizes: [20, 80], - minSize: [450, 500], + minSize: [600, 500], gutterSize: 5, }); diff --git a/styles.css b/styles.css index 5323f23..a260117 100644 --- a/styles.css +++ b/styles.css @@ -29,8 +29,6 @@ --border-radius: 5px; --property-height: 32px; - --subproperty-height: 22px; - --subprop-value-width: 125px; } * { @@ -117,6 +115,7 @@ canvas { padding: 5px 10px; border: 1px solid var(--gray-400); border-radius: 10px; + width: 100%; } .container-checkbox { @@ -165,7 +164,7 @@ canvas { } .group-heading { - color: var(--gray-600); + color: var(--gray-700); letter-spacing: 4px; font-size: 85%; padding: 12px 0px; @@ -175,7 +174,7 @@ canvas { display: flex; align-items: center; padding: 0px 5px; - width: 150px; + width: 40%; height: var(--property-height); overflow: auto; } @@ -185,56 +184,23 @@ canvas { display: flex; align-items: center; width: 0px; -} - - - -.subproperty-container { - display: flex; - flex-direction: column; - width: 100%; -} - -.subproperty { - display: flex; - flex-direction: row; - align-items: center; - color: var(--text-standard); - font-size: 85%; - width: 100%; - padding-top: 2px; - padding-bottom: 2px; - border-bottom: 1px solid var(--vertical-divider); -} -.subproperty:last-child { - border-bottom: 0px; -} - -.subprop-key-container { - align-self: center; - width: 50%; - overflow: auto; -} - -.subprop-value-container { - flex-grow: 1; - display: flex; - align-items: center; - min-height: var(--subproperty-height); - width: var(--subprop-value-width); - overflow: auto; - padding-left: 5px; + overflow-x: auto; } input { font-family: 'Rubik', sans-serif; text-align: center; + font-size: inherit; + outline: none; } input::-webkit-outer-spin-button, input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; } +input[type=number]:not(:disabled) { + cursor: text; +} ::placeholder { color: var(--text-dark); @@ -262,6 +228,7 @@ select { display: flex; align-items: center; justify-content: center; + position: relative; } .button-progress { @@ -390,7 +357,8 @@ select { .toolbar-column { display: flex; flex-direction: row; - margin: 20px 10px; + margin: 20px; + gap: 20px; } .toolbar-column:last-child { @@ -402,12 +370,9 @@ select { flex-direction: row; box-shadow: rgba(0, 0, 0, 0.1) 0px 0px 16px; border-radius: var(--border-radius); - margin-left: 10px; - margin-right: 10px; } .container-icon-button { - width: var(--property-height); height: var(--property-height); display: flex; align-items: center; @@ -481,19 +446,16 @@ svg { } .colour-swatch { - border: 1px solid #8C8C8C80; - border-radius: 5px; + border: none; -webkit-appearance: none; -moz-appearance: none; appearance: none; background: none; width: 100%; + height: var(--property-height); margin: 0px; padding: 0px; } -.colour-swatch-hover { - border-color: var(--text-standard) !important; -} .colour-swatch::-webkit-color-swatch-wrapper { border: none; @@ -505,17 +467,34 @@ svg { margin: 0px; padding: 0px; border-radius: 5px; + border: 1px solid var(--gray-600); +} +.enabled.colour-swatch::-webkit-color-swatch:hover { + border: 1px solid var(--gray-700); + filter: brightness(1.1); + cursor: pointer; } .texture-preview { - border: 1px solid #8C8C8C80; + border: 1px solid var(--gray-600); border-radius: 5px; - width: calc(100% - 2px); + width: 100%; } .texture-preview-missing { border-color: orange !important; } +.texture-preview-placeholder { + border: 1px solid var(--gray-400); + color: var(--text-dim); + border-radius: 5px; + width: 100%; + aspect-ratio: 1/1; + display: flex; + align-items: center; + justify-content: center; +} + .texture-hover { border-color: var(--text-standard) !important; } @@ -556,6 +535,7 @@ svg { display: flex; flex-direction: column; gap: 2px; + width: 100%; } .row-item { @@ -611,6 +591,9 @@ svg { border-top-right-radius: 5px; border-bottom-right-radius: 5px; } +.struct-prop.container-icon-button:only-of-type { + border-radius: 5px; +} .button-loading { box-shadow: 0 0 0 0 rgba(0, 0, 0, 1); @@ -664,4 +647,20 @@ a { ::-webkit-scrollbar-corner { background: rgb(20, 20, 20); +} + +.comp-slider-value { + padding: 0px; + width: 40%; + margin-right: 5px; +} +.comp-slider-outer { + flex-grow: 1; + padding: 0px; +} +.comp-slider-inner { + padding: 0px; +} +.comp-slider.enabled { + cursor: e-resize; } \ No newline at end of file