use crate::compat::ToString;
use crate::core::{Color, Rect, Size};
use crate::event::{Event, EventHandler};
use crate::render::RenderContext;
use crate::widget::capability::coercion::{expect_string, expect_u32};
use crate::widget::capability::properties_trait::{base_property_get, base_property_set};
use crate::widget::capability::types::{CapabilityAccessError, CapabilityValue};
use crate::widget::capability::WidgetProperties;
use crate::widget::display_widgets::draw_line;
use crate::widget::{BaseWidget, Draw, Widget, WidgetKind};
use crate::{impl_widget_property_hooks, property_names_of};
#[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) {
if self.orientation == ori {
return;
}
self.orientation = ori;
self.base.request_redraw();
}
pub fn set_thickness(&mut self, t: u32) {
let thickness = t.max(1);
if self.thickness == thickness {
return;
}
self.thickness = thickness;
self.base.request_redraw();
}
pub fn thickness(&self) -> u32 {
self.thickness
}
pub fn color(&self) -> Option<Color> {
self.color
}
pub fn set_color(&mut self, color: Option<Color>) {
if self.color == color {
return;
}
self.color = color;
self.base.request_redraw();
}
fn line_color(&self) -> Color {
self.color.or_else(|| self.style().border_color).unwrap_or(Color::rgb(180, 180, 180))
}
}
impl Widget for Line {
fn base(&self) -> &BaseWidget {
&self.base
}
fn set_state_theme_hook(&mut self) {
crate::style::reapply_active_theme_state(self);
}
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_draw_bridge!();
impl_widget_property_hooks!();
}
impl WidgetProperties for Line {
fn get(&self, name: &str) -> Result<CapabilityValue, CapabilityAccessError> {
match name {
"orientation" => {
let orientation = match self.orientation() {
LineOrientation::Horizontal => "horizontal",
LineOrientation::Vertical => "vertical",
};
Ok(CapabilityValue::String(orientation.to_string()))
}
"color" => Ok(CapabilityValue::Color(self.line_color())),
"thickness" => Ok(CapabilityValue::UInt(u64::from(self.thickness()))),
_ => base_property_get(self, name),
}
}
fn set(&mut self, name: &str, value: CapabilityValue) -> Result<(), CapabilityAccessError> {
match name {
"orientation" => {
let token = expect_string(value)?;
let orientation = match token.as_str() {
"horizontal" => LineOrientation::Horizontal,
"vertical" => LineOrientation::Vertical,
_ => return Err(CapabilityAccessError::TypeMismatch),
};
self.set_orientation(orientation);
Ok(())
}
"color" => {
match value {
CapabilityValue::Null => self.set_color(None),
CapabilityValue::Color(color) => self.set_color(Some(color)),
CapabilityValue::String(raw) => match Color::parse_hex(&raw) {
Some(color) => self.set_color(Some(color)),
None => return Err(CapabilityAccessError::TypeMismatch),
},
_ => return Err(CapabilityAccessError::TypeMismatch),
}
Ok(())
}
"thickness" => {
self.set_thickness(expect_u32(value)?);
Ok(())
}
_ => base_property_set(self, name, value),
}
}
fn property_names(&self) -> &'static [&'static str] {
property_names_of!["orientation", "thickness", "color", BASE_PROPERTY_NAMES]
}
fn command(&mut self, name: &str) -> Result<(), CapabilityAccessError> {
match name {
"set_orientation" => Err(CapabilityAccessError::OutOfRange),
_ => Err(CapabilityAccessError::UnknownCommand),
}
}
}
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();
let is_vertical = self.orientation == LineOrientation::Vertical;
let line_color = self.line_color();
draw_line(context, rect, is_vertical, self.thickness, line_color);
}
}
#[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_colour_override_round_trips() {
use crate::widget::capability::WidgetProperties;
let mut line = Line::new(LineOrientation::Horizontal, Rect::new(0, 0, 200, 4));
assert_eq!(line.color(), None);
line.set_color(Some(Color::RED));
assert_eq!(line.color(), Some(Color::RED));
assert_eq!(
line.get("color"),
Ok(CapabilityValue::Color(Color::RED)),
"the published colour is the effective one the painter uses"
);
line.set_color(None);
assert_eq!(line.color(), None, "`None` restores theme-following");
}
#[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)));
}
}