From 75763598797d01761616992859b7d8170ebd2922 Mon Sep 17 00:00:00 2001 From: Louis Poirier Date: Thu, 31 Oct 2024 01:54:58 +0100 Subject: [PATCH] refactor: add fields to widgets. Define properties of widgets as fields. Define components to bind widget's field by type. Update to bind widget's fields using field components. WIP. Closes #47 --- CHANGELOG.md | 8 + src/app/formatters/lua-sol2.formatter.ts | 44 ++-- src/app/models/fields/array.field.ts | 11 + src/app/models/fields/bool.field.ts | 11 + src/app/models/fields/color.field.ts | 12 + src/app/models/fields/enum.field.ts | 22 ++ src/app/models/fields/field.ts | 65 ++++++ src/app/models/fields/flags.field.ts | 50 ++++ src/app/models/fields/float.field.ts | 11 + src/app/models/fields/integer.field.ts | 11 + src/app/models/fields/number.field.ts | 11 + src/app/models/fields/size.field.ts | 24 ++ src/app/models/fields/string.field.ts | 11 + src/app/models/math.ts | 29 ++- src/app/models/object.ts | 8 + src/app/models/widgets/bloc-for.widget.ts | 4 +- src/app/models/widgets/button.widget.ts | 27 ++- src/app/models/widgets/checkbox.widget.ts | 10 +- src/app/models/widgets/child-window.widget.ts | 29 +-- .../widgets/collapsing-header.widget.ts | 9 +- src/app/models/widgets/combo.widget.ts | 14 +- src/app/models/widgets/dummy.widget.ts | 26 ++- .../models/widgets/input-color-edit.widget.ts | 26 +-- src/app/models/widgets/input-number.widget.ts | 58 +++-- src/app/models/widgets/input-text.widget.ts | 23 +- .../models/widgets/input-textarea.widget.ts | 24 +- src/app/models/widgets/label.widget.ts | 10 +- src/app/models/widgets/listbox.widget.ts | 18 +- src/app/models/widgets/menu-item.widget.ts | 18 +- src/app/models/widgets/menu.widget.ts | 8 +- src/app/models/widgets/modal.widget.ts | 14 +- src/app/models/widgets/plot.widget.ts | 51 +++-- src/app/models/widgets/popup.widget.ts | 14 +- src/app/models/widgets/progress-bar.widget.ts | 12 +- src/app/models/widgets/text.widget.ts | 24 +- src/app/models/widgets/tree-node.widget.ts | 3 + src/app/models/widgets/widget.helper.ts | 215 ++---------------- src/app/models/widgets/widget.ts | 173 +++++++++++++- src/app/models/widgets/window.widget.ts | 28 ++- .../editor/fields/abstract-field.component.ts | 80 +++++++ .../array-string-field.component.css | 0 .../array-string-field.component.html | 6 + .../array-string-field.component.spec.ts | 23 ++ .../array-string-field.component.ts | 38 ++++ .../bool-field/bool-field.component.css | 0 .../bool-field/bool-field.component.html | 5 + .../bool-field/bool-field.component.spec.ts | 23 ++ .../fields/bool-field/bool-field.component.ts | 25 ++ .../color-field/color-field.component.css | 0 .../color-field/color-field.component.html | 8 + .../color-field/color-field.component.spec.ts | 23 ++ .../color-field/color-field.component.ts | 77 +++++++ .../enum-field/enum-field.component.css | 0 .../enum-field/enum-field.component.html | 8 + .../enum-field/enum-field.component.spec.ts | 23 ++ .../fields/enum-field/enum-field.component.ts | 32 +++ .../flags-field/flags-field.component.css | 0 .../flags-field/flags-field.component.html | 8 + .../flags-field/flags-field.component.spec.ts | 23 ++ .../flags-field/flags-field.component.ts | 63 +++++ .../integer-field/integer-field.component.css | 0 .../integer-field.component.html | 8 + .../integer-field.component.spec.ts | 23 ++ .../integer-field/integer-field.component.ts | 39 ++++ .../size-field/size-field.component.css | 7 + .../size-field/size-field.component.html | 40 ++++ .../size-field/size-field.component.spec.ts | 23 ++ .../fields/size-field/size-field.component.ts | 206 +++++++++++++++++ .../string-field/string-field.component.css | 0 .../string-field/string-field.component.html | 6 + .../string-field.component.spec.ts | 23 ++ .../string-field/string-field.component.ts | 31 +++ .../abstract-properties.component.ts | 47 +++- .../bloc-for-properties.component.html | 13 +- .../bloc-for-properties.component.ts | 27 +-- .../button-properties.component.html | 38 +--- .../button-properties.component.ts | 65 +----- .../checkbox-properties.component.html | 22 +- .../checkbox-properties.component.ts | 48 +--- .../child-window-properties.component.css | 3 - .../child-window-properties.component.html | 48 +--- .../child-window-properties.component.ts | 107 +-------- ...ollapsing-header-properties.component.html | 17 +- .../collapsing-header-properties.component.ts | 62 +---- .../combo-properties.component.html | 29 +-- .../combo-properties.component.ts | 59 ++--- .../dummy-properties.component.html | 21 +- .../dummy-properties.component.ts | 52 +---- ...input-color-edit-properties.component.html | 42 +--- .../input-color-edit-properties.component.ts | 133 +++-------- .../input-number-properties.component.html | 26 +-- .../input-number-properties.component.ts | 45 ++-- .../menu-item-properties.component.html | 12 +- .../plot-properties.component.html | 46 ++-- .../popup-properties.component.html | 10 +- .../progress-bar-properties.component.html | 14 +- 96 files changed, 1855 insertions(+), 1168 deletions(-) create mode 100644 src/app/models/fields/array.field.ts create mode 100644 src/app/models/fields/bool.field.ts create mode 100644 src/app/models/fields/color.field.ts create mode 100644 src/app/models/fields/enum.field.ts create mode 100644 src/app/models/fields/field.ts create mode 100644 src/app/models/fields/flags.field.ts create mode 100644 src/app/models/fields/float.field.ts create mode 100644 src/app/models/fields/integer.field.ts create mode 100644 src/app/models/fields/number.field.ts create mode 100644 src/app/models/fields/size.field.ts create mode 100644 src/app/models/fields/string.field.ts create mode 100644 src/app/models/object.ts create mode 100644 src/app/pages/editor/fields/abstract-field.component.ts create mode 100644 src/app/pages/editor/fields/array-string-field/array-string-field.component.css create mode 100644 src/app/pages/editor/fields/array-string-field/array-string-field.component.html create mode 100644 src/app/pages/editor/fields/array-string-field/array-string-field.component.spec.ts create mode 100644 src/app/pages/editor/fields/array-string-field/array-string-field.component.ts create mode 100644 src/app/pages/editor/fields/bool-field/bool-field.component.css create mode 100644 src/app/pages/editor/fields/bool-field/bool-field.component.html create mode 100644 src/app/pages/editor/fields/bool-field/bool-field.component.spec.ts create mode 100644 src/app/pages/editor/fields/bool-field/bool-field.component.ts create mode 100644 src/app/pages/editor/fields/color-field/color-field.component.css create mode 100644 src/app/pages/editor/fields/color-field/color-field.component.html create mode 100644 src/app/pages/editor/fields/color-field/color-field.component.spec.ts create mode 100644 src/app/pages/editor/fields/color-field/color-field.component.ts create mode 100644 src/app/pages/editor/fields/enum-field/enum-field.component.css create mode 100644 src/app/pages/editor/fields/enum-field/enum-field.component.html create mode 100644 src/app/pages/editor/fields/enum-field/enum-field.component.spec.ts create mode 100644 src/app/pages/editor/fields/enum-field/enum-field.component.ts create mode 100644 src/app/pages/editor/fields/flags-field/flags-field.component.css create mode 100644 src/app/pages/editor/fields/flags-field/flags-field.component.html create mode 100644 src/app/pages/editor/fields/flags-field/flags-field.component.spec.ts create mode 100644 src/app/pages/editor/fields/flags-field/flags-field.component.ts create mode 100644 src/app/pages/editor/fields/integer-field/integer-field.component.css create mode 100644 src/app/pages/editor/fields/integer-field/integer-field.component.html create mode 100644 src/app/pages/editor/fields/integer-field/integer-field.component.spec.ts create mode 100644 src/app/pages/editor/fields/integer-field/integer-field.component.ts create mode 100644 src/app/pages/editor/fields/size-field/size-field.component.css create mode 100644 src/app/pages/editor/fields/size-field/size-field.component.html create mode 100644 src/app/pages/editor/fields/size-field/size-field.component.spec.ts create mode 100644 src/app/pages/editor/fields/size-field/size-field.component.ts create mode 100644 src/app/pages/editor/fields/string-field/string-field.component.css create mode 100644 src/app/pages/editor/fields/string-field/string-field.component.html create mode 100644 src/app/pages/editor/fields/string-field/string-field.component.spec.ts create mode 100644 src/app/pages/editor/fields/string-field/string-field.component.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 4579add..e49fdd0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - improve `ProgressBar` by showing thumb hint over DOM elements. +- properties of widgets to show `Label` and `Tooltip` first, when present. + +### Added +- relative sizing in widgets: enable `%` button to define size as a percentage + of the parent, or use absolute size in pixels. +- support for relative size in `Dummy` widget. +- feature to set/unset alpha flags and alpha component of the color based on + property `Alpha channel` in widget `InputColorEdit`. [#51]: https://github.com/poirierlouis/FellowImGui/issues/51 diff --git a/src/app/formatters/lua-sol2.formatter.ts b/src/app/formatters/lua-sol2.formatter.ts index 86ed33b..e5f7f7d 100644 --- a/src/app/formatters/lua-sol2.formatter.ts +++ b/src/app/formatters/lua-sol2.formatter.ts @@ -45,6 +45,7 @@ import {FIGTableWidget} from "../models/widgets/table.widget"; import {FIGTableRowWidget} from "../models/widgets/table-row.widget"; import {FIGTableColumnWidget} from "../models/widgets/table-column.widget"; import {FIGMenuBarWidget} from "../models/widgets/menu-bar.widget"; +import {SizeField} from "../models/fields/size.field"; interface InputNumberFormatItem { readonly fn: string; @@ -95,6 +96,22 @@ export class FIGLuaSol2Formatter extends FIGFormatter { return `ImGuiDir.${capitalize(FIGDir[arrow])}`; } + private formatSize(label: string, + type: FIGWidgetType, + size: SizeField): string[] { + const varWidth: string = this.formatVar(`${label} width`, type); + const varHeight: string = this.formatVar(`${label} height`, type); + let width: string = size.value!.width.toString(); + let height: string = size.value!.height.toString(); + + if (size.isPercentage) { + this.append(`local ${varWidth}, ${varHeight} = ImGui.GetContentRegionAvail()`); + width += ` * ${varWidth}`; + height += ` * ${varHeight}`; + } + return [width, height]; + } + protected override formatFlags(flags: number, flagsList: T[], flagsType: any, flagName: string): string { let varFlags: string = ''; @@ -143,24 +160,11 @@ export class FIGLuaSol2Formatter extends FIGFormatter { } protected override formatChildWindow(widget: FIGChildWindowWidget): void { - const isPercentage = (value: number) => value > 0.0 && value <= 1.0; - const varWidth: string = this.formatVar(`${widget.label} width`, widget.type); - const varHeight: string = this.formatVar(`${widget.label} height`, widget.type); const varArgs: string[] = [this.formatString(widget.label)]; - let width: string = widget.size.width.toString(); - let height: string = widget.size.height.toString(); + const varSize: string[] = this.formatSize(widget.label, widget.type, widget.getField('size') as SizeField); - if (isPercentage(widget.size.width) || isPercentage(widget.size.height)) { - this.append(`local ${varWidth}, ${varHeight} = ImGui.GetContentRegionAvail()`); - if (isPercentage(widget.size.width)) { - width += ` * ${varWidth}`; - } - if (isPercentage(widget.size.height)) { - height += ` * ${varHeight}`; - } - } - varArgs.push(width); - varArgs.push(height); + varArgs.push(varSize[0]); + varArgs.push(varSize[1]); varArgs.push(widget.frameBorder.toString()); if (widget.flags !== 0) { varArgs.push(this.formatFlags(widget.flags, FIGWindowWidget.flags, FIGWindowFlags, 'ImGuiWindowFlags')); @@ -316,7 +320,9 @@ export class FIGLuaSol2Formatter extends FIGFormatter { } protected override formatDummy(widget: FIGDummyWidget): void { - this.append(`ImGui.Dummy(${widget.width}, ${widget.height})`); + const varSize: string[] = this.formatSize('dummy', widget.type, widget.getField('size') as SizeField); + + this.append(`ImGui.Dummy(${varSize[0]}, ${varSize[1]})`); this.formatTooltip(widget); } @@ -594,8 +600,8 @@ export class FIGLuaSol2Formatter extends FIGFormatter { const isInteger: boolean = FIGInputNumberWidget.isInteger(widget.dataType); let value: string; - if (size === 0) { - const number: number = widget.value as number; + if (size === 1) { + const number: number = widget.value[0]; value = isInteger ? number.toString() : number.toFixed(precision); } else { diff --git a/src/app/models/fields/array.field.ts b/src/app/models/fields/array.field.ts new file mode 100644 index 0000000..7f418e6 --- /dev/null +++ b/src/app/models/fields/array.field.ts @@ -0,0 +1,11 @@ +import {Field, FieldType} from "./field"; + +export class ArrayField extends Field { + constructor(name: string, + label: string, + value?: unknown[], + isOptional: boolean = false, + defaultValue?: unknown[]) { + super(FieldType.array, name, label, value, isOptional, defaultValue); + } +} diff --git a/src/app/models/fields/bool.field.ts b/src/app/models/fields/bool.field.ts new file mode 100644 index 0000000..506ef92 --- /dev/null +++ b/src/app/models/fields/bool.field.ts @@ -0,0 +1,11 @@ +import {Field, FieldType} from "./field"; + +export class BoolField extends Field { + constructor(name: string, + label: string, + value?: boolean, + isOptional: boolean = false, + defaultValue?: boolean) { + super(FieldType.bool, name, label, value, isOptional, defaultValue); + } +} diff --git a/src/app/models/fields/color.field.ts b/src/app/models/fields/color.field.ts new file mode 100644 index 0000000..ed54d0f --- /dev/null +++ b/src/app/models/fields/color.field.ts @@ -0,0 +1,12 @@ +import {Field, FieldType} from "./field"; +import {Color} from "../math"; + +export class ColorField extends Field { + constructor(name: string, + label: string, + value?: Color, + isOptional: boolean = false, + defaultValue?: Color) { + super(FieldType.color, name, label, value, isOptional, defaultValue); + } +} diff --git a/src/app/models/fields/enum.field.ts b/src/app/models/fields/enum.field.ts new file mode 100644 index 0000000..5fd559f --- /dev/null +++ b/src/app/models/fields/enum.field.ts @@ -0,0 +1,22 @@ +import {Field, FieldType} from "./field"; + +export type EnumFieldType = string | number; + +export interface EnumOption { + readonly value: number; + readonly label: string; +} + +export class EnumField extends Field { + public readonly options: EnumOption[]; + + constructor(name: string, + label: string, + options: EnumOption[], + value?: T, + isOptional: boolean = false, + defaultValue?: T) { + super(FieldType.enum, name, label, value, isOptional, defaultValue); + this.options = options; + } +} diff --git a/src/app/models/fields/field.ts b/src/app/models/fields/field.ts new file mode 100644 index 0000000..6a0061b --- /dev/null +++ b/src/app/models/fields/field.ts @@ -0,0 +1,65 @@ +export enum FieldType { + bool, + integer, + float, + number, + string, + array, + size, + flags, + color, + enum, +} + +export type FieldCallback = (value: any) => void; + +export class Field { + readonly type: FieldType; + readonly name: string; + readonly label: string; + readonly isOptional: boolean; + + value?: T; + defaultValue?: T; + + private readonly listeners: FieldCallback[]; + + protected constructor(type: FieldType, + name: string, + label: string, + value?: T, + isOptional: boolean = false, + defaultValue?: T) { + this.type = type; + this.name = name; + this.label = label; + this.value = value; + this.isOptional = isOptional; + this.defaultValue = defaultValue; + + this.listeners = []; + } + + get isRequired(): boolean { + return !this.isOptional; + } + + public addListener(fn: FieldCallback): void { + this.listeners.push(fn); + } + + public removeListener(fn: FieldCallback): void { + const index: number = this.listeners.findIndex((listener) => listener === fn); + + if (index !== -1) { + this.listeners.splice(index, 1); + } + } + + public emit(): void { + for (const listener of this.listeners) { + listener(this.value); + } + } + +} diff --git a/src/app/models/fields/flags.field.ts b/src/app/models/fields/flags.field.ts new file mode 100644 index 0000000..22a77e7 --- /dev/null +++ b/src/app/models/fields/flags.field.ts @@ -0,0 +1,50 @@ +import {Field, FieldType} from "./field"; +import {EnumFieldType} from "./enum.field"; + +export interface FlagOption { + readonly value: number; + readonly label: string; +} + +export function getOptions(flags: Record): FlagOption[] { + return Object.keys(flags) + .filter((key: EnumFieldType) => !isNaN(Number(key))) + .map((key: EnumFieldType) => { + return { + value: Number.parseInt(key as string), + label: flags[key] + } as FlagOption; + }); +} + +export class FlagsField extends Field { + readonly options: FlagOption[]; + + constructor(name: string, + label: string, + options: FlagOption[], + value?: number, + isOptional: boolean = false, + defaultValue?: number) { + super(FieldType.flags, name, label, value, isOptional, defaultValue); + this.options = options; + } + + public disable(mask: number): void { + if (this.value === undefined) { + return; + } + if ((this.value & mask) === mask) { + this.value ^= mask; + } + this.emit(); + } + + public enable(mask: number): void { + if (this.value === undefined) { + return; + } + this.value |= mask; + this.emit(); + } +} diff --git a/src/app/models/fields/float.field.ts b/src/app/models/fields/float.field.ts new file mode 100644 index 0000000..b6e0830 --- /dev/null +++ b/src/app/models/fields/float.field.ts @@ -0,0 +1,11 @@ +import {Field, FieldType} from "./field"; + +export class FloatField extends Field { + constructor(name: string, + label: string, + value?: number, + isOptional: boolean = false, + defaultValue?: number) { + super(FieldType.float, name, label, value, isOptional, defaultValue); + } +} diff --git a/src/app/models/fields/integer.field.ts b/src/app/models/fields/integer.field.ts new file mode 100644 index 0000000..bd8fb87 --- /dev/null +++ b/src/app/models/fields/integer.field.ts @@ -0,0 +1,11 @@ +import {Field, FieldType} from "./field"; + +export class IntegerField extends Field { + constructor(name: string, + label: string, + value?: number, + isOptional: boolean = false, + defaultValue?: number) { + super(FieldType.integer, name, label, value, isOptional, defaultValue); + } +} diff --git a/src/app/models/fields/number.field.ts b/src/app/models/fields/number.field.ts new file mode 100644 index 0000000..31386a6 --- /dev/null +++ b/src/app/models/fields/number.field.ts @@ -0,0 +1,11 @@ +import {Field, FieldType} from "./field"; + +export class NumberField extends Field { + constructor(name: string, + label: string, + value?: number, + isOptional: boolean = false, + defaultValue?: number) { + super(FieldType.number, name, label, value, isOptional, defaultValue); + } +} diff --git a/src/app/models/fields/size.field.ts b/src/app/models/fields/size.field.ts new file mode 100644 index 0000000..18aa236 --- /dev/null +++ b/src/app/models/fields/size.field.ts @@ -0,0 +1,24 @@ +import {Field, FieldType} from "./field"; +import {Size} from "../math"; + +export class SizeField extends Field { + readonly acceptRelative: boolean; + + constructor(name: string, + label: string, + acceptRelative: boolean = false, + value?: Size, + isOptional: boolean = false, + defaultValue?: Size) { + super(FieldType.size, name, label, value, isOptional, defaultValue); + this.acceptRelative = acceptRelative; + } + + public get isPercentage(): boolean { + if (!this.value) { + return false; + } + return (this.value.width > 0.0 && this.value.width <= 1.0) || + (this.value.height > 0.0 && this.value.height <= 1.0); + } +} diff --git a/src/app/models/fields/string.field.ts b/src/app/models/fields/string.field.ts new file mode 100644 index 0000000..3b13d86 --- /dev/null +++ b/src/app/models/fields/string.field.ts @@ -0,0 +1,11 @@ +import {Field, FieldType} from "./field"; + +export class StringField extends Field { + constructor(name: string, + label: string, + value?: string, + isOptional: boolean = false, + defaultValue?: string) { + super(FieldType.string, name, label, value, isOptional, defaultValue); + } +} diff --git a/src/app/models/math.ts b/src/app/models/math.ts index e5e3c48..aac2d9c 100644 --- a/src/app/models/math.ts +++ b/src/app/models/math.ts @@ -12,10 +12,10 @@ export interface Vector4 { w: number; } -export interface Size { +export type Size = Record<'width' | 'height', number> & { width: number; height: number; -} +}; export interface Color { r: number; @@ -43,9 +43,10 @@ export function stringifyHEX(value: Color): string { return `#${r}${g}${b}${a}`; } +const rgbaRule: RegExp = new RegExp(/rgba?\((?[0-9]{1,3}), (?[0-9]{1,3}), (?[0-9]{1,3})(, (?-?([0-9]*[.])?[0-9]+))?\)/); + export function parseRGBA(value: string): Color | undefined { - const rule = new RegExp(/rgba?\((?[0-9]{1,3}), (?[0-9]{1,3}), (?[0-9]{1,3})(, (?-?([0-9]*[.])?[0-9]+))?\)/); - const match = value.match(rule); + const match: RegExpMatchArray | null = value.match(rgbaRule); if (!match) { return undefined; @@ -58,6 +59,22 @@ export function parseRGBA(value: string): Color | undefined { }; } +const hexRule: RegExp = new RegExp(/#(?[0-9A-Fa-f]{2})(?[0-9A-Fa-f]{2})(?[0-9A-Fa-f]{2})(?[0-9A-Fa-f]{2})?/); + +export function parseHEX(value: string): Color | undefined { + const match: RegExpMatchArray | null = value.match(hexRule); + + if (!match) { + return undefined; + } + return { + r: parseInt(match.groups!['r'], 16) / 255.0, + g: parseInt(match.groups!['g'], 16) / 255.0, + b: parseInt(match.groups!['b'], 16) / 255.0, + a: parseInt(match.groups!['a'], 16) / 255.0, + }; +} + export function plotSin(size: number): number[] { const data: number[] = []; @@ -66,3 +83,7 @@ export function plotSin(size: number): number[] { } return data; } + +export function isFloat(value: number | null): boolean { + return (value === null) ? false : value % 1 !== 0; +} diff --git a/src/app/models/object.ts b/src/app/models/object.ts new file mode 100644 index 0000000..01ce4a0 --- /dev/null +++ b/src/app/models/object.ts @@ -0,0 +1,8 @@ +export function hasFunction(obj: object | null, fnName: string): boolean { + while ((obj = Reflect.getPrototypeOf(obj as object)) !== null) { + if (Reflect.ownKeys(obj).find((key) => key === fnName)) { + return true; + } + } + return false; +} diff --git a/src/app/models/widgets/bloc-for.widget.ts b/src/app/models/widgets/bloc-for.widget.ts index 7854904..5a5b9ea 100644 --- a/src/app/models/widgets/bloc-for.widget.ts +++ b/src/app/models/widgets/bloc-for.widget.ts @@ -11,11 +11,11 @@ export class FIGBlocForWidget extends FIGContainer { {name: 'size', optional: true, default: 10} ]; - size: number; + size: number = 10; constructor(options?: FIGBlocForOptions) { super(FIGWidgetType.blocFor, true); - this.size = options?.size ?? 10; + this.registerInteger('size', 'Size', options?.size, true, 10); this._focusOffset.y = 0; } diff --git a/src/app/models/widgets/button.widget.ts b/src/app/models/widgets/button.widget.ts index ac0f680..9ab1a46 100644 --- a/src/app/models/widgets/button.widget.ts +++ b/src/app/models/widgets/button.widget.ts @@ -2,6 +2,7 @@ import {FIGWidgetType} from "./widget"; import {FIGTooltipOption, FIGWithTooltip} from "./with-tooltip.widget"; import {Vector2} from "../math"; import {FIGSerializeProperty} from "../../parsers/document.parser"; +import {EnumOption} from "../fields/enum.field"; export enum FIGDir { left, @@ -11,6 +12,14 @@ export enum FIGDir { none = -1 } +export const FIGDirOptions: EnumOption[] = [ + {value: FIGDir.none, label: 'None'}, + {value: FIGDir.left, label: 'Left'}, + {value: FIGDir.right, label: 'Right'}, + {value: FIGDir.up, label: 'Up'}, + {value: FIGDir.down, label: 'Down'}, +]; + export interface FIGButtonOptions extends FIGTooltipOption { readonly label?: string; readonly isFill?: boolean; @@ -27,18 +36,18 @@ export class FIGButtonWidget extends FIGWithTooltip { {name: 'tooltip', optional: true, default: undefined}, ]; - label: string; - isFill: boolean; - isSmall: boolean; - arrow: FIGDir; + label: string = 'Button'; + isFill: boolean = false; + isSmall: boolean = false; + arrow: FIGDir = FIGDir.none; constructor(options?: FIGButtonOptions) { super(FIGWidgetType.button, true); - this.label = options?.label ?? 'Button'; - this.isFill = options?.isFill ?? false; - this.isSmall = options?.isSmall ?? false; - this.arrow = options?.arrow ?? FIGDir.none; - this.tooltip = options?.tooltip; + this.registerString('label', 'Label', options?.label ?? 'Button'); + this.registerString('tooltip', 'Tooltip', options?.tooltip, true); + this.registerBool('isFill', 'Fill', options?.isFill, true, false); + this.registerBool('isSmall', 'Small', options?.isSmall, true, false); + this.registerEnum('arrow', 'Arrow', FIGDirOptions, options?.arrow, true, FIGDir.none); } public get name(): string { diff --git a/src/app/models/widgets/checkbox.widget.ts b/src/app/models/widgets/checkbox.widget.ts index 2431f39..5967a5e 100644 --- a/src/app/models/widgets/checkbox.widget.ts +++ b/src/app/models/widgets/checkbox.widget.ts @@ -14,14 +14,14 @@ export class FIGCheckboxWidget extends FIGWithTooltip { {name: 'tooltip', optional: true, default: undefined}, ]; - label: string; - isChecked: boolean; + label: string = 'Checkbox'; + isChecked: boolean = false; constructor(options?: FIGCheckboxOptions) { super(FIGWidgetType.checkbox, true); - this.label = options?.label ?? 'Checkbox'; - this.isChecked = options?.isChecked ?? false; - this.tooltip = options?.tooltip; + this.registerString('label', 'Label', options?.label ?? 'Checkbox'); + this.registerString('tooltip', 'Tooltip', options?.tooltip, true); + this.registerBool('isChecked', 'Checked', options?.isChecked, true, false); } public get name(): string { diff --git a/src/app/models/widgets/child-window.widget.ts b/src/app/models/widgets/child-window.widget.ts index 698f4b9..3ace759 100644 --- a/src/app/models/widgets/child-window.widget.ts +++ b/src/app/models/widgets/child-window.widget.ts @@ -2,8 +2,10 @@ import {FIGContainer} from "./container"; import {FIGWidgetType} from "./widget"; import {Size, Vector2} from "../math"; import {getEnumValues} from "../enum"; -import {FIGWindowFlags} from "./window.widget"; +import {FIGWindowFlags, FIGWindowFlagsOptions} from "./window.widget"; import {FIGSerializeProperty} from "../../parsers/document.parser"; +import {FIGWidgetHelper} from "./widget.helper"; +import {SizeField} from "../fields/size.field"; export interface FIGChildWindowOptions { readonly label?: string; @@ -21,17 +23,17 @@ export class FIGChildWindowWidget extends FIGContainer { {name: 'flags', optional: true, default: 0}, ]; - label: string; - size: Size; - frameBorder: boolean; - flags: number; + label: string = 'Child Window'; + size: Size = {width: 0, height: 0}; + frameBorder: boolean = true; + flags: number = 0; constructor(options?: FIGChildWindowOptions) { super(FIGWidgetType.childWindow, true); - this.label = options?.label ?? 'Child Window'; - this.size = options?.size ?? {width: 0, height: 0}; - this.frameBorder = options?.frameBorder ?? true; - this.flags = options?.flags ?? 0; + this.registerString('label', 'Label', options?.label ?? 'Child Window'); + this.registerSize('size', 'Size', true, options?.size, true, {width: 0, height: 0}); + this.registerBool('frameBorder', 'Show frame border', options?.frameBorder, true, true); + this.registerFlags('flags', 'Flags', FIGWindowFlagsOptions, options?.flags, true, 0); } public get name(): string { @@ -39,15 +41,8 @@ export class FIGChildWindowWidget extends FIGContainer { } public override draw(): void { - const size: Vector2 = {x: this.size.width, y: this.size.height}; - const region: Vector2 = ImGui.GetContentRegionAvail(); + const size: Vector2 | undefined = FIGWidgetHelper.computeSize(this.getField('size') as SizeField); - if (size.x > 0.0 && size.x <= 1.0) { - size.x *= region.x; - } - if (size.y > 0.0 && size.y <= 1.0) { - size.y *= region.y; - } if (ImGui.BeginChild(this.label, size, this.frameBorder, this.flags)) { for (const child of this.children) { child.draw(); diff --git a/src/app/models/widgets/collapsing-header.widget.ts b/src/app/models/widgets/collapsing-header.widget.ts index d4f2c22..429d2dd 100644 --- a/src/app/models/widgets/collapsing-header.widget.ts +++ b/src/app/models/widgets/collapsing-header.widget.ts @@ -1,6 +1,7 @@ import {FIGWidgetType} from "./widget"; import {FIGContainer} from "./container"; import {FIGSerializeProperty} from "../../parsers/document.parser"; +import {FIGTreeNodeFlagsOptions} from "./tree-node.widget"; export interface FIGCollapsingHeaderOptions { readonly label?: string; @@ -13,13 +14,13 @@ export class FIGCollapsingHeaderWidget extends FIGContainer { {name: 'flags', optional: true, default: 0}, ]; - label: string; - flags: number; + label: string = 'Header'; + flags: number = 0; constructor(options?: FIGCollapsingHeaderOptions) { super(FIGWidgetType.collapsingHeader, true); - this.label = options?.label ?? 'Header'; - this.flags = options?.flags ?? 0; + this.registerString('label', 'Label', options?.label ?? 'Header'); + this.registerFlags('flags', 'Flags', FIGTreeNodeFlagsOptions, options?.flags, true, 0); this._focusOffset.x = 0; } diff --git a/src/app/models/widgets/combo.widget.ts b/src/app/models/widgets/combo.widget.ts index 754b256..da11bea 100644 --- a/src/app/models/widgets/combo.widget.ts +++ b/src/app/models/widgets/combo.widget.ts @@ -14,17 +14,17 @@ export class FIGComboWidget extends FIGWithTooltip { {name: 'tooltip', optional: true, default: undefined} ]; - label: string; - readonly items: string[]; + label: string = 'Combo'; + items: string[] = []; - selectedItem: number; + selectedItem: number = 0; constructor(options?: FIGComboOptions) { super(FIGWidgetType.combo, true); - this.label = options?.label ?? 'Combo'; - this.items = options?.items ?? []; - this.selectedItem = 0; - this.tooltip = options?.tooltip; + this.registerString('label', 'Label', options?.label ?? 'Combo'); + this.registerString('tooltip', 'Tooltip', options?.tooltip, true); + this.registerArray('items', 'List of items', options?.items, true, []); + this.registerInteger('selectedItem', 'Selected item', 0, true, 0); } public get name(): string { diff --git a/src/app/models/widgets/dummy.widget.ts b/src/app/models/widgets/dummy.widget.ts index 91c6783..039b93c 100644 --- a/src/app/models/widgets/dummy.widget.ts +++ b/src/app/models/widgets/dummy.widget.ts @@ -1,12 +1,17 @@ import {FIGWidgetType} from "./widget"; import {FIGTooltipOption, FIGWithTooltip} from "./with-tooltip.widget"; import {FIGSerializeProperty} from "../../parsers/document.parser"; +import {Size, Vector2} from "../math"; +import {FIGWidgetHelper} from "./widget.helper"; +import {SizeField} from "../fields/size.field"; export interface FIGDummyOptions extends FIGTooltipOption { readonly width?: number; readonly height?: number; + readonly size?: Size; } +// TODO: fix serialization, from width/height to size export class FIGDummyWidget extends FIGWithTooltip { public static readonly serializers: FIGSerializeProperty[] = [ {name: 'width', optional: true, default: 100}, @@ -14,20 +19,25 @@ export class FIGDummyWidget extends FIGWithTooltip { {name: 'tooltip', optional: true, default: undefined} ]; - width: number; - height: number; + public readonly name = 'Dummy'; + + size: Size = {width: 100, height: 100}; constructor(options?: FIGDummyOptions) { super(FIGWidgetType.dummy, true); - this.width = options?.width ?? 100; - this.height = options?.height ?? 100; - this.tooltip = options?.tooltip; - } + let size: Size | undefined = options?.size; - public readonly name = 'Dummy'; + if (options?.width !== undefined && options?.height !== undefined) { + size = {width: options.width, height: options.height}; + } + this.registerString('tooltip', 'Tooltip', options?.tooltip, true); + this.registerSize('size', 'Size', true, size, true, {width: 100, height: 100}); + } public override draw(): void { - ImGui.Dummy({x: this.width, y: this.height}); + const size: Vector2 | undefined = FIGWidgetHelper.computeSize(this.getField('size') as SizeField); + + ImGui.Dummy(size); this.drawTooltip(); this.drawFocus(); this.scrollTo(); diff --git a/src/app/models/widgets/input-color-edit.widget.ts b/src/app/models/widgets/input-color-edit.widget.ts index d32f95f..ca2a7da 100644 --- a/src/app/models/widgets/input-color-edit.widget.ts +++ b/src/app/models/widgets/input-color-edit.widget.ts @@ -3,6 +3,7 @@ import {FIGTooltipOption, FIGWithTooltip} from "./with-tooltip.widget"; import {Color} from "../math"; import {FIGSerializeProperty} from "../../parsers/document.parser"; import {getEnumValues} from "../enum"; +import {FlagOption, getOptions} from "../fields/flags.field"; export enum FIGInputColorEditFlags { NoAlpha = 2, @@ -36,6 +37,8 @@ export enum FIGInputColorEditMasks { InputMask_ = 402653184 } +export const FIGInputColorEditFlagsOptions: FlagOption[] = getOptions(FIGInputColorEditFlags); + export interface FIGInputColorEditOptions extends FIGTooltipOption { readonly label?: string; readonly color?: Color; @@ -53,18 +56,18 @@ export class FIGInputColorEditWidget extends FIGWithTooltip { {name: 'flags', optional: true, default: 0} ]; - label: string; - color: Color; - withAlpha: boolean; - flags: number; + label: string = 'Input Color Edit'; + color: Color = {r: 0.5, g: 0.5, b: 0.5, a: 1.0}; + withAlpha: boolean = false; + flags: number = 0; constructor(options?: FIGInputColorEditOptions) { super(FIGWidgetType.inputColorEdit, true); - this.label = options?.label ?? 'Input Color Edit'; - this.color = options?.color ?? {r: 0.5, g: 0.5, b: 0.5, a: 0.5}; - this.withAlpha = options?.withAlpha ?? false; - this.tooltip = options?.tooltip; - this.flags = options?.flags ?? 0; + this.registerString('label', 'Label', options?.label ?? 'Input Color Edit'); + this.registerString('tooltip', 'Tooltip', options?.tooltip, true); + this.registerColor('color', 'Color', options?.color, true, {r: 0.5, g: 0.5, b: 0.5, a: 1.0}); + this.registerBool('withAlpha', 'Alpha channel', options?.withAlpha, true, false); + this.registerFlags('flags', 'Flags', FIGInputColorEditFlagsOptions, options?.flags, true, 0); } public get name(): string { @@ -81,10 +84,7 @@ export class FIGInputColorEditWidget extends FIGWithTooltip { ImGui.ColorEdit4(this.label, values, this.flags); } if (this.diffColor(prevValues, values)) { - this.color.r = values[0]; - this.color.g = values[1]; - this.color.b = values[2]; - this.color.a = values[3]; + this.color = {r: values[0], g: values[1], b: values[2], a: values[3]}; this.triggerUpdate(); } this.drawTooltip(); diff --git a/src/app/models/widgets/input-number.widget.ts b/src/app/models/widgets/input-number.widget.ts index 36278ee..c325200 100644 --- a/src/app/models/widgets/input-number.widget.ts +++ b/src/app/models/widgets/input-number.widget.ts @@ -2,6 +2,7 @@ import {FIGWidgetType} from "./widget"; import {FIGTooltipOption, FIGWithTooltip} from "./with-tooltip.widget"; import {getPrecision} from "../string"; import {FIGSerializeProperty} from "../../parsers/document.parser"; +import {EnumOption} from "../fields/enum.field"; export enum FIGInputNumberType { int, @@ -17,9 +18,22 @@ export enum FIGInputNumberType { double } +export const FIGInputNumberTypeOptions: EnumOption[] = [ + {value: FIGInputNumberType.int, label: 'Int'}, + {value: FIGInputNumberType.int2, label: 'Int2'}, + {value: FIGInputNumberType.int3, label: 'Int3'}, + {value: FIGInputNumberType.int4, label: 'Int4'}, + {value: FIGInputNumberType.float, label: 'Float'}, + {value: FIGInputNumberType.float2, label: 'Float2'}, + {value: FIGInputNumberType.float3, label: 'Float3'}, + {value: FIGInputNumberType.float4, label: 'Float4'}, + {value: FIGInputNumberType.double, label: 'Double'}, +]; + + export interface FIGInputNumberOptions extends FIGTooltipOption { readonly label?: string; - readonly value?: number | number[]; + readonly value?: number[]; readonly step?: number; readonly stepFast?: number; readonly format?: string; @@ -27,8 +41,8 @@ export interface FIGInputNumberOptions extends FIGTooltipOption { } interface DrawItem { - readonly fn: (...args: any[]) => any; - readonly args: (self: FIGInputNumberWidget) => any[]; + readonly fn: (...args: unknown[]) => unknown; + readonly args: (self: FIGInputNumberWidget) => unknown[]; } export class FIGInputNumberWidget extends FIGWithTooltip { @@ -45,7 +59,7 @@ export class FIGInputNumberWidget extends FIGWithTooltip { private static readonly drawers: DrawItem[] = [ { fn: ImGui.InputInt, - args: (self: FIGInputNumberWidget) => [(_ = self.value) => self.value = _, self.step, self.stepFast] + args: (self: FIGInputNumberWidget) => [(_ = self.value[0]) => self.value[0] = _, self.step, self.stepFast] }, {fn: ImGui.InputInt2, args: (self: FIGInputNumberWidget) => [self.value]}, {fn: ImGui.InputInt3, args: (self: FIGInputNumberWidget) => [self.value]}, @@ -53,7 +67,7 @@ export class FIGInputNumberWidget extends FIGWithTooltip { { fn: ImGui.InputFloat, - args: (self: FIGInputNumberWidget) => [(_ = self.value) => self.value = _, self.step, self.stepFast, self.format] + args: (self: FIGInputNumberWidget) => [(_ = self.value[0]) => self.value[0] = _, self.step, self.stepFast, self.format] }, {fn: ImGui.InputFloat2, args: (self: FIGInputNumberWidget) => [self.value, self.format]}, {fn: ImGui.InputFloat3, args: (self: FIGInputNumberWidget) => [self.value, self.format]}, @@ -61,26 +75,26 @@ export class FIGInputNumberWidget extends FIGWithTooltip { { fn: ImGui.InputDouble, - args: (self: FIGInputNumberWidget) => [(_ = self.value) => self.value = _, self.step, self.stepFast, self.format] + args: (self: FIGInputNumberWidget) => [(_ = self.value[0]) => self.value[0] = _, self.step, self.stepFast, self.format] } ]; - label: string; - dataType: FIGInputNumberType; - value: number | number[]; - step: number; - stepFast: number; - format: string; + label: string = 'Input Number'; + dataType: FIGInputNumberType = FIGInputNumberType.int; + value: number[] = [0]; + step: number = 1; + stepFast: number = 10; + format: string = '%.3f'; constructor(options?: FIGInputNumberOptions) { super(FIGWidgetType.inputNumber, true); - this.label = options?.label ?? 'Input Number'; - this.dataType = options?.dataType ?? FIGInputNumberType.int; - this.value = options?.value ?? 0; - this.step = options?.step ?? (FIGInputNumberWidget.isInteger(this.dataType) ? 1 : 0.01); - this.stepFast = options?.stepFast ?? (FIGInputNumberWidget.isInteger(this.dataType) ? 10 : 1); - this.format = options?.format ?? (this.dataType === FIGInputNumberType.double ? '%.8f' : '%.3f'); - this.tooltip = options?.tooltip; + this.registerString('label', 'Label', options?.label ?? 'Input Number'); + this.registerString('tooltip', 'Tooltip', options?.tooltip, true); + this.registerEnum('dataType', 'Data type', FIGInputNumberTypeOptions, options?.dataType, true, FIGInputNumberType.int); + this.registerArray('value', 'Value', options?.value, true, [0]); + this.registerNumber('step', 'Step', options?.step, true, (FIGInputNumberWidget.isInteger(this.dataType) ? 1 : 0.01)); + this.registerNumber('stepFast', 'Step fast', options?.stepFast, true, (FIGInputNumberWidget.isInteger(this.dataType) ? 10 : 1)); + this.registerString('format', 'Format', options?.format, true, (this.dataType === FIGInputNumberType.double ? '%.8f' : '%.3f')); } public get name(): string { @@ -104,7 +118,7 @@ export class FIGInputNumberWidget extends FIGWithTooltip { } else if (dataType === FIGInputNumberType.int4 || dataType === FIGInputNumberType.float4) { return 4; } - return 0; + return 1; } public static getPrecision(widget: FIGInputNumberWidget): number | undefined { @@ -115,9 +129,9 @@ export class FIGInputNumberWidget extends FIGWithTooltip { } public override draw(): void { - const prevValue: number | number[] = (this.value instanceof Array) ? [...this.value] : this.value; + const prevValue: number | number[] = [...this.value]; const drawer: DrawItem = FIGInputNumberWidget.drawers[this.dataType]; - const args: any[] = [this.label]; + const args: unknown[] = [this.label]; args.push(...drawer.args(this)); drawer.fn(...args); diff --git a/src/app/models/widgets/input-text.widget.ts b/src/app/models/widgets/input-text.widget.ts index 944cfe4..5ff16ff 100644 --- a/src/app/models/widgets/input-text.widget.ts +++ b/src/app/models/widgets/input-text.widget.ts @@ -2,6 +2,7 @@ import {FIGWidgetType} from "./widget"; import {FIGTooltipOption, FIGWithTooltip} from "./with-tooltip.widget"; import {getEnumValues} from "../enum"; import {FIGSerializeProperty} from "../../parsers/document.parser"; +import {FlagOption, getOptions} from "../fields/flags.field"; export enum FIGInputTextFlags { CharsDecimal = 1, @@ -27,6 +28,8 @@ export enum FIGInputTextFlags { EscapeClearsAll = 1048576 } +export const FIGInputTextFlagsOptions: FlagOption[] = getOptions(FIGInputTextFlags); + export interface FIGInputTextOptions extends FIGTooltipOption { readonly label?: string; readonly value?: string; @@ -46,20 +49,20 @@ export class FIGInputTextWidget extends FIGWithTooltip { {name: 'flags', optional: true, default: 0} ]; - label: string; - value: string; + label: string = 'Text'; + value: string = ''; hint?: string; - bufferSize: number; - flags: number; + bufferSize: number = 256; + flags: number = 0; constructor(options?: FIGInputTextOptions) { super(FIGWidgetType.inputText, true); - this.label = options?.label ?? 'Text'; - this.value = options?.value ?? ''; - this.hint = options?.hint; - this.tooltip = options?.tooltip; - this.bufferSize = options?.bufferSize ?? 256; - this.flags = options?.flags ?? 0; + this.registerString('label', 'Label', options?.label ?? 'Text'); + this.registerString('tooltip', 'Tooltip', options?.tooltip, true); + this.registerString('hint', 'Hint', options?.hint, true); + this.registerString('value', 'Value', options?.value, true, ''); + this.registerInteger('bufferSize', 'Buffer size', options?.bufferSize, true, 256); + this.registerFlags('flags', 'Flags', FIGInputTextFlagsOptions, options?.flags, true, 0); } public get name(): string { diff --git a/src/app/models/widgets/input-textarea.widget.ts b/src/app/models/widgets/input-textarea.widget.ts index 36b3bd0..0d12ac7 100644 --- a/src/app/models/widgets/input-textarea.widget.ts +++ b/src/app/models/widgets/input-textarea.widget.ts @@ -1,6 +1,6 @@ import {FIGWidgetType} from "./widget"; import {FIGTooltipOption, FIGWithTooltip} from "./with-tooltip.widget"; -import {FIGInputTextFlags} from "./input-text.widget"; +import {FIGInputTextFlags, FIGInputTextFlagsOptions} from "./input-text.widget"; import {Vector2} from "../math"; import {FIGSerializeProperty} from "../../parsers/document.parser"; @@ -22,21 +22,21 @@ export class FIGInputTextareaWidget extends FIGWithTooltip { {name: 'flags', optional: true, default: 0} ]; - label: string; - value: string; - linesSize: number; - bufferSize: number; - flags: number; + label: string = '##InputTextMultiline'; + value: string = ''; + linesSize: number = 6; + bufferSize: number = 256; + flags: number = 0; constructor(options?: FIGInputTextareaOptions) { super(FIGWidgetType.inputTextarea, true); - this.label = options?.label ?? '##InputTextMultiline'; - this.value = options?.value ?? ''; - this.tooltip = options?.tooltip; - this.linesSize = options?.linesSize ?? 6; - this.bufferSize = options?.bufferSize ?? 256; + this.registerString('label', 'Label', options?.label ?? '##InputTextMultiline'); + this.registerString('tooltip', 'Tooltip', options?.tooltip, true); + this.registerString('value', 'Value', options?.value, true, ''); + this.registerInteger('linesSize', 'Height in lines', options?.linesSize, true, 6); + this.registerInteger('bufferSize', 'Buffer size', options?.bufferSize, true, 256); + this.registerFlags('flags', 'Flags', FIGInputTextFlagsOptions, options?.flags, true, 0); this.bufferSize = Math.max(this.value.length, this.bufferSize); - this.flags = options?.flags ?? 0; } public get name(): string { diff --git a/src/app/models/widgets/label.widget.ts b/src/app/models/widgets/label.widget.ts index 7454ea1..7b6dcb5 100644 --- a/src/app/models/widgets/label.widget.ts +++ b/src/app/models/widgets/label.widget.ts @@ -14,14 +14,14 @@ export class FIGLabelWidget extends FIGWithTooltip { {name: 'tooltip', optional: true, default: undefined} ]; - label: string; - value: string; + label: string = 'Label'; + value: string = 'Value'; constructor(options?: FIGLabelOptions) { super(FIGWidgetType.label, true); - this.label = options?.label ?? 'Label'; - this.value = options?.value ?? 'Value'; - this.tooltip = options?.tooltip; + this.registerString('label', 'Label', options?.label ?? 'Label'); + this.registerString('tooltip', 'Tooltip', options?.tooltip, true); + this.registerString('value', 'Value', options?.value ?? 'Value'); } public get name(): string { diff --git a/src/app/models/widgets/listbox.widget.ts b/src/app/models/widgets/listbox.widget.ts index 56bcd17..72137e5 100644 --- a/src/app/models/widgets/listbox.widget.ts +++ b/src/app/models/widgets/listbox.widget.ts @@ -16,19 +16,19 @@ export class FIGListBoxWidget extends FIGWithTooltip { {name: 'tooltip', optional: true, default: undefined} ]; - label: string; - readonly items: string[]; - itemsSize: number; + label: string = 'ListBox'; + items: string[] = ['Item 1', 'Item 2', 'Item 3', 'Item 4']; + itemsSize: number = 4; - selectedItem: number; + selectedItem: number = 0; constructor(options?: FIGListBoxOptions) { super(FIGWidgetType.listbox, true); - this.label = options?.label ?? 'ListBox'; - this.items = options?.items ?? ['Item 1', 'Item 2', 'Item 3', 'Item 4']; - this.itemsSize = options?.itemsSize ?? 4; - this.selectedItem = 0; - this.tooltip = options?.tooltip; + this.registerString('label', 'Label', options?.label ?? 'ListBox'); + this.registerString('tooltip', 'Tooltip', options?.tooltip, true); + this.registerArray('items', 'List of items', options?.items, true, ['Item 1', 'Item 2', 'Item 3', 'Item 4']); + this.registerInteger('itemsSize', 'Height in items', options?.itemsSize, true, 4); + //this.registerInteger('selectedItem', 'Selected item', 0, true, 0); } public get name(): string { diff --git a/src/app/models/widgets/menu-item.widget.ts b/src/app/models/widgets/menu-item.widget.ts index 2481bb9..d7e7825 100644 --- a/src/app/models/widgets/menu-item.widget.ts +++ b/src/app/models/widgets/menu-item.widget.ts @@ -18,19 +18,19 @@ export class FIGMenuItemWidget extends FIGWidget { {name: 'enabled', optional: true, default: true} ]; - label: string; + label: string = 'MenuItem'; shortcut?: string; - isSelectable: boolean; - isSelected: boolean; - enabled: boolean; + isSelectable: boolean = false; + isSelected: boolean = false; + enabled: boolean = true; constructor(options?: FIGMenuItemOptions) { super(FIGWidgetType.menuItem, true); - this.label = options?.label ?? 'MenuItem'; - this.shortcut = options?.shortcut; - this.isSelectable = options?.isSelectable ?? false; - this.isSelected = options?.isSelected ?? false; - this.enabled = options?.enabled ?? true; + this.registerString('label', 'Label', options?.label ?? 'MenuItem'); + this.registerString('shortcut', 'Shortcut', options?.shortcut, true); + this.registerBool('enabled', 'Enabled', options?.enabled, true, true); + this.registerBool('isSelectable', 'Is selectable', options?.isSelectable, true, false); + this.registerBool('isSelected', 'Is selected', options?.isSelected, true, false); this._focusOffset.x = 0; this._focusOffset.y = 0; } diff --git a/src/app/models/widgets/menu.widget.ts b/src/app/models/widgets/menu.widget.ts index 5e3715c..065fbc0 100644 --- a/src/app/models/widgets/menu.widget.ts +++ b/src/app/models/widgets/menu.widget.ts @@ -13,13 +13,13 @@ export class FIGMenuWidget extends FIGContainer { {name: 'enabled', optional: true, default: true} ]; - label: string; - enabled: boolean; + label: string = 'Menu'; + enabled: boolean = true; constructor(options?: FIGMenuOptions) { super(FIGWidgetType.menu, true); - this.label = options?.label ?? 'Menu'; - this.enabled = options?.enabled ?? true; + this.registerString('label', 'Label', options?.label ?? 'Menu'); + this.registerBool('enabled', 'Enabled', options?.enabled, true, true); this._focusOffset.x = 0; this._focusOffset.y = 0; } diff --git a/src/app/models/widgets/modal.widget.ts b/src/app/models/widgets/modal.widget.ts index 75defc0..1fbecbc 100644 --- a/src/app/models/widgets/modal.widget.ts +++ b/src/app/models/widgets/modal.widget.ts @@ -1,13 +1,13 @@ import {FIGContainer} from "./container"; import {FIGWidgetType} from "./widget"; import {getEnumValues} from "../enum"; -import {FIGWindowFlags} from "./window.widget"; +import {FIGWindowFlags, FIGWindowFlagsOptions} from "./window.widget"; import {FIGSerializeProperty} from "../../parsers/document.parser"; export interface FIGModalOptions { readonly label?: string; - readonly isOpen?: boolean; readonly flags?: number; + readonly isOpen?: boolean; } export class FIGModalWidget extends FIGContainer { @@ -17,17 +17,17 @@ export class FIGModalWidget extends FIGContainer { {name: 'flags', optional: true, default: 0} ]; - label: string; - isOpen: boolean; - flags: number; + label: string = 'Modal'; + flags: number = 0; + isOpen: boolean; debug: boolean; constructor(options?: FIGModalOptions) { super(FIGWidgetType.modal, true); - this.label = options?.label ?? 'Modal'; + this.registerString('label', 'Label', options?.label ?? 'Modal'); + this.registerFlags('flags', 'flags', FIGWindowFlagsOptions, options?.flags, true, 0); this.isOpen = false; - this.flags = options?.flags ?? 0; this.debug = true; } diff --git a/src/app/models/widgets/plot.widget.ts b/src/app/models/widgets/plot.widget.ts index ba47c67..ad37535 100644 --- a/src/app/models/widgets/plot.widget.ts +++ b/src/app/models/widgets/plot.widget.ts @@ -1,12 +1,18 @@ import {FIGWidget, FIGWidgetType} from "./widget"; import {plotSin, Size, Vector2} from "../math"; import {FIGSerializeProperty} from "../../parsers/document.parser"; +import {EnumOption} from "../fields/enum.field"; export enum FIGPlotType { lines, histogram } +export const FIGPlotTypeOptions: EnumOption[] = [ + {value: FIGPlotType.lines, label: 'Lines'}, + {value: FIGPlotType.histogram, label: 'Histogram'}, +]; + export interface FIGPlotOptions { readonly plotType?: FIGPlotType; readonly label?: string; @@ -37,9 +43,8 @@ export class FIGPlotWidget extends FIGWidget { {name: 'stride', optional: true, default: undefined} ]; - plotType: FIGPlotType; - label: string; - values: number[]; + label: string = ''; + plotType: FIGPlotType = FIGPlotType.lines; valueOffset?: number; overlayText?: string; scaleMin?: number; @@ -47,17 +52,18 @@ export class FIGPlotWidget extends FIGWidget { size?: Size; stride?: number; + values: number[] = plotSin(31); + constructor(options?: FIGPlotOptions) { super(FIGWidgetType.plot, true); - this.plotType = options?.plotType ?? FIGPlotType.lines; - this.label = options?.label ?? 'Lines'; - this.values = options?.values ?? plotSin(31); - this.valueOffset = options?.valueOffset; - this.overlayText = options?.overlayText; - this.scaleMin = options?.scaleMin; - this.scaleMax = options?.scaleMax; - this.size = options?.size; - this.stride = options?.stride; + this.registerString('label', 'Label', options?.label ?? 'Lines'); + this.registerEnum('plotType', 'Plot Type', FIGPlotTypeOptions, options?.plotType, true, FIGPlotType.lines); + this.registerString('overlayText', 'Overlay text', options?.overlayText, true); + this.registerSize('size', 'Size', false, options?.size, true, {width: 0, height: 100}); + this.registerInteger('valueOffset', 'Value offset', options?.valueOffset, true); + this.registerFloat('scaleMin', 'Scale min', options?.scaleMin, true); + this.registerFloat('scaleMax', 'Scale max', options?.scaleMax, true); + this.registerInteger('stride', 'Stride', options?.stride, true); } public get name(): string { @@ -66,22 +72,19 @@ export class FIGPlotWidget extends FIGWidget { public override draw(): void { const size: Vector2 | undefined = this.size ? {x: this.size.width, y: this.size.height} : undefined; + let plotFn: (...args: unknown[]) => void = ImGui.PlotLines; if (this.plotType === FIGPlotType.lines) { - ImGui.PlotLines( - this.label, this.values, this.values.length, - this.valueOffset, this.overlayText, - this.scaleMin, this.scaleMax, - size, this.stride - ); + plotFn = ImGui.PlotLines; } else if (this.plotType === FIGPlotType.histogram) { - ImGui.PlotHistogram( - this.label, this.values, this.values.length, - this.valueOffset, this.overlayText, - this.scaleMin, this.scaleMax, - size, this.stride - ); + plotFn = ImGui.PlotHistogram; } + plotFn( + this.label, this.values, this.values.length, + this.valueOffset, this.overlayText, + this.scaleMin, this.scaleMax, + size, this.stride + ); this.drawFocus(); this.scrollTo(); } diff --git a/src/app/models/widgets/popup.widget.ts b/src/app/models/widgets/popup.widget.ts index a9ff500..71bd4fd 100644 --- a/src/app/models/widgets/popup.widget.ts +++ b/src/app/models/widgets/popup.widget.ts @@ -13,19 +13,21 @@ export class FIGPopupWidget extends FIGContainer { {name: 'contextItem', optional: true, default: false} ]; - label: string; - contextItem: boolean; + label: string = '##Popup'; + contextItem: boolean = false; + isOpen: boolean; - debugLabel: string; debug: boolean; + debugLabel: string; constructor(options?: FIGPopupOptions) { super(FIGWidgetType.popup, true); - this.label = options?.label ?? '##Popup'; - this.contextItem = options?.contextItem ?? false; + this.registerString('label', 'Label', options?.label ?? '##Popup'); + this.registerBool('contextItem', 'Context item (right click)', options?.contextItem, true, false); + this.isOpen = false; - this.debugLabel = `Open '${this.label.slice(2)}'`; this.debug = true; + this.debugLabel = `Open '${this.label.slice(2)}'`; } public get name(): string { diff --git a/src/app/models/widgets/progress-bar.widget.ts b/src/app/models/widgets/progress-bar.widget.ts index cb93044..b0f08dd 100644 --- a/src/app/models/widgets/progress-bar.widget.ts +++ b/src/app/models/widgets/progress-bar.widget.ts @@ -17,16 +17,16 @@ export class FIGProgressBarWidget extends FIGWithTooltip { {name: 'tooltip', optional: true, default: undefined} ]; - value: number; label?: string; - isFill: boolean; + isFill: boolean = false; + value: number = 0; constructor(options?: FIGProgressBarOptions) { super(FIGWidgetType.progressBar, true); - this.value = options?.value ?? 0.0; - this.label = options?.label; - this.isFill = options?.isFill ?? false; - this.tooltip = options?.tooltip; + this.registerString('label', 'Label', options?.label, true); + this.registerString('tooltip', 'Tooltip', options?.tooltip, true); + this.registerBool('isFill', 'Fill', options?.isFill, true, false); + this.registerInteger('value', 'Value', options?.value, true, 0); } public get name(): string { diff --git a/src/app/models/widgets/text.widget.ts b/src/app/models/widgets/text.widget.ts index 89b6901..43b02e0 100644 --- a/src/app/models/widgets/text.widget.ts +++ b/src/app/models/widgets/text.widget.ts @@ -29,22 +29,22 @@ export class FIGTextWidget extends FIGWithTooltip { {name: 'tooltip', optional: true, default: undefined} ]; - text: string; + text: string = 'Text'; color?: Color; - isDisabled: boolean; - isWrapped: boolean; - hasBullet: boolean; - align: boolean; + isDisabled: boolean = false; + isWrapped: boolean = false; + hasBullet: boolean = false; + align: boolean = false; constructor(options?: FIGTextOptions) { super(FIGWidgetType.text, true); - this.text = options?.text ?? 'Text'; - this.color = options?.color; - this.isDisabled = options?.isDisabled ?? false; - this.isWrapped = options?.isWrapped ?? false; - this.hasBullet = options?.hasBullet ?? false; - this.align = options?.align ?? false; - this.tooltip = options?.tooltip; + this.registerString('text', 'Text', options?.text ?? 'Text'); + this.registerString('tooltip', 'Tooltip', options?.tooltip); + this.registerColor('color', 'Color', options?.color); + this.registerBool('isDisabled', 'Disabled', options?.isDisabled ?? false); + this.registerBool('isWrapped', 'Wrapped', options?.isWrapped ?? false); + this.registerBool('hasBullet', 'Bullet', options?.hasBullet ?? false); + this.registerBool('align', 'Align to frame padding', options?.align ?? false); } public get name(): string { diff --git a/src/app/models/widgets/tree-node.widget.ts b/src/app/models/widgets/tree-node.widget.ts index b4102fb..2b863fa 100644 --- a/src/app/models/widgets/tree-node.widget.ts +++ b/src/app/models/widgets/tree-node.widget.ts @@ -2,6 +2,7 @@ import {FIGWidgetType} from "./widget"; import {getEnumValues} from "../enum"; import {FIGContainer} from "./container"; import {FIGSerializeProperty} from "../../parsers/document.parser"; +import {FlagOption, getOptions} from "../fields/flags.field"; export enum FIGTreeNodeFlags { Selected = 1, @@ -20,6 +21,8 @@ export enum FIGTreeNodeFlags { NavLeftJumpsBackHere = 8192 } +export const FIGTreeNodeFlagsOptions: FlagOption[] = getOptions(FIGTreeNodeFlags); + export interface FIGTreeNodeOptions { readonly label?: string; readonly flags?: number; diff --git a/src/app/models/widgets/widget.helper.ts b/src/app/models/widgets/widget.helper.ts index f11614e..af58bb8 100644 --- a/src/app/models/widgets/widget.helper.ts +++ b/src/app/models/widgets/widget.helper.ts @@ -1,210 +1,21 @@ -import {FIGTextOptions, FIGTextWidget} from "./text.widget"; -import {FIGWindowOptions, FIGWindowWidget} from "./window.widget"; -import {FIGWidget} from "./widget"; -import {FIGSeparatorWidget} from "./separator.widget"; -import {FIGButtonOptions, FIGButtonWidget} from "./button.widget"; -import {FIGLabelOptions, FIGLabelWidget} from "./label.widget"; -import {FIGInputTextOptions, FIGInputTextWidget} from "./input-text.widget"; -import {FIGCheckboxOptions, FIGCheckboxWidget} from "./checkbox.widget"; -import {FIGRadioOptions, FIGRadioWidget} from "./radio.widget"; -import {FIGComboOptions, FIGComboWidget} from "./combo.widget"; -import {FIGProgressBarOptions, FIGProgressBarWidget} from "./progress-bar.widget"; -import {FIGInputNumberOptions, FIGInputNumberWidget} from "./input-number.widget"; -import {FIGInputColorEditOptions, FIGInputColorEditWidget} from "./input-color-edit.widget"; -import {FIGCollapsingHeaderOptions, FIGCollapsingHeaderWidget} from "./collapsing-header.widget"; -import {FIGBulletOptions, FIGBulletWidget} from "./bullet.widget"; -import {FIGInputTextareaOptions, FIGInputTextareaWidget} from "./input-textarea.widget"; -import {FIGListBoxOptions, FIGListBoxWidget} from "./listbox.widget"; -import {FIGTabBarOptions, FIGTabBarWidget} from "./tab-bar.widget"; -import {FIGTabItemOptions, FIGTabItemWidget} from "./tab-item.widget"; -import {FIGPlotOptions, FIGPlotType, FIGPlotWidget} from "./plot.widget"; -import {FIGVerticalSliderOptions, FIGVerticalSliderWidget} from "./vertical-slider.widget"; -import {FIGSameLineWidget} from "./same-line.widget"; -import {FIGNewLineWidget} from "./new-line.widget"; -import {FIGSpacingWidget} from "./spacing.widget"; -import {FIGDummyOptions, FIGDummyWidget} from "./dummy.widget"; -import {FIGTreeNodeOptions, FIGTreeNodeWidget} from "./tree-node.widget"; -import {FIGSliderOptions, FIGSliderWidget} from "./slider.widget"; -import {FIGChildWindowOptions, FIGChildWindowWidget} from "./child-window.widget"; -import {FIGSelectableOptions, FIGSelectableWidget} from "./selectable.widget"; -import {FIGGroupWidget} from "./group.widget"; -import {FIGModalOptions, FIGModalWidget} from "./modal.widget"; -import {FIGPopupOptions, FIGPopupWidget} from "./popup.widget"; -import {FIGMenuItemOptions, FIGMenuItemWidget} from "./menu-item.widget"; -import {FIGMenuOptions, FIGMenuWidget} from "./menu.widget"; +import {SizeField} from "../fields/size.field"; +import {Size, Vector2} from "../math"; export class FIGWidgetHelper { - // Layouts - public static createWindow(options?: FIGWindowOptions, - children: FIGWidget[] = []): FIGWindowWidget { - const widget: FIGWindowWidget = new FIGWindowWidget(options); + public static computeSize(field: SizeField): Vector2 | undefined { + if (!field.value) { + return undefined; + } + const size: Size = {...field.value ?? {width: 0, height: 0}}; - widget.children.push(...children); - return widget; - } - - public static createChildWindow(options?: FIGChildWindowOptions, - children: FIGWidget[] = []): FIGChildWindowWidget { - const widget: FIGChildWindowWidget = new FIGChildWindowWidget(options); - - widget.children.push(...children); - return widget; - } - - public static createModal(options?: FIGModalOptions, - children: FIGWidget[] = []): FIGModalWidget { - const widget: FIGModalWidget = new FIGModalWidget(options); - - widget.children.push(...children); - return widget; - } - - public static createCollapsingHeader(options?: FIGCollapsingHeaderOptions, children: FIGWidget[] = []): FIGCollapsingHeaderWidget { - const widget: FIGCollapsingHeaderWidget = new FIGCollapsingHeaderWidget(options); - - widget.children.push(...children); - return widget; - } - - public static createTabBar(options?: FIGTabBarOptions, tabs: FIGTabItemWidget[] = []): FIGTabBarWidget { - const widget: FIGTabBarWidget = new FIGTabBarWidget(options); - - widget.children.push(...tabs); - return widget; - } - - public static createTabItem(options?: FIGTabItemOptions, children: FIGWidget[] = []): FIGTabItemWidget { - const widget: FIGTabItemWidget = new FIGTabItemWidget(options); - - widget.children.push(...children); - return widget; - } - - public static createGroup(children: FIGWidget[] = []): FIGGroupWidget { - const widget: FIGGroupWidget = new FIGGroupWidget(); - - widget.children.push(...children); - return widget; - } - - public static createSameLine(): FIGSameLineWidget { - return new FIGSameLineWidget(); - } - - public static createNewLine(): FIGNewLineWidget { - return new FIGNewLineWidget(); - } - - public static createSpacing(): FIGSpacingWidget { - return new FIGSpacingWidget(); - } - - public static createDummy(options?: FIGDummyOptions): FIGDummyWidget { - return new FIGDummyWidget(options); - } - - public static createSeparator(): FIGSeparatorWidget { - return new FIGSeparatorWidget(); - } - - // Basics - public static createBullet(options?: FIGBulletOptions): FIGBulletWidget { - return new FIGBulletWidget(options); - } - - public static createText(options?: FIGTextOptions): FIGTextWidget { - return new FIGTextWidget(options); - } - - public static createButton(options?: FIGButtonOptions): FIGButtonWidget { - return new FIGButtonWidget(options); - } - - public static createProgressBar(options?: FIGProgressBarOptions): FIGProgressBarWidget { - return new FIGProgressBarWidget(options); - } - - public static createPlotLines(options?: FIGPlotOptions): FIGPlotWidget { - return new FIGPlotWidget({...options, plotType: FIGPlotType.lines}); - } - - public static createPlotHistogram(options?: FIGPlotOptions): FIGPlotWidget { - return new FIGPlotWidget({...options, plotType: FIGPlotType.histogram}); - } - - public static createTreeNode(options?: FIGTreeNodeOptions, children: FIGWidget[] = []): FIGTreeNodeWidget { - const widget: FIGTreeNodeWidget = new FIGTreeNodeWidget(options); - - widget.children.push(...children); - return widget; - } - - public static createSelectable(options?: FIGSelectableOptions): FIGSelectableWidget { - return new FIGSelectableWidget(options); - } - - public static createPopup(options?: FIGPopupOptions, children: FIGWidget[] = []): FIGPopupWidget { - const widget: FIGPopupWidget = new FIGPopupWidget(options); - - widget.children.push(...children); - return widget; - } - - public static createMenu(options?: FIGMenuOptions, children: FIGWidget[] = []): FIGMenuWidget { - const widget: FIGMenuWidget = new FIGMenuWidget(options); - - widget.children.push(...children); - return widget; - } - - public static createMenuItem(options?: FIGMenuItemOptions): FIGMenuItemWidget { - return new FIGMenuItemWidget(options); - } - - // Forms / Inputs - public static createLabel(options?: FIGLabelOptions): FIGLabelWidget { - return new FIGLabelWidget(options); - } - - public static createInputText(options?: FIGInputTextOptions): FIGInputTextWidget { - return new FIGInputTextWidget(options); - } - - public static createInputTextarea(options?: FIGInputTextareaOptions): FIGInputTextareaWidget { - return new FIGInputTextareaWidget(options); - } - - public static createInputNumber(options?: FIGInputNumberOptions): FIGInputNumberWidget { - return new FIGInputNumberWidget(options); - } - - public static createInputColorEdit(options?: FIGInputColorEditOptions): FIGInputColorEditWidget { - return new FIGInputColorEditWidget(options); - } - - public static createListBox(options?: FIGListBoxOptions): FIGListBoxWidget { - return new FIGListBoxWidget(options); - } - - public static createSlider(options?: FIGSliderOptions): FIGSliderWidget { - return new FIGSliderWidget(options); - } - - public static createVerticalSlider(options?: FIGVerticalSliderOptions): FIGVerticalSliderWidget { - return new FIGVerticalSliderWidget(options); - } - - public static createCheckbox(options?: FIGCheckboxOptions): FIGCheckboxWidget { - return new FIGCheckboxWidget(options); - } - - public static createRadio(options?: FIGRadioOptions): FIGRadioWidget { - return new FIGRadioWidget(options); - } + if (field.acceptRelative && field.isPercentage) { + const region: Vector2 = ImGui.GetContentRegionAvail(); - public static createCombo(options?: FIGComboOptions): FIGComboWidget { - return new FIGComboWidget(options); + size.width *= region.x; + size.height *= region.y; + } + return {x: size.width, y: size.height}; } } diff --git a/src/app/models/widgets/widget.ts b/src/app/models/widgets/widget.ts index 40389b1..0bdc7e5 100644 --- a/src/app/models/widgets/widget.ts +++ b/src/app/models/widgets/widget.ts @@ -1,8 +1,19 @@ import {v4 as uuidv4} from "uuid"; import {BehaviorSubject, Observable} from "rxjs"; import {FIGContainer} from "./container"; -import {Vector2} from "../math"; +import {Color, Size, Vector2} from "../math"; import {FIGEvent, FIGEventType} from "../events/event"; +import {Field} from "../fields/field"; +import {StringField} from "../fields/string.field"; +import {SizeField} from "../fields/size.field"; +import {FlagOption, FlagsField} from "../fields/flags.field"; +import {BoolField} from "../fields/bool.field"; +import {ColorField} from "../fields/color.field"; +import {IntegerField} from "../fields/integer.field"; +import {EnumField, EnumFieldType, EnumOption} from "../fields/enum.field"; +import {ArrayField} from "../fields/array.field"; +import {NumberField} from "../fields/number.field"; +import {FloatField} from "../fields/float.field"; export enum FIGWidgetType { // NOTE: order types per category. Manually increment type's value for @@ -55,6 +66,8 @@ export enum FIGWidgetType { blocFor } +type Fields = Record; + export abstract class FIGWidget { public static readonly excludeKeys: string[] = [ 'uuid', 'type', 'needParent', 'parent', 'isFocused', 'children', @@ -73,12 +86,15 @@ export abstract class FIGWidget { protected readonly updateSubject: BehaviorSubject = new BehaviorSubject(undefined); public readonly update$: Observable = this.updateSubject.asObservable(); + protected readonly _focusOffset: Vector2 = {x: 4, y: 4}; private eventSubject?: BehaviorSubject; - protected readonly _focusOffset: Vector2 = {x: 4, y: 4}; private readonly _focusMin: Vector2 = {x: Number.MAX_VALUE, y: Number.MAX_VALUE}; private readonly _focusMax: Vector2 = {x: Number.MIN_VALUE, y: Number.MIN_VALUE}; + private readonly fields: Fields; + private readonly properties: string[]; + private _isSelected: boolean = false; protected constructor(type: FIGWidgetType, @@ -86,8 +102,12 @@ export abstract class FIGWidget { this.uuid = uuidv4(); this.type = type; this.needParent = needParent; + this.fields = {}; + this.properties = []; } + public abstract get name(): string; + public static isContainer(type: FIGWidgetType): boolean { return type === FIGWidgetType.window || type === FIGWidgetType.childWindow || @@ -106,8 +126,6 @@ export abstract class FIGWidget { type === FIGWidgetType.blocFor; } - public abstract get name(): string; - public abstract draw(): void; /** @@ -134,6 +152,10 @@ export abstract class FIGWidget { } + public getField(name: string): Field { + return this.fields[name]; + } + public listen(): void { if (!this.eventSubject) { return; @@ -164,6 +186,10 @@ export abstract class FIGWidget { this._isSelected = true; } + public getFields(): Field[] { + return this.properties.map((name) => this.fields[name]); + } + protected drawFocus(): void { if (this.isFocused) { this.growFocusRect(); @@ -202,4 +228,143 @@ export abstract class FIGWidget { this._focusMax.y = Math.max(this._focusMax.y, max.y); } + protected registerBool(name: string, + label: string, + value?: boolean, + isOptional: boolean = false, + defaultValue?: boolean): void { + if (isOptional) { + value ??= defaultValue; + } + this.fields[name] = new BoolField(name, label, value, isOptional, defaultValue) as Field; + this.registerField(name); + } + + protected registerInteger(name: string, + label: string, + value?: number, + isOptional: boolean = false, + defaultValue?: number): void { + if (isOptional) { + value ??= defaultValue; + } + this.fields[name] = new IntegerField(name, label, value, isOptional, defaultValue) as Field; + this.registerField(name); + } + + protected registerFloat(name: string, + label: string, + value?: number, + isOptional: boolean = false, + defaultValue?: number): void { + if (isOptional) { + value ??= defaultValue; + } + this.fields[name] = new FloatField(name, label, value, isOptional, defaultValue) as Field; + this.registerField(name); + } + + protected registerNumber(name: string, + label: string, + value?: number, + isOptional: boolean = false, + defaultValue?: number): void { + if (isOptional) { + value ??= defaultValue; + } + this.fields[name] = new NumberField(name, label, value, isOptional, defaultValue) as Field; + this.registerField(name); + } + + protected registerString(name: string, + label: string, + value?: string, + isOptional: boolean = false, + defaultValue?: string): void { + if (isOptional) { + value ??= defaultValue; + } + this.fields[name] = new StringField(name, label, value, isOptional, defaultValue) as Field; + this.registerField(name); + } + + protected registerArray(name: string, + label: string, + value?: unknown[], + isOptional: boolean = false, + defaultValue?: unknown[]): void { + if (isOptional) { + value ??= defaultValue; + } + this.fields[name] = new ArrayField(name, label, value, isOptional, defaultValue) as Field; + this.registerField(name); + } + + protected registerSize(name: string, + label: string, + isRelative: boolean = false, + value?: Size, + isOptional: boolean = false, + defaultValue?: Size): void { + if (isOptional) { + value ??= defaultValue; + } + this.fields[name] = new SizeField(name, label, isRelative, value, isOptional, defaultValue) as Field; + this.registerField(name); + } + + protected registerFlags(name: string, + label: string, + options: FlagOption[], + value?: number, + isOptional: boolean = false, + defaultValue: number = 0): void { + if (isOptional) { + value ??= defaultValue; + } + this.fields[name] = new FlagsField(name, label, options, value, isOptional, defaultValue) as Field; + this.registerField(name); + } + + protected registerColor(name: string, + label: string, + value?: Color, + isOptional: boolean = false, + defaultValue?: Color): void { + if (isOptional) { + value ??= defaultValue; + } + this.fields[name] = new ColorField(name, label, value, isOptional, defaultValue) as Field; + this.registerField(name); + } + + protected registerEnum(name: string, + label: string, + options: E | EnumOption[], + value?: EnumFieldType, + isOptional: boolean = false, + defaultValue?: EnumFieldType): void { + if (isOptional) { + value ??= defaultValue; + } + this.fields[name] = new EnumField(name, label, options as EnumOption[], value, isOptional, defaultValue) as Field; + this.registerField(name); + } + + private registerField(name: string): void { + this.properties.push(name); + Object.defineProperty(this, name, { + get: () => this.fields[name].value, + set: (value: never) => { + const prevValue: unknown = this.fields[name].value; + + if (prevValue === value) { + return; + } + this.fields[name].value = value; + this.fields[name].emit(); + }, + }); + } + } diff --git a/src/app/models/widgets/window.widget.ts b/src/app/models/widgets/window.widget.ts index f639dd0..bab6974 100644 --- a/src/app/models/widgets/window.widget.ts +++ b/src/app/models/widgets/window.widget.ts @@ -3,6 +3,7 @@ import {FIGWidgetType} from "./widget"; import {Size} from "../math"; import {getEnumValues} from "../enum"; import {FIGSerializeProperty} from "../../parsers/document.parser"; +import {FlagOption, getOptions} from "../fields/flags.field"; export enum FIGWindowFlags { NoTitleBar = 1, @@ -46,6 +47,9 @@ export enum FIGCondFlags { Appearing = 8 } +export const FIGWindowFlagsOptions: FlagOption[] = getOptions(FIGWindowFlags); +export const FIGWindowCondFlagsOptions: FlagOption[] = getOptions(FIGCondFlags); + export interface FIGWindowOptions { readonly label?: string; readonly size?: Size; @@ -62,22 +66,28 @@ export class FIGWindowWidget extends FIGContainer { {name: 'size', optional: true, default: undefined, type: 'object', innerType: [{name: 'width'}, {name: 'height'}]}, {name: 'flags', optional: true, default: 0}, {name: 'sizeFlags', optional: true, default: 0}, - {name: 'minSize', optional: true, default: undefined, type: 'object', innerType: [{name: 'width'}, {name: 'height'}]} + { + name: 'minSize', + optional: true, + default: undefined, + type: 'object', + innerType: [{name: 'width'}, {name: 'height'}] + } ]; - label: string; + label: string = ''; + flags: number = 0; size?: Size; - flags: number; - sizeFlags: number; minSize?: Size; + sizeFlags: number = 0; constructor(options?: FIGWindowOptions) { super(FIGWidgetType.window, false); - this.label = options?.label ?? 'Window'; - this.size = options?.size; - this.flags = options?.flags ?? 0; - this.sizeFlags = options?.sizeFlags ?? 0; - this.minSize = options?.minSize; + this.registerString( 'label', 'Title', options?.label ?? 'Window'); + this.registerFlags('flags', 'Flags', FIGWindowFlagsOptions, options?.flags, true); + this.registerSize('size', 'Size', false, options?.size, true); + this.registerSize('minSize', 'Min size', false, options?.minSize, true); + this.registerFlags('sizeFlags', 'Size flags', FIGWindowCondFlagsOptions, options?.sizeFlags, true); } public get name(): string { diff --git a/src/app/pages/editor/fields/abstract-field.component.ts b/src/app/pages/editor/fields/abstract-field.component.ts new file mode 100644 index 0000000..b861d25 --- /dev/null +++ b/src/app/pages/editor/fields/abstract-field.component.ts @@ -0,0 +1,80 @@ +import {Component, DestroyRef, EventEmitter, Input, OnInit, Output} from "@angular/core"; +import {FormControl, ValidatorFn, Validators} from "@angular/forms"; +import {Field} from "../../../models/fields/field"; +import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; + +@Component({ + selector: 'fig-abstract-field', + template: '' +}) +export abstract class AbstractFieldComponent implements OnInit { + + @Output() + update: EventEmitter = new EventEmitter(); + + field!: F; + form: FormControl = new FormControl(); + + protected constructor(protected readonly dr: DestroyRef) { + } + + @Input({ + alias: 'field', + transform: (value: Field) => value as F, + required: true + }) + set _field(field: F) { + this.field?.removeListener(this.onFieldChanged.bind(this)); + this.field = field; + this.field.addListener(this.onFieldChanged.bind(this)); + this.form.setValue(this.transformFromField(field.value as FieldType), {emitEvent: false}); + this.form.setValidators(this.getValidators()); + this.form.updateValueAndValidity(); + this.onFieldLoaded(); + } + + public ngOnInit(): void { + this.form.valueChanges.pipe(takeUntilDestroyed(this.dr)).subscribe(this.onFormChanged.bind(this)); + } + + protected transformFromForm(value?: FormType): FieldType { + return value as FieldType; + } + + protected transformFromField(value?: FieldType): FormType { + return value as FormType; + } + + protected onFieldLoaded(): void { + // NOTE: implemented by ...FieldComponent children + } + + protected getValidators(): ValidatorFn[] { + const validators: ValidatorFn[] = []; + + if (this.field.isRequired) { + validators.push(Validators.required); + } + return validators; + } + + protected onFieldChanged(value: FieldType): void { + this.form.setValue(this.transformFromField(value), {emitEvent: false}); + } + + protected onFormChanged(): void { + const prevValue: FieldType = this.field.value as FieldType; + const value: FieldType | undefined = this.transformFromForm(this.form.value); + + if (prevValue === value) { + return; + } + if (this.field.isRequired && value === undefined) { + return; + } + this.field.value = value; + this.field.emit(); + this.update.emit(this.field); + } + +} diff --git a/src/app/pages/editor/fields/array-string-field/array-string-field.component.css b/src/app/pages/editor/fields/array-string-field/array-string-field.component.css new file mode 100644 index 0000000..e69de29 diff --git a/src/app/pages/editor/fields/array-string-field/array-string-field.component.html b/src/app/pages/editor/fields/array-string-field/array-string-field.component.html new file mode 100644 index 0000000..f798341 --- /dev/null +++ b/src/app/pages/editor/fields/array-string-field/array-string-field.component.html @@ -0,0 +1,6 @@ + + {{ field.label }} + + diff --git a/src/app/pages/editor/fields/array-string-field/array-string-field.component.spec.ts b/src/app/pages/editor/fields/array-string-field/array-string-field.component.spec.ts new file mode 100644 index 0000000..c69cb20 --- /dev/null +++ b/src/app/pages/editor/fields/array-string-field/array-string-field.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ArrayStringFieldComponent } from './array-string-field.component'; + +describe('ArrayStringFieldComponent', () => { + let component: ArrayStringFieldComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ArrayStringFieldComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(ArrayStringFieldComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/pages/editor/fields/array-string-field/array-string-field.component.ts b/src/app/pages/editor/fields/array-string-field/array-string-field.component.ts new file mode 100644 index 0000000..08e50dc --- /dev/null +++ b/src/app/pages/editor/fields/array-string-field/array-string-field.component.ts @@ -0,0 +1,38 @@ +import {Component, DestroyRef} from '@angular/core'; +import {ArrayField} from "../../../../models/fields/array.field"; +import {AbstractFieldComponent} from "../abstract-field.component"; +import {MatFormField, MatLabel} from "@angular/material/form-field"; +import {MatInput} from "@angular/material/input"; +import {ReactiveFormsModule} from "@angular/forms"; +import {CdkTextareaAutosize} from "@angular/cdk/text-field"; + +@Component({ + selector: 'fig-array-string-field', + standalone: true, + imports: [ + MatLabel, + MatInput, + MatFormField, + ReactiveFormsModule, + CdkTextareaAutosize + ], + templateUrl: './array-string-field.component.html', + styleUrl: './array-string-field.component.css' +}) +export class ArrayStringFieldComponent extends AbstractFieldComponent { + + constructor(dr: DestroyRef) { + super(dr); + } + + protected override transformFromForm(value?: string): string[] { + value ??= ''; + return value.split('\n'); + } + + protected override transformFromField(value?: string[]): string { + value ??= []; + return value.join('\n'); + } + +} diff --git a/src/app/pages/editor/fields/bool-field/bool-field.component.css b/src/app/pages/editor/fields/bool-field/bool-field.component.css new file mode 100644 index 0000000..e69de29 diff --git a/src/app/pages/editor/fields/bool-field/bool-field.component.html b/src/app/pages/editor/fields/bool-field/bool-field.component.html new file mode 100644 index 0000000..b68eb83 --- /dev/null +++ b/src/app/pages/editor/fields/bool-field/bool-field.component.html @@ -0,0 +1,5 @@ +
+ {{ field.label }} + +
diff --git a/src/app/pages/editor/fields/bool-field/bool-field.component.spec.ts b/src/app/pages/editor/fields/bool-field/bool-field.component.spec.ts new file mode 100644 index 0000000..f405f83 --- /dev/null +++ b/src/app/pages/editor/fields/bool-field/bool-field.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { BoolFieldComponent } from './bool-field.component'; + +describe('BoolFieldComponent', () => { + let component: BoolFieldComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [BoolFieldComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(BoolFieldComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/pages/editor/fields/bool-field/bool-field.component.ts b/src/app/pages/editor/fields/bool-field/bool-field.component.ts new file mode 100644 index 0000000..61d293d --- /dev/null +++ b/src/app/pages/editor/fields/bool-field/bool-field.component.ts @@ -0,0 +1,25 @@ +import {Component, DestroyRef} from '@angular/core'; +import {AbstractFieldComponent} from "../abstract-field.component"; +import {BoolField} from "../../../../models/fields/bool.field"; +import {ReactiveFormsModule} from "@angular/forms"; +import {MatLabel} from "@angular/material/form-field"; +import {MatSlideToggle} from "@angular/material/slide-toggle"; + +@Component({ + selector: 'fig-bool-field', + standalone: true, + imports: [ + MatLabel, + MatSlideToggle, + ReactiveFormsModule + ], + templateUrl: './bool-field.component.html', + styleUrl: './bool-field.component.css' +}) +export class BoolFieldComponent extends AbstractFieldComponent { + + constructor(dr: DestroyRef) { + super(dr); + } + +} diff --git a/src/app/pages/editor/fields/color-field/color-field.component.css b/src/app/pages/editor/fields/color-field/color-field.component.css new file mode 100644 index 0000000..e69de29 diff --git a/src/app/pages/editor/fields/color-field/color-field.component.html b/src/app/pages/editor/fields/color-field/color-field.component.html new file mode 100644 index 0000000..24c1567 --- /dev/null +++ b/src/app/pages/editor/fields/color-field/color-field.component.html @@ -0,0 +1,8 @@ +
+ Color + +
diff --git a/src/app/pages/editor/fields/color-field/color-field.component.spec.ts b/src/app/pages/editor/fields/color-field/color-field.component.spec.ts new file mode 100644 index 0000000..e23a6f4 --- /dev/null +++ b/src/app/pages/editor/fields/color-field/color-field.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ColorFieldComponent } from './color-field.component'; + +describe('ColorFieldComponent', () => { + let component: ColorFieldComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ColorFieldComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(ColorFieldComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/pages/editor/fields/color-field/color-field.component.ts b/src/app/pages/editor/fields/color-field/color-field.component.ts new file mode 100644 index 0000000..28883ca --- /dev/null +++ b/src/app/pages/editor/fields/color-field/color-field.component.ts @@ -0,0 +1,77 @@ +import {Component, DestroyRef, ViewChild} from '@angular/core'; +import {MatLabel} from "@angular/material/form-field"; +import {NgxColorsModule, NgxColorsTriggerDirective} from "ngx-colors"; +import {AbstractFieldComponent} from "../abstract-field.component"; +import {ColorField} from "../../../../models/fields/color.field"; +import {PanelComponent} from "ngx-colors/lib/components/panel/panel.component"; +import {Color, parseHEX, parseRGBA, stringifyHEX, stringifyRGBA} from "../../../../models/math"; +import {ReactiveFormsModule} from "@angular/forms"; + +@Component({ + selector: 'fig-color-field', + standalone: true, + imports: [ + MatLabel, + NgxColorsModule, + ReactiveFormsModule + ], + templateUrl: './color-field.component.html', + styleUrl: './color-field.component.css' +}) +export class ColorFieldComponent extends AbstractFieldComponent { + + private static defaultColor: Color = {r: 0.5, g: 0.5, b: 0.5, a: 1.0}; + + @ViewChild(NgxColorsTriggerDirective) + ngxColor!: NgxColorsTriggerDirective; + + constructor(dr: DestroyRef) { + super(dr); + } + + private get $panel(): PanelComponent | undefined { + return this.ngxColor.panelRef?.instance; + } + + protected override transformFromField(value?: Color): string | undefined { + if (!value) { + return undefined; + } + return stringifyHEX(value); + } + + protected override transformFromForm(value?: string): Color | undefined { + if (!value) { + return ColorFieldComponent.defaultColor; + } + const isRGBA: boolean = value.startsWith('rgba(') || value.startsWith('rgb('); + + if (isRGBA) { + return parseRGBA(value); + } + return parseHEX(value); + } + + protected override onFieldChanged(value: Color | undefined) { + super.onFieldChanged(value); + if (!this.$panel) { + return; + } + if (!value) { + this.$panel.color = ''; + return; + } + this.$panel.color = stringifyRGBA(value); + } + + protected onColorPickerOpened(): void { + if (!this.$panel) { + return; + } + this.$panel.menu = 3; + if (this.$panel.color.length === 0 && this.field.value) { + this.$panel.color = stringifyRGBA(this.field.value); + } + } + +} diff --git a/src/app/pages/editor/fields/enum-field/enum-field.component.css b/src/app/pages/editor/fields/enum-field/enum-field.component.css new file mode 100644 index 0000000..e69de29 diff --git a/src/app/pages/editor/fields/enum-field/enum-field.component.html b/src/app/pages/editor/fields/enum-field/enum-field.component.html new file mode 100644 index 0000000..479e653 --- /dev/null +++ b/src/app/pages/editor/fields/enum-field/enum-field.component.html @@ -0,0 +1,8 @@ + + {{ field.label }} + + @for (option of options; track option.value) { + {{ option.label }} + } + + diff --git a/src/app/pages/editor/fields/enum-field/enum-field.component.spec.ts b/src/app/pages/editor/fields/enum-field/enum-field.component.spec.ts new file mode 100644 index 0000000..231dfc4 --- /dev/null +++ b/src/app/pages/editor/fields/enum-field/enum-field.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { EnumFieldComponent } from './enum-field.component'; + +describe('EnumFieldComponent', () => { + let component: EnumFieldComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [EnumFieldComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(EnumFieldComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/pages/editor/fields/enum-field/enum-field.component.ts b/src/app/pages/editor/fields/enum-field/enum-field.component.ts new file mode 100644 index 0000000..6018421 --- /dev/null +++ b/src/app/pages/editor/fields/enum-field/enum-field.component.ts @@ -0,0 +1,32 @@ +import {Component, DestroyRef} from '@angular/core'; +import {ReactiveFormsModule} from "@angular/forms"; +import {MatFormField, MatLabel} from "@angular/material/form-field"; +import {MatOption, MatSelect} from "@angular/material/select"; +import {AbstractFieldComponent} from "../abstract-field.component"; +import {EnumField, EnumFieldType, EnumOption} from "../../../../models/fields/enum.field"; + +@Component({ + selector: 'fig-enum-field', + standalone: true, + imports: [ + MatLabel, + MatOption, + MatSelect, + MatFormField, + ReactiveFormsModule + ], + templateUrl: './enum-field.component.html', + styleUrl: './enum-field.component.css' +}) +export class EnumFieldComponent extends AbstractFieldComponent, EnumFieldType> { + + public readonly options: EnumOption[] = []; + + constructor(dr: DestroyRef) { + super(dr); + } + + protected override onFieldLoaded() { + this.options.push(...this.field.options); + } +} diff --git a/src/app/pages/editor/fields/flags-field/flags-field.component.css b/src/app/pages/editor/fields/flags-field/flags-field.component.css new file mode 100644 index 0000000..e69de29 diff --git a/src/app/pages/editor/fields/flags-field/flags-field.component.html b/src/app/pages/editor/fields/flags-field/flags-field.component.html new file mode 100644 index 0000000..7c94138 --- /dev/null +++ b/src/app/pages/editor/fields/flags-field/flags-field.component.html @@ -0,0 +1,8 @@ + + {{ field.label }} + + @for (flag of flags; track flag.value) { + {{ flag.label }} + } + + diff --git a/src/app/pages/editor/fields/flags-field/flags-field.component.spec.ts b/src/app/pages/editor/fields/flags-field/flags-field.component.spec.ts new file mode 100644 index 0000000..bba33a1 --- /dev/null +++ b/src/app/pages/editor/fields/flags-field/flags-field.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { FlagsFieldComponent } from './flags-field.component'; + +describe('FlagsFieldComponent', () => { + let component: FlagsFieldComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [FlagsFieldComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(FlagsFieldComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/pages/editor/fields/flags-field/flags-field.component.ts b/src/app/pages/editor/fields/flags-field/flags-field.component.ts new file mode 100644 index 0000000..05f7efe --- /dev/null +++ b/src/app/pages/editor/fields/flags-field/flags-field.component.ts @@ -0,0 +1,63 @@ +import {Component, DestroyRef} from '@angular/core'; +import {AbstractFieldComponent} from "../abstract-field.component"; +import {FlagOption, FlagsField} from "../../../../models/fields/flags.field"; +import {MatFormField, MatLabel} from "@angular/material/form-field"; +import {MatOption, MatSelect} from "@angular/material/select"; +import {ReactiveFormsModule} from "@angular/forms"; + +@Component({ + selector: 'fig-flags-field', + standalone: true, + imports: [ + MatLabel, + MatSelect, + MatOption, + MatFormField, + ReactiveFormsModule + ], + templateUrl: './flags-field.component.html', + styleUrl: './flags-field.component.css' +}) +export class FlagsFieldComponent extends AbstractFieldComponent { + + readonly flags: FlagOption[] = []; + + constructor(dr: DestroyRef) { + super(dr); + } + + protected override transformFromForm(value?: number[]): number { + let flags: number = 0; + + if (value === undefined || value.length === 0) { + return flags; + } + for (const option of this.field.options) { + const isEnabled: boolean = value.includes(option.value); + + if (isEnabled) { + flags |= option.value; + } + } + return flags; + } + + protected override transformFromField(value?: number): number[] { + const flags: number[] = []; + + if (value === undefined) { + return []; + } + for (const option of this.field.options) { + if (((value as number) & option.value) === option.value) { + flags.push(option.value); + } + } + return flags; + } + + protected override onFieldLoaded() { + this.flags.push(...this.field.options); + } + +} diff --git a/src/app/pages/editor/fields/integer-field/integer-field.component.css b/src/app/pages/editor/fields/integer-field/integer-field.component.css new file mode 100644 index 0000000..e69de29 diff --git a/src/app/pages/editor/fields/integer-field/integer-field.component.html b/src/app/pages/editor/fields/integer-field/integer-field.component.html new file mode 100644 index 0000000..3c3e651 --- /dev/null +++ b/src/app/pages/editor/fields/integer-field/integer-field.component.html @@ -0,0 +1,8 @@ + + {{field.label}} + + diff --git a/src/app/pages/editor/fields/integer-field/integer-field.component.spec.ts b/src/app/pages/editor/fields/integer-field/integer-field.component.spec.ts new file mode 100644 index 0000000..1d5fa90 --- /dev/null +++ b/src/app/pages/editor/fields/integer-field/integer-field.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { IntegerFieldComponent } from './integer-field.component'; + +describe('IntegerFieldComponent', () => { + let component: IntegerFieldComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [IntegerFieldComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(IntegerFieldComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/pages/editor/fields/integer-field/integer-field.component.ts b/src/app/pages/editor/fields/integer-field/integer-field.component.ts new file mode 100644 index 0000000..520d4d1 --- /dev/null +++ b/src/app/pages/editor/fields/integer-field/integer-field.component.ts @@ -0,0 +1,39 @@ +import {Component, DestroyRef, Input, numberAttribute} from '@angular/core'; +import {ReactiveFormsModule, ValidatorFn, Validators} from "@angular/forms"; +import {MatFormField, MatLabel} from "@angular/material/form-field"; +import {MatInput} from "@angular/material/input"; +import {AbstractFieldComponent} from "../abstract-field.component"; +import {IntegerField} from "../../../../models/fields/integer.field"; + +@Component({ + selector: 'fig-integer-field', + standalone: true, + imports: [ + MatInput, + MatLabel, + MatFormField, + ReactiveFormsModule + ], + templateUrl: './integer-field.component.html', + styleUrl: './integer-field.component.css' +}) +export class IntegerFieldComponent extends AbstractFieldComponent { + + @Input({required: true, transform: numberAttribute}) + min: number = 0; + + @Input({required: true, transform: numberAttribute}) + step: number = 1; + + constructor(dr: DestroyRef) { + super(dr); + } + + protected override getValidators(): ValidatorFn[] { + return [ + ...super.getValidators(), + Validators.min(this.min), + ]; + } + +} diff --git a/src/app/pages/editor/fields/size-field/size-field.component.css b/src/app/pages/editor/fields/size-field/size-field.component.css new file mode 100644 index 0000000..0730832 --- /dev/null +++ b/src/app/pages/editor/fields/size-field/size-field.component.css @@ -0,0 +1,7 @@ +.row button { + margin-top: 16px; +} + +p { + cursor: pointer; +} diff --git a/src/app/pages/editor/fields/size-field/size-field.component.html b/src/app/pages/editor/fields/size-field/size-field.component.html new file mode 100644 index 0000000..5da8bc6 --- /dev/null +++ b/src/app/pages/editor/fields/size-field/size-field.component.html @@ -0,0 +1,40 @@ +
+ + Width + + + + + Height + + + + @if (acceptRelative) { + + } +
+ +@if (acceptRelative && isRelative && showHelp) { +

+ Define size as a percentage of the parent region.
+ You can fill the parent with a value of 0 (or less). +

+} diff --git a/src/app/pages/editor/fields/size-field/size-field.component.spec.ts b/src/app/pages/editor/fields/size-field/size-field.component.spec.ts new file mode 100644 index 0000000..ff94740 --- /dev/null +++ b/src/app/pages/editor/fields/size-field/size-field.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SizeFieldComponent } from './size-field.component'; + +describe('SizeFieldComponent', () => { + let component: SizeFieldComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [SizeFieldComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(SizeFieldComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/pages/editor/fields/size-field/size-field.component.ts b/src/app/pages/editor/fields/size-field/size-field.component.ts new file mode 100644 index 0000000..4e3bb5a --- /dev/null +++ b/src/app/pages/editor/fields/size-field/size-field.component.ts @@ -0,0 +1,206 @@ +import {Component, DestroyRef, EventEmitter, Input, OnInit, Output} from '@angular/core'; +import {MatFormField, MatLabel, MatSuffix} from "@angular/material/form-field"; +import {MatIcon} from "@angular/material/icon"; +import {MatInput} from "@angular/material/input"; +import {MatIconButton} from "@angular/material/button"; +import {FormControl, ReactiveFormsModule, ValidatorFn, Validators} from "@angular/forms"; +import {SizeField} from "../../../../models/fields/size.field"; +import {MatTooltip} from "@angular/material/tooltip"; +import {isFloat, Size} from "../../../../models/math"; +import {Field} from "../../../../models/fields/field"; +import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; +import {debounceTime} from "rxjs"; + +@Component({ + selector: 'fig-size-field', + standalone: true, + imports: [ + MatIcon, + MatLabel, + MatInput, + MatSuffix, + MatTooltip, + MatFormField, + MatIconButton, + ReactiveFormsModule + ], + templateUrl: './size-field.component.html', + styleUrl: './size-field.component.css' +}) +export class SizeFieldComponent implements OnInit { + + static showHelp: boolean = true; + + @Output() + update: EventEmitter = new EventEmitter(); + + field!: SizeField; + width: FormControl = new FormControl(); + height: FormControl = new FormControl(); + + min: number = -1; + max: number | null = null; + step: number = 1; + + acceptRelative: boolean = false; + isRelative: boolean = false; + + constructor(protected readonly dr: DestroyRef) { + + } + + @Input({ + alias: 'field', + transform: (value: Field) => value as SizeField, + required: true + }) + set _field(field: SizeField) { + this.field?.removeListener(this.onFieldChanged.bind(this)); + this.field = field; + this.field.addListener(this.onFieldChanged.bind(this)); + + this.width.setValue(this.value?.width ?? null, {emitEvent: false}); + this.width.setValidators(this.getValidators()); + this.width.updateValueAndValidity(); + + this.height.setValue(this.value?.height ?? null, {emitEvent: false}); + this.height.setValidators(this.getValidators()); + this.height.updateValueAndValidity(); + + this.onFieldLoaded(); + } + + public get showHelp(): boolean { + return SizeFieldComponent.showHelp; + } + + private get value(): Size | undefined { + return this.field.value; + } + + public ngOnInit(): void { + this.width.valueChanges + .pipe( + debounceTime(300), + takeUntilDestroyed(this.dr) + ) + .subscribe((value) => this.onFormChanged('width', value)); + this.height.valueChanges + .pipe( + debounceTime(300), + takeUntilDestroyed(this.dr) + ) + .subscribe((value) => this.onFormChanged('height', value)); + } + + public toggleRelative(): void { + this.isRelative = !this.isRelative; + if (this.isRelative) { + this.field.value = {width: 0.5, height: 0.5}; + this.width.setValue(this.field.value.width * 100); + this.height.setValue(this.field.value.height * 100); + this.min = 0; + this.max = 100; + this.step = 10; + } else { + this.field.value = this.field.defaultValue ?? {width: 0, height: 0}; + this.width.setValue(this.field.value.width); + this.height.setValue(this.field.value.height); + this.min = -1; + this.max = null; + this.step = 1; + } + this.field.emit(); + } + + public hideHelp(): void { + SizeFieldComponent.showHelp = false; + } + + protected onFieldLoaded(): void { + this.acceptRelative = this.field.acceptRelative; + this.isRelative = false; + this.min = -1; + this.max = null; + this.step = 1; + if (!this.value) { + return; + } + if (!this.field.isPercentage) { + return; + } + this.isRelative = true; + this.min = 0; + this.max = 100; + this.step = 10; + this.width.setValue(this.value.width * 100, {emitEvent: false}); + this.height.setValue(this.value.height * 100, {emitEvent: false}); + } + + protected getValidators(): ValidatorFn[] { + const validators: ValidatorFn[] = []; + + if (this.field.isRequired) { + validators.push(Validators.required); + } + if (this.isRelative) { + validators.push(Validators.min(0)); + validators.push(Validators.max(100)); + } else { + validators.push(Validators.min(-1)); + } + return [ + ...validators, + Validators.pattern('\\d*'), + ]; + } + + protected transform(value: number | null): number | null { + if (value !== null && this.isRelative) { + return value / 100.0; + } + return value; + } + + protected onFieldChanged(value: unknown): void { + let width: number | null = null; + let height: number | null = null; + + if (value) { + width = (value as Size).width; + height = (value as Size).height; + if (this.isRelative) { + width *= 100.0; + height *= 100.0; + } + } + this.width.setValue(width, {emitEvent: false}); + this.height.setValue(height, {emitEvent: false}); + } + + protected onFormChanged(property: 'width' | 'height', value: number | null): void { + if (value !== null && isFloat(value)) { + return; + } + const prevValue: number | undefined = this.value?.[property]; + + value = this.transform(value); + if (prevValue === value) { + return; + } + if (this.field.isRequired && value === null) { + return; + } + if (value === null) { + this.field.value = undefined; + this.field.emit(); + return; + } + if (!this.field.value) { + this.field.value = (this.isRelative) ? {width: 0.5, height: 0.5} : {width: 0, height: 0}; + } + this.field.value[property] = value; + this.field.emit(); + } + +} diff --git a/src/app/pages/editor/fields/string-field/string-field.component.css b/src/app/pages/editor/fields/string-field/string-field.component.css new file mode 100644 index 0000000..e69de29 diff --git a/src/app/pages/editor/fields/string-field/string-field.component.html b/src/app/pages/editor/fields/string-field/string-field.component.html new file mode 100644 index 0000000..446188c --- /dev/null +++ b/src/app/pages/editor/fields/string-field/string-field.component.html @@ -0,0 +1,6 @@ + + {{field.label}} + + diff --git a/src/app/pages/editor/fields/string-field/string-field.component.spec.ts b/src/app/pages/editor/fields/string-field/string-field.component.spec.ts new file mode 100644 index 0000000..965dab4 --- /dev/null +++ b/src/app/pages/editor/fields/string-field/string-field.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { StringFieldComponent } from './string-field.component'; + +describe('StringFieldComponent', () => { + let component: StringFieldComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [StringFieldComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(StringFieldComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/pages/editor/fields/string-field/string-field.component.ts b/src/app/pages/editor/fields/string-field/string-field.component.ts new file mode 100644 index 0000000..7263ae0 --- /dev/null +++ b/src/app/pages/editor/fields/string-field/string-field.component.ts @@ -0,0 +1,31 @@ +import {Component, DestroyRef} from '@angular/core'; +import {MatFormField, MatLabel} from "@angular/material/form-field"; +import {ReactiveFormsModule} from "@angular/forms"; +import {MatInput} from "@angular/material/input"; +import {AbstractFieldComponent} from "../abstract-field.component"; +import {StringField} from "../../../../models/fields/string.field"; + +@Component({ + selector: 'fig-string-field', + standalone: true, + imports: [ + MatInput, + MatLabel, + MatFormField, + ReactiveFormsModule + ], + templateUrl: './string-field.component.html', + styleUrl: './string-field.component.css' +}) +export class StringFieldComponent extends AbstractFieldComponent { + + constructor(dr: DestroyRef) { + super(dr); + } + + protected override transformFromForm(value?: string): string | undefined { + value = value?.trim(); + return value; + } + +} diff --git a/src/app/pages/editor/properties/abstract-properties.component.ts b/src/app/pages/editor/properties/abstract-properties.component.ts index a289496..0b29cee 100644 --- a/src/app/pages/editor/properties/abstract-properties.component.ts +++ b/src/app/pages/editor/properties/abstract-properties.component.ts @@ -3,6 +3,9 @@ import {FIGWidget} from "../../../models/widgets/widget"; import {debounceTime, map, Observable, Subscription} from "rxjs"; import {FormGroup} from "@angular/forms"; import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; +import {Field} from "../../../models/fields/field"; +import {hasFunction} from "../../../models/object"; +import {capitalize} from "../../../models/string"; export interface FlagItem { readonly label: string; @@ -36,6 +39,14 @@ export abstract class AbstractPropertiesComponent implement this.dispose(); } + protected getField(name: string): Field { + return this.widget.getField(name) as Field; + } + + protected updateWidget(): void { + this.update.emit(this.widget); + } + protected listenProperty(property: string, debounce?: number): Observable { return this.form.get(property)!.valueChanges.pipe( (debounce) ? debounceTime(debounce) : map((_) => _), @@ -65,12 +76,16 @@ export abstract class AbstractPropertiesComponent implement protected load(): void { this.updateS = this.widget.update$.subscribe(this.onUpdated.bind(this)); + this.addFieldListeners(); this.updateForm(); } - protected abstract updateForm(): void; + protected updateForm(): void { + // TODO: remove me + } protected dispose(): void { + this.removeFieldListeners(); this.updateS?.unsubscribe(); } @@ -78,4 +93,34 @@ export abstract class AbstractPropertiesComponent implement this.updateForm(); } + private addFieldListeners(): void { + if (!this.widget) { + return; + } + for (const field of this.widget.getFields()) { + const name: string = `on${capitalize(field.name)}Changed`; + + if (hasFunction(this, name)) { + field.addListener(this.getListener(name)); + } + } + } + + private removeFieldListeners(): void { + if (!this.widget) { + return; + } + for (const field of this.widget.getFields()) { + const name: string = `on${capitalize(field.name)}Changed`; + + if (hasFunction(this, name)) { + field.removeListener(this.getListener(name)); + } + } + } + + private getListener(name: string): () => void { + return ((this as never)[name] as () => void).bind(this); + } + } diff --git a/src/app/pages/editor/properties/bloc-for-properties/bloc-for-properties.component.html b/src/app/pages/editor/properties/bloc-for-properties/bloc-for-properties.component.html index b95582f..e2f3349 100644 --- a/src/app/pages/editor/properties/bloc-for-properties/bloc-for-properties.component.html +++ b/src/app/pages/editor/properties/bloc-for-properties/bloc-for-properties.component.html @@ -1,9 +1,6 @@ -
- - Size - - + + diff --git a/src/app/pages/editor/properties/bloc-for-properties/bloc-for-properties.component.ts b/src/app/pages/editor/properties/bloc-for-properties/bloc-for-properties.component.ts index 5a80383..528daf6 100644 --- a/src/app/pages/editor/properties/bloc-for-properties/bloc-for-properties.component.ts +++ b/src/app/pages/editor/properties/bloc-for-properties/bloc-for-properties.component.ts @@ -1,44 +1,21 @@ import {Component, DestroyRef} from '@angular/core'; -import {MatFormField, MatLabel} from "@angular/material/form-field"; -import {MatInput} from "@angular/material/input"; -import {FormControl, FormGroup, ReactiveFormsModule, Validators} from "@angular/forms"; import {AbstractPropertiesComponent} from "../abstract-properties.component"; import {FIGBlocForWidget} from "../../../../models/widgets/bloc-for.widget"; +import {IntegerFieldComponent} from "../../fields/integer-field/integer-field.component"; @Component({ selector: 'fig-bloc-for-properties', standalone: true, imports: [ - MatInput, - MatLabel, - MatFormField, - ReactiveFormsModule + IntegerFieldComponent ], templateUrl: './bloc-for-properties.component.html', styleUrl: './bloc-for-properties.component.css' }) export class BlocForPropertiesComponent extends AbstractPropertiesComponent { - override form: FormGroup = new FormGroup({ - size: new FormControl(10, {validators: Validators.min(1)}), - }); - constructor(dr: DestroyRef) { super(dr); - this.listenProperty('size').subscribe(this.onSizeChanged.bind(this)); - } - - protected override updateForm(): void { - this.setProperty('size', this.widget.size); - } - - private onSizeChanged(value: number): void { - if (!this.testProperty('size')) { - this.setProperty('size', this.widget.size); - return; - } - this.widget.size = value; - this.update.emit(); } } diff --git a/src/app/pages/editor/properties/button-properties/button-properties.component.html b/src/app/pages/editor/properties/button-properties/button-properties.component.html index 98c22fe..afbd0fb 100644 --- a/src/app/pages/editor/properties/button-properties/button-properties.component.html +++ b/src/app/pages/editor/properties/button-properties/button-properties.component.html @@ -1,36 +1,12 @@ -
- - Label - - + + - - Tooltip - - + -
- Fill - -
+ -
- Small - -
+ - - Arrow - - None - Left - Right - Up - Down - - + diff --git a/src/app/pages/editor/properties/button-properties/button-properties.component.ts b/src/app/pages/editor/properties/button-properties/button-properties.component.ts index 10a68be..a7b53f2 100644 --- a/src/app/pages/editor/properties/button-properties/button-properties.component.ts +++ b/src/app/pages/editor/properties/button-properties/button-properties.component.ts @@ -1,101 +1,52 @@ import {Component, DestroyRef} from '@angular/core'; -import {MatFormField, MatLabel} from "@angular/material/form-field"; -import {MatInput} from "@angular/material/input"; -import {FormControl, FormGroup, ReactiveFormsModule} from "@angular/forms"; import {FIGButtonWidget, FIGDir} from "../../../../models/widgets/button.widget"; import {AbstractPropertiesComponent} from "../abstract-properties.component"; -import {MatSlideToggle} from "@angular/material/slide-toggle"; -import {MatOption, MatSelect} from "@angular/material/select"; +import {StringFieldComponent} from "../../fields/string-field/string-field.component"; +import {BoolFieldComponent} from "../../fields/bool-field/bool-field.component"; +import {EnumFieldComponent} from "../../fields/enum-field/enum-field.component"; @Component({ selector: 'fig-button-properties', standalone: true, imports: [ - MatInput, - MatLabel, - MatSelect, - MatOption, - MatFormField, - MatSlideToggle, - ReactiveFormsModule + BoolFieldComponent, + EnumFieldComponent, + StringFieldComponent ], templateUrl: './button-properties.component.html', styleUrl: './button-properties.component.css' }) export class ButtonPropertiesComponent extends AbstractPropertiesComponent { - override form: FormGroup = new FormGroup({ - label: new FormControl(''), - tooltip: new FormControl(null), - isFill: new FormControl(false), - isSmall: new FormControl(false), - arrow: new FormControl(FIGDir.none), - }); - - protected readonly FIGArrowDirection = FIGDir; - constructor(dr: DestroyRef) { super(dr); - this.listenProperty('label').subscribe(this.onLabelChanged.bind(this)); - this.listenProperty('tooltip').subscribe(this.onTooltipChanged.bind(this)); - this.listenProperty('isFill').subscribe(this.onIsFillChanged.bind(this)); - this.listenProperty('isSmall').subscribe(this.onIsSmallChanged.bind(this)); - this.listenProperty('arrow').subscribe(this.onArrowChanged.bind(this)); - } - - protected override updateForm() { - this.setProperty('label', this.widget.label); - this.setProperty('tooltip', this.widget.tooltip ?? null); - this.setProperty('isFill', this.widget.isFill); - this.setProperty('isSmall', this.widget.isSmall); - this.setProperty('arrow', this.widget.arrow); - } - - private onLabelChanged(value: string): void { - this.widget.label = value; - this.update.emit(); - } - - private onTooltipChanged(value: string | null): void { - if (value && value.trim().length === 0) { - value = null; - } - this.widget.tooltip = value ?? undefined; - this.update.emit(); } private onIsFillChanged(value: boolean): void { - this.widget.isFill = value; if (value) { this.resetIsSmall(); this.resetArrow(); } - this.update.emit(); } private onIsSmallChanged(value: boolean): void { - this.widget.isSmall = value; if (value) { this.resetIsFill(); this.resetArrow(); } - this.update.emit(); } private onArrowChanged(value: FIGDir): void { - this.widget.arrow = value; - if (value) { + if (value !== FIGDir.none) { this.resetIsFill(); this.resetIsSmall(); } - this.update.emit(); } private resetIsFill(): void { if (!this.widget.isFill) { return; } - this.setProperty('isFill', false); this.widget.isFill = false; } @@ -103,7 +54,6 @@ export class ButtonPropertiesComponent extends AbstractPropertiesComponent - - Label - - +
+ - - Tooltip - - + -
- Checked - -
+ diff --git a/src/app/pages/editor/properties/checkbox-properties/checkbox-properties.component.ts b/src/app/pages/editor/properties/checkbox-properties/checkbox-properties.component.ts index 4881897..38f9577 100644 --- a/src/app/pages/editor/properties/checkbox-properties/checkbox-properties.component.ts +++ b/src/app/pages/editor/properties/checkbox-properties/checkbox-properties.component.ts @@ -1,61 +1,25 @@ import {Component, DestroyRef} from '@angular/core'; -import {MatFormField, MatLabel} from "@angular/material/form-field"; -import {MatInput} from "@angular/material/input"; -import {FormControl, FormGroup, ReactiveFormsModule} from "@angular/forms"; import {AbstractPropertiesComponent} from "../abstract-properties.component"; import {FIGCheckboxWidget} from "../../../../models/widgets/checkbox.widget"; -import {MatSlideToggle} from "@angular/material/slide-toggle"; +import {BoolFieldComponent} from "../../fields/bool-field/bool-field.component"; +import {EnumFieldComponent} from "../../fields/enum-field/enum-field.component"; +import {StringFieldComponent} from "../../fields/string-field/string-field.component"; @Component({ selector: 'fig-checkbox-properties', standalone: true, imports: [ - MatInput, - MatLabel, - MatFormField, - MatSlideToggle, - ReactiveFormsModule + BoolFieldComponent, + EnumFieldComponent, + StringFieldComponent ], templateUrl: './checkbox-properties.component.html', styleUrl: './checkbox-properties.component.css' }) export class CheckboxPropertiesComponent extends AbstractPropertiesComponent { - override form: FormGroup = new FormGroup({ - label: new FormControl(''), - isChecked: new FormControl(false), - tooltip: new FormControl(null), - }); - constructor(dr: DestroyRef) { super(dr); - this.listenProperty('label').subscribe(this.onLabelChanged.bind(this)); - this.listenProperty('tooltip').subscribe(this.onTooltipChanged.bind(this)); - this.listenProperty('isChecked').subscribe(this.onIsCheckedChanged.bind(this)); - } - - protected override updateForm(): void { - this.setProperty('label', this.widget.label); - this.setProperty('tooltip', this.widget.tooltip ?? null); - this.setProperty('isChecked', this.widget.isChecked); - } - - private onLabelChanged(value: string): void { - this.widget.label = value; - this.update.emit(); - } - - private onTooltipChanged(value: string | null): void { - if (value && value.trim().length === 0) { - value = null; - } - this.widget.tooltip = value ?? undefined; - this.update.emit(); - } - - private onIsCheckedChanged(value: boolean): void { - this.widget.isChecked = value; - this.update.emit(); } } diff --git a/src/app/pages/editor/properties/child-window-properties/child-window-properties.component.css b/src/app/pages/editor/properties/child-window-properties/child-window-properties.component.css index 14b8237..e69de29 100644 --- a/src/app/pages/editor/properties/child-window-properties/child-window-properties.component.css +++ b/src/app/pages/editor/properties/child-window-properties/child-window-properties.component.css @@ -1,3 +0,0 @@ -.row button { - margin-top: 16px; -} diff --git a/src/app/pages/editor/properties/child-window-properties/child-window-properties.component.html b/src/app/pages/editor/properties/child-window-properties/child-window-properties.component.html index 2aceddf..93b7caf 100644 --- a/src/app/pages/editor/properties/child-window-properties/child-window-properties.component.html +++ b/src/app/pages/editor/properties/child-window-properties/child-window-properties.component.html @@ -1,46 +1,10 @@ -
- - Label - - + + -
- - Width - - + - - Height - - + - -
- - @if (showHelpSize) { -

- Use values between 0.0 and 1.0 to define a size in percentage of the - parent region.
- You can fill the parent with a value of 0 (or less). -

- } - -
- Show frame border - -
- - - Flags - - @for (flag of flags; track flag.value) { - {{ flag.label }} - } - - + diff --git a/src/app/pages/editor/properties/child-window-properties/child-window-properties.component.ts b/src/app/pages/editor/properties/child-window-properties/child-window-properties.component.ts index 4752280..9d9f180 100644 --- a/src/app/pages/editor/properties/child-window-properties/child-window-properties.component.ts +++ b/src/app/pages/editor/properties/child-window-properties/child-window-properties.component.ts @@ -1,116 +1,27 @@ import {Component, DestroyRef} from '@angular/core'; -import {MatFormField, MatLabel} from "@angular/material/form-field"; -import {MatInput} from "@angular/material/input"; -import {MatOption} from "@angular/material/autocomplete"; -import {MatSelect} from "@angular/material/select"; -import {FormControl, FormGroup, ReactiveFormsModule} from "@angular/forms"; -import {AbstractPropertiesComponent, FlagItem} from "../abstract-properties.component"; -import {FIGWindowFlags, FIGWindowWidget} from "../../../../models/widgets/window.widget"; +import {AbstractPropertiesComponent} from "../abstract-properties.component"; import {FIGChildWindowWidget} from "../../../../models/widgets/child-window.widget"; -import {MatSlideToggle} from "@angular/material/slide-toggle"; -import {MatIconButton} from "@angular/material/button"; -import {MatIcon} from "@angular/material/icon"; +import {BoolFieldComponent} from "../../fields/bool-field/bool-field.component"; +import {StringFieldComponent} from "../../fields/string-field/string-field.component"; +import {SizeFieldComponent} from "../../fields/size-field/size-field.component"; +import {FlagsFieldComponent} from "../../fields/flags-field/flags-field.component"; @Component({ selector: 'fig-child-window-properties', standalone: true, imports: [ - MatIcon, - MatInput, - MatLabel, - MatSelect, - MatOption, - MatFormField, - MatIconButton, - MatSlideToggle, - ReactiveFormsModule + BoolFieldComponent, + SizeFieldComponent, + FlagsFieldComponent, + StringFieldComponent ], templateUrl: './child-window-properties.component.html', styleUrl: './child-window-properties.component.css' }) export class ChildWindowPropertiesComponent extends AbstractPropertiesComponent { - readonly flags: FlagItem[] = []; - - override form: FormGroup = new FormGroup({ - label: new FormControl(''), - width: new FormControl(null), - height: new FormControl(null), - frameBorder: new FormControl(true), - flags: new FormControl([]), - }); - - showHelpSize: boolean = false; - constructor(dr: DestroyRef) { super(dr); - for (const flag of FIGWindowWidget.flags) { - this.flags.push({ - label: FIGWindowFlags[flag], - value: flag - }); - } - this.listenProperty('label').subscribe(this.onLabelChanged.bind(this)); - this.listenProperty('width', 300).subscribe(this.onWidthChanged.bind(this)); - this.listenProperty('height', 300).subscribe(this.onHeightChanged.bind(this)); - this.listenProperty('frameBorder').subscribe(this.onFrameBorderChanged.bind(this)); - this.listenProperty('flags').subscribe(this.onFlagsChanged.bind(this)); - } - - protected override updateForm(): void { - this.setProperty('label', this.widget.label); - this.setProperty('width', this.widget.size.width); - this.setProperty('height', this.widget.size.height); - this.setProperty('frameBorder', this.widget.frameBorder); - const flags: number[] = []; - - for (const flag of this.flags) { - if ((this.widget.flags & flag.value) === flag.value) { - flags.push(flag.value); - } - } - this.setProperty('flags', flags); - } - - private onLabelChanged(value: string): void { - this.widget.label = value; - this.update.emit(); - } - - private onWidthChanged(value: number | null): void { - if (!this.testProperty('width') || value === null) { - this.setProperty('width', this.widget.size.width); - return; - } - this.widget.size.width = value; - this.update.emit(); - } - - private onHeightChanged(value: number | null): void { - if (!this.testProperty('height') || value === null) { - this.setProperty('height', this.widget.size.height); - return; - } - this.widget.size.height = value; - this.update.emit(); - } - - private onFrameBorderChanged(value: boolean): void { - this.widget.frameBorder = value; - this.update.emit(); - } - - private onFlagsChanged(value: number[]): void { - for (const flag of this.flags) { - const isEnabled: boolean = value.includes(flag.value); - - if (isEnabled) { - this.widget.flags |= flag.value; - } else if ((this.widget.flags & flag.value) === flag.value) { - this.widget.flags ^= flag.value; - } - } - this.update.emit(); } } diff --git a/src/app/pages/editor/properties/collapsing-header-properties/collapsing-header-properties.component.html b/src/app/pages/editor/properties/collapsing-header-properties/collapsing-header-properties.component.html index 25044fc..94f36b9 100644 --- a/src/app/pages/editor/properties/collapsing-header-properties/collapsing-header-properties.component.html +++ b/src/app/pages/editor/properties/collapsing-header-properties/collapsing-header-properties.component.html @@ -1,15 +1,6 @@ -
- - Label - - + + - - Flags - - @for (flag of flags; track flag.value) { - {{ flag.label }} - } - - + diff --git a/src/app/pages/editor/properties/collapsing-header-properties/collapsing-header-properties.component.ts b/src/app/pages/editor/properties/collapsing-header-properties/collapsing-header-properties.component.ts index 70e267d..892c0ab 100644 --- a/src/app/pages/editor/properties/collapsing-header-properties/collapsing-header-properties.component.ts +++ b/src/app/pages/editor/properties/collapsing-header-properties/collapsing-header-properties.component.ts @@ -1,75 +1,23 @@ import {Component, DestroyRef} from '@angular/core'; -import {MatFormField, MatLabel} from "@angular/material/form-field"; -import {MatInput} from "@angular/material/input"; -import {FormControl, FormGroup, ReactiveFormsModule} from "@angular/forms"; -import {AbstractPropertiesComponent, FlagItem} from "../abstract-properties.component"; +import {AbstractPropertiesComponent} from "../abstract-properties.component"; import {FIGCollapsingHeaderWidget} from "../../../../models/widgets/collapsing-header.widget"; -import {MatOption, MatSelect} from "@angular/material/select"; -import {FIGTreeNodeFlags, FIGTreeNodeWidget} from "../../../../models/widgets/tree-node.widget"; +import {StringFieldComponent} from "../../fields/string-field/string-field.component"; +import {FlagsFieldComponent} from "../../fields/flags-field/flags-field.component"; @Component({ selector: 'fig-collapsing-header-properties', standalone: true, imports: [ - MatInput, - MatLabel, - MatSelect, - MatOption, - MatFormField, - ReactiveFormsModule, + FlagsFieldComponent, + StringFieldComponent, ], templateUrl: './collapsing-header-properties.component.html', styleUrl: './collapsing-header-properties.component.css' }) export class CollapsingHeaderPropertiesComponent extends AbstractPropertiesComponent { - readonly flags: FlagItem[] = []; - - override form: FormGroup = new FormGroup({ - label: new FormControl(''), - flags: new FormControl([]), - }); - constructor(dr: DestroyRef) { super(dr); - for (const flag of FIGTreeNodeWidget.flags) { - this.flags.push({ - label: FIGTreeNodeFlags[flag], - value: flag - }); - } - this.listenProperty('label').subscribe(this.onLabelChanged.bind(this)); - this.listenProperty('flags').subscribe(this.onFlagsChanged.bind(this)); - } - - protected override updateForm() { - this.setProperty('label', this.widget.label); - const flags: number[] = []; - - for (const flag of this.flags) { - if ((this.widget.flags & flag.value) === flag.value) { - flags.push(flag.value); - } - } - this.setProperty('flags', flags); - } - - private onLabelChanged(value: string): void { - this.widget.label = value; - this.update.emit(); - } - - private onFlagsChanged(value: number[]): void { - for (const flag of this.flags) { - const isEnabled: boolean = value.includes(flag.value); - - if (isEnabled) { - this.widget.flags |= flag.value; - } else if ((this.widget.flags & flag.value) === flag.value) { - this.widget.flags ^= flag.value; - } - } - this.update.emit(); } } diff --git a/src/app/pages/editor/properties/combo-properties/combo-properties.component.html b/src/app/pages/editor/properties/combo-properties/combo-properties.component.html index f8852b1..31ff89e 100644 --- a/src/app/pages/editor/properties/combo-properties/combo-properties.component.html +++ b/src/app/pages/editor/properties/combo-properties/combo-properties.component.html @@ -1,27 +1,16 @@ -
- - Label - - + + - - Tooltip - - + - - List of items - - + Selected item - + diff --git a/src/app/pages/editor/properties/combo-properties/combo-properties.component.ts b/src/app/pages/editor/properties/combo-properties/combo-properties.component.ts index 562c100..c363bda 100644 --- a/src/app/pages/editor/properties/combo-properties/combo-properties.component.ts +++ b/src/app/pages/editor/properties/combo-properties/combo-properties.component.ts @@ -1,71 +1,44 @@ import {Component, DestroyRef} from '@angular/core'; -import {FormControl, FormGroup, ReactiveFormsModule} from "@angular/forms"; -import {MatFormField, MatLabel} from "@angular/material/form-field"; -import {MatInput} from "@angular/material/input"; -import {MatSlideToggle} from "@angular/material/slide-toggle"; import {AbstractPropertiesComponent} from "../abstract-properties.component"; import {FIGComboWidget} from "../../../../models/widgets/combo.widget"; -import {CdkTextareaAutosize} from "@angular/cdk/text-field"; +import {StringFieldComponent} from "../../fields/string-field/string-field.component"; +import {ArrayStringFieldComponent} from "../../fields/array-string-field/array-string-field.component"; +import {MatFormField, MatLabel} from "@angular/material/form-field"; +import {MatInput} from "@angular/material/input"; +import {FormControl, ReactiveFormsModule} from "@angular/forms"; @Component({ selector: 'fig-combo-properties', standalone: true, imports: [ - MatInput, MatLabel, + MatInput, MatFormField, - MatSlideToggle, ReactiveFormsModule, - CdkTextareaAutosize + StringFieldComponent, + ArrayStringFieldComponent ], templateUrl: './combo-properties.component.html', styleUrl: './combo-properties.component.css' }) export class ComboPropertiesComponent extends AbstractPropertiesComponent { - override form: FormGroup = new FormGroup({ - label: new FormControl(''), - tooltip: new FormControl(null), - items: new FormControl(null), - selectedItem: new FormControl({value: '', disabled: true}), - }); + selectedItem: FormControl = new FormControl({value: '', disabled: true}); constructor(dr: DestroyRef) { super(dr); - this.listenProperty('label').subscribe(this.onLabelChanged.bind(this)); - this.listenProperty('tooltip').subscribe(this.onTooltipChanged.bind(this)); - this.listenProperty('items', 300).subscribe(this.onItemsChanged.bind(this)); - } - - protected override updateForm(): void { - this.setProperty('label', this.widget.label); - this.setProperty('tooltip', this.widget.tooltip ?? null); - this.setProperty('items', this.widget.items.join('\n')); - this.setProperty('selectedItem', this.widget.items[this.widget.selectedItem]); - } - - private onLabelChanged(value: string): void { - this.widget.label = value; - this.update.emit(); } - private onTooltipChanged(value: string | null): void { - if (value && value.trim().length === 0) { - value = null; - } - this.widget.tooltip = value ?? undefined; - this.update.emit(); + protected override load() { + super.load(); + this.onSelectedItemChanged(this.getField('selectedItem').value ?? 0); } - private onItemsChanged(value: string | null): void { - value ??= ''; - const items: string[] = value!.split('\n'); + private onSelectedItemChanged(item: number): void { + const items: string[] = this.getField('items').value ?? []; + const selected: string = items[item]; - this.widget.items.length = 0; - this.widget.items.push(...items); - this.widget.selectedItem = 0; - this.setProperty('selectedItem', this.widget.items[0]); - this.update.emit(); + this.selectedItem.setValue(selected); } } diff --git a/src/app/pages/editor/properties/dummy-properties/dummy-properties.component.html b/src/app/pages/editor/properties/dummy-properties/dummy-properties.component.html index a89b40e..3396850 100644 --- a/src/app/pages/editor/properties/dummy-properties/dummy-properties.component.html +++ b/src/app/pages/editor/properties/dummy-properties/dummy-properties.component.html @@ -1,20 +1,5 @@ -
- - Width - - + + - - Height - - - - - Tooltip - - + diff --git a/src/app/pages/editor/properties/dummy-properties/dummy-properties.component.ts b/src/app/pages/editor/properties/dummy-properties/dummy-properties.component.ts index 5aa592c..1b17817 100644 --- a/src/app/pages/editor/properties/dummy-properties/dummy-properties.component.ts +++ b/src/app/pages/editor/properties/dummy-properties/dummy-properties.component.ts @@ -1,67 +1,23 @@ import {Component, DestroyRef} from '@angular/core'; -import {MatFormField, MatLabel} from "@angular/material/form-field"; -import {MatInput} from "@angular/material/input"; -import {FormControl, FormGroup, ReactiveFormsModule, Validators} from "@angular/forms"; import {AbstractPropertiesComponent} from "../abstract-properties.component"; import {FIGDummyWidget} from "../../../../models/widgets/dummy.widget"; +import {StringFieldComponent} from "../../fields/string-field/string-field.component"; +import {SizeFieldComponent} from "../../fields/size-field/size-field.component"; @Component({ selector: 'fig-dummy-properties', standalone: true, imports: [ - MatInput, - MatLabel, - MatFormField, - ReactiveFormsModule + SizeFieldComponent, + StringFieldComponent ], templateUrl: './dummy-properties.component.html', styleUrl: './dummy-properties.component.css' }) export class DummyPropertiesComponent extends AbstractPropertiesComponent { - override form: FormGroup = new FormGroup({ - width: new FormControl(100, {validators: Validators.min(0)}), - height: new FormControl(100, {validators: Validators.min(0)}), - tooltip: new FormControl(null), - }); - constructor(dr: DestroyRef) { super(dr); - this.listenProperty('width').subscribe(this.onWidthChanged.bind(this)); - this.listenProperty('height').subscribe(this.onHeightChanged.bind(this)); - this.listenProperty('tooltip').subscribe(this.onTooltipChanged.bind(this)); - } - - protected override updateForm() { - this.setProperty('width', this.widget.width); - this.setProperty('height', this.widget.height); - this.setProperty('tooltip', this.widget.tooltip ?? null); - } - - private onWidthChanged(value: number): void { - if (!this.testProperty('width')) { - this.setProperty('width', this.widget.width); - return; - } - this.widget.width = value; - this.update.emit(); - } - - private onHeightChanged(value: number): void { - if (!this.testProperty('height')) { - this.setProperty('height', this.widget.height); - return; - } - this.widget.height = value; - this.update.emit(); - } - - private onTooltipChanged(value: string | null): void { - if (value && value.trim().length === 0) { - value = null; - } - this.widget.tooltip = value ?? undefined; - this.update.emit(); } } diff --git a/src/app/pages/editor/properties/input-color-edit-properties/input-color-edit-properties.component.html b/src/app/pages/editor/properties/input-color-edit-properties/input-color-edit-properties.component.html index 8b94432..ceb947b 100644 --- a/src/app/pages/editor/properties/input-color-edit-properties/input-color-edit-properties.component.html +++ b/src/app/pages/editor/properties/input-color-edit-properties/input-color-edit-properties.component.html @@ -1,39 +1,13 @@ -
- - Label - - + + -
- Color - -
+ + -
- Alpha channel - -
+ - - Tooltip - - + - - Flags - - @for (flag of flags; track flag.value) { - {{ flag.label }} - } - - + diff --git a/src/app/pages/editor/properties/input-color-edit-properties/input-color-edit-properties.component.ts b/src/app/pages/editor/properties/input-color-edit-properties/input-color-edit-properties.component.ts index 299d4ea..4832e1b 100644 --- a/src/app/pages/editor/properties/input-color-edit-properties/input-color-edit-properties.component.ts +++ b/src/app/pages/editor/properties/input-color-edit-properties/input-color-edit-properties.component.ts @@ -1,129 +1,56 @@ -import {Component, DestroyRef, ViewChild} from '@angular/core'; -import {MatFormField, MatLabel} from "@angular/material/form-field"; -import {MatInput} from "@angular/material/input"; -import {MatSlideToggle} from "@angular/material/slide-toggle"; -import {NgxColorsModule, NgxColorsTriggerDirective} from "ngx-colors"; -import {FormControl, FormGroup, ReactiveFormsModule} from "@angular/forms"; -import {AbstractPropertiesComponent, FlagItem} from "../abstract-properties.component"; -import {PanelComponent} from "ngx-colors/lib/components/panel/panel.component"; -import {Color, parseRGBA, stringifyHEX, stringifyRGBA} from "../../../../models/math"; +import {Component, DestroyRef} from '@angular/core'; +import {AbstractPropertiesComponent} from "../abstract-properties.component"; import {FIGInputColorEditFlags, FIGInputColorEditWidget} from "../../../../models/widgets/input-color-edit.widget"; -import {MatOption} from "@angular/material/autocomplete"; -import {MatSelect} from "@angular/material/select"; +import {StringFieldComponent} from "../../fields/string-field/string-field.component"; +import {BoolFieldComponent} from "../../fields/bool-field/bool-field.component"; +import {FlagsFieldComponent} from "../../fields/flags-field/flags-field.component"; +import {ColorFieldComponent} from "../../fields/color-field/color-field.component"; +import {Color} from "../../../../models/math"; +import {FlagsField} from "../../../../models/fields/flags.field"; @Component({ selector: 'fig-input-color-edit-properties', standalone: true, imports: [ - MatInput, - MatLabel, - MatSelect, - MatOption, - MatFormField, - MatSlideToggle, - NgxColorsModule, - ReactiveFormsModule + BoolFieldComponent, + ColorFieldComponent, + FlagsFieldComponent, + StringFieldComponent ], templateUrl: './input-color-edit-properties.component.html', styleUrl: './input-color-edit-properties.component.css' }) export class InputColorEditPropertiesComponent extends AbstractPropertiesComponent { - @ViewChild(NgxColorsTriggerDirective) - ngxColor!: NgxColorsTriggerDirective; - - readonly flags: FlagItem[] = []; - - override form: FormGroup = new FormGroup({ - label: new FormControl(''), - color: new FormControl(''), - withAlpha: new FormControl(false), - tooltip: new FormControl(null), - flags: new FormControl([]), - }); - constructor(dr: DestroyRef) { super(dr); - for (const flag of FIGInputColorEditWidget.flags) { - this.flags.push({ - label: FIGInputColorEditFlags[flag], - value: flag - }); - } - this.listenProperty('label').subscribe(this.onLabelChanged.bind(this)); - this.listenProperty('color').subscribe(this.onColorChanged.bind(this)); - this.listenProperty('withAlpha').subscribe(this.onWithAlphaChanged.bind(this)); - this.listenProperty('tooltip').subscribe(this.onTooltipChanged.bind(this)); - this.listenProperty('flags').subscribe(this.onFlagsChanged.bind(this)); - } - - protected onColorPickerOpened(): void { - const $panel: PanelComponent = this.ngxColor.panelRef.instance; - - $panel.menu = 3; - if ($panel.color.length === 0) { - $panel.color = stringifyRGBA(this.widget.color); - }/* else if ($panel.color.length === 0) { - $panel.color = 'rgb(255, 255, 255)'; - }*/ } - protected override updateForm(): void { - const color: Color = {...this.widget.color}; - - if (!this.widget.withAlpha) { - color.a = 1.0; + private onColorChanged(value?: Color): void { + if (!value) { + return; } - this.setProperty('label', this.widget.label); - this.setProperty('color', stringifyHEX(color)); - this.setProperty('withAlpha', this.widget.withAlpha); - this.setProperty('tooltip', this.widget.tooltip ?? null); - const flags: number[] = []; - - for (const flag of this.flags) { - if ((this.widget.flags & flag.value) === flag.value) { - flags.push(flag.value); - } + if (value.a >= 1.0) { + return; } - this.setProperty('flags', flags); - } - - private onLabelChanged(value: string): void { - this.widget.label = value; - this.update.emit(); - } - - private onColorChanged(value: string): void { - const alpha: number = this.widget.withAlpha ? 0.5 : 1.0; - - this.widget.color = parseRGBA(value) ?? {r: 0.5, g: 0.5, b: 0.5, a: alpha}; - this.update.emit(); + this.widget.withAlpha = true; } private onWithAlphaChanged(value: boolean): void { - this.widget.withAlpha = value; - this.update.emit(); - } - - private onTooltipChanged(value: string | null): void { - if (value && value.trim().length === 0) { - value = null; - } - this.widget.tooltip = value ?? undefined; - this.update.emit(); - } - - private onFlagsChanged(value: number[]): void { - for (const flag of this.flags) { - const isEnabled: boolean = value.includes(flag.value); + const color: Color = {...this.widget.color}; + const field: FlagsField = this.getField('flags') as FlagsField; - if (isEnabled) { - this.widget.flags |= flag.value; - } else if ((this.widget.flags & flag.value) === flag.value) { - this.widget.flags ^= flag.value; - } + if (!value) { + color.a = 1.0; + field.disable(FIGInputColorEditFlags.AlphaBar); + field.disable(FIGInputColorEditFlags.AlphaPreviewHalf); + } else { + color.a = 0.5; + field.disable(FIGInputColorEditFlags.NoAlpha); + field.enable(FIGInputColorEditFlags.AlphaBar); + field.enable(FIGInputColorEditFlags.AlphaPreviewHalf); } - this.update.emit(); + this.widget.color = color; } } diff --git a/src/app/pages/editor/properties/input-number-properties/input-number-properties.component.html b/src/app/pages/editor/properties/input-number-properties/input-number-properties.component.html index 83061b6..86d0ef7 100644 --- a/src/app/pages/editor/properties/input-number-properties/input-number-properties.component.html +++ b/src/app/pages/editor/properties/input-number-properties/input-number-properties.component.html @@ -1,4 +1,16 @@
+ + Label + + + + + Tooltip + + + Data Type @@ -18,7 +30,7 @@
@switch (getArraySize(widget.dataType)) { - @case (0) { + @case (1) { Value @@ -105,16 +117,4 @@
} - - - Label - - - - - Tooltip - - diff --git a/src/app/pages/editor/properties/input-number-properties/input-number-properties.component.ts b/src/app/pages/editor/properties/input-number-properties/input-number-properties.component.ts index bbc4f6d..57eecf0 100644 --- a/src/app/pages/editor/properties/input-number-properties/input-number-properties.component.ts +++ b/src/app/pages/editor/properties/input-number-properties/input-number-properties.component.ts @@ -65,20 +65,17 @@ export class InputNumberPropertiesComponent extends AbstractPropertiesComponent< protected override updateForm() { this.setProperty('dataType', this.widget.dataType); const size: number = FIGInputNumberWidget.getArraySize(this.widget.dataType); + const values: number[] = this.widget.value; - if (size > 0) { - const values: number[] = this.widget.value as number[]; - - this.setProperty('value0', this.formatNumber(values[0])); - this.setProperty('value1', this.formatNumber(values[1])); - if (size > 2) { - this.setProperty('value2', this.formatNumber(values[2])); - } - if (size > 3) { - this.setProperty('value3', this.formatNumber(values[3])); - } - } else { - this.setProperty('value0', this.formatNumber(this.widget.value as number)); + this.setProperty('value0', this.formatNumber(values[0])); + if (size > 1) { + this.setProperty('value2', this.formatNumber(values[1])); + } + if (size > 2) { + this.setProperty('value2', this.formatNumber(values[2])); + } + if (size > 3) { + this.setProperty('value3', this.formatNumber(values[3])); } this.setProperty('step', this.widget.step); this.setProperty('stepFast', this.widget.stepFast); @@ -96,16 +93,16 @@ export class InputNumberPropertiesComponent extends AbstractPropertiesComponent< } private onDataTypeChanged(dataType: FIGInputNumberType): void { - const prevValue: number | number[] = this.widget.value; + const prevValue: number[] = this.widget.value; const prevSize: number = this.getArraySize(this.widget.dataType); const size: number = this.getArraySize(dataType); this.widget.dataType = dataType; - if (size === 0 && prevValue instanceof Array) { - this.widget.value = prevValue[0]; - this.setProperty('value0', this.formatNumber(this.widget.value)); - } else if (size > 0 && !(prevValue instanceof Array)) { - const values: number[] = [prevValue]; + if (size === 1) { + this.widget.value[0] = prevValue[0]; + this.setProperty('value0', this.formatNumber(this.widget.value[0])); + } else if (size > 1) { + const values: number[] = prevValue; for (let i = 0; i < size - 1; i++) { values.push(0); @@ -115,14 +112,14 @@ export class InputNumberPropertiesComponent extends AbstractPropertiesComponent< this.setProperty(`value${i}`, this.formatNumber(values[i])); } } else if (prevSize < size) { - const values: number[] = this.widget.value as number[]; + const values: number[] = this.widget.value; for (let i = prevSize; i < size; i++) { values.push(0); this.setProperty(`value${i}`, this.formatNumber(values[i])); } } else if (prevSize > size) { - const values: number[] = this.widget.value as number[]; + const values: number[] = this.widget.value; const delta: number = prevSize - size; const index: number = prevSize - delta; @@ -133,7 +130,7 @@ export class InputNumberPropertiesComponent extends AbstractPropertiesComponent< private onValueChanged(value: string, index: number): void { const isArray: boolean = FIGInputNumberWidget.isArray(this.widget.dataType); - const prevValue: number = (isArray) ? (this.widget.value as number[])[index] : this.widget.value as number; + const prevValue: number = (isArray) ? this.widget.value[index] : this.widget.value[0]; const number: number = +value; if (FIGInputNumberWidget.isInteger(this.widget.dataType) && !Number.isInteger(number)) { @@ -141,9 +138,9 @@ export class InputNumberPropertiesComponent extends AbstractPropertiesComponent< return; } if (isArray) { - (this.widget.value as number[])[index] = number; + this.widget.value[index] = number; } else { - this.widget.value = number; + this.widget.value[0] = number; } this.update.emit(); } diff --git a/src/app/pages/editor/properties/menu-item-properties/menu-item-properties.component.html b/src/app/pages/editor/properties/menu-item-properties/menu-item-properties.component.html index 251865d..f452f6d 100644 --- a/src/app/pages/editor/properties/menu-item-properties/menu-item-properties.component.html +++ b/src/app/pages/editor/properties/menu-item-properties/menu-item-properties.component.html @@ -12,20 +12,20 @@
- Is selectable + Enabled + formControlName="enabled">
- Is selected + Is selectable + formControlName="isSelectable">
- Enabled + Is selected + formControlName="isSelected">
diff --git a/src/app/pages/editor/properties/plot-properties/plot-properties.component.html b/src/app/pages/editor/properties/plot-properties/plot-properties.component.html index 4291061..d05b3cc 100644 --- a/src/app/pages/editor/properties/plot-properties/plot-properties.component.html +++ b/src/app/pages/editor/properties/plot-properties/plot-properties.component.html @@ -1,4 +1,9 @@
+ + Label + + + Plot Type @@ -8,46 +13,48 @@ - Label - - - - - Value offset + Overlay text + type="text" + formControlName="overlayText" />
- Scale min + Width + formControlName="width" /> - Scale max + Height + formControlName="height" />
+ + Value offset + + +
- Width + Scale min + formControlName="scaleMin" /> - Height + Scale max + formControlName="scaleMax" />
@@ -57,11 +64,4 @@ type="number" formControlName="stride" /> - - - Overlay text - -
diff --git a/src/app/pages/editor/properties/popup-properties/popup-properties.component.html b/src/app/pages/editor/properties/popup-properties/popup-properties.component.html index 8e9d01f..3882f0e 100644 --- a/src/app/pages/editor/properties/popup-properties/popup-properties.component.html +++ b/src/app/pages/editor/properties/popup-properties/popup-properties.component.html @@ -11,14 +11,14 @@ formControlName="contextItem"> - - Debug label - - -
Show debug button
+ + + Debug label + + diff --git a/src/app/pages/editor/properties/progress-bar-properties/progress-bar-properties.component.html b/src/app/pages/editor/properties/progress-bar-properties/progress-bar-properties.component.html index 80dbcbb..ce223e9 100644 --- a/src/app/pages/editor/properties/progress-bar-properties/progress-bar-properties.component.html +++ b/src/app/pages/editor/properties/progress-bar-properties/progress-bar-properties.component.html @@ -1,11 +1,4 @@
-
- Value - - - -
- Label +
+ Value + + + +
+
Fill