From ffd305c1258a4e43b36011b0e7b74ab35d0d970e Mon Sep 17 00:00:00 2001 From: Danila Poyarkov Date: Sat, 28 Feb 2026 11:42:13 +0300 Subject: [PATCH] Page properties panel with canvas background color picker When nothing is selected, the right panel shows Page section with a color picker for the canvas background (like Figma). The page color is stored in reactive state and passed to the renderer. --- components.d.ts | 1 + src/components/PropertiesPanel.vue | 5 +++- src/components/properties/PageSection.vue | 34 +++++++++++++++++++++++ src/composables/use-canvas.ts | 1 + src/engine/renderer.ts | 3 +- src/stores/editor.ts | 4 ++- 6 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 src/components/properties/PageSection.vue diff --git a/components.d.ts b/components.d.ts index 321ba6220..91f602130 100644 --- a/components.d.ts +++ b/components.d.ts @@ -40,6 +40,7 @@ declare module 'vue' { IconLucideSearch: typeof import('~icons/lucide/search')['default'] LayersPanel: typeof import('./src/components/LayersPanel.vue')['default'] LayoutSection: typeof import('./src/components/properties/LayoutSection.vue')['default'] + PageSection: typeof import('./src/components/properties/PageSection.vue')['default'] PositionSection: typeof import('./src/components/properties/PositionSection.vue')['default'] PropertiesPanel: typeof import('./src/components/PropertiesPanel.vue')['default'] ScrubInput: typeof import('./src/components/ScrubInput.vue')['default'] diff --git a/src/components/PropertiesPanel.vue b/src/components/PropertiesPanel.vue index c46418a29..b47dc7711 100644 --- a/src/components/PropertiesPanel.vue +++ b/src/components/PropertiesPanel.vue @@ -6,6 +6,7 @@ import { useEditorStore } from '../stores/editor' import AppearanceSection from './properties/AppearanceSection.vue' import FillSection from './properties/FillSection.vue' import LayoutSection from './properties/LayoutSection.vue' +import PageSection from './properties/PageSection.vue' import PositionSection from './properties/PositionSection.vue' import StrokeSection from './properties/StrokeSection.vue' import TypographySection from './properties/TypographySection.vue' @@ -62,6 +63,8 @@ const multiCount = computed(() => store.selectedNodes.value.length) -
No selection
+
+ +
diff --git a/src/components/properties/PageSection.vue b/src/components/properties/PageSection.vue new file mode 100644 index 000000000..b9608db37 --- /dev/null +++ b/src/components/properties/PageSection.vue @@ -0,0 +1,34 @@ + + + diff --git a/src/composables/use-canvas.ts b/src/composables/use-canvas.ts index f4a39fdb3..028d1dd57 100644 --- a/src/composables/use-canvas.ts +++ b/src/composables/use-canvas.ts @@ -56,6 +56,7 @@ export function useCanvas(canvasRef: Ref, store: Edito renderer.viewportWidth = canvasRef.value?.clientWidth ?? 0 renderer.viewportHeight = canvasRef.value?.clientHeight ?? 0 renderer.showRulers = !new URLSearchParams(window.location.search).has('no-rulers') + renderer.pageColor = store.state.pageColor renderer.render(store.graph, store.state.selectedIds, { editingTextId: store.state.editingTextId, marquee: store.state.marquee, diff --git a/src/engine/renderer.ts b/src/engine/renderer.ts index 20ccfb10b..4a60567da 100644 --- a/src/engine/renderer.ts +++ b/src/engine/renderer.ts @@ -63,6 +63,7 @@ export class SkiaRenderer { viewportWidth = 0 viewportHeight = 0 showRulers = true + pageColor = CANVAS_BG_COLOR private selColor(alpha = 1) { return this.ck.Color4f(SELECTION_COLOR.r, SELECTION_COLOR.g, SELECTION_COLOR.b, alpha) @@ -127,7 +128,7 @@ export class SkiaRenderer { render(graph: SceneGraph, selectedIds: Set, overlays: RenderOverlays = {}): void { const canvas = this.surface.getCanvas() - canvas.clear(this.ck.Color4f(CANVAS_BG_COLOR.r, CANVAS_BG_COLOR.g, CANVAS_BG_COLOR.b, 1)) + canvas.clear(this.ck.Color4f(this.pageColor.r, this.pageColor.g, this.pageColor.b, 1)) // Scene layer (world coordinates) canvas.save() diff --git a/src/stores/editor.ts b/src/stores/editor.ts index 66e093173..b8f323375 100644 --- a/src/stores/editor.ts +++ b/src/stores/editor.ts @@ -1,6 +1,7 @@ import { reactive, shallowRef, computed } from 'vue' -import { DEFAULT_SHAPE_FILL, DEFAULT_FRAME_FILL } from '../constants' +import { DEFAULT_SHAPE_FILL, DEFAULT_FRAME_FILL, CANVAS_BG_COLOR } from '../constants' +import type { Color } from '../types' import { parseFigmaClipboard, importClipboardNodes, @@ -88,6 +89,7 @@ export function createEditorStore() { penCursorX: null as number | null, penCursorY: null as number | null, panX: 0, + pageColor: { ...CANVAS_BG_COLOR } as Color, panY: 0, zoom: 1, renderVersion: 0