mirror of
https://github.com/penpot/penpot.git
synced 2026-05-24 08:26:12 -04:00
wip
This commit is contained in:
@@ -46,6 +46,13 @@
|
||||
;; `set-wasm-modifiers` calls fire in between.
|
||||
(defonce ^:private interactive-transform-active? (atom false))
|
||||
|
||||
;; Holds the property changes (`extract-property-changes` output)
|
||||
;; applied during the in-flight gesture. Written per drag frame in
|
||||
;; `set-wasm-modifiers`, read at gesture-end (`clear-local-transform`)
|
||||
;; to revert. Kept out of Redux to avoid root-atom churn at drag
|
||||
;; cadence (~62.5 Hz) and the re-frame subscription scan that follows.
|
||||
(defonce ^:private gesture-wasm-props (volatile! nil))
|
||||
|
||||
(defn- ensure-interactive-transform-start!
|
||||
[]
|
||||
(when (compare-and-set! interactive-transform-active? false true)
|
||||
@@ -303,12 +310,14 @@
|
||||
;; skip shadows / blur).
|
||||
(ensure-interactive-transform-end!)
|
||||
(wasm.api/clean-modifiers)
|
||||
(set-wasm-props! (dsh/lookup-page-objects state) (:wasm-props state) [])))
|
||||
(let [last-wasm-props @gesture-wasm-props]
|
||||
(vreset! gesture-wasm-props nil)
|
||||
(set-wasm-props! (dsh/lookup-page-objects state) last-wasm-props []))))
|
||||
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(-> state
|
||||
(dissoc :workspace-modifiers :wasm-props :prev-wasm-props)
|
||||
(dissoc :workspace-modifiers)
|
||||
(dissoc :app.main.data.workspace.transforms/current-move-selected)))))
|
||||
|
||||
(defn create-modif-tree
|
||||
@@ -684,14 +693,6 @@
|
||||
:or {ignore-constraints false ignore-snap-pixel false}
|
||||
:as params}]
|
||||
(ptk/reify ::set-wasm-modifiers
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [property-changes
|
||||
(extract-property-changes modif-tree)]
|
||||
(-> state
|
||||
(assoc :prev-wasm-props (:wasm-props state))
|
||||
(assoc :wasm-props property-changes))))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
;; Entering an interactive transform (drag/resize/rotate). Flip
|
||||
@@ -701,8 +702,9 @@
|
||||
;; `clear-local-transform`.
|
||||
(ensure-interactive-transform-start!)
|
||||
(wasm.api/clean-modifiers)
|
||||
(let [prev-wasm-props (:prev-wasm-props state)
|
||||
wasm-props (:wasm-props state)
|
||||
(let [prev-wasm-props @gesture-wasm-props
|
||||
wasm-props (extract-property-changes modif-tree)
|
||||
_ (vreset! gesture-wasm-props wasm-props)
|
||||
objects (dsh/lookup-page-objects state)
|
||||
snap-pixel?
|
||||
(and (not ignore-snap-pixel) (contains? (:workspace-layout state) :snap-pixel-grid))
|
||||
|
||||
@@ -743,31 +743,44 @@
|
||||
(rx/take-until stopper)
|
||||
(rx/share))
|
||||
|
||||
modifiers-stream
|
||||
;; Per-pointermove: combine inputs and cache the most-recent
|
||||
;; non-nil grid cell. The tap fires per move so the cache stays
|
||||
;; fresh regardless of where the WASM render branch samples.
|
||||
move-stream-with-cell-data
|
||||
(->> move-stream
|
||||
(rx/with-latest-from array/conj ms/mouse-position-shift)
|
||||
(rx/tap
|
||||
(fn [[_ _ _ cell-data _]]
|
||||
(when (some? cell-data)
|
||||
(vreset! prev-cell-data cell-data))))
|
||||
(rx/share))
|
||||
|
||||
(rx/map
|
||||
(fn [[move-vector target-frame drop-index cell-data shift?]]
|
||||
(let [cell-data (or cell-data @prev-cell-data)
|
||||
x-disp? (> (mth/abs (:x move-vector)) (mth/abs (:y move-vector)))
|
||||
[move-vector snap-ignore-axis]
|
||||
(cond
|
||||
(and shift? x-disp?)
|
||||
[(assoc move-vector :y 0) :y]
|
||||
;; Heavy build (modifier tree construction). Pulled out as a
|
||||
;; standalone fn so the WASM render branch can apply it post-
|
||||
;; sample and the commit branch can apply it once on the final
|
||||
;; event, rather than running it per pointermove via the shared
|
||||
;; modifiers-stream below.
|
||||
build-modifier
|
||||
(fn [[move-vector target-frame drop-index cell-data shift?]]
|
||||
(let [cell-data (or cell-data @prev-cell-data)
|
||||
x-disp? (> (mth/abs (:x move-vector)) (mth/abs (:y move-vector)))
|
||||
[move-vector snap-ignore-axis]
|
||||
(cond
|
||||
(and shift? x-disp?)
|
||||
[(assoc move-vector :y 0) :y]
|
||||
|
||||
shift?
|
||||
[(assoc move-vector :x 0) :x]
|
||||
shift?
|
||||
[(assoc move-vector :x 0) :x]
|
||||
|
||||
:else
|
||||
[move-vector nil])]
|
||||
[(-> (dwm/create-modif-tree ids (ctm/move-modifiers move-vector))
|
||||
(dwm/build-change-frame-modifiers objects selected target-frame drop-index cell-data))
|
||||
snap-ignore-axis])))
|
||||
:else
|
||||
[move-vector nil])]
|
||||
[(-> (dwm/create-modif-tree ids (ctm/move-modifiers move-vector))
|
||||
(dwm/build-change-frame-modifiers objects selected target-frame drop-index cell-data))
|
||||
snap-ignore-axis]))
|
||||
|
||||
modifiers-stream
|
||||
(->> move-stream-with-cell-data
|
||||
(rx/map build-modifier)
|
||||
(rx/share))]
|
||||
|
||||
(if (features/active-feature? state "render-wasm/v1")
|
||||
@@ -779,11 +792,14 @@
|
||||
(rx/of true)
|
||||
(rx/empty)))))]
|
||||
(rx/merge
|
||||
(->> modifiers-stream
|
||||
;; Sampled render path: `build-modifier` runs only for sampled
|
||||
;; events (~62.5 Hz), not per pointermove. Modifier-tree
|
||||
;; construction and frame-modifier propagation are skipped on
|
||||
;; dropped frames.
|
||||
(->> move-stream-with-cell-data
|
||||
(rx/take-until duplicate-stopper)
|
||||
;; Sample at a fixed cadence to keep preview smooth. Unlike a throttle,
|
||||
;; this tends to avoid perceptible "jumps" while still capping WASM work.
|
||||
(rx/sample 16)
|
||||
(rx/map build-modifier)
|
||||
(rx/map
|
||||
(fn [[modifiers snap-ignore-axis]]
|
||||
(dwm/set-wasm-modifiers modifiers
|
||||
@@ -802,14 +818,18 @@
|
||||
(dws/duplicate-selected false true))
|
||||
(rx/empty)))))
|
||||
|
||||
;; Last event will write the modifiers creating the changes
|
||||
(->> move-stream
|
||||
;; Last event writes the modifiers creating the changes.
|
||||
;; Build from the final move event directly so the commit
|
||||
;; reflects the user's actual release position rather than
|
||||
;; the latest sampled value (which can be ~16 ms stale).
|
||||
(->> move-stream-with-cell-data
|
||||
(rx/last)
|
||||
(rx/take-until duplicate-stopper)
|
||||
(rx/with-latest-from modifiers-stream)
|
||||
(rx/mapcat
|
||||
(fn [[[_ target-frame drop-index drop-cell] [modifiers snap-ignore-axis]]]
|
||||
(let [undo-id (js/Symbol)]
|
||||
(fn [args]
|
||||
(let [[_ target-frame drop-index drop-cell _] args
|
||||
[modifiers snap-ignore-axis] (build-modifier args)
|
||||
undo-id (js/Symbol)]
|
||||
(rx/of
|
||||
(dwu/start-undo-transaction undo-id)
|
||||
(dwm/apply-wasm-modifiers modifiers
|
||||
|
||||
@@ -388,9 +388,50 @@
|
||||
(def workspace-wasm-editor-styles
|
||||
(l/derived :workspace-wasm-editor-styles st/state))
|
||||
|
||||
(def ^:private wasm-modifier-jitter-threshold-px
|
||||
;; Suppress drag-preview pushes whose translation delta is below half a
|
||||
;; pixel. The Skia renderer keeps running at full sample cadence; this
|
||||
;; only coarsens the JS-side outline geometry recompute that the memoed
|
||||
;; consumers in `viewport_wasm` derive from `wasm-modifiers`.
|
||||
0.5)
|
||||
|
||||
(defn- ^boolean wasm-modifiers-significant-change?
|
||||
"Cheap predicate: did `new` move any shape by ≥ threshold pixels vs `prev`.
|
||||
Only short-circuits for *pure-translation* matrices (drag); resize/rotate
|
||||
matrices always pass through."
|
||||
[prev new threshold]
|
||||
(cond
|
||||
(or (nil? prev) (nil? new)) true
|
||||
(not= (count prev) (count new)) true
|
||||
:else
|
||||
(let [prev-map (into {} prev)]
|
||||
(loop [pairs new]
|
||||
(if-let [pair (first pairs)]
|
||||
(let [id (nth pair 0)
|
||||
^js m-new (nth pair 1)
|
||||
^js m-prev (get prev-map id)]
|
||||
(cond
|
||||
(nil? m-prev) true
|
||||
(or (not (== 1 (.-a m-new))) (not (== 0 (.-b m-new)))
|
||||
(not (== 0 (.-c m-new))) (not (== 1 (.-d m-new)))
|
||||
(not (== 1 (.-a m-prev))) (not (== 0 (.-b m-prev)))
|
||||
(not (== 0 (.-c m-prev))) (not (== 1 (.-d m-prev))))
|
||||
true
|
||||
(or (> (js/Math.abs (- (.-e m-new) (.-e m-prev))) threshold)
|
||||
(> (js/Math.abs (- (.-f m-new) (.-f m-prev))) threshold))
|
||||
true
|
||||
:else (recur (rest pairs))))
|
||||
false)))))
|
||||
|
||||
(def workspace-wasm-modifiers
|
||||
(let [a (atom nil)]
|
||||
(rx/sub! ms/wasm-modifiers #(reset! a %))
|
||||
(let [a (atom nil)
|
||||
last-emitted (volatile! nil)]
|
||||
(rx/sub! ms/wasm-modifiers
|
||||
(fn [val]
|
||||
(when (wasm-modifiers-significant-change?
|
||||
@last-emitted val wasm-modifier-jitter-threshold-px)
|
||||
(vreset! last-emitted val)
|
||||
(reset! a val))))
|
||||
a))
|
||||
|
||||
(def ^:private workspace-modifiers-with-objects
|
||||
|
||||
@@ -3270,6 +3270,23 @@ impl RenderState {
|
||||
.get_tiles_of(shape.id)
|
||||
.map_or(Vec::new(), |t| t.iter().copied().collect());
|
||||
|
||||
// Drag fast path: when the shape's tile footprint is identical to
|
||||
// last frame, the tile *content* still changed (the shape moved
|
||||
// within those tiles), so callers must still invalidate the tile
|
||||
// cache for that set — but the index `remove_shape_at` +
|
||||
// `add_shape_at` shuffle is pure churn.
|
||||
if self.options.is_interactive_transform() {
|
||||
let new_count = ((rex - rsx + 1) * (rey - rsy + 1)) as usize;
|
||||
if old_tiles.len() == new_count
|
||||
&& old_tiles.iter().all(|t| {
|
||||
let (x, y) = (t.x(), t.y());
|
||||
x >= rsx && x <= rex && y >= rsy && y <= rey
|
||||
})
|
||||
{
|
||||
return old_tiles.into_iter().collect();
|
||||
}
|
||||
}
|
||||
|
||||
let mut result = HashSet::<tiles::Tile>::with_capacity(old_tiles.len());
|
||||
|
||||
// First, remove the shape from all tiles where it was previously located
|
||||
|
||||
Reference in New Issue
Block a user