use std::hash::{DefaultHasher, Hash, Hasher};
use gpui::{App, ElementId, Entity, Path, Pixels, Point, Window};
#[derive(Default)]
pub struct PathCache {
key: Option<u64>,
path: Option<Path<Pixels>>,
}
impl PathCache {
pub fn get(
&mut self,
key: u64,
origin: Point<Pixels>,
build: impl FnOnce() -> Option<Path<Pixels>>,
) -> Option<Path<Pixels>> {
if self.key != Some(key) {
self.path = build();
self.key = Some(key);
}
self.path.as_ref().map(|path| translated(path, origin))
}
pub fn is_warm(&self) -> bool {
self.key.is_some()
}
}
fn translated(path: &Path<Pixels>, offset: Point<Pixels>) -> Path<Pixels> {
let mut path = path.clone();
path.bounds.origin = path.bounds.origin + offset;
for vertex in &mut path.vertices {
vertex.xy_position = vertex.xy_position + offset;
}
path
}
#[derive(Default)]
pub struct PathCaches {
slots: Vec<PathCache>,
}
impl PathCaches {
pub fn for_paint(key: impl Into<ElementId>, window: &mut Window, cx: &mut App) -> Entity<Self> {
window.use_keyed_state(key, cx, |_, _| Self::default())
}
pub fn slot(&mut self, index: usize) -> &mut PathCache {
if self.slots.len() <= index {
self.slots.resize_with(index + 1, PathCache::default);
}
&mut self.slots[index]
}
pub fn slot_pair(&mut self, index: usize) -> (&mut PathCache, &mut PathCache) {
let first = 2 * index;
if self.slots.len() <= first + 1 {
self.slots.resize_with(first + 2, PathCache::default);
}
let (head, tail) = self.slots.split_at_mut(first + 1);
(&mut head[first], &mut tail[0])
}
}
pub struct ShapeKey(DefaultHasher);
impl ShapeKey {
pub fn new(extra: impl Hash) -> Self {
let mut hasher = DefaultHasher::new();
extra.hash(&mut hasher);
Self(hasher)
}
pub fn point(&mut self, point: Point<Pixels>) -> &mut Self {
point.x.as_f32().to_bits().hash(&mut self.0);
point.y.as_f32().to_bits().hash(&mut self.0);
self
}
pub fn f32(&mut self, value: f32) -> &mut Self {
value.to_bits().hash(&mut self.0);
self
}
pub fn finish(&self) -> u64 {
self.0.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
use gpui::{PathBuilder, point, px};
fn diagonal() -> Option<Path<Pixels>> {
let mut builder = PathBuilder::stroke(px(2.));
builder.move_to(point(px(0.), px(0.)));
builder.line_to(point(px(10.), px(10.)));
builder.build().ok()
}
#[test]
fn builds_once_per_key_and_moves_to_each_origin() {
let mut cache = PathCache::default();
let mut builds = 0;
let first = cache
.get(1, point(px(100.), px(50.)), || {
builds += 1;
diagonal()
})
.unwrap();
let second = cache
.get(1, point(px(200.), px(50.)), || {
builds += 1;
diagonal()
})
.unwrap();
assert_eq!(builds, 1);
assert_eq!(first.vertices.len(), second.vertices.len());
for (a, b) in first.vertices.iter().zip(&second.vertices) {
assert_eq!(b.xy_position.x - a.xy_position.x, px(100.));
assert_eq!(b.xy_position.y, a.xy_position.y);
assert_eq!(a.st_position, b.st_position);
}
assert_eq!(second.bounds.origin.x - first.bounds.origin.x, px(100.));
assert_eq!(first.bounds.size, second.bounds.size);
cache.get(2, point(px(0.), px(0.)), || {
builds += 1;
diagonal()
});
assert_eq!(builds, 2);
}
#[test]
fn keys_follow_points_and_extras() {
let a = ShapeKey::new(("linear", 1.0f32.to_bits()))
.point(point(px(1.), px(2.)))
.finish();
let same = ShapeKey::new(("linear", 1.0f32.to_bits()))
.point(point(px(1.), px(2.)))
.finish();
let moved = ShapeKey::new(("linear", 1.0f32.to_bits()))
.point(point(px(1.), px(3.)))
.finish();
let thicker = ShapeKey::new(("linear", 2.0f32.to_bits()))
.point(point(px(1.), px(2.)))
.finish();
assert_eq!(a, same);
assert_ne!(a, moved);
assert_ne!(a, thicker);
}
}