From 3a5286a54f8a2ee64b19aa5dd136010fdff32097 Mon Sep 17 00:00:00 2001 From: Leo dev Date: Mon, 4 Aug 2025 08:16:34 -0500 Subject: [PATCH] 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);