Skip to content

Commit

Permalink
refactor: add fields to widgets.
Browse files Browse the repository at this point in the history
Define properties of widgets as fields.
Define <fig-*-field> components to bind widget's field by type.
Update <fig-*-properties> to bind widget's fields using field components.
WIP.

Closes #47
  • Loading branch information
poirierlouis committed Nov 9, 2024
1 parent e030c5a commit 7576359
Show file tree
Hide file tree
Showing 96 changed files with 1,855 additions and 1,168 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
44 changes: 25 additions & 19 deletions src/app/formatters/lua-sol2.formatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<T>(flags: number, flagsList: T[], flagsType: any, flagName: string): string {
let varFlags: string = '';

Expand Down Expand Up @@ -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'));
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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 {
Expand Down
11 changes: 11 additions & 0 deletions src/app/models/fields/array.field.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import {Field, FieldType} from "./field";

export class ArrayField extends Field<unknown[]> {
constructor(name: string,
label: string,
value?: unknown[],
isOptional: boolean = false,
defaultValue?: unknown[]) {
super(FieldType.array, name, label, value, isOptional, defaultValue);
}
}
11 changes: 11 additions & 0 deletions src/app/models/fields/bool.field.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import {Field, FieldType} from "./field";

export class BoolField extends Field<boolean> {
constructor(name: string,
label: string,
value?: boolean,
isOptional: boolean = false,
defaultValue?: boolean) {
super(FieldType.bool, name, label, value, isOptional, defaultValue);
}
}
12 changes: 12 additions & 0 deletions src/app/models/fields/color.field.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import {Field, FieldType} from "./field";
import {Color} from "../math";

export class ColorField extends Field<Color> {
constructor(name: string,
label: string,
value?: Color,
isOptional: boolean = false,
defaultValue?: Color) {
super(FieldType.color, name, label, value, isOptional, defaultValue);
}
}
22 changes: 22 additions & 0 deletions src/app/models/fields/enum.field.ts
Original file line number Diff line number Diff line change
@@ -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<T extends EnumFieldType> extends Field<T> {
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;
}
}
65 changes: 65 additions & 0 deletions src/app/models/fields/field.ts
Original file line number Diff line number Diff line change
@@ -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<T = unknown> {
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);
}
}

}
50 changes: 50 additions & 0 deletions src/app/models/fields/flags.field.ts
Original file line number Diff line number Diff line change
@@ -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<EnumFieldType, EnumFieldType>): 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<number> {
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();
}
}
11 changes: 11 additions & 0 deletions src/app/models/fields/float.field.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import {Field, FieldType} from "./field";

export class FloatField extends Field<number> {
constructor(name: string,
label: string,
value?: number,
isOptional: boolean = false,
defaultValue?: number) {
super(FieldType.float, name, label, value, isOptional, defaultValue);
}
}
11 changes: 11 additions & 0 deletions src/app/models/fields/integer.field.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import {Field, FieldType} from "./field";

export class IntegerField extends Field<number> {
constructor(name: string,
label: string,
value?: number,
isOptional: boolean = false,
defaultValue?: number) {
super(FieldType.integer, name, label, value, isOptional, defaultValue);
}
}
11 changes: 11 additions & 0 deletions src/app/models/fields/number.field.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import {Field, FieldType} from "./field";

export class NumberField extends Field<number> {
constructor(name: string,
label: string,
value?: number,
isOptional: boolean = false,
defaultValue?: number) {
super(FieldType.number, name, label, value, isOptional, defaultValue);
}
}
24 changes: 24 additions & 0 deletions src/app/models/fields/size.field.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import {Field, FieldType} from "./field";
import {Size} from "../math";

export class SizeField extends Field<Size> {
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);
}
}
11 changes: 11 additions & 0 deletions src/app/models/fields/string.field.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import {Field, FieldType} from "./field";

export class StringField extends Field<string> {
constructor(name: string,
label: string,
value?: string,
isOptional: boolean = false,
defaultValue?: string) {
super(FieldType.string, name, label, value, isOptional, defaultValue);
}
}
Loading

0 comments on commit 7576359

Please sign in to comment.