use crate::core::{Color, Point, Rect, Size};
use crate::event::{Event, EventHandler};
use crate::render::RenderContext;
use crate::widget::{BaseWidget, Draw, Widget, WidgetKind};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LineOrientation {
Horizontal,
Vertical,
}
pub struct Line {
base: BaseWidget,
orientation: LineOrientation,
thickness: u32,
color: Option<Color>,
}
impl Line {
pub fn new(orientation: LineOrientation, rect: Rect) -> Self {
Self {
base: BaseWidget::new(WidgetKind::Line, rect, "Line"),
orientation,
thickness: 2,
color: None,
}
}
pub fn orientation(&self) -> LineOrientation {
self.orientation
}
pub fn set_orientation(&mut self, ori: LineOrientation) {
self.orientation = ori;
}
pub fn set_thickness(&mut self, t: u32) {
self.thickness = t.max(1);
}
}
impl Widget for Line {
fn base(&self) -> &BaseWidget {
&self.base
}
fn base_mut(&mut self) -> &mut BaseWidget {
&mut self.base
}
fn size_hint(&self) -> Size {
match self.orientation {
LineOrientation::Horizontal => Size::new(120, self.thickness.max(2)),
LineOrientation::Vertical => Size::new(self.thickness.max(2), 120),
}
}
}
impl EventHandler for Line {
fn handle_event(&mut self, event: &Event) {
self.base.handle_event(event);
}
}
impl Draw for Line {
fn draw(&mut self, context: &mut RenderContext) {
let rect = self.geometry();
if rect.width == 0 || rect.height == 0 {
return;
}
let line_color = self
.color
.or_else(|| self.style().border_color)
.unwrap_or(Color::from_rgb(180, 180, 180));
let thickness = self.thickness.max(1);
match self.orientation {
LineOrientation::Horizontal => {
let mid_y = rect.y + rect.height as i32 / 2;
let from = Point::new(rect.x, mid_y);
let to = Point::new(rect.x + rect.width as i32, mid_y);
context.draw_line_stroke(from, to, line_color, thickness);
if thickness > 1 && thickness % 2 == 0 {
let extra_from = Point::new(rect.x, mid_y - 1);
let extra_to = Point::new(rect.x + rect.width as i32, mid_y - 1);
context.draw_line_stroke(extra_from, extra_to, line_color, 1);
}
}
LineOrientation::Vertical => {
let mid_x = rect.x + rect.width as i32 / 2;
let from = Point::new(mid_x, rect.y);
let to = Point::new(mid_x, rect.y + rect.height as i32);
context.draw_line_stroke(from, to, line_color, thickness);
if thickness > 1 && thickness % 2 == 0 {
let extra_from = Point::new(mid_x - 1, rect.y);
let extra_to = Point::new(mid_x - 1, rect.y + rect.height as i32);
context.draw_line_stroke(extra_from, extra_to, line_color, 1);
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::{Color, Rect, Size};
use crate::render::{PaintBackend, SoftwarePaintBackend};
#[test]
fn line_creation() {
let line = Line::new(LineOrientation::Horizontal, Rect::new(0, 0, 200, 4));
assert_eq!(line.orientation(), LineOrientation::Horizontal);
assert_eq!(line.kind(), WidgetKind::Line);
}
#[test]
fn line_orientation() {
let mut line = Line::new(LineOrientation::Horizontal, Rect::new(0, 0, 200, 4));
assert_eq!(line.orientation(), LineOrientation::Horizontal);
line.set_orientation(LineOrientation::Vertical);
assert_eq!(line.orientation(), LineOrientation::Vertical);
line.set_orientation(LineOrientation::Horizontal);
assert_eq!(line.orientation(), LineOrientation::Horizontal);
}
#[test]
fn line_set_thickness() {
let mut line = Line::new(LineOrientation::Horizontal, Rect::new(0, 0, 200, 4));
line.set_thickness(5);
assert_eq!(line.thickness, 5);
line.set_thickness(0);
assert_eq!(line.thickness, 1);
}
#[test]
fn line_draw_no_panic() {
let mut line = Line::new(LineOrientation::Horizontal, Rect::new(0, 0, 200, 4));
let mut backend = SoftwarePaintBackend::new(Size::new(200, 10), 1.0);
backend.begin_frame(Color::WHITE);
let mut context = RenderContext::new(&mut backend);
line.draw(&mut context);
backend.end_frame();
let rgba = backend.frame_rgba();
assert!(!rgba.is_empty());
}
#[test]
fn line_draw_vertical_no_panic() {
let mut line = Line::new(LineOrientation::Vertical, Rect::new(0, 0, 4, 200));
let mut backend = SoftwarePaintBackend::new(Size::new(10, 200), 1.0);
backend.begin_frame(Color::WHITE);
let mut context = RenderContext::new(&mut backend);
line.draw(&mut context);
backend.end_frame();
let rgba = backend.frame_rgba();
assert!(!rgba.is_empty());
}
#[test]
fn line_draw_zero_geometry_no_panic() {
let mut line = Line::new(LineOrientation::Horizontal, Rect::new(0, 0, 0, 0));
let mut backend = SoftwarePaintBackend::new(Size::new(10, 10), 1.0);
backend.begin_frame(Color::WHITE);
let mut context = RenderContext::new(&mut backend);
line.draw(&mut context);
backend.end_frame();
}
#[test]
fn line_size_hint() {
let line = Line::new(LineOrientation::Horizontal, Rect::new(0, 0, 200, 4));
assert_eq!(line.size_hint(), Size::new(120, 2));
let mut line = Line::new(LineOrientation::Vertical, Rect::new(0, 0, 4, 200));
line.set_thickness(6);
assert_eq!(line.size_hint(), Size::new(6, 120));
}
#[test]
fn line_geometry_delegation() {
let mut line = Line::new(LineOrientation::Horizontal, Rect::new(0, 0, 200, 4));
line.set_geometry(Rect::new(5, 5, 150, 6));
assert_eq!(line.geometry(), Rect::new(5, 5, 150, 6));
}
#[test]
fn line_event_delegation() {
let mut line = Line::new(LineOrientation::Horizontal, Rect::new(0, 0, 200, 4));
line.handle_event(&Event::KeyDown((37, 0)));
}
}