mal/ui/
mod.rs

1pub mod help;
2mod side_menu;
3mod top_three;
4pub mod util;
5use crate::app::*;
6use ratatui::{
7    layout::{Alignment, Constraint, Direction, Layout, Margin, Rect},
8    style::Style,
9    text::{Line, Span},
10    widgets::{Block, BorderType, Borders, Paragraph},
11    Frame,
12};
13use util::get_color;
14mod display_block;
15
16pub fn draw_main_layout(f: &mut Frame, app: &mut App) {
17    let margin = util::get_main_layout_margin(app);
18    let app_area;
19    if app.app_config.behavior.show_logger {
20        let logger_area;
21        [app_area, logger_area] = Layout::default()
22            .direction(ratatui::layout::Direction::Horizontal)
23            .constraints([Constraint::Percentage(60), Constraint::Percentage(40)])
24            .areas(f.area());
25        app.render_logs(f, logger_area);
26    } else {
27        app_area = f.area();
28    }
29
30    // draw the logger area
31
32    let parent_layout = Layout::default()
33        .direction(Direction::Vertical)
34        .constraints([Constraint::Length(3), Constraint::Min(1)].as_ref())
35        .margin(margin)
36        .split(app_area);
37
38    // Search Input and help
39    draw_input_and_help_box(f, app, parent_layout[0]);
40
41    // draw side and dipsplay sections
42    let chunk = side_menu::draw_routes(f, app, parent_layout[1]);
43    display_block::draw_display_layout(f, app, chunk);
44}
45
46pub fn draw_input_and_help_box(f: &mut Frame, app: &App, layout_chunk: Rect) {
47    let [search_chunk, title_chunk] = Layout::default()
48        .direction(Direction::Horizontal)
49        .constraints([Constraint::Percentage(18), Constraint::Percentage(82)])
50        .areas(layout_chunk);
51    // removing the little gap
52    let search_chunk = search_chunk.inner(Margin::new(1, 0));
53    let current_block = app.active_block;
54
55    let highlight_state = current_block == ActiveBlock::Input;
56
57    let input_string: String = app.input.iter().collect();
58    let lines = Span::from(input_string);
59    let input = Paragraph::new(lines).block(
60        Block::default()
61            .borders(Borders::ALL)
62            .border_type(BorderType::Rounded)
63            .title(Span::styled(
64                "Search",
65                get_color(highlight_state, app.app_config.theme),
66            ))
67            .border_style(get_color(highlight_state, app.app_config.theme)),
68    );
69    f.render_widget(input, search_chunk);
70
71    let mut title = app.display_block_title.clone();
72    if title.is_empty() {
73        title = "Home".to_string(); // Default title , since i couldn't initialize it in app.rs:15
74    }
75    let block = Block::default()
76        .borders(Borders::ALL)
77        .border_type(BorderType::Rounded)
78        .border_style(Style::default().fg(app.app_config.theme.inactive));
79
80    let lines = Line::from(Span::from(title))
81        .alignment(Alignment::Center)
82        .style(Style::default().fg(app.app_config.theme.banner));
83
84    let help = Paragraph::new(lines)
85        .block(block)
86        .alignment(Alignment::Center)
87        .style(Style::default().fg(app.app_config.theme.banner));
88    f.render_widget(help, title_chunk);
89}
90
91pub fn format_number_with_commas(number: u64) -> String {
92    let num_str = number.to_string();
93    let mut result = String::new();
94    let mut count = 0;
95
96    for c in num_str.chars().rev() {
97        if count == 3 {
98            result.push(',');
99            count = 0;
100        }
101        result.push(c);
102        count += 1;
103    }
104
105    result.chars().rev().collect()
106}
107
108fn get_end_index(app: &App, typ: &str) -> usize {
109    match typ {
110        "anime" => {
111            let data_len = app.search_results.anime.as_ref().unwrap().data.len();
112            if app.start_card_list_index as usize + DISPLAY_RAWS_NUMBER * DISPLAY_COLUMN_NUMBER
113                > data_len - 1
114            // end is bigger than last index
115            {
116                data_len - 1
117            } else {
118                // start index + 14 to get the last index
119                app.start_card_list_index as usize + DISPLAY_COLUMN_NUMBER * DISPLAY_RAWS_NUMBER - 1
120            }
121        }
122        "manga" => {
123            let data_len = app.search_results.manga.as_ref().unwrap().data.len();
124            if app.start_card_list_index as usize + DISPLAY_RAWS_NUMBER * DISPLAY_COLUMN_NUMBER
125                > data_len - 1
126            // end is bigger than last index in the data
127            {
128                data_len - 1
129            } else {
130                // start index + 14 to get the last index
131                app.start_card_list_index as usize + DISPLAY_COLUMN_NUMBER * DISPLAY_RAWS_NUMBER - 1
132            }
133        }
134        //TODO: handle these cases:
135        "anime_ranking" => {
136            let data_len = app.anime_ranking_data.as_ref().unwrap().data.len();
137            if app.start_card_list_index as usize + DISPLAY_RAWS_NUMBER * DISPLAY_COLUMN_NUMBER
138                > data_len - 1
139            // end is bigger than last index in the data
140            {
141                data_len - 1
142            } else {
143                // start index + 14 to get the last index
144                app.start_card_list_index as usize + DISPLAY_COLUMN_NUMBER * DISPLAY_RAWS_NUMBER - 1
145            }
146        }
147        "manga_ranking" => {
148            let data_len = app.manga_ranking_data.as_ref().unwrap().data.len();
149            if app.start_card_list_index as usize + DISPLAY_RAWS_NUMBER * DISPLAY_COLUMN_NUMBER
150                > data_len - 1
151            // end is bigger than last index in the data
152            {
153                data_len - 1
154            } else {
155                // start index + 14 to get the last index
156                app.start_card_list_index as usize + DISPLAY_COLUMN_NUMBER * DISPLAY_RAWS_NUMBER - 1
157            }
158        }
159        _ => panic!("Unknown type: {}", typ),
160    }
161}
162
163pub fn get_end_card_index(app: &App) -> usize {
164    match app.active_display_block {
165        ActiveDisplayBlock::SearchResultBlock => match app.search_results.selected_tab {
166            SelectedSearchTab::Manga => get_end_index(app, "manga"),
167            SelectedSearchTab::Anime => get_end_index(app, "anime"),
168        },
169        ActiveDisplayBlock::AnimeRanking => get_end_index(app, "anime_ranking"),
170        ActiveDisplayBlock::MangaRanking => get_end_index(app, "manga_ranking"),
171        _ => {
172            // Default case, if no specific block is active
173            get_end_index(app, "anime")
174        }
175    }
176}