diff --git a/CHANGELOG.md b/CHANGELOG.md index edc8f0e8d..03b2ac896 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ - Use `@use-gesture/vanilla` for wheel gesture lifecycle handling and faster trackpad zoom behavior. - Add auto-layout inspector controls for min/max dimensions, Auto gap distribution, wrap cross-axis gap, and two-axis padding controls. - Add signed Tauri updater configuration, release artifacts, startup update checks, and a native Check for Updates menu item. -- Add inspector variable binding controls for fill and stroke colors, including existing variable selection and named color variable creation. +- Add inspector variable binding controls for fill and stroke colors plus width/height sizing, including existing variable selection and named variable creation. ### Fixes diff --git a/packages/core/src/editor/variables.ts b/packages/core/src/editor/variables.ts index 14284c271..5bc0c8c88 100644 --- a/packages/core/src/editor/variables.ts +++ b/packages/core/src/editor/variables.ts @@ -14,6 +14,10 @@ export function createVariableActions(ctx: EditorContext) { return ctx.graph.resolveColorVariable(id) } + function resolveNumberVariable(id: string) { + return ctx.graph.resolveNumberVariable(id) + } + function getVariablesForCollection(collectionId: string) { return ctx.graph.getVariablesForCollection(collectionId) } @@ -177,6 +181,7 @@ export function createVariableActions(ctx: EditorContext) { getVariablesByType, getVariable, resolveColorVariable, + resolveNumberVariable, getVariablesForCollection, getCollection, getCollections, diff --git a/packages/vue/src/controls/number-variable-binding/use.ts b/packages/vue/src/controls/number-variable-binding/use.ts new file mode 100644 index 000000000..432100cc1 --- /dev/null +++ b/packages/vue/src/controls/number-variable-binding/use.ts @@ -0,0 +1,55 @@ +import { useVariableBinding } from '#vue/controls/variable-binding/use' +import { randomHex } from '@open-pencil/core/random' + +import type { VariableCollection } from '@open-pencil/core/scene-graph' + +const FALLBACK_NUMBER_VARIABLE_NAME = 'New number' + +type NumberBindingPath = 'width' | 'height' + +export function useNumberVariableBinding(path: NumberBindingPath) { + const binding = useVariableBinding({ + type: 'FLOAT', + path + }) + + function numberCollection(): VariableCollection { + const existing = binding.store + .getCollections() + .find((collection) => + collection.variableIds.some((variableId) => binding.store.getVariable(variableId)?.type === 'FLOAT') + ) + if (existing) return existing + + const collection: VariableCollection = { + id: `col:${randomHex(8)}`, + name: 'Numbers', + modes: [{ modeId: 'default', name: 'Mode 1' }], + defaultModeId: 'default', + variableIds: [] + } + binding.store.addCollection(collection) + return collection + } + + function createAndBindVariable(nodeId: string, value: number, name = FALLBACK_NUMBER_VARIABLE_NAME) { + const collection = numberCollection() + const id = `var:${randomHex(8)}` + binding.store.addVariable({ + id, + name: name.trim() || FALLBACK_NUMBER_VARIABLE_NAME, + type: 'FLOAT', + collectionId: collection.id, + valuesByMode: Object.fromEntries(collection.modes.map((mode) => [mode.modeId, value])), + description: '', + hiddenFromPublishing: false + }) + binding.bindVariable(nodeId, id) + } + + return { + ...binding, + numberVariables: binding.variables, + createAndBindVariable + } +} diff --git a/packages/vue/src/i18n/messages.ts b/packages/vue/src/i18n/messages.ts index ce9e665af..b2f22b945 100644 --- a/packages/vue/src/i18n/messages.ts +++ b/packages/vue/src/i18n/messages.ts @@ -174,6 +174,7 @@ export const panelMessages = i18n('panels', { create: 'Create', createVariable: 'Create variable', createColorVariable: params('Create color variable from {value}'), + createNumberVariable: params('Create number variable from {value}'), variableName: 'Variable name', mixed: 'Mixed', layersCount: params('{count} layers'), diff --git a/packages/vue/src/index.ts b/packages/vue/src/index.ts index 8d606b306..9f63ddf73 100644 --- a/packages/vue/src/index.ts +++ b/packages/vue/src/index.ts @@ -55,6 +55,7 @@ export { useExport } from '#vue/document/export/use' export type { ExportFormatId } from '#vue/document/export/use' export { useFillControls } from '#vue/controls/fill/use' export { useColorVariableBinding } from '#vue/controls/color-variable-binding/use' +export { useNumberVariableBinding } from '#vue/controls/number-variable-binding/use' export { useVariableBinding } from '#vue/controls/variable-binding/use' export type { VariableBindingState, diff --git a/packages/vue/src/locales/de.json b/packages/vue/src/locales/de.json index b52bb598d..408bdcb28 100644 --- a/packages/vue/src/locales/de.json +++ b/packages/vue/src/locales/de.json @@ -172,7 +172,8 @@ "createVariable": "Variable erstellen", "createColorVariable": "Farbvariable aus {value} erstellen", "variableName": "Variablenname", - "create": "Erstellen" + "create": "Erstellen", + "createNumberVariable": "Zahlenvariable aus {value} erstellen" }, "pages": { "newPage": "Neue Seite", diff --git a/packages/vue/src/locales/es.json b/packages/vue/src/locales/es.json index f52c73f03..e9d311496 100644 --- a/packages/vue/src/locales/es.json +++ b/packages/vue/src/locales/es.json @@ -172,7 +172,8 @@ "createVariable": "Crear variable", "createColorVariable": "Crear variable de color desde {value}", "variableName": "Nombre de variable", - "create": "Crear" + "create": "Crear", + "createNumberVariable": "Crear variable numérica desde {value}" }, "pages": { "newPage": "Nueva página", diff --git a/packages/vue/src/locales/fr.json b/packages/vue/src/locales/fr.json index 7afa9a0e0..4202736c5 100644 --- a/packages/vue/src/locales/fr.json +++ b/packages/vue/src/locales/fr.json @@ -172,7 +172,8 @@ "createVariable": "Créer une variable", "createColorVariable": "Créer une variable de couleur depuis {value}", "variableName": "Nom de la variable", - "create": "Créer" + "create": "Créer", + "createNumberVariable": "Créer une variable numérique depuis {value}" }, "pages": { "newPage": "Nouvelle page", diff --git a/packages/vue/src/locales/it.json b/packages/vue/src/locales/it.json index 256b48126..9324941cb 100644 --- a/packages/vue/src/locales/it.json +++ b/packages/vue/src/locales/it.json @@ -172,7 +172,8 @@ "createVariable": "Crea variabile", "createColorVariable": "Crea variabile colore da {value}", "variableName": "Nome variabile", - "create": "Crea" + "create": "Crea", + "createNumberVariable": "Crea variabile numerica da {value}" }, "pages": { "newPage": "Nuova pagina", diff --git a/packages/vue/src/locales/pl.json b/packages/vue/src/locales/pl.json index 8f162109e..cdb8a6222 100644 --- a/packages/vue/src/locales/pl.json +++ b/packages/vue/src/locales/pl.json @@ -172,7 +172,8 @@ "createVariable": "Utwórz zmienną", "createColorVariable": "Utwórz zmienną koloru z {value}", "variableName": "Nazwa zmiennej", - "create": "Utwórz" + "create": "Utwórz", + "createNumberVariable": "Utwórz zmienną liczbową z {value}" }, "pages": { "newPage": "Nowa strona", diff --git a/packages/vue/src/locales/ru.json b/packages/vue/src/locales/ru.json index 15c1ceae3..8d4d1f324 100644 --- a/packages/vue/src/locales/ru.json +++ b/packages/vue/src/locales/ru.json @@ -172,7 +172,8 @@ "createVariable": "Создать переменную", "createColorVariable": "Создать переменную цвета из {value}", "variableName": "Имя переменной", - "create": "Создать" + "create": "Создать", + "createNumberVariable": "Создать числовую переменную из {value}" }, "pages": { "newPage": "Новая страница", diff --git a/packages/vue/src/locales/zh-CN.json b/packages/vue/src/locales/zh-CN.json index 9924b5216..ae3d8ef19 100644 --- a/packages/vue/src/locales/zh-CN.json +++ b/packages/vue/src/locales/zh-CN.json @@ -172,7 +172,8 @@ "createVariable": "创建变量", "createColorVariable": "从 {value} 创建颜色变量", "variableName": "变量名称", - "create": "创建" + "create": "创建", + "createNumberVariable": "从 {value} 创建数字变量" }, "pages": { "newPage": "新建页面", diff --git a/src/components/ScrubInput.vue b/src/components/ScrubInput.vue index 1a6ce2c15..aacdad59c 100644 --- a/src/components/ScrubInput.vue +++ b/src/components/ScrubInput.vue @@ -68,6 +68,7 @@ defineOptions({ inheritAttrs: false }) :max="max === Infinity ? undefined : max" :step="step" /> + (null) const heightFieldRef = ref(null) const limitFieldRefs = ref>({ @@ -39,7 +43,7 @@ const limitFieldRefs = ref>({ maxHeight: null }) -const { panels } = useI18n() +const { panels, dialogs } = useI18n() const sizingSelect = useSelectUI({ item: 'rounded py-1.5 pr-2 pl-6 text-xs' }) const widthLimitItems = [ @@ -118,6 +122,35 @@ function handleLimitSelect(prop: SizeLimitProp, value: string) { else if (value === 'REMOVE') ctx.removeSizeLimit(prop) } +function resolvedBoundNumber(axis: 'width' | 'height'): number | undefined { + const binding = axis === 'width' ? widthVariableBinding : heightVariableBinding + const variable = binding.getBoundVariable(ctx.node.id) + return variable ? binding.store.resolveNumberVariable(variable.id) : undefined +} + +function updateSizeProp(axis: 'width' | 'height', value: number) { + const binding = axis === 'width' ? widthVariableBinding : heightVariableBinding + if (binding.getBoundVariable(ctx.node.id)) binding.unbindVariable(ctx.node.id) + ctx.updateProp(axis, value) +} + +function commitSizeProp(axis: 'width' | 'height', value: number, previous: number) { + ctx.commitProp(axis, value, previous) +} + +function bindSizeVariable(axis: 'width' | 'height', variableId: string) { + const binding = axis === 'width' ? widthVariableBinding : heightVariableBinding + binding.bindVariable(ctx.node.id, variableId) + const value = binding.store.resolveNumberVariable(variableId) + if (value != null) ctx.updateProp(axis, value) +} + +function createAndBindSizeVariable(axis: 'width' | 'height', name: string) { + const binding = axis === 'width' ? widthVariableBinding : heightVariableBinding + const value = ctx.node[axis] + binding.createAndBindVariable(ctx.node.id, value, name) +} + function handleSizeSelect(axis: 'width' | 'height', value: SizeSelectValue) { if (value === 'FIXED' || value === 'HUG' || value === 'FILL') { if (axis === 'width') ctx.setWidthSizing(value) @@ -135,13 +168,35 @@ function handleSizeSelect(axis: 'width' | 'height', value: SizeSelectValue) {
-