mirror of
https://github.com/penpot/penpot.git
synced 2025-02-10 17:18:21 -05:00
commit
925058467f
15 changed files with 217 additions and 151 deletions
16
CHANGES.md
16
CHANGES.md
|
@ -3,10 +3,15 @@
|
|||
## :rocket: Next
|
||||
|
||||
### :boom: Breaking changes
|
||||
|
||||
- Some stroke-caps can change behaviour
|
||||
- Text display bug fix could potentialy make some texts jump a line
|
||||
|
||||
### :sparkles: New features
|
||||
|
||||
- Add boolean shapes: intersections, unions, difference and exclusions.
|
||||
- Add advanced prototyping [#244](https://tree.taiga.io/project/penpot/us/244).
|
||||
- Change order of the teams menu so it's in the joined time order
|
||||
|
||||
### :bug: Bugs fixed
|
||||
|
||||
|
@ -26,12 +31,15 @@
|
|||
- Fix zoom context menu in viewer [Taiga #2041](https://tree.taiga.io/project/penpot/issue/2041)
|
||||
- Fix stroke caps adjustments in relation with stroke size [Taiga #2123](https://tree.taiga.io/project/penpot/issue/2123)
|
||||
- Fix problem duplicating paths [Taiga #2147](https://tree.taiga.io/project/penpot/issue/2147)
|
||||
- Fix problem inheriting attributes from SVG root when importing [Taiga #2124](https://tree.taiga.io/project/penpot/issue/2124)
|
||||
- Fix problem with lines and inside/outside stroke [Taiga #2146](https://tree.taiga.io/project/penpot/issue/2146)
|
||||
- Add stroke width in selection calculation [Taiga #2146](https://tree.taiga.io/project/penpot/issue/2146)
|
||||
- Fix shift+wheel to horizontal scrolling in MacOS [#1217](https://github.com/penpot/penpot/issues/1217)
|
||||
- Fix path stroke is not working properly with high thickness [Taiga #2154](https://tree.taiga.io/project/penpot/issue/2154)
|
||||
- Fix bug with transformation operations [Taiga #2155](https://tree.taiga.io/project/penpot/issue/2155)
|
||||
- Fix bug in firefox when a text box is inside a mask [Taiga #2152](https://tree.taiga.io/project/penpot/issue/2152)
|
||||
|
||||
### :arrow_up: Deps updates
|
||||
### :boom: Breaking changes
|
||||
|
||||
- Some stroke-caps can change behaviour
|
||||
- Text display bug fix could potentialy make some texts jump a line
|
||||
|
||||
### :heart: Community contributions by (Thank you!)
|
||||
|
||||
|
|
|
@ -58,7 +58,7 @@
|
|||
join team as t on (t.id = tp.team_id)
|
||||
where t.deleted_at is null
|
||||
and tp.profile_id = ?
|
||||
order by t.created_at asc")
|
||||
order by tp.created_at asc")
|
||||
|
||||
(defn retrieve-teams
|
||||
[conn profile-id]
|
||||
|
|
|
@ -160,6 +160,7 @@
|
|||
;; PATHS
|
||||
(d/export gsp/content->selrect)
|
||||
(d/export gsp/transform-content)
|
||||
(d/export gsp/open-path?)
|
||||
|
||||
;; Intersection
|
||||
(d/export gin/overlaps?)
|
||||
|
|
|
@ -284,12 +284,19 @@
|
|||
(defn overlaps?
|
||||
"General case to check for overlaping between shapes and a rectangle"
|
||||
[shape rect]
|
||||
(let [stroke-width (/ (or (:stroke-width shape) 0) 2)
|
||||
rect (-> rect
|
||||
(update :x - stroke-width)
|
||||
(update :y - stroke-width)
|
||||
(update :width + (* 2 stroke-width))
|
||||
(update :height + (* 2 stroke-width))
|
||||
)]
|
||||
(or (not shape)
|
||||
(let [path? (= :path (:type shape))
|
||||
circle? (= :circle (:type shape))]
|
||||
(and (overlaps-rect-points? rect (:points shape))
|
||||
(or (not path?) (overlaps-path? shape rect))
|
||||
(or (not circle?) (overlaps-ellipse? shape rect))))))
|
||||
(or (not circle?) (overlaps-ellipse? shape rect)))))))
|
||||
|
||||
(defn has-point-rect?
|
||||
[rect point]
|
||||
|
|
|
@ -126,8 +126,8 @@
|
|||
|
||||
(let [tangent (curve-tangent curve t)]
|
||||
(cond
|
||||
(> (:y tangent) 0) 1
|
||||
(< (:y tangent) 0) -1
|
||||
(> (:y tangent) 0) -1
|
||||
(< (:y tangent) 0) 1
|
||||
:else 0)))
|
||||
|
||||
(defn curve-split
|
||||
|
@ -822,30 +822,27 @@
|
|||
(let [selrect (content->selrect content)
|
||||
ray-line [point (gpt/point (inc (:x point)) (:y point))]
|
||||
|
||||
closed-subpaths
|
||||
closed-content
|
||||
(into []
|
||||
(comp (filter sp/is-closed?)
|
||||
(mapcat :data))
|
||||
(->> content
|
||||
(sp/close-subpaths)
|
||||
(sp/get-subpaths)
|
||||
(filterv sp/is-closed?))
|
||||
(sp/get-subpaths)))
|
||||
|
||||
cast-ray
|
||||
(fn [cmd]
|
||||
(case (:command cmd)
|
||||
:line-to (ray-line-intersect point (command->line cmd))
|
||||
:curve-to (ray-curve-intersect ray-line (command->bezier cmd))
|
||||
#_:else []))
|
||||
#_:else []))]
|
||||
|
||||
is-point-in-subpath?
|
||||
(fn [subpath]
|
||||
(and (gpr/contains-point? (content->selrect (:data subpath)) point)
|
||||
(->> (:data subpath)
|
||||
(and (gpr/contains-point? selrect point)
|
||||
(->> closed-content
|
||||
(mapcat cast-ray)
|
||||
(map second)
|
||||
(reduce +)
|
||||
(not= 0))))]
|
||||
|
||||
(and (gpr/contains-point? selrect point)
|
||||
(some is-point-in-subpath? closed-subpaths))))
|
||||
(not= 0)))))
|
||||
|
||||
(defn split-line-to
|
||||
"Given a point and a line-to command will create a two new line-to commands
|
||||
|
@ -948,3 +945,14 @@
|
|||
(gsc/transform-points points-center transform-inverse)
|
||||
(gpr/points->selrect))]
|
||||
[points selrect]))
|
||||
|
||||
|
||||
(defn open-path?
|
||||
[shape]
|
||||
|
||||
(and (= :path (:type shape))
|
||||
(not (->> shape
|
||||
:content
|
||||
(sp/close-subpaths)
|
||||
(sp/get-subpaths)
|
||||
(every? sp/is-closed?)))))
|
||||
|
|
|
@ -73,9 +73,15 @@
|
|||
(fn [subpaths current]
|
||||
(let [is-move? (= :move-to (:command current))
|
||||
last-idx (dec (count subpaths))]
|
||||
(if is-move?
|
||||
(cond
|
||||
is-move?
|
||||
(conj subpaths (make-subpath current))
|
||||
(update subpaths last-idx add-subpath-command current))))]
|
||||
|
||||
(>= last-idx 0)
|
||||
(update subpaths last-idx add-subpath-command current)
|
||||
|
||||
:else
|
||||
subpaths)))]
|
||||
(->> content
|
||||
(reduce reduce-subpath []))))
|
||||
|
||||
|
|
|
@ -432,6 +432,7 @@
|
|||
(ptk/reify ::duplicate-selected
|
||||
ptk/WatchEvent
|
||||
(watch [it state _]
|
||||
(when (nil? (get-in state [:workspace-local :transform]))
|
||||
(let [page-id (:current-page-id state)
|
||||
objects (wsh/lookup-page-objects state page-id)
|
||||
selected (wsh/lookup-selected state)
|
||||
|
@ -461,7 +462,7 @@
|
|||
:undo-changes uchanges
|
||||
:origin it})
|
||||
(select-shapes selected)
|
||||
(memorize-duplicated id-original id-duplicated))))))
|
||||
(memorize-duplicated id-original id-duplicated)))))))
|
||||
|
||||
(defn change-hover-state
|
||||
[id value]
|
||||
|
|
|
@ -178,7 +178,8 @@
|
|||
:y (+ y offset-y)}
|
||||
(gsh/setup-selrect)
|
||||
(assoc :svg-attrs (-> (:attrs svg-data)
|
||||
(dissoc :viewBox :xmlns))))))
|
||||
(dissoc :viewBox :xmlns)
|
||||
(d/without-keys usvg/inheritable-props))))))
|
||||
|
||||
(defn create-group [name frame-id svg-data {:keys [attrs]}]
|
||||
(let [svg-transform (usvg/parse-transform (:transform attrs))
|
||||
|
@ -387,7 +388,7 @@
|
|||
(setup-stroke)))
|
||||
|
||||
children (cond->> (:content element-data)
|
||||
(= tag :g)
|
||||
(or (= tag :g) (= tag :svg))
|
||||
(mapv #(usvg/inherit-attributes attrs %)))]
|
||||
[shape children]))))
|
||||
|
||||
|
@ -487,11 +488,15 @@
|
|||
;; Creates the root shape
|
||||
changes (dwc/add-shape-changes page-id objects selected root-shape false)
|
||||
|
||||
root-attrs (-> (:attrs svg-data)
|
||||
(usvg/format-styles))
|
||||
|
||||
;; Reduces the children to create the changes to add the children shapes
|
||||
[_ [rchanges uchanges]]
|
||||
(reduce (partial add-svg-child-changes page-id objects selected frame-id root-id svg-data)
|
||||
[unames changes]
|
||||
(d/enumerate (:content svg-data)))
|
||||
(d/enumerate (->> (:content svg-data)
|
||||
(mapv #(usvg/inherit-attributes root-attrs %)))))
|
||||
|
||||
reg-objects-action {:type :reg-objects
|
||||
:page-id page-id
|
||||
|
|
|
@ -70,10 +70,7 @@
|
|||
(defn- fix-init-point
|
||||
"Fix the initial point so the resizes are accurate"
|
||||
[initial handler shape]
|
||||
(let [{:keys [x y width height]} (:selrect shape)
|
||||
{:keys [rotation]} shape
|
||||
rotation (or rotation 0)]
|
||||
(if (= rotation 0)
|
||||
(let [{:keys [x y width height]} (:selrect shape)]
|
||||
(cond-> initial
|
||||
(contains? #{:left :top-left :bottom-left} handler)
|
||||
(assoc :x x)
|
||||
|
@ -85,8 +82,7 @@
|
|||
(assoc :y y)
|
||||
|
||||
(contains? #{:bottom :bottom-right :bottom-left} handler)
|
||||
(assoc :y (+ y height)))
|
||||
initial)))
|
||||
(assoc :y (+ y height)))))
|
||||
|
||||
(defn finish-transform []
|
||||
(ptk/reify ::finish-transform
|
||||
|
@ -285,10 +281,19 @@
|
|||
(letfn [(resize [shape initial layout [point lock? center? point-snap]]
|
||||
(let [{:keys [width height]} (:selrect shape)
|
||||
{:keys [rotation]} shape
|
||||
|
||||
shape-center (gsh/center-shape shape)
|
||||
shape-transform (:transform shape (gmt/matrix))
|
||||
shape-transform-inverse (:transform-inverse shape (gmt/matrix))
|
||||
|
||||
rotation (or rotation 0)
|
||||
|
||||
initial (gsh/transform-point-center initial shape-center shape-transform-inverse)
|
||||
initial (fix-init-point initial handler shape)
|
||||
|
||||
point (gsh/transform-point-center (if (= rotation 0) point-snap point)
|
||||
shape-center shape-transform-inverse)
|
||||
|
||||
shapev (-> (gpt/point width height))
|
||||
|
||||
scale-text (:scale-text layout)
|
||||
|
@ -300,8 +305,7 @@
|
|||
handler-mult (let [[x y] (handler-multipliers handler)] (gpt/point x y))
|
||||
|
||||
;; Difference between the origin point in the coordinate system of the rotation
|
||||
deltav (-> (gpt/to-vec initial (if (= rotation 0) point-snap point))
|
||||
(gpt/transform (gmt/rotate-matrix (- rotation)))
|
||||
deltav (-> (gpt/to-vec initial point)
|
||||
(gpt/multiply handler-mult))
|
||||
|
||||
;; Resize vector
|
||||
|
@ -317,24 +321,23 @@
|
|||
scalev)
|
||||
|
||||
;; Resize origin point given the selected handler
|
||||
origin (handler-resize-origin (:selrect shape) handler)
|
||||
handler-origin (handler-resize-origin (:selrect shape) handler)
|
||||
|
||||
shape-center (gsh/center-shape shape)
|
||||
shape-transform (:transform shape (gmt/matrix))
|
||||
shape-transform-inverse (:transform-inverse shape (gmt/matrix))
|
||||
|
||||
;; If we want resize from center, displace the shape
|
||||
;; so it is still centered after resize.
|
||||
displacement (when center?
|
||||
displacement
|
||||
(when center?
|
||||
(-> shape-center
|
||||
(gpt/subtract origin)
|
||||
(gpt/subtract handler-origin)
|
||||
(gpt/multiply scalev)
|
||||
(gpt/add origin)
|
||||
(gpt/add handler-origin)
|
||||
(gpt/subtract shape-center)
|
||||
(gpt/multiply (gpt/point -1 -1))
|
||||
(gpt/transform shape-transform)))
|
||||
|
||||
origin (cond-> (gsh/transform-point-center origin shape-center shape-transform)
|
||||
resize-origin
|
||||
(cond-> (gsh/transform-point-center handler-origin shape-center shape-transform)
|
||||
(some? displacement)
|
||||
(gpt/add displacement))
|
||||
|
||||
|
@ -344,7 +347,7 @@
|
|||
(rx/of (set-modifiers ids
|
||||
{:displacement displacement
|
||||
:resize-vector scalev
|
||||
:resize-origin origin
|
||||
:resize-origin resize-origin
|
||||
:resize-transform shape-transform
|
||||
:resize-scale-text scale-text
|
||||
:resize-transform-inverse shape-transform-inverse}))))
|
||||
|
|
|
@ -25,22 +25,24 @@
|
|||
[cuerdas.core :as str]
|
||||
[rumext.alpha :as mf]))
|
||||
|
||||
(defn bounds
|
||||
(defn calc-bounds
|
||||
[object objects]
|
||||
(if (= :group (:type object))
|
||||
(let [children-bounds
|
||||
(into []
|
||||
(comp (map #(get objects %))
|
||||
(map #(bounds % objects)))
|
||||
(:shapes object))]
|
||||
(gsh/join-rects children-bounds))
|
||||
|
||||
(let [padding (filters/calculate-padding object)]
|
||||
(let [xf-get-bounds (comp (map #(get objects %)) (map #(calc-bounds % objects)))
|
||||
padding (filters/calculate-padding object)
|
||||
obj-bounds
|
||||
(-> (filters/get-filters-bounds object)
|
||||
(update :x - padding)
|
||||
(update :y - padding)
|
||||
(update :width + (* 2 padding))
|
||||
(update :height + (* 2 padding))))))
|
||||
(update :height + (* 2 padding)))]
|
||||
|
||||
(if (= :group (:type object))
|
||||
(->> (:shapes object)
|
||||
(into [obj-bounds] xf-get-bounds)
|
||||
(gsh/join-rects))
|
||||
|
||||
obj-bounds)))
|
||||
|
||||
(mf/defc object-svg
|
||||
{::mf/wrap [mf/memo]}
|
||||
|
@ -64,7 +66,7 @@
|
|||
objects (reduce updt-fn objects mod-ids)
|
||||
object (get objects object-id)
|
||||
|
||||
{:keys [x y width height] :as bs} (bounds object objects)
|
||||
{:keys [x y width height] :as bs} (calc-bounds object objects)
|
||||
[_ _ width height :as coords] (->> [x y width height] (map #(* % zoom)))
|
||||
|
||||
vbox (str/join " " coords)
|
||||
|
|
|
@ -14,12 +14,14 @@
|
|||
[rumext.alpha :as mf]))
|
||||
|
||||
(defn- stroke-type->dasharray
|
||||
[style]
|
||||
(case style
|
||||
:mixed "5,5,1,5"
|
||||
:dotted "5,5"
|
||||
:dashed "10,10"
|
||||
nil))
|
||||
[width style]
|
||||
(let [values (case style
|
||||
:mixed [5 5 1 5]
|
||||
:dotted [5 5]
|
||||
:dashed [10 10]
|
||||
nil)]
|
||||
|
||||
(->> values (map #(+ % width)) (str/join ","))))
|
||||
|
||||
(defn- truncate-side
|
||||
[shape ra-attr rb-attr dimension-attr]
|
||||
|
@ -102,10 +104,11 @@
|
|||
|
||||
(defn add-stroke [attrs shape render-id]
|
||||
(let [stroke-style (:stroke-style shape :none)
|
||||
stroke-color-gradient-id (str "stroke-color-gradient_" render-id)]
|
||||
stroke-color-gradient-id (str "stroke-color-gradient_" render-id)
|
||||
stroke-width (:stroke-width shape 1)]
|
||||
(if (not= stroke-style :none)
|
||||
(let [stroke-attrs
|
||||
(cond-> {:strokeWidth (:stroke-width shape 1)}
|
||||
(cond-> {:strokeWidth stroke-width}
|
||||
(:stroke-color-gradient shape)
|
||||
(assoc :stroke (str/format "url(#%s)" stroke-color-gradient-id))
|
||||
|
||||
|
@ -118,7 +121,7 @@
|
|||
(assoc :strokeOpacity (:stroke-opacity shape nil))
|
||||
|
||||
(not= stroke-style :svg)
|
||||
(assoc :strokeDasharray (stroke-type->dasharray stroke-style))
|
||||
(assoc :strokeDasharray (stroke-type->dasharray stroke-width stroke-style))
|
||||
|
||||
;; For simple line caps we use svg stroke-line-cap attribute. This
|
||||
;; only works if all caps are the same and we are not using the tricks
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
(ns app.main.ui.shapes.custom-stroke
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.main.ui.context :as muc]
|
||||
[app.util.object :as obj]
|
||||
[cuerdas.core :as str]
|
||||
|
@ -145,6 +146,8 @@
|
|||
|
||||
(mf/defc stroke-defs
|
||||
[{:keys [shape render-id]}]
|
||||
(when (and (= (:type shape) :path)
|
||||
(gsh/open-path? shape))
|
||||
(cond
|
||||
(and (= :inner (:stroke-alignment shape :center))
|
||||
(> (:stroke-width shape 0) 0))
|
||||
|
@ -160,7 +163,7 @@
|
|||
(some? (:stroke-cap-end shape)))
|
||||
(= (:stroke-alignment shape) :center))
|
||||
[:& cap-markers {:shape shape
|
||||
:render-id render-id}]))
|
||||
:render-id render-id}])))
|
||||
|
||||
;; Outer alingmnent: display the shape in two layers. One
|
||||
;; without stroke (only fill), and another one only with stroke
|
||||
|
@ -253,15 +256,17 @@
|
|||
stroke-position (:stroke-alignment shape :center)
|
||||
has-stroke? (and (> stroke-width 0)
|
||||
(not= stroke-style :none))
|
||||
closed? (or (not= :path (:type shape))
|
||||
(not (gsh/open-path? shape)))
|
||||
inner? (= :inner stroke-position)
|
||||
outer? (= :outer stroke-position)]
|
||||
|
||||
(cond
|
||||
(and has-stroke? inner?)
|
||||
(and has-stroke? inner? closed?)
|
||||
[:& inner-stroke {:shape shape}
|
||||
child]
|
||||
|
||||
(and has-stroke? outer?)
|
||||
(and has-stroke? outer? closed?)
|
||||
[:& outer-stroke {:shape shape}
|
||||
child]
|
||||
|
||||
|
|
|
@ -27,13 +27,23 @@
|
|||
[(first childs) (rest childs)]
|
||||
[nil childs])
|
||||
|
||||
;; We need to separate mask and clip into two because a bug in Firefox
|
||||
;; breaks when the group has clip+mask+foreignObject
|
||||
;; Clip and mask separated will work in every platform
|
||||
; Firefox bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1734805
|
||||
[clip-wrapper clip-props]
|
||||
(if masked-group?
|
||||
["g" (-> (obj/new)
|
||||
(obj/set! "clipPath" (clip-url render-id mask)))]
|
||||
[mf/Fragment nil])
|
||||
|
||||
[mask-wrapper mask-props]
|
||||
(if masked-group?
|
||||
["g" (-> (obj/new)
|
||||
(obj/set! "clipPath" (clip-url render-id mask))
|
||||
(obj/set! "mask" (mask-url render-id mask)))]
|
||||
[mf/Fragment nil])]
|
||||
|
||||
[:> clip-wrapper clip-props
|
||||
[:> mask-wrapper mask-props
|
||||
(when masked-group?
|
||||
[:> render-mask #js {:frame frame :mask mask}])
|
||||
|
@ -41,7 +51,7 @@
|
|||
(for [item childs]
|
||||
[:& shape-wrapper {:frame frame
|
||||
:shape item
|
||||
:key (:id item)}])]))))
|
||||
:key (:id item)}])]]))))
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -363,12 +363,14 @@
|
|||
delta-y (-> (.-deltaY ^js event)
|
||||
(* unit)
|
||||
(/ zoom))
|
||||
|
||||
delta-x (-> (.-deltaX ^js event)
|
||||
(* unit)
|
||||
(/ zoom))]
|
||||
(dom/prevent-default event)
|
||||
(dom/stop-propagation event)
|
||||
(if (kbd/shift? event)
|
||||
(if (and (not (cfg/check-platform? :macos)) ;; macos sends delta-x automaticaly, don't need to do it
|
||||
(kbd/shift? event))
|
||||
(st/emit! (dw/update-viewport-position {:x #(+ % delta-y)}))
|
||||
(st/emit! (dw/update-viewport-position {:x #(+ % delta-x)
|
||||
:y #(+ % delta-y)})))))))))
|
||||
|
|
|
@ -229,7 +229,12 @@
|
|||
current-transform (mf/deref refs/current-transform)
|
||||
|
||||
selrect (:selrect shape)
|
||||
transform (geom/transform-matrix shape {:no-flip true})]
|
||||
transform (geom/transform-matrix shape {:no-flip true})
|
||||
|
||||
rotation (-> (gpt/point 1 0)
|
||||
(gpt/transform (:transform shape))
|
||||
(gpt/angle)
|
||||
(mod 360))]
|
||||
|
||||
(when (not (#{:move :rotate} current-transform))
|
||||
[:g.controls {:pointer-events (if disable-handlers "none" "visible")}
|
||||
|
@ -249,7 +254,7 @@
|
|||
:on-rotate on-rotate
|
||||
:on-resize (partial on-resize position)
|
||||
:transform transform
|
||||
:rotation (:rotation shape)
|
||||
:rotation rotation
|
||||
:color color
|
||||
:overflow-text overflow-text}
|
||||
props (map->obj (merge common-props props))]
|
||||
|
|
Loading…
Add table
Reference in a new issue