0
Fork 0
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:
Alejandro Alonso 2023-09-06 14:53:41 +02:00 committed by Alonso Torres
parent b943a034c9
commit fbff2f103e
6 changed files with 88 additions and 25 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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