0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-03-16 01:31:22 -05:00

Allows rotation for shapes

This commit is contained in:
alonso.torres 2020-12-22 17:44:51 +01:00
parent 5636881463
commit 84007e6ad1
10 changed files with 105 additions and 49 deletions

View file

@ -293,7 +293,9 @@
"Function that checks if a number is nil or nan. Will return 0 when not "Function that checks if a number is nil or nan. Will return 0 when not
valid and the number otherwise." valid and the number otherwise."
[v] [v]
(if (or (not v) (mth/nan? v)) 0 v)) (if (or (not v)
(not (mth/finite? v))
(mth/nan? v)) 0 v))
(defmacro export (defmacro export

View file

@ -48,7 +48,7 @@
(us/assert #{:width :height} attr) (us/assert #{:width :height} attr)
(us/assert number? value) (us/assert number? value)
(let [{:keys [proportion proportion-lock]} shape (let [{:keys [proportion proportion-lock]} shape
size (select-keys shape [:width :height]) size (select-keys (:selrect shape) [:width :height])
new-size (if-not proportion-lock new-size (if-not proportion-lock
(assoc size attr value) (assoc size attr value)
(if (= attr :width) (if (= attr :width)
@ -260,6 +260,7 @@
(d/export gco/center-shape) (d/export gco/center-shape)
(d/export gco/center-selrect) (d/export gco/center-selrect)
(d/export gco/center-rect) (d/export gco/center-rect)
(d/export gco/center-points)
(d/export gpr/rect->selrect) (d/export gpr/rect->selrect)
(d/export gpr/rect->points) (d/export gpr/rect->points)
(d/export gpr/points->selrect) (d/export gpr/points->selrect)
@ -268,7 +269,9 @@
(d/export gtr/transform-point-center) (d/export gtr/transform-point-center)
(d/export gtr/transform-rect) (d/export gtr/transform-rect)
(d/export gtr/update-group-selrect) (d/export gtr/update-group-selrect)
(d/export gtr/transform-points)
;; PATHS ;; PATHS
(d/export gsp/content->points) (d/export gsp/content->points)
(d/export gsp/content->selrect) (d/export gsp/content->selrect)
(d/export gsp/transform-content)

View file

@ -14,7 +14,13 @@
[app.common.geom.shapes.common :as gco] [app.common.geom.shapes.common :as gco]
[app.common.geom.shapes.path :as gpa] [app.common.geom.shapes.path :as gpa]
[app.common.geom.shapes.rect :as gpr] [app.common.geom.shapes.rect :as gpr]
[app.common.math :as mth])) [app.common.math :as mth]
[app.common.data :as d]))
(defn- modif-rotation [shape]
(let [cur-rotation (d/check-num (:rotation shape))
delta-angle (d/check-num (get-in shape [:modifiers :rotation]))]
(mod (+ cur-rotation delta-angle) 360)))
(defn transform-matrix (defn transform-matrix
"Returns a transformation matrix without changing the shape properties. "Returns a transformation matrix without changing the shape properties.
@ -191,18 +197,23 @@
(defn apply-transform-path (defn apply-transform-path
[shape transform] [shape transform]
(let [content (gpa/transform-content (:content shape) transform) (let [content (gpa/transform-content (:content shape) transform)
selrect (gpa/content->selrect content)
points (gpr/rect->points selrect) ;; Calculate the new selrect by "unrotate" the shape
;;rotation (mod (+ (:rotation shape 0) rotation (modif-rotation shape)
;; (or (get-in shape [:modifiers :rotation]) 0)) center (gpt/transform (gco/center-shape shape) transform)
;; 360) content-rotated (gpa/transform-content content (gmt/rotate-matrix (- rotation) center))
] selrect (gpa/content->selrect content-rotated)
;; Transform the points
points (-> (:points shape)
(transform-points transform))]
(assoc shape (assoc shape
:content content :content content
:points points :points points
:selrect selrect :selrect selrect
;;:rotation rotation :transform (gmt/rotate-matrix rotation)
))) :transform-inverse (gmt/rotate-matrix (- rotation))
:rotation rotation)))
(defn apply-transform-rect (defn apply-transform-rect
"Given a new set of points transformed, set up the rectangle so it keeps "Given a new set of points transformed, set up the rectangle so it keeps

View file

@ -1038,9 +1038,12 @@
(let [page-id (:current-page-id state) (let [page-id (:current-page-id state)
objects (dwc/lookup-page-objects state page-id) objects (dwc/lookup-page-objects state page-id)
shape (get objects id) shape (get objects id)
cpos (gpt/point (:x shape) (:y shape))
pos (gpt/point (or (:x position) (:x shape)) bbox (-> shape :points gsh/points->selrect)
(or (:y position) (:y shape)))
cpos (gpt/point (:x bbox) (:y bbox))
pos (gpt/point (or (:x position) (:x bbox))
(or (:y position) (:y bbox)))
displ (gmt/translate-matrix (gpt/subtract pos cpos))] displ (gmt/translate-matrix (gpt/subtract pos cpos))]
(rx/of (dwt/set-modifiers [id] {:displacement displ}) (rx/of (dwt/set-modifiers [id] {:displacement displ})
(dwt/apply-modifiers [id])))))) (dwt/apply-modifiers [id]))))))
@ -1544,6 +1547,7 @@
(d/export dwt/start-move-selected) (d/export dwt/start-move-selected)
(d/export dwt/move-selected) (d/export dwt/move-selected)
(d/export dwt/set-rotation) (d/export dwt/set-rotation)
(d/export dwt/increase-rotation)
(d/export dwt/set-modifiers) (d/export dwt/set-modifiers)
(d/export dwt/apply-modifiers) (d/export dwt/apply-modifiers)
(d/export dwt/update-dimensions) (d/export dwt/update-dimensions)

View file

@ -16,6 +16,7 @@
[app.common.math :as mth] [app.common.math :as mth]
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh] [app.common.geom.shapes :as gsh]
[app.common.geom.matrix :as gmt]
[app.util.data :as ud] [app.util.data :as ud]
[app.common.data :as cd] [app.common.data :as cd]
[app.util.geom.path :as ugp] [app.util.geom.path :as ugp]
@ -87,12 +88,37 @@
[:workspace-drawing :object]) [:workspace-drawing :object])
path))) path)))
(defn- points->components [shape content]
(let [rotation (:rotation shape 0)
center (gsh/center-shape shape)
content-rotated (gsh/transform-content content (gmt/rotate-matrix (- rotation) center))
;; Calculates the new selrect with points given the old center
points (-> (gsh/content->selrect content-rotated)
(gsh/rect->points)
(gsh/transform-points center (:transform shape (gmt/matrix))))
points-center (gsh/center-points points)
;; Points is now the selrect but the center is different so we can create the selrect
;; through points
selrect (-> points
(gsh/transform-points points-center (:transform-inverse shape (gmt/matrix)))
(gsh/points->selrect))]
[points selrect]))
(defn update-selrect (defn update-selrect
"Updates the selrect and points for a path" "Updates the selrect and points for a path"
[shape] [shape]
(let [selrect (gsh/content->selrect (:content shape)) (if (= (:rotation shape 0) 0)
points (gsh/rect->points selrect)] (let [content (:content shape)
(assoc shape :points points :selrect selrect))) selrect (gsh/content->selrect content)
points (gsh/rect->points selrect)]
(assoc shape :points points :selrect selrect))
(let [content (:content shape)
[points selrect] (points->components shape content)]
(assoc shape :points points :selrect selrect))))
(defn closest-angle [angle] (defn closest-angle [angle]
(cond (cond
@ -157,13 +183,12 @@
;; TODO: Enter now finish path but can finish drawing/editing as well ;; TODO: Enter now finish path but can finish drawing/editing as well
(= enter-keycode (:key event))))) (= enter-keycode (:key event)))))
(defn generate-path-changes [page-id shape-id old-content new-content] (defn generate-path-changes [page-id shape old-content new-content]
(us/verify ::content old-content) (us/verify ::content old-content)
(us/verify ::content new-content) (us/verify ::content new-content)
(let [old-selrect (gsh/content->selrect old-content) (let [shape-id (:id shape)
old-points (gsh/rect->points old-selrect) [old-points old-selrect] (points->components shape old-content)
new-selrect (gsh/content->selrect new-content) [new-points new-selrect] (points->components shape new-content)
new-points (gsh/rect->points new-selrect)
rch [{:type :mod-obj rch [{:type :mod-obj
:id shape-id :id shape-id
@ -393,7 +418,7 @@
shape (get-in state (get-path state)) shape (get-in state (get-path state))
selected-points (get-in state [:workspace-local :edit-path id :selected-points] #{}) selected-points (get-in state [:workspace-local :edit-path id :selected-points] #{})
new-content (reduce ugp/make-corner-point (:content shape) selected-points) new-content (reduce ugp/make-corner-point (:content shape) selected-points)
[rch uch] (generate-path-changes page-id id (:content shape) new-content)] [rch uch] (generate-path-changes page-id shape (:content shape) new-content)]
(rx/of (dwc/commit-changes rch uch {:commit-local? true})))))) (rx/of (dwc/commit-changes rch uch {:commit-local? true}))))))
(defn make-curve [] (defn make-curve []
@ -405,7 +430,7 @@
shape (get-in state (get-path state)) shape (get-in state (get-path state))
selected-points (get-in state [:workspace-local :edit-path id :selected-points] #{}) selected-points (get-in state [:workspace-local :edit-path id :selected-points] #{})
new-content (reduce ugp/make-curve-point (:content shape) selected-points) new-content (reduce ugp/make-curve-point (:content shape) selected-points)
[rch uch] (generate-path-changes page-id id (:content shape) new-content)] [rch uch] (generate-path-changes page-id shape (:content shape) new-content)]
(rx/of (dwc/commit-changes rch uch {:commit-local? true})))))) (rx/of (dwc/commit-changes rch uch {:commit-local? true}))))))
(defn path-handler-enter [index prefix] (defn path-handler-enter [index prefix]
@ -552,7 +577,7 @@
shape (get-in state (get-path state)) shape (get-in state (get-path state))
content-modifiers (get-in state [:workspace-local :edit-path id :content-modifiers]) content-modifiers (get-in state [:workspace-local :edit-path id :content-modifiers])
new-content (ugp/apply-content-modifiers (:content shape) content-modifiers) new-content (ugp/apply-content-modifiers (:content shape) content-modifiers)
[rch uch] (generate-path-changes page-id (:id shape) (:content shape) new-content)] [rch uch] (generate-path-changes page-id shape (:content shape) new-content)]
(rx/of (dwc/commit-changes rch uch {:commit-local? true}) (rx/of (dwc/commit-changes rch uch {:commit-local? true})
(fn [state] (update-in state [:workspace-local :edit-path id] dissoc :content-modifiers))))))) (fn [state] (update-in state [:workspace-local :edit-path id] dissoc :content-modifiers)))))))
@ -573,7 +598,7 @@
shape (get-in state (get-path state)) shape (get-in state (get-path state))
page-id (:current-page-id state) page-id (:current-page-id state)
old-content (get-in state [:workspace-local :edit-path id :old-content]) old-content (get-in state [:workspace-local :edit-path id :old-content])
[rch uch] (generate-path-changes page-id id old-content (:content shape))] [rch uch] (generate-path-changes page-id shape old-content (:content shape))]
(rx/of (dwc/commit-changes rch uch {:commit-local? true})))))) (rx/of (dwc/commit-changes rch uch {:commit-local? true}))))))
(declare start-draw-mode) (declare start-draw-mode)

