use rlvgl_core::draw::{draw_widget_bg, fill_rounded_rect};
use rlvgl_core::event::Event;
use rlvgl_core::renderer::Renderer;
use rlvgl_core::style::Style;
use rlvgl_core::widget::{Color, Rect, Widget};
pub const BRIGHT_MIN: u8 = 80;
pub const BRIGHT_MAX: u8 = 255;
pub struct Led {
bounds: Rect,
color: Color,
brightness: u8,
pub style: Style,
}
impl Led {
pub fn new(bounds: Rect) -> Self {
Self {
bounds,
color: Color(255, 0, 0, 255),
brightness: BRIGHT_MAX,
style: Style {
radius: 255,
..Style::default()
},
}
}
pub fn set_color(&mut self, color: Color) {
self.color = color;
}
pub fn color(&self) -> Color {
self.color
}
pub fn set_brightness(&mut self, brightness: u8) {
self.brightness = brightness.clamp(BRIGHT_MIN, BRIGHT_MAX);
}
pub fn brightness(&self) -> u8 {
self.brightness
}
pub fn on(&mut self) {
self.brightness = BRIGHT_MAX;
}
pub fn off(&mut self) {
self.brightness = BRIGHT_MIN;
}
pub fn toggle(&mut self) {
if self.brightness == BRIGHT_MAX {
self.off();
} else {
self.on();
}
}
fn lit_color(&self) -> Color {
let scale = u16::from(self.brightness);
Color(
((u16::from(self.color.0) * scale) / 255) as u8,
((u16::from(self.color.1) * scale) / 255) as u8,
((u16::from(self.color.2) * scale) / 255) as u8,
self.color.3,
)
.with_alpha(self.style.alpha)
}
fn inner_rect(&self) -> Option<Rect> {
if self.bounds.width <= 0 || self.bounds.height <= 0 {
return None;
}
let border = i32::from(self.style.border_width);
let rect = Rect {
x: self.bounds.x + border,
y: self.bounds.y + border,
width: self.bounds.width - border * 2,
height: self.bounds.height - border * 2,
};
(rect.width > 0 && rect.height > 0).then_some(rect)
}
}
impl Widget for Led {
fn bounds(&self) -> Rect {
self.bounds
}
fn draw(&self, renderer: &mut dyn Renderer) {
draw_widget_bg(renderer, self.bounds, &self.style);
let Some(rect) = self.inner_rect() else {
return;
};
let color = self.lit_color();
if color.3 == 0 {
return;
}
let radius = self.style.radius.saturating_sub(self.style.border_width);
fill_rounded_rect(renderer, rect, color, radius);
}
fn handle_event(&mut self, _event: &Event) -> bool {
false
}
fn set_bounds(&mut self, bounds: Rect) {
self.bounds = bounds;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn brightness_clamps_and_toggle_uses_lvgl_bounds() {
let mut led = Led::new(rect(0, 0, 10, 10));
led.set_brightness(0);
assert_eq!(led.brightness(), BRIGHT_MIN);
led.set_brightness(250);
assert_eq!(led.brightness(), 250);
led.on();
assert_eq!(led.brightness(), BRIGHT_MAX);
led.toggle();
assert_eq!(led.brightness(), BRIGHT_MIN);
led.toggle();
assert_eq!(led.brightness(), BRIGHT_MAX);
}
#[test]
fn lit_color_scales_rgb_and_preserves_alpha_policy() {
let mut led = Led::new(rect(0, 0, 10, 10));
led.set_color(Color(100, 50, 25, 200));
led.set_brightness(128);
led.style.alpha = 128;
assert_eq!(led.lit_color(), Color(50, 25, 12, 100));
}
#[test]
fn set_bounds_adopts_layout_bounds() {
let mut led = Led::new(rect(0, 0, 10, 10));
led.set_bounds(rect(5, 6, 7, 8));
assert_eq!(led.bounds(), rect(5, 6, 7, 8));
}
fn rect(x: i32, y: i32, width: i32, height: i32) -> Rect {
Rect {
x,
y,
width,
height,
}
}
}