1use crate::core;
20use crate::core::border;
21use crate::core::layout;
22use crate::core::mouse;
23use crate::core::renderer;
24use crate::core::widget::Tree;
25use crate::core::{
26 Color, Element, Layout, Length, Pixels, Rectangle, Size, Theme, Widget,
27};
28
29#[allow(missing_debug_implementations)]
48pub struct Rule<'a, Theme = crate::Theme>
49where
50 Theme: Catalog,
51{
52 width: Length,
53 height: Length,
54 is_horizontal: bool,
55 class: Theme::Class<'a>,
56}
57
58impl<'a, Theme> Rule<'a, Theme>
59where
60 Theme: Catalog,
61{
62 pub fn horizontal(height: impl Into<Pixels>) -> Self {
64 Rule {
65 width: Length::Fill,
66 height: Length::Fixed(height.into().0),
67 is_horizontal: true,
68 class: Theme::default(),
69 }
70 }
71
72 pub fn vertical(width: impl Into<Pixels>) -> Self {
74 Rule {
75 width: Length::Fixed(width.into().0),
76 height: Length::Fill,
77 is_horizontal: false,
78 class: Theme::default(),
79 }
80 }
81
82 #[must_use]
84 pub fn style(mut self, style: impl Fn(&Theme) -> Style + 'a) -> Self
85 where
86 Theme::Class<'a>: From<StyleFn<'a, Theme>>,
87 {
88 self.class = (Box::new(style) as StyleFn<'a, Theme>).into();
89 self
90 }
91
92 #[cfg(feature = "advanced")]
94 #[must_use]
95 pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self {
96 self.class = class.into();
97 self
98 }
99}
100
101impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer>
102 for Rule<'_, Theme>
103where
104 Renderer: core::Renderer,
105 Theme: Catalog,
106{
107 fn size(&self) -> Size<Length> {
108 Size {
109 width: self.width,
110 height: self.height,
111 }
112 }
113
114 fn layout(
115 &self,
116 _tree: &mut Tree,
117 _renderer: &Renderer,
118 limits: &layout::Limits,
119 ) -> layout::Node {
120 layout::atomic(limits, self.width, self.height)
121 }
122
123 fn draw(
124 &self,
125 _state: &Tree,
126 renderer: &mut Renderer,
127 theme: &Theme,
128 _style: &renderer::Style,
129 layout: Layout<'_>,
130 _cursor: mouse::Cursor,
131 _viewport: &Rectangle,
132 ) {
133 let bounds = layout.bounds();
134 let style = theme.style(&self.class);
135
136 let bounds = if self.is_horizontal {
137 let line_y = (bounds.y + (bounds.height / 2.0)
138 - (style.width as f32 / 2.0))
139 .round();
140
141 let (offset, line_width) = style.fill_mode.fill(bounds.width);
142 let line_x = bounds.x + offset;
143
144 Rectangle {
145 x: line_x,
146 y: line_y,
147 width: line_width,
148 height: style.width as f32,
149 }
150 } else {
151 let line_x = (bounds.x + (bounds.width / 2.0)
152 - (style.width as f32 / 2.0))
153 .round();
154
155 let (offset, line_height) = style.fill_mode.fill(bounds.height);
156 let line_y = bounds.y + offset;
157
158 Rectangle {
159 x: line_x,
160 y: line_y,
161 width: style.width as f32,
162 height: line_height,
163 }
164 };
165
166 renderer.fill_quad(
167 renderer::Quad {
168 bounds,
169 border: border::rounded(style.radius),
170 ..renderer::Quad::default()
171 },
172 style.color,
173 );
174 }
175}
176
177impl<'a, Message, Theme, Renderer> From<Rule<'a, Theme>>
178 for Element<'a, Message, Theme, Renderer>
179where
180 Message: 'a,
181 Theme: 'a + Catalog,
182 Renderer: 'a + core::Renderer,
183{
184 fn from(rule: Rule<'a, Theme>) -> Element<'a, Message, Theme, Renderer> {
185 Element::new(rule)
186 }
187}
188
189#[derive(Debug, Clone, Copy, PartialEq)]
191pub struct Style {
192 pub color: Color,
194 pub width: u16,
196 pub radius: border::Radius,
198 pub fill_mode: FillMode,
200}
201
202#[derive(Debug, Clone, Copy, PartialEq)]
204pub enum FillMode {
205 Full,
207 Percent(f32),
212 Padded(u16),
214 AsymmetricPadding(u16, u16),
217}
218
219impl FillMode {
220 pub fn fill(&self, space: f32) -> (f32, f32) {
228 match *self {
229 FillMode::Full => (0.0, space),
230 FillMode::Percent(percent) => {
231 if percent >= 100.0 {
232 (0.0, space)
233 } else {
234 let percent_width = (space * percent / 100.0).round();
235
236 (((space - percent_width) / 2.0).round(), percent_width)
237 }
238 }
239 FillMode::Padded(padding) => {
240 if padding == 0 {
241 (0.0, space)
242 } else {
243 let padding = padding as f32;
244 let mut line_width = space - (padding * 2.0);
245 if line_width < 0.0 {
246 line_width = 0.0;
247 }
248
249 (padding, line_width)
250 }
251 }
252 FillMode::AsymmetricPadding(first_pad, second_pad) => {
253 let first_pad = first_pad as f32;
254 let second_pad = second_pad as f32;
255 let mut line_width = space - first_pad - second_pad;
256 if line_width < 0.0 {
257 line_width = 0.0;
258 }
259
260 (first_pad, line_width)
261 }
262 }
263 }
264}
265
266pub trait Catalog: Sized {
268 type Class<'a>;
270
271 fn default<'a>() -> Self::Class<'a>;
273
274 fn style(&self, class: &Self::Class<'_>) -> Style;
276}
277
278pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> Style + 'a>;
282
283impl Catalog for Theme {
284 type Class<'a> = StyleFn<'a, Self>;
285
286 fn default<'a>() -> Self::Class<'a> {
287 Box::new(default)
288 }
289
290 fn style(&self, class: &Self::Class<'_>) -> Style {
291 class(self)
292 }
293}
294
295pub fn default(theme: &Theme) -> Style {
297 let palette = theme.extended_palette();
298
299 Style {
300 color: palette.background.strong.color,
301 width: 1,
302 radius: 0.0.into(),
303 fill_mode: FillMode::Full,
304 }
305}