diff --git a/.vscode/settings.json b/.vscode/settings.json index e6eedf9..3666538 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,6 @@ { - "rust-analyzer.linkedProjects": ["./examples/demo/Cargo.toml"] + "rust-analyzer.linkedProjects": [ + "./examples/demo/Cargo.toml", + "./hframe-core/Cargo.toml" + ] } diff --git a/hframe-core/Cargo.lock b/hframe-core/Cargo.lock new file mode 100644 index 0000000..14240c0 --- /dev/null +++ b/hframe-core/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "hframe-core" +version = "0.1.0" diff --git a/hframe-core/Cargo.toml b/hframe-core/Cargo.toml new file mode 100644 index 0000000..c6b3cfd --- /dev/null +++ b/hframe-core/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "hframe-core" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/hframe-core/src/composed_area.rs b/hframe-core/src/composed_area.rs new file mode 100644 index 0000000..62fa94d --- /dev/null +++ b/hframe-core/src/composed_area.rs @@ -0,0 +1,20 @@ +use crate::{geo::*, id::*}; + +#[derive(Debug, Clone)] +pub(crate) struct ComposedHtml { + pub(crate) content: String, +} + +#[derive(Debug, Clone)] +pub(crate) enum ComposedAreaKind { + Canvas, + Html(ComposedHtml), +} + +#[derive(Debug, Clone)] +pub(crate) struct ComposedArea { + pub(crate) id: Id, + pub(crate) size: Size, + pub(crate) abs_pos: Pos, + pub(crate) kind: ComposedAreaKind, +} diff --git a/hframe-core/src/context.rs b/hframe-core/src/context.rs new file mode 100644 index 0000000..a1703a8 --- /dev/null +++ b/hframe-core/src/context.rs @@ -0,0 +1,54 @@ +use crate::{ + composed_area::ComposedArea, platform::Platform, test_platform::TestPlatform, tree::Node, +}; + +struct Context { + tree: Node, + platform: P, +} + +#[cfg(test)] +mod tests { + use crate::{ + composed_area::{ComposedAreaKind, ComposedHtml}, + geo::{Pos, Size}, + id::Id, + }; + + use super::*; + + #[test] + fn it_works() { + let ctx = Context { + tree: Node::new(ComposedArea { + id: Id::from("root"), + size: Size::new(100.0, 100.0), + abs_pos: Pos::new(0.0, 0.0), + kind: ComposedAreaKind::Canvas, + }) + .nest( + Node::new(ComposedArea { + id: Id::from("child"), + size: Size::new(50.0, 50.0), + abs_pos: Pos::new(10.0, 10.0), + kind: ComposedAreaKind::Canvas, + }) + .nest(Node::new(ComposedArea { + id: Id::from("grandchild"), + size: Size::new(25.0, 25.0), + abs_pos: Pos::new(5.0, 5.0), + kind: ComposedAreaKind::Html(ComposedHtml { + content: "
hello
".into(), + }), + })), + ), + platform: TestPlatform { + mouse_pos: Pos::new(0.0, 0.0), + }, + }; + + ctx.tree + .find(|node| node.read(|data| data.value.id == Id::from("grandchild"))) + .unwrap(); + } +} diff --git a/hframe-core/src/geo.rs b/hframe-core/src/geo.rs new file mode 100644 index 0000000..70e8664 --- /dev/null +++ b/hframe-core/src/geo.rs @@ -0,0 +1,37 @@ +#[derive(Debug, Clone, Copy, PartialEq, Default)] +pub(crate) struct Pos { + pub(crate) x: f64, + pub(crate) y: f64, +} + +impl Pos { + pub(crate) fn new(x: f64, y: f64) -> Self { + Pos { x, y } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Default)] +pub(crate) struct Size { + pub(crate) width: f64, + pub(crate) height: f64, +} + +impl Size { + pub(crate) fn new(width: f64, height: f64) -> Self { + Size { width, height } + } +} + +/* +#[derive(Debug, Clone, Copy, PartialEq, Default)] +struct Rect { + pos: Pos, + size: Size, +} + +impl Rect { + fn new(pos: Pos, size: Size) -> Self { + Rect { pos, size } + } +} +*/ diff --git a/hframe-core/src/id.rs b/hframe-core/src/id.rs new file mode 100644 index 0000000..b34165a --- /dev/null +++ b/hframe-core/src/id.rs @@ -0,0 +1,34 @@ +use std::hash::{DefaultHasher, Hash, Hasher}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct Id(u64); + +impl From for Id { + fn from(id: u64) -> Self { + Id(id) + } +} + +impl From<&str> for Id { + fn from(id: &str) -> Self { + let mut hasher = DefaultHasher::new(); + id.hash(&mut hasher); + Id(hasher.finish()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn it_works() { + let id = Id::from("hello"); + assert_eq!(id, Id::from("hello")); + assert_ne!(id, Id::from("world")); + + let id = Id::from(42); + assert_eq!(id, Id::from(42)); + assert_ne!(id, Id::from(43)); + } +} diff --git a/hframe-core/src/lib.rs b/hframe-core/src/lib.rs new file mode 100644 index 0000000..4480cd5 --- /dev/null +++ b/hframe-core/src/lib.rs @@ -0,0 +1,23 @@ +mod composed_area; +mod context; +mod geo; +mod id; +mod platform; +mod test_platform; +mod tree; +mod web_platform; + +pub fn add(left: usize, right: usize) -> usize { + left + right +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn it_works() { + let result = add(2, 2); + assert_eq!(result, 4); + } +} diff --git a/hframe-core/src/platform.rs b/hframe-core/src/platform.rs new file mode 100644 index 0000000..e9a9d84 --- /dev/null +++ b/hframe-core/src/platform.rs @@ -0,0 +1,5 @@ +use crate::geo::Pos; + +pub(crate) trait Platform { + fn mouse_pos(&self) -> Pos; +} diff --git a/hframe-core/src/test_platform.rs b/hframe-core/src/test_platform.rs new file mode 100644 index 0000000..212d192 --- /dev/null +++ b/hframe-core/src/test_platform.rs @@ -0,0 +1,11 @@ +use crate::{geo::Pos, platform::Platform}; + +pub(crate) struct TestPlatform { + pub(crate) mouse_pos: Pos, +} + +impl Platform for TestPlatform { + fn mouse_pos(&self) -> Pos { + self.mouse_pos + } +} diff --git a/hframe-core/src/tree.rs b/hframe-core/src/tree.rs new file mode 100644 index 0000000..b876a1a --- /dev/null +++ b/hframe-core/src/tree.rs @@ -0,0 +1,137 @@ +use std::{cell::RefCell, fmt::Debug, rc::Rc}; + +struct Handle(Rc>); + +impl Clone for Handle { + fn clone(&self) -> Self { + Handle(Rc::clone(&self.0)) + } +} + +impl Handle { + fn new(value: T) -> Self { + Handle(Rc::new(RefCell::new(value))) + } + + fn read(&self, f: impl FnOnce(&T) -> R) -> R { + match self.0.try_borrow() { + Ok(value) => f(&value), + Err(_) => panic!("The handle can't be read because it's being written somewhere else. Hint: Search where a `read_mut` closure is being used."), + } + } + + fn read_mut(&self, f: impl FnOnce(&mut T) -> R) -> R { + match self.0.try_borrow_mut() { + Ok(mut value) => f(&mut value), + Err(_) => panic!("The handle can't be written because it's being read somewhere else. Hint: Search where a `read` closure is being used."), + } + } +} + +pub(crate) struct NodeData { + pub(crate) value: T, + pub(crate) children: Vec>, +} + +pub(crate) struct Node(Handle>); + +impl Clone for Node { + fn clone(&self) -> Self { + Node(self.0.clone()) + } +} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub(crate) enum Walk { + Continue, + Stop, +} + +impl Node { + pub(crate) fn new(value: T) -> Self { + Node(Handle::new(NodeData { + value, + children: Vec::new(), + })) + } + + pub(crate) fn nest(&self, node: Node) -> Node { + self.read_mut(|data| data.children.push(node)); + self.clone() + } + + fn walk_impl(&self, f: &mut impl FnMut(Node, usize) -> Walk, depth: usize) -> Walk { + if f(self.clone(), depth) == Walk::Stop { + return Walk::Stop; + } + + self.read(|data| { + for child in data.children.iter() { + if child.walk_impl(f, depth + 1) == Walk::Stop { + return Walk::Stop; + } + } + + Walk::Continue + }) + } + + pub(crate) fn walk(&self, mut f: impl FnMut(Node, usize) -> Walk) { + self.walk_impl(&mut f, 0); + } + + // kepp this private to fully hide the handle + fn data(&self) -> Handle> { + self.0.clone() + } + + pub(crate) fn find(&self, predicate: impl Fn(Node) -> bool) -> Option> { + let mut found = None; + + self.walk(|node, _| { + if predicate(node.clone()) { + found = Some(node.clone()); + Walk::Stop + } else { + Walk::Continue + } + }); + + found + } + + pub(crate) fn read(&self, f: impl FnOnce(&NodeData) -> R) -> R { + self.data().read(|data| f(&data)) + } + + pub(crate) fn read_mut(&self, f: impl FnOnce(&mut NodeData) -> R) -> R { + self.data().read_mut(|data| f(data)) + } +} + +impl Debug for Node { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if f.alternate() { + self.walk(|node, depth| { + node.read(|data| { + writeln!(f, "{:indent$}{:?}", "", data.value, indent = depth * 2).unwrap(); + Walk::Continue + }) + }); + } else { + self.read(|data| { + write!(f, "{:?}", data.value).unwrap(); + + if !data.children.is_empty() { + write!(f, " {{").unwrap(); + for child in data.children.iter() { + write!(f, " {:?}", child).unwrap(); + } + write!(f, " }}").unwrap(); + } + }); + } + + Ok(()) + } +} diff --git a/hframe-core/src/web_platform.rs b/hframe-core/src/web_platform.rs new file mode 100644 index 0000000..a58b73b --- /dev/null +++ b/hframe-core/src/web_platform.rs @@ -0,0 +1,9 @@ +use crate::platform::Platform; + +pub struct WebPlatform; + +impl Platform for WebPlatform { + fn mouse_pos(&self) -> crate::geo::Pos { + crate::geo::Pos::new(0.0, 0.0) + } +}