diff --git a/CHANGES.md b/CHANGES.md index 2518f1b0cb..11b308b336 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -48,7 +48,7 @@ - Preserve vector content when pasting from external tools such as Inkscape: recognise SVG sent as text/plain (with optional XML declaration and HTML comments), skip the raster preview when an SVG sibling is on the clipboard, and ignore empty SVG blobs that some tools advertise alongside the real payload, so pasted graphics arrive editable without spurious "SVG is invalid" warnings [Github #546](https://github.com/penpot/penpot/issues/546) - Add Shift+Numpad0/1/2 as aliases to Shift+0/1/2 for zoom shortcuts [Github #2457](https://github.com/penpot/penpot/issues/2457) - +- Adds a **Pixel grid color** picker in the viewport settings, next to the existing canvas color control [Github #7750](https://github.com/penpot/penpot/issues/7750) ### :bug: Bugs fixed - Fix plugin API `fileVersion.restore()` promise hanging indefinitely on restore failure [Github #9092](https://github.com/penpot/penpot/issues/9092) diff --git a/common/src/app/common/files/changes.cljc b/common/src/app/common/files/changes.cljc index 5b45a7ecd4..c9aa3d3faa 100644 --- a/common/src/app/common/files/changes.cljc +++ b/common/src/app/common/files/changes.cljc @@ -261,7 +261,11 @@ ;; All props are optional, background can be nil because is the ;; way to remove already set background [:background {:optional true} [:maybe ctc/schema:hex-color]] - [:name {:optional true} :string]]] + [:name {:optional true} :string] + ;; Pixel grid display controls — nil removes the per-page override + ;; and falls back to the default hardcoded grid color/opacity. + [:pixel-grid-color {:optional true} [:maybe ctc/schema:hex-color]] + [:pixel-grid-opacity {:optional true} [:maybe ::sm/safe-number]]]] [:set-plugin-data schema:set-plugin-data-change] @@ -853,8 +857,10 @@ [data {:keys [id] :as params}] (d/update-in-when data [:pages-index id] (fn [page] - (let [name (get params :name) - bg (get params :background :not-found)] + (let [name (get params :name) + bg (get params :background :not-found) + grid-color (get params :pixel-grid-color :not-found) + grid-op (get params :pixel-grid-opacity :not-found)] (cond-> page (string? name) (assoc :name name) @@ -863,7 +869,19 @@ (assoc :background bg) (nil? bg) - (dissoc :background)))))) + (dissoc :background) + + (string? grid-color) + (assoc :pixel-grid-color grid-color) + + (and (not= grid-color :not-found) (nil? grid-color)) + (dissoc :pixel-grid-color) + + (number? grid-op) + (assoc :pixel-grid-opacity grid-op) + + (and (not= grid-op :not-found) (nil? grid-op)) + (dissoc :pixel-grid-opacity)))))) (defmethod process-change :set-plugin-data [data {:keys [object-type object-id page-id namespace key value]}] diff --git a/common/src/app/common/files/changes_builder.cljc b/common/src/app/common/files/changes_builder.cljc index 93ac58d03b..5106493082 100644 --- a/common/src/app/common/files/changes_builder.cljc +++ b/common/src/app/common/files/changes_builder.cljc @@ -213,21 +213,33 @@ (let [page (::page (meta changes))] (mod-page changes page options))) - ([changes page {:keys [name background]}] + ([changes page {:keys [name background pixel-grid-color pixel-grid-opacity]}] (let [change {:type :mod-page :id (:id page)} redo (cond-> change (some? name) (assoc :name name) (some? background) - (assoc :background background)) + (assoc :background background) + + (some? pixel-grid-color) + (assoc :pixel-grid-color pixel-grid-color) + + (some? pixel-grid-opacity) + (assoc :pixel-grid-opacity pixel-grid-opacity)) undo (cond-> change (some? name) (assoc :name (:name page)) (some? background) - (assoc :background (:background page)))] + (assoc :background (:background page)) + + (some? pixel-grid-color) + (assoc :pixel-grid-color (:pixel-grid-color page)) + + (some? pixel-grid-opacity) + (assoc :pixel-grid-opacity (:pixel-grid-opacity page)))] (-> changes (update :redo-changes conj redo) diff --git a/common/src/app/common/types/page.cljc b/common/src/app/common/types/page.cljc index 0f3e05f97a..e6fa26a006 100644 --- a/common/src/app/common/types/page.cljc +++ b/common/src/app/common/types/page.cljc @@ -59,6 +59,10 @@ [:guides {:optional true} schema:guides] [:plugin-data {:optional true} ctpg/schema:plugin-data] [:background {:optional true} ctc/schema:hex-color] + ;; Per-page pixel grid color. Falls back to a hardcoded default when + ;; unset so existing files render identically to before. + [:pixel-grid-color {:optional true} ctc/schema:hex-color] + [:pixel-grid-opacity {:optional true} ::sm/safe-number] [:comment-thread-positions {:optional true} [:map-of ::sm/uuid schema:comment-thread-position]]]) diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index 1d5acfed38..8dffa26e77 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -1256,6 +1256,24 @@ (pcb/mod-page {:background (:color color)}))] (rx/of (dch/commit-changes changes))))))) +(defn change-pixel-grid-color + "Update the pixel grid color (and optional alpha) for the given page. + Mirrors `change-canvas-color` — stored on the page so the choice + travels with the file and persists across sessions." + ([color] + (change-pixel-grid-color nil color)) + ([page-id color] + (ptk/reify ::change-pixel-grid-color + ptk/WatchEvent + (watch [it state _] + (let [page-id (or page-id (:current-page-id state)) + page (dsh/lookup-page state page-id) + changes (-> (pcb/empty-changes it) + (pcb/with-page page) + (pcb/mod-page {:pixel-grid-color (:color color) + :pixel-grid-opacity (:opacity color)}))] + (rx/of (dch/commit-changes changes))))))) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/page.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/page.cljs index 740750b0cf..c5876940b8 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/page.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/page.cljs @@ -24,17 +24,39 @@ (-> (l/key :background) (l/derived refs/workspace-page))) +(def ^:private ref:pixel-grid-color + (-> (l/key :pixel-grid-color) + (l/derived refs/workspace-page))) + +(def ^:private ref:pixel-grid-opacity + (-> (l/key :pixel-grid-opacity) + (l/derived refs/workspace-page))) + +;; Default pixel grid color shown in the picker when the user hasn't +;; set a custom one. Matches the legacy hardcoded CSS variable. +(def ^:private default-pixel-grid-color "#0070E4") + (mf/defc options* {::mf/wrap [mf/memo]} [] (let [background (mf/deref ref:background-color) + grid-color (mf/deref ref:pixel-grid-color) + grid-alpha (mf/deref ref:pixel-grid-opacity) + on-change (mf/use-fn #(st/emit! (dw/change-canvas-color %))) on-open (mf/use-fn #(st/emit! (dwu/start-undo-transaction :options))) on-close (mf/use-fn #(st/emit! (dwu/commit-undo-transaction :options))) + on-grid-change + (mf/use-fn #(st/emit! (dw/change-pixel-grid-color %))) + color (mf/with-memo [background] {:color (d/nilv background clr/canvas) - :opacity 1})] + :opacity 1}) + + grid (mf/with-memo [grid-color grid-alpha] + {:color (d/nilv grid-color default-pixel-grid-color) + :opacity (d/nilv grid-alpha 0.2)})] [:div {:class (stl/css :element-set)} [:div {:class (stl/css :element-title)} @@ -52,5 +74,15 @@ :on-change on-change :origin :canvas :on-open on-open + :on-close on-close}] + + [:> color-row* + {:disable-gradient true + :disable-image true + :title "Pixel grid color" + :color grid + :on-change on-grid-change + :origin :pixel-grid + :on-open on-open :on-close on-close}]]])) diff --git a/frontend/src/app/main/ui/workspace/viewport/widgets.cljs b/frontend/src/app/main/ui/workspace/viewport/widgets.cljs index a47897d2d6..dac8657ad2 100644 --- a/frontend/src/app/main/ui/workspace/viewport/widgets.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/widgets.cljs @@ -33,24 +33,36 @@ (mf/defc pixel-grid* [{:keys [vbox zoom]}] - [:g.pixel-grid - [:defs - [:pattern {:id "pixel-grid" - :viewBox "0 0 1 1" - :width 1 - :height 1 - :pattern-units "userSpaceOnUse"} - [:path {:d "M 1 0 L 0 0 0 1" - :style {:fill "none" - :stroke (if (dbg/enabled? :pixel-grid) "red" "var(--status-color-info-500)") - :stroke-opacity (if (dbg/enabled? :pixel-grid) 1 "0.2") - :stroke-width (str (/ 1 zoom))}}]]] - [:rect {:x (:x vbox) - :y (:y vbox) - :width (:width vbox) - :height (:height vbox) - :fill (str "url(#pixel-grid)") - :style {:pointer-events "none"}}]]) + (let [page (mf/deref refs/workspace-page) + custom-color (:pixel-grid-color page) + custom-alpha (:pixel-grid-opacity page) + debug? (dbg/enabled? :pixel-grid) + stroke (cond + debug? "red" + custom-color custom-color + :else "var(--status-color-info-500)") + opacity (cond + debug? 1 + (some? custom-alpha) custom-alpha + :else 0.2)] + [:g.pixel-grid + [:defs + [:pattern {:id "pixel-grid" + :viewBox "0 0 1 1" + :width 1 + :height 1 + :pattern-units "userSpaceOnUse"} + [:path {:d "M 1 0 L 0 0 0 1" + :style {:fill "none" + :stroke stroke + :stroke-opacity opacity + :stroke-width (str (/ 1 zoom))}}]]] + [:rect {:x (:x vbox) + :y (:y vbox) + :width (:width vbox) + :height (:height vbox) + :fill (str "url(#pixel-grid)") + :style {:pointer-events "none"}}]])) (mf/defc cursor-tooltip* [{:keys [zoom tooltip]}]