diff --git a/Cargo.lock b/Cargo.lock index 6cf5d23d57..1e4193a4cb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7376,6 +7376,7 @@ name = "storybook" version = "0.1.0" dependencies = [ "anyhow", + "derive_more", "gpui2", "log", "refineable", diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index c95c0a6105..9bf881a2e1 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -3589,7 +3589,7 @@ impl BorrowWindowContext for EventContext<'_, '_, '_, V> { } } -pub(crate) enum Reference<'a, T> { +pub enum Reference<'a, T> { Immutable(&'a T), Mutable(&'a mut T), } diff --git a/crates/storybook/Cargo.toml b/crates/storybook/Cargo.toml index 53b05100ef..a14482038c 100644 --- a/crates/storybook/Cargo.toml +++ b/crates/storybook/Cargo.toml @@ -9,8 +9,9 @@ name = "storybook" path = "src/storybook.rs" [dependencies] -gpui2 = { path = "../gpui2" } anyhow.workspace = true +derive_more.workspace = true +gpui2 = { path = "../gpui2" } log.workspace = true refineable = { path = "../refineable" } rust-embed.workspace = true diff --git a/crates/storybook/src/sketch.rs b/crates/storybook/src/sketch.rs new file mode 100644 index 0000000000..2ead3ec6d5 --- /dev/null +++ b/crates/storybook/src/sketch.rs @@ -0,0 +1,519 @@ +use anyhow::{anyhow, Result}; +use gpui2::{Layout, LayoutId, Reference, Vector2F}; +use std::{any::Any, collections::HashMap, marker::PhantomData, rc::Rc}; + +pub trait Context { + type EntityContext<'a, 'b, T>; + + fn add_entity(&mut self, build_entity: F) -> Handle + where + F: FnOnce(&mut Self::EntityContext<'_, '_, T>) -> T; + + fn update_entity(&mut self, handle: &Handle, update: F) -> R + where + F: FnOnce(&mut T, &mut Self::EntityContext<'_, '_, T>) -> R, + T: 'static; + + fn update_window(&mut self, window_id: WindowId, update: F) -> Result + where + F: FnOnce(&mut WindowContext) -> R; +} + +pub struct AppContext { + entity_count: usize, + entities: HashMap>, + windows: HashMap, +} + +impl Context for AppContext { + type EntityContext<'a, 'b, T> = ModelContext<'a, T>; + + fn add_entity(&mut self, build: F) -> Handle + where + F: FnOnce(&mut Self::EntityContext<'_, '_, T>) -> T, + { + let id = EntityId::new(&mut self.entity_count); + let entity = build(&mut ModelContext::mutable(self, id)); + self.entities.insert(id, Box::new(entity)); + Handle { + id, + entity_type: PhantomData, + } + } + + fn update_entity(&mut self, handle: &Handle, update: F) -> R + where + F: FnOnce(&mut T, &mut Self::EntityContext<'_, '_, T>) -> R, + T: 'static, + { + let mut entity = self.entities.remove(&handle.id).unwrap(); + let result = update( + entity.downcast_mut().unwrap(), + &mut ModelContext::mutable(self, handle.id), + ); + self.entities.insert(handle.id, entity); + result + } + + fn update_window(&mut self, window_id: WindowId, update: F) -> Result + where + F: FnOnce(&mut WindowContext<'_, '_>) -> R, + { + let mut window = self + .windows + .remove(&window_id) + .ok_or_else(|| anyhow!("window closed"))?; + let result = update(&mut WindowContext::mutable(self, &mut window)); + self.windows.insert(window_id, window); + Ok(result) + } +} + +impl AppContext { + pub fn new() -> Self { + unimplemented!() + } + + pub fn open_window(&self, root_element: E, state: S) -> WindowHandle { + unimplemented!() + } + + pub fn add_entity(&mut self, entity: F) -> Handle + where + F: FnOnce(&mut ModelContext) -> T, + { + let id = EntityId::new(&mut self.entity_count); + + Handle { + id, + entity_type: PhantomData, + } + } + + fn update_window( + &mut self, + window_id: WindowId, + update: impl FnOnce(&mut WindowContext) -> R, + ) -> Result { + let mut window = self + .windows + .remove(&window_id) + .ok_or_else(|| anyhow!("window not found"))?; + + let mut cx = WindowContext::mutable(self, &mut window); + let result = update(&mut cx); + self.windows.insert(window_id, window); + Ok(result) + } +} + +pub struct ModelContext<'a, T> { + app: Reference<'a, AppContext>, + entity_type: PhantomData, + entity_id: EntityId, +} + +impl<'a, T> ModelContext<'a, T> { + fn mutable(app: &mut AppContext, entity_id: EntityId) -> Self { + Self { + app: Reference::Mutable(app), + entity_type: PhantomData, + entity_id, + } + } + + fn immutable(app: &AppContext, entity_id: EntityId) -> Self { + Self { + app: Reference::Immutable(app), + entity_type: PhantomData, + entity_id, + } + } +} + +pub struct Window { + id: WindowId, +} + +pub struct WindowContext<'a, 'b> { + app: Reference<'a, AppContext>, + window: Reference<'b, Window>, +} + +impl<'a, 'b> WindowContext<'a, 'b> { + fn mutable(app: &mut AppContext, window: &mut Window) -> Self { + Self { + app: Reference::Mutable(app), + window: Reference::Mutable(window), + } + } + + fn app_context(&mut self) -> &mut AppContext { + &mut *self.app + } +} + +impl<'a, 'b> Context for WindowContext<'a, 'b> { + type EntityContext<'c, 'd, T> = ViewContext<'c, 'd, T>; + + fn add_entity(&mut self, build_entity: F) -> Handle + where + F: FnOnce(&mut Self::EntityContext<'_, '_, T>) -> T, + { + let id = EntityId::new(&mut self.app_context().entity_count); + let mut cx = ViewContext::mutable(&mut self.app_context(), &mut self.window, id); + let entity = build_entity(&mut cx); + self.app.entities.insert(id, Box::new(entity)); + Handle { + id, + entity_type: PhantomData, + } + } + + fn update_entity(&mut self, handle: &Handle, update: F) -> R + where + F: FnOnce(&mut T, &mut Self::EntityContext<'_, '_, T>) -> R, + T: 'static, + { + let mut entity = self.app.entities.remove(&handle.id).unwrap(); + let result = update( + entity.downcast_mut().unwrap(), + &mut ViewContext::mutable(&mut self.app, &mut self.window, handle.id), + ); + self.app.entities.insert(handle.id, entity); + result + } + + fn update_window(&mut self, window_id: WindowId, update: F) -> Result + where + F: FnOnce(&mut WindowContext) -> R, + { + if window_id == self.window.id { + Ok(update(self)) + } else { + self.app.update_window(window_id, update) + } + } +} + +pub struct ViewContext<'a, 'b, T> { + app: Reference<'a, AppContext>, + window: Reference<'b, Window>, + entity_type: PhantomData, + entity_id: EntityId, +} + +impl<'a, 'b, V> ViewContext<'a, 'b, V> { + fn mutable(app: &'a mut AppContext, window: &'b mut Window, entity_id: EntityId) -> Self { + Self { + app: Reference::Mutable(app), + window: Reference::Mutable(window), + entity_type: PhantomData, + entity_id, + } + } + + fn immutable(app: &'a AppContext, window: &'b Window, entity_id: EntityId) -> Self { + Self { + app: Reference::Immutable(app), + window: Reference::Immutable(window), + entity_type: PhantomData, + entity_id, + } + } + + fn window_context(&self) -> WindowContext { + WindowContext { + app: Reference::Immutable(&*self.app), + window: Reference::Immutable(&*self.window), + } + } + + fn window_context_mut(&mut self) -> WindowContext { + WindowContext { + app: Reference::Mutable(&mut *self.app), + window: Reference::Mutable(&mut *self.window), + } + } +} + +impl<'a, 'b, V> Context for ViewContext<'a, 'b, V> { + type EntityContext<'c, 'd, T> = ViewContext<'c, 'd, T>; + + fn add_entity(&mut self, build_entity: F) -> Handle + where + F: FnOnce(&mut Self::EntityContext<'_, '_, T>) -> T, + { + self.window_context_mut().add_entity(build_entity) + } + + fn update_entity(&mut self, handle: &Handle, update: F) -> R + where + F: FnOnce(&mut T, &mut Self::EntityContext<'_, '_, T>) -> R, + T: 'static, + { + self.window_context_mut().update_entity(handle, update) + } + + fn update_window(&mut self, window_id: WindowId, update: F) -> Result + where + F: FnOnce(&mut WindowContext<'_, '_>) -> R, + { + self.window_context_mut().update_window(window_id, update) + } +} + +#[derive(Clone, Copy, Eq, PartialEq, Hash)] +pub struct WindowId(usize); + +pub struct WindowHandle(PhantomData); + +#[derive(Clone, Copy, Eq, PartialEq, Hash)] +pub struct EntityId(usize); + +impl EntityId { + fn new(entity_count: &mut usize) -> EntityId { + let id = *entity_count; + *entity_count += 1; + Self(id) + } +} + +pub struct Handle { + id: EntityId, + entity_type: PhantomData, +} + +impl Handle { + fn update<'a, C: Context, R>( + &self, + cx: &'a mut C, + update: impl FnOnce(&mut T, &mut C::EntityContext<'_, '_, T>) -> R, + ) -> R { + cx.update_entity(self, update) + } +} + +pub trait Element: 'static { + type State; + type FrameState; + + fn add_layout_node( + &mut self, + state: &mut Self::State, + cx: &mut ViewContext, + ) -> Result<(LayoutId, Self::FrameState)>; + + fn paint( + &mut self, + layout: Layout, + state: &mut Self::State, + frame_state: &mut Self::FrameState, + cx: &mut ViewContext, + ) -> Result<()>; +} + +pub trait ParentElement { + fn child(self, child: impl IntoAnyElement) -> Self; +} + +trait ElementObject { + fn add_layout_node(&mut self, state: &mut S, cx: &mut ViewContext) -> Result; + fn paint( + &mut self, + parent_origin: Vector2F, + state: &mut S, + cx: &mut ViewContext, + ) -> Result<()>; +} + +struct RenderedElement { + element: E, + phase: ElementRenderPhase, +} + +enum ElementRenderPhase { + Rendered, + LayoutNodeAdded { layout_id: LayoutId, frame_state: S }, + Painted { layout: Layout, frame_state: S }, +} + +impl RenderedElement { + fn new(element: E) -> Self { + RenderedElement { + element, + phase: ElementRenderPhase::Rendered, + } + } +} + +impl ElementObject for RenderedElement { + fn add_layout_node( + &mut self, + state: &mut E::State, + cx: &mut ViewContext, + ) -> Result { + let (layout_id, frame_state) = self.element.add_layout_node(state, cx)?; + self.phase = ElementRenderPhase::LayoutNodeAdded { + layout_id, + frame_state, + }; + Ok(layout_id) + } + + fn paint( + &mut self, + parent_origin: Vector2F, + state: &mut E::State, + cx: &mut ViewContext, + ) -> Result<()> { + todo!() + } +} + +pub struct AnyElement(Box>); + +impl AnyElement { + pub fn layout(&mut self, state: &mut S, cx: &mut ViewContext) -> Result { + self.0.add_layout_node(state, cx) + } + + pub fn paint( + &mut self, + parent_origin: Vector2F, + state: &mut S, + cx: &mut ViewContext, + ) -> Result<()> { + self.0.paint(parent_origin, state, cx) + } +} + +pub trait IntoAnyElement { + fn into_any_element(self) -> AnyElement; +} + +impl IntoAnyElement for E { + fn into_any_element(self) -> AnyElement { + AnyElement(Box::new(RenderedElement::new(self))) + } +} + +impl IntoAnyElement for AnyElement { + fn into_any_element(self) -> AnyElement { + self + } +} + +pub struct View { + render: Rc AnyElement>, +} + +impl View { + fn render(&self, cx: &mut WindowContext) -> AnyElement { + (self.render)(cx) + } +} + +pub fn view( + state: Handle, + render: impl 'static + Fn(&mut ChildState, &mut ViewContext) -> AnyElement, +) -> View { + View { + render: Rc::new(move |cx| state.update(cx, |state, cx| render(state, cx))), + } +} + +pub struct Div(PhantomData); + +impl Element for Div { + type State = S; + type FrameState = (); + + fn add_layout_node( + &mut self, + state: &mut Self::State, + cx: &mut ViewContext, + ) -> Result<(LayoutId, Self::FrameState)> { + todo!() + } + + fn paint( + &mut self, + layout: Layout, + state: &mut Self::State, + frame_state: &mut Self::FrameState, + cx: &mut ViewContext, + ) -> Result<()> { + todo!() + } +} + +impl ParentElement for Div { + fn child(self, child: impl IntoAnyElement) -> Self { + todo!() + } +} + +pub fn div() -> Div { + todo!() +} + +pub struct Workspace { + left_panel: View, +} + +fn workspace( + state: &mut Workspace, + cx: &mut ViewContext, +) -> impl Element { + div().child(state.left_panel.render(&mut cx)) +} + +pub struct CollabPanel { + filter_editor: Handle, +} + +impl CollabPanel { + fn new(cx: &mut ViewContext) -> Self { + Self { + filter_editor: cx.add_entity(|cx| Editor::new(cx)), + } + } +} + +struct EditorElement { + input: bool, +} + +impl EditorElement { + pub fn input(mut self) -> Self { + self.input = true; + self + } +} + +struct Editor {} + +impl Editor { + pub fn new(_: &mut ViewContext) -> Self { + Editor {} + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test() { + let mut cx = AppContext::new(); + let collab_panel = cx.add_entity(|cx| CollabPanel::new(cx)); + + // let + // let mut workspace = Workspace { + // left_panel: view(), + // } + + // cx.open_window(workspace::Workspace, state) + } +} diff --git a/crates/storybook/src/storybook.rs b/crates/storybook/src/storybook.rs index 04e1038988..ac84f934fa 100644 --- a/crates/storybook/src/storybook.rs +++ b/crates/storybook/src/storybook.rs @@ -12,6 +12,7 @@ use simplelog::SimpleLogger; mod collab_panel; mod components; mod element_ext; +mod sketch; mod theme; mod workspace;