mirror of
https://github.com/penpot/penpot.git
synced 2026-01-03 11:58:46 -05:00
Compare commits
10 Commits
develop
...
superalex-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f5eb09ca58 | ||
|
|
78f4787e22 | ||
|
|
621aee75e1 | ||
|
|
513e7374cb | ||
|
|
7459239639 | ||
|
|
4b8f4d2d5b | ||
|
|
d50da7ed66 | ||
|
|
eb735c4cd1 | ||
|
|
caf250a84e | ||
|
|
48ecee8c35 |
@@ -517,8 +517,7 @@
|
||||
(when verify?
|
||||
(check-changes items))
|
||||
|
||||
(binding [*touched-changes* (volatile! #{})
|
||||
cts/*wasm-sync* (not cts/*wasm-sync-override*)]
|
||||
(binding [*touched-changes* (volatile! #{})]
|
||||
(let [result (reduce #(or (process-change %1 %2) %1) data items)
|
||||
result (reduce process-touched-change result @*touched-changes*)]
|
||||
;; Validate result shapes (only on the backend)
|
||||
|
||||
@@ -36,12 +36,7 @@
|
||||
[app.common.uuid :as uuid]
|
||||
[clojure.set :as set]))
|
||||
|
||||
(defonce ^:dynamic *wasm-sync* false)
|
||||
|
||||
;; This is a temporary workaround so the changes-builder doesn't generate updates
|
||||
;; in the WASM model.
|
||||
(defonce ^:dynamic *wasm-sync-override* false)
|
||||
|
||||
(defonce ^:dynamic *shape-changes* nil)
|
||||
(defonce wasm-enabled? false)
|
||||
(defonce wasm-create-shape (constantly nil))
|
||||
|
||||
|
||||
@@ -7,14 +7,18 @@
|
||||
(ns app.main.data.changes
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.files.changes :as cpc]
|
||||
[app.common.logging :as log]
|
||||
[app.common.time :as ct]
|
||||
[app.common.types.shape :as cts]
|
||||
[app.common.types.shape-tree :as ctst]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.event :as ev]
|
||||
[app.main.data.helpers :as dsh]
|
||||
[app.main.features :as features]
|
||||
[app.main.worker :as mw]
|
||||
[app.render-wasm.shape :as wasm.shape]
|
||||
[beicon.v2.core :as rx]
|
||||
[potok.v2.core :as ptk]))
|
||||
|
||||
@@ -99,7 +103,21 @@
|
||||
pids (into #{} xf:map-page-id redo-changes)]
|
||||
(reduce #(ctst/update-object-indices %1 %2) fdata pids)))]
|
||||
|
||||
(update-in state [:files file-id :data] apply-changes)))))
|
||||
(if (features/active-feature? state "render-wasm/v1")
|
||||
;; Update the wasm model
|
||||
(let [shape-changes (volatile! {})
|
||||
|
||||
state
|
||||
(binding [cts/*shape-changes* shape-changes]
|
||||
(update-in state [:files file-id :data] apply-changes))]
|
||||
|
||||
(let [objects (dm/get-in state [:files file-id :data :pages-index (:current-page-id state) :objects])]
|
||||
(wasm.shape/process-shape-changes! objects @shape-changes))
|
||||
|
||||
state)
|
||||
|
||||
;; wasm renderer deactivated
|
||||
(update-in state [:files file-id :data] apply-changes))))))
|
||||
|
||||
(defn commit
|
||||
"Create a commit event instance"
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
[app.common.types.component :as ctk]
|
||||
[app.common.types.container :as ctn]
|
||||
[app.common.types.modifiers :as ctm]
|
||||
[app.common.types.shape :as shape]
|
||||
[app.common.types.shape-tree :as ctst]
|
||||
[app.common.types.shape.attrs :refer [editable-attrs]]
|
||||
[app.common.types.shape.layout :as ctl]
|
||||
@@ -227,21 +226,26 @@
|
||||
wasm-props
|
||||
(concat clean-props wasm-props)
|
||||
|
||||
wasm-props
|
||||
;; Stores a map shape -> set of properties changed
|
||||
;; this is the standard format used by process-shape-changes
|
||||
shape-changes
|
||||
(-> (group-by first wasm-props)
|
||||
(update-vals #(map second %)))]
|
||||
(update-vals #(into #{} (map (comp :property second)) %)))
|
||||
|
||||
;; Props are grouped by id and then assoc to the shape the new value
|
||||
(doseq [[id properties] wasm-props]
|
||||
(let [shape
|
||||
(->> properties
|
||||
(reduce
|
||||
(fn [shape {:keys [property value]}]
|
||||
(assoc shape property value))
|
||||
(get objects id)))]
|
||||
|
||||
;; With the new values to the shape change multi props
|
||||
(wasm.shape/set-wasm-multi-attrs! shape (->> properties (map :property)))))))
|
||||
;; Create a new objects only with the temporary modifications
|
||||
objects-changed
|
||||
(->> wasm-props
|
||||
(reduce
|
||||
(fn [objects [id properties]]
|
||||
(let [shape
|
||||
(->> properties
|
||||
(reduce
|
||||
(fn [shape {:keys [property value]}]
|
||||
(assoc shape property value))
|
||||
(get objects id)))]
|
||||
(assoc objects id shape)))
|
||||
objects))]
|
||||
(wasm.shape/process-shape-changes! objects-changed shape-changes)))
|
||||
|
||||
(defn clear-local-transform []
|
||||
(ptk/reify ::clear-local-transform
|
||||
@@ -649,8 +653,7 @@
|
||||
(let [objects (dsh/lookup-page-objects state)
|
||||
|
||||
ignore-tree
|
||||
(binding [shape/*wasm-sync* false]
|
||||
(calculate-ignore-tree modif-tree objects))
|
||||
(calculate-ignore-tree modif-tree objects)
|
||||
|
||||
options
|
||||
(-> params
|
||||
|
||||
@@ -78,20 +78,19 @@
|
||||
(not-empty))
|
||||
|
||||
changes
|
||||
(binding [cts/*wasm-sync-override* true]
|
||||
(-> (pcb/empty-changes it page-id)
|
||||
(pcb/set-save-undo? save-undo?)
|
||||
(pcb/set-stack-undo? stack-undo?)
|
||||
(cls/generate-update-shapes ids
|
||||
update-fn
|
||||
objects
|
||||
{:attrs attrs
|
||||
:changed-sub-attr changed-sub-attr
|
||||
:ignore-tree ignore-tree
|
||||
:ignore-touched ignore-touched
|
||||
:with-objects? with-objects?})
|
||||
(cond-> undo-group
|
||||
(pcb/set-undo-group undo-group))))
|
||||
(-> (pcb/empty-changes it page-id)
|
||||
(pcb/set-save-undo? save-undo?)
|
||||
(pcb/set-stack-undo? stack-undo?)
|
||||
(cls/generate-update-shapes ids
|
||||
update-fn
|
||||
objects
|
||||
{:attrs attrs
|
||||
:changed-sub-attr changed-sub-attr
|
||||
:ignore-tree ignore-tree
|
||||
:ignore-touched ignore-touched
|
||||
:with-objects? with-objects?})
|
||||
(cond-> undo-group
|
||||
(pcb/set-undo-group undo-group)))
|
||||
|
||||
changes
|
||||
(add-undo-group changes state)]
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
[app.common.geom.rect :as grc]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.logic.shapes :as cls]
|
||||
[app.common.types.shape :as cts]
|
||||
[app.common.types.shape.layout :as ctl]
|
||||
[app.common.types.token :as tk]
|
||||
[app.main.constants :refer [size-presets]]
|
||||
@@ -295,9 +294,8 @@
|
||||
(mf/use-fn
|
||||
(mf/deps ids)
|
||||
(fn [value attr]
|
||||
(binding [cts/*wasm-sync* true]
|
||||
(st/emit! (udw/trigger-bounding-box-cloaking ids)
|
||||
(udw/update-dimensions ids attr value)))))
|
||||
(st/emit! (udw/trigger-bounding-box-cloaking ids)
|
||||
(udw/update-dimensions ids attr value))))
|
||||
|
||||
on-size-change
|
||||
(mf/use-fn
|
||||
@@ -306,16 +304,14 @@
|
||||
(if (or (string? value) (int? value))
|
||||
(do
|
||||
(st/emit! (udw/trigger-bounding-box-cloaking ids))
|
||||
(binding [cts/*wasm-sync* true]
|
||||
(run! #(do-size-change value attr) shapes)))
|
||||
(run! #(do-size-change value attr) shapes))
|
||||
(do
|
||||
(let [resolved-value (:resolved-value (first value))]
|
||||
(st/emit! (udw/trigger-bounding-box-cloaking ids)
|
||||
(dwta/toggle-token {:token (first value)
|
||||
:attrs #{attr}
|
||||
:shape-ids ids}))
|
||||
(binding [cts/*wasm-sync* true]
|
||||
(run! #(do-size-change resolved-value attr) shapes)))))))
|
||||
(run! #(do-size-change resolved-value attr) shapes))))))
|
||||
|
||||
on-proportion-lock-change
|
||||
(mf/use-fn
|
||||
@@ -337,16 +333,14 @@
|
||||
(if (or (string? value) (int? value))
|
||||
(do
|
||||
(st/emit! (udw/trigger-bounding-box-cloaking ids))
|
||||
(binding [cts/*wasm-sync* true]
|
||||
(run! #(do-position-change %1 value attr) shapes)))
|
||||
(run! #(do-position-change %1 value attr) shapes))
|
||||
(do
|
||||
(let [resolved-value (:resolved-value (first value))]
|
||||
(st/emit! (udw/trigger-bounding-box-cloaking ids)
|
||||
(dwta/toggle-token {:token (first value)
|
||||
:attrs #{attr}
|
||||
:shape-ids ids}))
|
||||
(binding [cts/*wasm-sync* true]
|
||||
(run! #(do-position-change %1 resolved-value attr) shapes)))))))
|
||||
(run! #(do-position-change %1 resolved-value attr) shapes))))))
|
||||
|
||||
;; ROTATION
|
||||
do-rotation-change
|
||||
@@ -362,16 +356,14 @@
|
||||
(if (or (string? value) (int? value))
|
||||
(do
|
||||
(st/emit! (udw/trigger-bounding-box-cloaking ids))
|
||||
(binding [cts/*wasm-sync* true]
|
||||
(run! #(do-rotation-change value) shapes)))
|
||||
(run! #(do-rotation-change value) shapes))
|
||||
(do
|
||||
(let [resolved-value (:resolved-value (first value))]
|
||||
(st/emit! (udw/trigger-bounding-box-cloaking ids)
|
||||
(dwta/toggle-token {:token (first value)
|
||||
:attrs #{:rotation}
|
||||
:shape-ids ids}))
|
||||
(binding [cts/*wasm-sync* true]
|
||||
(run! #(do-rotation-change resolved-value) shapes)))))))
|
||||
(run! #(do-rotation-change resolved-value) shapes))))))
|
||||
|
||||
on-width-change
|
||||
(mf/use-fn (mf/deps on-size-change) #(on-size-change % :width))
|
||||
|
||||
@@ -113,8 +113,7 @@
|
||||
objects-modified
|
||||
(mf/with-memo
|
||||
[base-objects wasm-modifiers]
|
||||
(binding [cts/*wasm-sync* false]
|
||||
(apply-modifiers-to-selected selected base-objects wasm-modifiers)))
|
||||
(apply-modifiers-to-selected selected base-objects wasm-modifiers))
|
||||
|
||||
selected-shapes (->> selected
|
||||
(into [] (keep (d/getf objects-modified)))
|
||||
|
||||
@@ -120,8 +120,11 @@
|
||||
(-write writer (str "#penpot/shape " (:id delegate)))))
|
||||
|
||||
;; --- SHAPE IMPL
|
||||
|
||||
(defn- set-wasm-single-attr!
|
||||
;; When an attribute is sent to WASM it could still be pending some side operations
|
||||
;; for example: font loading when changing a text, this is an async operation that will
|
||||
;; resolve eventually.
|
||||
;; The `set-wasm-attr!` can return a list of callbacks to be executed in a second pass.
|
||||
(defn- set-wasm-attr!
|
||||
[shape k]
|
||||
(let [v (get shape k)
|
||||
id (get shape :id)]
|
||||
@@ -226,58 +229,37 @@
|
||||
(ctl/flex-layout? shape)
|
||||
(api/set-flex-layout shape)))
|
||||
|
||||
;; Property not in WASM
|
||||
nil)))
|
||||
|
||||
(defn set-wasm-multi-attrs!
|
||||
(defn process-shape!
|
||||
[shape properties]
|
||||
(let [shape-id (dm/get-prop shape :id)]
|
||||
(when (shape-in-current-page? shape-id)
|
||||
(api/use-shape shape-id)
|
||||
(let [result
|
||||
(->> properties
|
||||
(mapcat #(set-wasm-single-attr! shape %)))
|
||||
pending (-> (d/index-by :key :callback result) vals)]
|
||||
(if (and pending (seq pending))
|
||||
(->> (rx/from pending)
|
||||
(rx/mapcat (fn [callback] (callback)))
|
||||
(rx/reduce conj [])
|
||||
(rx/subs!
|
||||
(fn [_]
|
||||
(api/update-shape-tiles)
|
||||
(api/clear-drawing-cache)
|
||||
(api/request-render "set-wasm-attrs-pending"))))
|
||||
(do
|
||||
(api/update-shape-tiles)
|
||||
(api/request-render "set-wasm-attrs")))))))
|
||||
|
||||
(defn set-wasm-attrs!
|
||||
[shape k v]
|
||||
(let [shape-id (dm/get-prop shape :id)
|
||||
old-value (get shape k)]
|
||||
(when (and (shape-in-current-page? shape-id)
|
||||
(not (identical? old-value v)))
|
||||
(let [shape (assoc shape k v)]
|
||||
(if (shape-in-current-page? shape-id)
|
||||
(do
|
||||
(api/use-shape shape-id)
|
||||
(let [result (set-wasm-single-attr! shape k)
|
||||
pending (-> (d/index-by :key :callback result) vals)]
|
||||
(if (and pending (seq pending))
|
||||
(->> (rx/from pending)
|
||||
(rx/mapcat (fn [callback] (callback)))
|
||||
(rx/reduce conj [])
|
||||
(rx/subs!
|
||||
(fn [_]
|
||||
(api/update-shape-tiles)
|
||||
(api/clear-drawing-cache)
|
||||
(api/request-render "set-wasm-attrs-pending"))))
|
||||
(do
|
||||
(api/update-shape-tiles)
|
||||
(api/request-render "set-wasm-attrs"))))))))
|
||||
(->> properties
|
||||
(mapcat #(set-wasm-attr! shape %))
|
||||
(d/index-by :key :callback)
|
||||
(vals)
|
||||
(rx/from)
|
||||
(rx/mapcat (fn [callback] (callback)))
|
||||
(rx/reduce conj [])))
|
||||
(rx/empty))))
|
||||
|
||||
(defn process-shape-changes!
|
||||
[objects shape-changes]
|
||||
(->> (rx/from shape-changes)
|
||||
(rx/mapcat (fn [[shape-id props]] (process-shape! (get objects shape-id) props)))
|
||||
(rx/subs!
|
||||
(fn [_]
|
||||
(api/update-shape-tiles)
|
||||
(api/request-render "set-wasm-attrs")))))
|
||||
|
||||
(defn- impl-assoc
|
||||
[self k v]
|
||||
(when ^boolean shape/*wasm-sync*
|
||||
(binding [shape/*wasm-sync* false]
|
||||
(set-wasm-attrs! self k v)))
|
||||
(when shape/*shape-changes*
|
||||
(vswap! shape/*shape-changes* update (:id self) (fnil conj #{}) k))
|
||||
|
||||
(case k
|
||||
:id
|
||||
@@ -299,10 +281,14 @@
|
||||
|
||||
(defn- impl-dissoc
|
||||
[self k]
|
||||
(when ^boolean shape/*wasm-sync*
|
||||
(binding [shape/*wasm-sync* false]
|
||||
(when (shape-in-current-page? (.-id ^ShapeProxy self))
|
||||
(set-wasm-attrs! self k nil))))
|
||||
#_(when ^boolean shape/*wasm-sync*
|
||||
(binding [shape/*wasm-sync* false]
|
||||
(when (shape-in-current-page? (.-id ^ShapeProxy self))
|
||||
(set-wasm-attrs! self k nil))))
|
||||
|
||||
(when shape/*shape-changes*
|
||||
(vswap! shape/*shape-changes* update (:id self) (fnil conj #{}) k))
|
||||
|
||||
(case k
|
||||
:id
|
||||
(ShapeProxy. nil
|
||||
|
||||
@@ -20,10 +20,11 @@ use mem::SerializableResult;
|
||||
use shapes::{StructureEntry, StructureEntryType, TransformEntry};
|
||||
use skia_safe as skia;
|
||||
use state::State;
|
||||
use std::collections::HashMap;
|
||||
use utils::uuid_from_u32_quartet;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub(crate) static mut STATE: Option<Box<State>> = None;
|
||||
pub(crate) static mut STATE: Option<Box<State<'static>>> = None;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! with_state_mut {
|
||||
@@ -253,8 +254,8 @@ pub extern "C" fn set_shape_masked_group(masked: bool) {
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn set_shape_selrect(left: f32, top: f32, right: f32, bottom: f32) {
|
||||
with_state_mut!(state, {
|
||||
state.set_selrect_for_current_shape(left, top, right, bottom);
|
||||
with_current_shape_mut!(state, |shape: &mut Shape| {
|
||||
shape.set_selrect(left, top, right, bottom);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -537,6 +538,7 @@ pub extern "C" fn set_structure_modifiers() {
|
||||
.collect();
|
||||
|
||||
with_state_mut!(state, {
|
||||
let mut structure = HashMap::new();
|
||||
for entry in entries {
|
||||
match entry.entry_type {
|
||||
StructureEntryType::ScaleContent => {
|
||||
@@ -548,15 +550,17 @@ pub extern "C" fn set_structure_modifiers() {
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
state.structure.entry(entry.parent).or_insert_with(Vec::new);
|
||||
state
|
||||
.structure
|
||||
structure.entry(entry.parent).or_insert_with(Vec::new);
|
||||
structure
|
||||
.get_mut(&entry.parent)
|
||||
.expect("Parent not found for entry")
|
||||
.push(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
if !structure.is_empty() {
|
||||
state.shapes.set_structure(structure);
|
||||
}
|
||||
});
|
||||
|
||||
mem::free_bytes();
|
||||
@@ -567,7 +571,8 @@ pub extern "C" fn clean_modifiers() {
|
||||
with_state_mut!(state, {
|
||||
state.structure.clear();
|
||||
state.scale_content.clear();
|
||||
state.modifiers.clear();
|
||||
// state.modifiers.clear();
|
||||
state.shapes.clean_modifiers();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -595,11 +600,16 @@ pub extern "C" fn set_modifiers() {
|
||||
.map(|data| TransformEntry::from_bytes(data.try_into().unwrap()))
|
||||
.collect();
|
||||
|
||||
let mut modifiers = HashMap::new();
|
||||
let mut ids = Vec::<Uuid>::new();
|
||||
for entry in entries {
|
||||
modifiers.insert(entry.id, entry.transform);
|
||||
ids.push(entry.id);
|
||||
}
|
||||
|
||||
with_state_mut!(state, {
|
||||
for entry in entries {
|
||||
state.modifiers.insert(entry.id, entry.transform);
|
||||
}
|
||||
state.rebuild_modifier_tiles();
|
||||
state.set_modifiers(modifiers);
|
||||
state.rebuild_modifier_tiles(ids);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use super::Matrix;
|
||||
use crate::render::{RenderState, SurfaceId};
|
||||
use crate::shapes::{BoolType, Path, Segment, Shape, StructureEntry, ToPath, Type};
|
||||
use crate::state::ShapesPool;
|
||||
use crate::state::ShapesPoolRef;
|
||||
use crate::uuid::Uuid;
|
||||
use bezier_rs::{Bezier, BezierHandles, ProjectionOptions, TValue};
|
||||
use glam::DVec2;
|
||||
@@ -387,7 +387,7 @@ fn beziers_to_segments(beziers: &[(BezierSource, Bezier)]) -> Vec<Segment> {
|
||||
pub fn bool_from_shapes(
|
||||
bool_type: BoolType,
|
||||
children_ids: &IndexSet<Uuid>,
|
||||
shapes: &ShapesPool,
|
||||
shapes: ShapesPoolRef,
|
||||
modifiers: &HashMap<Uuid, Matrix>,
|
||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
||||
) -> Path {
|
||||
@@ -424,7 +424,7 @@ pub fn bool_from_shapes(
|
||||
|
||||
pub fn update_bool_to_path(
|
||||
shape: &Shape,
|
||||
shapes: &ShapesPool,
|
||||
shapes: ShapesPoolRef,
|
||||
modifiers: &HashMap<Uuid, Matrix>,
|
||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
||||
) -> Shape {
|
||||
@@ -449,7 +449,7 @@ pub fn update_bool_to_path(
|
||||
pub fn debug_render_bool_paths(
|
||||
render_state: &mut RenderState,
|
||||
shape: &Shape,
|
||||
shapes: &ShapesPool,
|
||||
shapes: ShapesPoolRef,
|
||||
modifiers: &HashMap<Uuid, Matrix>,
|
||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
||||
) {
|
||||
|
||||
@@ -22,9 +22,10 @@ pub use surfaces::{SurfaceId, Surfaces};
|
||||
|
||||
use crate::performance;
|
||||
use crate::shapes::{
|
||||
Blur, BlurType, Corners, Fill, Shadow, Shape, SolidColor, Stroke, StructureEntry, Type,
|
||||
all_with_ancestors, Blur, BlurType, Corners, Fill, Shadow, Shape, SolidColor, Stroke,
|
||||
StructureEntry, Type,
|
||||
};
|
||||
use crate::state::ShapesPool;
|
||||
use crate::state::{ShapesPoolMutRef, ShapesPoolRef};
|
||||
use crate::tiles::{self, PendingTiles, TileRect};
|
||||
use crate::uuid::Uuid;
|
||||
use crate::view::Viewbox;
|
||||
@@ -275,7 +276,7 @@ pub fn get_cache_size(viewbox: Viewbox, scale: f32) -> skia::ISize {
|
||||
|
||||
fn is_modified_child(
|
||||
shape: &Shape,
|
||||
shapes: &ShapesPool,
|
||||
shapes: ShapesPoolRef,
|
||||
modifiers: &HashMap<Uuid, Matrix>,
|
||||
) -> bool {
|
||||
if modifiers.is_empty() {
|
||||
@@ -474,7 +475,7 @@ impl RenderState {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn render_shape(
|
||||
&mut self,
|
||||
shapes: &ShapesPool,
|
||||
shapes: ShapesPoolRef,
|
||||
modifiers: &HashMap<Uuid, Matrix>,
|
||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
||||
shape: &Shape,
|
||||
@@ -833,7 +834,7 @@ impl RenderState {
|
||||
|
||||
pub fn render_from_cache(
|
||||
&mut self,
|
||||
shapes: &ShapesPool,
|
||||
shapes: ShapesPoolRef,
|
||||
modifiers: &HashMap<Uuid, Matrix>,
|
||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
||||
) {
|
||||
@@ -878,7 +879,7 @@ impl RenderState {
|
||||
|
||||
pub fn start_render_loop(
|
||||
&mut self,
|
||||
tree: &ShapesPool,
|
||||
tree: ShapesPoolRef,
|
||||
modifiers: &HashMap<Uuid, Matrix>,
|
||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
||||
scale_content: &HashMap<Uuid, f32>,
|
||||
@@ -938,7 +939,7 @@ impl RenderState {
|
||||
|
||||
pub fn process_animation_frame(
|
||||
&mut self,
|
||||
tree: &ShapesPool,
|
||||
tree: ShapesPoolRef,
|
||||
modifiers: &HashMap<Uuid, Matrix>,
|
||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
||||
scale_content: &HashMap<Uuid, f32>,
|
||||
@@ -1021,7 +1022,7 @@ impl RenderState {
|
||||
#[inline]
|
||||
pub fn render_shape_exit(
|
||||
&mut self,
|
||||
tree: &ShapesPool,
|
||||
tree: ShapesPoolRef,
|
||||
modifiers: &HashMap<Uuid, Matrix>,
|
||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
||||
element: &Shape,
|
||||
@@ -1133,13 +1134,8 @@ impl RenderState {
|
||||
self.get_rect_bounds(rect)
|
||||
}
|
||||
|
||||
pub fn get_shape_extrect_bounds(
|
||||
&mut self,
|
||||
shape: &Shape,
|
||||
tree: &ShapesPool,
|
||||
modifiers: &HashMap<Uuid, Matrix>,
|
||||
) -> Rect {
|
||||
let rect = shape.extrect(tree, modifiers);
|
||||
pub fn get_shape_extrect_bounds(&mut self, shape: &Shape, tree: ShapesPoolRef) -> Rect {
|
||||
let rect = shape.extrect(tree);
|
||||
self.get_rect_bounds(rect)
|
||||
}
|
||||
|
||||
@@ -1176,7 +1172,7 @@ impl RenderState {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn render_drop_black_shadow(
|
||||
&mut self,
|
||||
shapes: &ShapesPool,
|
||||
shapes: ShapesPoolRef,
|
||||
modifiers: &HashMap<Uuid, Matrix>,
|
||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
||||
shape: &Shape,
|
||||
@@ -1250,7 +1246,7 @@ impl RenderState {
|
||||
|
||||
pub fn render_shape_tree_partial_uncached(
|
||||
&mut self,
|
||||
tree: &ShapesPool,
|
||||
tree: ShapesPoolRef,
|
||||
modifiers: &HashMap<Uuid, Matrix>,
|
||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
||||
scale_content: &HashMap<Uuid, f32>,
|
||||
@@ -1277,7 +1273,7 @@ impl RenderState {
|
||||
// If the shape is not in the tile set, then we update
|
||||
// it.
|
||||
if self.tiles.get_tiles_of(node_id).is_none() {
|
||||
self.update_tile_for(element, tree, modifiers);
|
||||
self.update_tile_for(element, tree);
|
||||
}
|
||||
|
||||
if visited_children {
|
||||
@@ -1296,18 +1292,14 @@ impl RenderState {
|
||||
let transformed_element: Cow<Shape> = Cow::Borrowed(element);
|
||||
|
||||
let is_visible = transformed_element
|
||||
.extrect(tree, modifiers)
|
||||
.extrect(tree)
|
||||
.intersects(self.render_area)
|
||||
&& !transformed_element.hidden
|
||||
&& !transformed_element.visually_insignificant(
|
||||
self.get_scale(),
|
||||
tree,
|
||||
modifiers,
|
||||
);
|
||||
&& !transformed_element.visually_insignificant(self.get_scale(), tree);
|
||||
|
||||
if self.options.is_debug_visible() {
|
||||
let shape_extrect_bounds =
|
||||
self.get_shape_extrect_bounds(&transformed_element, tree, modifiers);
|
||||
self.get_shape_extrect_bounds(&transformed_element, tree);
|
||||
debug::render_debug_shape(self, None, Some(shape_extrect_bounds));
|
||||
}
|
||||
|
||||
@@ -1538,7 +1530,7 @@ impl RenderState {
|
||||
|
||||
pub fn render_shape_tree_partial(
|
||||
&mut self,
|
||||
tree: &ShapesPool,
|
||||
tree: ShapesPoolRef,
|
||||
modifiers: &HashMap<Uuid, Matrix>,
|
||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
||||
scale_content: &HashMap<Uuid, f32>,
|
||||
@@ -1656,23 +1648,14 @@ impl RenderState {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_tiles_for_shape(
|
||||
&mut self,
|
||||
shape: &Shape,
|
||||
tree: &ShapesPool,
|
||||
modifiers: &HashMap<Uuid, Matrix>,
|
||||
) -> TileRect {
|
||||
pub fn get_tiles_for_shape(&mut self, shape: &Shape, tree: ShapesPoolRef) -> TileRect {
|
||||
let extrect = shape.extrect(tree);
|
||||
let tile_size = tiles::get_tile_size(self.get_scale());
|
||||
tiles::get_tiles_for_rect(shape.extrect(tree, modifiers), tile_size)
|
||||
tiles::get_tiles_for_rect(extrect, tile_size)
|
||||
}
|
||||
|
||||
pub fn update_tile_for(
|
||||
&mut self,
|
||||
shape: &Shape,
|
||||
tree: &ShapesPool,
|
||||
modifiers: &HashMap<Uuid, Matrix>,
|
||||
) {
|
||||
let TileRect(rsx, rsy, rex, rey) = self.get_tiles_for_shape(shape, tree, modifiers);
|
||||
pub fn update_tile_for(&mut self, shape: &Shape, tree: ShapesPoolRef) {
|
||||
let TileRect(rsx, rsy, rex, rey) = self.get_tiles_for_shape(shape, tree);
|
||||
let old_tiles: HashSet<tiles::Tile> = self
|
||||
.tiles
|
||||
.get_tiles_of(shape.id)
|
||||
@@ -1701,7 +1684,7 @@ impl RenderState {
|
||||
|
||||
pub fn rebuild_tiles_shallow(
|
||||
&mut self,
|
||||
tree: &ShapesPool,
|
||||
tree: ShapesPoolRef,
|
||||
modifiers: &HashMap<Uuid, Matrix>,
|
||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
||||
) {
|
||||
@@ -1716,7 +1699,7 @@ impl RenderState {
|
||||
if let Some(modifier) = modifiers.get(&shape_id) {
|
||||
shape.to_mut().apply_transform(modifier);
|
||||
}
|
||||
self.update_tile_for(&shape, tree, modifiers);
|
||||
self.update_tile_for(&shape, tree);
|
||||
} else {
|
||||
// We only need to rebuild tiles from the first level.
|
||||
let children = shape.modified_children_ids(structure.get(&shape.id), false);
|
||||
@@ -1731,7 +1714,7 @@ impl RenderState {
|
||||
|
||||
pub fn rebuild_tiles(
|
||||
&mut self,
|
||||
tree: &ShapesPool,
|
||||
tree: ShapesPoolRef,
|
||||
modifiers: &HashMap<Uuid, Matrix>,
|
||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
||||
) {
|
||||
@@ -1746,7 +1729,7 @@ impl RenderState {
|
||||
if let Some(modifier) = modifiers.get(&shape_id) {
|
||||
shape.to_mut().apply_transform(modifier);
|
||||
}
|
||||
self.update_tile_for(&shape, tree, modifiers);
|
||||
self.update_tile_for(&shape, tree);
|
||||
}
|
||||
|
||||
let children = shape.modified_children_ids(structure.get(&shape.id), false);
|
||||
@@ -1769,64 +1752,26 @@ impl RenderState {
|
||||
pub fn invalidate_and_update_tiles(
|
||||
&mut self,
|
||||
shape_ids: &IndexSet<Uuid>,
|
||||
tree: &mut ShapesPool,
|
||||
modifiers: &HashMap<Uuid, Matrix>,
|
||||
tree: ShapesPoolMutRef<'_>,
|
||||
) {
|
||||
for shape_id in shape_ids {
|
||||
if let Some(shape) = tree.get_mut(shape_id) {
|
||||
shape.invalidate_extrect();
|
||||
}
|
||||
if let Some(shape) = tree.get(shape_id) {
|
||||
if !shape.id.is_nil() {
|
||||
self.update_tile_for(shape, tree, modifiers);
|
||||
self.update_tile_for(shape, tree);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Processes all ancestors of a shape, invalidating their extended rectangles and updating their tiles
|
||||
///
|
||||
/// When a shape changes, all its ancestors need to have their extended rectangles recalculated
|
||||
/// because they may contain the changed shape. This function:
|
||||
/// 1. Computes all ancestors of the shape
|
||||
/// 2. Invalidates the extrect cache for each ancestor
|
||||
/// 3. Updates the tiles for each ancestor to ensure proper rendering
|
||||
pub fn process_shape_ancestors(
|
||||
&mut self,
|
||||
shape: &Shape,
|
||||
tree: &mut ShapesPool,
|
||||
modifiers: &HashMap<Uuid, Matrix>,
|
||||
) {
|
||||
let ancestors = shape.all_ancestors(tree, false);
|
||||
self.invalidate_and_update_tiles(&ancestors, tree, modifiers);
|
||||
}
|
||||
|
||||
/// Rebuilds tiles for shapes with modifiers and processes their ancestors
|
||||
///
|
||||
/// This function applies transformation modifiers to shapes and updates their tiles.
|
||||
/// Additionally, it processes all ancestors of modified shapes to ensure their
|
||||
/// extended rectangles are properly recalculated and their tiles are updated.
|
||||
/// This is crucial for frames and groups that contain transformed children.
|
||||
pub fn rebuild_modifier_tiles(
|
||||
&mut self,
|
||||
tree: &mut ShapesPool,
|
||||
modifiers: &HashMap<Uuid, Matrix>,
|
||||
) {
|
||||
let mut ancestors = IndexSet::new();
|
||||
for (uuid, matrix) in modifiers {
|
||||
let mut shape = {
|
||||
let Some(shape) = tree.get(uuid) else {
|
||||
panic!("Invalid current shape")
|
||||
};
|
||||
let shape: Cow<Shape> = Cow::Borrowed(shape);
|
||||
shape
|
||||
};
|
||||
|
||||
shape.to_mut().apply_transform(matrix);
|
||||
ancestors.insert(*uuid);
|
||||
ancestors.extend(shape.all_ancestors(tree, false));
|
||||
}
|
||||
self.invalidate_and_update_tiles(&ancestors, tree, modifiers);
|
||||
pub fn rebuild_modifier_tiles(&mut self, tree: ShapesPoolMutRef<'_>, ids: Vec<Uuid>) {
|
||||
let ancestors = all_with_ancestors(&ids, tree, false);
|
||||
self.invalidate_and_update_tiles(&ancestors, tree);
|
||||
}
|
||||
|
||||
pub fn get_scale(&self) -> f32 {
|
||||
|
||||
@@ -4,14 +4,14 @@ use std::collections::HashMap;
|
||||
use crate::math::{Matrix, Rect};
|
||||
use crate::shapes::modifiers::grid_layout::grid_cell_data;
|
||||
use crate::shapes::{Shape, StructureEntry};
|
||||
use crate::state::ShapesPool;
|
||||
use crate::state::ShapesPoolRef;
|
||||
use crate::uuid::Uuid;
|
||||
|
||||
pub fn render_overlay(
|
||||
zoom: f32,
|
||||
canvas: &skia::Canvas,
|
||||
shape: &Shape,
|
||||
shapes: &ShapesPool,
|
||||
shapes: ShapesPoolRef,
|
||||
modifiers: &HashMap<Uuid, Matrix>,
|
||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
||||
) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use skia_safe::{self as skia, Color4f};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use super::{RenderState, ShapesPool, SurfaceId};
|
||||
use super::{RenderState, ShapesPoolRef, SurfaceId};
|
||||
use crate::math::Matrix;
|
||||
use crate::render::grid_layout;
|
||||
use crate::shapes::StructureEntry;
|
||||
@@ -9,7 +9,7 @@ use crate::uuid::Uuid;
|
||||
|
||||
pub fn render(
|
||||
render_state: &mut RenderState,
|
||||
shapes: &ShapesPool,
|
||||
shapes: ShapesPoolRef,
|
||||
modifiers: &HashMap<Uuid, Matrix>,
|
||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
||||
) {
|
||||
|
||||
@@ -3,7 +3,7 @@ use skia_safe::{self as skia};
|
||||
use crate::uuid::Uuid;
|
||||
use std::borrow::Cow;
|
||||
use std::cell::OnceCell;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::collections::HashSet;
|
||||
use std::iter::once;
|
||||
|
||||
mod blend;
|
||||
@@ -50,7 +50,7 @@ pub use transform::*;
|
||||
use crate::math::{self, Bounds, Matrix, Point};
|
||||
use indexmap::IndexSet;
|
||||
|
||||
use crate::state::ShapesPool;
|
||||
use crate::state::ShapesPoolRef;
|
||||
|
||||
const MIN_VISIBLE_SIZE: f32 = 2.0;
|
||||
const ANTIALIAS_THRESHOLD: f32 = 15.0;
|
||||
@@ -180,6 +180,60 @@ pub struct Shape {
|
||||
pub shadows: Vec<Shadow>,
|
||||
pub layout_item: Option<LayoutItem>,
|
||||
pub extrect: OnceCell<math::Rect>,
|
||||
pub bounds: OnceCell<math::Bounds>,
|
||||
}
|
||||
|
||||
// Returns all ancestor shapes of this shape, traversing up the parent hierarchy
|
||||
//
|
||||
// This function walks up the parent chain starting from this shape's parent,
|
||||
// collecting all ancestor IDs. It stops when it reaches a nil UUID or when
|
||||
// an ancestor is hidden (unless include_hidden is true).
|
||||
//
|
||||
// # Arguments
|
||||
// * `shapes` - The shapes pool containing all shapes
|
||||
// * `include_hidden` - Whether to include hidden ancestors in the result
|
||||
//
|
||||
// # Returns
|
||||
// A set of ancestor UUIDs in traversal order (closest ancestor first)
|
||||
pub fn all_with_ancestors(
|
||||
shapes: &[Uuid],
|
||||
shapes_pool: ShapesPoolRef,
|
||||
include_hidden: bool,
|
||||
) -> IndexSet<Uuid> {
|
||||
let mut pending = Vec::from_iter(shapes.iter());
|
||||
let mut result = IndexSet::new();
|
||||
|
||||
while !pending.is_empty() {
|
||||
let Some(current_id) = pending.pop() else {
|
||||
break;
|
||||
};
|
||||
|
||||
result.insert(*current_id);
|
||||
|
||||
let Some(parent_id) = shapes_pool.get(current_id).and_then(|s| s.parent_id) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if parent_id == Uuid::nil() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if result.contains(&parent_id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if the ancestor is hidden
|
||||
let Some(parent) = shapes_pool.get(&parent_id) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if !include_hidden && parent.hidden() {
|
||||
continue;
|
||||
}
|
||||
|
||||
pending.push(&parent.id);
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
impl Shape {
|
||||
@@ -207,6 +261,7 @@ impl Shape {
|
||||
shadows: Vec::with_capacity(1),
|
||||
layout_item: None,
|
||||
extrect: OnceCell::new(),
|
||||
bounds: OnceCell::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,6 +292,10 @@ impl Shape {
|
||||
self.extrect = OnceCell::new();
|
||||
}
|
||||
|
||||
pub fn invalidate_bounds(&mut self) {
|
||||
self.bounds = OnceCell::new();
|
||||
}
|
||||
|
||||
pub fn set_parent(&mut self, id: Uuid) {
|
||||
self.parent_id = Some(id);
|
||||
}
|
||||
@@ -266,6 +325,7 @@ impl Shape {
|
||||
|
||||
pub fn set_selrect(&mut self, left: f32, top: f32, right: f32, bottom: f32) {
|
||||
self.invalidate_extrect();
|
||||
self.invalidate_bounds();
|
||||
self.selrect.set_ltrb(left, top, right, bottom);
|
||||
if let Type::Text(ref mut text) = self.shape_type {
|
||||
text.update_layout(self.selrect);
|
||||
@@ -617,13 +677,8 @@ impl Shape {
|
||||
self.selrect.width()
|
||||
}
|
||||
|
||||
pub fn visually_insignificant(
|
||||
&self,
|
||||
scale: f32,
|
||||
shapes_pool: &ShapesPool,
|
||||
modifiers: &HashMap<Uuid, Matrix>,
|
||||
) -> bool {
|
||||
let extrect = self.extrect(shapes_pool, modifiers);
|
||||
pub fn visually_insignificant(&self, scale: f32, shapes_pool: ShapesPoolRef) -> bool {
|
||||
let extrect = self.extrect(shapes_pool);
|
||||
extrect.width() * scale < MIN_VISIBLE_SIZE && extrect.height() * scale < MIN_VISIBLE_SIZE
|
||||
}
|
||||
|
||||
@@ -632,8 +687,7 @@ impl Shape {
|
||||
|| self.selrect.height() * scale > ANTIALIAS_THRESHOLD
|
||||
}
|
||||
|
||||
// TODO: Maybe store this inside the shape
|
||||
pub fn bounds(&self) -> Bounds {
|
||||
pub fn calculate_bounds(&self) -> Bounds {
|
||||
let mut bounds = Bounds::new(
|
||||
Point::new(self.selrect.x(), self.selrect.y()),
|
||||
Point::new(self.selrect.x() + self.selrect.width(), self.selrect.y()),
|
||||
@@ -659,18 +713,18 @@ impl Shape {
|
||||
bounds
|
||||
}
|
||||
|
||||
pub fn bounds(&self) -> Bounds {
|
||||
*self.bounds.get_or_init(|| self.calculate_bounds())
|
||||
}
|
||||
|
||||
pub fn selrect(&self) -> math::Rect {
|
||||
self.selrect
|
||||
}
|
||||
|
||||
pub fn extrect(
|
||||
&self,
|
||||
shapes_pool: &ShapesPool,
|
||||
modifiers: &HashMap<Uuid, Matrix>,
|
||||
) -> math::Rect {
|
||||
pub fn extrect(&self, shapes_pool: ShapesPoolRef) -> math::Rect {
|
||||
*self
|
||||
.extrect
|
||||
.get_or_init(|| self.calculate_extrect(shapes_pool, modifiers))
|
||||
.get_or_init(|| self.calculate_extrect(shapes_pool))
|
||||
}
|
||||
|
||||
pub fn get_text_content(&self) -> &TextContent {
|
||||
@@ -783,8 +837,7 @@ impl Shape {
|
||||
fn apply_children_bounds(
|
||||
&self,
|
||||
mut rect: math::Rect,
|
||||
shapes_pool: &ShapesPool,
|
||||
modifiers: &HashMap<Uuid, Matrix>,
|
||||
shapes_pool: ShapesPoolRef,
|
||||
) -> math::Rect {
|
||||
let include_children = match self.shape_type {
|
||||
Type::Group(_) => true,
|
||||
@@ -795,15 +848,7 @@ impl Shape {
|
||||
if include_children {
|
||||
for child_id in self.children_ids(false) {
|
||||
if let Some(child_shape) = shapes_pool.get(&child_id) {
|
||||
// Create a copy of the child shape to apply any transformations
|
||||
let mut transformed_element: Cow<Shape> = Cow::Borrowed(child_shape);
|
||||
if let Some(modifier) = modifiers.get(&child_id) {
|
||||
transformed_element.to_mut().apply_transform(modifier);
|
||||
}
|
||||
|
||||
// Get the child's extended rectangle and join it with the container's rectangle
|
||||
let child_extrect = transformed_element.extrect(shapes_pool, modifiers);
|
||||
rect.join(child_extrect);
|
||||
rect.join(child_shape.extrect(shapes_pool));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -811,12 +856,8 @@ impl Shape {
|
||||
rect
|
||||
}
|
||||
|
||||
pub fn calculate_extrect(
|
||||
&self,
|
||||
shapes_pool: &ShapesPool,
|
||||
modifiers: &HashMap<Uuid, Matrix>,
|
||||
) -> math::Rect {
|
||||
let shape = self.transformed(modifiers.get(&self.id));
|
||||
pub fn calculate_extrect(&self, shapes_pool: ShapesPoolRef) -> math::Rect {
|
||||
let shape = self;
|
||||
let max_stroke = Stroke::max_bounds_width(shape.strokes.iter(), shape.is_open());
|
||||
|
||||
let mut rect = match &shape.shape_type {
|
||||
@@ -839,7 +880,7 @@ impl Shape {
|
||||
rect = self.apply_stroke_bounds(rect, max_stroke);
|
||||
rect = self.apply_shadow_bounds(rect);
|
||||
rect = self.apply_blur_bounds(rect);
|
||||
rect = self.apply_children_bounds(rect, shapes_pool, modifiers);
|
||||
rect = self.apply_children_bounds(rect, shapes_pool);
|
||||
|
||||
rect
|
||||
}
|
||||
@@ -883,9 +924,27 @@ impl Shape {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn children_ids_iter(&self, include_hidden: bool) -> Box<dyn Iterator<Item = &Uuid> + '_> {
|
||||
if include_hidden {
|
||||
return Box::new(self.children.iter().rev());
|
||||
}
|
||||
|
||||
if let Type::Bool(_) = self.shape_type {
|
||||
Box::new([].iter())
|
||||
} else if let Type::Group(group) = self.shape_type {
|
||||
if group.masked {
|
||||
Box::new(self.children.iter().rev().take(self.children.len() - 1))
|
||||
} else {
|
||||
Box::new(self.children.iter().rev())
|
||||
}
|
||||
} else {
|
||||
Box::new(self.children.iter().rev())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn all_children(
|
||||
&self,
|
||||
shapes: &ShapesPool,
|
||||
shapes: ShapesPoolRef,
|
||||
include_hidden: bool,
|
||||
include_self: bool,
|
||||
) -> IndexSet<Uuid> {
|
||||
@@ -906,47 +965,6 @@ impl Shape {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns all ancestor shapes of this shape, traversing up the parent hierarchy
|
||||
///
|
||||
/// This function walks up the parent chain starting from this shape's parent,
|
||||
/// collecting all ancestor IDs. It stops when it reaches a nil UUID or when
|
||||
/// an ancestor is hidden (unless include_hidden is true).
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `shapes` - The shapes pool containing all shapes
|
||||
/// * `include_hidden` - Whether to include hidden ancestors in the result
|
||||
///
|
||||
/// # Returns
|
||||
/// A set of ancestor UUIDs in traversal order (closest ancestor first)
|
||||
pub fn all_ancestors(&self, shapes: &ShapesPool, include_hidden: bool) -> IndexSet<Uuid> {
|
||||
let mut ancestors = IndexSet::new();
|
||||
let mut current_id = self.id;
|
||||
|
||||
// Traverse upwards using parent_id
|
||||
while let Some(parent_id) = shapes.get(¤t_id).and_then(|s| s.parent_id) {
|
||||
// If the parent_id is the zero UUID, there are no more ancestors
|
||||
if parent_id == Uuid::nil() {
|
||||
break;
|
||||
}
|
||||
|
||||
// Check if the ancestor is hidden
|
||||
if let Some(parent) = shapes.get(&parent_id) {
|
||||
if !include_hidden && parent.hidden() {
|
||||
break;
|
||||
}
|
||||
ancestors.insert(parent_id);
|
||||
current_id = parent_id;
|
||||
} else {
|
||||
// FIXME: This should panic! I've removed it temporarily until
|
||||
// we fix the problems with shapes without parents.
|
||||
// panic!("Parent can't be found");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ancestors
|
||||
}
|
||||
|
||||
pub fn get_matrix(&self) -> Matrix {
|
||||
let mut matrix = Matrix::new_identity();
|
||||
matrix.post_translate(self.left_top());
|
||||
@@ -954,7 +972,7 @@ impl Shape {
|
||||
matrix
|
||||
}
|
||||
|
||||
pub fn get_concatenated_matrix(&self, shapes: &ShapesPool) -> Matrix {
|
||||
pub fn get_concatenated_matrix(&self, shapes: ShapesPoolRef) -> Matrix {
|
||||
let mut matrix = Matrix::new_identity();
|
||||
let mut current_id = self.id;
|
||||
while let Some(parent_id) = shapes.get(¤t_id).and_then(|s| s.parent_id) {
|
||||
@@ -1122,8 +1140,12 @@ impl Shape {
|
||||
}
|
||||
|
||||
pub fn apply_transform(&mut self, transform: &Matrix) {
|
||||
self.invalidate_extrect();
|
||||
self.transform_selrect(transform);
|
||||
|
||||
// We don't need to invalidate this? we can just transform it
|
||||
self.invalidate_extrect();
|
||||
self.invalidate_bounds();
|
||||
|
||||
if let shape_type @ (Type::Path(_) | Type::Bool(_)) = &mut self.shape_type {
|
||||
if let Some(path) = shape_type.path_mut() {
|
||||
path.transform(transform);
|
||||
@@ -1133,11 +1155,41 @@ impl Shape {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn transformed(&self, transform: Option<&Matrix>) -> Self {
|
||||
pub fn apply_structure(&mut self, structure: &Vec<StructureEntry>) {
|
||||
let mut result: Vec<Uuid> = Vec::from_iter(self.children.iter().copied());
|
||||
let mut to_remove = HashSet::<&Uuid>::new();
|
||||
|
||||
for st in structure {
|
||||
match st.entry_type {
|
||||
StructureEntryType::AddChild => {
|
||||
result.insert(result.len() - st.index as usize, st.id);
|
||||
}
|
||||
StructureEntryType::RemoveChild => {
|
||||
to_remove.insert(&st.id);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
self.children = result
|
||||
.iter()
|
||||
.filter(|id| !to_remove.contains(id))
|
||||
.copied()
|
||||
.collect();
|
||||
}
|
||||
|
||||
pub fn transformed(
|
||||
&self,
|
||||
transform: Option<&Matrix>,
|
||||
structure: Option<&Vec<StructureEntry>>,
|
||||
) -> Self {
|
||||
let mut shape: Cow<Shape> = Cow::Borrowed(self);
|
||||
if let Some(transform) = transform {
|
||||
shape.to_mut().apply_transform(transform);
|
||||
}
|
||||
if let Some(structure) = structure {
|
||||
shape.to_mut().apply_structure(structure);
|
||||
}
|
||||
shape.into_owned()
|
||||
}
|
||||
|
||||
@@ -1230,6 +1282,36 @@ impl Shape {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn modified_children_ids_iter<'a>(
|
||||
&'a self,
|
||||
structure: Option<&'a Vec<StructureEntry>>,
|
||||
include_hidden: bool,
|
||||
) -> Box<dyn Iterator<Item = Cow<'a, Uuid>> + 'a> {
|
||||
if let Some(structure) = structure {
|
||||
let mut result: Vec<Cow<'a, Uuid>> = self
|
||||
.children_ids_iter(include_hidden)
|
||||
.map(Cow::Borrowed)
|
||||
.collect();
|
||||
let mut to_remove = HashSet::<Cow<'a, Uuid>>::new();
|
||||
|
||||
for st in structure {
|
||||
match st.entry_type {
|
||||
StructureEntryType::AddChild => {
|
||||
result.insert(result.len() - st.index as usize, Cow::Owned(st.id));
|
||||
}
|
||||
StructureEntryType::RemoveChild => {
|
||||
to_remove.insert(Cow::Owned(st.id));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
Box::new(result.into_iter().filter(move |id| !to_remove.contains(id)))
|
||||
} else {
|
||||
Box::new(self.children_ids_iter(include_hidden).map(Cow::Borrowed))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn drop_shadow_paints(&self) -> Vec<skia_safe::Paint> {
|
||||
let drop_shadows: Vec<&Shadow> = self.drop_shadows_visible().collect();
|
||||
|
||||
|
||||
@@ -13,13 +13,13 @@ use crate::shapes::{
|
||||
ConstraintH, ConstraintV, Frame, Group, GrowType, Layout, Modifier, Shape, StructureEntry,
|
||||
TransformEntry, Type,
|
||||
};
|
||||
use crate::state::{ShapesPool, State};
|
||||
use crate::state::{ShapesPoolRef, State};
|
||||
use crate::uuid::Uuid;
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn propagate_children(
|
||||
shape: &Shape,
|
||||
shapes: &ShapesPool,
|
||||
shapes: ShapesPoolRef,
|
||||
parent_bounds_before: &Bounds,
|
||||
parent_bounds_after: &Bounds,
|
||||
transform: Matrix,
|
||||
@@ -88,32 +88,29 @@ fn propagate_children(
|
||||
result
|
||||
}
|
||||
|
||||
// FIXME: PERFORMANCE
|
||||
fn calculate_group_bounds(
|
||||
shape: &Shape,
|
||||
shapes: &ShapesPool,
|
||||
shapes: ShapesPoolRef,
|
||||
bounds: &HashMap<Uuid, Bounds>,
|
||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
||||
) -> Option<Bounds> {
|
||||
let shape_bounds = bounds.find(shape);
|
||||
let mut result = Vec::<Point>::new();
|
||||
|
||||
let children_ids = shape.modified_children_ids(structure.get(&shape.id), true);
|
||||
for child_id in children_ids.iter() {
|
||||
let Some(child) = shapes.get(child_id) else {
|
||||
for child_id in shape.modified_children_ids_iter(structure.get(&shape.id), true) {
|
||||
let Some(child) = shapes.get(&child_id) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let child_bounds = bounds.find(child);
|
||||
result.append(&mut child_bounds.points());
|
||||
}
|
||||
|
||||
shape_bounds.with_points(result)
|
||||
}
|
||||
|
||||
fn calculate_bool_bounds(
|
||||
shape: &Shape,
|
||||
shapes: &ShapesPool,
|
||||
shapes: ShapesPoolRef,
|
||||
bounds: &HashMap<Uuid, Bounds>,
|
||||
modifiers: &HashMap<Uuid, Matrix>,
|
||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
||||
@@ -277,30 +274,32 @@ fn propagate_reflow(
|
||||
let shapes = &state.shapes;
|
||||
let mut reflow_parent = false;
|
||||
|
||||
if reflown.contains(&id) {
|
||||
return;
|
||||
}
|
||||
|
||||
match &shape.shape_type {
|
||||
Type::Frame(Frame {
|
||||
layout: Some(_), ..
|
||||
}) => {
|
||||
if !reflown.contains(id) {
|
||||
let mut skip_reflow = false;
|
||||
if shape.is_layout_horizontal_fill() || shape.is_layout_vertical_fill() {
|
||||
if let Some(parent_id) = shape.parent_id {
|
||||
if !reflown.contains(&parent_id) {
|
||||
// If this is a fill layout but the parent has not been reflown yet
|
||||
// we wait for the next iteration for reflow
|
||||
skip_reflow = true;
|
||||
reflow_parent = true;
|
||||
}
|
||||
let mut skip_reflow = false;
|
||||
if shape.is_layout_horizontal_fill() || shape.is_layout_vertical_fill() {
|
||||
if let Some(parent_id) = shape.parent_id {
|
||||
if !reflown.contains(&parent_id) {
|
||||
// If this is a fill layout but the parent has not been reflown yet
|
||||
// we wait for the next iteration for reflow
|
||||
skip_reflow = true;
|
||||
reflow_parent = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if shape.is_layout_vertical_auto() || shape.is_layout_horizontal_auto() {
|
||||
reflow_parent = true;
|
||||
}
|
||||
if shape.is_layout_vertical_auto() || shape.is_layout_horizontal_auto() {
|
||||
reflow_parent = true;
|
||||
}
|
||||
|
||||
if !skip_reflow {
|
||||
layout_reflows.push(*id);
|
||||
}
|
||||
if !skip_reflow {
|
||||
layout_reflows.push(*id);
|
||||
}
|
||||
}
|
||||
Type::Group(Group { masked: true }) => {
|
||||
@@ -310,6 +309,7 @@ fn propagate_reflow(
|
||||
bounds.insert(shape.id, child_bounds);
|
||||
reflow_parent = true;
|
||||
}
|
||||
reflown.insert(*id);
|
||||
}
|
||||
Type::Group(_) => {
|
||||
if let Some(shape_bounds) =
|
||||
@@ -318,6 +318,7 @@ fn propagate_reflow(
|
||||
bounds.insert(shape.id, shape_bounds);
|
||||
reflow_parent = true;
|
||||
}
|
||||
reflown.insert(*id);
|
||||
}
|
||||
Type::Bool(_) => {
|
||||
if let Some(shape_bounds) =
|
||||
@@ -326,6 +327,7 @@ fn propagate_reflow(
|
||||
bounds.insert(shape.id, shape_bounds);
|
||||
reflow_parent = true;
|
||||
}
|
||||
reflown.insert(*id);
|
||||
}
|
||||
_ => {
|
||||
// Other shapes don't have to be reflown
|
||||
@@ -462,7 +464,7 @@ mod tests {
|
||||
let parent_id = Uuid::new_v4();
|
||||
|
||||
let shapes = {
|
||||
let mut shapes = ShapesPool::new();
|
||||
let mut shapes = ShapesPoolRef::new();
|
||||
shapes.initialize(10);
|
||||
|
||||
let child_id = Uuid::new_v4();
|
||||
@@ -505,7 +507,7 @@ mod tests {
|
||||
fn test_group_bounds() {
|
||||
let parent_id = Uuid::new_v4();
|
||||
let shapes = {
|
||||
let mut shapes = ShapesPool::new();
|
||||
let mut shapes = ShapesPoolRef::new();
|
||||
shapes.initialize(10);
|
||||
|
||||
let child1_id = Uuid::new_v4();
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::shapes::{
|
||||
AlignContent, AlignItems, AlignSelf, FlexData, JustifyContent, LayoutData, LayoutItem,
|
||||
Modifier, Shape, StructureEntry,
|
||||
};
|
||||
use crate::state::ShapesPool;
|
||||
use crate::state::ShapesPoolRef;
|
||||
use crate::uuid::Uuid;
|
||||
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
@@ -179,7 +179,7 @@ fn initialize_tracks(
|
||||
layout_bounds: &Bounds,
|
||||
layout_axis: &LayoutAxis,
|
||||
flex_data: &FlexData,
|
||||
shapes: &ShapesPool,
|
||||
shapes: ShapesPoolRef,
|
||||
bounds: &HashMap<Uuid, Bounds>,
|
||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
||||
) -> Vec<TrackData> {
|
||||
@@ -433,7 +433,7 @@ fn calculate_track_data(
|
||||
layout_data: &LayoutData,
|
||||
flex_data: &FlexData,
|
||||
layout_bounds: &Bounds,
|
||||
shapes: &ShapesPool,
|
||||
shapes: ShapesPoolRef,
|
||||
bounds: &HashMap<Uuid, Bounds>,
|
||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
||||
) -> Vec<TrackData> {
|
||||
@@ -574,7 +574,7 @@ pub fn reflow_flex_layout(
|
||||
shape: &Shape,
|
||||
layout_data: &LayoutData,
|
||||
flex_data: &FlexData,
|
||||
shapes: &ShapesPool,
|
||||
shapes: ShapesPoolRef,
|
||||
bounds: &mut HashMap<Uuid, Bounds>,
|
||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
||||
) -> VecDeque<Modifier> {
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::shapes::{
|
||||
JustifyContent, JustifyItems, JustifySelf, Layout, LayoutData, LayoutItem, Modifier, Shape,
|
||||
StructureEntry, Type,
|
||||
};
|
||||
use crate::state::ShapesPool;
|
||||
use crate::state::ShapesPoolRef;
|
||||
use crate::uuid::Uuid;
|
||||
use indexmap::IndexSet;
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
@@ -45,7 +45,7 @@ pub fn calculate_tracks(
|
||||
grid_data: &GridData,
|
||||
layout_bounds: &Bounds,
|
||||
cells: &Vec<GridCell>,
|
||||
shapes: &ShapesPool,
|
||||
shapes: ShapesPoolRef,
|
||||
bounds: &HashMap<Uuid, Bounds>,
|
||||
) -> Vec<TrackData> {
|
||||
let layout_size = if is_column {
|
||||
@@ -122,7 +122,7 @@ fn set_auto_base_size(
|
||||
column: bool,
|
||||
tracks: &mut [TrackData],
|
||||
cells: &Vec<GridCell>,
|
||||
shapes: &ShapesPool,
|
||||
shapes: ShapesPoolRef,
|
||||
bounds: &HashMap<Uuid, Bounds>,
|
||||
) {
|
||||
for cell in cells {
|
||||
@@ -173,7 +173,7 @@ fn set_auto_multi_span(
|
||||
column: bool,
|
||||
tracks: &mut [TrackData],
|
||||
cells: &[GridCell],
|
||||
shapes: &ShapesPool,
|
||||
shapes: ShapesPoolRef,
|
||||
bounds: &HashMap<Uuid, Bounds>,
|
||||
) {
|
||||
// Remove groups with flex (will be set in flex_multi_span)
|
||||
@@ -248,7 +248,7 @@ fn set_flex_multi_span(
|
||||
layout_data: &LayoutData,
|
||||
tracks: &mut [TrackData],
|
||||
cells: &[GridCell],
|
||||
shapes: &ShapesPool,
|
||||
shapes: ShapesPoolRef,
|
||||
bounds: &HashMap<Uuid, Bounds>,
|
||||
) {
|
||||
// Remove groups without flex
|
||||
@@ -539,7 +539,7 @@ fn cell_bounds(
|
||||
pub fn create_cell_data<'a>(
|
||||
layout_bounds: &Bounds,
|
||||
children: &IndexSet<Uuid>,
|
||||
shapes: &'a ShapesPool,
|
||||
shapes: ShapesPoolRef<'a>,
|
||||
cells: &Vec<GridCell>,
|
||||
column_tracks: &[TrackData],
|
||||
row_tracks: &[TrackData],
|
||||
@@ -602,7 +602,7 @@ pub fn create_cell_data<'a>(
|
||||
|
||||
pub fn grid_cell_data<'a>(
|
||||
shape: &Shape,
|
||||
shapes: &'a ShapesPool,
|
||||
shapes: ShapesPoolRef<'a>,
|
||||
modifiers: &HashMap<Uuid, Matrix>,
|
||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
||||
allow_empty: bool,
|
||||
@@ -723,7 +723,7 @@ pub fn reflow_grid_layout(
|
||||
shape: &Shape,
|
||||
layout_data: &LayoutData,
|
||||
grid_data: &GridData,
|
||||
shapes: &ShapesPool,
|
||||
shapes: ShapesPoolRef,
|
||||
bounds: &mut HashMap<Uuid, Bounds>,
|
||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
||||
) -> VecDeque<Modifier> {
|
||||
|
||||
@@ -4,7 +4,7 @@ use super::{Corners, Path, Segment, Shape, StructureEntry, Type};
|
||||
use crate::math;
|
||||
|
||||
use crate::shapes::text_paths::TextPaths;
|
||||
use crate::state::ShapesPool;
|
||||
use crate::state::ShapesPoolRef;
|
||||
use crate::uuid::Uuid;
|
||||
use std::collections::HashMap;
|
||||
|
||||
@@ -13,7 +13,7 @@ const BEZIER_CIRCLE_C: f32 = 0.551_915_05;
|
||||
pub trait ToPath {
|
||||
fn to_path(
|
||||
&self,
|
||||
shapes: &ShapesPool,
|
||||
shapes: ShapesPoolRef,
|
||||
modifiers: &HashMap<Uuid, Matrix>,
|
||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
||||
) -> Path;
|
||||
@@ -182,15 +182,14 @@ fn transform_segments(segments: Vec<Segment>, shape: &Shape) -> Vec<Segment> {
|
||||
impl ToPath for Shape {
|
||||
fn to_path(
|
||||
&self,
|
||||
shapes: &ShapesPool,
|
||||
shapes: ShapesPoolRef,
|
||||
modifiers: &HashMap<Uuid, Matrix>,
|
||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
||||
) -> Path {
|
||||
let shape = self.transformed(modifiers.get(&self.id));
|
||||
match shape.shape_type {
|
||||
match &self.shape_type {
|
||||
Type::Frame(ref frame) => {
|
||||
let children = shape.modified_children_ids(structure.get(&shape.id), true);
|
||||
let mut result = Path::new(rect_segments(&shape, frame.corners));
|
||||
let children = self.modified_children_ids(structure.get(&self.id), true);
|
||||
let mut result = Path::new(rect_segments(&self, frame.corners));
|
||||
for id in children {
|
||||
let Some(shape) = shapes.get(&id) else {
|
||||
continue;
|
||||
@@ -201,7 +200,7 @@ impl ToPath for Shape {
|
||||
}
|
||||
|
||||
Type::Group(_) => {
|
||||
let children = shape.modified_children_ids(structure.get(&shape.id), true);
|
||||
let children = self.modified_children_ids(structure.get(&self.id), true);
|
||||
let mut result = Path::default();
|
||||
for id in children {
|
||||
let Some(shape) = shapes.get(&id) else {
|
||||
@@ -215,13 +214,13 @@ impl ToPath for Shape {
|
||||
Path::new(segments)
|
||||
}
|
||||
|
||||
Type::Bool(bool_data) => bool_data.path,
|
||||
Type::Bool(bool_data) => bool_data.path.clone(),
|
||||
|
||||
Type::Rect(ref rect) => Path::new(rect_segments(&shape, rect.corners)),
|
||||
Type::Rect(ref rect) => Path::new(rect_segments(&self, rect.corners)),
|
||||
|
||||
Type::Path(path_data) => path_data,
|
||||
Type::Path(path_data) => path_data.clone(),
|
||||
|
||||
Type::Circle => Path::new(circle_segments(&shape)),
|
||||
Type::Circle => Path::new(circle_segments(&self)),
|
||||
|
||||
Type::SVGRaw(_) => Path::default(),
|
||||
|
||||
@@ -232,7 +231,7 @@ impl ToPath for Shape {
|
||||
result = join_paths(result, Path::from_skia_path(path));
|
||||
}
|
||||
|
||||
Path::new(transform_segments(result.segments().clone(), &shape))
|
||||
Path::new(transform_segments(result.segments().clone(), &self))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::collections::HashMap;
|
||||
|
||||
mod shapes_pool;
|
||||
mod text_editor;
|
||||
pub use shapes_pool::*;
|
||||
pub use shapes_pool::{ShapesPool, ShapesPoolMutRef, ShapesPoolRef};
|
||||
pub use text_editor::*;
|
||||
|
||||
use crate::render::RenderState;
|
||||
@@ -19,17 +19,17 @@ use crate::shapes::modifiers::grid_layout::grid_cell_data;
|
||||
/// It is created by [init] and passed to the other exported functions.
|
||||
/// Note that rust-skia data structures are not thread safe, so a state
|
||||
/// must not be shared between different Web Workers.
|
||||
pub(crate) struct State {
|
||||
pub(crate) struct State<'a> {
|
||||
pub render_state: RenderState,
|
||||
pub text_editor_state: TextEditorState,
|
||||
pub current_id: Option<Uuid>,
|
||||
pub shapes: ShapesPool,
|
||||
pub shapes: ShapesPool<'a>,
|
||||
pub modifiers: HashMap<Uuid, skia::Matrix>,
|
||||
pub scale_content: HashMap<Uuid, f32>,
|
||||
pub structure: HashMap<Uuid, Vec<StructureEntry>>,
|
||||
}
|
||||
|
||||
impl State {
|
||||
impl<'a> State<'a> {
|
||||
pub fn new(width: i32, height: i32) -> Self {
|
||||
State {
|
||||
render_state: RenderState::new(width, height),
|
||||
@@ -114,8 +114,7 @@ impl State {
|
||||
// We don't really do a self.shapes.remove so that redo/undo keep working
|
||||
if let Some(shape) = self.shapes.get(&id) {
|
||||
let tiles::TileRect(rsx, rsy, rex, rey) =
|
||||
self.render_state
|
||||
.get_tiles_for_shape(shape, &self.shapes, &self.modifiers);
|
||||
self.render_state.get_tiles_for_shape(shape, &self.shapes);
|
||||
for x in rsx..=rex {
|
||||
for y in rsy..=rey {
|
||||
let tile = tiles::Tile(x, y);
|
||||
@@ -157,28 +156,9 @@ impl State {
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the selection rectangle for the current shape and processes its ancestors
|
||||
///
|
||||
/// When a shape's selection rectangle changes, all its ancestors need to have their
|
||||
/// extended rectangles recalculated because the shape's bounds may have changed.
|
||||
/// This ensures proper rendering of frames and groups containing the modified shape.
|
||||
// FIXME: PERFORMANCE
|
||||
pub fn set_selrect_for_current_shape(&mut self, left: f32, top: f32, right: f32, bottom: f32) {
|
||||
let shape = {
|
||||
let Some(shape) = self.current_shape_mut() else {
|
||||
panic!("Invalid current shape")
|
||||
};
|
||||
shape.set_selrect(left, top, right, bottom);
|
||||
shape.clone()
|
||||
};
|
||||
self.render_state
|
||||
.process_shape_ancestors(&shape, &mut self.shapes, &self.modifiers);
|
||||
}
|
||||
|
||||
pub fn update_tile_for_shape(&mut self, shape_id: Uuid) {
|
||||
if let Some(shape) = self.shapes.get(&shape_id) {
|
||||
self.render_state
|
||||
.update_tile_for(shape, &self.shapes, &self.modifiers);
|
||||
self.render_state.update_tile_for(shape, &self.shapes);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,7 +168,7 @@ impl State {
|
||||
};
|
||||
if !shape.id.is_nil() {
|
||||
self.render_state
|
||||
.update_tile_for(&shape.clone(), &self.shapes, &self.modifiers);
|
||||
.update_tile_for(&shape.clone(), &self.shapes);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,9 +182,17 @@ impl State {
|
||||
.rebuild_tiles(&self.shapes, &self.modifiers, &self.structure);
|
||||
}
|
||||
|
||||
pub fn rebuild_modifier_tiles(&mut self) {
|
||||
self.render_state
|
||||
.rebuild_modifier_tiles(&mut self.shapes, &self.modifiers);
|
||||
pub fn rebuild_modifier_tiles(&mut self, ids: Vec<Uuid>) {
|
||||
// SAFETY: We're extending the lifetime of the mutable borrow to 'a.
|
||||
// This is safe because:
|
||||
// 1. shapes has lifetime 'a in the struct
|
||||
// 2. The reference won't outlive the struct
|
||||
// 3. No other references to shapes exist during this call
|
||||
unsafe {
|
||||
let shapes_ptr = &mut self.shapes as *mut ShapesPool<'a>;
|
||||
self.render_state
|
||||
.rebuild_modifier_tiles(&mut *shapes_ptr, ids);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn font_collection(&self) -> &FontCollection {
|
||||
@@ -235,4 +223,8 @@ impl State {
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn set_modifiers(&mut self, modifiers: HashMap<Uuid, skia::Matrix>) {
|
||||
self.shapes.set_modifiers(modifiers);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,11 +5,16 @@ use crate::performance;
|
||||
use crate::shapes::Shape;
|
||||
use crate::uuid::Uuid;
|
||||
|
||||
use crate::shapes::StructureEntry;
|
||||
use crate::skia;
|
||||
|
||||
use std::cell::OnceCell;
|
||||
|
||||
const SHAPES_POOL_ALLOC_MULTIPLIER: f32 = 1.3;
|
||||
|
||||
/// A pool allocator for `Shape` objects that attempts to minimize memory reallocations.
|
||||
///
|
||||
/// `ShapesPool` pre-allocates a contiguous vector of `Shape` instances,
|
||||
/// `ShapesPoolImpl` pre-allocates a contiguous vector of `Shape` instances,
|
||||
/// which can be reused and indexed efficiently. This design helps avoid
|
||||
/// memory reallocation overhead by reserving enough space in advance.
|
||||
///
|
||||
@@ -18,18 +23,32 @@ const SHAPES_POOL_ALLOC_MULTIPLIER: f32 = 1.3;
|
||||
/// Shapes are stored in a `Vec<Shape>`, which keeps the `Shape` instances
|
||||
/// in a contiguous memory block.
|
||||
///
|
||||
pub struct ShapesPool {
|
||||
pub struct ShapesPoolImpl<'a> {
|
||||
shapes: Vec<Shape>,
|
||||
shapes_uuid_to_idx: HashMap<Uuid, usize>,
|
||||
counter: usize,
|
||||
|
||||
shapes_uuid_to_idx: HashMap<&'a Uuid, usize>,
|
||||
|
||||
modified_shape_cache: HashMap<&'a Uuid, OnceCell<Shape>>,
|
||||
modifiers: HashMap<&'a Uuid, skia::Matrix>,
|
||||
structure: HashMap<&'a Uuid, Vec<StructureEntry>>,
|
||||
}
|
||||
|
||||
impl ShapesPool {
|
||||
// Type aliases to avoid writing lifetimes everywhere
|
||||
pub type ShapesPool<'a> = ShapesPoolImpl<'a>;
|
||||
pub type ShapesPoolRef<'a> = &'a ShapesPoolImpl<'a>;
|
||||
pub type ShapesPoolMutRef<'a> = &'a mut ShapesPoolImpl<'a>;
|
||||
|
||||
impl<'a> ShapesPoolImpl<'a> {
|
||||
pub fn new() -> Self {
|
||||
ShapesPool {
|
||||
ShapesPoolImpl {
|
||||
shapes: vec![],
|
||||
counter: 0,
|
||||
shapes_uuid_to_idx: HashMap::default(),
|
||||
|
||||
modified_shape_cache: HashMap::default(),
|
||||
modifiers: HashMap::default(),
|
||||
structure: HashMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,22 +62,119 @@ impl ShapesPool {
|
||||
return;
|
||||
}
|
||||
|
||||
// Reserve exact capacity to avoid any future reallocations
|
||||
// This is critical because we store &'a Uuid references that would be invalidated
|
||||
let target_capacity = (capacity as f32 * SHAPES_POOL_ALLOC_MULTIPLIER) as usize;
|
||||
self.shapes
|
||||
.reserve_exact(target_capacity.saturating_sub(self.shapes.len()));
|
||||
|
||||
self.shapes
|
||||
.extend(iter::repeat_with(|| Shape::new(Uuid::nil())).take(additional as usize));
|
||||
performance::end_measure!("shapes_pool_initialize");
|
||||
}
|
||||
|
||||
pub fn add_shape(&mut self, id: Uuid) -> &mut Shape {
|
||||
if self.counter >= self.shapes.len() {
|
||||
let did_reallocate = if self.counter >= self.shapes.len() {
|
||||
// We need more space. Check if we'll need to reallocate the Vec.
|
||||
let current_capacity = self.shapes.capacity();
|
||||
let additional = (self.shapes.len() as f32 * SHAPES_POOL_ALLOC_MULTIPLIER) as usize;
|
||||
let needed_capacity = self.shapes.len() + additional;
|
||||
|
||||
let will_reallocate = needed_capacity > current_capacity;
|
||||
|
||||
if will_reallocate {
|
||||
// Reserve extra space to minimize future reallocations
|
||||
let extra_reserve = (needed_capacity as f32 * 0.5) as usize;
|
||||
self.shapes
|
||||
.reserve(needed_capacity + extra_reserve - current_capacity);
|
||||
}
|
||||
|
||||
self.shapes
|
||||
.extend(iter::repeat_with(|| Shape::new(Uuid::nil())).take(additional));
|
||||
}
|
||||
let new_shape = &mut self.shapes[self.counter];
|
||||
|
||||
will_reallocate
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
let idx = self.counter;
|
||||
let new_shape = &mut self.shapes[idx];
|
||||
new_shape.id = id;
|
||||
self.shapes_uuid_to_idx.insert(id, self.counter);
|
||||
|
||||
// Get a reference to the id field in the shape with lifetime 'a
|
||||
// SAFETY: This is safe because:
|
||||
// 1. We pre-allocate enough capacity to avoid Vec reallocation
|
||||
// 2. The shape and its id field won't move within the Vec
|
||||
// 3. The reference won't outlive the ShapesPoolImpl
|
||||
let id_ref: &'a Uuid = unsafe { &*(&self.shapes[idx].id as *const Uuid) };
|
||||
|
||||
self.shapes_uuid_to_idx.insert(id_ref, idx);
|
||||
self.counter += 1;
|
||||
new_shape
|
||||
|
||||
// If the Vec reallocated, we need to rebuild all references in the HashMaps
|
||||
// because the old references point to deallocated memory
|
||||
if did_reallocate {
|
||||
self.rebuild_references();
|
||||
}
|
||||
|
||||
&mut self.shapes[idx]
|
||||
}
|
||||
|
||||
/// Rebuilds all &'a Uuid references in the HashMaps after a Vec reallocation.
|
||||
/// This is necessary because Vec reallocation invalidates all existing references.
|
||||
fn rebuild_references(&mut self) {
|
||||
// Rebuild shapes_uuid_to_idx with fresh references
|
||||
let mut new_map = HashMap::with_capacity(self.shapes_uuid_to_idx.len());
|
||||
for (_, idx) in self.shapes_uuid_to_idx.drain() {
|
||||
let id_ref: &'a Uuid = unsafe { &*(&self.shapes[idx].id as *const Uuid) };
|
||||
new_map.insert(id_ref, idx);
|
||||
}
|
||||
self.shapes_uuid_to_idx = new_map;
|
||||
|
||||
// Rebuild modifiers with fresh references
|
||||
if !self.modifiers.is_empty() {
|
||||
let old_modifiers: Vec<(Uuid, skia::Matrix)> = self
|
||||
.modifiers
|
||||
.drain()
|
||||
.map(|(uuid_ref, matrix)| (*uuid_ref, matrix))
|
||||
.collect();
|
||||
|
||||
for (uuid, matrix) in old_modifiers {
|
||||
if let Some(uuid_ref) = self.get_uuid_ref(&uuid) {
|
||||
self.modifiers.insert(uuid_ref, matrix);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Rebuild structure with fresh references
|
||||
if !self.structure.is_empty() {
|
||||
let old_structure: Vec<(Uuid, Vec<StructureEntry>)> = self
|
||||
.structure
|
||||
.drain()
|
||||
.map(|(uuid_ref, entries)| (*uuid_ref, entries))
|
||||
.collect();
|
||||
|
||||
for (uuid, entries) in old_structure {
|
||||
if let Some(uuid_ref) = self.get_uuid_ref(&uuid) {
|
||||
self.structure.insert(uuid_ref, entries);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Rebuild modified_shape_cache with fresh references
|
||||
if !self.modified_shape_cache.is_empty() {
|
||||
let old_cache: Vec<(Uuid, OnceCell<Shape>)> = self
|
||||
.modified_shape_cache
|
||||
.drain()
|
||||
.map(|(uuid_ref, cell)| (*uuid_ref, cell))
|
||||
.collect();
|
||||
|
||||
for (uuid, cell) in old_cache {
|
||||
if let Some(uuid_ref) = self.get_uuid_ref(&uuid) {
|
||||
self.modified_shape_cache.insert(uuid_ref, cell);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
@@ -66,17 +182,48 @@ impl ShapesPool {
|
||||
}
|
||||
|
||||
pub fn has(&self, id: &Uuid) -> bool {
|
||||
self.shapes_uuid_to_idx.contains_key(id)
|
||||
self.shapes_uuid_to_idx.contains_key(&id)
|
||||
}
|
||||
|
||||
pub fn get_mut(&mut self, id: &Uuid) -> Option<&mut Shape> {
|
||||
let idx = *self.shapes_uuid_to_idx.get(id)?;
|
||||
let idx = *self.shapes_uuid_to_idx.get(&id)?;
|
||||
Some(&mut self.shapes[idx])
|
||||
}
|
||||
|
||||
pub fn get(&self, id: &Uuid) -> Option<&Shape> {
|
||||
let idx = *self.shapes_uuid_to_idx.get(id)?;
|
||||
Some(&self.shapes[idx])
|
||||
pub fn get(&self, id: &Uuid) -> Option<&'a Shape> {
|
||||
let idx = *self.shapes_uuid_to_idx.get(&id)?;
|
||||
|
||||
// SAFETY: We're extending the lifetimes to 'a.
|
||||
// This is safe because:
|
||||
// 1. All internal HashMaps and the shapes Vec have fields with lifetime 'a
|
||||
// 2. The shape at idx won't be moved or reallocated (pre-allocated Vec)
|
||||
// 3. The id is stored in shapes[idx].id which has lifetime 'a
|
||||
// 4. The references won't outlive the ShapesPoolImpl
|
||||
unsafe {
|
||||
let shape_ptr = &self.shapes[idx] as *const Shape;
|
||||
let modifiers_ptr = &self.modifiers as *const HashMap<&'a Uuid, skia::Matrix>;
|
||||
let structure_ptr = &self.structure as *const HashMap<&'a Uuid, Vec<StructureEntry>>;
|
||||
let cache_ptr = &self.modified_shape_cache as *const HashMap<&'a Uuid, OnceCell<Shape>>;
|
||||
|
||||
// Extend the lifetime of id to 'a - safe because it's the same Uuid stored in shapes[idx].id
|
||||
let id_ref: &'a Uuid = &*(id as *const Uuid);
|
||||
|
||||
if (*modifiers_ptr).contains_key(&id_ref) || (*structure_ptr).contains_key(&id_ref) {
|
||||
if let Some(cell) = (*cache_ptr).get(&id_ref) {
|
||||
Some(cell.get_or_init(|| {
|
||||
let shape = &*shape_ptr;
|
||||
shape.transformed(
|
||||
(*modifiers_ptr).get(&id_ref),
|
||||
(*structure_ptr).get(&id_ref),
|
||||
)
|
||||
}))
|
||||
} else {
|
||||
Some(&*shape_ptr)
|
||||
}
|
||||
} else {
|
||||
Some(&*shape_ptr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
@@ -87,4 +234,60 @@ impl ShapesPool {
|
||||
pub fn iter_mut(&mut self) -> std::slice::IterMut<'_, Shape> {
|
||||
self.shapes.iter_mut()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn clean_shape_cache(&mut self) {
|
||||
self.modified_shape_cache.clear()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn set_modifiers(&mut self, modifiers: HashMap<Uuid, skia::Matrix>) {
|
||||
// self.clean_shape_cache();
|
||||
|
||||
// Convert HashMap<Uuid, V> to HashMap<&'a Uuid, V> using references from shapes and
|
||||
// Initialize the cache cells because later we don't want to have the mutable pointer
|
||||
let mut modifiers_with_refs = HashMap::with_capacity(modifiers.len());
|
||||
for (uuid, matrix) in modifiers {
|
||||
if let Some(uuid_ref) = self.get_uuid_ref(&uuid) {
|
||||
self.modified_shape_cache.insert(uuid_ref, OnceCell::new());
|
||||
modifiers_with_refs.insert(uuid_ref, matrix);
|
||||
}
|
||||
}
|
||||
self.modifiers = modifiers_with_refs;
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn set_structure(&mut self, structure: HashMap<Uuid, Vec<StructureEntry>>) {
|
||||
// Convert HashMap<Uuid, V> to HashMap<&'a Uuid, V> using references from shapes and
|
||||
// Initialize the cache cells because later we don't want to have the mutable pointer
|
||||
let mut structure_with_refs = HashMap::with_capacity(structure.len());
|
||||
for (uuid, entries) in structure {
|
||||
if let Some(uuid_ref) = self.get_uuid_ref(&uuid) {
|
||||
self.modified_shape_cache.insert(uuid_ref, OnceCell::new());
|
||||
structure_with_refs.insert(uuid_ref, entries);
|
||||
}
|
||||
}
|
||||
self.structure = structure_with_refs;
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn clean_modifiers(&mut self) {
|
||||
self.clean_shape_cache();
|
||||
self.modifiers = HashMap::default();
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn clean_structure(&mut self) {
|
||||
self.clean_shape_cache();
|
||||
self.structure = HashMap::default();
|
||||
}
|
||||
|
||||
/// Get a reference to the Uuid stored in a shape, if it exists
|
||||
pub fn get_uuid_ref(&self, id: &Uuid) -> Option<&'a Uuid> {
|
||||
let idx = *self.shapes_uuid_to_idx.get(&id)?;
|
||||
// SAFETY: We're returning a reference with lifetime 'a to a Uuid stored
|
||||
// in the shapes Vec. This is safe because the Vec is stable (pre-allocated)
|
||||
// and won't be reallocated.
|
||||
unsafe { Some(&*(&self.shapes[idx].id as *const Uuid)) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use crate::uuid::Uuid;
|
||||
use crate::view::Viewbox;
|
||||
use indexmap::IndexSet;
|
||||
use skia_safe as skia;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
@@ -114,7 +113,7 @@ pub fn get_tile_rect(tile: Tile, scale: f32) -> skia::Rect {
|
||||
|
||||
// This structure is usseful to keep all the shape uuids by shape id.
|
||||
pub struct TileHashMap {
|
||||
grid: HashMap<Tile, IndexSet<Uuid>>,
|
||||
grid: HashMap<Tile, HashSet<Uuid>>,
|
||||
index: HashMap<Uuid, HashSet<Tile>>,
|
||||
}
|
||||
|
||||
@@ -126,13 +125,13 @@ impl TileHashMap {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_shapes_at(&mut self, tile: Tile) -> Option<&IndexSet<Uuid>> {
|
||||
pub fn get_shapes_at(&mut self, tile: Tile) -> Option<&HashSet<Uuid>> {
|
||||
self.grid.get(&tile)
|
||||
}
|
||||
|
||||
pub fn remove_shape_at(&mut self, tile: Tile, id: Uuid) {
|
||||
if let Some(shapes) = self.grid.get_mut(&tile) {
|
||||
shapes.shift_remove(&id);
|
||||
shapes.remove(&id);
|
||||
}
|
||||
|
||||
if let Some(tiles) = self.index.get_mut(&id) {
|
||||
|
||||
Reference in New Issue
Block a user