1#![allow(dead_code)]
7
8use alloc::{format, string::String, vec, vec::Vec};
9
10use super::renderer::draw_string_into_buffer;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum SettingsPanel {
19 Display,
20 Network,
21 Users,
22 Appearance,
23 Audio,
24 Bluetooth,
25 Power,
26 About,
27}
28
29impl SettingsPanel {
30 pub fn label(&self) -> &'static str {
32 match self {
33 Self::Display => "Display",
34 Self::Network => "Network",
35 Self::Users => "Users",
36 Self::Appearance => "Appearance",
37 Self::Audio => "Audio",
38 Self::Bluetooth => "Bluetooth",
39 Self::Power => "Power",
40 Self::About => "About",
41 }
42 }
43
44 pub fn all() -> &'static [SettingsPanel] {
46 &[
47 Self::Display,
48 Self::Network,
49 Self::Users,
50 Self::Appearance,
51 Self::Audio,
52 Self::Bluetooth,
53 Self::Power,
54 Self::About,
55 ]
56 }
57
58 fn index(&self) -> usize {
60 match self {
61 Self::Display => 0,
62 Self::Network => 1,
63 Self::Users => 2,
64 Self::Appearance => 3,
65 Self::Audio => 4,
66 Self::Bluetooth => 5,
67 Self::Power => 6,
68 Self::About => 7,
69 }
70 }
71}
72
73#[derive(Debug, Clone)]
79pub struct DisplaySettings {
80 pub resolution_index: usize,
81 pub brightness: u8,
82 pub available_resolutions: Vec<(usize, usize)>,
83}
84
85impl Default for DisplaySettings {
86 fn default() -> Self {
87 Self {
88 resolution_index: 0,
89 brightness: 80,
90 available_resolutions: vec![(1280, 800), (1024, 768), (800, 600), (1920, 1080)],
91 }
92 }
93}
94
95impl DisplaySettings {
96 fn item_count(&self) -> usize {
98 2 }
100}
101
102#[derive(Debug, Clone)]
104pub struct NetworkSettings {
105 pub hostname: String,
106 pub dhcp_enabled: bool,
107 pub ip_address: String,
108 pub gateway: String,
109 pub dns: String,
110}
111
112impl Default for NetworkSettings {
113 fn default() -> Self {
114 Self {
115 hostname: String::from("veridian"),
116 dhcp_enabled: true,
117 ip_address: String::from("10.0.2.15"),
118 gateway: String::from("10.0.2.2"),
119 dns: String::from("10.0.2.3"),
120 }
121 }
122}
123
124impl NetworkSettings {
125 fn item_count(&self) -> usize {
126 5 }
128}
129
130#[derive(Debug, Clone)]
132pub struct UserSettings {
133 pub username: String,
134 pub shell: String,
135 pub home_dir: String,
136}
137
138impl Default for UserSettings {
139 fn default() -> Self {
140 Self {
141 username: String::from("root"),
142 shell: String::from("/bin/vsh"),
143 home_dir: String::from("/root"),
144 }
145 }
146}
147
148impl UserSettings {
149 fn item_count(&self) -> usize {
150 3 }
152}
153
154#[derive(Debug, Clone, Copy, PartialEq, Eq)]
156pub enum PanelPosition {
157 Top,
158 Bottom,
159}
160
161#[derive(Debug, Clone)]
163pub struct AppearanceSettings {
164 pub theme_index: usize,
165 pub font_size: u8,
166 pub show_desktop_icons: bool,
167 pub panel_position: PanelPosition,
168}
169
170impl Default for AppearanceSettings {
171 fn default() -> Self {
172 Self {
173 theme_index: 0,
174 font_size: 16,
175 show_desktop_icons: true,
176 panel_position: PanelPosition::Bottom,
177 }
178 }
179}
180
181impl AppearanceSettings {
182 fn item_count(&self) -> usize {
183 4 }
185}
186
187#[derive(Debug, Clone)]
189pub struct AudioSettings {
190 pub master_volume: u8,
191 pub muted: bool,
192 pub output_device: String,
193}
194
195impl Default for AudioSettings {
196 fn default() -> Self {
197 Self {
198 master_volume: 75,
199 muted: false,
200 output_device: String::from("Default Output"),
201 }
202 }
203}
204
205impl AudioSettings {
206 fn item_count(&self) -> usize {
207 3 }
209}
210
211#[derive(Debug, Clone)]
213pub struct BluetoothSettings {
214 pub enabled: bool,
215 pub discoverable: bool,
216 pub device_name: String,
217}
218
219impl Default for BluetoothSettings {
220 fn default() -> Self {
221 Self {
222 enabled: false,
223 discoverable: false,
224 device_name: String::from("VeridianOS"),
225 }
226 }
227}
228
229impl BluetoothSettings {
230 fn item_count(&self) -> usize {
231 3 }
233}
234
235#[derive(Debug, Clone)]
237pub struct PowerSettings {
238 pub profile_index: usize,
239 pub screen_timeout_min: u8,
240 pub profiles: Vec<&'static str>,
241}
242
243impl Default for PowerSettings {
244 fn default() -> Self {
245 Self {
246 profile_index: 0,
247 screen_timeout_min: 10,
248 profiles: vec!["Balanced", "Performance", "Power Saver"],
249 }
250 }
251}
252
253impl PowerSettings {
254 fn item_count(&self) -> usize {
255 2 }
257}
258
259#[derive(Debug, Clone)]
261pub struct AboutInfo {
262 pub os_name: &'static str,
263 pub version: &'static str,
264 pub kernel_version: &'static str,
265 pub arch: &'static str,
266 pub hostname: String,
267}
268
269impl Default for AboutInfo {
270 fn default() -> Self {
271 Self {
272 os_name: "VeridianOS",
273 version: "0.25.2",
274 kernel_version: "0.25.2-gui",
275 arch: core::env!("CARGO_PKG_NAME"), hostname: String::from("veridian"),
277 }
278 }
279}
280
281#[derive(Debug, Clone, PartialEq, Eq)]
287pub enum SettingsAction {
288 None,
290 Close,
292 Apply,
294 SwitchPanel(SettingsPanel),
296}
297
298pub struct SettingsApp {
304 pub active_panel: SettingsPanel,
306
307 pub display: DisplaySettings,
309 pub network: NetworkSettings,
310 pub user: UserSettings,
311 pub appearance: AppearanceSettings,
312 pub about: AboutInfo,
313 pub audio: AudioSettings,
314 pub bluetooth: BluetoothSettings,
315 pub power_settings: PowerSettings,
316
317 pub selected_item: usize,
319
320 pub surface_id: Option<u32>,
322
323 pub width: usize,
325 pub height: usize,
326}
327
328const SIDEBAR_WIDTH: usize = 140;
330const SIDEBAR_ITEM_HEIGHT: usize = 24;
331const CONTENT_X: usize = SIDEBAR_WIDTH + 12;
332const CONTENT_Y: usize = 12;
333const LINE_HEIGHT: usize = 22;
334const CHAR_W: usize = 8;
335
336impl SettingsApp {
337 pub fn new() -> Self {
339 let mut app = Self {
340 active_panel: SettingsPanel::Display,
341 display: DisplaySettings::default(),
342 network: NetworkSettings::default(),
343 user: UserSettings::default(),
344 appearance: AppearanceSettings::default(),
345 about: AboutInfo::default(),
346 audio: AudioSettings::default(),
347 bluetooth: BluetoothSettings::default(),
348 power_settings: PowerSettings::default(),
349 selected_item: 0,
350 surface_id: None,
351 width: 600,
352 height: 400,
353 };
354 app.refresh_from_kernel();
355 app
356 }
357
358 pub fn refresh_from_kernel(&mut self) {
360 let iface = crate::net::ip::get_interface_config();
362 let ip = iface.ip_addr.0;
363 self.network.ip_address = format!("{}.{}.{}.{}", ip[0], ip[1], ip[2], ip[3]);
364 if let Some(gw) = iface.gateway {
365 self.network.gateway = format!("{}.{}.{}.{}", gw.0[0], gw.0[1], gw.0[2], gw.0[3]);
366 }
367 if let Ok(raw_vol) =
371 crate::audio::mixer::with_mixer(|m: &mut crate::audio::mixer::AudioMixer| {
372 m.get_master_volume()
373 })
374 {
375 self.audio.master_volume = ((raw_vol as u32 * 100) / 65535) as u8;
377 }
378
379 let governor = crate::power::get_governor();
381 self.power_settings.profile_index = match governor {
382 crate::power::Governor::OnDemand => 0, crate::power::Governor::Performance => 1, crate::power::Governor::PowerSave => 2, };
386
387 let db = crate::syscall::userland_ext::UserDatabase::new();
389 if let Some(entry) = db.get_user_by_uid(0) {
390 self.user.username = entry.username.clone();
391 self.user.home_dir = entry.home.clone();
392 self.user.shell = entry.shell.clone();
393 }
394 }
395
396 pub fn switch_panel(&mut self, panel: SettingsPanel) {
398 self.active_panel = panel;
399 self.selected_item = 0;
400 }
401
402 fn current_item_count(&self) -> usize {
404 match self.active_panel {
405 SettingsPanel::Display => self.display.item_count(),
406 SettingsPanel::Network => self.network.item_count(),
407 SettingsPanel::Users => self.user.item_count(),
408 SettingsPanel::Appearance => self.appearance.item_count(),
409 SettingsPanel::Audio => self.audio.item_count(),
410 SettingsPanel::Bluetooth => self.bluetooth.item_count(),
411 SettingsPanel::Power => self.power_settings.item_count(),
412 SettingsPanel::About => 0, }
414 }
415
416 pub fn handle_key(&mut self, key: u8) -> SettingsAction {
418 match key {
419 b'\t' => {
421 let panels = SettingsPanel::all();
422 let next = (self.active_panel.index() + 1) % panels.len();
423 let panel = panels[next];
424 self.switch_panel(panel);
425 SettingsAction::SwitchPanel(panel)
426 }
427 b'k' | b'K' => {
429 if self.selected_item > 0 {
430 self.selected_item -= 1;
431 }
432 SettingsAction::None
433 }
434 b'j' | b'J' => {
436 let max = self.current_item_count();
437 if max > 0 && self.selected_item < max - 1 {
438 self.selected_item += 1;
439 }
440 SettingsAction::None
441 }
442 b'\r' | b'\n' => {
444 self.activate_selected();
445 SettingsAction::Apply
446 }
447 0x1B => SettingsAction::Close,
449 _ => SettingsAction::None,
450 }
451 }
452
453 pub fn handle_click(&mut self, x: usize, y: usize) -> SettingsAction {
455 if x < SIDEBAR_WIDTH {
457 let panels = SettingsPanel::all();
458 let index = y / SIDEBAR_ITEM_HEIGHT;
459 if index < panels.len() {
460 let panel = panels[index];
461 self.switch_panel(panel);
462 return SettingsAction::SwitchPanel(panel);
463 }
464 return SettingsAction::None;
465 }
466
467 if x >= CONTENT_X && y >= CONTENT_Y {
469 let item = (y - CONTENT_Y) / LINE_HEIGHT;
470 let max = self.current_item_count();
471 if item < max {
472 self.selected_item = item;
473 self.activate_selected();
474 return SettingsAction::Apply;
475 }
476 }
477
478 SettingsAction::None
479 }
480
481 fn activate_selected(&mut self) {
483 match self.active_panel {
484 SettingsPanel::Display => match self.selected_item {
485 0 => {
486 let n = self.display.available_resolutions.len();
488 if n > 0 {
489 self.display.resolution_index = (self.display.resolution_index + 1) % n;
490 }
491 }
492 1 => {
493 self.display.brightness = if self.display.brightness >= 100 {
495 10
496 } else {
497 self.display.brightness + 10
498 };
499 }
500 _ => {}
501 },
502 SettingsPanel::Network => {
503 if self.selected_item == 1 {
504 self.network.dhcp_enabled = !self.network.dhcp_enabled;
506 }
507 }
509 SettingsPanel::Users => {
510 }
512 SettingsPanel::Appearance => match self.selected_item {
513 0 => {
514 self.appearance.theme_index = (self.appearance.theme_index + 1) % 6;
517 }
518 1 => {
519 self.appearance.font_size = if self.appearance.font_size >= 20 {
521 12
522 } else {
523 self.appearance.font_size + 2
524 };
525 }
526 2 => {
527 self.appearance.show_desktop_icons = !self.appearance.show_desktop_icons;
528 }
529 3 => {
530 self.appearance.panel_position = match self.appearance.panel_position {
531 PanelPosition::Top => PanelPosition::Bottom,
532 PanelPosition::Bottom => PanelPosition::Top,
533 };
534 }
535 _ => {}
536 },
537 SettingsPanel::Audio => match self.selected_item {
538 0 => {
539 self.audio.master_volume = if self.audio.master_volume >= 100 {
541 0
542 } else {
543 self.audio.master_volume + 10
544 };
545 let raw_vol = ((self.audio.master_volume as u32 * 65535) / 100) as u16;
547 let _ = crate::audio::mixer::with_mixer(
548 |m: &mut crate::audio::mixer::AudioMixer| {
549 m.set_master_volume(raw_vol);
550 },
551 );
552 }
553 1 => {
554 self.audio.muted = !self.audio.muted;
555 }
556 _ => {}
557 },
558 SettingsPanel::Bluetooth => match self.selected_item {
559 0 => {
560 self.bluetooth.enabled = !self.bluetooth.enabled;
561 }
562 1 => {
563 self.bluetooth.discoverable = !self.bluetooth.discoverable;
564 }
565 _ => {}
566 },
567 SettingsPanel::Power => match self.selected_item {
568 0 => {
569 let n = self.power_settings.profiles.len();
570 if n > 0 {
571 self.power_settings.profile_index =
572 (self.power_settings.profile_index + 1) % n;
573 }
574 let gov = match self.power_settings.profile_index {
576 0 => crate::power::Governor::OnDemand,
577 1 => crate::power::Governor::Performance,
578 _ => crate::power::Governor::PowerSave,
579 };
580 let _ = crate::power::set_governor(gov);
581 }
582 1 => {
583 self.power_settings.screen_timeout_min =
585 match self.power_settings.screen_timeout_min {
586 5 => 10,
587 10 => 15,
588 15 => 30,
589 30 => 0,
590 _ => 5,
591 };
592 }
593 _ => {}
594 },
595 SettingsPanel::About => {} }
597 }
598
599 pub fn render_to_u8_buffer(&self, buf: &mut [u8], buf_width: usize, buf_height: usize) {
607 for chunk in buf.chunks_exact_mut(4) {
609 chunk[0] = 0x28; chunk[1] = 0x28; chunk[2] = 0x28; chunk[3] = 0xFF; }
614
615 for y in 0..buf_height {
617 for x in 0..SIDEBAR_WIDTH.min(buf_width) {
618 let off = (y * buf_width + x) * 4;
619 if off + 3 < buf.len() {
620 buf[off] = 0x32;
621 buf[off + 1] = 0x32;
622 buf[off + 2] = 0x32;
623 buf[off + 3] = 0xFF;
624 }
625 }
626 }
627
628 let panels = SettingsPanel::all();
630 for (i, panel) in panels.iter().enumerate() {
631 let item_y = i * SIDEBAR_ITEM_HEIGHT;
632
633 if *panel == self.active_panel {
635 for dy in 0..SIDEBAR_ITEM_HEIGHT {
636 for x in 0..SIDEBAR_WIDTH.min(buf_width) {
637 let off = ((item_y + dy) * buf_width + x) * 4;
638 if off + 3 < buf.len() {
639 buf[off] = 0x55;
640 buf[off + 1] = 0x44;
641 buf[off + 2] = 0x33;
642 buf[off + 3] = 0xFF;
643 }
644 }
645 }
646 }
647
648 let color = if *panel == self.active_panel {
649 0xFFFFFF
650 } else {
651 0xAAAAAA
652 };
653 draw_string_into_buffer(
654 buf,
655 buf_width,
656 panel.label().as_bytes(),
657 10,
658 item_y + 4,
659 color,
660 );
661 }
662
663 for y in 0..buf_height {
665 let off = (y * buf_width + SIDEBAR_WIDTH) * 4;
666 if off + 3 < buf.len() {
667 buf[off] = 0x55;
668 buf[off + 1] = 0x55;
669 buf[off + 2] = 0x55;
670 buf[off + 3] = 0xFF;
671 }
672 }
673
674 match self.active_panel {
676 SettingsPanel::Display => {
677 self.render_display_panel(buf, buf_width);
678 }
679 SettingsPanel::Network => {
680 self.render_network_panel(buf, buf_width);
681 }
682 SettingsPanel::Users => {
683 self.render_users_panel(buf, buf_width);
684 }
685 SettingsPanel::Appearance => {
686 self.render_appearance_panel(buf, buf_width);
687 }
688 SettingsPanel::Audio => {
689 self.render_audio_panel(buf, buf_width);
690 }
691 SettingsPanel::Bluetooth => {
692 self.render_bluetooth_panel(buf, buf_width);
693 }
694 SettingsPanel::Power => {
695 self.render_power_panel(buf, buf_width);
696 }
697 SettingsPanel::About => {
698 self.render_about_panel(buf, buf_width);
699 }
700 }
701 }
702
703 pub fn render_to_buffer(&self, buffer: &mut [u32], buf_width: usize, buf_height: usize) {
707 let byte_len = buf_width * buf_height * 4;
708 let mut byte_buf = vec![0u8; byte_len];
709 self.render_to_u8_buffer(&mut byte_buf, buf_width, buf_height);
710
711 for (i, chunk) in byte_buf.chunks_exact(4).enumerate() {
713 if i < buffer.len() {
714 buffer[i] = (chunk[3] as u32) << 24
715 | (chunk[2] as u32) << 16
716 | (chunk[1] as u32) << 8
717 | (chunk[0] as u32);
718 }
719 }
720 }
721
722 fn render_label_value(
725 buf: &mut [u8],
726 buf_width: usize,
727 row: usize,
728 label: &[u8],
729 value: &[u8],
730 selected: bool,
731 ) {
732 let y = CONTENT_Y + row * LINE_HEIGHT;
733
734 if selected {
736 for dy in 0..LINE_HEIGHT {
737 for x in CONTENT_X..buf_width {
738 let off = ((y + dy) * buf_width + x) * 4;
739 if off + 3 < buf.len() {
740 buf[off] = 0x44;
741 buf[off + 1] = 0x3A;
742 buf[off + 2] = 0x30;
743 buf[off + 3] = 0xFF;
744 }
745 }
746 }
747 }
748
749 let label_color: u32 = 0x999999;
750 let value_color: u32 = if selected { 0xFFCC66 } else { 0xDDDDDD };
751
752 draw_string_into_buffer(buf, buf_width, label, CONTENT_X, y + 3, label_color);
753 let val_x = CONTENT_X + (label.len() + 1) * CHAR_W;
754 draw_string_into_buffer(buf, buf_width, value, val_x, y + 3, value_color);
755 }
756
757 fn render_display_panel(&self, buf: &mut [u8], buf_width: usize) {
758 let res = if self.display.resolution_index < self.display.available_resolutions.len() {
759 let (w, h) = self.display.available_resolutions[self.display.resolution_index];
760 format!("{}x{}", w, h)
761 } else {
762 String::from("unknown")
763 };
764
765 Self::render_label_value(
766 buf,
767 buf_width,
768 0,
769 b"Resolution:",
770 res.as_bytes(),
771 self.selected_item == 0,
772 );
773
774 let bright = format!("{}%", self.display.brightness);
775 Self::render_label_value(
776 buf,
777 buf_width,
778 1,
779 b"Brightness:",
780 bright.as_bytes(),
781 self.selected_item == 1,
782 );
783 }
784
785 fn render_network_panel(&self, buf: &mut [u8], buf_width: usize) {
786 Self::render_label_value(
787 buf,
788 buf_width,
789 0,
790 b"Hostname:",
791 self.network.hostname.as_bytes(),
792 self.selected_item == 0,
793 );
794
795 let dhcp_str = if self.network.dhcp_enabled {
796 "ON"
797 } else {
798 "OFF"
799 };
800 Self::render_label_value(
801 buf,
802 buf_width,
803 1,
804 b"DHCP:",
805 dhcp_str.as_bytes(),
806 self.selected_item == 1,
807 );
808
809 Self::render_label_value(
810 buf,
811 buf_width,
812 2,
813 b"IP Address:",
814 self.network.ip_address.as_bytes(),
815 self.selected_item == 2,
816 );
817
818 Self::render_label_value(
819 buf,
820 buf_width,
821 3,
822 b"Gateway:",
823 self.network.gateway.as_bytes(),
824 self.selected_item == 3,
825 );
826
827 Self::render_label_value(
828 buf,
829 buf_width,
830 4,
831 b"DNS:",
832 self.network.dns.as_bytes(),
833 self.selected_item == 4,
834 );
835 }
836
837 fn render_users_panel(&self, buf: &mut [u8], buf_width: usize) {
838 Self::render_label_value(
839 buf,
840 buf_width,
841 0,
842 b"Username:",
843 self.user.username.as_bytes(),
844 self.selected_item == 0,
845 );
846
847 Self::render_label_value(
848 buf,
849 buf_width,
850 1,
851 b"Shell:",
852 self.user.shell.as_bytes(),
853 self.selected_item == 1,
854 );
855
856 Self::render_label_value(
857 buf,
858 buf_width,
859 2,
860 b"Home:",
861 self.user.home_dir.as_bytes(),
862 self.selected_item == 2,
863 );
864 }
865
866 fn render_appearance_panel(&self, buf: &mut [u8], buf_width: usize) {
867 let theme = match self.appearance.theme_index {
868 0 => "Dark",
869 1 => "Light",
870 2 => "Solarized Dark",
871 3 => "Solarized Light",
872 4 => "Nord",
873 5 => "Dracula",
874 _ => "Custom",
875 };
876 Self::render_label_value(
877 buf,
878 buf_width,
879 0,
880 b"Theme:",
881 theme.as_bytes(),
882 self.selected_item == 0,
883 );
884
885 let fsz = format!("{}px", self.appearance.font_size);
886 Self::render_label_value(
887 buf,
888 buf_width,
889 1,
890 b"Font Size:",
891 fsz.as_bytes(),
892 self.selected_item == 1,
893 );
894
895 let icons = if self.appearance.show_desktop_icons {
896 "ON"
897 } else {
898 "OFF"
899 };
900 Self::render_label_value(
901 buf,
902 buf_width,
903 2,
904 b"Desktop Icons:",
905 icons.as_bytes(),
906 self.selected_item == 2,
907 );
908
909 let pos = match self.appearance.panel_position {
910 PanelPosition::Top => "Top",
911 PanelPosition::Bottom => "Bottom",
912 };
913 Self::render_label_value(
914 buf,
915 buf_width,
916 3,
917 b"Panel Position:",
918 pos.as_bytes(),
919 self.selected_item == 3,
920 );
921 }
922
923 fn render_about_panel(&self, buf: &mut [u8], buf_width: usize) {
924 let mem = crate::mm::get_memory_stats();
926 let total_mb = (mem.total_frames * 4096) / (1024 * 1024);
927 let used_frames = mem.total_frames.saturating_sub(mem.free_frames);
928 let used_mb = (used_frames * 4096) / (1024 * 1024);
929 let mem_str = format!("{}/{} MB", used_mb, total_mb);
930
931 let arch_str = if cfg!(target_arch = "x86_64") {
933 "x86_64"
934 } else if cfg!(target_arch = "aarch64") {
935 "aarch64"
936 } else if cfg!(target_arch = "riscv64") {
937 "riscv64gc"
938 } else {
939 "unknown"
940 };
941
942 let rows: [(&[u8], &[u8]); 7] = [
943 (b"OS:", self.about.os_name.as_bytes()),
944 (b"Version:", self.about.version.as_bytes()),
945 (b"Kernel:", self.about.kernel_version.as_bytes()),
946 (b"Package:", self.about.arch.as_bytes()),
947 (b"Hostname:", self.about.hostname.as_bytes()),
948 (b"Arch:", arch_str.as_bytes()),
949 (b"Memory:", mem_str.as_bytes()),
950 ];
951
952 for (i, (label, value)) in rows.iter().enumerate() {
953 Self::render_label_value(buf, buf_width, i, label, value, false);
955 }
956 }
957
958 fn render_audio_panel(&self, buf: &mut [u8], buf_width: usize) {
959 let vol = format!(
960 "{}%{}",
961 self.audio.master_volume,
962 if self.audio.muted { " (muted)" } else { "" }
963 );
964 Self::render_label_value(
965 buf,
966 buf_width,
967 0,
968 b"Volume:",
969 vol.as_bytes(),
970 self.selected_item == 0,
971 );
972
973 let mute = if self.audio.muted { "Yes" } else { "No" };
974 Self::render_label_value(
975 buf,
976 buf_width,
977 1,
978 b"Muted:",
979 mute.as_bytes(),
980 self.selected_item == 1,
981 );
982
983 Self::render_label_value(
984 buf,
985 buf_width,
986 2,
987 b"Output:",
988 self.audio.output_device.as_bytes(),
989 self.selected_item == 2,
990 );
991 }
992
993 fn render_bluetooth_panel(&self, buf: &mut [u8], buf_width: usize) {
994 let enabled = if self.bluetooth.enabled { "On" } else { "Off" };
995 Self::render_label_value(
996 buf,
997 buf_width,
998 0,
999 b"Bluetooth:",
1000 enabled.as_bytes(),
1001 self.selected_item == 0,
1002 );
1003
1004 let disc = if self.bluetooth.discoverable {
1005 "Yes"
1006 } else {
1007 "No"
1008 };
1009 Self::render_label_value(
1010 buf,
1011 buf_width,
1012 1,
1013 b"Discoverable:",
1014 disc.as_bytes(),
1015 self.selected_item == 1,
1016 );
1017
1018 Self::render_label_value(
1019 buf,
1020 buf_width,
1021 2,
1022 b"Device Name:",
1023 self.bluetooth.device_name.as_bytes(),
1024 self.selected_item == 2,
1025 );
1026
1027 let y = CONTENT_Y + 4 * LINE_HEIGHT;
1029 draw_string_into_buffer(
1030 buf,
1031 buf_width,
1032 b"Paired Devices: (none)",
1033 CONTENT_X,
1034 y,
1035 0x777777,
1036 );
1037 }
1038
1039 fn render_power_panel(&self, buf: &mut [u8], buf_width: usize) {
1040 let profile = if self.power_settings.profile_index < self.power_settings.profiles.len() {
1041 self.power_settings.profiles[self.power_settings.profile_index]
1042 } else {
1043 "Unknown"
1044 };
1045 Self::render_label_value(
1046 buf,
1047 buf_width,
1048 0,
1049 b"Power Profile:",
1050 profile.as_bytes(),
1051 self.selected_item == 0,
1052 );
1053
1054 let timeout = if self.power_settings.screen_timeout_min == 0 {
1055 String::from("Never")
1056 } else {
1057 format!("{} min", self.power_settings.screen_timeout_min)
1058 };
1059 Self::render_label_value(
1060 buf,
1061 buf_width,
1062 1,
1063 b"Screen Timeout:",
1064 timeout.as_bytes(),
1065 self.selected_item == 1,
1066 );
1067
1068 let y = CONTENT_Y + 3 * LINE_HEIGHT;
1070 let governor = match crate::power::get_governor() {
1071 crate::power::Governor::OnDemand => "OnDemand",
1072 crate::power::Governor::Performance => "Performance",
1073 crate::power::Governor::PowerSave => "PowerSave",
1074 };
1075 let gov_label = format!("Governor: {}", governor);
1076 draw_string_into_buffer(buf, buf_width, gov_label.as_bytes(), CONTENT_X, y, 0x777777);
1077 }
1078}
1079
1080impl Default for SettingsApp {
1081 fn default() -> Self {
1082 Self::new()
1083 }
1084}