diff --git a/components/canvas/rulers-and-guides.tsx b/components/canvas/rulers-and-guides.tsx new file mode 100644 index 0000000..0acce95 --- /dev/null +++ b/components/canvas/rulers-and-guides.tsx @@ -0,0 +1,130 @@ +'use client'; + +import { useGuidesStore } from '@/store'; +import { useCanvasStore } from '@/store'; + +interface RulersAndGuidesProps { + canvasWidth: number; + canvasHeight: number; +} + +export function RulersAndGuides({ canvasWidth, canvasHeight }: RulersAndGuidesProps) { + const { guides, showGuides, showRulers } = useGuidesStore(); + const { zoom, offsetX, offsetY } = useCanvasStore(); + + if (!showGuides && !showRulers) return null; + + const rulerSize = 20; + + return ( + <> + {/* Horizontal Ruler */} + {showRulers && ( +
+ + {Array.from({ length: Math.ceil(canvasWidth / (zoom * 50)) }).map((_, i) => { + const x = i * 50 * zoom + offsetX; + if (x < 0 || x > canvasWidth) return null; + return ( + + + + {i * 50} + + + ); + })} + +
+ )} + + {/* Vertical Ruler */} + {showRulers && ( +
+ + {Array.from({ length: Math.ceil(canvasHeight / (zoom * 50)) }).map((_, i) => { + const y = i * 50 * zoom + offsetY; + if (y < 0 || y > canvasHeight) return null; + return ( + + + + {i * 50} + + + ); + })} + +
+ )} + + {/* Guides */} + {showGuides && guides.map((guide) => ( +
+ ))} + + ); +} diff --git a/store/guides-store.ts b/store/guides-store.ts new file mode 100644 index 0000000..7b0bcd0 --- /dev/null +++ b/store/guides-store.ts @@ -0,0 +1,91 @@ +import { create } from 'zustand'; +import { persist } from 'zustand/middleware'; + +export interface Guide { + id: string; + type: 'horizontal' | 'vertical'; + position: number; // In canvas pixels +} + +interface GuidesStore { + /** All guides */ + guides: Guide[]; + /** Whether guides are visible */ + showGuides: boolean; + /** Whether rulers are visible */ + showRulers: boolean; + /** Snap to guides threshold in pixels */ + snapDistance: number; + + /** Add a guide */ + addGuide: (type: 'horizontal' | 'vertical', position: number) => void; + /** Remove a guide */ + removeGuide: (id: string) => void; + /** Update guide position */ + updateGuide: (id: string, position: number) => void; + /** Clear all guides */ + clearGuides: () => void; + /** Toggle guides visibility */ + toggleGuides: () => void; + /** Toggle rulers visibility */ + toggleRulers: () => void; + /** Set snap distance */ + setSnapDistance: (distance: number) => void; +} + +export const useGuidesStore = create()( + persist( + (set, get) => ({ + guides: [], + showGuides: true, + showRulers: true, + snapDistance: 10, + + addGuide: (type, position) => { + const id = `guide-${Date.now()}-${Math.random()}`; + set((state) => ({ + guides: [...state.guides, { id, type, position }], + })); + }, + + removeGuide: (id) => { + set((state) => ({ + guides: state.guides.filter((g) => g.id !== id), + })); + }, + + updateGuide: (id, position) => { + set((state) => ({ + guides: state.guides.map((g) => + g.id === id ? { ...g, position } : g + ), + })); + }, + + clearGuides: () => { + set({ guides: [] }); + }, + + toggleGuides: () => { + set((state) => ({ showGuides: !state.showGuides })); + }, + + toggleRulers: () => { + set((state) => ({ showRulers: !state.showRulers })); + }, + + setSnapDistance: (distance) => { + set({ snapDistance: Math.max(0, Math.min(50, distance)) }); + }, + }), + { + name: 'guides-storage', + partialize: (state) => ({ + guides: state.guides, + showGuides: state.showGuides, + showRulers: state.showRulers, + snapDistance: state.snapDistance, + }), + } + ) +); diff --git a/store/index.ts b/store/index.ts index 04e7911..03e02e1 100644 --- a/store/index.ts +++ b/store/index.ts @@ -11,3 +11,4 @@ export * from './text-store'; export * from './ui-store'; export * from './toast-store'; export * from './context-menu-store'; +export * from './guides-store';