🐛 Fix Alt/Option to draw shapes from center point

Use the Alt/Option key stream (mouse-position-alt) instead of
the Command/Meta stream (mouse-position-mod) so the modifier
is actually detected during shape drawing.

When Alt is held, mirror the mouse point around the initial
click so that the click becomes the center of the drawn shape.
This aligns drawing behavior with resizing (transforms.cljs)
and with other design tools (Figma, Sketch, Illustrator).

Closes #8360

Signed-of-by: Serhii Shvets <justone128@gmail.com>
This commit is contained in:
Serhii Shvets 2026-02-15 00:20:25 +02:00 committed by Andrey Antukh
parent 082c8adb1d
commit 2b34767b2b
2 changed files with 31 additions and 17 deletions

View File

@ -17,6 +17,8 @@
### :bug: Bugs fixed
- Fix Alt/Option to draw shapes from center point (by @offreal) [Github #8361](https://github.com/penpot/penpot/pull/8361)
## 2.14.0 (Unreleased)

View File

@ -6,6 +6,7 @@
(ns app.main.data.workspace.drawing.box
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.geom.point :as gpt]
[app.common.geom.rect :as grc]
@ -28,9 +29,9 @@
[beicon.v2.core :as rx]
[potok.v2.core :as ptk]))
(defn adjust-ratio
(defn- adjust-ratio
[point initial]
(let [v (gpt/to-vec point initial)
(let [v (gpt/to-vec point initial)
dx (mth/abs (:x v))
dy (mth/abs (:y v))
sx (mth/sign (:x v))
@ -43,32 +44,43 @@
(> dy dx)
(assoc :x (- (:x point) (* sx (- dy dx)))))))
(defn resize-shape [{:keys [x y width height] :as shape} initial point lock? mod? snap-pixel?]
(defn- resize-shape
[{:keys [x y width height] :as shape} initial point lock? mod? snap-pixel?]
(if (and (some? x) (some? y) (some? width) (some? height))
(let [draw-rect (cond-> (grc/make-rect initial (cond-> point lock? (adjust-ratio initial)))
snap-pixel?
(-> (update :width max 1)
(update :height max 1)))
(let [p2
(cond-> point lock? (adjust-ratio initial))
shape-rect (grc/make-rect x y width height)
p1
(if mod?
(gpt/point (- (* 2 (:x initial)) (:x p2))
(- (* 2 (:y initial)) (:y p2)))
initial)
scalev (gpt/point (/ (:width draw-rect)
(:width shape-rect))
(/ (:height draw-rect)
(:height shape-rect)))
draw-rect
(cond-> (grc/make-rect p1 p2)
snap-pixel?
(-> (update :width d/max 1)
(update :height d/max 1)))
movev (gpt/to-vec (gpt/point shape-rect)
(gpt/point draw-rect))]
shape-rect
(grc/make-rect x y width height)
scalev
(gpt/point (/ (:width draw-rect) (:width shape-rect))
(/ (:height draw-rect) (:height shape-rect)))
movev
(gpt/to-vec (gpt/point shape-rect) (gpt/point draw-rect))]
(-> shape
(assoc :click-draw? false)
(vary-meta merge {:mod? mod?})
(gsh/transform-shape (-> (ctm/empty)
(ctm/resize scalev (gpt/point x y))
(ctm/move movev)))))
shape))
(defn- update-drawing [state initial point lock? mod? snap-pixel?]
(defn- update-drawing
[state initial point lock? mod? snap-pixel?]
(update-in state [:workspace-drawing :object] resize-shape initial point lock? mod? snap-pixel?))
(defn move-drawing
@ -128,7 +140,7 @@
;; Take until before the snap calculation otherwise we could cancel the snap in the worker
;; and its a problem for fast moving drawing
(rx/take-until stopper)
(rx/with-latest-from ms/mouse-position-shift ms/mouse-position-mod)
(rx/with-latest-from ms/mouse-position-shift ms/mouse-position-alt)
(rx/switch-map
(fn [[point :as current]]
(->> (snap/closest-snap-point page-id [shape] objects layout zoom focus point)