gpui_component/plot/
path_cache.rs1use std::hash::{DefaultHasher, Hash, Hasher};
2
3use gpui::{App, ElementId, Entity, Path, Pixels, Point, Window};
4
5#[derive(Default)]
16pub struct PathCache {
17 key: Option<u64>,
18 path: Option<Path<Pixels>>,
20}
21
22impl PathCache {
23 pub fn get(
26 &mut self,
27 key: u64,
28 origin: Point<Pixels>,
29 build: impl FnOnce() -> Option<Path<Pixels>>,
30 ) -> Option<Path<Pixels>> {
31 if self.key != Some(key) {
32 self.path = build();
33 self.key = Some(key);
34 }
35 self.path.as_ref().map(|path| translated(path, origin))
36 }
37
38 pub fn is_warm(&self) -> bool {
40 self.key.is_some()
41 }
42}
43
44fn translated(path: &Path<Pixels>, offset: Point<Pixels>) -> Path<Pixels> {
47 let mut path = path.clone();
48 path.bounds.origin = path.bounds.origin + offset;
49 for vertex in &mut path.vertices {
50 vertex.xy_position = vertex.xy_position + offset;
51 }
52 path
53}
54
55#[derive(Default)]
74pub struct PathCaches {
75 slots: Vec<PathCache>,
76}
77
78impl PathCaches {
79 pub fn for_paint(key: impl Into<ElementId>, window: &mut Window, cx: &mut App) -> Entity<Self> {
82 window.use_keyed_state(key, cx, |_, _| Self::default())
83 }
84
85 pub fn slot(&mut self, index: usize) -> &mut PathCache {
88 if self.slots.len() <= index {
89 self.slots.resize_with(index + 1, PathCache::default);
90 }
91 &mut self.slots[index]
92 }
93
94 pub fn slot_pair(&mut self, index: usize) -> (&mut PathCache, &mut PathCache) {
98 let first = 2 * index;
99 if self.slots.len() <= first + 1 {
100 self.slots.resize_with(first + 2, PathCache::default);
101 }
102 let (head, tail) = self.slots.split_at_mut(first + 1);
103 (&mut head[first], &mut tail[0])
104 }
105}
106
107pub struct ShapeKey(DefaultHasher);
110
111impl ShapeKey {
112 pub fn new(extra: impl Hash) -> Self {
113 let mut hasher = DefaultHasher::new();
114 extra.hash(&mut hasher);
115 Self(hasher)
116 }
117
118 pub fn point(&mut self, point: Point<Pixels>) -> &mut Self {
119 point.x.as_f32().to_bits().hash(&mut self.0);
120 point.y.as_f32().to_bits().hash(&mut self.0);
121 self
122 }
123
124 pub fn f32(&mut self, value: f32) -> &mut Self {
125 value.to_bits().hash(&mut self.0);
126 self
127 }
128
129 pub fn finish(&self) -> u64 {
130 self.0.finish()
131 }
132}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137 use gpui::{PathBuilder, point, px};
138
139 fn diagonal() -> Option<Path<Pixels>> {
140 let mut builder = PathBuilder::stroke(px(2.));
141 builder.move_to(point(px(0.), px(0.)));
142 builder.line_to(point(px(10.), px(10.)));
143 builder.build().ok()
144 }
145
146 #[test]
147 fn builds_once_per_key_and_moves_to_each_origin() {
148 let mut cache = PathCache::default();
149 let mut builds = 0;
150 let first = cache
151 .get(1, point(px(100.), px(50.)), || {
152 builds += 1;
153 diagonal()
154 })
155 .unwrap();
156 let second = cache
157 .get(1, point(px(200.), px(50.)), || {
158 builds += 1;
159 diagonal()
160 })
161 .unwrap();
162 assert_eq!(builds, 1);
163 assert_eq!(first.vertices.len(), second.vertices.len());
164 for (a, b) in first.vertices.iter().zip(&second.vertices) {
165 assert_eq!(b.xy_position.x - a.xy_position.x, px(100.));
166 assert_eq!(b.xy_position.y, a.xy_position.y);
167 assert_eq!(a.st_position, b.st_position);
168 }
169 assert_eq!(second.bounds.origin.x - first.bounds.origin.x, px(100.));
170 assert_eq!(first.bounds.size, second.bounds.size);
171
172 cache.get(2, point(px(0.), px(0.)), || {
173 builds += 1;
174 diagonal()
175 });
176 assert_eq!(builds, 2);
177 }
178
179 #[test]
180 fn keys_follow_points_and_extras() {
181 let a = ShapeKey::new(("linear", 1.0f32.to_bits()))
182 .point(point(px(1.), px(2.)))
183 .finish();
184 let same = ShapeKey::new(("linear", 1.0f32.to_bits()))
185 .point(point(px(1.), px(2.)))
186 .finish();
187 let moved = ShapeKey::new(("linear", 1.0f32.to_bits()))
188 .point(point(px(1.), px(3.)))
189 .finish();
190 let thicker = ShapeKey::new(("linear", 2.0f32.to_bits()))
191 .point(point(px(1.), px(2.)))
192 .finish();
193 assert_eq!(a, same);
194 assert_ne!(a, moved);
195 assert_ne!(a, thicker);
196 }
197}