use rlvgl_core::event::Event;
use rlvgl_core::raster::PointF;
use rlvgl_core::renderer::Renderer;
use rlvgl_core::style::Style;
use rlvgl_core::widget::{Rect, Widget};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Point {
pub x: i32,
pub y: i32,
}
#[derive(Debug)]
pub struct Line<'a> {
bounds: Rect,
points: &'a [Point],
y_invert: bool,
pub style: Style,
}
impl<'a> Line<'a> {
pub fn new(bounds: Rect, points: &'a [Point]) -> Self {
let style = Style {
border_width: 1,
..Style::default()
};
Self {
bounds,
points,
y_invert: false,
style,
}
}
pub fn points(&self) -> &'a [Point] {
self.points
}
pub fn set_points(&mut self, points: &'a [Point]) {
self.points = points;
}
pub fn set_y_invert(&mut self, y_invert: bool) {
self.y_invert = y_invert;
}
pub fn y_invert(&self) -> bool {
self.y_invert
}
fn point_to_framebuffer(&self, point: Point) -> PointF {
let y = if self.y_invert {
self.bounds.height - point.y
} else {
point.y
};
PointF::new((self.bounds.x + point.x) as f32, (self.bounds.y + y) as f32)
}
}
impl Widget for Line<'_> {
fn bounds(&self) -> Rect {
self.bounds
}
fn draw(&self, renderer: &mut dyn Renderer) {
if self.style.border_width == 0 || self.style.alpha == 0 {
return;
}
let color = self.style.border_color.with_alpha(self.style.alpha);
let width = self.style.border_width as f32;
for pair in self.points.windows(2) {
renderer.stroke_line_aa(
self.point_to_framebuffer(pair[0]),
self.point_to_framebuffer(pair[1]),
width,
color,
);
}
}
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::*;
use alloc::vec::Vec;
use rlvgl_core::font::ShapedText;
use rlvgl_core::widget::Color;
#[derive(Debug, Clone, Copy, PartialEq)]
struct Stroke {
a: PointF,
b: PointF,
width: f32,
color: Color,
}
struct RecordingRenderer {
strokes: Vec<Stroke>,
}
impl RecordingRenderer {
fn new() -> Self {
Self {
strokes: Vec::new(),
}
}
}
impl Renderer for RecordingRenderer {
fn fill_rect(&mut self, _rect: Rect, _color: Color) {}
fn draw_text(&mut self, _position: (i32, i32), _text: &str, _color: Color) {}
fn draw_text_shaped(
&mut self,
_shaped: &ShapedText<'_>,
_origin: (i32, i32),
_color: Color,
) {
}
fn stroke_line_aa(&mut self, a: PointF, b: PointF, width: f32, color: Color) {
self.strokes.push(Stroke { a, b, width, color });
}
}
#[test]
fn draws_adjacent_segments_with_style_stroke() {
let points = [
Point { x: 1, y: 2 },
Point { x: 11, y: 12 },
Point { x: 21, y: 2 },
];
let mut line = Line::new(
Rect {
x: 10,
y: 20,
width: 40,
height: 30,
},
&points,
);
line.style.border_color = Color(10, 20, 30, 200);
line.style.border_width = 3;
line.style.alpha = 128;
let mut renderer = RecordingRenderer::new();
line.draw(&mut renderer);
assert_eq!(
renderer.strokes,
alloc::vec![
Stroke {
a: PointF::new(11.0, 22.0),
b: PointF::new(21.0, 32.0),
width: 3.0,
color: Color(10, 20, 30, 200).with_alpha(128),
},
Stroke {
a: PointF::new(21.0, 32.0),
b: PointF::new(31.0, 22.0),
width: 3.0,
color: Color(10, 20, 30, 200).with_alpha(128),
},
]
);
}
#[test]
fn y_invert_maps_against_bounds_height() {
let points = [Point { x: 0, y: 0 }, Point { x: 10, y: 7 }];
let mut line = Line::new(
Rect {
x: 4,
y: 5,
width: 30,
height: 20,
},
&points,
);
line.set_y_invert(true);
let mut renderer = RecordingRenderer::new();
line.draw(&mut renderer);
assert_eq!(
renderer.strokes,
alloc::vec![Stroke {
a: PointF::new(4.0, 25.0),
b: PointF::new(14.0, 18.0),
width: 1.0,
color: line.style.border_color.with_alpha(line.style.alpha),
}]
);
}
#[test]
fn set_points_and_set_bounds_update_draw_inputs() {
let old_points = [Point { x: 0, y: 0 }, Point { x: 1, y: 1 }];
let new_points = [Point { x: 2, y: 3 }, Point { x: 4, y: 5 }];
let mut line = Line::new(
Rect {
x: 0,
y: 0,
width: 10,
height: 10,
},
&old_points,
);
line.set_points(&new_points);
line.set_bounds(Rect {
x: 7,
y: 11,
width: 20,
height: 30,
});
assert_eq!(line.points(), &new_points);
assert_eq!(
line.bounds(),
Rect {
x: 7,
y: 11,
width: 20,
height: 30,
}
);
let mut renderer = RecordingRenderer::new();
line.draw(&mut renderer);
assert_eq!(
renderer.strokes,
alloc::vec![Stroke {
a: PointF::new(9.0, 14.0),
b: PointF::new(11.0, 16.0),
width: 1.0,
color: line.style.border_color.with_alpha(line.style.alpha),
}]
);
}
#[test]
fn zero_width_or_short_point_lists_do_not_stroke() {
let points = [Point { x: 0, y: 0 }];
let mut line = Line::new(
Rect {
x: 0,
y: 0,
width: 10,
height: 10,
},
&points,
);
let mut renderer = RecordingRenderer::new();
line.draw(&mut renderer);
assert!(renderer.strokes.is_empty());
let points = [Point { x: 0, y: 0 }, Point { x: 5, y: 5 }];
line.set_points(&points);
line.style.border_width = 0;
line.draw(&mut renderer);
assert!(renderer.strokes.is_empty());
}
}