forked from mirror/ObjToSchematic
Final touches to UI refactor
This commit is contained in:
parent
e9a913a615
commit
083dd8ded8
@ -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();
|
||||
});
|
||||
|
@ -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';
|
||||
|
||||
|
@ -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<T> {
|
||||
export abstract class BaseUIElement<T> implements IInterfaceItem {
|
||||
private _id: string;
|
||||
private _isEnabled: boolean;
|
||||
private _isHovered: boolean;
|
||||
|
29
src/ui/elements/colour_element.ts
Normal file
29
src/ui/elements/colour_element.ts
Normal 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');
|
||||
}
|
||||
}
|
||||
}
|
@ -11,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>[]) {
|
||||
@ -32,11 +30,6 @@ 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);
|
||||
@ -54,20 +47,6 @@ export class ComboBoxElement<T> extends ConfigUIElement<T, HTMLSelectElement> {
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
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();
|
||||
|
||||
@ -86,6 +65,12 @@ export class ComboBoxElement<T> extends ConfigUIElement<T, HTMLSelectElement> {
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
protected _onValueChanged(): void {
|
||||
super._onValueChanged();
|
||||
|
||||
console.log('combo changed');
|
||||
}
|
||||
|
||||
protected _onEnabledChanged(): void {
|
||||
super._onEnabledChanged();
|
||||
this._getElement().disabled = this.disabled;
|
||||
|
@ -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() {
|
||||
@ -100,9 +102,11 @@ export abstract class ConfigUIElement<T, F> extends BaseUIElement<F> {
|
||||
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) => {
|
||||
|
@ -59,7 +59,7 @@ export class HeaderUIElement extends BaseUIElement<HTMLDivElement> {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="toolbar-group" style="margin-right: 0px;">
|
||||
<div class="toolbar-group">
|
||||
${this._githubButton.generateHTML()}
|
||||
${this._bugButton.generateHTML()}
|
||||
${this._discordButton.generateHTML()}
|
||||
|
@ -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,16 +29,18 @@ 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 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">
|
||||
<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>
|
||||
</div>
|
||||
</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();
|
||||
}
|
||||
}
|
||||
|
@ -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(() => {
|
||||
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 {
|
||||
|
@ -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;
|
||||
@ -135,7 +136,7 @@ export class PaletteElement extends FullConfigUIElement<Palette, HTMLDivElement>
|
||||
|
||||
return `
|
||||
<div class="row-container" style="width: 100%; gap: 5px;">
|
||||
<input class="struct-prop" 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">
|
||||
@ -180,6 +181,7 @@ export class PaletteElement extends FullConfigUIElement<Palette, HTMLDivElement>
|
||||
this._exportTo.setEnabled(this.enabled);
|
||||
|
||||
this._onCountSelectedChanged();
|
||||
this._updateStyles();
|
||||
}
|
||||
|
||||
public override registerEvents(): void {
|
||||
@ -226,6 +228,8 @@ export class PaletteElement extends FullConfigUIElement<Palette, HTMLDivElement>
|
||||
this._exportTo.finalise();
|
||||
|
||||
this._onCountSelectedChanged();
|
||||
|
||||
this._updateStyles();
|
||||
//this._selectAll.finalise();
|
||||
//this._deselectAll.finalise();
|
||||
//this._importFrom.finalise();
|
||||
|
@ -18,7 +18,7 @@ export class SliderElement extends ConfigUIElement<number, HTMLDivElement> {
|
||||
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<number, HTMLDivElement> {
|
||||
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<number, HTMLDivElement> {
|
||||
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<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="struct-prop" style="padding: 0px; width: 20%; margin-right: 5px;" type="number" id="${this._getSliderValueId()}" min="${this._min}" max="${this._max}" step="${this._step}" value="${this.getValue().toFixed(this._decimals)}">
|
||||
<div class="struct-prop" id="${this._getId()}" style="flex-grow: 1; padding: 0px;">
|
||||
<div class="struct-prop" 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>
|
||||
`;
|
||||
@ -132,6 +137,20 @@ export class SliderElement extends ConfigUIElement<number, HTMLDivElement> {
|
||||
|
||||
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<number, HTMLDivElement> {
|
||||
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,
|
||||
});
|
||||
|
@ -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;
|
||||
|
@ -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());
|
||||
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);
|
||||
addSubproperty('Alpha map', this._alphaMapElement._generateInnerHTML());
|
||||
addSubproperty('Channel', this._alphaChannelElement._generateInnerHTML());
|
||||
builder.add(this._alphaMapElement.generateHTML());
|
||||
builder.add(this._alphaChannelElement.generateHTML());
|
||||
}
|
||||
if (this._alphaValueElement) {
|
||||
addSubproperty('Alpha', this._alphaValueElement._generateInnerHTML());
|
||||
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 {
|
||||
|
@ -14,7 +14,6 @@ export type TToolbarItemParams = {
|
||||
|
||||
export class ToolbarItemElement extends BaseUIElement<HTMLDivElement> {
|
||||
private _iconSVG: SVGSVGElement;
|
||||
private _small: boolean;
|
||||
private _label: string;
|
||||
private _onClick?: () => void;
|
||||
private _isActive: boolean;
|
||||
@ -34,19 +33,13 @@ export class ToolbarItemElement extends BaseUIElement<HTMLDivElement> {
|
||||
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) {
|
||||
|
@ -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>
|
||||
`;
|
||||
|
||||
@ -215,4 +217,35 @@ export namespace AppIcons {
|
||||
<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>
|
||||
`;
|
||||
}
|
||||
|
@ -492,7 +492,7 @@ export class UI {
|
||||
|
||||
Split(['.column-sidebar', '.column-canvas'], {
|
||||
sizes: [20, 80],
|
||||
minSize: [450, 500],
|
||||
minSize: [600, 500],
|
||||
gutterSize: 5,
|
||||
});
|
||||
|
||||
|
107
styles.css
107
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);
|
||||
@ -665,3 +648,19 @@ 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;
|
||||
}
|
Loading…
Reference in New Issue
Block a user