Sometimes it would be nice to integrate tweakpane with a custom object/state management solution. The on('change') API works for most cases, but it breaks in certain circumstances.
I've detailed a custom plugin solution in #606. Here I'd like to suggest an alternative approach that involves passing an extended BindingTarget.
Problem
I'm going present one concrete use case. Let's consider a configuration object to be reused by tweakpane and zustand as state management.
import { subscribeWithSelector } from 'zustand/middleware';
import { createStore } from 'zustand/vanilla';
const state = {
uvDisplacementOffset: 5.0,
uvStrengthOffset: 5.0,
// other props...
};
const store = createStore(subscribeWithSelector<typeof state>(() => state));
const pane = new Pane({ title: 'Debug Options' });
const binding = pane.addBinding(store.state, 'uvDisplacementOffset', {
// options...
});
binding.on('change', ({ value, target }) => store.setState({ [target.key]: value }));
Now the store is type-safe and I can use it like this:
However, that equality function is now efectively broken, because internally the BindingTarget class is going to write the prop before the change event fires. So the previous and next values are always the same and the callback never fires.
In addition, monitor inputs will never update from other setState calls.
Proposed solution
The BindingTarget class is already exported by the core package. I could then extend it and override the read, write, and writeProperty methods so they leverage setState instead.
import {
Bindable,
BindingTarget,
} from '@tweakpane/core';
class CustomBindingTarget extends BindingTarget<T extends Bindable, K extends keyof T> {
public readonly key: K;
private readonly obj_: T;
constructor(obj: T, key: K) {
this.obj_ = obj;
this.key = key;
}
public read(): T[K]
public write(value: T[K]): void
public writeProperty(name: K, value: T[K]): void
}
const bindingTarget = new CustomBindingTarget(store.getState());
I'm not sure about the API implementation. It could be integrated within addBinding or perhaps create a new addBindingTarget method.
From what I could see, a base implementation without generics would not be too complicated. But things get muddier when making BindingTarget<T, K> a generic. I'd have to propagate the types along a bunch of other methods within the RackApi class and beyond.
Thoughts? Is this something you would consider, maybe with a POC to see how it works?
Sometimes it would be nice to integrate tweakpane with a custom object/state management solution. The
on('change')
API works for most cases, but it breaks in certain circumstances.I've detailed a custom plugin solution in #606. Here I'd like to suggest an alternative approach that involves passing an extended
BindingTarget
.Problem
I'm going present one concrete use case. Let's consider a configuration object to be reused by tweakpane and zustand as state management.
Now the store is type-safe and I can use it like this:
However, that equality function is now efectively broken, because internally the
BindingTarget
class is going to write the prop before thechange
event fires. So the previous and next values are always the same and the callback never fires.In addition, monitor inputs will never update from other
setState
calls.Proposed solution
The
BindingTarget
class is already exported by the core package. I could then extend it and override theread
,write
, andwriteProperty
methods so they leveragesetState
instead.I'm not sure about the API implementation. It could be integrated within
addBinding
or perhaps create a newaddBindingTarget
method.From what I could see, a base implementation without generics would not be too complicated. But things get muddier when making
BindingTarget<T, K>
a generic. I'd have to propagate the types along a bunch of other methods within theRackApi
class and beyond.Thoughts? Is this something you would consider, maybe with a POC to see how it works?