Add customizable pixel grid color (#9155)

Let users pick the pixel grid color from a standard color picker.
The grid color was previously hardcoded, making it invisible on
mid-tone canvases. Choice is stored on the file so it persists
across sessions. Defaults preserve the current appearance when
unset.

Closes #7750

Signed-off-by: jack-stormentswe <crazycoder131@gmail.com>
This commit is contained in:
Jack Storment 2026-04-27 15:37:27 -04:00 committed by GitHub
parent bd1e0fb23f
commit aa5bfe6dda
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 123 additions and 27 deletions

View File

@ -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)

View File

@ -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]}]

View File

@ -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)

View File

@ -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]]])

View File

@ -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)))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

View File

@ -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}]]]))

View File

@ -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]}]