use crossterm::event::{Event, KeyCode, KeyEventKind};
use ratatui::{
layout::{Direction, Flex},
style::Stylize,
text::Line,
};
use ratatui_kit::prelude::*;
use std::{cell::RefCell, rc::Rc};
use crate::theme::AppChromeTheme;
pub(crate) fn focus_row(mut index: ratatui_kit::State<usize>, row: usize) -> Handler<'static, ()> {
Handler::from(move |_: ()| index.set(row))
}
#[derive(Props, Default)]
pub struct SettingItemProps {
pub is_editing: bool,
pub top_title: String,
pub bottom_title: String,
pub on_click: Handler<'static, ()>,
pub on_activate: Handler<'static, ()>,
pub children: Vec<AnyElement<'static>>,
}
#[component]
pub fn SettingItem(
props: &mut SettingItemProps,
mut hooks: Hooks,
) -> impl Into<AnyElement<'static>> {
let theme = hooks.use_component_theme::<AppChromeTheme>();
let is_editing = props.is_editing;
let mut on_click = props.on_click.take();
let mut on_activate = props.on_activate.take();
hooks.use_pointer(PointerOptions::default(), move |ev| {
if let PointerKind::Click { .. } = ev.kind {
if on_click.is_default() && on_activate.is_default() {
return EventResult::Ignored;
}
on_click(());
if is_editing && !on_activate.is_default() {
on_activate(());
}
EventResult::Consumed
} else {
EventResult::Ignored
}
});
let mut top_title = Line::from(props.top_title.clone());
let mut bottom_title = Line::from(props.bottom_title.clone());
if props.is_editing {
top_title = top_title.not_dim();
bottom_title = bottom_title.not_dim();
}
let border_style = if props.is_editing {
theme.border.patch(theme.highlight)
} else {
theme.border
};
element!(Border(
top_title: top_title,
border_style: border_style,
bottom_title: bottom_title,
style: if props.is_editing {
theme.border.not_dim()
} else {
theme.border
}
) {
{ std::mem::take(&mut props.children) }
})
}
#[derive(Props, Default)]
pub struct AdjustableSettingItemProps {
pub is_editing: bool,
pub label: String,
pub value: String,
pub on_decrease: Handler<'static, ()>,
pub on_increase: Handler<'static, ()>,
pub on_click: Handler<'static, ()>,
}
#[component]
pub fn AdjustableSettingItem(
props: &mut AdjustableSettingItemProps,
mut hooks: Hooks,
) -> impl Into<AnyElement<'static>> {
let theme = hooks.use_component_theme::<AppChromeTheme>();
let is_editing = props.is_editing;
let on_decrease = Rc::new(RefCell::new(props.on_decrease.take()));
let on_increase = Rc::new(RefCell::new(props.on_increase.take()));
let mut on_click = props.on_click.take();
hooks.use_event_handler(EventScope::Current, EventPriority::Normal, {
let on_decrease = on_decrease.clone();
let on_increase = on_increase.clone();
move |event| {
let Event::Key(key) = event else {
return EventResult::Ignored;
};
if key.kind != KeyEventKind::Press {
return EventResult::Ignored;
}
if !is_editing {
return EventResult::Ignored;
}
match key.code {
KeyCode::Left | KeyCode::Char('h') => {
(on_decrease.borrow_mut())(());
EventResult::Consumed
}
KeyCode::Right | KeyCode::Char('l') => {
(on_increase.borrow_mut())(());
EventResult::Consumed
}
_ => EventResult::Ignored,
}
}
});
hooks.use_pointer(PointerOptions::default(), {
let on_decrease = on_decrease.clone();
let on_increase = on_increase.clone();
move |ev| {
if let PointerKind::Click { .. } = ev.kind {
on_click(());
if is_editing {
let mid = ev.area.x + ev.area.width / 2;
if ev.position.x < mid {
(on_decrease.borrow_mut())(());
} else {
(on_increase.borrow_mut())(());
}
}
EventResult::Consumed
} else {
EventResult::Ignored
}
}
});
element!(SettingItem(is_editing: is_editing) {
View(flex_direction: Direction::Horizontal, justify_content: Flex::SpaceBetween) {
widget(Line::from(props.label.clone()).style(theme.text))
widget(Line::from(props.value.clone()).style(theme.text))
}
})
}