View file

@ -431,6 +431,20 @@
(let [page-id (:current-page-id state)] (let [page-id (:current-page-id state)]
(d/update-in-when state [:workspace-data :pages-index page-id :objects] set-rotation))))))) (d/update-in-when state [:workspace-data :pages-index page-id :objects] set-rotation)))))))
(defn increase-rotation [ids rotation]
(ptk/reify ::increase-rotation
ptk/WatchEvent
(watch [_ state stream]
(let [page-id (:current-page-id state)
objects (dwc/lookup-page-objects state page-id)
rotate-shape (fn [shape]
(let [delta (- rotation (:rotation shape))]
(set-rotation delta [shape])))]
(rx/concat
(rx/from (->> ids (map #(get objects %)) (map rotate-shape)))
(rx/of (apply-modifiers ids)))))))
(defn apply-modifiers (defn apply-modifiers
[ids] [ids]
(us/verify (s/coll-of uuid?) ids) (us/verify (s/coll-of uuid?) ids)

View file

@ -13,7 +13,6 @@
[rumext.alpha :as mf] [rumext.alpha :as mf]
[app.main.ui.shapes.attrs :as attrs] [app.main.ui.shapes.attrs :as attrs]
[app.main.ui.shapes.custom-stroke :refer [shape-custom-stroke]] [app.main.ui.shapes.custom-stroke :refer [shape-custom-stroke]]
[app.common.geom.shapes :as geom]
[app.util.object :as obj] [app.util.object :as obj]
[app.util.geom.path :as ugp])) [app.util.geom.path :as ugp]))
@ -24,14 +23,11 @@
[props] [props]
(let [shape (unchecked-get props "shape") (let [shape (unchecked-get props "shape")
background? (unchecked-get props "background?") background? (unchecked-get props "background?")
;; {:keys [id x y width height]} (geom/shape->rect-shape shape)
{:keys [id x y width height]} (:selrect shape) {:keys [id x y width height]} (:selrect shape)
transform (geom/transform-matrix shape)
pdata (ugp/content->path (:content shape)) pdata (ugp/content->path (:content shape))
props (-> (attrs/extract-style-attrs shape) props (-> (attrs/extract-style-attrs shape)
(obj/merge! (obj/merge!
#js {:transform transform #js {:d pdata}))]
:d pdata}))]
(if background? (if background?
[:g [:g
[:path {:stroke "transparent" [:path {:stroke "transparent"

View file

@ -44,16 +44,17 @@
(def selection-rect-width 1) (def selection-rect-width 1)
(mf/defc selection-rect [{:keys [transform rect zoom color]}] (mf/defc selection-rect [{:keys [transform rect zoom color]}]
(let [{:keys [x y width height]} rect] (when rect
[:rect.main (let [{:keys [x y width height]} rect]
{:x x [:rect.main
:y y {:x x
:width width :y y
:height height :width width
:transform transform :height height
:style {:stroke color :transform transform
:stroke-width (/ selection-rect-width zoom) :style {:stroke color
:fill "transparent"}}])) :stroke-width (/ selection-rect-width zoom)
:fill "transparent"}}])))
(defn- handlers-for-selection [{:keys [x y width height]}] (defn- handlers-for-selection [{:keys [x y width height]}]
[;; TOP-LEFT [;; TOP-LEFT
@ -181,9 +182,7 @@
current-transform (mf/deref refs/current-transform) current-transform (mf/deref refs/current-transform)
selrect (:selrect shape) selrect (:selrect shape)
transform (geom/transform-matrix shape {:no-flip true}) transform (geom/transform-matrix shape {:no-flip true})]
tr-shape (geom/transform-shape shape)]
(when (not (#{:move :rotate} current-transform)) (when (not (#{:move :rotate} current-transform))
[:g.controls [:g.controls
@ -193,7 +192,7 @@
:transform transform :transform transform
:zoom zoom :zoom zoom
:color color}] :color color}]
[:& outline {:shape tr-shape :color color}] [:& outline {:shape shape :color color}]
;; Handlers ;; Handlers
(for [{:keys [type position props]} (handlers-for-selection selrect)] (for [{:keys [type position props]} (handlers-for-selection selrect)]

View file

@ -35,11 +35,12 @@
:stroke-width "1px" :stroke-width "1px"
:stroke-opacity 0.5}]])) :stroke-opacity 0.5}]]))
(mf/defc render-rect [{{:keys [x y width height]} :rect :keys [color]}] (mf/defc render-rect [{{:keys [x y width height]} :rect :keys [color transform]}]
[:rect {:x x [:rect {:x x
:y y :y y
:width width :width width
:height height :height height
:transform (or transform "none")
:style {:stroke color :style {:stroke color
:fill "transparent" :fill "transparent"
:stroke-width "1px" :stroke-width "1px"
@ -76,7 +77,7 @@
:fill line-color :fill line-color
:stroke "white" :stroke "white"
:stroke-width 0.1} :stroke-width 0.1}
(str/format "%s - (%s, %s)" (str/slice (str (:id shape)) 0 8) (fixed (:x shape)) (fixed (:y shape)))] (str/format "%s - (%s, %s)" (str/slice (str (:id shape)) 0 8) (fixed (:x selrect)) (fixed (:y selrect)))]
[:& cross-point {:point shape-center [:& cross-point {:point shape-center
:zoom zoom :zoom zoom
@ -87,7 +88,7 @@
:zoom zoom :zoom zoom
:color line-color}]) :color line-color}])
[:& render-rect-points {:rect selrect [:& render-rect-points {:points (:points shape)
:color line-color}] :color line-color}]
[:& render-rect {:rect selrect [:& render-rect {:rect selrect

View file

@ -45,7 +45,8 @@
:ry (/ height 2)} :ry (/ height 2)}
:path :path
{:d (ugp/content->path (:content shape))} {:d (ugp/content->path (:content shape))
:transform nil}
{:x x {:x x
:y y :y y