1#![allow(dead_code)]
11
12extern crate alloc;
13use alloc::format;
14use alloc::string::{String, ToString};
15use alloc::vec::Vec;
16
17use crate::kernel::draw::{Color, Screen};
18use crate::kernel::text_box::TextBox;
19use crate::kernel::window_mgr::App;
20use crate::os_lib::cloud::{self, MsAuth, OneDriveFile};
21
22#[derive(PartialEq, Clone, Copy)]
24enum Pane {
25 Remote,
26 Local,
27}
28
29pub struct OneDriveApp {
30 auth: MsAuth,
31 remote: Vec<OneDriveFile>,
33 local: Vec<String>,
34 remote_sel: usize,
35 local_sel: usize,
36 pane: Pane,
37 scroll: usize,
38 folder_path: Vec<(String, String)>,
40 device_user_code: String,
42 device_url: String,
43 device_code: String,
44 awaiting_auth: bool,
45 viewer_open: bool,
47 viewer_title: String,
48 viewer_text: String,
49 viewer_scroll: usize,
50 status: String,
52 last_win_w: u32,
54 setup_mode: bool,
57 setup_id_box: TextBox,
58}
59
60impl Default for OneDriveApp {
61 fn default() -> Self {
62 Self::new()
63 }
64}
65
66impl OneDriveApp {
67 pub fn new() -> Self {
68 let auth = cloud::load_ms_auth();
69 let mut app = Self {
70 auth,
71 remote: Vec::new(),
72 local: Vec::new(),
73 remote_sel: 0,
74 local_sel: 0,
75 pane: Pane::Remote,
76 scroll: 0,
77 folder_path: Vec::new(),
78 device_user_code: String::new(),
79 device_url: String::new(),
80 device_code: String::new(),
81 awaiting_auth: false,
82 viewer_open: false,
83 viewer_title: String::new(),
84 viewer_text: String::new(),
85 viewer_scroll: 0,
86 status: String::new(),
87 last_win_w: 900,
88 setup_mode: false,
89 setup_id_box: TextBox::new(),
90 };
91 app.reload_local();
92 if app.auth.is_logged_in() {
93 app.status = String::from("Logged in. Press R to load OneDrive files.");
94 } else if app.auth.has_client() {
95 app.status = String::from("Press L to log in to OneDrive.");
96 } else {
97 app.status = String::from("Setup required: press S to enter client_id.");
98 }
99 app
100 }
101
102 fn reload_local(&mut self) {
103 let fs = crate::kernel::fs::get_fs();
104 self.local = fs
105 .list_files()
106 .iter()
107 .map(|m| m.get_filename().to_string())
108 .collect();
109 if self.local_sel >= self.local.len() {
110 self.local_sel = 0;
111 }
112 }
113
114 fn try_refresh(&mut self) -> bool {
116 if self.auth.refresh_token.is_empty() || !self.auth.has_client() {
117 return false;
118 }
119 match cloud::onedrive_refresh_token(&self.auth.client_id, &self.auth.refresh_token) {
120 Ok(tok) if !tok.access_token.is_empty() => {
121 self.auth.access_token = tok.access_token;
122 if !tok.refresh_token.is_empty() {
123 self.auth.refresh_token = tok.refresh_token;
124 }
125 if let Err(e) = cloud::save_ms_auth(&self.auth) {
128 crate::error!("[ONEDRIVE] refreshed token not saved: {}", e);
129 self.status = alloc::format!("Warning: token not saved ({})", e);
130 }
131 true
132 }
133 _ => false,
134 }
135 }
136
137 fn action_login(&mut self) {
140 if !self.auth.has_client() {
141 self.status = String::from("No client_id. Press S to enter it first.");
142 return;
143 }
144 self.status = String::from("Requesting device code...");
145 match cloud::onedrive_request_device_code(&self.auth.client_id) {
146 Ok(dc) if !dc.user_code.is_empty() => {
147 self.device_user_code = dc.user_code;
148 self.device_url = dc.verification_url;
149 self.device_code = dc.device_code;
150 self.awaiting_auth = true;
151 self.status =
152 String::from("Open the URL on another device, enter the code, then press P.");
153 }
154 Ok(_) => {
155 self.status =
156 String::from("Device code request returned empty (check client_id/scope).")
157 }
158 Err(e) => self.status = format!("Device code error: {}", e),
159 }
160 }
161
162 fn action_poll(&mut self) {
163 if !self.awaiting_auth {
164 return;
165 }
166 self.status = String::from("Polling for authorization...");
167 match cloud::onedrive_poll_token(&self.auth.client_id, &self.device_code) {
168 Ok(tok) if !tok.access_token.is_empty() => {
169 self.auth.access_token = tok.access_token;
170 if !tok.refresh_token.is_empty() {
171 self.auth.refresh_token = tok.refresh_token;
172 }
173 self.awaiting_auth = false;
174 match cloud::save_ms_auth(&self.auth) {
175 Ok(()) => self.status = String::from("Logged in! Loading OneDrive files..."),
176 Err(e) => {
177 crate::error!("[ONEDRIVE] login token not saved: {}", e);
178 self.status = alloc::format!(
179 "Logged in, but token NOT saved ({}). Login will not persist.",
180 e
181 );
182 }
183 }
184 self.action_refresh_remote();
185 }
186 Ok(tok) if tok.error == "authorization_pending" => {
187 self.status =
188 String::from("Still pending. Authorize in browser, then press P again.");
189 }
190 Ok(tok) if tok.error == "slow_down" => {
191 self.status = String::from("Slow down: wait a few seconds, then press P.");
192 }
193 Ok(tok) if !tok.error.is_empty() => {
194 self.status = format!("Auth error: {}", tok.error);
195 }
196 Ok(_) => self.status = String::from("No token yet. Press P again."),
197 Err(e) => self.status = format!("Poll error: {}", e),
198 }
199 }
200
201 fn current_folder_id(&self) -> Option<&str> {
203 self.folder_path.last().map(|(id, _)| id.as_str())
204 }
205
206 fn action_refresh_remote(&mut self) {
207 if !self.auth.is_logged_in() {
208 self.status = String::from("Not logged in.");
209 return;
210 }
211 self.status = String::from("Loading OneDrive files...");
212 let folder_id = self.current_folder_id().map(String::from);
213 match cloud::onedrive_list_files_in_folder(&self.auth.access_token, folder_id.as_deref())
214 {
215 Ok(files) => {
216 self.remote = files;
217 self.remote_sel = 0;
218 self.scroll = 0;
219 self.status = format!("Loaded {} OneDrive files.", self.remote.len());
220 }
221 Err("unauthorized (token expired?)") => {
222 if self.try_refresh() {
223 match cloud::onedrive_list_files_in_folder(
224 &self.auth.access_token,
225 folder_id.as_deref(),
226 ) {
227 Ok(files) => {
228 self.remote = files;
229 self.status = format!("Loaded {} OneDrive files.", self.remote.len());
230 }
231 Err(e) => self.status = format!("OneDrive list error after refresh: {}", e),
232 }
233 } else {
234 self.status =
235 String::from("Token expired and refresh failed. Press L to re-login.");
236 }
237 }
238 Err(e) => self.status = format!("OneDrive list error: {}", e),
239 }
240 }
241
242 fn action_enter_folder(&mut self, id: String, name: String) {
244 self.folder_path.push((id, name));
245 self.action_refresh_remote();
246 }
247
248 fn action_leave_folder(&mut self) {
250 if self.folder_path.pop().is_some() {
251 self.action_refresh_remote();
252 }
253 }
254
255 fn action_open_remote(&mut self) {
258 if self.remote.is_empty() {
259 return;
260 }
261 let f = self.remote[self.remote_sel].clone();
262 if f.is_folder {
263 self.action_enter_folder(f.id, f.name);
264 return;
265 }
266 self.status = format!("Fetching '{}'...", f.name);
267 let mut result = cloud::onedrive_download_binary(&self.auth.access_token, &f.id);
268 if let Err("unauthorized (token expired?)") = result {
269 if self.try_refresh() {
270 result = cloud::onedrive_download_binary(&self.auth.access_token, &f.id);
271 }
272 }
273 match result {
274 Ok(bytes) => {
275 self.viewer_title = f.name.clone();
276 self.viewer_text = String::from_utf8_lossy(&bytes).into_owned();
277 self.viewer_scroll = 0;
278 self.viewer_open = true;
279 self.status = format!("Opened '{}'. Press V to close.", f.name);
280 }
281 Err(e) => self.status = format!("Open error: {}", e),
282 }
283 }
284
285 fn action_download_remote(&mut self) {
288 if self.remote.is_empty() {
289 return;
290 }
291 let f = self.remote[self.remote_sel].clone();
292 if f.is_folder {
293 self.status = String::from("That is a folder.");
294 return;
295 }
296 self.status = format!("Downloading '{}'...", f.name);
297 let mut result = cloud::onedrive_download_binary(&self.auth.access_token, &f.id);
298 if let Err("unauthorized (token expired?)") = result {
299 if self.try_refresh() {
300 result = cloud::onedrive_download_binary(&self.auth.access_token, &f.id);
301 }
302 }
303 match result {
304 Ok(bytes) => {
305 let len = bytes.len();
306 let fs = crate::kernel::fs::get_fs();
307 match fs.save_file(&f.name, &bytes, "downloaded") {
308 Ok(()) => {
309 self.status = format!("Downloaded '{}' ({} bytes).", f.name, len);
310 self.reload_local();
311 }
312 Err(e) => self.status = format!("Download save error: {}", e),
313 }
314 }
315 Err(e) => self.status = format!("Download error: {}", e),
316 }
317 }
318
319 fn action_upload_local(&mut self) {
320 if !self.auth.is_logged_in() {
321 self.status = String::from("Not logged in.");
322 return;
323 }
324 if self.local.is_empty() {
325 return;
326 }
327 let name = self.local[self.local_sel].clone();
328 let fs = crate::kernel::fs::get_fs();
329 let content = match fs.read_file(&name) {
330 Some((_m, bytes)) => bytes,
331 None => {
332 self.status = format!("Local file '{}' not found.", name);
333 return;
334 }
335 };
336 self.status = format!("Uploading '{}'...", name);
337 let folder_id = self.current_folder_id().map(String::from);
338 let mut result = cloud::onedrive_upload_binary(
339 &self.auth.access_token,
340 folder_id.as_deref(),
341 &name,
342 &content,
343 );
344 if let Err("unauthorized (token expired?)") = result {
345 if self.try_refresh() {
346 result = cloud::onedrive_upload_binary(
347 &self.auth.access_token,
348 folder_id.as_deref(),
349 &name,
350 &content,
351 );
352 }
353 }
354 match result {
355 Ok(id) => {
356 self.status = format!("Uploaded '{}' (id={}). Press R to refresh.", name, id);
357 }
358 Err(e) => self.status = format!("Upload error: {}", e),
359 }
360 }
361
362 fn action_delete_remote(&mut self) {
363 if self.remote.is_empty() {
364 return;
365 }
366 let f = self.remote[self.remote_sel].clone();
367 self.status = format!("Deleting '{}'...", f.name);
368 let mut result = cloud::onedrive_delete(&self.auth.access_token, &f.id);
369 if let Err("unauthorized (token expired?)") = result {
370 if self.try_refresh() {
371 result = cloud::onedrive_delete(&self.auth.access_token, &f.id);
372 }
373 }
374 match result {
375 Ok(()) => {
376 self.remote.remove(self.remote_sel);
377 if self.remote_sel >= self.remote.len() && self.remote_sel > 0 {
378 self.remote_sel -= 1;
379 }
380 self.status = format!("Deleted '{}'.", f.name);
381 }
382 Err(e) => self.status = format!("Delete error: {}", e),
383 }
384 }
385
386 fn action_logout(&mut self) {
387 self.auth.access_token = String::new();
388 self.auth.refresh_token = String::new();
389 self.remote.clear();
390 self.folder_path.clear();
391 match cloud::save_ms_auth(&self.auth) {
394 Ok(()) => {
395 self.status = String::from("Logged out (client_id kept). Press L to log in.")
396 }
397 Err(e) => {
398 crate::error!("[ONEDRIVE] logout not persisted: {}", e);
399 self.status =
400 alloc::format!("Logout NOT saved ({}). Token may remain on disk!", e);
401 }
402 }
403 }
404
405 fn action_enter_setup(&mut self) {
408 self.setup_id_box = TextBox::with_text(&self.auth.client_id);
409 self.setup_mode = true;
410 }
411
412 fn action_save_setup(&mut self) {
414 self.auth.client_id = self.setup_id_box.text.clone();
415 match cloud::save_ms_auth(&self.auth) {
416 Ok(()) => {
417 self.setup_mode = false;
418 self.status = if self.auth.has_client() {
419 String::from("Saved client_id. Press L to log in.")
420 } else {
421 String::from("Saved, but client_id still empty.")
422 };
423 }
424 Err(e) => self.status = format!("Save error: {}", e),
425 }
426 }
427}
428
429const HEADER_H: u32 = 28;
430const ROW_H: u32 = 20;
431
432impl App for OneDriveApp {
433 fn name(&self) -> &str {
434 "onedrive"
435 }
436
437 fn instance_info(&self) -> String {
438 if self.auth.is_logged_in() {
439 String::from("OneDrive")
440 } else {
441 String::from("OneDrive (logged out)")
442 }
443 }
444
445 fn draw(&mut self, screen: &Screen, win_x: u32, win_y: u32, win_w: u32, win_h: u32) {
446 self.last_win_w = win_w;
447 let config = crate::kernel::config::get_config();
448 screen.boxfill(
450 win_x,
451 win_y,
452 win_x + win_w,
453 win_y + win_h,
454 Color(config.theme.app_bg),
455 );
456 screen.boxfill(
458 win_x,
459 win_y,
460 win_x + win_w,
461 win_y + HEADER_H,
462 Color(config.theme.window_title_bar),
463 );
464 screen.draw_string_vector(win_x + 12, win_y + 7, "OneDrive", Color(config.theme.window_title_fg), 16);
465 let legend =
466 "L:login P:poll R:refresh Enter:open/enter U:upload B:download D:delete BS:up-folder Tab:pane V:close O:logout S:setup";
467 screen.draw_string_vector(win_x + 130, win_y + 9, legend, Color(config.theme.non_active_window_border), 11);
468
469 let body_y = win_y + HEADER_H + 4;
470
471 if self.setup_mode {
473 screen.draw_string_vector(
474 win_x + 14,
475 body_y + 4,
476 "Azure Portal (portal.azure.com): App registrations > New registration,",
477 Color(config.theme.non_active_window_border),
478 12,
479 );
480 screen.draw_string_vector(
481 win_x + 14,
482 body_y + 22,
483 "Supported account types: any, no redirect URI needed (public client, device code flow).",
484 Color(config.theme.non_active_window_border),
485 12,
486 );
487 screen.draw_string_vector(
488 win_x + 14,
489 body_y + 40,
490 "API permissions: Files.ReadWrite + offline_access (Microsoft Graph, delegated).",
491 Color(config.theme.non_active_window_border),
492 12,
493 );
494 screen.draw_string_vector(
495 win_x + 14,
496 body_y + 62,
497 "Application (client) ID:",
498 Color(config.theme.terminal_fg),
499 13,
500 );
501 self.draw_text_field(
502 screen,
503 win_x + 14,
504 body_y + 80,
505 win_w - 28,
506 &self.setup_id_box,
507 true,
508 "(paste client_id / Application ID)",
509 false,
510 );
511 screen.draw_string_vector(
512 win_x + 14,
513 body_y + 118,
514 "Enter: save Esc: cancel",
515 Color(config.theme.non_active_window_border),
516 12,
517 );
518 self.draw_status(screen, win_x, win_y, win_w, win_h);
519 return;
520 }
521
522 if !self.auth.has_client() {
524 let lines = [
525 "Setup required.",
526 "Press S to enter your Azure App client_id directly in this app,",
527 "or save it into 'onedrive_auth.json' yourself:",
528 "1. Azure Portal (portal.azure.com): App registrations > New registration.",
529 " Supported account types: any. No redirect URI needed.",
530 "2. Enable 'Allow public client flows' (device code).",
531 "3. API permissions: Files.ReadWrite + offline_access (Microsoft Graph).",
532 "4. Press S here, paste the client_id (Application ID), press Enter to save.",
533 ];
534 let mut y = body_y + 6;
535 for l in lines.iter() {
536 screen.draw_string_vector(win_x + 14, y, l, Color(config.theme.terminal_fg), 12);
537 y += 20;
538 }
539 self.draw_status(screen, win_x, win_y, win_w, win_h);
540 return;
541 }
542
543 if self.awaiting_auth {
544 screen.draw_string_vector(
545 win_x + 14,
546 body_y + 10,
547 "Authorize this device:",
548 Color(config.theme.terminal_fg),
549 14,
550 );
551 screen.draw_string_vector(
552 win_x + 14,
553 body_y + 40,
554 "1. Open this URL on your phone/PC:",
555 Color(config.theme.non_active_window_border),
556 12,
557 );
558 screen.draw_string_vector(
559 win_x + 30,
560 body_y + 62,
561 &self.device_url,
562 Color(config.theme.active_window_border),
563 13,
564 );
565 screen.draw_string_vector(
566 win_x + 14,
567 body_y + 92,
568 "2. Enter this code:",
569 Color(config.theme.non_active_window_border),
570 12,
571 );
572 screen.draw_string_vector(
573 win_x + 30,
574 body_y + 116,
575 &self.device_user_code,
576 Color(config.theme.focus_highlight),
577 24,
578 );
579 screen.draw_string_vector(
580 win_x + 14,
581 body_y + 152,
582 "3. Press P here to complete login.",
583 Color(config.theme.non_active_window_border),
584 12,
585 );
586 self.draw_status(screen, win_x, win_y, win_w, win_h);
587 return;
588 }
589
590 if self.viewer_open {
592 screen.draw_string_vector(
593 win_x + 14,
594 body_y,
595 &format!("View: {}", self.viewer_title),
596 Color(config.theme.focus_highlight),
597 14,
598 );
599 let mut y = body_y + 24;
600 let max_y = win_y + win_h - 28;
601 for line in self.viewer_text.lines().skip(self.viewer_scroll) {
602 if y + ROW_H > max_y {
603 break;
604 }
605 let shown: String = line.chars().take(90).collect();
607 screen.draw_string_vector(win_x + 14, y, &shown, Color(config.theme.terminal_fg), 12);
608 y += 16;
609 }
610 self.draw_status(screen, win_x, win_y, win_w, win_h);
611 return;
612 }
613
614 let mut crumb = String::from("/");
616 for (_, name) in self.folder_path.iter() {
617 crumb.push_str(name);
618 crumb.push('/');
619 }
620 let crumb_shown: String = crumb.chars().take(80).collect();
621 screen.draw_string_vector(
622 win_x + 14,
623 body_y,
624 &crumb_shown,
625 Color(config.theme.non_active_window_border),
626 11,
627 );
628
629 let mid_x = win_x + win_w / 2;
631 let remote_focus = self.pane == Pane::Remote;
632 let pane_header_y = body_y + 16;
633 screen.draw_string_vector(
634 win_x + 14,
635 pane_header_y,
636 "OneDrive (remote)",
637 if remote_focus {
638 Color(config.theme.active_window_border)
639 } else {
640 Color(config.theme.non_active_window_border)
641 },
642 13,
643 );
644 screen.draw_string_vector(
645 mid_x + 14,
646 pane_header_y,
647 "Local (this OS)",
648 if !remote_focus {
649 Color(config.theme.active_window_border)
650 } else {
651 Color(config.theme.non_active_window_border)
652 },
653 13,
654 );
655
656 let list_y = pane_header_y + 22;
657 let max_rows = ((win_h - HEADER_H - 76) / ROW_H) as usize;
658
659 for (i, f) in self
661 .remote
662 .iter()
663 .enumerate()
664 .skip(self.scroll)
665 .take(max_rows)
666 {
667 let y = list_y + (i - self.scroll) as u32 * ROW_H;
668 if remote_focus && i == self.remote_sel {
669 screen.boxfill(
670 win_x + 8,
671 y - 2,
672 mid_x - 8,
673 y + ROW_H - 4,
674 Color(config.theme.button_active_bg),
675 );
676 }
677 let tag = if f.is_folder { "[D] " } else { " " };
678 let label: String = format!("{}{}", tag, f.name);
679 let shown: String = label.chars().take(40).collect();
680 screen.draw_string_vector(win_x + 14, y, &shown, Color(config.theme.terminal_fg), 12);
681 }
682 if self.remote.is_empty() {
683 screen.draw_string_vector(
684 win_x + 14,
685 list_y,
686 "(press R to load)",
687 Color(config.theme.non_active_window_border),
688 12,
689 );
690 }
691
692 for (i, name) in self.local.iter().enumerate().take(max_rows) {
694 let y = list_y + i as u32 * ROW_H;
695 if !remote_focus && i == self.local_sel {
696 screen.boxfill(
697 mid_x + 8,
698 y - 2,
699 win_x + win_w - 8,
700 y + ROW_H - 4,
701 Color(config.theme.button_active_bg),
702 );
703 }
704 let shown: String = name.chars().take(40).collect();
705 screen.draw_string_vector(mid_x + 14, y, &shown, Color(config.theme.terminal_fg), 12);
706 }
707
708 self.draw_status(screen, win_x, win_y, win_w, win_h);
709 }
710
711 fn on_key(&mut self, keycode: u8, pressed: bool, ascii: Option<char>) {
712 if !pressed {
713 return;
714 }
715 if self.setup_mode {
717 match keycode {
718 0x1C => self.action_save_setup(),
719 0x01 => {
720 self.setup_mode = false;
721 self.status = String::from("Setup cancelled.");
722 }
723 _ => {
724 let _ = self.setup_id_box.handle_key(keycode, ascii);
725 }
726 }
727 return;
728 }
729 let c = match ascii {
730 Some(c) => c.to_ascii_lowercase(),
731 None => return,
732 };
733 if self.viewer_open {
735 match c {
736 'v' | 'q' => self.viewer_open = false,
737 'j' => self.viewer_scroll = self.viewer_scroll.saturating_add(1),
738 'k' => self.viewer_scroll = self.viewer_scroll.saturating_sub(1),
739 _ => {}
740 }
741 return;
742 }
743 match c {
744 'l' => self.action_login(),
745 'p' => self.action_poll(),
746 'r' => self.action_refresh_remote(),
747 'u' => self.action_upload_local(),
748 'd' => self.action_delete_remote(),
749 'o' => self.action_logout(),
750 's' => self.action_enter_setup(),
751 'b' if self.pane == Pane::Remote => self.action_download_remote(),
752 '\x08' if self.pane == Pane::Remote => self.action_leave_folder(),
753 '\t' => {
754 self.pane = if self.pane == Pane::Remote {
755 Pane::Local
756 } else {
757 Pane::Remote
758 }
759 }
760 '\n' | '\r' => {
761 if self.pane == Pane::Remote {
762 self.action_open_remote();
763 } else {
764 self.action_upload_local();
765 }
766 }
767 'j' => self.move_sel(1),
768 'k' => self.move_sel(-1),
769 _ => {}
770 }
771 }
772
773 fn on_mouse(
774 &mut self,
775 local_x: i32,
776 local_y: i32,
777 btn_left: bool,
778 _btn_right: bool,
779 wheel: i32,
780 ) {
781 if wheel != 0 && self.viewer_open {
782 if wheel < 0 {
783 self.viewer_scroll = self.viewer_scroll.saturating_add(1);
784 } else {
785 self.viewer_scroll = self.viewer_scroll.saturating_sub(1);
786 }
787 return;
788 }
789 if !btn_left {
790 return;
791 }
792 let list_top = (HEADER_H + 26) as i32;
794 if local_y < list_top {
795 return;
796 }
797 let row = ((local_y - list_top) / ROW_H as i32).max(0) as usize;
798 let mid = (self.last_win_w / 2) as i32;
801 if local_x < mid {
802 self.pane = Pane::Remote;
803 if row + self.scroll < self.remote.len() {
804 self.remote_sel = row + self.scroll;
805 }
806 } else {
807 self.pane = Pane::Local;
808 if row < self.local.len() {
809 self.local_sel = row;
810 }
811 }
812 }
813}
814
815impl OneDriveApp {
816 fn move_sel(&mut self, delta: i32) {
817 if self.pane == Pane::Remote {
818 let n = self.remote.len();
819 if n == 0 {
820 return;
821 }
822 let cur = self.remote_sel as i32 + delta;
823 self.remote_sel = cur.clamp(0, n as i32 - 1) as usize;
824 } else {
825 let n = self.local.len();
826 if n == 0 {
827 return;
828 }
829 let cur = self.local_sel as i32 + delta;
830 self.local_sel = cur.clamp(0, n as i32 - 1) as usize;
831 }
832 }
833
834 #[allow(clippy::too_many_arguments)]
835 fn draw_text_field(
836 &self,
837 screen: &Screen,
838 x: u32,
839 y: u32,
840 w: u32,
841 tb: &TextBox,
842 focused: bool,
843 placeholder: &str,
844 mask: bool,
845 ) {
846 let t = &crate::kernel::config::get_config().theme;
847 let border = if focused { t.active_window_border } else { t.browser_secondary };
848 let bg = if focused { t.browser_bg } else { t.ui_input_bg };
849 screen.boxfill(x, y, x + w, y + 28, Color(bg));
850 screen.boxfill(x, y, x + w, y + 1, Color(border));
851 screen.boxfill(x, y + 27, x + w, y + 28, Color(border));
852 screen.boxfill(x, y, x + 1, y + 28, Color(border));
853 screen.boxfill(x + w - 1, y, x + w, y + 28, Color(border));
854
855 let display: String = if tb.text.is_empty() && !focused {
856 String::from(placeholder)
857 } else if focused {
858 tb.get_text_with_cursor(true)
859 .chars()
860 .map(|c| if mask && c != '|' { '*' } else { c })
861 .collect()
862 } else if mask {
863 tb.text.chars().map(|_| '*').collect()
864 } else {
865 tb.text.clone()
866 };
867
868 let text_color = if tb.text.is_empty() && !focused {
869 t.fn_bar_dim_fg
870 } else {
871 t.login_text_fg
872 };
873 let shown: String = display.chars().take(60).collect();
874 screen.draw_string_vector(x + 8, y + 7, &shown, Color(text_color), 13);
875 }
876
877 fn draw_status(&self, screen: &Screen, win_x: u32, win_y: u32, win_w: u32, win_h: u32) {
878 let config = crate::kernel::config::get_config();
879 let bar_y = win_y + win_h - 24;
880 screen.boxfill(
881 win_x,
882 bar_y,
883 win_x + win_w,
884 win_y + win_h,
885 Color(config.theme.window_title_bar),
886 );
887 let shown: String = self.status.chars().take(110).collect();
888 screen.draw_string_vector(win_x + 12, bar_y + 5, &shown, Color(config.theme.window_title_fg), 12);
889 }
890}