mirror of
https://github.com/penpot/penpot.git
synced 2025-12-11 22:14:05 +01:00
✨ Changes to modifiers
This commit is contained in:
@@ -18,7 +18,6 @@
|
|||||||
[app.main.data.helpers :as dsh]
|
[app.main.data.helpers :as dsh]
|
||||||
[app.main.features :as features]
|
[app.main.features :as features]
|
||||||
[app.main.worker :as mw]
|
[app.main.worker :as mw]
|
||||||
[app.render-wasm.api :as wasm.api]
|
|
||||||
[app.render-wasm.shape :as wasm.shape]
|
[app.render-wasm.shape :as wasm.shape]
|
||||||
[beicon.v2.core :as rx]
|
[beicon.v2.core :as rx]
|
||||||
[potok.v2.core :as ptk]))
|
[potok.v2.core :as ptk]))
|
||||||
@@ -113,15 +112,7 @@
|
|||||||
(update-in state [:files file-id :data] apply-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])]
|
(let [objects (dm/get-in state [:files file-id :data :pages-index (:current-page-id state) :objects])]
|
||||||
(run!
|
(wasm.shape/process-shape-changes! objects @shape-changes))
|
||||||
(fn [[shape-id props]]
|
|
||||||
(wasm.api/use-shape shape-id)
|
|
||||||
(let [shape (get objects shape-id)]
|
|
||||||
(run! (partial wasm.shape/set-shape-wasm-attr! shape) props)))
|
|
||||||
@shape-changes))
|
|
||||||
|
|
||||||
(wasm.api/update-shape-tiles)
|
|
||||||
(wasm.api/request-render "set-wasm-attrs")
|
|
||||||
|
|
||||||
state)
|
state)
|
||||||
|
|
||||||
|
|||||||
@@ -226,22 +226,26 @@
|
|||||||
wasm-props
|
wasm-props
|
||||||
(concat clean-props 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)
|
(-> (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
|
;; Create a new objects only with the temporary modifications
|
||||||
(run! (fn [[id properties]]
|
objects-changed
|
||||||
(let [shape
|
(->> wasm-props
|
||||||
(->> properties
|
(reduce
|
||||||
(reduce
|
(fn [objects [id properties]]
|
||||||
(fn [shape {:keys [property value]}]
|
(let [shape
|
||||||
(assoc shape property value))
|
(->> properties
|
||||||
(get objects id)))]
|
(reduce
|
||||||
|
(fn [shape {:keys [property value]}]
|
||||||
;; With the new values to the shape change multi props
|
(assoc shape property value))
|
||||||
(wasm.shape/set-wasm-multi-attrs! shape (->> properties (map :property)))))
|
(get objects id)))]
|
||||||
wasm-props)))
|
(assoc objects id shape)))
|
||||||
|
objects))]
|
||||||
|
(wasm.shape/process-shape-changes! objects-changed shape-changes)))
|
||||||
|
|
||||||
(defn clear-local-transform []
|
(defn clear-local-transform []
|
||||||
(ptk/reify ::clear-local-transform
|
(ptk/reify ::clear-local-transform
|
||||||
|
|||||||
@@ -6,12 +6,14 @@
|
|||||||
|
|
||||||
(ns app.render-wasm.shape
|
(ns app.render-wasm.shape
|
||||||
(:require
|
(:require
|
||||||
|
[app.common.data :as d]
|
||||||
[app.common.data.macros :as dm]
|
[app.common.data.macros :as dm]
|
||||||
[app.common.transit :as t]
|
[app.common.transit :as t]
|
||||||
[app.common.types.shape :as shape]
|
[app.common.types.shape :as shape]
|
||||||
[app.common.types.shape.layout :as ctl]
|
[app.common.types.shape.layout :as ctl]
|
||||||
[app.main.refs :as refs]
|
[app.main.refs :as refs]
|
||||||
[app.render-wasm.api :as api]
|
[app.render-wasm.api :as api]
|
||||||
|
[beicon.v2.core :as rx]
|
||||||
[cljs.core :as c]
|
[cljs.core :as c]
|
||||||
[cuerdas.core :as str]))
|
[cuerdas.core :as str]))
|
||||||
|
|
||||||
@@ -118,8 +120,11 @@
|
|||||||
(-write writer (str "#penpot/shape " (:id delegate)))))
|
(-write writer (str "#penpot/shape " (:id delegate)))))
|
||||||
|
|
||||||
;; --- SHAPE IMPL
|
;; --- SHAPE IMPL
|
||||||
|
;; When an attribute is sent to WASM it could still be pending some side operations
|
||||||
(defn set-shape-wasm-attr!
|
;; 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]
|
[shape k]
|
||||||
(let [v (get shape k)
|
(let [v (get shape k)
|
||||||
id (get shape :id)]
|
id (get shape :id)]
|
||||||
@@ -224,14 +229,32 @@
|
|||||||
(ctl/flex-layout? shape)
|
(ctl/flex-layout? shape)
|
||||||
(api/set-flex-layout shape)))
|
(api/set-flex-layout shape)))
|
||||||
|
|
||||||
|
;; Property not in WASM
|
||||||
nil)))
|
nil)))
|
||||||
|
|
||||||
(defn set-wasm-multi-attrs!
|
(defn process-shape!
|
||||||
[shape properties]
|
[shape properties]
|
||||||
(let [shape-id (dm/get-prop shape :id)]
|
(let [shape-id (dm/get-prop shape :id)]
|
||||||
(when (shape-in-current-page? shape-id)
|
(if (shape-in-current-page? shape-id)
|
||||||
(api/use-shape shape-id)
|
(do
|
||||||
(run! (partial set-shape-wasm-attr! shape) properties))))
|
(api/use-shape shape-id)
|
||||||
|
(->> 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
|
(defn- impl-assoc
|
||||||
[self k v]
|
[self k v]
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ use mem::SerializableResult;
|
|||||||
use shapes::{StructureEntry, StructureEntryType, TransformEntry};
|
use shapes::{StructureEntry, StructureEntryType, TransformEntry};
|
||||||
use skia_safe as skia;
|
use skia_safe as skia;
|
||||||
use state::State;
|
use state::State;
|
||||||
|
use std::collections::HashMap;
|
||||||
use utils::uuid_from_u32_quartet;
|
use utils::uuid_from_u32_quartet;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
@@ -537,6 +538,7 @@ pub extern "C" fn set_structure_modifiers() {
|
|||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
with_state_mut!(state, {
|
with_state_mut!(state, {
|
||||||
|
let mut structure = HashMap::new();
|
||||||
for entry in entries {
|
for entry in entries {
|
||||||
match entry.entry_type {
|
match entry.entry_type {
|
||||||
StructureEntryType::ScaleContent => {
|
StructureEntryType::ScaleContent => {
|
||||||
@@ -548,15 +550,17 @@ pub extern "C" fn set_structure_modifiers() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
state.structure.entry(entry.parent).or_insert_with(Vec::new);
|
structure.entry(entry.parent).or_insert_with(Vec::new);
|
||||||
state
|
structure
|
||||||
.structure
|
|
||||||
.get_mut(&entry.parent)
|
.get_mut(&entry.parent)
|
||||||
.expect("Parent not found for entry")
|
.expect("Parent not found for entry")
|
||||||
.push(entry);
|
.push(entry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if !structure.is_empty() {
|
||||||
|
state.shapes.set_structure(structure);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
mem::free_bytes();
|
mem::free_bytes();
|
||||||
@@ -567,7 +571,8 @@ pub extern "C" fn clean_modifiers() {
|
|||||||
with_state_mut!(state, {
|
with_state_mut!(state, {
|
||||||
state.structure.clear();
|
state.structure.clear();
|
||||||
state.scale_content.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()))
|
.map(|data| TransformEntry::from_bytes(data.try_into().unwrap()))
|
||||||
.collect();
|
.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, {
|
with_state_mut!(state, {
|
||||||
for entry in entries {
|
state.set_modifiers(modifiers);
|
||||||
state.modifiers.insert(entry.id, entry.transform);
|
state.rebuild_modifier_tiles(ids);
|
||||||
}
|
|
||||||
state.rebuild_modifier_tiles();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1134,13 +1134,8 @@ impl RenderState {
|
|||||||
self.get_rect_bounds(rect)
|
self.get_rect_bounds(rect)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_shape_extrect_bounds(
|
pub fn get_shape_extrect_bounds(&mut self, shape: &Shape, tree: &ShapesPool) -> Rect {
|
||||||
&mut self,
|
let rect = shape.extrect(tree);
|
||||||
shape: &Shape,
|
|
||||||
tree: &ShapesPool,
|
|
||||||
modifiers: &HashMap<Uuid, Matrix>,
|
|
||||||
) -> Rect {
|
|
||||||
let rect = shape.extrect(tree, modifiers);
|
|
||||||
self.get_rect_bounds(rect)
|
self.get_rect_bounds(rect)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1278,7 +1273,7 @@ impl RenderState {
|
|||||||
// If the shape is not in the tile set, then we update
|
// If the shape is not in the tile set, then we update
|
||||||
// it.
|
// it.
|
||||||
if self.tiles.get_tiles_of(node_id).is_none() {
|
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 {
|
if visited_children {
|
||||||
@@ -1297,18 +1292,14 @@ impl RenderState {
|
|||||||
let transformed_element: Cow<Shape> = Cow::Borrowed(element);
|
let transformed_element: Cow<Shape> = Cow::Borrowed(element);
|
||||||
|
|
||||||
let is_visible = transformed_element
|
let is_visible = transformed_element
|
||||||
.extrect(tree, modifiers)
|
.extrect(tree)
|
||||||
.intersects(self.render_area)
|
.intersects(self.render_area)
|
||||||
&& !transformed_element.hidden
|
&& !transformed_element.hidden
|
||||||
&& !transformed_element.visually_insignificant(
|
&& !transformed_element.visually_insignificant(self.get_scale(), tree);
|
||||||
self.get_scale(),
|
|
||||||
tree,
|
|
||||||
modifiers,
|
|
||||||
);
|
|
||||||
|
|
||||||
if self.options.is_debug_visible() {
|
if self.options.is_debug_visible() {
|
||||||
let shape_extrect_bounds =
|
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));
|
debug::render_debug_shape(self, None, Some(shape_extrect_bounds));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1657,23 +1648,14 @@ impl RenderState {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_tiles_for_shape(
|
pub fn get_tiles_for_shape(&mut self, shape: &Shape, tree: &ShapesPool) -> TileRect {
|
||||||
&mut self,
|
let extrect = shape.extrect(tree);
|
||||||
shape: &Shape,
|
|
||||||
tree: &ShapesPool,
|
|
||||||
modifiers: &HashMap<Uuid, Matrix>,
|
|
||||||
) -> TileRect {
|
|
||||||
let tile_size = tiles::get_tile_size(self.get_scale());
|
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(
|
pub fn update_tile_for(&mut self, shape: &Shape, tree: &ShapesPool) {
|
||||||
&mut self,
|
let TileRect(rsx, rsy, rex, rey) = self.get_tiles_for_shape(shape, tree);
|
||||||
shape: &Shape,
|
|
||||||
tree: &ShapesPool,
|
|
||||||
modifiers: &HashMap<Uuid, Matrix>,
|
|
||||||
) {
|
|
||||||
let TileRect(rsx, rsy, rex, rey) = self.get_tiles_for_shape(shape, tree, modifiers);
|
|
||||||
let old_tiles: HashSet<tiles::Tile> = self
|
let old_tiles: HashSet<tiles::Tile> = self
|
||||||
.tiles
|
.tiles
|
||||||
.get_tiles_of(shape.id)
|
.get_tiles_of(shape.id)
|
||||||
@@ -1717,7 +1699,7 @@ impl RenderState {
|
|||||||
if let Some(modifier) = modifiers.get(&shape_id) {
|
if let Some(modifier) = modifiers.get(&shape_id) {
|
||||||
shape.to_mut().apply_transform(modifier);
|
shape.to_mut().apply_transform(modifier);
|
||||||
}
|
}
|
||||||
self.update_tile_for(&shape, tree, modifiers);
|
self.update_tile_for(&shape, tree);
|
||||||
} else {
|
} else {
|
||||||
// We only need to rebuild tiles from the first level.
|
// We only need to rebuild tiles from the first level.
|
||||||
let children = shape.modified_children_ids(structure.get(&shape.id), false);
|
let children = shape.modified_children_ids(structure.get(&shape.id), false);
|
||||||
@@ -1747,7 +1729,7 @@ impl RenderState {
|
|||||||
if let Some(modifier) = modifiers.get(&shape_id) {
|
if let Some(modifier) = modifiers.get(&shape_id) {
|
||||||
shape.to_mut().apply_transform(modifier);
|
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);
|
let children = shape.modified_children_ids(structure.get(&shape.id), false);
|
||||||
@@ -1771,15 +1753,11 @@ impl RenderState {
|
|||||||
&mut self,
|
&mut self,
|
||||||
shape_ids: &IndexSet<Uuid>,
|
shape_ids: &IndexSet<Uuid>,
|
||||||
tree: &mut ShapesPool,
|
tree: &mut ShapesPool,
|
||||||
modifiers: &HashMap<Uuid, Matrix>,
|
|
||||||
) {
|
) {
|
||||||
for shape_id in shape_ids {
|
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 let Some(shape) = tree.get(shape_id) {
|
||||||
if !shape.id.is_nil() {
|
if !shape.id.is_nil() {
|
||||||
self.update_tile_for(shape, tree, modifiers);
|
self.update_tile_for(shape, tree);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1791,14 +1769,9 @@ impl RenderState {
|
|||||||
/// Additionally, it processes all ancestors of modified shapes to ensure their
|
/// Additionally, it processes all ancestors of modified shapes to ensure their
|
||||||
/// extended rectangles are properly recalculated and their tiles are updated.
|
/// extended rectangles are properly recalculated and their tiles are updated.
|
||||||
/// This is crucial for frames and groups that contain transformed children.
|
/// This is crucial for frames and groups that contain transformed children.
|
||||||
pub fn rebuild_modifier_tiles(
|
pub fn rebuild_modifier_tiles(&mut self, tree: &mut ShapesPool, ids: Vec<Uuid>) {
|
||||||
&mut self,
|
|
||||||
tree: &mut ShapesPool,
|
|
||||||
modifiers: &HashMap<Uuid, Matrix>,
|
|
||||||
) {
|
|
||||||
let ids: Vec<_> = modifiers.keys().collect();
|
|
||||||
let ancestors = all_with_ancestors(&ids, tree, false);
|
let ancestors = all_with_ancestors(&ids, tree, false);
|
||||||
self.invalidate_and_update_tiles(&ancestors, tree, modifiers);
|
self.invalidate_and_update_tiles(&ancestors, tree);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_scale(&self) -> f32 {
|
pub fn get_scale(&self) -> f32 {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use skia_safe::{self as skia};
|
|||||||
use crate::uuid::Uuid;
|
use crate::uuid::Uuid;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::cell::OnceCell;
|
use std::cell::OnceCell;
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::HashSet;
|
||||||
use std::iter::once;
|
use std::iter::once;
|
||||||
|
|
||||||
mod blend;
|
mod blend;
|
||||||
@@ -196,11 +196,11 @@ pub struct Shape {
|
|||||||
// # Returns
|
// # Returns
|
||||||
// A set of ancestor UUIDs in traversal order (closest ancestor first)
|
// A set of ancestor UUIDs in traversal order (closest ancestor first)
|
||||||
pub fn all_with_ancestors(
|
pub fn all_with_ancestors(
|
||||||
shapes: &[&Uuid],
|
shapes: &[Uuid],
|
||||||
shapes_pool: &ShapesPool,
|
shapes_pool: &ShapesPool,
|
||||||
include_hidden: bool,
|
include_hidden: bool,
|
||||||
) -> IndexSet<Uuid> {
|
) -> IndexSet<Uuid> {
|
||||||
let mut pending = Vec::from(shapes);
|
let mut pending = Vec::from_iter(shapes.iter());
|
||||||
let mut result = IndexSet::new();
|
let mut result = IndexSet::new();
|
||||||
|
|
||||||
while !pending.is_empty() {
|
while !pending.is_empty() {
|
||||||
@@ -677,13 +677,8 @@ impl Shape {
|
|||||||
self.selrect.width()
|
self.selrect.width()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn visually_insignificant(
|
pub fn visually_insignificant(&self, scale: f32, shapes_pool: &ShapesPool) -> bool {
|
||||||
&self,
|
let extrect = self.extrect(shapes_pool);
|
||||||
scale: f32,
|
|
||||||
shapes_pool: &ShapesPool,
|
|
||||||
modifiers: &HashMap<Uuid, Matrix>,
|
|
||||||
) -> bool {
|
|
||||||
let extrect = self.extrect(shapes_pool, modifiers);
|
|
||||||
extrect.width() * scale < MIN_VISIBLE_SIZE && extrect.height() * scale < MIN_VISIBLE_SIZE
|
extrect.width() * scale < MIN_VISIBLE_SIZE && extrect.height() * scale < MIN_VISIBLE_SIZE
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -726,14 +721,10 @@ impl Shape {
|
|||||||
self.selrect
|
self.selrect
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn extrect(
|
pub fn extrect(&self, shapes_pool: &ShapesPool) -> math::Rect {
|
||||||
&self,
|
|
||||||
shapes_pool: &ShapesPool,
|
|
||||||
modifiers: &HashMap<Uuid, Matrix>,
|
|
||||||
) -> math::Rect {
|
|
||||||
*self
|
*self
|
||||||
.extrect
|
.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 {
|
pub fn get_text_content(&self) -> &TextContent {
|
||||||
@@ -843,12 +834,7 @@ impl Shape {
|
|||||||
rect
|
rect
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apply_children_bounds(
|
fn apply_children_bounds(&self, mut rect: math::Rect, shapes_pool: &ShapesPool) -> math::Rect {
|
||||||
&self,
|
|
||||||
mut rect: math::Rect,
|
|
||||||
shapes_pool: &ShapesPool,
|
|
||||||
modifiers: &HashMap<Uuid, Matrix>,
|
|
||||||
) -> math::Rect {
|
|
||||||
let include_children = match self.shape_type {
|
let include_children = match self.shape_type {
|
||||||
Type::Group(_) => true,
|
Type::Group(_) => true,
|
||||||
Type::Frame(_) => !self.clip_content,
|
Type::Frame(_) => !self.clip_content,
|
||||||
@@ -858,15 +844,7 @@ impl Shape {
|
|||||||
if include_children {
|
if include_children {
|
||||||
for child_id in self.children_ids(false) {
|
for child_id in self.children_ids(false) {
|
||||||
if let Some(child_shape) = shapes_pool.get(&child_id) {
|
if let Some(child_shape) = shapes_pool.get(&child_id) {
|
||||||
// Create a copy of the child shape to apply any transformations
|
rect.join(child_shape.extrect(shapes_pool));
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -874,12 +852,8 @@ impl Shape {
|
|||||||
rect
|
rect
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn calculate_extrect(
|
pub fn calculate_extrect(&self, shapes_pool: &ShapesPool) -> math::Rect {
|
||||||
&self,
|
let shape = self;
|
||||||
shapes_pool: &ShapesPool,
|
|
||||||
modifiers: &HashMap<Uuid, Matrix>,
|
|
||||||
) -> math::Rect {
|
|
||||||
let shape = self.transformed(modifiers.get(&self.id));
|
|
||||||
let max_stroke = Stroke::max_bounds_width(shape.strokes.iter(), shape.is_open());
|
let max_stroke = Stroke::max_bounds_width(shape.strokes.iter(), shape.is_open());
|
||||||
|
|
||||||
let mut rect = match &shape.shape_type {
|
let mut rect = match &shape.shape_type {
|
||||||
@@ -902,7 +876,7 @@ impl Shape {
|
|||||||
rect = self.apply_stroke_bounds(rect, max_stroke);
|
rect = self.apply_stroke_bounds(rect, max_stroke);
|
||||||
rect = self.apply_shadow_bounds(rect);
|
rect = self.apply_shadow_bounds(rect);
|
||||||
rect = self.apply_blur_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
|
rect
|
||||||
}
|
}
|
||||||
@@ -1177,11 +1151,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);
|
let mut shape: Cow<Shape> = Cow::Borrowed(self);
|
||||||
if let Some(transform) = transform {
|
if let Some(transform) = transform {
|
||||||
shape.to_mut().apply_transform(transform);
|
shape.to_mut().apply_transform(transform);
|
||||||
}
|
}
|
||||||
|
if let Some(structure) = structure {
|
||||||
|
shape.to_mut().apply_structure(structure);
|
||||||
|
}
|
||||||
shape.into_owned()
|
shape.into_owned()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -186,11 +186,10 @@ impl ToPath for Shape {
|
|||||||
modifiers: &HashMap<Uuid, Matrix>,
|
modifiers: &HashMap<Uuid, Matrix>,
|
||||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
||||||
) -> Path {
|
) -> Path {
|
||||||
let shape = self.transformed(modifiers.get(&self.id));
|
match &self.shape_type {
|
||||||
match shape.shape_type {
|
|
||||||
Type::Frame(ref frame) => {
|
Type::Frame(ref frame) => {
|
||||||
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::new(rect_segments(&shape, frame.corners));
|
let mut result = Path::new(rect_segments(&self, frame.corners));
|
||||||
for id in children {
|
for id in children {
|
||||||
let Some(shape) = shapes.get(&id) else {
|
let Some(shape) = shapes.get(&id) else {
|
||||||
continue;
|
continue;
|
||||||
@@ -201,7 +200,7 @@ impl ToPath for Shape {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Type::Group(_) => {
|
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();
|
let mut result = Path::default();
|
||||||
for id in children {
|
for id in children {
|
||||||
let Some(shape) = shapes.get(&id) else {
|
let Some(shape) = shapes.get(&id) else {
|
||||||
@@ -215,13 +214,13 @@ impl ToPath for Shape {
|
|||||||
Path::new(segments)
|
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(),
|
Type::SVGRaw(_) => Path::default(),
|
||||||
|
|
||||||
@@ -232,7 +231,7 @@ impl ToPath for Shape {
|
|||||||
result = join_paths(result, Path::from_skia_path(path));
|
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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -114,8 +114,7 @@ impl State {
|
|||||||
// We don't really do a self.shapes.remove so that redo/undo keep working
|
// We don't really do a self.shapes.remove so that redo/undo keep working
|
||||||
if let Some(shape) = self.shapes.get(&id) {
|
if let Some(shape) = self.shapes.get(&id) {
|
||||||
let tiles::TileRect(rsx, rsy, rex, rey) =
|
let tiles::TileRect(rsx, rsy, rex, rey) =
|
||||||
self.render_state
|
self.render_state.get_tiles_for_shape(shape, &self.shapes);
|
||||||
.get_tiles_for_shape(shape, &self.shapes, &self.modifiers);
|
|
||||||
for x in rsx..=rex {
|
for x in rsx..=rex {
|
||||||
for y in rsy..=rey {
|
for y in rsy..=rey {
|
||||||
let tile = tiles::Tile(x, y);
|
let tile = tiles::Tile(x, y);
|
||||||
@@ -159,8 +158,7 @@ impl State {
|
|||||||
|
|
||||||
pub fn update_tile_for_shape(&mut self, shape_id: Uuid) {
|
pub fn update_tile_for_shape(&mut self, shape_id: Uuid) {
|
||||||
if let Some(shape) = self.shapes.get(&shape_id) {
|
if let Some(shape) = self.shapes.get(&shape_id) {
|
||||||
self.render_state
|
self.render_state.update_tile_for(shape, &self.shapes);
|
||||||
.update_tile_for(shape, &self.shapes, &self.modifiers);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,7 +168,7 @@ impl State {
|
|||||||
};
|
};
|
||||||
if !shape.id.is_nil() {
|
if !shape.id.is_nil() {
|
||||||
self.render_state
|
self.render_state
|
||||||
.update_tile_for(&shape.clone(), &self.shapes, &self.modifiers);
|
.update_tile_for(&shape.clone(), &self.shapes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,9 +182,9 @@ impl State {
|
|||||||
.rebuild_tiles(&self.shapes, &self.modifiers, &self.structure);
|
.rebuild_tiles(&self.shapes, &self.modifiers, &self.structure);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rebuild_modifier_tiles(&mut self) {
|
pub fn rebuild_modifier_tiles(&mut self, ids: Vec<Uuid>) {
|
||||||
self.render_state
|
self.render_state
|
||||||
.rebuild_modifier_tiles(&mut self.shapes, &self.modifiers);
|
.rebuild_modifier_tiles(&mut self.shapes, ids);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn font_collection(&self) -> &FontCollection {
|
pub fn font_collection(&self) -> &FontCollection {
|
||||||
@@ -217,4 +215,8 @@ impl State {
|
|||||||
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_modifiers(&mut self, modifiers: HashMap<Uuid, skia::Matrix>) {
|
||||||
|
self.shapes.set_modifiers(modifiers);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,11 @@ use crate::performance;
|
|||||||
use crate::shapes::Shape;
|
use crate::shapes::Shape;
|
||||||
use crate::uuid::Uuid;
|
use crate::uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::shapes::StructureEntry;
|
||||||
|
use crate::skia;
|
||||||
|
|
||||||
|
use std::cell::OnceCell;
|
||||||
|
|
||||||
const SHAPES_POOL_ALLOC_MULTIPLIER: f32 = 1.3;
|
const SHAPES_POOL_ALLOC_MULTIPLIER: f32 = 1.3;
|
||||||
|
|
||||||
/// A pool allocator for `Shape` objects that attempts to minimize memory reallocations.
|
/// A pool allocator for `Shape` objects that attempts to minimize memory reallocations.
|
||||||
@@ -20,8 +25,13 @@ const SHAPES_POOL_ALLOC_MULTIPLIER: f32 = 1.3;
|
|||||||
///
|
///
|
||||||
pub struct ShapesPool {
|
pub struct ShapesPool {
|
||||||
shapes: Vec<Shape>,
|
shapes: Vec<Shape>,
|
||||||
shapes_uuid_to_idx: HashMap<Uuid, usize>,
|
|
||||||
counter: usize,
|
counter: usize,
|
||||||
|
|
||||||
|
shapes_uuid_to_idx: HashMap<Uuid, usize>,
|
||||||
|
|
||||||
|
modified_shape_cache: HashMap<Uuid, OnceCell<Shape>>,
|
||||||
|
modifiers: HashMap<Uuid, skia::Matrix>,
|
||||||
|
structure: HashMap<Uuid, Vec<StructureEntry>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ShapesPool {
|
impl ShapesPool {
|
||||||
@@ -30,6 +40,10 @@ impl ShapesPool {
|
|||||||
shapes: vec![],
|
shapes: vec![],
|
||||||
counter: 0,
|
counter: 0,
|
||||||
shapes_uuid_to_idx: HashMap::default(),
|
shapes_uuid_to_idx: HashMap::default(),
|
||||||
|
|
||||||
|
modified_shape_cache: HashMap::default(),
|
||||||
|
modifiers: HashMap::default(),
|
||||||
|
structure: HashMap::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,7 +90,22 @@ impl ShapesPool {
|
|||||||
|
|
||||||
pub fn get(&self, id: &Uuid) -> Option<&Shape> {
|
pub fn get(&self, id: &Uuid) -> Option<&Shape> {
|
||||||
let idx = *self.shapes_uuid_to_idx.get(id)?;
|
let idx = *self.shapes_uuid_to_idx.get(id)?;
|
||||||
Some(&self.shapes[idx])
|
if self.modifiers.contains_key(id) || self.structure.contains_key(id) {
|
||||||
|
if let Some(cell) = self.modified_shape_cache.get(id) {
|
||||||
|
Some(cell.get_or_init(|| {
|
||||||
|
let shape = &self.shapes[idx];
|
||||||
|
shape.transformed(
|
||||||
|
self.modifiers.get(id),
|
||||||
|
self.structure.get(id)
|
||||||
|
)
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
let shape = &self.shapes[idx];
|
||||||
|
Some(shape)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Some(&self.shapes[idx])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
@@ -87,4 +116,44 @@ impl ShapesPool {
|
|||||||
pub fn iter_mut(&mut self) -> std::slice::IterMut<'_, Shape> {
|
pub fn iter_mut(&mut self) -> std::slice::IterMut<'_, Shape> {
|
||||||
self.shapes.iter_mut()
|
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();
|
||||||
|
|
||||||
|
// Initialize the cache cells because
|
||||||
|
// later we don't want to have the mutable pointer
|
||||||
|
for key in modifiers.keys() {
|
||||||
|
self.modified_shape_cache.insert(*key, OnceCell::new());
|
||||||
|
}
|
||||||
|
self.modifiers = modifiers;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn set_structure(&mut self, structure: HashMap<Uuid, Vec<StructureEntry>>) {
|
||||||
|
// self.clean_shape_cache();
|
||||||
|
// Initialize the cache cells because
|
||||||
|
// later we don't want to have the mutable pointer
|
||||||
|
for key in structure.keys() {
|
||||||
|
self.modified_shape_cache.insert(*key, OnceCell::new());
|
||||||
|
}
|
||||||
|
self.structure = structure;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user