Skip to main content

atmos/kernel/
dialog.rs

1// src/dialog.rs
2#![allow(dead_code)]
3extern crate alloc;
4use alloc::string::String;
5use alloc::vec::Vec;
6
7use crate::kernel::draw::{Color, Screen};
8
9#[derive(Debug, Clone, PartialEq)]
10pub enum DialogMode {
11    Open,
12    Save,
13}
14
15pub struct FileDialog {
16    pub mode: DialogMode,
17    pub title: String,
18    pub files: Vec<String>,
19    pub selected_idx: Option<usize>,
20    pub filename_input: String,
21    pub is_open: bool,
22    pub result: Option<String>,
23    pub offset_y: usize,
24}
25
26impl Default for FileDialog {
27    fn default() -> Self {
28        Self::new()
29    }
30}
31
32impl FileDialog {
33    pub fn new() -> Self {
34        Self {
35            mode: DialogMode::Open,
36            title: String::new(),
37            files: Vec::new(),
38            selected_idx: None,
39            filename_input: String::new(),
40            is_open: false,
41            result: None,
42            offset_y: 0,
43        }
44    }
45
46    pub fn open(&mut self, mode: DialogMode, default_filename: &str) {
47        self.mode = mode.clone();
48        self.title = if mode == DialogMode::Open {
49            String::from("Open File")
50        } else {
51            String::from("Save File")
52        };
53        self.filename_input = String::from(default_filename);
54        self.is_open = true;
55        self.result = None;
56        self.selected_idx = None;
57        self.offset_y = 0;
58        self.refresh_files();
59    }
60
61    pub fn refresh_files(&mut self) {
62        self.files.clear();
63        let fs = crate::kernel::fs::get_fs();
64        for meta in fs.list_files() {
65            self.files.push(String::from(meta.get_filename()));
66        }
67    }
68
69    pub fn close(&mut self) {
70        self.is_open = false;
71    }
72
73    pub fn handle_mouse(
74        &mut self,
75        mx: i32,
76        my: i32,
77        left_click: bool,
78        screen_width: u32,
79        screen_height: u32,
80    ) -> bool {
81        if !self.is_open {
82            return false;
83        }
84
85        let cx = screen_width as i32 / 2;
86        let cy = screen_height as i32 / 2;
87        let w = 600;
88        let h = 400;
89        let x = cx - w / 2;
90        let y = cy - h / 2;
91
92        if left_click {
93            // Check if clicked outside
94            if mx < x || mx > x + w || my < y || my > y + h {
95                // Ignore or close? Let's just ignore.
96            } else {
97                // Clicked inside
98                // Check close button
99                if mx >= x + w - 30 && mx <= x + w - 10 && my >= y + 10 && my <= y + 30 {
100                    self.close();
101                    return true;
102                }
103
104                // Check file list
105                let list_x = x + 20;
106                let list_y = y + 50;
107                let list_w = w - 40;
108                let list_h = 240;
109
110                if mx >= list_x && mx <= list_x + list_w && my >= list_y && my <= list_y + list_h {
111                    let rel_y = my - list_y;
112                    let item_idx = (rel_y / 30) as usize + self.offset_y;
113                    if item_idx < self.files.len() {
114                        self.selected_idx = Some(item_idx);
115                        self.filename_input = self.files[item_idx].clone();
116                    }
117                }
118
119                // Check buttons
120                let btn_y = y + h - 40;
121                let cancel_x = x + w - 200;
122                let action_x = x + w - 90;
123
124                if mx >= cancel_x && mx <= cancel_x + 80 && my >= btn_y && my <= btn_y + 30 {
125                    self.close();
126                } else if mx >= action_x
127                    && mx <= action_x + 80
128                    && my >= btn_y
129                    && my <= btn_y + 30
130                    && !self.filename_input.is_empty()
131                {
132                    self.result = Some(self.filename_input.clone());
133                    self.close();
134                }
135            }
136        } else {
137            // Check wheel scroll (pseudo, using left/right for now since wheel isn't passed here easily unless modified)
138        }
139
140        true // Consume event
141    }
142
143    pub fn handle_key(&mut self, keycode: u8, c: Option<char>) -> bool {
144        if !self.is_open {
145            return false;
146        }
147
148        if keycode == 0x01 {
149            // ESC
150            self.close();
151            return true;
152        }
153
154        if keycode == 0x1C {
155            // Enter
156            if !self.filename_input.is_empty() {
157                self.result = Some(self.filename_input.clone());
158                self.close();
159            }
160            return true;
161        }
162
163        if keycode == 0x0E {
164            // Backspace
165            if !self.filename_input.is_empty() {
166                self.filename_input.pop();
167            }
168            return true;
169        }
170
171        if let Some(ch) = c {
172            if ch.is_ascii_alphanumeric() || ch == '.' || ch == '_' || ch == '-' {
173                self.filename_input.push(ch);
174            }
175        }
176
177        true // Consume event
178    }
179
180    pub fn draw(&self, screen: &mut Screen) {
181        if !self.is_open {
182            return;
183        }
184
185        let t = &crate::kernel::config::get_config().theme;
186
187        let cx = screen.width / 2;
188        let cy = screen.height / 2;
189        let w = 600;
190        let h = 400;
191        let x = cx - w / 2;
192        let y = cy - h / 2;
193
194        // Shadow
195        screen.boxfill(x + 5, y + 5, x + w + 5, y + h + 5, Color(0x80000000));
196
197        // Background
198        screen.boxfill(x, y, x + w, y + h, Color(t.login_dialog_bg));
199
200        // Title bar
201        screen.boxfill(x, y, x + w, y + 40, Color(t.window_title_bar));
202        screen.draw_string_vector(x + 20, y + 10, &self.title, Color(t.window_title_fg), 20);
203
204        // Close button
205        screen.draw_string_vector(x + w - 30, y + 10, "X", Color(t.win_btn_close), 20);
206
207        // File list background
208        let list_x = x + 20;
209        let list_y = y + 50;
210        let list_w = w - 40;
211        screen.boxfill(x + 20, y + 50, x + w - 20, y + 50 + 240, Color(t.terminal_bg));
212
213        // Files
214        for (i, file) in self.files.iter().skip(self.offset_y).take(8).enumerate() {
215            let item_y = list_y + (i as u32) * 30;
216            if Some(i + self.offset_y) == self.selected_idx {
217                screen.boxfill(
218                    list_x,
219                    item_y,
220                    list_x + list_w,
221                    item_y + 30,
222                    Color(t.focus_highlight),
223                );
224            }
225            screen.draw_string_vector(list_x + 10, item_y + 6, file, Color(t.terminal_fg), 16);
226        }
227
228        // Input box
229        let input_y = y + 310;
230        screen.draw_string_vector(x + 20, input_y + 5, "Filename:", Color(t.window_title_fg), 16);
231        screen.boxfill(
232            x + 110,
233            input_y,
234            x + w - 20,
235            input_y + 30,
236            Color(t.button_bg),
237        );
238        screen.draw_string_vector(
239            x + 115,
240            input_y + 6,
241            &self.filename_input,
242            Color(t.app_fg),
243            16,
244        );
245
246        // Buttons
247        let btn_y = y + h - 40;
248        let cancel_x = x + w - 200;
249        let action_x = x + w - 90;
250
251        screen.boxfill(
252            cancel_x,
253            btn_y,
254            cancel_x + 80,
255            btn_y + 30,
256            Color(t.button_bg),
257        );
258        screen.draw_string_vector(cancel_x + 10, btn_y + 6, "Cancel", Color(t.button_fg), 16);
259
260        screen.boxfill(action_x, btn_y, action_x + 80, btn_y + 30, Color(t.button_active_bg));
261        screen.draw_string_vector(
262            action_x + 20,
263            btn_y + 6,
264            if self.mode == DialogMode::Open {
265                "Open"
266            } else {
267                "Save"
268            },
269            Color(t.button_active_fg),
270            16,
271        );
272    }
273}
274
275pub static mut GLOBAL_FILE_DIALOG: Option<FileDialog> = None;