1use crate::core;
20use crate::core::border;
21use crate::core::layout;
22use crate::core::mouse;
23use crate::core::renderer;
24use crate::core::widget;
25use crate::core::widget::Tree;
26use crate::core::widget::operation::accessible::{Accessible, Role};
27use crate::core::{Color, Element, Layout, Length, Pixels, Rectangle, Size, Theme, Widget};
28
29pub fn horizontal<'a, Theme>(height: impl Into<Pixels>) -> Rule<'a, Theme>
31where
32 Theme: Catalog,
33{
34 Rule {
35 thickness: Length::Fixed(height.into().0),
36 is_vertical: false,
37 class: Theme::default(),
38 }
39}
40
41pub fn vertical<'a, Theme>(width: impl Into<Pixels>) -> Rule<'a, Theme>
43where
44 Theme: Catalog,
45{
46 Rule {
47 thickness: Length::Fixed(width.into().0),
48 is_vertical: true,
49 class: Theme::default(),
50 }
51}
52
53pub struct Rule<'a, Theme = crate::Theme>
72where
73 Theme: Catalog,
74{
75 thickness: Length,
76 is_vertical: bool,
77 class: Theme::Class<'a>,
78}
79
80impl<'a, Theme> Rule<'a, Theme>
81where
82 Theme: Catalog,
83{
84 #[must_use]
86 pub fn style(mut self, style: impl Fn(&Theme) -> Style + 'a) -> Self
87 where
88 Theme::Class<'a>: From<StyleFn<'a, Theme>>,
89 {
90 self.class = (Box::new(style) as StyleFn<'a, Theme>).into();
91 self
92 }
93
94 #[cfg(feature = "advanced")]
96 #[must_use]
97 pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self {
98 self.class = class.into();
99 self
100 }
101}
102
103impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer> for Rule<'_, Theme>
104where
105 Renderer: core::Renderer,
106 Theme: Catalog,
107{
108 fn size(&self) -> Size<Length> {
109 if self.is_vertical {
110 Size {
111 width: self.thickness,
112 height: Length::Fill,
113 }
114 } else {
115 Size {
116 width: Length::Fill,
117 height: self.thickness,
118 }
119 }
120 }
121
122 fn layout(
123 &mut self,
124 _tree: &mut Tree,
125 _renderer: &Renderer,
126 limits: &layout::Limits,
127 ) -> layout::Node {
128 let size = <Self as Widget<(), Theme, Renderer>>::size(self);
129
130 layout::atomic(limits, size.width, size.height)
131 }
132
133 fn draw(
134 &self,
135 _tree: &Tree,
136 renderer: &mut Renderer,
137 theme: &Theme,
138 _style: &renderer::Style,
139 layout: Layout<'_>,
140 _cursor: mouse::Cursor,
141 _viewport: &Rectangle,
142 ) {
143 let bounds = layout.bounds();
144 let style = theme.style(&self.class);
145
146 let mut bounds = if self.is_vertical {
147 let line_x = bounds.x;
148
149 let (offset, line_height) = style.fill_mode.fill(bounds.height);
150 let line_y = bounds.y + offset;
151
152 Rectangle {
153 x: line_x,
154 y: line_y,
155 width: bounds.width,
156 height: line_height,
157 }
158 } else {
159 let line_y = bounds.y;
160
161 let (offset, line_width) = style.fill_mode.fill(bounds.width);
162 let line_x = bounds.x + offset;
163
164 Rectangle {
165 x: line_x,
166 y: line_y,
167 width: line_width,
168 height: bounds.height,
169 }
170 };
171
172 if style.snap {
173 let unit = 1.0 / renderer.scale_factor().unwrap_or(1.0);
174
175 bounds.width = bounds.width.max(unit);
176 bounds.height = bounds.height.max(unit);
177 }
178
179 renderer.fill_quad(
180 renderer::Quad {
181 bounds,
182 border: border::rounded(style.radius),
183 snap: style.snap,
184 ..renderer::Quad::default()
185 },
186 style.color,
187 );
188 }
189
190 fn operate(
191 &mut self,
192 _tree: &mut Tree,
193 layout: Layout<'_>,
194 _renderer: &Renderer,
195 operation: &mut dyn widget::Operation,
196 ) {
197 operation.accessible(
198 None,
199 layout.bounds(),
200 &Accessible {
201 role: Role::Separator,
202 ..Accessible::default()
203 },
204 );
205 }
206}
207
208impl<'a, Message, Theme, Renderer> From<Rule<'a, Theme>> for Element<'a, Message, Theme, Renderer>
209where
210 Message: 'a,
211 Theme: 'a + Catalog,
212 Renderer: 'a + core::Renderer,
213{
214 fn from(rule: Rule<'a, Theme>) -> Element<'a, Message, Theme, Renderer> {
215 Element::new(rule)
216 }
217}
218
219#[derive(Debug, Clone, Copy, PartialEq)]
221pub struct Style {
222 pub color: Color,
224 pub radius: border::Radius,
226 pub fill_mode: FillMode,
228 pub snap: bool,
230}
231
232#[derive(Debug, Clone, Copy, PartialEq)]
234pub enum FillMode {
235 Full,
237 Percent(f32),
242 Padded(u16),
244 AsymmetricPadding(u16, u16),
247}
248
249impl FillMode {
250 pub fn fill(&self, space: f32) -> (f32, f32) {
258 match *self {
259 FillMode::Full => (0.0, space),
260 FillMode::Percent(percent) => {
261 if percent >= 100.0 {
262 (0.0, space)
263 } else {
264 let percent_width = (space * percent / 100.0).round();
265
266 (((space - percent_width) / 2.0).round(), percent_width)
267 }
268 }
269 FillMode::Padded(padding) => {
270 if padding == 0 {
271 (0.0, space)
272 } else {
273 let padding = padding as f32;
274 let mut line_width = space - (padding * 2.0);
275 if line_width < 0.0 {
276 line_width = 0.0;
277 }
278
279 (padding, line_width)
280 }
281 }
282 FillMode::AsymmetricPadding(first_pad, second_pad) => {
283 let first_pad = first_pad as f32;
284 let second_pad = second_pad as f32;
285 let mut line_width = space - first_pad - second_pad;
286 if line_width < 0.0 {
287 line_width = 0.0;
288 }
289
290 (first_pad, line_width)
291 }
292 }
293 }
294}
295
296pub trait Catalog: Sized {
298 type Class<'a>;
300
301 fn default<'a>() -> Self::Class<'a>;
303
304 fn style(&self, class: &Self::Class<'_>) -> Style;
306}
307
308pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> Style + 'a>;
312
313impl Catalog for Theme {
314 type Class<'a> = StyleFn<'a, Self>;
315
316 fn default<'a>() -> Self::Class<'a> {
317 Box::new(default)
318 }
319
320 fn style(&self, class: &Self::Class<'_>) -> Style {
321 class(self)
322 }
323}
324
325pub fn default(theme: &Theme) -> Style {
327 let palette = theme.palette();
328
329 Style {
330 color: palette.background.strong.color,
331 radius: 0.0.into(),
332 fill_mode: FillMode::Full,
333 snap: true,
334 }
335}
336
337pub fn weak(theme: &Theme) -> Style {
339 let palette = theme.palette();
340
341 Style {
342 color: palette.background.weak.color,
343 radius: 0.0.into(),
344 fill_mode: FillMode::Full,
345 snap: true,
346 }
347}