From 419fa1d984ef425645209a6730ba8fb5146ea9ef Mon Sep 17 00:00:00 2001 From: Togglebit Date: Fri, 30 Aug 2024 11:00:42 +0200 Subject: [PATCH 01/13] with value now returns whatever the closure returns --- anathema-runtime/src/lib.rs | 1 - anathema-store/src/tree/mod.rs | 7 ++++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/anathema-runtime/src/lib.rs b/anathema-runtime/src/lib.rs index 7fe0e191..b3db030f 100644 --- a/anathema-runtime/src/lib.rs +++ b/anathema-runtime/src/lib.rs @@ -165,7 +165,6 @@ impl RuntimeBuilder { future_values: FutureValues::empty(), changes: Changes::empty(), - // tab_indices: TabIndices::new(), component_registry: self.component_registry, globals, document: self.document, diff --git a/anathema-store/src/tree/mod.rs b/anathema-store/src/tree/mod.rs index bede3e9c..00399f13 100644 --- a/anathema-store/src/tree/mod.rs +++ b/anathema-store/src/tree/mod.rs @@ -223,13 +223,14 @@ impl Tree { /// Perform a given operation (`F`) on a mutable reference to a value in the tree /// while still having mutable access to the rest of the tree. - pub fn with_value_mut(&mut self, value_id: ValueId, f: F) + pub fn with_value_mut(&mut self, value_id: ValueId, f: F) -> V where - F: FnOnce(&[u16], &mut T, &mut Self), + F: FnOnce(&[u16], &mut T, &mut Self) -> V, { let mut ticket = self.values.checkout(value_id); - f(&ticket.value.0, &mut ticket.value.1, self); + let value = f(&ticket.value.0, &mut ticket.value.1, self); self.values.restore(ticket); + value } /// Get mutable access to a node value along with the children From 0039ba1d7d7a6b3d41aa48159d29b4453b5ea7fd Mon Sep 17 00:00:00 2001 From: Togglebit Date: Fri, 30 Aug 2024 17:21:59 +0200 Subject: [PATCH 02/13] flush on finalise --- anathema-backend/src/tui/mod.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/anathema-backend/src/tui/mod.rs b/anathema-backend/src/tui/mod.rs index 4437429c..b1140d2f 100644 --- a/anathema-backend/src/tui/mod.rs +++ b/anathema-backend/src/tui/mod.rs @@ -3,7 +3,7 @@ //! It uses two buffers and only draws the diffs from top left to bottom right, making it less //! likely to flicker when moving the cursor etc. #![deny(missing_docs)] -use std::io::Stdout; +use std::io::{Stdout, Write}; use std::ops::Add; use std::time::Duration; @@ -186,6 +186,8 @@ impl Backend for TuiBackend { if self.enable_mouse { let _ = Screen::enable_mouse(&mut self.output); } + + let _ = self.output.flush(); } } From 1a66e2808e158816bf4adbd89ea7eb623434292d Mon Sep 17 00:00:00 2001 From: Togglebit Date: Fri, 30 Aug 2024 17:57:08 +0200 Subject: [PATCH 03/13] show the cursor and then hide it to work around a windows isue --- anathema-backend/src/tui/mod.rs | 3 +++ anathema-backend/src/tui/screen.rs | 10 ++++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/anathema-backend/src/tui/mod.rs b/anathema-backend/src/tui/mod.rs index b1140d2f..566f1238 100644 --- a/anathema-backend/src/tui/mod.rs +++ b/anathema-backend/src/tui/mod.rs @@ -171,6 +171,9 @@ impl Backend for TuiBackend { } fn finalize(&mut self) { + // This is to fix an issue with Windows cmd.exe + let _ = Screen::show_cursor(&mut self.output); + if self.hide_cursor { let _ = Screen::hide_cursor(&mut self.output); } diff --git a/anathema-backend/src/tui/screen.rs b/anathema-backend/src/tui/screen.rs index 0e58c034..1280b8f0 100644 --- a/anathema-backend/src/tui/screen.rs +++ b/anathema-backend/src/tui/screen.rs @@ -21,13 +21,19 @@ pub struct Screen { impl Screen { /// Hide the cursor pub(super) fn hide_cursor(mut output: impl Write) -> Result<()> { - output.queue(cursor::Hide)?; + output.execute(cursor::Hide)?; + Ok(()) + } + + /// Show the cursor + pub(super) fn show_cursor(mut output: impl Write) -> Result<()> { + output.execute(cursor::Show)?; Ok(()) } /// Enable mouse support pub(super) fn enable_mouse(mut output: impl Write) -> Result<()> { - output.queue(EnableMouseCapture)?; + output.execute(EnableMouseCapture)?; Ok(()) } From f8940f1cb0db43826f4d22da77f19738234cf516 Mon Sep 17 00:00:00 2001 From: FishingHacks Date: Sat, 31 Aug 2024 19:22:52 +0200 Subject: [PATCH 04/13] Started documenting the RuntimeBuilder/Runtime, Backend and TuiBackend --- anathema-backend/src/lib.rs | 8 ++++++++ anathema-backend/src/tui/mod.rs | 18 +++++++++++------- anathema-runtime/src/lib.rs | 15 +++++++++++++++ 3 files changed, 34 insertions(+), 7 deletions(-) diff --git a/anathema-backend/src/lib.rs b/anathema-backend/src/lib.rs index e85f6645..85f25328 100644 --- a/anathema-backend/src/lib.rs +++ b/anathema-backend/src/lib.rs @@ -12,12 +12,14 @@ pub mod tui; pub trait Backend { fn size(&self) -> Size; + /// When [Backend::next_event] returns [Event::Stop], this function will be called to make sure the Backend wants anathema exit. fn quit_test(&self, event: Event) -> bool; fn next_event(&mut self, timeout: Duration) -> Option; fn resize(&mut self, new_size: Size); + /// Paint an internal buffer. This should not change the screen. fn paint<'bp>( &mut self, element: &mut Element<'bp>, @@ -28,9 +30,15 @@ pub trait Backend { ignore_floats: bool, ); + /// Publish the changes to the Buffer to the Screen. fn render(&mut self); + /// Clear the internal buffer entirely. This should not change the screen. fn clear(&mut self); + /// Finalizes the backend. This is called just before we start running the anathema main loop. + /// This should set the terminal in a state where its usable for the tui, for example by enabling events. + /// + /// There is no guarantee that other functions will be called before this. They should not cause a panic. fn finalize(&mut self) {} } diff --git a/anathema-backend/src/tui/mod.rs b/anathema-backend/src/tui/mod.rs index 566f1238..f7e9c87e 100644 --- a/anathema-backend/src/tui/mod.rs +++ b/anathema-backend/src/tui/mod.rs @@ -38,25 +38,29 @@ pub struct TuiBackendBuilder { } impl TuiBackendBuilder { - /// Enable alt screen + // Note: This will only apply when TuiBackend::finalize is called. + /// Enable an alternative screen. When using this with stdout it means the output will not persist once the program exits. This won't apply until Runtime::run is called. pub fn enable_alt_screen(mut self) -> Self { self.enable_alt_screen = true; self } - - /// Enable mouse support + + // Note: This will only apply when TuiBackend::finalize is called. + /// Enable mouse support. This won't apply until Runtime::run is called. pub fn enable_mouse(mut self) -> Self { self.enable_mouse = true; self } - - /// Enable raw mode + + // Note: This will only apply when TuiBackend::finalize is called. + /// Enable raw mode: input will not be forwarded to the screen. This won't apply until Runtime::run is called. pub fn enable_raw_mode(mut self) -> Self { self.enable_raw_mode = true; self } - - /// Hide the cursor (not the mouse cursor) + + // Note: This will only apply when TuiBackend::finalize is called. + /// Hide the text cursor. This won't apply until Runtime::run is called. pub fn hide_cursor(mut self) -> Self { self.hide_cursor = true; self diff --git a/anathema-runtime/src/lib.rs b/anathema-runtime/src/lib.rs index b3db030f..e53e3401 100644 --- a/anathema-runtime/src/lib.rs +++ b/anathema-runtime/src/lib.rs @@ -59,6 +59,9 @@ pub struct RuntimeBuilder { } impl RuntimeBuilder { + /// Registers a [Component] with the runtime. This returns a unique [ComponentId] that can be used to send messages to the component. + /// A component can only be used once in a template, even if it wouldn't actually be displayed in the end. + /// If you want multiple occurrences, register it as a prototype instead, see [RuntimeBuilder::register_prototype], it is basically a drop-in replacement pub fn register_component( &mut self, ident: impl Into, @@ -72,6 +75,10 @@ impl RuntimeBuilder { Ok(id.into()) } + /// Registers a [Component] as a prototype to the [Runtime], which allows the usage of multiple instances of the component in the templates. + /// This is useful if you want to reuse the component (Such as an input or button component) or if you use your component in if-blocks or for loops. + /// If you don't need that, consider using [RuntimeBuilder::register_component] or [RuntimeBuilder::register_default] instead. + /// Unlike when registering a component, this won't return a [ComponentId] because there's no meaningful way to express which component the message would go to. pub fn register_prototype( &mut self, ident: impl Into, @@ -90,6 +97,10 @@ impl RuntimeBuilder { Ok(()) } + /// Registers a [Component] with the runtime. This returns a unique [ComponentId] that can be used to send messages to the component. + /// Uses the [Default::default] implementation for the [Component] and [Component::State]. + /// A component can only be used once in a template, even if it wouldn't actually be displayed in the end. + /// If you want multiple occurrences, register it as a prototype instead, see [RuntimeBuilder::register_prototype], it is basically a drop-in replacement pub fn register_default( &mut self, ident: impl Into, @@ -106,6 +117,7 @@ impl RuntimeBuilder { Ok(id.into()) } + /// Returns the Runtime [Emitter] to emit messages to components pub fn emitter(&self) -> Emitter { self.emitter.clone() } @@ -140,6 +152,8 @@ impl RuntimeBuilder { Ok(watcher) } + /// Builds the [Runtime]. This will remove the ability to add new components or prototypes. + /// Fails if compiling the [Document] or creating the file watcher fails. pub fn finish(mut self) -> Result> where T: Backend, @@ -243,6 +257,7 @@ where Self::builder(document, backend) } + /// Creates a [RuntimeBuilder] based on the [Document] and [Backend]. Hot Reloading is configured on the [Document] pub fn builder(document: Document, backend: T) -> RuntimeBuilder { let mut factory = Factory::new(); From 296589e6b5749c7926cdbe1577757661150a99de Mon Sep 17 00:00:00 2001 From: Togglebit Date: Fri, 30 Aug 2024 11:00:42 +0200 Subject: [PATCH 05/13] with value now returns whatever the closure returns --- anathema-runtime/src/events.rs | 281 +++++++++---------------- anathema-runtime/src/lib.rs | 170 +++++++-------- anathema-store/src/tree/mod.rs | 18 +- anathema-widgets/src/components/mod.rs | 197 ++++++++--------- anathema-widgets/src/nodes/eval.rs | 8 +- anathema-widgets/src/nodes/future.rs | 2 +- anathema-widgets/src/nodes/loops.rs | 4 +- anathema-widgets/src/widget/mod.rs | 47 +++-- 8 files changed, 317 insertions(+), 410 deletions(-) diff --git a/anathema-runtime/src/events.rs b/anathema-runtime/src/events.rs index 03972ac4..7ad460e2 100644 --- a/anathema-runtime/src/events.rs +++ b/anathema-runtime/src/events.rs @@ -3,13 +3,13 @@ use std::time::{Duration, Instant}; use anathema_backend::Backend; use anathema_geometry::Size; use anathema_state::{AnyState, States}; -use anathema_store::storage::strings::Strings; use anathema_widgets::components::events::{Event, KeyCode, KeyEvent, KeyState}; -use anathema_widgets::components::{AssociatedEvents, Emitter, UntypedContext}; +use anathema_widgets::components::{AssociatedEvents, UntypedContext}; use anathema_widgets::layout::{Constraints, Viewport}; -use anathema_widgets::{AttributeStorage, Components, Elements, WidgetKind, WidgetTree}; +use anathema_widgets::{AttributeStorage, Components, WidgetTree}; use crate::error::{Error, Result}; +use crate::tree::Tree; pub(super) struct EventHandler; @@ -21,56 +21,19 @@ impl EventHandler { sleep_micros: u128, backend: &mut impl Backend, viewport: &mut Viewport, - emitter: &Emitter, tree: &mut WidgetTree<'bp>, - components: &mut Components, - states: &mut States, - attribute_storage: &mut AttributeStorage<'bp>, constraints: &mut Constraints, - assoc_events: &mut AssociatedEvents, - strings: &Strings, + event_ctx: &mut EventCtx<'_, '_, 'bp>, ) -> Result<()> { while let Some(event) = backend.next_event(poll_duration) { - let Some(event) = global_event( - backend, - components, - event, - tree, - states, - attribute_storage, - emitter, - *viewport, - assoc_events, - strings, - ) else { + let Some(event) = global_event(event_ctx, backend, tree, event) else { return Ok(()); }; // Ignore mouse events, as they are handled by global event if !event.is_mouse_event() { - if let Some((widget_id, state_id)) = components.current() { - tree.with_value_mut(widget_id, |path, widget, tree| { - let WidgetKind::Component(component) = widget else { return }; - let state = states.get_mut(state_id); - - let parent = component - .parent - .and_then(|parent| components.get_by_component_id(parent)) - .map(|parent| parent.component_id.into()); - - let Some((node, values)) = tree.get_node_by_path(path) else { return }; - let elements = Elements::new(node.children(), values, attribute_storage); - let context = UntypedContext { - emitter, - viewport: *viewport, - assoc_events, - state_id, - parent, - strings, - assoc_functions: component.assoc_functions, - }; - component.dyn_component.any_event(event, state, elements, context); - }); + if let Some((widget_id, state_id)) = event_ctx.components.get(event_ctx.components.tab_index) { + tree.with_component(widget_id, state_id, event_ctx, |comp, ctx| comp.any_event(ctx, event)); } } @@ -82,34 +45,18 @@ impl EventHandler { constraints.set_max_width(size.width); constraints.set_max_height(size.height); + // Remember to update the viewport on the context + event_ctx.context.viewport = *viewport; + // Notify all components of the resize - let len = components.len(); + let len = event_ctx.components.len(); for i in 0..len { - let (widget_id, state_id) = - components.get(i).expect("components can not change during this call"); - - tree.with_value_mut(widget_id, |path, widget, tree| { - let WidgetKind::Component(component) = widget else { return }; - let state = states.get_mut(state_id); + let (widget_id, state_id) = event_ctx + .components + .get(i) + .expect("components can not change during this call"); - let parent = component - .parent - .and_then(|parent| components.get_by_component_id(parent)) - .map(|parent| parent.component_id.into()); - - let Some((node, values)) = tree.get_node_by_path(path) else { return }; - let elements = Elements::new(node.children(), values, attribute_storage); - let context = UntypedContext { - emitter, - viewport: *viewport, - assoc_events, - state_id, - parent, - strings, - assoc_functions: component.assoc_functions, - }; - component.dyn_component.any_resize(state, elements, context); - }); + tree.with_component(widget_id, state_id, event_ctx, |comp, ctx| comp.any_resize(ctx)); } } Event::Blur => (), @@ -126,42 +73,29 @@ impl EventHandler { // ----------------------------------------------------------------------------- // - Drain associated events - // ----------------------------------------------------------------------------- - while let Some(mut event) = assoc_events.next() { - states.with_mut(event.state, |state, states| { + while let Some(mut event) = event_ctx.assoc_events.next() { + event_ctx.states.with_mut(event.state, |state, states| { let common_val = (event.f)(state); let Some(common_val) = common_val.to_common() else { return }; - let Some(entry) = components.get_by_component_id(event.parent.into()) else { + let Some(entry) = event_ctx.components.get_by_component_id(event.parent.into()) else { return; }; let (widget_id, state_id) = (entry.widget_id, entry.state_id); - tree.with_value_mut(widget_id, |path, widget, tree| { - let WidgetKind::Component(component) = widget else { return }; - - let event_ident = strings.get_ref_unchecked(event.external); - - let state = states.get_mut(state_id); - let parent = component - .parent - .and_then(|parent| components.get_by_component_id(parent)) - .map(|parent| parent.component_id.into()); + let strings = event_ctx.context.strings; - let Some((node, values)) = tree.get_node_by_path(path) else { return }; - let elements = Elements::new(node.children(), values, attribute_storage); - let context = UntypedContext { - emitter, - viewport: *viewport, - assoc_events, - state_id, - parent, - strings, - assoc_functions: component.assoc_functions, - }; + let mut event_ctx = EventCtx { + states, + components: event_ctx.components, + attribute_storage: event_ctx.attribute_storage, + assoc_events: event_ctx.assoc_events, + context: event_ctx.context, + }; - component - .dyn_component - .any_receive(state, event_ident, common_val, elements, context); + tree.with_component(widget_id, state_id, &mut event_ctx, |comp, ctx| { + let event_ident = strings.get_ref_unchecked(event.external); + comp.any_receive(ctx, event_ident, common_val) }); }) } @@ -171,22 +105,28 @@ impl EventHandler { } } -pub fn global_event<'bp, T: Backend>( +// TODO: rename this, it has nothing to do with the events, +// but rather calling functions on dyn components +pub(crate) struct EventCtx<'a, 'rt, 'bp> { + pub components: &'a mut Components, + pub states: &'a mut States, + pub attribute_storage: &'a mut AttributeStorage<'bp>, + pub assoc_events: &'a mut AssociatedEvents, + pub context: UntypedContext<'rt>, +} + +fn global_event<'bp, T: Backend>( + event_ctx: &mut EventCtx<'_, '_, 'bp>, backend: &mut T, - components: &mut Components, - event: Event, tree: &mut WidgetTree<'bp>, - states: &mut States, - attribute_storage: &mut AttributeStorage<'bp>, - emitter: &Emitter, - viewport: Viewport, - assoc_events: &mut AssociatedEvents, - strings: &Strings, + event: Event, ) -> Option { // ----------------------------------------------------------------------------- // - Ctrl-c to quite - // This should be on by default. // Give it a good name + // + // TODO: Do away with this thing once we add a global event handler // ----------------------------------------------------------------------------- if backend.quit_test(event) { return Some(Event::Stop); @@ -201,60 +141,66 @@ pub fn global_event<'bp, T: Backend>( .. }) = event { - let prev = match code { - KeyCode::Tab => components.next(), - KeyCode::BackTab => components.prev(), + enum Dir { + F, + B, + } + + let index = event_ctx.components.tab_index; + let dir = match code { + KeyCode::Tab => Dir::F, + KeyCode::BackTab => Dir::B, _ => return Some(event), }; - if let Some((widget_id, state_id)) = components.get(prev) { - tree.with_value_mut(widget_id, |path, widget, tree| { - let WidgetKind::Component(component) = widget else { return }; - - let parent = component - .parent - .and_then(|parent| components.get_by_component_id(parent)) - .map(|parent| parent.component_id.into()); - - let Some((node, values)) = tree.get_node_by_path(path) else { return }; - let elements = Elements::new(node.children(), values, attribute_storage); - let state = states.get_mut(state_id); - let context = UntypedContext { - emitter, - viewport, - assoc_events, - state_id, - parent, - strings, - assoc_functions: component.assoc_functions, - }; - component.dyn_component.any_blur(state, elements, context); - }); - } + loop { + // ----------------------------------------------------------------------------- + // - Blur - + // ----------------------------------------------------------------------------- + if let Some((widget_id, state_id)) = event_ctx.components.get(event_ctx.components.tab_index) { + tree.with_component(widget_id, state_id, event_ctx, |comp, ctx| comp.any_blur(ctx)); + } - if let Some((widget_id, state_id)) = components.current() { - tree.with_value_mut(widget_id, |path, widget, tree| { - let WidgetKind::Component(component) = widget else { return }; + // ----------------------------------------------------------------------------- + // - Change index - + // ----------------------------------------------------------------------------- + match dir { + Dir::F => { + event_ctx.components.tab_index += 1; + if event_ctx.components.tab_index >= event_ctx.components.len() { + event_ctx.components.tab_index = 0; + } + } + Dir::B => match event_ctx.components.tab_index >= 1 { + true => event_ctx.components.tab_index -= 1, + false => event_ctx.components.tab_index = event_ctx.components.len() - 1, + }, + } - let parent = component - .parent - .and_then(|parent| components.get_by_component_id(parent)) - .map(|parent| parent.component_id.into()); + if index == event_ctx.components.tab_index { + break; + } - let Some((node, values)) = tree.get_node_by_path(path) else { return }; - let elements = Elements::new(node.children(), values, attribute_storage); - let state = states.get_mut(state_id); - let context = UntypedContext { - emitter, - viewport, - assoc_events, - state_id, - parent, - strings, - assoc_functions: component.assoc_functions, - }; - component.dyn_component.any_focus(state, elements, context); - }); + // ----------------------------------------------------------------------------- + // - Focus - + // ----------------------------------------------------------------------------- + if let Some((widget_id, state_id)) = event_ctx.components.current() { + tree.with_component(widget_id, state_id, event_ctx, |comp, ctx| comp.any_focus(ctx)); + + let cont = tree + .with_component(widget_id, state_id, event_ctx, |comp, ctx| { + if !comp.accept_focus_any() { + return true; + } + comp.any_focus(ctx); + false + }) + .unwrap_or(true); + + if !cont { + break; + } + } } return None; @@ -262,30 +208,13 @@ pub fn global_event<'bp, T: Backend>( // Mouse events are global if let Event::Mouse(_) = event { - for i in 0..components.len() { - let (widget_id, state_id) = components.get(i).expect("components can not change during this call"); - tree.with_value_mut(widget_id, |path, widget, tree| { - let WidgetKind::Component(component) = widget else { return }; - - let parent = component - .parent - .and_then(|parent| components.get_by_component_id(parent)) - .map(|parent| parent.component_id.into()); + for i in 0..event_ctx.components.len() { + let (widget_id, state_id) = event_ctx + .components + .get(i) + .expect("components can not change during this call"); - let Some((node, values)) = tree.get_node_by_path(path) else { return }; - let elements = Elements::new(node.children(), values, attribute_storage); - let state = states.get_mut(state_id); - let context = UntypedContext { - emitter, - viewport, - assoc_events, - state_id, - parent, - strings, - assoc_functions: component.assoc_functions, - }; - let _ = component.dyn_component.any_event(event, state, elements, context); - }); + tree.with_component(widget_id, state_id, event_ctx, |comp, ctx| comp.any_event(ctx, event)); } } diff --git a/anathema-runtime/src/lib.rs b/anathema-runtime/src/lib.rs index 7fe0e191..27c88ca7 100644 --- a/anathema-runtime/src/lib.rs +++ b/anathema-runtime/src/lib.rs @@ -36,11 +36,12 @@ use anathema_widgets::components::{ use anathema_widgets::layout::text::StringStorage; use anathema_widgets::layout::{layout_widget, position_widget, Constraints, LayoutCtx, LayoutFilter, Viewport}; use anathema_widgets::{ - eval_blueprint, try_resolve_future_values, update_tree, AttributeStorage, Components, Elements, EvalContext, - Factory, FloatingWidgets, Scope, WidgetKind, WidgetTree, + eval_blueprint, try_resolve_future_values, update_tree, AttributeStorage, Components, EvalContext, Factory, + FloatingWidgets, Scope, WidgetKind, WidgetTree, }; -use events::EventHandler; +use events::{EventCtx, EventHandler}; use notify::{recommended_watcher, Event, RecommendedWatcher, RecursiveMode, Watcher}; +use tree::Tree; pub use crate::error::{Error, Result}; @@ -48,6 +49,7 @@ static REBUILD: AtomicBool = AtomicBool::new(false); mod error; mod events; +mod tree; pub struct RuntimeBuilder { document: Document, @@ -165,7 +167,6 @@ impl RuntimeBuilder { future_values: FutureValues::empty(), changes: Changes::empty(), - // tab_indices: TabIndices::new(), component_registry: self.component_registry, globals, document: self.document, @@ -307,13 +308,6 @@ where return; } - // use std::io::Write; - // let mut file = std::fs::OpenOptions::new() - // .append(true) - // .write(true) - // .open("/tmp/log.lol").unwrap(); - // file.write(format!("{}\n", self.changes.len()).as_bytes()).unwrap(); - let mut scope = Scope::new(); self.changes.drain().rev().for_each(|(sub, change)| { sub.iter().for_each(|sub| { @@ -347,37 +341,28 @@ where attribute_storage: &mut AttributeStorage<'bp>, assoc_events: &mut AssociatedEvents, ) -> Duration { + let context = UntypedContext { + emitter: &self.emitter, + viewport: self.viewport, + strings: &mut self.document.strings, + }; + + let mut event_ctx = EventCtx { + components: &mut self.components, + states, + attribute_storage, + assoc_events, + context, + }; + while let Ok(msg) = self.message_receiver.try_recv() { - if let Some((widget_id, state_id)) = self + if let Some((widget_id, state_id)) = event_ctx .components .get_by_component_id(msg.recipient()) .map(|e| (e.widget_id, e.state_id)) { - tree.with_value_mut(widget_id, |path, widget, tree| { - let WidgetKind::Component(component) = widget else { return }; - let state = states.get_mut(state_id); - - let parent = component - .parent - .and_then(|parent| self.components.get_by_component_id(parent)) - .map(|parent| parent.component_id.into()); - - let Some((node, values)) = tree.get_node_by_path(path) else { return }; - let elements = Elements::new(node.children(), values, attribute_storage); - - let context = UntypedContext { - emitter: &self.emitter, - viewport: self.viewport, - assoc_events, - state_id, - parent, - strings: &mut self.document.strings, - assoc_functions: component.assoc_functions, - }; - - component - .dyn_component - .any_message(msg.payload(), state, elements, context); + tree.with_component(widget_id, state_id, &mut event_ctx, |a, b| { + a.any_message(msg.payload(), b) }); } @@ -438,31 +423,27 @@ where } } - // Select the first widget - if let Some((widget_id, state_id)) = self.components.current() { - tree.with_value_mut(widget_id, |path, widget, tree| { - let WidgetKind::Component(component) = widget else { return }; - let state = states.get_mut(state_id); - - let parent = component - .parent - .and_then(|parent| self.components.get_by_component_id(parent)) - .map(|parent| parent.component_id.into()); - - let Some((node, values)) = tree.get_node_by_path(path) else { return }; - let elements = Elements::new(node.children(), values, &mut attribute_storage); - let context = UntypedContext { - emitter: &self.emitter, - viewport: self.viewport, - assoc_events: &mut assoc_events, - state_id, - parent, - strings: &mut self.document.strings, - assoc_functions: component.assoc_functions, - }; - component.dyn_component.any_focus(state, elements, context); - }); - } + // // Select the first widget + // if let Some((widget_id, state_id)) = self.components.current() { + // tree.with_value_mut(widget_id, |path, widget, tree| { + // let WidgetKind::Component(component) = widget else { return }; + // let state = states.get_mut(state_id); + + // let parent = component + // .parent + // .and_then(|parent| self.components.get_by_component_id(parent)) + // .map(|parent| parent.component_id.into()); + + // let Some((node, values)) = tree.get_node_by_path(path) else { return }; + // let elements = Elements::new(node.children(), values, &mut attribute_storage); + + // let component_ctx = ComponentContext::new(state_id, component.parent, component.assoc_functions); + + // component + // .dyn_component + // .any_focus(state, elements, context, component_ctx); + // }); + // } let mut dt = Instant::now(); loop { @@ -557,24 +538,34 @@ where // Clear the text buffer self.string_storage.clear(); + // Call the `tick` function on all components + self.tick_components(tree, states, attribute_storage, dt.elapsed(), assoc_events); + + let context = UntypedContext { + emitter: &self.emitter, + viewport: self.viewport, + strings: &self.document.strings, + }; + + let mut event_ctx = EventCtx { + components: &mut self.components, + states, + attribute_storage, + assoc_events, + context, + }; + self.event_handler.handle( poll_duration, fps_now, sleep_micros, &mut self.backend, &mut self.viewport, - &self.emitter, tree, - &mut self.components, - states, - attribute_storage, &mut self.constraints, - assoc_events, - &self.document.strings, + &mut event_ctx, )?; - // Call the `tick` function on all components - self.tick_components(tree, states, attribute_storage, dt.elapsed(), assoc_events); *dt = Instant::now(); self.apply_futures(globals, tree, states, attribute_storage); @@ -677,36 +668,27 @@ where dt: Duration, assoc_events: &mut AssociatedEvents, ) { + let context = UntypedContext { + emitter: &self.emitter, + viewport: self.viewport, + strings: &self.document.strings, + }; + for i in 0..self.components.len() { let (widget_id, state_id) = self .components .get(i) .expect("the components can not change as a result of this step"); - tree.with_value_mut(widget_id, |path, widget, tree| { - let WidgetKind::Component(component) = widget else { return }; - let state = states.get_mut(state_id); - - let parent = component - .parent - .and_then(|parent| self.components.get_by_component_id(parent)) - .map(|parent| parent.component_id.into()); - - let Some((node, values)) = tree.get_node_by_path(path) else { return }; - let elements = Elements::new(node.children(), values, attribute_storage); - - let context = UntypedContext { - emitter: &self.emitter, - viewport: self.viewport, - assoc_events, - state_id, - parent, - strings: &mut self.document.strings, - assoc_functions: component.assoc_functions, - }; - - component.dyn_component.any_tick(state, elements, context, dt); - }); + let mut event_ctx = EventCtx { + components: &mut self.components, + states, + attribute_storage, + assoc_events, + context, + }; + + tree.with_component(widget_id, state_id, &mut event_ctx, |a, b| a.any_tick(b, dt)); } } } diff --git a/anathema-store/src/tree/mod.rs b/anathema-store/src/tree/mod.rs index bede3e9c..b30e063a 100644 --- a/anathema-store/src/tree/mod.rs +++ b/anathema-store/src/tree/mod.rs @@ -211,25 +211,25 @@ impl Tree { /// Perform a given operation (`F`) on a reference to a value in the tree /// while still haveing mutable access to the rest of the tree. - pub fn with_value(&mut self, value_id: ValueId, mut f: F) -> R + pub fn with_value(&self, value_id: ValueId, mut f: F) -> Option where - F: FnMut(&[u16], &T, &mut Self) -> R, + F: FnMut(&[u16], &T, &Self) -> R, { - let ticket = self.values.checkout(value_id); - let ret = f(&ticket.value.0, &ticket.value.1, self); - self.values.restore(ticket); - ret + let value = self.values.get(value_id)?; + let ret = f(&value.0, &value.1, self); + Some(ret) } /// Perform a given operation (`F`) on a mutable reference to a value in the tree /// while still having mutable access to the rest of the tree. - pub fn with_value_mut(&mut self, value_id: ValueId, f: F) + pub fn with_value_mut(&mut self, value_id: ValueId, f: F) -> V where - F: FnOnce(&[u16], &mut T, &mut Self), + F: FnOnce(&[u16], &mut T, &mut Self) -> V, { let mut ticket = self.values.checkout(value_id); - f(&ticket.value.0, &mut ticket.value.1, self); + let value = f(&ticket.value.0, &mut ticket.value.1, self); self.values.restore(ticket); + value } /// Get mutable access to a node value along with the children diff --git a/anathema-widgets/src/components/mod.rs b/anathema-widgets/src/components/mod.rs index ab69d87a..3df6d190 100644 --- a/anathema-widgets/src/components/mod.rs +++ b/anathema-widgets/src/components/mod.rs @@ -164,11 +164,19 @@ impl Emitter { } } -pub struct Context<'rt, T>(UntypedContext<'rt>, PhantomData); +pub struct Context<'rt, T> { + inner: UntypedContext<'rt>, + _p: PhantomData, + component_ctx: ComponentContext<'rt>, +} impl<'rt, T: 'static> Context<'rt, T> { - fn new(context: UntypedContext<'rt>) -> Self { - Self(context, PhantomData) + fn new(context: UntypedContext<'rt>, component_ctx: ComponentContext<'rt>) -> Self { + Self { + inner: context, + _p: PhantomData, + component_ctx, + } } /// Publish event @@ -182,16 +190,16 @@ impl<'rt, T: 'static> Context<'rt, T> { F: FnMut(&T) -> &Value + 'static, V: AnyState, { - let Some(internal) = self.0.strings.lookup(ident) else { return }; + let Some(internal) = self.inner.strings.lookup(ident) else { return }; - let ids = self.0.assoc_functions.iter().find(|(i, _)| *i == internal); + let ids = self.component_ctx.assoc_functions.iter().find(|(i, _)| *i == internal); // If there is no parent there is no one to emit the event to. - let Some(parent) = self.0.parent else { return }; + let Some(parent) = self.component_ctx.parent else { return }; let Some((_, external)) = ids else { return }; - self.0.assoc_events.push( - self.0.state_id, + self.component_ctx.assoc_events.push( + self.component_ctx.state_id, parent, *external, Box::new(move |state: &dyn AnyState| -> SharedState<'_> { @@ -221,24 +229,53 @@ impl<'rt, T> Deref for Context<'rt, T> { type Target = UntypedContext<'rt>; fn deref(&self) -> &Self::Target { - &self.0 + &self.inner } } impl<'rt, T> DerefMut for Context<'rt, T> { fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 + &mut self.inner } } +pub struct LoLCtx<'state, 'tree, 'bp> { + pub state: Option<&'state mut dyn AnyState>, + pub elements: Elements<'tree, 'bp>, + pub context: UntypedContext<'tree>, + pub component_ctx: ComponentContext<'tree>, +} + +#[derive(Copy, Clone)] pub struct UntypedContext<'rt> { pub emitter: &'rt Emitter, pub viewport: Viewport, - pub assoc_events: &'rt mut AssociatedEvents, - pub state_id: StateId, - pub parent: Option, + // pub assoc_events: &'rt mut AssociatedEvents, pub strings: &'rt Strings, + // pub assoc_functions: &'rt [(StringId, StringId)], +} + +pub struct ComponentContext<'rt> { + pub parent: Option, + pub state_id: StateId, pub assoc_functions: &'rt [(StringId, StringId)], + pub assoc_events: &'rt mut AssociatedEvents, +} + +impl<'rt> ComponentContext<'rt> { + pub fn new( + state_id: StateId, + parent: Option, + assoc_functions: &'rt [(StringId, StringId)], + assoc_events: &'rt mut AssociatedEvents, + ) -> Self { + Self { + parent: parent.map(Into::into), + state_id, + assoc_functions, + assoc_events, + } + } } pub struct AssociatedEvent { @@ -383,44 +420,19 @@ pub enum ComponentKind { } pub trait AnyComponent { - fn any_event( - &mut self, - ev: Event, - state: Option<&mut dyn AnyState>, - elements: Elements<'_, '_>, - context: UntypedContext<'_>, - ) -> Event; + fn any_event(&mut self, ctx: LoLCtx<'_, '_, '_>, ev: Event) -> Event; - fn any_message( - &mut self, - message: Box, - state: Option<&mut dyn AnyState>, - elements: Elements<'_, '_>, - context: UntypedContext<'_>, - ); + fn any_message(&mut self, message: Box, ctx: LoLCtx<'_, '_, '_>); - fn any_tick( - &mut self, - state: Option<&mut dyn AnyState>, - elements: Elements<'_, '_>, - context: UntypedContext<'_>, - dt: Duration, - ); + fn any_tick(&mut self, ctx: LoLCtx<'_, '_, '_>, dt: Duration); - fn any_focus(&mut self, state: Option<&mut dyn AnyState>, elements: Elements<'_, '_>, context: UntypedContext<'_>); + fn any_focus(&mut self, ctx: LoLCtx<'_, '_, '_>); - fn any_blur(&mut self, state: Option<&mut dyn AnyState>, elements: Elements<'_, '_>, context: UntypedContext<'_>); + fn any_blur(&mut self, ctx: LoLCtx<'_, '_, '_>); - fn any_resize(&mut self, state: Option<&mut dyn AnyState>, elements: Elements<'_, '_>, context: UntypedContext<'_>); + fn any_resize(&mut self, ctx: LoLCtx<'_, '_, '_>); - fn any_receive( - &mut self, - state: Option<&mut dyn AnyState>, - name: &str, - value: CommonVal<'_>, - elements: Elements<'_, '_>, - context: UntypedContext<'_>, - ); + fn any_receive(&mut self, ctx: LoLCtx<'_, '_, '_>, name: &str, value: CommonVal<'_>); fn accept_focus_any(&self) -> bool; } @@ -430,22 +442,17 @@ where T: Component, T: 'static, { - fn any_event( - &mut self, - event: Event, - state: Option<&mut dyn AnyState>, - widgets: Elements<'_, '_>, - context: UntypedContext<'_>, - ) -> Event { - let state = state + fn any_event(&mut self, ctx: LoLCtx<'_, '_, '_>, event: Event) -> Event { + let state = ctx + .state .and_then(|s| s.to_any_mut().downcast_mut::()) .expect("components always have a state"); - let context = Context::::new(context); + let context = Context::::new(ctx.context, ctx.component_ctx); match event { Event::Blur | Event::Focus => (), // Application focus, not component focus. - Event::Key(ev) => self.on_key(ev, state, widgets, context), - Event::Mouse(ev) => self.on_mouse(ev, state, widgets, context), + Event::Key(ev) => self.on_key(ev, state, ctx.elements, context), + Event::Mouse(ev) => self.on_mouse(ev, state, ctx.elements, context), Event::Resize(_, _) | Event::Noop | Event::Stop => (), } @@ -456,79 +463,61 @@ where self.accept_focus() } - fn any_message( - &mut self, - message: Box, - state: Option<&mut dyn AnyState>, - elements: Elements<'_, '_>, - context: UntypedContext<'_>, - ) { - let state = state + fn any_message(&mut self, message: Box, ctx: LoLCtx<'_, '_, '_>) { + let state = ctx + .state .and_then(|s| s.to_any_mut().downcast_mut::()) .expect("components always have a state"); let Ok(message) = message.downcast::() else { return }; - let context = Context::::new(context); - self.message(*message, state, elements, context); + let context = Context::::new(ctx.context, ctx.component_ctx); + self.message(*message, state, ctx.elements, context); } - fn any_focus(&mut self, state: Option<&mut dyn AnyState>, elements: Elements<'_, '_>, context: UntypedContext<'_>) { - let state = state + fn any_focus(&mut self, ctx: LoLCtx<'_, '_, '_>) { + let state = ctx + .state .and_then(|s| s.to_any_mut().downcast_mut::()) .expect("components always have a state"); - let context = Context::::new(context); - self.on_focus(state, elements, context); + let context = Context::::new(ctx.context, ctx.component_ctx); + self.on_focus(state, ctx.elements, context); } - fn any_blur(&mut self, state: Option<&mut dyn AnyState>, elements: Elements<'_, '_>, context: UntypedContext<'_>) { - let state = state + fn any_blur(&mut self, ctx: LoLCtx<'_, '_, '_>) { + let state = ctx + .state .and_then(|s| s.to_any_mut().downcast_mut::()) .expect("components always have a state"); - let context = Context::::new(context); - self.on_blur(state, elements, context); + let context = Context::::new(ctx.context, ctx.component_ctx); + self.on_blur(state, ctx.elements, context); } - fn any_tick( - &mut self, - state: Option<&mut dyn AnyState>, - elements: Elements<'_, '_>, - context: UntypedContext<'_>, - dt: Duration, - ) { - let state = state + fn any_tick(&mut self, ctx: LoLCtx<'_, '_, '_>, dt: Duration) { + let state = ctx + .state .and_then(|s| s.to_any_mut().downcast_mut::()) .expect("components always have a state"); - let context = Context::::new(context); - self.tick(state, elements, context, dt); + let context = Context::::new(ctx.context, ctx.component_ctx); + self.tick(state, ctx.elements, context, dt); } - fn any_resize( - &mut self, - state: Option<&mut dyn AnyState>, - elements: Elements<'_, '_>, - context: UntypedContext<'_>, - ) { - let state = state + fn any_resize(&mut self, ctx: LoLCtx<'_, '_, '_>) { + let state = ctx + .state .and_then(|s| s.to_any_mut().downcast_mut::()) .expect("components always have a state"); - let context = Context::::new(context); - self.resize(state, elements, context); + let context = Context::::new(ctx.context, ctx.component_ctx); + self.resize(state, ctx.elements, context); } - fn any_receive( - &mut self, - state: Option<&mut dyn AnyState>, - name: &str, - value: CommonVal<'_>, - elements: Elements<'_, '_>, - context: UntypedContext<'_>, - ) { - let state = state + fn any_receive(&mut self, ctx: LoLCtx<'_, '_, '_>, name: &str, value: CommonVal<'_>) { + let state = ctx + .state .and_then(|s| s.to_any_mut().downcast_mut::()) .expect("components always have a state"); - let context = Context::::new(context); + let context = Context::::new(ctx.context, ctx.component_ctx); - self.receive(name, value, state, elements, context); + self.receive(name, value, state, ctx.elements, context); } } diff --git a/anathema-widgets/src/nodes/eval.rs b/anathema-widgets/src/nodes/eval.rs index 4ef11381..c2f99464 100644 --- a/anathema-widgets/src/nodes/eval.rs +++ b/anathema-widgets/src/nodes/eval.rs @@ -161,7 +161,7 @@ impl ForLoopEval { .ok_or(Error::TreeTransactionFailed)?; // Scope the iteration value - tree.with_value(iter_id, |parent, widget, tree| { + tree.with_value_mut(iter_id, |parent, widget, tree| { let WidgetKind::Iteration(iter) = widget else { unreachable!() }; ctx.scope.scope_pending(LOOP_INDEX, iter.loop_index.to_pending()); @@ -201,7 +201,7 @@ impl Evaluator for ForLoopEval { let for_loop_id = transaction.commit_child(widget).ok_or(Error::TreeTransactionFailed)?; - tree.with_value(for_loop_id, move |parent, widget, tree| { + tree.with_value_mut(for_loop_id, move |parent, widget, tree| { let WidgetKind::For(for_loop) = widget else { unreachable!() }; self.eval_body(for_loop, ctx, parent, tree)?; Ok(()) @@ -228,7 +228,7 @@ impl Evaluator for ControlFlowEval { let for_loop_id = transaction.commit_child(widget).ok_or(Error::TreeTransactionFailed)?; let parent = tree.path(for_loop_id); - tree.with_value(for_loop_id, move |parent, _widget, tree| { + tree.with_value_mut(for_loop_id, move |parent, _widget, tree| { IfEval.eval(&control_flow.if_node, ctx, parent, tree)?; control_flow .elses @@ -387,7 +387,7 @@ impl Evaluator for ComponentEval { let path = tree.path(widget_id); ctx.components.push(path, widget_id, state_id, component_id); - tree.with_value(widget_id, move |parent, widget, tree| { + tree.with_value_mut(widget_id, move |parent, widget, tree| { let WidgetKind::Component(component) = widget else { unreachable!() }; ctx.scope.push(); diff --git a/anathema-widgets/src/nodes/future.rs b/anathema-widgets/src/nodes/future.rs index f29ece6f..02034201 100644 --- a/anathema-widgets/src/nodes/future.rs +++ b/anathema-widgets/src/nodes/future.rs @@ -163,7 +163,7 @@ fn try_resolve_value<'bp>( .ok_or(Error::TreeTransactionFailed)?; // Scope the iteration value - tree.with_value(iter_id, |parent, widget, tree| { + tree.with_value_mut(iter_id, |parent, widget, tree| { let WidgetKind::Iteration(iter) = widget else { unreachable!() }; ctx.scope.scope_pending(LOOP_INDEX, iter.loop_index.to_pending()); diff --git a/anathema-widgets/src/nodes/loops.rs b/anathema-widgets/src/nodes/loops.rs index fe16eada..96095543 100644 --- a/anathema-widgets/src/nodes/loops.rs +++ b/anathema-widgets/src/nodes/loops.rs @@ -64,7 +64,7 @@ impl<'bp> For<'bp> { *iter.loop_index.to_mut() += 1; }); - tree.with_value(iter_id, |parent, iter_widget, tree| { + tree.with_value_mut(iter_id, |parent, iter_widget, tree| { // NOTE // The value has to be scoped to the current binding and not // the iteration, since the collection might've changed more than once @@ -128,7 +128,7 @@ impl<'bp> For<'bp> { .ok_or(Error::TreeTransactionFailed)?; // Scope the iteration value - tree.with_value(iter_id, |parent, widget, tree| -> Result<()> { + tree.with_value_mut(iter_id, |parent, widget, tree| -> Result<()> { let WidgetKind::Iteration(iter) = widget else { unreachable!() }; ctx.scope.scope_pending(LOOP_INDEX, iter.loop_index.to_pending()); diff --git a/anathema-widgets/src/widget/mod.rs b/anathema-widgets/src/widget/mod.rs index 102797c1..6157cb28 100644 --- a/anathema-widgets/src/widget/mod.rs +++ b/anathema-widgets/src/widget/mod.rs @@ -54,7 +54,7 @@ impl PartialEq for CompEntry { } pub struct Components { - tab_index: usize, + pub tab_index: usize, inner: SortedList, comp_ids: SmallMap, } @@ -68,25 +68,32 @@ impl Components { } } - pub fn next(&mut self) -> usize { - let prev = self.tab_index; - if self.tab_index == self.inner.len() - 1 { - self.tab_index = 0; - } else { - self.tab_index += 1; - } - prev - } - - pub fn prev(&mut self) -> usize { - let prev = self.tab_index; - if self.tab_index == 0 { - self.tab_index = self.inner.len() - 1; - } else { - self.tab_index -= 1; - } - prev - } + // pub fn next(&mut self, tree: &WidgetTree<'_>) -> usize { + // let Some((widget_id, _)) = self.get(self.tab_index) else { panic!() }; + + // let val = tree.with_value(widget_id, |path, widget, tree| { + // let WidgetKind::Component(component) = widget else { return false }; + // component.dyn_component.accept_focus_any() + // }).unwrap_or(false); + + // let prev = self.tab_index; + // if self.tab_index == self.inner.len() - 1 { + // self.tab_index = 0; + // } else { + // self.tab_index += 1; + // } + // prev + // } + + // pub fn prev(&mut self) -> usize { + // let prev = self.tab_index; + // if self.tab_index == 0 { + // self.tab_index = self.inner.len() - 1; + // } else { + // self.tab_index -= 1; + // } + // prev + // } pub fn push(&mut self, path: Box<[u16]>, widget_id: WidgetId, state_id: StateId, component_id: WidgetComponentId) { let entry = CompEntry { From 9c9824714483476ff134af43fc86214e9aa4c87a Mon Sep 17 00:00:00 2001 From: FishingHacks Date: Sat, 31 Aug 2024 21:49:47 +0200 Subject: [PATCH 06/13] merge to latest branch of dev --- anathema-backend/src/lib.rs | 7 ++----- anathema-backend/src/tui/mod.rs | 6 +----- anathema-runtime/src/lib.rs | 28 ++++++++++++++++++++----- anathema-store/src/slab/generational.rs | 3 +++ anathema-store/src/stack.rs | 2 +- 5 files changed, 30 insertions(+), 16 deletions(-) diff --git a/anathema-backend/src/lib.rs b/anathema-backend/src/lib.rs index 85f25328..746f6cc6 100644 --- a/anathema-backend/src/lib.rs +++ b/anathema-backend/src/lib.rs @@ -19,7 +19,7 @@ pub trait Backend { fn resize(&mut self, new_size: Size); - /// Paint an internal buffer. This should not change the screen. + /// Paint the widgets fn paint<'bp>( &mut self, element: &mut Element<'bp>, @@ -36,9 +36,6 @@ pub trait Backend { /// Clear the internal buffer entirely. This should not change the screen. fn clear(&mut self); - /// Finalizes the backend. This is called just before we start running the anathema main loop. - /// This should set the terminal in a state where its usable for the tui, for example by enabling events. - /// - /// There is no guarantee that other functions will be called before this. They should not cause a panic. + /// Finalizes the backend. This is called when the runtime starts. fn finalize(&mut self) {} } diff --git a/anathema-backend/src/tui/mod.rs b/anathema-backend/src/tui/mod.rs index f7e9c87e..30b54844 100644 --- a/anathema-backend/src/tui/mod.rs +++ b/anathema-backend/src/tui/mod.rs @@ -38,28 +38,24 @@ pub struct TuiBackendBuilder { } impl TuiBackendBuilder { - // Note: This will only apply when TuiBackend::finalize is called. /// Enable an alternative screen. When using this with stdout it means the output will not persist once the program exits. This won't apply until Runtime::run is called. pub fn enable_alt_screen(mut self) -> Self { self.enable_alt_screen = true; self } - // Note: This will only apply when TuiBackend::finalize is called. /// Enable mouse support. This won't apply until Runtime::run is called. pub fn enable_mouse(mut self) -> Self { self.enable_mouse = true; self } - // Note: This will only apply when TuiBackend::finalize is called. - /// Enable raw mode: input will not be forwarded to the screen. This won't apply until Runtime::run is called. + /// When raw mode is enabled, every key press is sent to the terminal. If raw mode is not enabled, the return key has to be pressed to send characters to the terminal. This won't apply until Runtime::run is called. pub fn enable_raw_mode(mut self) -> Self { self.enable_raw_mode = true; self } - // Note: This will only apply when TuiBackend::finalize is called. /// Hide the text cursor. This won't apply until Runtime::run is called. pub fn hide_cursor(mut self) -> Self { self.hide_cursor = true; diff --git a/anathema-runtime/src/lib.rs b/anathema-runtime/src/lib.rs index b5f53e05..8891ec36 100644 --- a/anathema-runtime/src/lib.rs +++ b/anathema-runtime/src/lib.rs @@ -36,8 +36,8 @@ use anathema_widgets::components::{ use anathema_widgets::layout::text::StringStorage; use anathema_widgets::layout::{layout_widget, position_widget, Constraints, LayoutCtx, LayoutFilter, Viewport}; use anathema_widgets::{ - eval_blueprint, try_resolve_future_values, update_tree, AttributeStorage, Components, EvalContext, Factory, - FloatingWidgets, Scope, WidgetKind, WidgetTree, + eval_blueprint, try_resolve_future_values, update_tree, AttributeStorage, Components, Elements, EvalContext, + Factory, FloatingWidgets, Scope, WidgetKind, WidgetTree, }; use events::{EventCtx, EventHandler}; use notify::{recommended_watcher, Event, RecommendedWatcher, RecursiveMode, Watcher}; @@ -77,8 +77,8 @@ impl RuntimeBuilder { Ok(id.into()) } - /// Registers a [Component] as a prototype to the [Runtime], which allows the usage of multiple instances of the component in the templates. - /// This is useful if you want to reuse the component (Such as an input or button component) or if you use your component in if-blocks or for loops. + /// Registers a [Component] as a prototype with the [Runtime], which allows the usage of multiple instances of the component in the templates. + /// This is useful if for reuse of the component. /// If you don't need that, consider using [RuntimeBuilder::register_component] or [RuntimeBuilder::register_default] instead. /// Unlike when registering a component, this won't return a [ComponentId] because there's no meaningful way to express which component the message would go to. pub fn register_prototype( @@ -323,6 +323,13 @@ where return; } + // use std::io::Write; + // let mut file = std::fs::OpenOptions::new() + // .append(true) + // .write(true) + // .open("/tmp/log.lol").unwrap(); + // file.write(format!("{}\n", self.changes.len()).as_bytes()).unwrap(); + let mut scope = Scope::new(); self.changes.drain().rev().for_each(|(sub, change)| { sub.iter().for_each(|sub| { @@ -347,6 +354,7 @@ where }); } + /// Handles component messages for (ideally) at most half of a tick fn handle_messages<'bp>( &mut self, fps_now: Instant, @@ -402,6 +410,11 @@ where } } + /// 1 - Tries to build the first widget tree or throws an error + /// 2 - Selects the first [Component] and calls [Component::on_focus] on it + /// 3 - Repeatedly calls [Self::tick] until [REBUILD] is set to true or any error occured. Using the [Error::Stop], we exit the main loop. + /// 4 - Resets itself using [Self::reset] + /// 5 - Recursively calls [Self::internal_run]. Note: This does not free up the call stack. We should move this into a loop in [Self::run]. fn internal_run(&mut self) -> Result<()> { let mut fps_now = Instant::now(); let sleep_micros = ((1.0 / self.fps as f64) * 1000.0 * 1000.0) as u128; @@ -505,6 +518,11 @@ where self.globals = globals; } + /// Resets the Runtime: + /// - Throws away all futures, pending changes and value subscribers + /// - Reloads all components + /// - Moves all the components from the tree back to the registry. + /// - Recompiles the document fn reset(&mut self, tree: WidgetTree<'_>, states: &mut States) -> Result<()> { clear_all_futures(); clear_all_changes(); @@ -515,7 +533,7 @@ where self.string_storage = StringStorage::new(); // The only way we can get here is if we break the loop - // as a result of the hot_reload triggering. + // as a result of the hot_reload triggering or when building the first tree fails. self.document.reload_templates()?; // move all components from the tree back to the registry. diff --git a/anathema-store/src/slab/generational.rs b/anathema-store/src/slab/generational.rs index 69ac7b39..2909fbf1 100644 --- a/anathema-store/src/slab/generational.rs +++ b/anathema-store/src/slab/generational.rs @@ -33,6 +33,9 @@ impl From for Gen { /// A key is a combination of an index and a generation. /// To access a value using a key the value at the given index /// has to have a matching generation. +/// +/// Bits 0..48: 48-bit key +/// Bits 48..64 are the 16-bit generation #[derive(Copy, Clone, PartialEq, Hash, Eq)] pub struct Key(u64); diff --git a/anathema-store/src/stack.rs b/anathema-store/src/stack.rs index 2df5b5a4..00f4dc7e 100644 --- a/anathema-store/src/stack.rs +++ b/anathema-store/src/stack.rs @@ -29,7 +29,7 @@ impl Entry { } /// Allocate memory but never free it until the entire `Stack` is dropped. -/// Items popped from the stack are marked as `Empty` so the memory is resused. +/// Items popped from the stack are marked as `Empty` so the memory is reused. #[derive(Debug, Default)] pub struct Stack { inner: Vec>, From 72628fd09d461f74434572ff3eb163201eaf8773 Mon Sep 17 00:00:00 2001 From: FishingHacks Date: Sat, 31 Aug 2024 21:57:43 +0200 Subject: [PATCH 07/13] Update anathema-backend/src/tui/screen.rs Co-authored-by: Togglebit <74262215+togglebyte@users.noreply.github.com> --- anathema-backend/src/tui/screen.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/anathema-backend/src/tui/screen.rs b/anathema-backend/src/tui/screen.rs index 47c64b52..2c1a6a47 100644 --- a/anathema-backend/src/tui/screen.rs +++ b/anathema-backend/src/tui/screen.rs @@ -21,7 +21,7 @@ pub struct Screen { impl Screen { /// Hide the cursor pub(super) fn hide_cursor(mut output: impl Write) -> Result<()> { - output.execute(cursor::Hide)?; + output.queue(cursor::Hide)?; Ok(()) } From e8e68c1e86e90ba2c95296fd8a88a00dd7f2e4d8 Mon Sep 17 00:00:00 2001 From: FishingHacks Date: Sat, 31 Aug 2024 21:57:50 +0200 Subject: [PATCH 08/13] Update anathema-runtime/src/lib.rs Co-authored-by: Togglebit <74262215+togglebyte@users.noreply.github.com> --- anathema-runtime/src/lib.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/anathema-runtime/src/lib.rs b/anathema-runtime/src/lib.rs index 8891ec36..0109b4bb 100644 --- a/anathema-runtime/src/lib.rs +++ b/anathema-runtime/src/lib.rs @@ -79,8 +79,6 @@ impl RuntimeBuilder { /// Registers a [Component] as a prototype with the [Runtime], which allows the usage of multiple instances of the component in the templates. /// This is useful if for reuse of the component. - /// If you don't need that, consider using [RuntimeBuilder::register_component] or [RuntimeBuilder::register_default] instead. - /// Unlike when registering a component, this won't return a [ComponentId] because there's no meaningful way to express which component the message would go to. pub fn register_prototype( &mut self, ident: impl Into, From d2b410aed8401e24284ed8d069620b4e9c371a91 Mon Sep 17 00:00:00 2001 From: FishingHacks Date: Sat, 31 Aug 2024 21:57:59 +0200 Subject: [PATCH 09/13] Update anathema-backend/src/lib.rs Co-authored-by: Togglebit <74262215+togglebyte@users.noreply.github.com> --- anathema-backend/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/anathema-backend/src/lib.rs b/anathema-backend/src/lib.rs index 746f6cc6..d24a66b4 100644 --- a/anathema-backend/src/lib.rs +++ b/anathema-backend/src/lib.rs @@ -12,7 +12,6 @@ pub mod tui; pub trait Backend { fn size(&self) -> Size; - /// When [Backend::next_event] returns [Event::Stop], this function will be called to make sure the Backend wants anathema exit. fn quit_test(&self, event: Event) -> bool; fn next_event(&mut self, timeout: Duration) -> Option; From a2388577672b0e3c796f90035ee78571fcab82dd Mon Sep 17 00:00:00 2001 From: FishingHacks Date: Sat, 31 Aug 2024 21:58:15 +0200 Subject: [PATCH 10/13] Update anathema-backend/src/tui/screen.rs Co-authored-by: Togglebit <74262215+togglebyte@users.noreply.github.com> --- anathema-backend/src/tui/screen.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/anathema-backend/src/tui/screen.rs b/anathema-backend/src/tui/screen.rs index 2c1a6a47..f875eca7 100644 --- a/anathema-backend/src/tui/screen.rs +++ b/anathema-backend/src/tui/screen.rs @@ -39,7 +39,7 @@ impl Screen { /// Enable mouse support pub(super) fn enable_mouse(mut output: impl Write) -> Result<()> { - output.execute(EnableMouseCapture)?; + output.queue(EnableMouseCapture)?; Ok(()) } From 2f532ebc45610f0012ce0bc145b336defa40ef50 Mon Sep 17 00:00:00 2001 From: FishingHacks Date: Sat, 31 Aug 2024 21:58:44 +0200 Subject: [PATCH 11/13] Update anathema-backend/src/tui/screen.rs Co-authored-by: Togglebit <74262215+togglebyte@users.noreply.github.com> --- anathema-backend/src/tui/screen.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/anathema-backend/src/tui/screen.rs b/anathema-backend/src/tui/screen.rs index f875eca7..9a60f7b5 100644 --- a/anathema-backend/src/tui/screen.rs +++ b/anathema-backend/src/tui/screen.rs @@ -27,7 +27,7 @@ impl Screen { /// Show the cursor pub(super) fn show_cursor(mut output: impl Write) -> Result<()> { - output.execute(cursor::Show)?; + output.queue(cursor::Show)?; Ok(()) } From 56d02a44c3c0c554be637eccff3a0dca7c9ff84f Mon Sep 17 00:00:00 2001 From: FishingHacks Date: Sat, 31 Aug 2024 22:03:17 +0200 Subject: [PATCH 12/13] please work this time --- anathema-backend/src/tui/mod.rs | 6 +++--- anathema-backend/src/tui/screen.rs | 6 ------ anathema-store/src/slab/generational.rs | 2 +- 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/anathema-backend/src/tui/mod.rs b/anathema-backend/src/tui/mod.rs index 3f3291ce..f8b3f0ca 100644 --- a/anathema-backend/src/tui/mod.rs +++ b/anathema-backend/src/tui/mod.rs @@ -43,19 +43,19 @@ impl TuiBackendBuilder { self.enable_alt_screen = true; self } - + /// Enable mouse support. This won't apply until Runtime::run is called. pub fn enable_mouse(mut self) -> Self { self.enable_mouse = true; self } - + /// When raw mode is enabled, every key press is sent to the terminal. If raw mode is not enabled, the return key has to be pressed to send characters to the terminal. This won't apply until Runtime::run is called. pub fn enable_raw_mode(mut self) -> Self { self.enable_raw_mode = true; self } - + /// Hide the text cursor. This won't apply until Runtime::run is called. pub fn hide_cursor(mut self) -> Self { self.hide_cursor = true; diff --git a/anathema-backend/src/tui/screen.rs b/anathema-backend/src/tui/screen.rs index 9a60f7b5..73c35898 100644 --- a/anathema-backend/src/tui/screen.rs +++ b/anathema-backend/src/tui/screen.rs @@ -31,12 +31,6 @@ impl Screen { Ok(()) } - /// Show the cursor - pub(super) fn show_cursor(mut output: impl Write) -> Result<()> { - output.queue(cursor::Show)?; - Ok(()) - } - /// Enable mouse support pub(super) fn enable_mouse(mut output: impl Write) -> Result<()> { output.queue(EnableMouseCapture)?; diff --git a/anathema-store/src/slab/generational.rs b/anathema-store/src/slab/generational.rs index 2909fbf1..2cfe4645 100644 --- a/anathema-store/src/slab/generational.rs +++ b/anathema-store/src/slab/generational.rs @@ -33,7 +33,7 @@ impl From for Gen { /// A key is a combination of an index and a generation. /// To access a value using a key the value at the given index /// has to have a matching generation. -/// +/// /// Bits 0..48: 48-bit key /// Bits 48..64 are the 16-bit generation #[derive(Copy, Clone, PartialEq, Hash, Eq)] From d2f8c50905efd4c920b583af1d6abd2f5a78308b Mon Sep 17 00:00:00 2001 From: FishingHacks Date: Sat, 31 Aug 2024 22:14:28 +0200 Subject: [PATCH 13/13] fixed unused import for elements --- anathema-runtime/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/anathema-runtime/src/lib.rs b/anathema-runtime/src/lib.rs index 0109b4bb..a6d50d82 100644 --- a/anathema-runtime/src/lib.rs +++ b/anathema-runtime/src/lib.rs @@ -36,7 +36,7 @@ use anathema_widgets::components::{ use anathema_widgets::layout::text::StringStorage; use anathema_widgets::layout::{layout_widget, position_widget, Constraints, LayoutCtx, LayoutFilter, Viewport}; use anathema_widgets::{ - eval_blueprint, try_resolve_future_values, update_tree, AttributeStorage, Components, Elements, EvalContext, + eval_blueprint, try_resolve_future_values, update_tree, AttributeStorage, Components, EvalContext, Factory, FloatingWidgets, Scope, WidgetKind, WidgetTree, }; use events::{EventCtx, EventHandler};