From 87ae71072ebdc07e6ba8b5117dc76c2504d97a15 Mon Sep 17 00:00:00 2001 From: Leo Date: Wed, 30 Oct 2024 14:14:00 +0100 Subject: [PATCH] Large structure update --- Cargo.toml | 2 +- src/lib.rs | 101 ++++------ src/macros.rs | 118 +++++++++--- src/main.rs | 28 +-- src/ui.rs | 505 -------------------------------------------------- src/utils.rs | 36 ++-- 6 files changed, 161 insertions(+), 629 deletions(-) delete mode 100644 src/ui.rs diff --git a/Cargo.toml b/Cargo.toml index 091155f..6493178 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ version = "0.0.3" edition = "2021" license = "Apache-2.0" description = "A TUI library" -repository = "https://github.com/orus-dev/osui-rs" +repository = "https://github.com/orus-dev/osui" [lib] name = "osui" diff --git a/src/lib.rs b/src/lib.rs index f971844..bc2ba7f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,89 +1,72 @@ +use crossterm::ExecutableCommand; + pub mod key; pub mod macros; pub mod ui; pub mod utils; -use crossterm::{self, ExecutableCommand}; -use std::collections::HashMap; +pub trait Element: std::fmt::Debug { + fn get_child(&mut self) -> Option<&mut Box>; + fn get_data(&self) -> ElementData; + fn set_data(&mut self, _: ElementData); + fn render(&mut self) -> String { + String::new() + } + fn update(&mut self, _: crate::key::Key) -> UpdateResponse { + UpdateResponse::None + } + fn tick(&mut self, _: usize) {} +} -#[derive(Debug, Clone, PartialEq)] -pub struct Component { - pub render: fn(&mut Component) -> String, - pub update: fn(&mut Component, key::Key) -> UpdateResponse, - pub tick: fn(&mut Component, usize), - pub on_click: fn(&mut Component), +#[derive(Debug)] +pub struct ElementData { pub x: usize, pub y: usize, pub width: usize, pub height: usize, - pub expr: String, - pub children: Vec, - pub child: usize, - pub clicked: bool, - pub toggle: bool, - pub binds: HashMap, - pub style: ui::Style, -} - -impl Component { - /// Creates a new component - fn new() -> Component { - Component { - render: |_| String::new(), - update: |_, _| UpdateResponse::None, - tick: |_, _| {}, - on_click: |_| {}, - x: 0, - y: 0, - width: 0, - height: 0, - expr: String::new(), - children: Vec::new(), - child: 0, - clicked: false, - toggle: false, - binds: HashMap::new(), - style: ui::Style::default(), - } - } - - pub fn get_child(&mut self) -> Option<&mut Component> { - self.children.get_mut(self.child) - } + pub style: ui::styles::Style, } #[derive(Debug, Clone, PartialEq)] +pub enum UpdateResponse { + Exit, + Done, + None, + Tick(Vec), +} + pub struct App { - component: Component, + element: Box, } impl App { /// Creates a new screen to render components pub fn new() -> App { App { - component: Component::new(), + element: ui::text(), } } /// Sets a component - pub fn set_component(&mut self, component: Component) { + pub fn set_component(&mut self, element: Box) { let (width, height) = crossterm::terminal::size().unwrap(); - self.component.style.is_active = true; - self.component = component; - if self.component.width == 0 { - self.component.width = width as usize; + self.element = element; + let mut data = self.element.get_data(); + data.style.is_active = true; + if data.width == 0 { + data.width = width as usize; } - if self.component.height == 0 { - self.component.height = height as usize; + if data.height == 0 { + data.height = height as usize; } - self.component.style.is_active = true; + self.element.set_data(data); } /// Render to the screen pub fn render(&mut self) { let (width, height) = crossterm::terminal::size().unwrap(); let mut frame: Vec = create_frame!(width as usize, height as usize); - utils::render_to_frame(&mut frame, &mut self.component); + utils::render_to_frame(&mut frame, &mut self.element); utils::clear(); print!("{}", frame.join("")); utils::flush(); @@ -100,7 +83,7 @@ impl App { crossterm::terminal::enable_raw_mode().unwrap(); loop { self.render(); - match (self.component.update)(&mut self.component, key::read_key()) { + match self.element.update(key::read_key()) { UpdateResponse::Exit => { crossterm::terminal::disable_raw_mode().unwrap(); utils::show_cursor(); @@ -110,7 +93,7 @@ impl App { UpdateResponse::Tick(ticks) => { for (i, n) in ticks.into_iter().enumerate() { std::thread::sleep(std::time::Duration::from_millis(n as u64)); - (self.component.tick)(&mut self.component, i); + self.element.tick(i); self.render(); } } @@ -119,11 +102,3 @@ impl App { } } } - -#[derive(Debug, Clone, PartialEq)] -pub enum UpdateResponse { - Exit, - Done, - None, - Tick(Vec), -} diff --git a/src/macros.rs b/src/macros.rs index 33c37ae..a1e9722 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -8,7 +8,7 @@ macro_rules! __oml { }}; ($tag:ident ($($k:ident = $v:expr),*) {$($inner:tt)*} $($rest:tt)+) => {{ - let mut comps: Vec = osui::__oml!($($rest)+); + let mut comps: Vec> = osui::__oml!($($rest)+); comps.insert(0, oml!($tag ($($k = $v),*) {$($inner)*})); comps }}; @@ -19,30 +19,30 @@ macro_rules! __oml { }}; ($tag:ident {$($inner:tt)*} $($rest:tt)+) => {{ - let mut comps: Vec = osui::__oml!($($rest)+); + let mut comps: Vec> = osui::__oml!($($rest)+); comps.insert(0, oml!($tag {$($inner)*})); comps }}; // Expression, With components (EC) - ($tag:ident ($expr:expr; $($k:ident = $v:expr),*) {$($inner:tt)*}) => {{ - vec![oml!($tag ($expr; $($k = $v),*) {$($inner)*})] + ($tag:ident ($text:expr; $($k:ident = $v:expr),*) {$($inner:tt)*}) => {{ + vec![oml!($tag ($text; $($k = $v),*) {$($inner)*})] }}; - ($tag:ident ($expr:expr; $($k:ident = $v:expr),*) {$($inner:tt)*} $($rest:tt)+) => {{ - let mut comps: Vec = osui::__oml!($($rest)+); - comps.insert(0, oml!($tag ($expr; $($k = $v),*) {$($inner)*})); + ($tag:ident ($text:expr; $($k:ident = $v:expr),*) {$($inner:tt)*} $($rest:tt)+) => {{ + let mut comps: Vec> = osui::__oml!($($rest)+); + comps.insert(0, oml!($tag ($text; $($k = $v),*) {$($inner)*})); comps }}; // Expression (E) - ($tag:ident ($expr:expr; $($k:ident = $v:expr),*)) => {{ - vec![oml!($tag ($expr; $($k = $v),*))] + ($tag:ident ($text:expr; $($k:ident = $v:expr),*)) => {{ + vec![oml!($tag ($text; $($k = $v),*))] }}; - ($tag:ident ($expr:expr; $($k:ident = $v:expr),*) $($rest:tt)+) => {{ - let mut comps: Vec = osui::__oml!($($rest)+); - comps.insert(0, oml!($tag ($expr; $($k = $v),*))); + ($tag:ident ($text:expr; $($k:ident = $v:expr),*) $($rest:tt)+) => {{ + let mut comps: Vec> = osui::__oml!($($rest)+); + comps.insert(0, oml!($tag ($text; $($k = $v),*))); comps }}; @@ -52,19 +52,19 @@ macro_rules! __oml { }}; ($tag:ident ($($k:ident = $v:expr),*) $($rest:tt)+) => {{ - let mut comps: Vec = osui::__oml!($($rest)+); + let mut comps: Vec> = osui::__oml!($($rest)+); comps.insert(0, oml!($tag ($($k = $v),*))); comps }}; // Pre-Defined (D) - ($expr:expr;) => {{ - vec![oml!($expr;)] + ($text:expr;) => {{ + vec![oml!($text;)] }}; - ($expr:expr; $($rest:tt)+) => {{ - let mut comps: Vec = osui::__oml!($($rest)+); - comps.insert(0, oml!($expr)); + ($text:expr; $($rest:tt)+) => {{ + let mut comps: Vec> = osui::__oml!($($rest)+); + comps.insert(0, oml!($text)); comps }}; } @@ -82,10 +82,10 @@ macro_rules! oml { }}; // Expression, With components (EC) - ( $tag:ident ($expr:expr; $($k:ident = $v:expr),* ) {$($inner:tt)*} ) => {{ + ( $tag:ident ($text:expr; $($k:ident = $v:expr),* ) {$($inner:tt)*} ) => {{ let mut c = $tag(); c.children = osui::__oml!($($inner)*); - c.params = String::from($expr); + c.text = String::from($text); $( c.$k = $v; )* @@ -93,9 +93,9 @@ macro_rules! oml { }}; // Expression (E) - ( $tag:ident ($expr:expr; $($k:ident = $v:expr),*) ) => {{ + ( $tag:ident ($text:expr; $($k:ident = $v:expr),*) ) => {{ let mut c = $tag(); - c.expr = String::from($expr); + c.text = String::from($text); $( c.$k = $v; )* @@ -120,8 +120,8 @@ macro_rules! oml { }}; // Pre-Defined (D) - ($expr:expr;) => {{ - $expr + ($text:expr;) => {{ + $text }}; } @@ -134,3 +134,73 @@ macro_rules! create_frame { vec![" ".repeat($width); $height] }; } + +#[macro_export] +///```component!{ +/// MyElem {} +/// defaults {} +/// }``` +/// +/// Create a frame for rendering multiple components +macro_rules! element { + ( + $name:ident { + $( $inner:tt )* + } + defaults {$( $defaults:tt )*} + $( $functions:tt )* + ) => { + #[derive(Debug)] + pub struct $name { + pub x: usize, + pub y: usize, + pub width: usize, + pub height: usize, + pub children: Vec>, + pub child: usize, + pub text: String, + pub style: Style, + $( $inner )* + } + + impl Element for $name { + fn get_child(&mut self) -> Option<&mut Box> { + self.children.get_mut(self.child) + } + fn get_data(&self) -> ElementData { + ElementData { + x: self.x, + y: self.y, + width: self.width, + height: self.height, + style: self.style.clone(), + } + } + + fn set_data(&mut self, data: ElementData) { + self.x = data.x; + self.y = data.y; + self.width = data.width; + self.height = data.height; + self.style = data.style; + } + $( $functions )* + } + + impl $name { + pub fn new() -> $name { + $name { + children: Vec::new(), + x: 0, + y: 0, + width: 0, + height: 0, + child: 0, + text: String::new(), + style: Style::default(), + $( $defaults )* + } + } + } + }; +} diff --git a/src/main.rs b/src/main.rs index 1314b50..d086b0a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,29 +1,13 @@ -use osui::{oml, ui::*, App, Component}; +use osui::{oml, ui::*, App}; fn main() { - let mut app = App::new(); - app.set_component(app_elem()); - app.run(); + let mut app_screen = App::new(); + app_screen.set_component(app()); + app_screen.run(); } -fn onclick(c: &mut Component) { - if c.clicked { - c.expr = "Clicked!".to_string(); - } else { - c.expr = "click me".to_string() - } -} - -fn app_elem() -> Component { +fn app() -> Box { oml!( - tab { - div { - text("Hello, World!";) - } - - div { - button("click me"; toggle = false, on_click = onclick) - } - } + text("Hello, World!";) ) } diff --git a/src/ui.rs b/src/ui.rs deleted file mode 100644 index 0f5baec..0000000 --- a/src/ui.rs +++ /dev/null @@ -1,505 +0,0 @@ -use std::collections::HashMap; - -use crate::{ - create_frame, - key::{self, KeyKind}, - utils::{closest_component, render_to_frame}, - Component, UpdateResponse, -}; - -#[derive(Debug, Clone, PartialEq)] -pub struct Style { - pub bg: Color, - pub fg: Color, - pub outline: Color, - pub font: Font, - - pub hover_bg: Color, - pub hover_fg: Color, - pub hover_outline: Color, - pub hover_font: Font, - pub hover_cursor_fg: Color, - pub hover_cursor_bg: Color, - - pub clicked_bg: Color, - pub clicked_fg: Color, - pub clicked_outline: Color, - pub clicked_font: Font, - - pub selected_bg: Color, - pub selected_fg: Color, - pub selected_font: Font, - - pub cursor_fg: Color, - pub cursor_bg: Color, - - pub is_active: bool, -} - -impl Default for Style { - fn default() -> Style { - Style { - bg: Color::None, - fg: Color::None, - outline: Color::None, - font: Font::None, - - hover_bg: Color::None, - hover_fg: Color::None, - hover_outline: Color::None, - hover_font: Font::None, - hover_cursor_fg: Color::None, - hover_cursor_bg: Color::None, - - clicked_bg: Color::None, - clicked_fg: Color::None, - clicked_outline: Color::None, - clicked_font: Font::None, - - selected_bg: Color::None, - selected_fg: Color::None, - selected_font: Font::None, - cursor_fg: Color::None, - cursor_bg: Color::None, - - is_active: false, - } - } -} - -impl Style { - pub fn get(&self) -> String { - if self.is_active { - format!( - "{}{}{}", - self.fg.prioritize(&self.hover_fg).ansi(), - self.bg.prioritize(&self.hover_bg).ansi_bg(), - self.font.prioritize(&self.hover_font).ansi() - ) - } else { - format!( - "{}{}{}", - self.fg.ansi(), - self.bg.ansi_bg(), - self.font.ansi() - ) - } - } - - pub fn get_outline(&self) -> String { - if self.is_active { - self.outline.prioritize(&self.hover_outline).ansi() - } else { - self.outline.ansi() - } - } - - pub fn get_clicked(&self) -> String { - format!( - "{}{}{}", - self.fg.prioritize(&self.clicked_fg).ansi(), - self.bg.prioritize(&self.clicked_bg).ansi_bg(), - self.font.prioritize(&self.clicked_font).ansi() - ) - } - - pub fn get_selected(&self) -> String { - format!( - "{}{}{}", - self.fg.prioritize(&self.selected_fg).ansi(), - self.bg.prioritize(&self.selected_bg).ansi_bg(), - self.font.prioritize(&self.selected_font).ansi() - ) - } - - pub fn get_cursor(&self) -> String { - if self.is_active { - format!( - "{}{}", - self.cursor_fg.prioritize(&self.hover_cursor_fg).ansi(), - self.cursor_bg.prioritize(&self.hover_cursor_bg).ansi_bg(), - ) - } else { - format!("{}{}", self.cursor_fg.ansi(), self.cursor_bg.ansi_bg()) - } - } - - pub fn write(&self, s: &str) -> String { - format!("{}{}\x1b[0m", self.get(), s) - } - - pub fn write_outline(&self, s: &str) -> String { - format!("{}{}\x1b[0m", self.get_outline(), s) - } - - pub fn write_clicked(&self, s: &str) -> String { - format!("{}{}\x1b[0m", self.get_clicked(), s) - } - - pub fn write_selected(&self, s: &str) -> String { - format!("{}{}\x1b[0m", self.get_selected(), s) - } - - pub fn write_cursor(&self, s: &str) -> String { - format!("{}{}\x1b[0m", self.get_cursor(), s) - } -} - -#[derive(Debug, Clone, PartialEq)] -pub enum Font { - None, - Bold, - Underline, - Italic, - Reverse, - Strike, - Mul(Vec), -} - -impl Font { - pub fn ansi(&self) -> String { - String::from(match self { - Font::None => "", - Font::Bold => "\x1b[1m", - Font::Underline => "\x1b[4m", - Font::Italic => "\x1b[3m", - Font::Reverse => "\x1b[7m", - Font::Strike => "\x1b[9m", - Font::Mul(v) => { - let mut s = String::new(); - for n in v { - s += n.ansi().as_str(); - } - return s; - } - }) - } - - pub fn prioritize<'a>(&'a self, secondary: &'a Font) -> &Font { - if secondary == &Font::None { - self - } else { - secondary - } - } -} - -#[derive(Debug, Clone, PartialEq)] -pub enum Color { - None, - Rgb(u8, u8, u8), - Black, - Red, - Green, - Yellow, - Blue, - Magenta, - Cyan, - White, -} - -impl Color { - pub fn ansi(&self) -> String { - String::from(match self { - Color::None => "", - Self::Rgb(r, g, b) => { - return format!("\x1b[38;2;{r};{g};{b}m"); - } - Color::Black => "\x1b[30m", - Color::Red => "\x1b[31m", - Color::Green => "\x1b[32m", - Color::Yellow => "\x1b[33m", - Color::Blue => "\x1b[34m", - Color::Magenta => "\x1b[35m", - Color::Cyan => "\x1b[36m", - Color::White => "\x1b[37m", - }) - } - - pub fn ansi_bg(&self) -> String { - String::from(match self { - Color::None => "", - Self::Rgb(r, g, b) => { - return format!("\x1b[48;2;{r};{g};{b}m"); - } - Color::Black => "\x1b[40m", - Color::Red => "\x1b[41m", - Color::Green => "\x1b[42m", - Color::Yellow => "\x1b[43m", - Color::Blue => "\x1b[44m", - Color::Magenta => "\x1b[45m", - Color::Cyan => "\x1b[46m", - Color::White => "\x1b[47m", - }) - } - - pub fn prioritize<'a>(&'a self, secondary: &'a Color) -> &Color { - if secondary == &Color::None { - self - } else { - secondary - } - } -} - -pub fn div() -> Component { - let mut component = Component::new(); - - component.binds = HashMap::from([ - (KeyKind::Up, String::from("up")), - (KeyKind::Down, String::from("down")), - (KeyKind::Left, String::from("left")), - (KeyKind::Right, String::from("right")), - ]); - - fn update(this: &mut Component, k: key::Key) -> UpdateResponse { - if let Some(v) = this.binds.get(&k.kind) { - match v.as_str() { - "up" => { - this.child = - closest_component(&this.children, this.child, crate::utils::Direction::Up); - } - - "down" => { - this.child = closest_component( - &this.children, - this.child, - crate::utils::Direction::Down, - ); - } - - "left" => { - this.child = closest_component( - &this.children, - this.child, - crate::utils::Direction::Left, - ); - } - - "right" => { - this.child = closest_component( - &this.children, - this.child, - crate::utils::Direction::Right, - ); - } - - _ => {} - } - } else { - if let Some(child) = this.get_child() { - return (child.update)(child, k); - } - } - - UpdateResponse::None - } - - fn render(this: &mut Component) -> String { - let mut frame: Vec = create_frame!(this.width, this.height); - for (i, child) in &mut this.children.iter_mut().enumerate() { - if child.width == 0 { - child.width = this.width; - } - if child.height == 0 { - child.height = this.height; - } - child.style.is_active = this.style.is_active && i == this.child; - render_to_frame(&mut frame, child); - } - frame.join("\n") - } - - fn tick(this: &mut Component, i: usize) { - if let Some(child) = this.get_child() { - return (child.tick)(child, i); - } - } - - component.update = update; - component.render = render; - component.tick = tick; - component -} - -pub fn text() -> Component { - let mut component = Component::new(); - - fn update(_: &mut Component, _: key::Key) -> UpdateResponse { - UpdateResponse::None - } - - fn render(s: &mut Component) -> String { - s.expr.clone() - } - - component.update = update; - component.render = render; - component -} - -pub fn button() -> Component { - let mut component = Component::new(); - - component.binds = HashMap::from([(key::KeyKind::Enter, String::from("click"))]); - - fn update(this: &mut Component, k: key::Key) -> UpdateResponse { - if let Some(v) = this.binds.get(&k.kind) { - if v == "click" { - if this.toggle { - this.clicked = !this.clicked; - (this.on_click)(this); - } else { - return UpdateResponse::Tick(vec![1, 100]); - } - } - } - UpdateResponse::None - } - - fn tick(this: &mut Component, i: usize) { - if i == 0 { - this.clicked = true; - (this.on_click)(this); - } else if i == 1 { - this.clicked = false; - (this.on_click)(this); - } - } - - fn render(this: &mut Component) -> String { - if this.clicked { - return this.style.write_clicked(&this.expr); - } - this.style.write(&this.expr) - } - - component.update = update; - component.render = render; - component.tick = tick; - component -} - -pub fn tab() -> Component { - let mut component = Component::new(); - - component.binds = HashMap::from([ - (key::KeyKind::Tab, String::from("next")), - (key::KeyKind::ShiftTab, String::from("previous")), - ]); - - component.style.clicked_fg = crate::ui::Color::Red; - - fn update(this: &mut Component, k: key::Key) -> UpdateResponse { - if let Some(v) = this.binds.get(&k.kind) { - match v.as_str() { - "next" => { - if this.child + 1 < this.children.len() { - this.child += 1; - } else { - this.child = 0; - } - } - "previous" => { - if this.child == 0 { - this.child = this.children.len() - 1; - } else { - this.child -= 1; - } - } - _ => {} - } - } else { - if let Some(child) = this.get_child() { - return (child.update)(child, k); - } else { - } - } - UpdateResponse::None - } - - fn render(this: &mut Component) -> String { - let mut frame: Vec = create_frame!(this.width, this.height - 1); - let w = this.width; - let h = this.height; - if let Some(child) = this.get_child() { - if child.width == 0 { - child.width = w; - } - if child.height == 0 { - child.height = h; - } - child.style.is_active = true; - render_to_frame(&mut frame, child); - } - let mut v: String = " ".repeat((this.width / 2) - this.children.len()); - for (i, _) in (&this.children).into_iter().enumerate() { - if i == this.child { - v += this.style.write_clicked("*").as_str() - } else { - v += this.style.write("*").as_str() - } - } - format!("{}\n{}", v, frame.join("\n")) - } - - fn tick(this: &mut Component, i: usize) { - if let Some(child) = this.get_child() { - return (child.tick)(child, i); - } - } - - component.update = update; - component.tick = tick; - component.render = render; - component -} - -pub fn menu() -> Component { - let mut component = Component::new(); - - component.style.selected_bg = Color::Magenta; - component.style.selected_fg = Color::Black; - component.style.cursor_fg = Color::Magenta; - - fn render(this: &mut Component) -> String { - let mut res: Vec = Vec::new(); - - for (i, item) in this.children.iter_mut().enumerate() { - if i == this.child { - res.push(format!( - "{}{}", - this.style.write_cursor("> "), - this.style.write_selected(&item.expr) - )); - } else { - res.push(format!(" {}", &item.expr)); - } - } - - res.join(&this.style.write("\n")) - } - - fn update(this: &mut Component, k: key::Key) -> UpdateResponse { - if k.kind == key::KeyKind::Down { - if this.children.len() > this.child + 1 { - this.child += 1; - } else { - this.child = 0; - } - } else if k.kind == key::KeyKind::Up { - if this.child > 0 { - this.child -= 1; - } else { - this.child = this.children.len() - 1; - } - } else if k.kind == key::KeyKind::Enter { - (this.on_click)(this) - } - UpdateResponse::None - } - - component.render = render; - component.update = update; - component -} diff --git a/src/utils.rs b/src/utils.rs index 2881510..c40f70d 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,10 +1,11 @@ -use crate::Component; +use crate::Element; use lazy_static::lazy_static; use regex::Regex; use std::{ collections::HashMap, io::{stdout, Write}, }; + lazy_static! { pub static ref ANSI: Regex = Regex::new(r"(\x1b\[([0-9;]*)[a-zA-Z])+").unwrap(); } @@ -81,11 +82,12 @@ fn merge_line(frame_: &str, line_: &str, x: usize) -> String { } /// Render to a frame -pub fn render_to_frame(frame: &mut Vec, component: &mut Component) { - for (i, line) in (component.render)(component).split('\n').enumerate() { - if (component.y + i) < frame.len() { - let frame_line = frame.get_mut(component.y + i).unwrap(); - *frame_line = merge_line(&frame_line, line, component.x); +pub fn render_to_frame(frame: &mut Vec, element: &mut Box) { + let data = element.get_data(); + for (i, line) in element.render().split('\n').enumerate() { + if (data.y + i) < frame.len() { + let frame_line = frame.get_mut(data.y + i).unwrap(); + *frame_line = merge_line(&frame_line, line, data.x); } } } @@ -117,22 +119,28 @@ pub enum Direction { } pub fn closest_component( - components: &[Component], + components: &[Box], current_index: usize, direction: Direction, ) -> usize { - let current = &components[current_index]; + let current = &components[current_index].get_data(); components .iter() .enumerate() // Keep track of indices - .filter(|(_, comp)| match direction { - Direction::Left => comp.x < current.x && comp.y == current.y, // Left - Direction::Right => comp.x > current.x && comp.y == current.y, // Right - Direction::Up => comp.y < current.y && comp.x == current.x, // Up - Direction::Down => comp.y > current.y && comp.x == current.x, // Down + .filter(|(_, comp_)| { + let comp = comp_.get_data(); + match direction { + Direction::Left => comp.x < current.x && comp.y == current.y, // Left + Direction::Right => comp.x > current.x && comp.y == current.y, // Right + Direction::Up => comp.y < current.y && comp.x == current.x, // Up + Direction::Down => comp.y > current.y && comp.x == current.x, // Down + } }) - .min_by_key(|(_, comp)| current.x.abs_diff(comp.x) + current.y.abs_diff(comp.y)) // Find the closest component + .min_by_key(|(_, comp_)| { + let comp = comp_.get_data(); + current.x.abs_diff(comp.x) + current.y.abs_diff(comp.y) + }) // Find the closest component .map(|(index, _)| index) // Return the index of the closest component .unwrap_or(current_index) // If no component is found, return the current index }