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.
This commit is contained in:
parent
159ee6b7c9
commit
ffd305c125
1
components.d.ts
vendored
1
components.d.ts
vendored
|
|
@ -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']
|
||||
|
|
|
|||
|
|
@ -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)
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="px-3 py-4 text-xs text-muted">No selection</div>
|
||||
<div v-else class="flex-1 overflow-y-auto pb-4">
|
||||
<PageSection />
|
||||
</div>
|
||||
</aside>
|
||||
</template>
|
||||
|
|
|
|||
34
src/components/properties/PageSection.vue
Normal file
34
src/components/properties/PageSection.vue
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
<script setup lang="ts">
|
||||
import ColorPicker from '../ColorPicker.vue'
|
||||
import { useEditorStore } from '../../stores/editor'
|
||||
import { colorToHexRaw, parseColor } from '../../engine/color'
|
||||
|
||||
import type { Color } from '../../types'
|
||||
|
||||
const store = useEditorStore()
|
||||
|
||||
function updateColor(color: Color) {
|
||||
store.state.pageColor = color
|
||||
store.requestRender()
|
||||
}
|
||||
|
||||
function updateHex(hex: string) {
|
||||
const color = parseColor(hex.startsWith('#') ? hex : `#${hex}`)
|
||||
if (!color) return
|
||||
updateColor(color)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="border-b border-border px-3 py-2">
|
||||
<label class="mb-1.5 block text-[11px] text-muted">Page</label>
|
||||
<div class="flex items-center gap-1.5">
|
||||
<ColorPicker :color="store.state.pageColor" @update="updateColor" />
|
||||
<input
|
||||
class="min-w-0 flex-1 border-none bg-transparent font-mono text-xs text-surface outline-none"
|
||||
:value="colorToHexRaw(store.state.pageColor)"
|
||||
@change="updateHex(($event.target as HTMLInputElement).value)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -56,6 +56,7 @@ export function useCanvas(canvasRef: Ref<HTMLCanvasElement | null>, 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,
|
||||
|
|
|
|||
|
|
@ -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<string>, 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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue