Large structure update

This commit is contained in:
2024-10-30 14:14:00 +01:00
parent 943bcf874a
commit 87ae71072e
6 changed files with 161 additions and 629 deletions
+38 -63
View File
@@ -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<dyn Element>>;
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<Component>,
pub child: usize,
pub clicked: bool,
pub toggle: bool,
pub binds: HashMap<key::KeyKind, String>,
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<u32>),
}
pub struct App {
component: Component,
element: Box<dyn Element>,
}
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<dyn Element>) {
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<String> = 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<u32>),
}
+94 -24
View File
@@ -8,7 +8,7 @@ macro_rules! __oml {
}};
($tag:ident ($($k:ident = $v:expr),*) {$($inner:tt)*} $($rest:tt)+) => {{
let mut comps: Vec<osui::Component> = osui::__oml!($($rest)+);
let mut comps: Vec<Box<dyn osui::Element>> = 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::Component> = osui::__oml!($($rest)+);
let mut comps: Vec<Box<dyn osui::Element>> = 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::Component> = 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<Box<dyn osui::Element>> = 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::Component> = 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<Box<dyn osui::Element>> = 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::Component> = osui::__oml!($($rest)+);
let mut comps: Vec<Box<dyn osui::Element>> = 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::Component> = osui::__oml!($($rest)+);
comps.insert(0, oml!($expr));
($text:expr; $($rest:tt)+) => {{
let mut comps: Vec<Box<dyn osui::Element>> = 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<Box<dyn Element>>,
pub child: usize,
pub text: String,
pub style: Style,
$( $inner )*
}
impl Element for $name {
fn get_child(&mut self) -> Option<&mut Box<dyn Element>> {
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 )*
}
}
}
};
}
+6 -22
View File
@@ -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<Text> {
oml!(
tab {
div {
text("Hello, World!";)
}
div {
button("click me"; toggle = false, on_click = onclick)
}
}
text("Hello, World!";)
)
}
-505
View File
@@ -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<Font>),
}
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<String> = 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<String> = 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<String> = 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
}
+22 -14
View File
@@ -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<String>, 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<String>, element: &mut Box<dyn Element>) {
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<dyn Element>],
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
}