use std::collections::HashSet;
use std::time::Instant;
use ratatui::widgets::{ListState, ScrollbarState};
use super::DependencyMap;
use super::header::{Header, Tab};
pub struct App<'a> {
pub should_quit: bool,
pub header: Header<'a>,
pub installed: HashSet<String>,
pub dependencies: DependencyMap,
pub components: Vec<String>,
pub components_scroll: usize,
pub components_scroll_state: ScrollbarState,
pub components_list_state: ListState,
pub components_search_query: String,
pub components_search_active: bool,
pub components_checked: HashSet<String>,
pub demos: Vec<String>,
pub demos_scroll: usize,
pub demos_scroll_state: ScrollbarState,
pub demos_list_state: ListState,
pub demos_search_query: String,
pub demos_search_active: bool,
pub demos_checked: HashSet<String>,
pub hooks_scroll: usize,
pub hooks_scroll_state: ScrollbarState,
pub hooks_list_state: ListState,
pub hooks_search_query: String,
pub hooks_search_active: bool,
pub hooks_checked: HashSet<String>,
pub terminal_width: u16,
pub icons_selected: usize,
pub show_popup: bool,
pub popup_confirm_focused: bool, pub show_help_popup: bool,
pub last_click_time: Option<Instant>,
pub last_click_pos: Option<(u16, u16)>,
pub last_escape_time: Option<Instant>,
}
impl<'a> App<'a> {
pub fn new(
title: &'a str,
all_items: Vec<String>,
installed: HashSet<String>,
dependencies: DependencyMap,
) -> Self {
let (demos, components): (Vec<_>, Vec<_>) =
all_items.into_iter().partition(|s| s.starts_with("demo_"));
App {
should_quit: false,
header: Header::new(title),
installed,
dependencies,
components,
components_scroll: 0,
components_scroll_state: ScrollbarState::default(),
components_list_state: ListState::default(),
components_search_query: String::new(),
components_search_active: false,
components_checked: HashSet::new(),
demos,
demos_scroll: 0,
demos_scroll_state: ScrollbarState::default(),
demos_list_state: ListState::default(),
demos_search_query: String::new(),
demos_search_active: false,
demos_checked: HashSet::new(),
hooks_scroll: 0,
hooks_scroll_state: ScrollbarState::default(),
hooks_list_state: ListState::default(),
hooks_search_query: String::new(),
hooks_search_active: false,
hooks_checked: HashSet::new(),
terminal_width: 0,
icons_selected: 0,
show_popup: false,
popup_confirm_focused: true, show_help_popup: false,
last_click_time: None,
last_click_pos: None,
last_escape_time: None,
}
}
pub fn on_up(&mut self) {
match self.header.tabs.current {
Tab::Components => {
self.components_scroll = self.components_scroll.saturating_sub(1);
self.components_scroll_state = self.components_scroll_state.position(self.components_scroll);
}
Tab::Demos => {
self.demos_scroll = self.demos_scroll.saturating_sub(1);
self.demos_scroll_state = self.demos_scroll_state.position(self.demos_scroll);
}
Tab::Hooks => {
self.hooks_scroll = self.hooks_scroll.saturating_sub(1);
self.hooks_scroll_state = self.hooks_scroll_state.position(self.hooks_scroll);
}
Tab::Icons => {
self.icons_selected = self.icons_selected.saturating_sub(1);
}
Tab::Blocks | Tab::Settings => {}
}
}
pub fn on_down(&mut self) {
match self.header.tabs.current {
Tab::Components => {
self.components_scroll = self.components_scroll.saturating_add(1);
self.components_scroll_state = self.components_scroll_state.position(self.components_scroll);
}
Tab::Demos => {
self.demos_scroll = self.demos_scroll.saturating_add(1);
self.demos_scroll_state = self.demos_scroll_state.position(self.demos_scroll);
}
Tab::Hooks => {
self.hooks_scroll = self.hooks_scroll.saturating_add(1);
self.hooks_scroll_state = self.hooks_scroll_state.position(self.hooks_scroll);
}
Tab::Icons => {
if self.icons_selected < 1 {
self.icons_selected += 1;
}
}
Tab::Blocks | Tab::Settings => {}
}
}
pub fn on_right(&mut self) {
self.header.tabs.next();
}
pub fn on_left(&mut self) {
self.header.tabs.previous();
}
pub fn on_key(&mut self, c: char) {
if c == 'q' {
self.should_quit = true;
}
}
pub fn on_tick(&mut self) {}
pub fn on_mouse_click(&mut self, column: u16, row: u16, terminal_width: u16) {
self.terminal_width = terminal_width;
if row < 3 {
self.header.handle_click(column, terminal_width);
}
}
pub fn toggle_components_search(&mut self) {
self.components_search_active = !self.components_search_active;
if !self.components_search_active {
self.components_search_query.clear();
self.components_scroll = 0;
}
}
pub fn components_search_input(&mut self, c: char) {
if self.components_search_active {
self.components_search_query.push(c);
self.components_scroll = 0;
}
}
pub fn components_search_backspace(&mut self) {
if self.components_search_active {
self.components_search_query.pop();
self.components_scroll = 0;
}
}
pub fn toggle_component_checkbox(&mut self, component: &str) {
if self.components_checked.contains(component) {
self.components_checked.remove(component);
} else {
self.components_checked.insert(component.to_string());
}
}
pub fn toggle_popup(&mut self) {
self.show_popup = !self.show_popup;
if self.show_popup {
self.popup_confirm_focused = true; }
}
pub fn toggle_popup_button_focus(&mut self) {
self.popup_confirm_focused = !self.popup_confirm_focused;
}
pub fn toggle_help_popup(&mut self) {
self.show_help_popup = !self.show_help_popup;
}
pub fn deselect_all_components(&mut self) {
self.components_checked.clear();
}
pub fn toggle_demos_search(&mut self) {
self.demos_search_active = !self.demos_search_active;
if !self.demos_search_active {
self.demos_search_query.clear();
self.demos_scroll = 0;
}
}
pub fn demos_search_input(&mut self, c: char) {
if self.demos_search_active {
self.demos_search_query.push(c);
self.demos_scroll = 0;
}
}
pub fn demos_search_backspace(&mut self) {
if self.demos_search_active {
self.demos_search_query.pop();
self.demos_scroll = 0;
}
}
pub fn toggle_demo_checkbox(&mut self, demo: &str) {
if self.demos_checked.contains(demo) {
self.demos_checked.remove(demo);
} else {
self.demos_checked.insert(demo.to_string());
}
}
pub fn deselect_all_demos(&mut self) {
self.demos_checked.clear();
}
pub fn get_demos_double_click_info(&self, column: u16, row: u16, terminal_width: u16) -> Option<usize> {
if matches!(self.header.tabs.current, Tab::Demos) && !self.show_popup {
let left_panel_width = (terminal_width as f32 * 0.35) as u16;
if column <= left_panel_width && row > 6 {
let visual_row = (row - 7) as usize;
let viewport_offset = self.demos_list_state.offset();
let item_index = visual_row + viewport_offset;
return Some(item_index);
}
}
None
}
pub fn get_components_double_click_info(
&self,
column: u16,
row: u16,
terminal_width: u16,
) -> Option<usize> {
if matches!(self.header.tabs.current, Tab::Components) && !self.show_popup {
let left_panel_width = (terminal_width as f32 * 0.35) as u16;
if column <= left_panel_width && row > 6 {
let visual_row = (row - 7) as usize;
let viewport_offset = self.components_list_state.offset();
let item_index = visual_row + viewport_offset;
return Some(item_index);
}
}
None
}
pub fn toggle_hooks_search(&mut self) {
self.hooks_search_active = !self.hooks_search_active;
if !self.hooks_search_active {
self.hooks_search_query.clear();
self.hooks_scroll = 0;
}
}
pub fn hooks_search_input(&mut self, c: char) {
if self.hooks_search_active {
self.hooks_search_query.push(c);
self.hooks_scroll = 0;
}
}
pub fn hooks_search_backspace(&mut self) {
if self.hooks_search_active {
self.hooks_search_query.pop();
self.hooks_scroll = 0;
}
}
pub fn toggle_hook_checkbox(&mut self, hook: &str) {
if self.hooks_checked.contains(hook) {
self.hooks_checked.remove(hook);
} else {
self.hooks_checked.insert(hook.to_string());
}
}
pub fn deselect_all_hooks(&mut self) {
self.hooks_checked.clear();
}
pub fn get_hooks_double_click_info(&self, column: u16, row: u16, terminal_width: u16) -> Option<usize> {
if matches!(self.header.tabs.current, Tab::Hooks) && !self.show_popup {
let left_panel_width = (terminal_width as f32 * 0.35) as u16;
if column <= left_panel_width && row > 6 {
let visual_row = (row - 7) as usize;
let viewport_offset = self.hooks_list_state.offset();
let item_index = visual_row + viewport_offset;
return Some(item_index);
}
}
None
}
pub fn get_dependencies(&self, component: &str) -> Option<&Vec<String>> {
self.dependencies.get(component)
}
pub fn jump_to_letter_components(&mut self, letter: char) {
use super::widgets::helpers::filter_items;
let components_refs: Vec<&str> = self.components.iter().map(|s| s.as_str()).collect();
let filtered = filter_items(&components_refs, &self.components_search_query);
let lower = letter.to_ascii_lowercase();
if let Some(idx) = filtered.iter().position(|c| c.to_lowercase().starts_with(lower)) {
self.components_scroll = idx;
self.components_scroll_state = self.components_scroll_state.position(idx);
}
}
pub fn jump_to_letter_demos(&mut self, letter: char) {
use super::widgets::helpers::filter_items;
let demos_refs: Vec<&str> = self.demos.iter().map(|s| s.as_str()).collect();
let filtered = filter_items(&demos_refs, &self.demos_search_query);
let lower = letter.to_ascii_lowercase();
if let Some(idx) = filtered.iter().position(|d| {
d.strip_prefix("demo_")
.unwrap_or(d)
.to_lowercase()
.starts_with(lower)
}) {
self.demos_scroll = idx;
self.demos_scroll_state = self.demos_scroll_state.position(idx);
}
}
pub fn jump_to_letter_hooks(&mut self, letter: char) {
use super::tabs::tab2_hooks::HOOKS;
use super::widgets::helpers::filter_items;
let filtered = filter_items(HOOKS, &self.hooks_search_query);
let lower = letter.to_ascii_lowercase();
if let Some(idx) = filtered.iter().position(|h| {
h.strip_prefix("Use ")
.unwrap_or(h)
.to_lowercase()
.starts_with(lower)
}) {
self.hooks_scroll = idx;
self.hooks_scroll_state = self.hooks_scroll_state.position(idx);
}
}
}