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 pub fn width(mut self, width: impl Into<Length>) -> Self {
85 if self.is_horizontal {
86 self.width = width.into();
87 }
88 self
89 }
90
91 pub fn height(mut self, height: impl Into<Length>) -> Self {
94 if !self.is_horizontal {
95 self.height = height.into();
96 }
97 self
98 }
99
100 #[must_use]
102 pub fn style(mut self, style: impl Fn(&Theme) -> Style + 'a) -> Self
103 where
104 Theme::Class<'a>: From<StyleFn<'a, Theme>>,
105 {
106 self.class = (Box::new(style) as StyleFn<'a, Theme>).into();
107 self
108 }
109
110 #[cfg(feature = "advanced")]
112 #[must_use]
113 pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self {
114 self.class = class.into();
115 self
116 }
117}
118
119impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
120 for Rule<'a, Theme>
121where
122 Renderer: core::Renderer,
123 Theme: Catalog,
124{
125 fn size(&self) -> Size<Length> {
126 Size {
127 width: self.width,
128 height: self.height,
129 }
130 }
131
132 fn layout(
133 &self,
134 _tree: &mut Tree,
135 _renderer: &Renderer,
136 limits: &layout::Limits,
137 ) -> layout::Node {
138 layout::atomic(limits, self.width, self.height)
139 }
140
141 fn draw(
142 &self,
143 _state: &Tree,
144 renderer: &mut Renderer,
145 theme: &Theme,
146 _style: &renderer::Style,
147 layout: Layout<'_>,
148 _cursor: mouse::Cursor,
149 _viewport: &Rectangle,
150 ) {
151 let bounds = layout.bounds();
152 let style = theme.style(&self.class);
153
154 let bounds = if self.is_horizontal {
155 let line_y = (bounds.y + (bounds.height / 2.0)
156 - (style.width as f32 / 2.0))
157 .round();
158
159 let (offset, line_width) = style.fill_mode.fill(bounds.width);
160 let line_x = bounds.x + offset;
161
162 Rectangle {
163 x: line_x,
164 y: line_y,
165 width: line_width,
166 height: style.width as f32,
167 }
168 } else {
169 let line_x = (bounds.x + (bounds.width / 2.0)
170 - (style.width as f32 / 2.0))
171 .round();
172
173 let (offset, line_height) = style.fill_mode.fill(bounds.height);
174 let line_y = bounds.y + offset;
175
176 Rectangle {
177 x: line_x,
178 y: line_y,
179 width: style.width as f32,
180 height: line_height,
181 }
182 };
183
184 renderer.fill_quad(
185 renderer::Quad {
186 bounds,
187 border: border::rounded(style.radius),
188 ..renderer::Quad::default()
189 },
190 style.color,
191 );
192 }
193}
194
195impl<'a, Message, Theme, Renderer> From<Rule<'a, Theme>>
196 for Element<'a, Message, Theme, Renderer>
197where
198 Message: 'a,
199 Theme: 'a + Catalog,
200 Renderer: 'a + core::Renderer,
201{
202 fn from(rule: Rule<'a, Theme>) -> Element<'a, Message, Theme, Renderer> {
203 Element::new(rule)
204 }
205}
206
207#[derive(Debug, Clone, Copy, PartialEq)]
209pub struct Style {
210 pub color: Color,
212 pub width: u16,
214 pub radius: border::Radius,
216 pub fill_mode: FillMode,
218}
219
220#[derive(Debug, Clone, Copy, PartialEq)]
222pub enum FillMode {
223 Full,
225 Percent(f32),
230 Padded(u16),
232 AsymmetricPadding(u16, u16),
235}
236
237impl FillMode {
238 pub fn fill(&self, space: f32) -> (f32, f32) {
246 match *self {
247 FillMode::Full => (0.0, space),
248 FillMode::Percent(percent) => {
249 if percent >= 100.0 {
250 (0.0, space)
251 } else {
252 let percent_width = (space * percent / 100.0).round();
253
254 (((space - percent_width) / 2.0).round(), percent_width)
255 }
256 }
257 FillMode::Padded(padding) => {
258 if padding == 0 {
259 (0.0, space)
260 } else {
261 let padding = padding as f32;
262 let mut line_width = space - (padding * 2.0);
263 if line_width < 0.0 {
264 line_width = 0.0;
265 }
266
267 (padding, line_width)
268 }
269 }
270 FillMode::AsymmetricPadding(first_pad, second_pad) => {
271 let first_pad = first_pad as f32;
272 let second_pad = second_pad as f32;
273 let mut line_width = space - first_pad - second_pad;
274 if line_width < 0.0 {
275 line_width = 0.0;
276 }
277
278 (first_pad, line_width)
279 }
280 }
281 }
282}
283
284pub trait Catalog: Sized {
286 type Class<'a>;
288
289 fn default<'a>() -> Self::Class<'a>;
291
292 fn style(&self, class: &Self::Class<'_>) -> Style;
294}
295
296pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> Style + 'a>;
300
301impl Catalog for Theme {
302 type Class<'a> = StyleFn<'a, Self>;
303
304 fn default<'a>() -> Self::Class<'a> {
305 Box::new(default)
306 }
307
308 fn style(&self, class: &Self::Class<'_>) -> Style {
309 class(self)
310 }
311}
312
313pub fn default(theme: &Theme) -> Style {
315 let palette = theme.extended_palette();
316
317 Style {
318 color: palette.background.strong.color,
319 width: 1,
320 radius: 0.0.into(),
321 fill_mode: FillMode::Full,
322 }
323}