1use crate::gui::{Message, State};
2use crate::theme;
3use aethermap_common::LedPattern;
4use aethermap_common::LedZone;
5use iced::{
6 widget::{button, column, container, horizontal_rule, row, slider, text, Column, Space},
7 Alignment, Color, Element, Length, Theme,
8};
9use std::collections::HashMap;
10
11#[derive(Debug, Clone)]
12pub struct LedState {
13 pub zone_colors: HashMap<LedZone, (u8, u8, u8)>,
14 pub global_brightness: u8,
15 pub zone_brightness: HashMap<LedZone, u8>,
16 pub active_pattern: LedPattern,
17}
18
19impl Default for LedState {
20 fn default() -> Self {
21 Self {
22 zone_colors: HashMap::new(),
23 global_brightness: 100,
24 zone_brightness: HashMap::new(),
25 active_pattern: LedPattern::Static,
26 }
27 }
28}
29
30fn get_zone_color(state: &State, zone: LedZone) -> (u8, u8, u8) {
31 if let Some(device_id) = &state.led_config_device {
32 if let Some(led_state) = state.led_states.get(device_id) {
33 if let Some(&color) = led_state.zone_colors.get(&zone) {
34 return color;
35 }
36 }
37 }
38 (255, 255, 255)
39}
40
41fn led_color_style(
42 zone: Option<LedZone>,
43 zone_colors: &HashMap<LedZone, (u8, u8, u8)>,
44) -> iced::theme::Container {
45 let (r, g, b) = zone
46 .and_then(|z| zone_colors.get(&z))
47 .copied()
48 .unwrap_or((255, 255, 255));
49
50 struct LedColorStyle {
51 r: u8,
52 g: u8,
53 b: u8,
54 }
55
56 impl iced::widget::container::StyleSheet for LedColorStyle {
57 type Style = Theme;
58 fn appearance(&self, _style: &Self::Style) -> iced::widget::container::Appearance {
59 iced::widget::container::Appearance {
60 background: Some(Color::from_rgb8(self.r, self.g, self.b).into()),
61 ..Default::default()
62 }
63 }
64 }
65
66 iced::theme::Container::Custom(Box::new(LedColorStyle { r, g, b }))
67}
68
69fn view_led_rgb_sliders(state: &State) -> Element<'_, Message> {
70 let zone = state.selected_led_zone.unwrap_or(LedZone::Logo);
71 let (r, g, b) = state
72 .pending_led_color
73 .unwrap_or_else(|| get_zone_color(state, zone));
74
75 Column::new()
76 .spacing(8)
77 .push(
78 row![
79 text("Red:").size(12).width(Length::Fixed(40.0)),
80 text(format!("{}", r)).size(12).width(Length::Fixed(30.0)),
81 slider(0..=255, r, move |v| { Message::LedSliderChanged(v, g, b) })
82 .width(Length::Fill)
83 ]
84 .spacing(8)
85 .align_items(Alignment::Center),
86 )
87 .push(
88 row![
89 text("Green:").size(12).width(Length::Fixed(40.0)),
90 text(format!("{}", g)).size(12).width(Length::Fixed(30.0)),
91 slider(0..=255, g, move |v| { Message::LedSliderChanged(r, v, b) })
92 .width(Length::Fill)
93 ]
94 .spacing(8)
95 .align_items(Alignment::Center),
96 )
97 .push(
98 row![
99 text("Blue:").size(12).width(Length::Fixed(40.0)),
100 text(format!("{}", b)).size(12).width(Length::Fixed(30.0)),
101 slider(0..=255, b, move |v| { Message::LedSliderChanged(r, g, v) })
102 .width(Length::Fill)
103 ]
104 .spacing(8)
105 .align_items(Alignment::Center),
106 )
107 .into()
108}
109
110pub fn view(state: &State) -> Option<Element<'_, Message>> {
111 if let Some(ref device_id) = state.led_config_device {
112 let selected_zone = state.selected_led_zone.unwrap_or(LedZone::Logo);
113 let led_state = state.led_states.get(device_id);
114 let zone_colors = led_state.map(|s| &s.zone_colors);
115 let current_color = get_zone_color(state, selected_zone);
116
117 let zones = vec![
118 (LedZone::Logo, "Logo"),
119 (LedZone::Keys, "Keys"),
120 (LedZone::Thumbstick, "Thumbstick"),
121 ];
122
123 let zone_buttons: Vec<Element<'_, Message>> = zones
124 .into_iter()
125 .map(|(zone, label)| {
126 let is_selected = state.selected_led_zone == Some(zone);
127 button(text(label).size(12))
128 .on_press(Message::SelectLedZone(zone))
129 .style(if is_selected {
130 iced::theme::Button::Primary
131 } else {
132 iced::theme::Button::Secondary
133 })
134 .padding([6, 12])
135 .into()
136 })
137 .collect();
138
139 let preview = container(
140 container(
141 text(format!(
142 "RGB({}, {}, {})",
143 current_color.0, current_color.1, current_color.2
144 ))
145 .size(11)
146 .horizontal_alignment(iced::alignment::Horizontal::Center),
147 )
148 .width(Length::Fill)
149 .height(Length::Fill)
150 .align_x(iced::alignment::Horizontal::Center)
151 .align_y(iced::alignment::Vertical::Center),
152 )
153 .width(Length::Fixed(120.0))
154 .height(Length::Fixed(60.0))
155 .style(if let Some(colors) = zone_colors {
156 led_color_style(state.selected_led_zone, colors)
157 } else {
158 iced::theme::Container::Transparent
159 });
160
161 let patterns = vec![
162 (LedPattern::Static, "Static"),
163 (LedPattern::Breathing, "Breathing"),
164 (LedPattern::Rainbow, "Rainbow"),
165 ];
166
167 let current_pattern = led_state
168 .map(|s| s.active_pattern)
169 .unwrap_or(LedPattern::Static);
170
171 let pattern_buttons: Vec<Element<'_, Message>> = patterns
172 .into_iter()
173 .map(|(pattern, label)| {
174 let is_active = current_pattern == pattern;
175 button(text(label).size(11))
176 .on_press(Message::SetLedPattern(device_id.clone(), pattern))
177 .style(if is_active {
178 iced::theme::Button::Primary
179 } else {
180 iced::theme::Button::Secondary
181 })
182 .padding([4, 10])
183 .into()
184 })
185 .collect();
186
187 let brightness = led_state
188 .map(|s| s.global_brightness as f32)
189 .unwrap_or(100.0);
190
191 let dialog = container(
192 column![
193 row![
194 text("LED Configuration").size(18),
195 Space::with_width(Length::Fill),
196 button(text("\u{00d7}").size(20))
197 .on_press(Message::CloseLedConfig)
198 .style(iced::theme::Button::Text)
199 .padding([0, 8])
200 ]
201 .spacing(8)
202 .align_items(Alignment::Center),
203 horizontal_rule(1),
204 text(device_id).size(11).width(Length::Fill),
205 text("Zone:").size(13),
206 row(zone_buttons).spacing(8),
207 horizontal_rule(1),
208 text("Color:").size(13),
209 row![
210 preview,
211 column![
212 text("Adjust RGB sliders below").size(11),
213 text("to change color").size(11),
214 ]
215 .spacing(4)
216 ]
217 .spacing(12)
218 .align_items(Alignment::Center),
219 view_led_rgb_sliders(state),
220 horizontal_rule(1),
221 text(format!("Brightness: {}%", brightness as u8)).size(13),
222 slider(0.0..=100.0, brightness, move |v| {
223 Message::SetLedBrightness(device_id.clone(), None, v as u8)
224 })
225 .width(Length::Fill),
226 horizontal_rule(1),
227 text("Pattern:").size(13),
228 row(pattern_buttons).spacing(8),
229 horizontal_rule(1),
230 row![
231 Space::with_width(Length::Fill),
232 button(text("Close").size(13))
233 .on_press(Message::CloseLedConfig)
234 .style(iced::theme::Button::Secondary)
235 .padding([6, 16])
236 ]
237 .spacing(8)
238 ]
239 .spacing(12)
240 .padding(20),
241 )
242 .max_width(500)
243 .style(theme::styles::card);
244
245 Some(
246 container(dialog)
247 .width(Length::Fill)
248 .height(Length::Fill)
249 .align_x(iced::alignment::Horizontal::Center)
250 .align_y(iced::alignment::Vertical::Center)
251 .padding(40)
252 .style(iced::theme::Container::Transparent)
253 .into(),
254 )
255 } else {
256 None
257 }
258}