1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
//! A visual delimiter widget.

use figures::units::{Lp, UPx};
use figures::{Point, ScreenScale, Size};
use kludgine::shapes::{PathBuilder, StrokeOptions};
use kludgine::Color;

use crate::context::{GraphicsContext, LayoutContext};
use crate::styles::components::TextColor;
use crate::styles::{Dimension, FlexibleDimension};
use crate::value::{IntoValue, Value};
use crate::widget::Widget;
use crate::ConstraintLimit;

#[derive(Debug)]
enum Orientation {
    Horizontal,
    Vertical,
}

/// A visual delimiter that can be horizontal or vertical.
///
/// This is similar to html's `<hr>` tag.
#[derive(Debug)]
pub struct Delimiter {
    size: Value<FlexibleDimension>,
    orientation: Orientation,
}

impl Default for Delimiter {
    fn default() -> Self {
        Self::horizontal()
    }
}

impl Delimiter {
    fn new(orientation: Orientation) -> Self {
        Self {
            size: Value::Constant(FlexibleDimension::Auto),
            orientation,
        }
    }

    /// Returns a horizontal delimiter.
    #[must_use]
    pub fn horizontal() -> Self {
        Self::new(Orientation::Horizontal)
    }

    /// Returns a vertical delimiter.
    #[must_use]
    pub fn vertical() -> Self {
        Self::new(Orientation::Vertical)
    }

    /// Sets the size of the delimiter.
    ///
    /// If auto, a theme-derived size is used.
    #[must_use]
    pub fn size(mut self, size: impl IntoValue<FlexibleDimension>) -> Self {
        self.size = size.into_value();
        self
    }

    fn get_size(&self, context: &mut GraphicsContext<'_, '_, '_, '_>) -> Dimension {
        match self.size.get_tracking_invalidate(context) {
            FlexibleDimension::Auto => context.get(&DelimiterSize),
            FlexibleDimension::Dimension(dimension) => dimension,
        }
    }
}

impl Widget for Delimiter {
    fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_>) {
        let line_width = self.get_size(context).into_upx(context.gfx.scale());
        let half_line = line_width / 2;
        let end = match self.orientation {
            Orientation::Horizontal => Point::new(context.gfx.size().width - half_line, half_line),
            Orientation::Vertical => Point::new(half_line, context.gfx.size().height - half_line),
        };
        let color = context.get(&DelimiterColor);
        context.gfx.draw_shape(
            &PathBuilder::new(Point::squared(half_line))
                .line_to(end)
                .build()
                .stroke(StrokeOptions {
                    color,
                    line_width,
                    ..StrokeOptions::default()
                }),
        );
    }

    fn layout(
        &mut self,
        available_space: Size<ConstraintLimit>,
        context: &mut LayoutContext<'_, '_, '_, '_>,
    ) -> Size<UPx> {
        let size = self.get_size(context).into_upx(context.gfx.scale());
        match self.orientation {
            Orientation::Horizontal => Size::new(available_space.width.max(), size),
            Orientation::Vertical => Size::new(size, available_space.height.max()),
        }
    }
}

define_components! {
    Delimiter {
        /// The [`Dimension`] to use as the size of a [`Delimiter`] widget.
        DelimiterSize(Dimension, "size", Dimension::Lp(Lp::new(2)))
        /// The [`Color`] draw a [`Delimiter`] widget using.
        DelimiterColor(Color, "color", @TextColor)
    }
}