mirror of
https://github.com/penpot/penpot.git
synced 2025-02-14 02:58:39 -05:00
✨ Select through stroke only rectangle
This commit is contained in:
parent
b943a034c9
commit
fbff2f103e
6 changed files with 88 additions and 25 deletions
|
@ -6,6 +6,8 @@
|
||||||
|
|
||||||
### :sparkles: New features
|
### :sparkles: New features
|
||||||
|
|
||||||
|
- Select through stroke only rectangle [Taiga #5484](https://tree.taiga.io/project/penpot/issue/5484)
|
||||||
|
|
||||||
### :bug: Bugs fixed
|
### :bug: Bugs fixed
|
||||||
|
|
||||||
### :arrow_up: Deps updates
|
### :arrow_up: Deps updates
|
||||||
|
|
|
@ -185,6 +185,7 @@
|
||||||
|
|
||||||
;; Intersection
|
;; Intersection
|
||||||
(dm/export gsi/overlaps?)
|
(dm/export gsi/overlaps?)
|
||||||
|
(dm/export gsi/overlaps-path?)
|
||||||
(dm/export gsi/has-point?)
|
(dm/export gsi/has-point?)
|
||||||
(dm/export gsi/has-point-rect?)
|
(dm/export gsi/has-point-rect?)
|
||||||
(dm/export gsi/rect-contains-shape?)
|
(dm/export gsi/rect-contains-shape?)
|
||||||
|
|
|
@ -173,7 +173,7 @@
|
||||||
|
|
||||||
(defn overlaps-path?
|
(defn overlaps-path?
|
||||||
"Checks if the given rect overlaps with the path in any point"
|
"Checks if the given rect overlaps with the path in any point"
|
||||||
[shape rect]
|
[shape rect include-content?]
|
||||||
|
|
||||||
(when (d/not-empty? (:content shape))
|
(when (d/not-empty? (:content shape))
|
||||||
(let [ ;; If paths are too complex the intersection is too expensive
|
(let [ ;; If paths are too complex the intersection is too expensive
|
||||||
|
@ -189,9 +189,11 @@
|
||||||
(gpp/path->lines shape))
|
(gpp/path->lines shape))
|
||||||
start-point (-> shape :content (first) :params (gpt/point))]
|
start-point (-> shape :content (first) :params (gpt/point))]
|
||||||
|
|
||||||
(or (is-point-inside-nonzero? (first rect-points) path-lines)
|
(or (intersects-lines? rect-lines path-lines)
|
||||||
(is-point-inside-nonzero? start-point rect-lines)
|
(if include-content?
|
||||||
(intersects-lines? rect-lines path-lines)))))
|
(or (is-point-inside-nonzero? (first rect-points) path-lines)
|
||||||
|
(is-point-inside-nonzero? start-point rect-lines))
|
||||||
|
false)))))
|
||||||
|
|
||||||
(defn is-point-inside-ellipse?
|
(defn is-point-inside-ellipse?
|
||||||
"checks if a point is inside an ellipse"
|
"checks if a point is inside an ellipse"
|
||||||
|
@ -315,7 +317,7 @@
|
||||||
(cond
|
(cond
|
||||||
(cph/path-shape? shape)
|
(cph/path-shape? shape)
|
||||||
(and (overlaps-rect-points? rect (:points shape))
|
(and (overlaps-rect-points? rect (:points shape))
|
||||||
(overlaps-path? shape rect))
|
(overlaps-path? shape rect true))
|
||||||
|
|
||||||
(cph/circle-shape? shape)
|
(cph/circle-shape? shape)
|
||||||
(and (overlaps-rect-points? rect (:points shape))
|
(and (overlaps-rect-points? rect (:points shape))
|
||||||
|
|
|
@ -303,7 +303,8 @@
|
||||||
:rect selrect
|
:rect selrect
|
||||||
:include-frames? true
|
:include-frames? true
|
||||||
:ignore-groups? ignore-groups?
|
:ignore-groups? ignore-groups?
|
||||||
:full-frame? true})
|
:full-frame? true
|
||||||
|
:using-selrect? true})
|
||||||
(rx/map #(cph/clean-loops objects %))
|
(rx/map #(cph/clean-loops objects %))
|
||||||
(rx/map #(into initial-set (comp
|
(rx/map #(into initial-set (comp
|
||||||
(filter (complement blocked?))
|
(filter (complement blocked?))
|
||||||
|
|
|
@ -147,7 +147,8 @@
|
||||||
:page-id page-id
|
:page-id page-id
|
||||||
:rect rect
|
:rect rect
|
||||||
:include-frames? true
|
:include-frames? true
|
||||||
:clip-children? true})
|
:clip-children? true
|
||||||
|
:using-selrect? false})
|
||||||
;; When the ask-buffered is canceled returns null. We filter them
|
;; When the ask-buffered is canceled returns null. We filter them
|
||||||
;; to improve the behavior
|
;; to improve the behavior
|
||||||
(rx/filter some?))))))
|
(rx/filter some?))))))
|
||||||
|
|
|
@ -7,11 +7,13 @@
|
||||||
(ns app.worker.selection
|
(ns app.worker.selection
|
||||||
(:require
|
(:require
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
|
[app.common.geom.point :as gpt]
|
||||||
[app.common.geom.rect :as grc]
|
[app.common.geom.rect :as grc]
|
||||||
[app.common.geom.shapes :as gsh]
|
[app.common.geom.shapes :as gsh]
|
||||||
[app.common.geom.shapes.text :as gst]
|
[app.common.geom.shapes.text :as gst]
|
||||||
[app.common.pages :as cp]
|
[app.common.pages :as cp]
|
||||||
[app.common.pages.helpers :as cph]
|
[app.common.pages.helpers :as cph]
|
||||||
|
[app.common.types.modifiers :as ctm]
|
||||||
[app.common.uuid :as uuid]
|
[app.common.uuid :as uuid]
|
||||||
[app.util.quadtree :as qdt]
|
[app.util.quadtree :as qdt]
|
||||||
[app.worker.impl :as impl]
|
[app.worker.impl :as impl]
|
||||||
|
@ -117,7 +119,7 @@
|
||||||
(assoc data :index index)))
|
(assoc data :index index)))
|
||||||
|
|
||||||
(defn- query-index
|
(defn- query-index
|
||||||
[{index :index} rect frame-id full-frame? include-frames? ignore-groups? clip-children?]
|
[{index :index} rect frame-id full-frame? include-frames? ignore-groups? clip-children? using-selrect?]
|
||||||
(let [result (-> (qdt/search index (clj->js rect))
|
(let [result (-> (qdt/search index (clj->js rect))
|
||||||
(es6-iterator-seq))
|
(es6-iterator-seq))
|
||||||
|
|
||||||
|
@ -125,24 +127,78 @@
|
||||||
match-criteria?
|
match-criteria?
|
||||||
(fn [shape]
|
(fn [shape]
|
||||||
(and (not (:hidden shape))
|
(and (not (:hidden shape))
|
||||||
(or (= :frame (:type shape)) ;; We return frames even if blocked
|
(or (= :frame (:type shape)) ;; We return frames even if blocked
|
||||||
(not (:blocked shape)))
|
(not (:blocked shape)))
|
||||||
(or (not frame-id) (= frame-id (:frame-id shape)))
|
(or (not frame-id) (= frame-id (:frame-id shape)))
|
||||||
(case (:type shape)
|
(case (:type shape)
|
||||||
:frame include-frames?
|
:frame include-frames?
|
||||||
(:bool :group) (not ignore-groups?)
|
(:bool :group) (not ignore-groups?)
|
||||||
true)
|
true)
|
||||||
|
|
||||||
(or (not full-frame?)
|
(or (not full-frame?)
|
||||||
(not= :frame (:type shape))
|
(not= :frame (:type shape))
|
||||||
(and (d/not-empty? (:shapes shape))
|
(and (d/not-empty? (:shapes shape))
|
||||||
(gsh/rect-contains-shape? rect shape))
|
(gsh/rect-contains-shape? rect shape))
|
||||||
(and (empty? (:shapes shape))
|
(and (empty? (:shapes shape))
|
||||||
(gsh/overlaps? shape rect)))))
|
(gsh/overlaps? shape rect)))))
|
||||||
|
|
||||||
|
overlaps-outer-shape?
|
||||||
|
(fn [shape]
|
||||||
|
(let [padding (->> (:strokes shape)
|
||||||
|
(map #(case (get % :stroke-alignment :center)
|
||||||
|
:center (:stroke-width % 0)
|
||||||
|
:outer (* 2 (:stroke-width % 0))
|
||||||
|
:inner 0))
|
||||||
|
(reduce d/max 0))
|
||||||
|
|
||||||
|
scalev (gpt/point (/ (+ (:width shape) padding)
|
||||||
|
(:width shape))
|
||||||
|
(/ (+ (:height shape) padding)
|
||||||
|
(:height shape)))
|
||||||
|
|
||||||
|
outer-shape (-> shape
|
||||||
|
(gsh/transform-shape (-> (ctm/empty)
|
||||||
|
(ctm/resize scalev (gsh/shape->center shape)))))]
|
||||||
|
|
||||||
|
(gsh/overlaps? outer-shape rect)))
|
||||||
|
|
||||||
|
overlaps-inner-shape?
|
||||||
|
(fn [shape]
|
||||||
|
(let [padding (->> (:strokes shape)
|
||||||
|
(map #(case (get % :stroke-alignment :center)
|
||||||
|
:center (:stroke-width % 0)
|
||||||
|
:outer 0
|
||||||
|
:inner (* 2 (:stroke-width % 0))))
|
||||||
|
(reduce d/max 0))
|
||||||
|
|
||||||
|
scalev (gpt/point (/ (- (:width shape) padding)
|
||||||
|
(:width shape))
|
||||||
|
(/ (- (:height shape) padding)
|
||||||
|
(:height shape)))
|
||||||
|
|
||||||
|
inner-shape (-> shape
|
||||||
|
(gsh/transform-shape (-> (ctm/empty)
|
||||||
|
(ctm/resize scalev (gsh/shape->center shape)))))]
|
||||||
|
(gsh/overlaps? inner-shape rect)))
|
||||||
|
|
||||||
|
overlaps-path?
|
||||||
|
(fn [shape]
|
||||||
|
(let [padding (->> (:strokes shape)
|
||||||
|
(map :stroke-width)
|
||||||
|
(reduce d/max 0))
|
||||||
|
rect (grc/center->rect rect padding padding)]
|
||||||
|
(gsh/overlaps-path? shape rect false)))
|
||||||
|
|
||||||
overlaps?
|
overlaps?
|
||||||
(fn [shape]
|
(fn [shape]
|
||||||
(gsh/overlaps? shape rect))
|
(if (and (false? using-selrect?) (empty? (:fills shape)))
|
||||||
|
(do
|
||||||
|
(case (:type shape)
|
||||||
|
;; If the shape has no fills the overlap depends on the stroke
|
||||||
|
:rect (and (overlaps-outer-shape? shape) (not (overlaps-inner-shape? shape)))
|
||||||
|
:circle (and (overlaps-outer-shape? shape) (not (overlaps-inner-shape? shape)))
|
||||||
|
:path (overlaps-path? shape)))
|
||||||
|
(gsh/overlaps? shape rect)))
|
||||||
|
|
||||||
overlaps-parent?
|
overlaps-parent?
|
||||||
(fn [clip-parents]
|
(fn [clip-parents]
|
||||||
|
@ -186,8 +242,8 @@
|
||||||
nil)
|
nil)
|
||||||
|
|
||||||
(defmethod impl/handler :selection/query
|
(defmethod impl/handler :selection/query
|
||||||
[{:keys [page-id rect frame-id full-frame? include-frames? ignore-groups? clip-children?]
|
[{:keys [page-id rect frame-id full-frame? include-frames? ignore-groups? clip-children? using-selrect?]
|
||||||
:or {full-frame? false include-frames? false clip-children? true}
|
:or {full-frame? false include-frames? false clip-children? true using-selrect? false}
|
||||||
:as message}]
|
:as message}]
|
||||||
(when-let [index (get @state page-id)]
|
(when-let [index (get @state page-id)]
|
||||||
(query-index index rect frame-id full-frame? include-frames? ignore-groups? clip-children?)))
|
(query-index index rect frame-id full-frame? include-frames? ignore-groups? clip-children? using-selrect?)))
|
||||||
|
|
Loading…
Add table
Reference in a new issue