photon_ui/components/
settings_list.rs1use crossterm::event::{
2 KeyCode,
3 KeyModifiers,
4};
5
6use crate::{
7 Component,
8 Event,
9 Focusable,
10 InputResult,
11 RenderError,
12 Rendered,
13 theme::{
14 Palette,
15 Style,
16 Theme,
17 stylize,
18 },
19};
20
21pub struct SettingsList {
27 items: Vec<(String, bool)>,
28 selected: usize,
29 focused: bool,
30 on_change: Option<fn(usize, bool)>,
31}
32
33impl SettingsList {
34 pub fn new(items: Vec<(String, bool)>) -> Self {
36 Self {
37 items: items,
38 selected: 0,
39 focused: false,
40 on_change: None,
41 }
42 }
43
44 pub fn on_change(mut self, cb: fn(usize, bool)) -> Self {
48 self.on_change = Some(cb);
49 self
50 }
51
52 pub fn values(&self) -> Vec<bool> {
54 self.items.iter().map(|(_, v)| *v).collect()
55 }
56
57 pub fn set_selected(&mut self, index: usize) {
59 self.selected = index.min(self.items.len().saturating_sub(1));
60 }
61}
62
63impl Focusable for SettingsList {
64 fn focused(&self) -> bool {
65 self.focused
66 }
67
68 fn set_focused(&mut self, focused: bool) {
69 self.focused = focused;
70 }
71}
72
73impl Component for SettingsList {
74 fn render(&self, width: u16) -> Result<Rendered, RenderError> {
75 let theme = Theme::current();
76 let accent_style = Style::new().fg(theme.accent()).bold();
77 let primary_style = Style::new().fg(theme.text_primary());
78 let dim_style = Style::new().fg(theme.text_secondary());
79
80 let mut lines = Vec::new();
81 for (i, (name, value)) in self.items.iter().enumerate() {
82 let is_selected = i == self.selected;
83 let style = if is_selected && self.focused {
84 &accent_style
85 } else if is_selected {
86 &primary_style
87 } else {
88 &dim_style
89 };
90
91 let prefix = if is_selected { "> " } else { " " };
92 let check = if *value { "[x]" } else { "[ ]" };
93 let line = stylize(&format!("{}{} {}", prefix, check, name), style);
94 lines.push(crate::utils::truncate_to_width(&line, width, "…"));
95 }
96 Ok(Rendered {
97 lines,
98 cursor: None,
99 images: Vec::new(),
100 })
101 }
102
103 fn handle_input(&mut self, event: &Event) -> InputResult {
104 if let Event::Key(key) = event {
105 match key.code {
106 | KeyCode::Down => {
107 if self.selected + 1 < self.items.len() {
108 self.selected += 1;
109 }
110 InputResult::Handled
111 },
112 | KeyCode::Up => {
113 if self.selected > 0 {
114 self.selected -= 1;
115 }
116 InputResult::Handled
117 },
118 | KeyCode::Char('j') if !key.modifiers.contains(KeyModifiers::CONTROL) => {
119 if self.selected + 1 < self.items.len() {
120 self.selected += 1;
121 }
122 InputResult::Handled
123 },
124 | KeyCode::Char('k') if !key.modifiers.contains(KeyModifiers::CONTROL) => {
125 if self.selected > 0 {
126 self.selected -= 1;
127 }
128 InputResult::Handled
129 },
130 | KeyCode::Enter | KeyCode::Char(' ') => {
131 if let Some((_, value)) = self.items.get_mut(self.selected) {
132 *value = !*value;
133 if let Some(cb) = self.on_change {
134 cb(self.selected, *value);
135 }
136 }
137 InputResult::Handled
138 },
139 | _ => InputResult::Ignored,
140 }
141 } else {
142 InputResult::Ignored
143 }
144 }
145
146 fn as_focusable(&self) -> Option<&dyn Focusable> {
147 Some(self)
148 }
149
150 fn as_focusable_mut(&mut self) -> Option<&mut dyn Focusable> {
151 Some(self)
152 }
153}