Skip to content

Commit

Permalink
two-way binding
Browse files Browse the repository at this point in the history
  • Loading branch information
kleber-jg committed Aug 31, 2021
1 parent 34f81dc commit e01ed6c
Show file tree
Hide file tree
Showing 10 changed files with 78 additions and 28 deletions.
4 changes: 2 additions & 2 deletions TODO.todo
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,18 @@ Rotation:

Properties view:
[x] properties panel
[x] two-way binding
[ ] default properties editor for built in types
[x] string
[x] boolean
[x] number
[ ] object
[ ] array
[ ] custom properties editor for PIXI/Phaser common types
[ ] Point
[x] Point
[ ] Rectangle
[ ] Matrix
[ ] optimization: shold I create a object pool for PropertyEditors?
[ ] two-way binding

General Features:
[x] editor layout
Expand Down
15 changes: 9 additions & 6 deletions src/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ export enum DataOrigin {
INSPECTOR = 1,
};

export type PropertyChangedListener = (property: string, value: any, obj?: PIXI.DisplayObject, from?: DataOrigin) => void;

class DataClass {
private _selectedObject: PIXI.DisplayObject;

Expand All @@ -11,17 +13,18 @@ class DataClass {
public get selectedObject() { return this._selectedObject; }

public selectObject(value: PIXI.DisplayObject) {
if (value === this._selectedObject) return;
this._selectedObject = value;
this.onSelectedObjectChanged.dispatch(value);
}

private readonly onPropertyChanged: Record<DataOrigin, Phaser.Signal> = {
[DataOrigin.EDITOR]: new Phaser.Signal(),
[DataOrigin.INSPECTOR]: new Phaser.Signal(),
private readonly onPropertyChanged: Record<DataOrigin, PropertyChangedListener> = {
[DataOrigin.EDITOR]: null,
[DataOrigin.INSPECTOR]: null,
};

public addPropertyChangedListener(from: DataOrigin, listener: (property: string, value: any, obj?: PIXI.DisplayObject, from?: DataOrigin) => void, context?: any) {
this.onPropertyChanged[from].add(listener, context);
public setPropertyChangedListener(from: DataOrigin, listener: PropertyChangedListener) {
this.onPropertyChanged[from] = listener;
}

public propertyChanged(property: string, value: any, from: DataOrigin) {
Expand All @@ -40,7 +43,7 @@ class DataClass {
this._scheduledEvents = {};
Object.keys(events).forEach(k => {
const e = events[k];
this.onPropertyChanged[e.from].dispatch(k, e.value, this._selectedObject, e.from);
this.onPropertyChanged[e.from](k, e.value, this._selectedObject, e.from);
});
}
}
Expand Down
7 changes: 5 additions & 2 deletions src/editor/editor.view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,14 @@ export class EditorView extends Phaser.Group {

this.selection = new Selection(game);
this.addChild(this.selection);
Data.addPropertyChangedListener(DataOrigin.INSPECTOR, this.onPropertyChangedInsideInspector, this);
Data.setPropertyChangedListener(DataOrigin.INSPECTOR, this.onPropertyChangedInsideInspector.bind(this));
}

private onPropertyChangedInsideInspector(property: string, value: any, obj: PIXI.DisplayObject) {
if (obj && property in obj) obj[property] = value;
if (!(obj && property in obj)) return;
obj[property] = value;
obj.updateTransform();
this.selection.redraw();
}

private createTouchArea(game: Phaser.Game): Phaser.Graphics {
Expand Down
2 changes: 1 addition & 1 deletion src/editor/selection/selection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export class Selection extends Phaser.Group {
if (this.visible = !!obj) this.redraw();
}

private redraw() {
public redraw() {
this.view.clear();
if (!this._selectedObject) return;
const bounds = this._selectedObject.getBounds();
Expand Down
8 changes: 6 additions & 2 deletions src/ui/properties/editors/boolean/boolean-property-editor.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { Data, DataOrigin } from 'data';
import { PropertyInspectionData } from 'ui/properties-editors';
import { PropertyEditor } from '../property-editor';

export class BooleanPropertyEditor extends PropertyEditor<boolean> {
public static readonly tagName: string = 'phed-boolean-property-editor';

private input: HTMLElement;
private input: HTMLInputElement;

protected createInnerContent(value: boolean, fieldId: string, prop: PropertyInspectionData) {
const input = document.createElement('input');
Expand All @@ -13,14 +14,17 @@ export class BooleanPropertyEditor extends PropertyEditor<boolean> {
input.setAttribute('type', 'checkbox');
if (value) input.setAttribute('checked', '');
if (prop.data) Object.keys(prop.data).forEach(p => input.setAttribute(p, prop.data[p]));
this.onchange = this.onValueChanged.bind(this);

return input;
}

public updateContent(value: boolean) {
public doUpdateContent(value: boolean) {
if (value) this.input.setAttribute('checked', '');
else this.input.removeAttribute('checked');
}

public getInternalValue() { return this.input.checked; }
}

customElements.define(BooleanPropertyEditor.tagName, BooleanPropertyEditor);
15 changes: 11 additions & 4 deletions src/ui/properties/editors/number/number-property-editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,30 @@ import { PropertyEditor } from '../property-editor';
export class NumberPropertyEditor extends PropertyEditor<number> {
public static readonly tagName: string = 'phed-number-property-editor';

private input: HTMLElement;
private input: HTMLInputElement;

protected createInnerContent(value: number, fieldId: string, prop: PropertyInspectionData) {
value = value === null || isNaN(value) ? 0 : value;
const input = this.input = document.createElement('input');
input.id = fieldId;

input.setAttribute('type', 'number');
input.setAttribute('value', value.toString());
this.input.value = value.toString();
this.onchange = this.onValueChanged.bind(this);

if (prop.data) Object.keys(prop.data).forEach(p => input.setAttribute(p, prop.data[p]));

return input;
}

public updateContent(value: number) {
public doUpdateContent(value: number) {
value = value === null || isNaN(value) ? 0 : value;
this.input.setAttribute('value', value.toString());
this.input.value = value.toString();
}

public getInternalValue() {
const value = parseFloat(this.input.value);
return isNaN(value) ? 0 : value;
}
}

Expand Down
22 changes: 17 additions & 5 deletions src/ui/properties/editors/point/point-property-editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export class PointPropertyEditor extends PropertyEditor<PIXI.Point> {

private xinput: NumberPropertyEditor;
private yinput: NumberPropertyEditor;
private internalValue = new Phaser.Point();

public connectedCallback() {
super.connectedCallback();
Expand All @@ -14,24 +15,35 @@ export class PointPropertyEditor extends PropertyEditor<PIXI.Point> {

protected createInnerContent(value: PIXI.Point, fieldId: string) {
value = value ?? new PIXI.Point(0, 0);
this.internalValue.set(value.x, value.y);

const parent = document.createElement('div');
this.appendChild(parent);

const xinput = this.xinput = document.createElement(NumberPropertyEditor.tagName) as NumberPropertyEditor;
const xinput = this.xinput = document.createElement(NumberPropertyEditor.tagName,) as NumberPropertyEditor;
xinput.setContent({ name: 'x', typeHint: 'number' }, value.x, fieldId);
xinput.onchange = null;//this.onValueChanged.bind(this);
parent.appendChild(xinput);

const yinput = this.yinput = document.createElement(NumberPropertyEditor.tagName) as NumberPropertyEditor;
yinput.setContent({ name: 'y', typeHint: 'number' }, value.y);
yinput.onchange = null;//this.onValueChanged.bind(this);
parent.appendChild(yinput);

this.onchange = this.onValueChanged.bind(this);

return parent;
}

public updateContent(value: PIXI.Point) {
this.xinput.updateContent(value.x);
this.yinput.updateContent(value.y);
public doUpdateContent(value: PIXI.Point) {
this.xinput.doUpdateContent(value.x);
this.yinput.doUpdateContent(value.y);
this.internalValue.set(value.x, value.y);
}

public getInternalValue() {
this.internalValue.set(this.xinput.getInternalValue(), this.yinput.getInternalValue());
return this.internalValue.clone();
}
}

Expand Down
21 changes: 20 additions & 1 deletion src/ui/properties/editors/property-editor.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { Data, DataOrigin } from 'data';
import { PropertyInspectionData } from 'ui/properties-editors';
import './property-editor.scss';

export abstract class PropertyEditor<T> extends HTMLElement {
protected prop: PropertyInspectionData;
protected changedOnEditor = false;

protected static randomId() { return (Math.floor(Math.random() * 1000000)).toString(16); }

Expand Down Expand Up @@ -39,5 +41,22 @@ export abstract class PropertyEditor<T> extends HTMLElement {

protected abstract createInnerContent(value: T, fieldId: string, prop: PropertyInspectionData): HTMLElement;

public abstract updateContent(value: T): void;
public updateContent(value: T) {
this.changedOnEditor = true;
this.doUpdateContent(value);
}

/**
* Actually updates the value coming from the editor
* @param value The value set on editor
*/
public abstract doUpdateContent(value: T): void;


protected onValueChanged(e: Event) {
if (this.changedOnEditor) this.changedOnEditor = false;
else Data.propertyChanged(this.prop.name, this.getInternalValue(e), DataOrigin.INSPECTOR);
}

public abstract getInternalValue(e: Event): T;
}
9 changes: 5 additions & 4 deletions src/ui/properties/editors/string/string-property-editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,21 @@ import { PropertyEditor } from '../property-editor';

export class StringPropertyEditor extends PropertyEditor<string> {
public static readonly tagName: string = 'phed-string-property-editor';
private input: HTMLElement;
private input: HTMLInputElement;

protected createInnerContent(value: string, fieldId: string, prop: PropertyInspectionData) {
const input = this.input = document.createElement('input');
input.id = fieldId;
input.setAttribute('type', 'text');
input.setAttribute('value', value ?? '');
this.onchange = this.onValueChanged.bind(this);

if (prop.data) Object.keys(prop.data).forEach(p => input.setAttribute(p, prop.data[p]));
return input;
}

public updateContent(value: string) {
this.input.setAttribute('value', value ?? '');
}
public doUpdateContent(value: string) { this.input.setAttribute('value', value ?? ''); }
public getInternalValue() { return this.input.value || ''; }
}

customElements.define(StringPropertyEditor.tagName, StringPropertyEditor);
3 changes: 2 additions & 1 deletion src/ui/properties/panel/properties-panel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export class PropertiesPanel extends Widget {
content.classList.add('content');
this.appendChild(content);

Data.addPropertyChangedListener(DataOrigin.EDITOR, this.onPropertyChangedInsideEditor, this);
Data.setPropertyChangedListener(DataOrigin.EDITOR, this.onPropertyChangedInsideEditor.bind(this));
}

private onPropertyChangedInsideEditor(property: string, value: any) {
Expand All @@ -38,6 +38,7 @@ export class PropertiesPanel extends Widget {

public selectObject(obj: PIXI.DisplayObject) {
// TODO what happen with the instances? Are they garbage collected?
console.log('here');
const emptyContent = this.content.cloneNode(false);
this.replaceChild(emptyContent, this.content);
this.content = emptyContent as HTMLElement;
Expand Down

0 comments on commit e01ed6c

Please sign in to comment.