feat: BindableFloat — universal value source binding for all scalar properties
Lint & Test / test (push) Successful in 1m20s
Lint & Test / test (push) Successful in 1m20s
Introduce BindableFloat abstraction that allows any numeric property to be
either a static value or dynamically driven by a ValueSource. Backward-compatible
serialization: plain float when unbound, {value, source_id} dict when bound.
Backend:
- storage/bindable.py — BindableFloat dataclass + bfloat() helper
- 25+ scalar properties converted across all entity types
- Runtime VS acquisition in ColorStripStreamManager for CSS bindings
- All stream hot loops use self.resolve() for live values
- KeyColorsColorStripStream now inherits ColorStripStream
Frontend:
- BindableScalarWidget (slider + VS picker toggle) for all editors
- TypeScript BindableFloat type + helpers
- Graph editor edges for all bindable properties
- Audio source channel IconSelect grid
Fixes: daylight longitude, candlelight wind_strength/candle_type from_dict
This commit is contained in:
@@ -30,6 +30,8 @@ class AudioSourceModal extends Modal {
|
||||
|
||||
onForceClose() {
|
||||
if (_audioSourceTagsInput) { _audioSourceTagsInput.destroy(); _audioSourceTagsInput = null; }
|
||||
if (_asChannelIconSelect) { _asChannelIconSelect.destroy(); _asChannelIconSelect = null; }
|
||||
if (_asBandIconSelect) { _asBandIconSelect.destroy(); _asBandIconSelect = null; }
|
||||
}
|
||||
|
||||
snapshotValues() {
|
||||
@@ -58,6 +60,7 @@ let _asDeviceEntitySelect: EntitySelect | null = null;
|
||||
let _asParentEntitySelect: EntitySelect | null = null;
|
||||
let _asBandParentEntitySelect: EntitySelect | null = null;
|
||||
let _asBandIconSelect: IconSelect | null = null;
|
||||
let _asChannelIconSelect: IconSelect | null = null;
|
||||
|
||||
const _svg = (d: string): string => `<svg class="icon" viewBox="0 0 24 24">${d}</svg>`;
|
||||
|
||||
@@ -136,7 +139,7 @@ export async function showAudioSourceModal(sourceType: any, editData?: any) {
|
||||
} else if (editData.source_type === 'mono') {
|
||||
_loadMultichannelSources(editData.audio_source_id);
|
||||
(document.getElementById('audio-source-channel') as HTMLSelectElement).value = editData.channel || 'mono';
|
||||
(document.getElementById('audio-source-channel') as HTMLSelectElement).onchange = () => _autoGenerateAudioSourceName();
|
||||
_ensureChannelIconSelect();
|
||||
} else if (editData.source_type === 'band_extract') {
|
||||
_loadBandParentSources(editData.audio_source_id);
|
||||
(document.getElementById('audio-source-band') as HTMLSelectElement).value = editData.band || 'bass';
|
||||
@@ -155,7 +158,7 @@ export async function showAudioSourceModal(sourceType: any, editData?: any) {
|
||||
await _loadAudioDevices();
|
||||
} else if (sourceType === 'mono') {
|
||||
_loadMultichannelSources();
|
||||
(document.getElementById('audio-source-channel') as HTMLSelectElement).onchange = () => _autoGenerateAudioSourceName();
|
||||
_ensureChannelIconSelect();
|
||||
} else if (sourceType === 'band_extract') {
|
||||
_loadBandParentSources();
|
||||
(document.getElementById('audio-source-band') as HTMLSelectElement).value = 'bass';
|
||||
@@ -426,6 +429,28 @@ function _ensureBandIconSelect() {
|
||||
});
|
||||
}
|
||||
|
||||
const _icon = (d: string) => `<svg class="icon" viewBox="0 0 24 24">${d}</svg>`;
|
||||
|
||||
function _ensureChannelIconSelect() {
|
||||
const sel = document.getElementById('audio-source-channel') as HTMLSelectElement | null;
|
||||
if (!sel) return;
|
||||
const items = [
|
||||
{ value: 'mono', icon: _icon(P.headphones), label: t('audio_source.channel.mono'), desc: t('audio_source.channel.mono.desc') },
|
||||
{ value: 'left', icon: _icon(P.volume2), label: t('audio_source.channel.left'), desc: t('audio_source.channel.left.desc') },
|
||||
{ value: 'right', icon: _icon(P.volume2), label: t('audio_source.channel.right'), desc: t('audio_source.channel.right.desc') },
|
||||
];
|
||||
if (_asChannelIconSelect) {
|
||||
_asChannelIconSelect.updateItems(items);
|
||||
return;
|
||||
}
|
||||
_asChannelIconSelect = new IconSelect({
|
||||
target: sel,
|
||||
items,
|
||||
columns: 3,
|
||||
onChange: () => _autoGenerateAudioSourceName(),
|
||||
});
|
||||
}
|
||||
|
||||
function _loadBandParentSources(selectedId?: any) {
|
||||
const select = document.getElementById('audio-source-band-parent') as HTMLSelectElement | null;
|
||||
if (!select) return;
|
||||
|
||||
Reference in New Issue
Block a user