From db1de03fbb0f1b44b18cb110d5dea658552d2573 Mon Sep 17 00:00:00 2001 From: Leo dev Date: Mon, 4 Aug 2025 04:05:38 -0500 Subject: [PATCH 1/9] Refactored widget #26 --- src/widget.rs | 91 +++++++++++++++++++++++++++------------------------ 1 file changed, 48 insertions(+), 43 deletions(-) diff --git a/src/widget.rs b/src/widget.rs index 4d4cd20..984dda5 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -66,19 +66,22 @@ pub trait Component: Send + Sync { pub struct WidgetLoad(BoxedElement, HashMap); /// A widget with fixed content and no dynamic behavior. -pub struct StaticWidget(Mutex, Mutex>); +pub struct StaticWidget { + element: Mutex, + components: Mutex>, +} /// A widget with dynamic content and dependency tracking. /// /// This widget supports reactive updates and can be rebuilt using /// a provided `FnMut()` function when dependencies change. -pub struct DynWidget( - Mutex, - Mutex>, - Mutex WidgetLoad + Send + Sync>>, - Mutex>>, - Mutex WidgetLoad + Send + Sync>>>, -); +pub struct DynWidget { + element: Mutex, + components: Mutex>, + load: Mutex WidgetLoad + Send + Sync>>, + dependencies: Mutex>>, + injection: Mutex WidgetLoad + Send + Sync>>>, +} /// A reference-counted wrapper around either a static or dynamic widget. /// @@ -117,18 +120,11 @@ impl WidgetLoad { impl Widget { pub fn new_static(e: BoxedElement) -> Self { - Self::Static(StaticWidget(Mutex::new(e), Mutex::new(HashMap::new()))) + Self::Static(StaticWidget::new(e)) } - pub fn new_dyn WidgetLoad + 'static + Send + Sync>(mut e: F) -> Self { - let wl = e(); - Self::Dynamic(DynWidget( - Mutex::new(wl.0), - Mutex::new(wl.1), - Mutex::new(Box::new(e)), - Mutex::new(Vec::new()), - Mutex::new(None), - )) + pub fn new_dyn WidgetLoad + 'static + Send + Sync>(e: F) -> Self { + Self::Dynamic(DynWidget::new(e)) } } @@ -188,7 +184,7 @@ impl Widget { } Widget::Static(w) => { let wl = WidgetLoad::new(String::new()); - w.1.lock().unwrap().extend(f(wl).1); + w.components.lock().unwrap().extend(f(wl).1); } } } @@ -250,17 +246,20 @@ impl Widget { impl StaticWidget { fn after_render(&self) {} fn get_elem(&self) -> MutexGuard { - self.0.lock().unwrap() + self.element.lock().unwrap() } } impl StaticWidget { pub fn new(e: BoxedElement) -> Self { - Self(Mutex::new(e), Mutex::new(HashMap::new())) + Self { + element: Mutex::new(e), + components: Mutex::new(HashMap::new()), + } } pub fn component(&self, c: C) { - self.1 + self.components .lock() .unwrap() .entry(c.type_id()) @@ -268,11 +267,14 @@ impl StaticWidget { } pub fn set_component(&self, c: C) { - self.1.lock().unwrap().insert(c.type_id(), Box::new(c)); + self.components + .lock() + .unwrap() + .insert(c.type_id(), Box::new(c)); } pub fn get(&self) -> Option { - self.1 + self.components .lock() .unwrap() .get(&TypeId::of::()) @@ -284,43 +286,43 @@ impl StaticWidget { impl DynWidget { fn after_render(&self) {} fn get_elem(&self) -> MutexGuard { - self.0.lock().unwrap() + self.element.lock().unwrap() } } impl DynWidget { pub fn new WidgetLoad + 'static + Send + Sync>(mut e: F) -> Self { let wl = e(); - Self( - Mutex::new(wl.0), - Mutex::new(wl.1), - Mutex::new(Box::new(e)), - Mutex::new(Vec::new()), - Mutex::new(None), - ) + Self { + element: Mutex::new(wl.0), + components: Mutex::new(wl.1), + load: Mutex::new(Box::new(e)), + dependencies: Mutex::new(Vec::new()), + injection: Mutex::new(None), + } } /// Replace or modify the widget's structure on reload and init. pub fn inject WidgetLoad + 'static + Send + Sync>(&self, f: F) { - *self.4.lock().unwrap() = Some(Box::new(f)); + *self.injection.lock().unwrap() = Some(Box::new(f)); self.refresh(); } /// Rebuild the widget's content by re-evaluating the original function. pub fn refresh(&self) { - let mut w = (self.2.lock().unwrap())(); + let mut w = (self.load.lock().unwrap())(); - if let Some(we) = &mut *self.4.lock().unwrap() { + if let Some(we) = &mut *self.injection.lock().unwrap() { w = (we)(w); } - *self.0.lock().unwrap() = w.0; - *self.1.lock().unwrap() = w.1; + *self.element.lock().unwrap() = w.0; + *self.components.lock().unwrap() = w.1; } /// Re-evaluates the widget if any dependency has changed. pub fn auto_refresh(&self) { - for d in self.3.lock().unwrap().iter() { + for d in self.dependencies.lock().unwrap().iter() { if d.check() { self.refresh(); } @@ -330,17 +332,17 @@ impl DynWidget { /// Adds a dependency to this widget. pub fn dependency(&self, d: D) { d.add(); - self.3.lock().unwrap().push(Box::new(d)); + self.dependencies.lock().unwrap().push(Box::new(d)); } /// Adds a boxed dependency. pub fn dependency_box(&self, d: Box) { d.add(); - self.3.lock().unwrap().push(d); + self.dependencies.lock().unwrap().push(d); } pub fn component(&self, c: C) { - self.1 + self.components .lock() .unwrap() .entry(c.type_id()) @@ -348,11 +350,14 @@ impl DynWidget { } pub fn set_component(&self, c: C) { - self.1.lock().unwrap().insert(c.type_id(), Box::new(c)); + self.components + .lock() + .unwrap() + .insert(c.type_id(), Box::new(c)); } pub fn get(&self) -> Option { - self.1 + self.components .lock() .unwrap() .get(&TypeId::of::()) From fbd84f98d303a4c02fca81fee463053b13b71385 Mon Sep 17 00:00:00 2001 From: Leo dev Date: Mon, 4 Aug 2025 04:17:40 -0500 Subject: [PATCH 2/9] Added focus on widget #26 --- src/widget.rs | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/src/widget.rs b/src/widget.rs index 984dda5..2a2de8e 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -69,6 +69,7 @@ pub struct WidgetLoad(BoxedElement, HashMap); pub struct StaticWidget { element: Mutex, components: Mutex>, + focused: Mutex, } /// A widget with dynamic content and dependency tracking. @@ -81,6 +82,7 @@ pub struct DynWidget { load: Mutex WidgetLoad + Send + Sync>>, dependencies: Mutex>>, injection: Mutex WidgetLoad + Send + Sync>>>, + focused: Mutex, } /// A reference-counted wrapper around either a static or dynamic widget. @@ -129,6 +131,20 @@ impl Widget { } impl Widget { + pub fn is_focused(&self) -> bool { + match self { + Self::Dynamic(w) => *w.focused.lock().unwrap(), + Self::Static(w) => *w.focused.lock().unwrap(), + } + } + + pub fn set_focused(&self, f: bool) { + match self { + Self::Dynamic(w) => *w.focused.lock().unwrap() = f, + Self::Static(w) => *w.focused.lock().unwrap() = f, + } + } + pub fn get_elem(&self) -> MutexGuard { match self { Widget::Static(w) => w.get_elem(), @@ -232,12 +248,14 @@ impl Widget { wrapper.call(self, e); } - match &**self { - Widget::Dynamic(w) => { - w.get_elem().event(e); - } - Widget::Static(w) => { - w.get_elem().event(e); + if self.is_focused() { + match &**self { + Widget::Dynamic(w) => { + w.get_elem().event(e); + } + Widget::Static(w) => { + w.get_elem().event(e); + } } } } @@ -255,6 +273,7 @@ impl StaticWidget { Self { element: Mutex::new(e), components: Mutex::new(HashMap::new()), + focused: Mutex::new(false), } } @@ -299,6 +318,7 @@ impl DynWidget { load: Mutex::new(Box::new(e)), dependencies: Mutex::new(Vec::new()), injection: Mutex::new(None), + focused: Mutex::new(false), } } From 14602f95c8d4e7976afc5feea4fe92d1b2755668 Mon Sep 17 00:00:00 2001 From: Leo dev Date: Mon, 4 Aug 2025 04:24:41 -0500 Subject: [PATCH 3/9] First widget focus #26 --- src/lib.rs | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 2eab099..135fbc5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -108,31 +108,22 @@ impl Screen { /// Draws a static element and returns its widget handle. pub fn draw(self: &Arc, element: E) -> Arc { - let w = Arc::new(Widget::Static(StaticWidget::new(Box::new(element)))); - self.widgets.lock().unwrap().push(w.clone()); - w + self.draw_box(Box::new(element)) } /// Draws a boxed element and returns its widget handle. pub fn draw_box(self: &Arc, element: BoxedElement) -> Arc { let w = Arc::new(Widget::Static(StaticWidget::new(element))); - self.widgets.lock().unwrap().push(w.clone()); + self.draw_widget(w.clone()); w } - /// Adds an existing widget to the screen. - pub fn draw_widget(self: &Arc, widget: Arc) { - self.widgets.lock().unwrap().push(widget); - } - /// Draws a dynamic element using a closure and returns its widget handle. pub fn draw_dyn WidgetLoad + 'static + Send + Sync>( self: &Arc, element: F, ) -> Arc { - let w = Arc::new(Widget::Dynamic(DynWidget::new(element))); - self.widgets.lock().unwrap().push(w.clone()); - w + self.draw_box_dyn(Box::new(element)) } /// Draws a dynamic element from a boxed closure and returns its widget handle. @@ -141,10 +132,20 @@ impl Screen { element: Box WidgetLoad + Send + Sync>, ) -> Arc { let w = Arc::new(Widget::Dynamic(DynWidget::new(element))); - self.widgets.lock().unwrap().push(w.clone()); + self.draw_widget(w.clone()); w } + /// Adds an existing widget to the screen. + pub fn draw_widget(self: &Arc, widget: Arc) { + if self.widgets.lock().unwrap().len() == 0 { + widget.set_focused(true); // first widget is focused + self.widgets.lock().unwrap().push(widget); + } else { + self.widgets.lock().unwrap().push(widget); + } + } + /// Registers an extension with the screen. pub fn extension(self: &Arc, ext: E) { self.extensions From 48397c06fadd6b3f86a20ef9ea15f931c23ca774 Mon Sep 17 00:00:00 2001 From: Leo dev Date: Mon, 4 Aug 2025 04:45:55 -0500 Subject: [PATCH 4/9] Added ExtensionContext #26 --- src/extensions/input_handling.rs | 11 +++---- src/extensions/mod.rs | 53 +++++++++++++++++++++++++++++--- src/extensions/tick.rs | 13 ++++---- src/extensions/velocity.rs | 6 ++-- src/lib.rs | 7 +++-- 5 files changed, 68 insertions(+), 22 deletions(-) diff --git a/src/extensions/input_handling.rs b/src/extensions/input_handling.rs index 227d526..8939812 100644 --- a/src/extensions/input_handling.rs +++ b/src/extensions/input_handling.rs @@ -1,20 +1,19 @@ -use crate::extensions::Extension; +use crate::extensions::{Extension, ExtensionContext}; pub struct InputExtension; impl Extension for InputExtension { - fn init(&mut self, screen: std::sync::Arc) { + fn init(&mut self, ctx: &ExtensionContext) { + let ctx = ctx.clone(); crossterm::terminal::enable_raw_mode().unwrap(); std::thread::spawn(move || loop { if let Ok(e) = crossterm::event::read() { - for widget in screen.widgets.lock().unwrap().iter() { - widget.event(&e); - } + ctx.event(&e); } }); } - fn on_close(&mut self, _screen: std::sync::Arc) { + fn on_close(&mut self) { crossterm::terminal::disable_raw_mode().unwrap(); } } diff --git a/src/extensions/mod.rs b/src/extensions/mod.rs index d027629..ee53bf9 100644 --- a/src/extensions/mod.rs +++ b/src/extensions/mod.rs @@ -11,7 +11,7 @@ pub use velocity::*; use std::{ any::{type_name, Any}, fmt::Debug, - sync::{Arc, Mutex}, + sync::{Arc, Mutex, MutexGuard}, }; use crate::{ @@ -21,15 +21,25 @@ use crate::{ }; pub trait Extension { - fn init(&mut self, _screen: Arc) {} - fn on_close(&mut self, _screen: Arc) {} - fn render_widget(&mut self, _scope: &mut RenderScope, _widget: &Arc) {} + fn init(&mut self, _ctx: &ExtensionContext) {} + fn on_close(&mut self) {} + fn render_widget( + &mut self, + _scope: &mut RenderScope, + _widget: &Arc, + ) { + } } pub trait Event: Send + Sync { fn as_any(&self) -> &dyn Any; } +#[derive(Clone)] +pub struct ExtensionContext { + screen: Arc, +} + #[derive(Clone)] pub struct Handler(Arc, &E) + Send + Sync>>); @@ -64,3 +74,38 @@ impl<'a> dyn Event + 'a { self.as_any().downcast_ref() } } + +impl ExtensionContext { + pub fn new(screen: Arc) -> Self { + Self { screen } + } + + pub fn event(&self, e: &E) { + for widget in self.screen.widgets.lock().unwrap().iter() { + widget.event(e); + } + } + + pub fn get_widgets(&self) -> MutexGuard>> { + self.screen.widgets.lock().unwrap() + } + + pub fn iter_components, Option)>( + &self, + mut iterator: F, + ) { + for widget in self.screen.widgets.lock().unwrap().iter() { + iterator(widget, widget.get()); + } + } + + pub fn get_components(&self) -> Vec { + let mut components = Vec::new(); + for widget in self.screen.widgets.lock().unwrap().iter() { + if let Some(c) = widget.get() { + components.push(c); + } + } + components + } +} diff --git a/src/extensions/tick.rs b/src/extensions/tick.rs index 354814d..f48ce61 100644 --- a/src/extensions/tick.rs +++ b/src/extensions/tick.rs @@ -1,22 +1,21 @@ -use std::{fmt::Debug, sync::Arc}; +use std::fmt::Debug; -use crate::extensions::Extension; -use crate::{event, Screen}; +use crate::event; +use crate::extensions::{Extension, ExtensionContext}; pub struct TickExtension(pub u16); event!(TickEvent(pub u32)); impl Extension for TickExtension { - fn init(&mut self, screen: Arc) { + fn init(&mut self, ctx: &ExtensionContext) { + let ctx = ctx.clone(); let rate_dur = 1000 / self.0 as u64; std::thread::spawn({ move || { let mut tick = 0; loop { - for w in screen.widgets.lock().unwrap().iter() { - w.event(&TickEvent(tick)); - } + ctx.event(&TickEvent(tick)); tick += 1; std::thread::sleep(std::time::Duration::from_millis(rate_dur)); } diff --git a/src/extensions/velocity.rs b/src/extensions/velocity.rs index 74aabee..799f8a0 100644 --- a/src/extensions/velocity.rs +++ b/src/extensions/velocity.rs @@ -5,7 +5,6 @@ use crate::{ extensions::Extension, style::{Position, Transform}, widget::Widget, - Screen, }; pub struct VelocityExtension; @@ -43,12 +42,13 @@ impl VelocityExtension { } impl Extension for VelocityExtension { - fn init(&mut self, screen: Arc) { + fn init(&mut self, ctx: &super::ExtensionContext) { + let ctx = ctx.clone(); std::thread::spawn({ move || { let mut tick = 0; loop { - for widget in screen.widgets.lock().unwrap().iter() { + for widget in ctx.get_widgets().iter() { Self::apply_velocity_xy(tick, widget); } if tick > 1000 { diff --git a/src/lib.rs b/src/lib.rs index 135fbc5..c82b1cb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -33,6 +33,7 @@ use std::sync::{Arc, Mutex}; use crate::{ extensions::{Extension, Handler}, + prelude::ExtensionContext, render_scope::RenderScope, widget::{BoxedElement, DynWidget, Element, StaticWidget, Widget, WidgetLoad}, }; @@ -158,8 +159,10 @@ impl Screen { /// /// This method blocks and repeatedly renders the screen at a fixed interval. pub fn run(self: &Arc) -> std::io::Result<()> { + let ctx = ExtensionContext::new(self.clone()); + for ext in self.extensions.lock().unwrap().iter() { - ext.lock().unwrap().init(self.clone()); + ext.lock().unwrap().init(&ctx); } utils::hide_cursor()?; @@ -230,7 +233,7 @@ impl Screen { utils::clear().unwrap(); for ext in self.extensions.lock().unwrap().iter() { - ext.lock().unwrap().on_close(self.clone()); + ext.lock().unwrap().on_close(); } } } From 62e0c1eac1982b7c743d2da61075d8d80f38533d Mon Sep 17 00:00:00 2001 From: Leo dev Date: Mon, 4 Aug 2025 04:50:52 -0500 Subject: [PATCH 5/9] Added event on Extension #26 --- src/extensions/mod.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/extensions/mod.rs b/src/extensions/mod.rs index ee53bf9..f6abdc5 100644 --- a/src/extensions/mod.rs +++ b/src/extensions/mod.rs @@ -22,13 +22,9 @@ use crate::{ pub trait Extension { fn init(&mut self, _ctx: &ExtensionContext) {} + fn event(&mut self, _ctx: &ExtensionContext, _event: &dyn Event) {} fn on_close(&mut self) {} - fn render_widget( - &mut self, - _scope: &mut RenderScope, - _widget: &Arc, - ) { - } + fn render_widget(&mut self, _scope: &mut RenderScope, _widget: &Arc) {} } pub trait Event: Send + Sync { @@ -84,6 +80,10 @@ impl ExtensionContext { for widget in self.screen.widgets.lock().unwrap().iter() { widget.event(e); } + + for ext in self.screen.extensions.lock().unwrap().iter() { + ext.lock().unwrap().event(self, e); + } } pub fn get_widgets(&self) -> MutexGuard>> { From ca4ee54e9d62663274155a74e13f97193a538bb0 Mon Sep 17 00:00:00 2001 From: Leo dev Date: Mon, 4 Aug 2025 05:58:11 -0500 Subject: [PATCH 6/9] Added after render and render on extension with child handlers #26 --- src/elements/div.rs | 18 ++++++++++++---- src/elements/flex.rs | 36 +++++++++++++++++++++++++------- src/elements/heading.rs | 6 +++++- src/elements/input.rs | 6 +++++- src/elements/mod.rs | 20 +++++------------- src/elements/paginator.rs | 17 +++++++++++---- src/extensions/input_handling.rs | 4 ++-- src/extensions/mod.rs | 29 ++++++++++++++++++++----- src/extensions/tick.rs | 4 ++-- src/extensions/velocity.rs | 2 +- src/lib.rs | 17 +++++++-------- src/widget.rs | 4 ++-- 12 files changed, 108 insertions(+), 55 deletions(-) diff --git a/src/elements/div.rs b/src/elements/div.rs index 02b9748..f9a193b 100644 --- a/src/elements/div.rs +++ b/src/elements/div.rs @@ -20,12 +20,20 @@ impl Div { } impl Element for Div { - fn render(&mut self, scope: &mut crate::render_scope::RenderScope) { + fn render( + &mut self, + scope: &mut crate::render_scope::RenderScope, + _: &crate::extensions::Context, + ) { let (width, height) = scope.get_size_or(self.size.0, self.size.1); scope.use_area(width, height); } - fn after_render(&mut self, scope: &mut crate::render_scope::RenderScope) { + fn after_render( + &mut self, + scope: &mut crate::render_scope::RenderScope, + ctx: &crate::extensions::Context, + ) { let mut transform = scope.get_transform().clone(); let (w, h) = scope.get_parent_size(); scope.set_parent_size(transform.width, transform.height); @@ -43,7 +51,8 @@ impl Element for Div { if let Some(t) = elem.get() { scope.set_transform(&t); } - elem.get_elem().render(scope); + elem.get_elem().render(scope, ctx); + ctx.render(elem, scope); if let Some(t) = elem.get() { scope.set_transform(&t); } @@ -54,7 +63,8 @@ impl Element for Div { t.y += transform.y + transform.py; scope.draw(); - elem.get_elem().after_render(scope); + elem.get_elem().after_render(scope, ctx); + ctx.after_render(elem, scope); } scope.set_parent_size(w, h); self.size = (transform.width, transform.height); diff --git a/src/elements/flex.rs b/src/elements/flex.rs index 06e3737..160b42d 100644 --- a/src/elements/flex.rs +++ b/src/elements/flex.rs @@ -38,12 +38,20 @@ impl FlexCol { } impl Element for FlexRow { - fn render(&mut self, scope: &mut crate::render_scope::RenderScope) { + fn render( + &mut self, + scope: &mut crate::render_scope::RenderScope, + _: &crate::extensions::Context, + ) { let (width, height) = scope.get_size_or(self.size.0, self.size.1); scope.use_area(width, height); } - fn after_render(&mut self, scope: &mut crate::render_scope::RenderScope) { + fn after_render( + &mut self, + scope: &mut crate::render_scope::RenderScope, + ctx: &crate::extensions::Context, + ) { let mut transform = scope.get_transform().clone(); let (w, h) = scope.get_parent_size(); scope.set_parent_size(transform.width, transform.height); @@ -62,7 +70,8 @@ impl Element for FlexRow { if let Some(t) = elem.get() { scope.set_transform(&t); } - elem.get_elem().render(scope); + elem.get_elem().render(scope, ctx); + ctx.render(elem, scope); if let Some(t) = elem.get() { scope.set_transform(&t); } @@ -77,7 +86,8 @@ impl Element for FlexRow { scope.draw(); - elem.get_elem().after_render(scope); + elem.get_elem().after_render(scope, ctx); + ctx.after_render(elem, scope); } scope.set_parent_size(w, h); self.size = (transform.width, transform.height); @@ -98,12 +108,20 @@ impl Element for FlexRow { } impl Element for FlexCol { - fn render(&mut self, scope: &mut crate::render_scope::RenderScope) { + fn render( + &mut self, + scope: &mut crate::render_scope::RenderScope, + _: &crate::extensions::Context, + ) { let (width, height) = scope.get_size_or(self.size.0, self.size.1); scope.use_area(width, height); } - fn after_render(&mut self, scope: &mut crate::render_scope::RenderScope) { + fn after_render( + &mut self, + scope: &mut crate::render_scope::RenderScope, + ctx: &crate::extensions::Context, + ) { let mut transform = scope.get_transform().clone(); let (w, h) = scope.get_parent_size(); scope.set_parent_size(transform.width, transform.height); @@ -122,7 +140,8 @@ impl Element for FlexCol { if let Some(t) = elem.get() { scope.set_transform(&t); } - elem.get_elem().render(scope); + elem.get_elem().render(scope, ctx); + ctx.render(elem, scope); if let Some(t) = elem.get() { scope.set_transform(&t); } @@ -137,7 +156,8 @@ impl Element for FlexCol { scope.draw(); - elem.get_elem().after_render(scope); + elem.get_elem().after_render(scope, ctx); + ctx.after_render(elem, scope); } scope.set_parent_size(w, h); self.size = (transform.width, transform.height); diff --git a/src/elements/heading.rs b/src/elements/heading.rs index 9e432f1..d4dd1e8 100644 --- a/src/elements/heading.rs +++ b/src/elements/heading.rs @@ -24,7 +24,11 @@ impl Heading { } impl Element for Heading { - fn render(&mut self, scope: &mut crate::prelude::RenderScope) { + fn render( + &mut self, + scope: &mut crate::prelude::RenderScope, + _: &crate::extensions::Context, + ) { let mut s = String::new(); for element in &self.children { if element.get::().is_some() { diff --git a/src/elements/input.rs b/src/elements/input.rs index 0561798..5c5eb4d 100644 --- a/src/elements/input.rs +++ b/src/elements/input.rs @@ -11,7 +11,11 @@ pub struct Input { } impl Element for Input { - fn render(&mut self, scope: &mut crate::prelude::RenderScope) { + fn render( + &mut self, + scope: &mut crate::prelude::RenderScope, + _: &crate::extensions::Context, + ) { let s = self.state.get(); scope.draw_text(0, 0, &s); if let Some(c) = s.chars().nth(self.cursor) { diff --git a/src/elements/mod.rs b/src/elements/mod.rs index 68ec01e..58ee1fc 100644 --- a/src/elements/mod.rs +++ b/src/elements/mod.rs @@ -13,7 +13,11 @@ pub use paginator::*; use crate::Element; impl Element for String { - fn render(&mut self, scope: &mut crate::render_scope::RenderScope) { + fn render( + &mut self, + scope: &mut crate::render_scope::RenderScope, + _: &crate::extensions::Context, + ) { scope.draw_text(0, 0, self); } @@ -25,17 +29,3 @@ impl Element for String { self } } - -impl Element for (String, u32) { - fn render(&mut self, scope: &mut crate::render_scope::RenderScope) { - scope.draw_text_colored(0, 0, &self.0, self.1); - } - - fn as_any(&self) -> &dyn std::any::Any { - self - } - - fn as_any_mut(&mut self) -> &mut dyn std::any::Any { - self - } -} diff --git a/src/elements/paginator.rs b/src/elements/paginator.rs index da7de57..f9ef1d9 100644 --- a/src/elements/paginator.rs +++ b/src/elements/paginator.rs @@ -24,12 +24,20 @@ impl Paginator { } impl Element for Paginator { - fn render(&mut self, scope: &mut crate::render_scope::RenderScope) { + fn render( + &mut self, + scope: &mut crate::render_scope::RenderScope, + _: &crate::extensions::Context, + ) { let (width, height) = scope.get_size_or(self.size.0, self.size.1); scope.use_area(width, height); } - fn after_render(&mut self, scope: &mut crate::render_scope::RenderScope) { + fn after_render( + &mut self, + scope: &mut crate::render_scope::RenderScope, + ctx: &crate::extensions::Context, + ) { if let Some(elem) = self.children.get(self.index) { if elem.get::().is_some() { return; @@ -46,7 +54,7 @@ impl Element for Paginator { if let Some(t) = elem.get() { scope.set_transform(&t); } - elem.get_elem().render(scope); + elem.get_elem().render(scope, ctx); if let Some(t) = elem.get() { scope.set_transform(&t); } @@ -57,7 +65,8 @@ impl Element for Paginator { t.y += transform.y + transform.py; scope.draw(); - elem.get_elem().after_render(scope); + elem.get_elem().after_render(scope, ctx); + ctx.after_render(elem, scope); scope.set_parent_size(w, h); self.size = (transform.width, transform.height); diff --git a/src/extensions/input_handling.rs b/src/extensions/input_handling.rs index 8939812..94156a6 100644 --- a/src/extensions/input_handling.rs +++ b/src/extensions/input_handling.rs @@ -1,9 +1,9 @@ -use crate::extensions::{Extension, ExtensionContext}; +use crate::extensions::{Extension, Context}; pub struct InputExtension; impl Extension for InputExtension { - fn init(&mut self, ctx: &ExtensionContext) { + fn init(&mut self, ctx: &Context) { let ctx = ctx.clone(); crossterm::terminal::enable_raw_mode().unwrap(); std::thread::spawn(move || loop { diff --git a/src/extensions/mod.rs b/src/extensions/mod.rs index f6abdc5..cf22386 100644 --- a/src/extensions/mod.rs +++ b/src/extensions/mod.rs @@ -21,10 +21,17 @@ use crate::{ }; pub trait Extension { - fn init(&mut self, _ctx: &ExtensionContext) {} - fn event(&mut self, _ctx: &ExtensionContext, _event: &dyn Event) {} + fn init(&mut self, _ctx: &Context) {} + fn event(&mut self, _ctx: &Context, _event: &dyn Event) {} fn on_close(&mut self) {} - fn render_widget(&mut self, _scope: &mut RenderScope, _widget: &Arc) {} + fn render_widget(&mut self, _ctx: &Context, _scope: &mut RenderScope, _widget: &Arc) {} + fn after_render_widget( + &mut self, + _ctx: &Context, + _scope: &mut RenderScope, + _widget: &Arc, + ) { + } } pub trait Event: Send + Sync { @@ -32,7 +39,7 @@ pub trait Event: Send + Sync { } #[derive(Clone)] -pub struct ExtensionContext { +pub struct Context { screen: Arc, } @@ -71,7 +78,7 @@ impl<'a> dyn Event + 'a { } } -impl ExtensionContext { +impl Context { pub fn new(screen: Arc) -> Self { Self { screen } } @@ -108,4 +115,16 @@ impl ExtensionContext { } components } + + pub fn render(&self, w: &Arc, scope: &mut RenderScope) { + for ext in self.screen.extensions.lock().unwrap().iter() { + ext.lock().unwrap().render_widget(self, scope, w); + } + } + + pub fn after_render(&self, w: &Arc, scope: &mut RenderScope) { + for ext in self.screen.extensions.lock().unwrap().iter() { + ext.lock().unwrap().after_render_widget(self, scope, w); + } + } } diff --git a/src/extensions/tick.rs b/src/extensions/tick.rs index f48ce61..9f6701e 100644 --- a/src/extensions/tick.rs +++ b/src/extensions/tick.rs @@ -1,14 +1,14 @@ use std::fmt::Debug; use crate::event; -use crate::extensions::{Extension, ExtensionContext}; +use crate::extensions::{Context, Extension}; pub struct TickExtension(pub u16); event!(TickEvent(pub u32)); impl Extension for TickExtension { - fn init(&mut self, ctx: &ExtensionContext) { + fn init(&mut self, ctx: &Context) { let ctx = ctx.clone(); let rate_dur = 1000 / self.0 as u64; std::thread::spawn({ diff --git a/src/extensions/velocity.rs b/src/extensions/velocity.rs index 799f8a0..13fae15 100644 --- a/src/extensions/velocity.rs +++ b/src/extensions/velocity.rs @@ -42,7 +42,7 @@ impl VelocityExtension { } impl Extension for VelocityExtension { - fn init(&mut self, ctx: &super::ExtensionContext) { + fn init(&mut self, ctx: &super::Context) { let ctx = ctx.clone(); std::thread::spawn({ move || { diff --git a/src/lib.rs b/src/lib.rs index c82b1cb..0d2e386 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -33,7 +33,7 @@ use std::sync::{Arc, Mutex}; use crate::{ extensions::{Extension, Handler}, - prelude::ExtensionContext, + prelude::Context, render_scope::RenderScope, widget::{BoxedElement, DynWidget, Element, StaticWidget, Widget, WidgetLoad}, }; @@ -159,7 +159,7 @@ impl Screen { /// /// This method blocks and repeatedly renders the screen at a fixed interval. pub fn run(self: &Arc) -> std::io::Result<()> { - let ctx = ExtensionContext::new(self.clone()); + let ctx = Context::new(self.clone()); for ext in self.extensions.lock().unwrap().iter() { ext.lock().unwrap().init(&ctx); @@ -168,7 +168,7 @@ impl Screen { utils::hide_cursor()?; while *self.running.lock().unwrap() { - self.render()?; + self.render(&ctx)?; std::thread::sleep(std::time::Duration::from_millis(28)); } @@ -178,7 +178,7 @@ impl Screen { /// Renders all widgets and applies extensions. /// /// This method is called internally by `run`. - pub fn render(self: &Arc) -> std::io::Result<()> { + pub fn render(self: &Arc, ctx: &Context) -> std::io::Result<()> { let mut scope = RenderScope::new(); let (w, h) = crossterm::terminal::size().unwrap(); scope.set_parent_size(w, h); @@ -186,9 +186,6 @@ impl Screen { utils::clear()?; for elem in self.widgets.lock().unwrap().iter() { if elem.get::().is_some() || elem.get::().is_some() { - for ext in self.extensions.lock().unwrap().iter() { - ext.lock().unwrap().render_widget(&mut scope, elem); - } elem.auto_refresh(); continue; } @@ -206,17 +203,17 @@ impl Screen { } for ext in self.extensions.lock().unwrap().iter() { - ext.lock().unwrap().render_widget(&mut scope, elem); + ext.lock().unwrap().render_widget(ctx, &mut scope, elem); } - elem.get_elem().render(&mut scope); + elem.get_elem().render(&mut scope, ctx); if let Some(t) = elem.get() { scope.set_transform(&t); } scope.draw(); - elem.get_elem().after_render(&mut scope); + elem.get_elem().after_render(&mut scope, ctx); } elem.auto_refresh(); diff --git a/src/widget.rs b/src/widget.rs index 2a2de8e..b155ae3 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -31,11 +31,11 @@ pub type BoxedComponent = Box; pub trait Element: Send + Sync { /// Called to perform rendering for the element. #[allow(unused)] - fn render(&mut self, scope: &mut RenderScope) {} + fn render(&mut self, scope: &mut RenderScope, ctx: &crate::extensions::Context) {} /// Called after rendering, for follow-up logic or cleanup. #[allow(unused)] - fn after_render(&mut self, scope: &mut RenderScope) {} + fn after_render(&mut self, scope: &mut RenderScope, ctx: &crate::extensions::Context) {} /// Called to draw child widgets, if any. #[allow(unused)] From 3a5286a54f8a2ee64b19aa5dd136010fdff32097 Mon Sep 17 00:00:00 2001 From: Leo dev Date: Mon, 4 Aug 2025 08:16:34 -0500 Subject: [PATCH 7/9] Added focus #26 --- src/demos/mod.rs | 13 +- src/elements/flex.rs | 4 + src/extensions/focus.rs | 259 ++++++++++++++++++++++++++++++++++++++++ src/extensions/mod.rs | 3 + src/lib.rs | 4 + src/main.rs | 1 + 6 files changed, 278 insertions(+), 6 deletions(-) create mode 100644 src/extensions/focus.rs diff --git a/src/demos/mod.rs b/src/demos/mod.rs index 75ef0bc..20e0bc8 100644 --- a/src/demos/mod.rs +++ b/src/demos/mod.rs @@ -46,16 +46,17 @@ pub fn app(screen: Arc) -> Rsx { // TODO: div with width full } - FlexCol, gap: 2, { - @transform!{ y: Center }; - static Div { // static only affects the element, not children - %count - "This will increment every second: {count}" - } + FlexRow, gap: 1, { + %count + "This will increment every second: {count}" @Transform::new().padding(1, 1).dimensions(40, 1); @Style { foreground: Some(0xffffff), background: Background::RoundedOutline(0xff0000) }; Input { } + + @Transform::new().padding(1, 1).dimensions(40, 1); + @Style { foreground: Some(0xffffff), background: Background::RoundedOutline(0xffff00) }; + Input { } } } } diff --git a/src/elements/flex.rs b/src/elements/flex.rs index 160b42d..3325b4e 100644 --- a/src/elements/flex.rs +++ b/src/elements/flex.rs @@ -53,6 +53,7 @@ impl Element for FlexRow { ctx: &crate::extensions::Context, ) { let mut transform = scope.get_transform().clone(); + let transform_before = transform.clone(); let (w, h) = scope.get_parent_size(); scope.set_parent_size(transform.width, transform.height); @@ -91,6 +92,7 @@ impl Element for FlexRow { } scope.set_parent_size(w, h); self.size = (transform.width, transform.height); + scope.set_transform_raw(transform_before); } fn draw_child(&mut self, element: &Arc) { @@ -123,6 +125,7 @@ impl Element for FlexCol { ctx: &crate::extensions::Context, ) { let mut transform = scope.get_transform().clone(); + let transform_before = transform.clone(); let (w, h) = scope.get_parent_size(); scope.set_parent_size(transform.width, transform.height); @@ -160,6 +163,7 @@ impl Element for FlexCol { ctx.after_render(elem, scope); } scope.set_parent_size(w, h); + scope.set_transform_raw(transform_before); self.size = (transform.width, transform.height); } diff --git a/src/extensions/focus.rs b/src/extensions/focus.rs new file mode 100644 index 0000000..bed058d --- /dev/null +++ b/src/extensions/focus.rs @@ -0,0 +1,259 @@ +use std::sync::{Arc, Mutex}; + +use super::Extension; + +use crossterm::event::{Event as CrosstermEvent, KeyCode}; + +pub struct RelativeFocusExtension { + cursor: usize, + rendered: Arc>>, +} + +impl Extension for RelativeFocusExtension { + fn event(&mut self, ctx: &super::Context, event: &dyn super::Event) { + if let Some(e) = event.get::() { + match e { + CrosstermEvent::Key(k) => match k.code { + KeyCode::Right => { + let rendered = self.rendered.lock().unwrap(); + let (_, current_x, current_y) = rendered + .iter() + .find(|r| r.0 == self.cursor) + .unwrap_or(&(0, 0, 0)); + + if let Some(index) = find_closest_in_direction( + Direction::Right, + *current_x, + *current_y, + &rendered, + self.cursor, + ) { + self.cursor = index; + for (i, w) in ctx.get_widgets().iter().enumerate() { + w.set_focused(i == self.cursor); + } + } + } + + KeyCode::Left => { + let rendered = self.rendered.lock().unwrap(); + let (_, current_x, current_y) = rendered + .iter() + .find(|r| r.0 == self.cursor) + .unwrap_or(&(0, 0, 0)); + + if let Some(index) = find_closest_in_direction( + Direction::Left, + *current_x, + *current_y, + &rendered, + self.cursor, + ) { + self.cursor = index; + for (i, w) in ctx.get_widgets().iter().enumerate() { + w.set_focused(i == self.cursor); + } + } + } + + KeyCode::Up => { + let rendered = self.rendered.lock().unwrap(); + let (_, current_x, current_y) = rendered + .iter() + .find(|r| r.0 == self.cursor) + .unwrap_or(&(0, 0, 0)); + + if let Some(index) = find_closest_in_direction( + Direction::Up, + *current_x, + *current_y, + &rendered, + self.cursor, + ) { + self.cursor = index; + for (i, w) in ctx.get_widgets().iter().enumerate() { + w.set_focused(i == self.cursor); + } + } + } + + KeyCode::Down => { + let rendered = self.rendered.lock().unwrap(); + let (_, current_x, current_y) = *rendered + .iter() + .find(|r| r.0 == self.cursor) + .unwrap_or(&(0, 0, 0)); + + if let Some(index) = find_closest_in_direction( + Direction::Down, + current_x, + current_y, + &rendered, + self.cursor, + ) { + self.cursor = index; + + for (i, w) in ctx.get_widgets().iter().enumerate() { + w.set_focused(i == self.cursor); + } + } + } + + _ => {} + }, + _ => {} + } + } + } + + fn render(&mut self, _ctx: &super::Context, _scope: &mut crate::prelude::RenderScope) { + self.rendered.lock().unwrap().clear(); + } + + fn after_render_widget( + &mut self, + ctx: &super::Context, + scope: &mut crate::prelude::RenderScope, + widget: &std::sync::Arc, + ) { + let t = scope.get_transform().clone(); + let ctx = ctx.clone(); + let widget = widget.clone(); + + let rendered = self.rendered.clone(); + + std::thread::spawn(move || { + if let Some(p) = ctx + .get_widgets() + .iter() + .position(|w| Arc::ptr_eq(w, &widget)) + { + rendered.lock().unwrap().push((p, t.x, t.y)); + } + }); + } +} + +impl RelativeFocusExtension { + pub fn new() -> Self { + Self { + cursor: 0, + rendered: Arc::new(Mutex::new(Vec::new())), + } + } +} + +enum Direction { + Left, + Right, + Up, + Down, +} + +fn find_closest_in_direction( + direction: Direction, + current_x: u16, + current_y: u16, + rendered: &[(usize, u16, u16)], + cursor: usize, +) -> Option { + let mut closest_index: Option = None; + let mut closest_distance = u16::MAX; + let mut same_line_or_col_found = false; + + for &(i, x, y) in rendered { + if i == cursor { + continue; + } + + match direction { + Direction::Right if x > current_x => { + if y == current_y { + let dx = x - current_x; + if !same_line_or_col_found || dx < closest_distance { + closest_index = Some(i); + closest_distance = dx; + same_line_or_col_found = true; + } + } else if !same_line_or_col_found { + let dx = x - current_x; + let dy = (y as isize - current_y as isize).abs() as u16; + let dist = dx * dx + dy * dy; + if dist < closest_distance + || (dist == closest_distance && i < closest_index.unwrap_or(usize::MAX)) + { + closest_index = Some(i); + closest_distance = dist; + } + } + } + + Direction::Left if x < current_x => { + if y == current_y { + let dx = current_x - x; + if !same_line_or_col_found || dx < closest_distance { + closest_index = Some(i); + closest_distance = dx; + same_line_or_col_found = true; + } + } else if !same_line_or_col_found { + let dx = current_x - x; + let dy = (y as isize - current_y as isize).abs() as u16; + let dist = dx * dx + dy * dy; + if dist < closest_distance + || (dist == closest_distance && i < closest_index.unwrap_or(usize::MAX)) + { + closest_index = Some(i); + closest_distance = dist; + } + } + } + + Direction::Down if y > current_y => { + if x == current_x { + let dy = y - current_y; + if !same_line_or_col_found || dy < closest_distance { + closest_index = Some(i); + closest_distance = dy; + same_line_or_col_found = true; + } + } else if !same_line_or_col_found { + let dx = (x as isize - current_x as isize).abs() as u16; + let dy = y - current_y; + let dist = dx * dx + dy * dy; + if dist < closest_distance + || (dist == closest_distance && i < closest_index.unwrap_or(usize::MAX)) + { + closest_index = Some(i); + closest_distance = dist; + } + } + } + + Direction::Up if y < current_y => { + if x == current_x { + let dy = current_y - y; + if !same_line_or_col_found || dy < closest_distance { + closest_index = Some(i); + closest_distance = dy; + same_line_or_col_found = true; + } + } else if !same_line_or_col_found { + let dx = (x as isize - current_x as isize).abs() as u16; + let dy = current_y - y; + let dist = dx * dx + dy * dy; + if dist < closest_distance + || (dist == closest_distance && i < closest_index.unwrap_or(usize::MAX)) + { + closest_index = Some(i); + closest_distance = dist; + } + } + } + + _ => {} + } + } + + closest_index +} diff --git a/src/extensions/mod.rs b/src/extensions/mod.rs index cf22386..423962f 100644 --- a/src/extensions/mod.rs +++ b/src/extensions/mod.rs @@ -1,8 +1,10 @@ +pub mod focus; pub mod id; pub mod input_handling; pub mod tick; pub mod velocity; +pub use focus::*; pub use id::*; pub use input_handling::*; pub use tick::*; @@ -24,6 +26,7 @@ pub trait Extension { fn init(&mut self, _ctx: &Context) {} fn event(&mut self, _ctx: &Context, _event: &dyn Event) {} fn on_close(&mut self) {} + fn render(&mut self, _ctx: &Context, _scope: &mut RenderScope) {} fn render_widget(&mut self, _ctx: &Context, _scope: &mut RenderScope, _widget: &Arc) {} fn after_render_widget( &mut self, diff --git a/src/lib.rs b/src/lib.rs index 0d2e386..f3b4a82 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -183,6 +183,10 @@ impl Screen { let (w, h) = crossterm::terminal::size().unwrap(); scope.set_parent_size(w, h); + for ext in self.extensions.lock().unwrap().iter() { + ext.lock().unwrap().render(ctx, &mut scope); + } + utils::clear()?; for elem in self.widgets.lock().unwrap().iter() { if elem.get::().is_some() || elem.get::().is_some() { diff --git a/src/main.rs b/src/main.rs index d6cfd88..c915f8e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,7 @@ use osui::prelude::*; fn main() -> std::io::Result<()> { let screen = Screen::new(); screen.extension(InputExtension); + screen.extension(RelativeFocusExtension::new()); demos::app(screen.clone()).draw(&screen); From 896dfb0a363d5989bca5046f355cda7b93974416 Mon Sep 17 00:00:00 2001 From: Leo dev Date: Mon, 4 Aug 2025 08:23:51 -0500 Subject: [PATCH 8/9] Added a AlwaysFocused component #26 --- src/demos/mod.rs | 1 + src/extensions/focus.rs | 196 +++++++++++++++++++++------------------- 2 files changed, 105 insertions(+), 92 deletions(-) diff --git a/src/demos/mod.rs b/src/demos/mod.rs index 20e0bc8..e40515f 100644 --- a/src/demos/mod.rs +++ b/src/demos/mod.rs @@ -23,6 +23,7 @@ pub fn app(screen: Arc) -> Rsx { } } }}); + @AlwaysFocused; Paginator { FlexRow { Heading, smooth: false, { "OSUI" } diff --git a/src/extensions/focus.rs b/src/extensions/focus.rs index bed058d..f766be9 100644 --- a/src/extensions/focus.rs +++ b/src/extensions/focus.rs @@ -1,8 +1,19 @@ use std::sync::{Arc, Mutex}; +use crate::component; + use super::Extension; -use crossterm::event::{Event as CrosstermEvent, KeyCode}; +use crossterm::event::{Event as CrosstermEvent, KeyCode, KeyModifiers}; + +component!(AlwaysFocused); + +enum Direction { + Left, + Right, + Up, + Down, +} pub struct RelativeFocusExtension { cursor: usize, @@ -13,94 +24,102 @@ impl Extension for RelativeFocusExtension { fn event(&mut self, ctx: &super::Context, event: &dyn super::Event) { if let Some(e) = event.get::() { match e { - CrosstermEvent::Key(k) => match k.code { - KeyCode::Right => { - let rendered = self.rendered.lock().unwrap(); - let (_, current_x, current_y) = rendered - .iter() - .find(|r| r.0 == self.cursor) - .unwrap_or(&(0, 0, 0)); + CrosstermEvent::Key(k) => { + if k.modifiers.contains(KeyModifiers::SHIFT) { + match k.code { + KeyCode::Right => { + let rendered = self.rendered.lock().unwrap(); + let (_, current_x, current_y) = rendered + .iter() + .find(|r| r.0 == self.cursor) + .unwrap_or(&(0, 0, 0)); - if let Some(index) = find_closest_in_direction( - Direction::Right, - *current_x, - *current_y, - &rendered, - self.cursor, - ) { - self.cursor = index; - for (i, w) in ctx.get_widgets().iter().enumerate() { - w.set_focused(i == self.cursor); + if let Some(index) = find_closest_in_direction( + Direction::Right, + *current_x, + *current_y, + &rendered, + self.cursor, + ) { + self.cursor = index; + for (i, w) in ctx.get_widgets().iter().enumerate() { + w.set_focused(i == self.cursor); + } + } } + + KeyCode::Left => { + let rendered = self.rendered.lock().unwrap(); + let (_, current_x, current_y) = rendered + .iter() + .find(|r| r.0 == self.cursor) + .unwrap_or(&(0, 0, 0)); + + if let Some(index) = find_closest_in_direction( + Direction::Left, + *current_x, + *current_y, + &rendered, + self.cursor, + ) { + self.cursor = index; + for (i, w) in ctx.get_widgets().iter().enumerate() { + w.set_focused(i == self.cursor); + } + } + } + + KeyCode::Up => { + let rendered = self.rendered.lock().unwrap(); + let (_, current_x, current_y) = rendered + .iter() + .find(|r| r.0 == self.cursor) + .unwrap_or(&(0, 0, 0)); + + if let Some(index) = find_closest_in_direction( + Direction::Up, + *current_x, + *current_y, + &rendered, + self.cursor, + ) { + self.cursor = index; + for (i, w) in ctx.get_widgets().iter().enumerate() { + w.set_focused(i == self.cursor); + } + } + } + + KeyCode::Down => { + let rendered = self.rendered.lock().unwrap(); + let (_, current_x, current_y) = *rendered + .iter() + .find(|r| r.0 == self.cursor) + .unwrap_or(&(0, 0, 0)); + + if let Some(index) = find_closest_in_direction( + Direction::Down, + current_x, + current_y, + &rendered, + self.cursor, + ) { + self.cursor = index; + + for (i, w) in ctx.get_widgets().iter().enumerate() { + if let Some(AlwaysFocused) = w.get() { + w.set_focused(true); + } else { + w.set_focused(i == self.cursor); + } + } + } + } + + _ => {} } } - - KeyCode::Left => { - let rendered = self.rendered.lock().unwrap(); - let (_, current_x, current_y) = rendered - .iter() - .find(|r| r.0 == self.cursor) - .unwrap_or(&(0, 0, 0)); - - if let Some(index) = find_closest_in_direction( - Direction::Left, - *current_x, - *current_y, - &rendered, - self.cursor, - ) { - self.cursor = index; - for (i, w) in ctx.get_widgets().iter().enumerate() { - w.set_focused(i == self.cursor); - } - } - } - - KeyCode::Up => { - let rendered = self.rendered.lock().unwrap(); - let (_, current_x, current_y) = rendered - .iter() - .find(|r| r.0 == self.cursor) - .unwrap_or(&(0, 0, 0)); - - if let Some(index) = find_closest_in_direction( - Direction::Up, - *current_x, - *current_y, - &rendered, - self.cursor, - ) { - self.cursor = index; - for (i, w) in ctx.get_widgets().iter().enumerate() { - w.set_focused(i == self.cursor); - } - } - } - - KeyCode::Down => { - let rendered = self.rendered.lock().unwrap(); - let (_, current_x, current_y) = *rendered - .iter() - .find(|r| r.0 == self.cursor) - .unwrap_or(&(0, 0, 0)); - - if let Some(index) = find_closest_in_direction( - Direction::Down, - current_x, - current_y, - &rendered, - self.cursor, - ) { - self.cursor = index; - - for (i, w) in ctx.get_widgets().iter().enumerate() { - w.set_focused(i == self.cursor); - } - } - } - - _ => {} - }, + } _ => {} } } @@ -143,13 +162,6 @@ impl RelativeFocusExtension { } } -enum Direction { - Left, - Right, - Up, - Down, -} - fn find_closest_in_direction( direction: Direction, current_x: u16, From 9868f02c33cbb29ec5404bc4176718a239b6c654 Mon Sep 17 00:00:00 2001 From: Leo dev Date: Mon, 4 Aug 2025 08:24:35 -0500 Subject: [PATCH 9/9] Version update to 0.1.1 (for later) --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index bbf4fc5..d9080c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "osui" -version = "0.1.0" +version = "0.1.1" edition = "2021" license = "Apache-2.0" description = "A TUI library for advanced uis"