Skip to content

Commit

Permalink
Widgets now can optionally effect node state
Browse files Browse the repository at this point in the history
  • Loading branch information
EliCDavis committed Sep 15, 2024
1 parent 5190697 commit 5194d75
Show file tree
Hide file tree
Showing 12 changed files with 239 additions and 61 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Node Flow is a javascript library that enables developers to build node based to

## Install

Download the latest build [here](https://github.com/EliCDavis/node-flow/blob/gh-pages/dist/web/index.js).
Download the latest build [here](https://raw.githubusercontent.com/EliCDavis/node-flow/gh-pages/dist/web/NodeFlow.js).

## API

Expand Down
4 changes: 2 additions & 2 deletions esbuild.dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { NodeGlobalsPolyfillPlugin } from '@esbuild-plugins/node-globals-polyfil

const buildOptsNode: BuildOptions = {
entryPoints: ['./src/index.ts'],
outfile: './dist/node/index.js',
outfile: './dist/node/NodeFlow.js',
platform: 'node',
target: ['es2018'],
format: 'cjs',
Expand All @@ -20,7 +20,7 @@ const buildOptsNode: BuildOptions = {
const buildOptsWeb: BuildOptions = {
entryPoints: ['./src/index.ts'],
// inject: [],
outfile: './dist/web/index.js',
outfile: './dist/web/NodeFlow.js',
// external: [],
platform: 'browser',
target: ['esNext'],
Expand Down
2 changes: 1 addition & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
}
</style>

<script src="./dist/web/index.js"></script>
<script src="./dist/web/NodeFlow.js"></script>
</head>

<body>
Expand Down
3 changes: 2 additions & 1 deletion src/graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,8 @@ export class NodeFlowGraph {

#render(): void {
if (this.#canvas.parentNode !== null) {
var rect = this.#canvas.parentNode.getBoundingClientRect();
// Stupid as any because typescript doesn't think it exists
var rect = (this.#canvas.parentNode as any).getBoundingClientRect();
this.#canvas.width = rect.width;
this.#canvas.height = rect.height;
}
Expand Down
67 changes: 62 additions & 5 deletions src/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@ import { Theme } from "./theme";
import { TextAlign } from "./styles/canvasTextAlign";
import { TextBaseline } from "./styles/canvasTextBaseline";

type AnyPropertyChangeCallback = (propertyName: string, oldValue: any, newValue: any) => void
type PropertyChangeCallback = (oldValue: any, newValue: any) => void

interface NodeData {
[name: string]: any;
}

export interface WidgetConfig {
type?: string,
config?: any
Expand All @@ -26,6 +33,7 @@ export interface FlowNodeConfig {
position?: Vector2;
title?: string;
locked?: boolean;
data?: NodeData;

// Ports
inputs?: Array<PortConfig>;
Expand Down Expand Up @@ -113,6 +121,12 @@ export class FlowNode {

#widgetPositions: List<Box>;

#data: NodeData;

#registeredAnyPropertyChangeCallbacks: Array<AnyPropertyChangeCallback>;

#registeredPropertyChangeCallbacks: Map<string, Array<PropertyChangeCallback>>;

constructor(config?: FlowNodeConfig) {
this.#input = new Array<Port>();
this.#output = new Array<Port>();
Expand All @@ -122,6 +136,9 @@ export class FlowNode {
this.#widgetPositions = new List<Box>();
this.#elementSpacing = 15;
this.#locked = config?.locked === undefined ? false : config.locked;
this.#data = config?.data === undefined ? {} : config?.data;
this.#registeredPropertyChangeCallbacks = new Map<string, Array<PropertyChangeCallback>>();
this.#registeredAnyPropertyChangeCallbacks = new Array<AnyPropertyChangeCallback>();

this.#selected = false;
this.#onSelect = config?.onSelect;
Expand Down Expand Up @@ -187,7 +204,7 @@ export class FlowNode {
if (widget.type === undefined) {
continue;
}
this.addWidget(GlobalWidgetFactory.create(widget.type, widget.config))
this.addWidget(GlobalWidgetFactory.create(this, widget.type, widget.config))
}
}
}
Expand All @@ -206,6 +223,46 @@ export class FlowNode {
}
}

public subscribeToAnyPropertyChange(callback: AnyPropertyChangeCallback): void {
if (callback === undefined || callback === null) {
}
this.#registeredAnyPropertyChangeCallbacks.push(callback);
}

public subscribeToProperty(name: string, callback: PropertyChangeCallback): void {
if (!this.#registeredPropertyChangeCallbacks.has(name)) {
this.#registeredPropertyChangeCallbacks.set(name, []);
}

const callbacks = this.#registeredPropertyChangeCallbacks.get(name);
if (callbacks === undefined) {
return;
}
callbacks.push(callback);
}

public setProperty(name: string, value: any): void {
const oldValue = this.#data[name];
this.#data[name] = value;

for (let i = 0; i < this.#registeredAnyPropertyChangeCallbacks.length; i++) {
this.#registeredAnyPropertyChangeCallbacks[i](name, oldValue, value);
}

const callbacks = this.#registeredPropertyChangeCallbacks.get(name);
if (callbacks === undefined) {
return;
}

for (let i = 0; i < callbacks.length; i++) {
callbacks[i](oldValue, value);
}
}

public getProperty(name: string): any {
return this.#data[name];
}

public unselect(): void {
if (!this.#selected) {
return;
Expand Down Expand Up @@ -403,10 +460,10 @@ export class FlowNode {
ctx.fillStyle = "#154050"
ctx.beginPath();
ctx.roundRect(
titleBox.Position.x + (borderSize*scale*0.5),
titleBox.Position.y + (borderSize*scale*0.5),
titleBox.Size.x - (borderSize*scale),
titleBox.Size.y - (borderSize*scale*0.5),
titleBox.Position.x + (borderSize * scale * 0.5),
titleBox.Position.y + (borderSize * scale * 0.5),
titleBox.Size.x - (borderSize * scale),
titleBox.Size.y - (borderSize * scale * 0.5),
[nodeStyle.radius() * scale, nodeStyle.radius() * scale, 0, 0]
);
ctx.fill();
Expand Down
2 changes: 0 additions & 2 deletions src/widgets/button.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import { Theme } from "../theme";
import { BoxStyle } from "../styles/box";
import { TextStyle, TextStyleConfig } from "../styles/text";
import { TextBoxStyle, TextBoxStyleConfig, TextBoxStyleWithFallback } from "../styles/textBox";
import { Box, InBox } from "../types/box";
import { Vector2 } from "../types/vector2";
Expand Down
40 changes: 30 additions & 10 deletions src/widgets/color.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@ import { height, width } from "./widget";
import { Popup } from "../popup";
import { TextBoxStyle } from '../styles/textBox';
import { Theme } from "../theme";
import { FlowNode } from "../node";

export interface ColorWidgetConfig {
value?: string;

property?: string;

textStyle?: TextStyleConfig;

callback?: (newColor: string) => void;
Expand All @@ -30,6 +33,10 @@ function contrastColor(color: string): string {

export class ColorWidget {

#node: FlowNode;

#nodeProperty: string | undefined;

#value: string;

#contrast: string;
Expand All @@ -38,9 +45,10 @@ export class ColorWidget {

#callback?: (newColor: string) => void;

constructor(config?: ColorWidgetConfig) {
this.#value = config?.value === undefined ? "#000000" : config?.value;
this.#contrast = contrastColor(this.#value);
constructor(node: FlowNode, config?: ColorWidgetConfig) {
this.#node = node;
this.#nodeProperty = config?.property;
this.Set(config?.value === undefined ? "#000000" : config?.value);
this.#textBoxStyle = new TextBoxStyle({
box: {
color: this.#value,
Expand All @@ -53,25 +61,37 @@ export class ColorWidget {
});
this.#callback = config?.callback;


this.#textBoxStyle.setTextColor(this.#contrast);
if (this.#nodeProperty !== undefined) {
this.#node.subscribeToProperty(this.#nodeProperty, (oldVal, newVal) => {
this.Set(newVal);
});
}
}

Size(): Vector2 {
return { "x": width, "y": height }
}

Set(value: string): void {
this.#value = value;
this.#contrast = contrastColor(this.#value);
if (this.#value === value) {
return;
}

this.#textBoxStyle.setBoxColor(this.#value);
this.#textBoxStyle.setBorderColor(this.#contrast);
this.#textBoxStyle.setTextColor(this.#contrast);
this.#value = value;

if (this.#callback !== undefined) {
this.#callback(this.#value);
}

if (this.#nodeProperty !== undefined) {
this.#node.setProperty(this.#nodeProperty, this.#value);
}

// Update Styling
this.#contrast = contrastColor(this.#value);
this.#textBoxStyle.setBoxColor(this.#value);
this.#textBoxStyle.setBorderColor(this.#contrast);
this.#textBoxStyle.setTextColor(this.#contrast);
}

ClickStart(): void {
Expand Down
31 changes: 16 additions & 15 deletions src/widgets/factory.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { Widget } from "./widget";
import { NumberWidget } from './number';
import { ButtonWidget } from './button';
import { ColorWidget } from './color';
import { SliderWidget } from './slider';
import { StringWidget } from './string';
import { ToggleWidget } from './toggle';
import { NumberWidget, NumberWidgetConfig } from './number';
import { ButtonWidget, ButtonWidgetConfig } from './button';
import { ColorWidget, ColorWidgetConfig } from './color';
import { SliderWidget, SliderWidgetConfig } from './slider';
import { StringWidget, StringWidgetConfig } from './string';
import { ToggleWidget, ToggleWidgetConfig } from './toggle';
import { FlowNode } from "../node";

export type WidgetBuilder = (confg?: any) => Widget;
export type WidgetBuilder = (node: FlowNode, confg?: any) => Widget;

class WidgetFactory {

Expand All @@ -20,22 +21,22 @@ class WidgetFactory {
this.#registeredWidgets.set(widgetType, builder);
}

create(widgetType: string, config: any): Widget {
create(node: FlowNode, widgetType: string, config: any): Widget {
const builder = this.#registeredWidgets.get(widgetType)
if (builder === undefined) {
throw new Error("no builder registered for widget: " + widgetType);
}
return builder(config);
return builder(node, config);
}
}

const GlobalWidgetFactory = new WidgetFactory();

GlobalWidgetFactory.register("button", (config) => new ButtonWidget(config));
GlobalWidgetFactory.register("number", (config) => new NumberWidget(config));
GlobalWidgetFactory.register("color", (config) => new ColorWidget(config));
GlobalWidgetFactory.register("slider", (config) => new SliderWidget(config));
GlobalWidgetFactory.register("string", (config) => new StringWidget(config));
GlobalWidgetFactory.register("toggle", (config) => new ToggleWidget(config));
GlobalWidgetFactory.register("button", (node: FlowNode, config?: ButtonWidgetConfig) => new ButtonWidget(config));
GlobalWidgetFactory.register("number", (node: FlowNode, config?: NumberWidgetConfig) => new NumberWidget(node, config));
GlobalWidgetFactory.register("color", (node: FlowNode, config?: ColorWidgetConfig) => new ColorWidget(node, config));
GlobalWidgetFactory.register("slider", (node: FlowNode, config?: SliderWidgetConfig) => new SliderWidget(node, config));
GlobalWidgetFactory.register("string", (node: FlowNode, config?: StringWidgetConfig) => new StringWidget(node, config));
GlobalWidgetFactory.register("toggle", (node: FlowNode, config?: ToggleWidgetConfig) => new ToggleWidget(node, config));

export { GlobalWidgetFactory };
36 changes: 30 additions & 6 deletions src/widgets/number.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@ import { TextBoxStyle, TextBoxStyleConfig, TextBoxStyleWithFallback } from "../s
import { Box, InBox } from '../types/box';
import { Vector2 } from "../types/vector2";
import { height, width } from "./widget";
import { FlowNode } from "../node";

export interface NumberWidgetConfig {
value?: number;

property?: string;

idleBoxStyle?: TextBoxStyleConfig;

highlightBoxStyle?: TextBoxStyleConfig;
Expand All @@ -16,7 +19,11 @@ export interface NumberWidgetConfig {
}

export class NumberWidget {


#node: FlowNode;

#nodeProperty: string | undefined;

#value: number;

#idleBoxStyle: TextBoxStyle;
Expand All @@ -27,8 +34,10 @@ export class NumberWidget {

#callback?: (newNumber: number) => void;

constructor(config?: NumberWidgetConfig) {
this.#value = config?.value === undefined ? 0 : config?.value;
constructor(node: FlowNode, config?: NumberWidgetConfig) {
this.#node = node;
this.#nodeProperty = config?.property;
this.Set(config?.value === undefined ? 0 : config?.value);
this.#idleBoxStyle = new TextBoxStyle(TextBoxStyleWithFallback(config?.idleBoxStyle, {
box: {
color: Theme.Widget.BackgroundColor,
Expand All @@ -53,20 +62,35 @@ export class NumberWidget {
}));
this.#callback = config?.callback;

// https://stackoverflow.com/questions/5765398/whats-the-best-way-to-convert-a-number-to-a-string-in-javascript
this.#text = '' + this.#value;
if (this.#nodeProperty !== undefined) {
this.#node.subscribeToProperty(this.#nodeProperty, (oldVal, newVal) => {
this.Set(newVal);
});
}

}

Size(): Vector2 {
return { "x": width, "y": height }
}

Set(newNumber: number): void {
if (this.#value === newNumber) {
return;
}

this.#value = newNumber;
this.#text = '' + this.#value;

if (this.#nodeProperty !== undefined) {
this.#node.setProperty(this.#nodeProperty, this.#value);
}

if (this.#callback !== undefined) {
this.#callback(this.#value);
}

// https://stackoverflow.com/questions/5765398/whats-the-best-way-to-convert-a-number-to-a-string-in-javascript
this.#text = '' + this.#value;
}

ClickStart(): void {
Expand Down
Loading

0 comments on commit 5194d75

Please sign in to comment.