1use embedded_graphics_core::{draw_target::DrawTarget, pixelcolor::Rgb565};
2
3use crate::{
4 geometry::{EdgeInsets, Rect},
5 render::{Compositor, RenderCtx, TextAlign, TextStyle},
6 style::{Border, Style},
7};
8
9#[derive(Clone, Copy, Debug, PartialEq, Eq)]
10pub struct Block<'a> {
11 pub title: Option<&'a str>,
12 pub title_align: TextAlign,
13 pub border: Border,
14 pub style: Style,
15 pub padding: EdgeInsets,
16}
17
18impl<'a> Block<'a> {
19 pub const fn new() -> Self {
20 Self {
21 title: None,
22 title_align: TextAlign::Left,
23 border: Border::none(),
24 style: Style::new(),
25 padding: EdgeInsets::all(0),
26 }
27 }
28
29 pub const fn styled(style: Style) -> Self {
30 Self {
31 title: None,
32 title_align: TextAlign::Left,
33 border: style.border,
34 padding: style.padding,
35 style,
36 }
37 }
38
39 pub const fn title(mut self, title: &'a str) -> Self {
40 self.title = Some(title);
41 self
42 }
43
44 pub const fn title_align(mut self, align: TextAlign) -> Self {
45 self.title_align = align;
46 self
47 }
48
49 pub const fn border(mut self, border: Border) -> Self {
50 self.border = border;
51 self
52 }
53
54 pub const fn padding(mut self, padding: EdgeInsets) -> Self {
55 self.padding = padding;
56 self
57 }
58
59 pub fn inner(self, rect: impl Into<Rect>) -> Rect {
60 let rect = rect.into();
61 let border = self.border.width as i16;
62 rect.inset(EdgeInsets {
63 left: self.padding.left.saturating_add(border),
64 right: self.padding.right.saturating_add(border),
65 top: self.padding.top.saturating_add(border),
66 bottom: self.padding.bottom.saturating_add(border),
67 })
68 }
69
70 pub fn title_area(self, rect: impl Into<Rect>) -> Option<Rect> {
71 let rect = rect.into();
72 self.title.map(|_| {
73 Rect::new(
74 rect.x + self.border.width as i32 + self.padding.left.max(0) as i32,
75 rect.y,
76 rect.w
77 .saturating_sub(self.border.width as u32 * 2)
78 .saturating_sub(self.padding.left.max(0) as u32)
79 .saturating_sub(self.padding.right.max(0) as u32),
80 self.style.font.line_height() + 1,
81 )
82 })
83 }
84
85 pub fn content_area(self, rect: impl Into<Rect>) -> Rect {
86 let rect = rect.into();
87 let inner = self.inner(rect);
88 if self.title.is_none() {
89 return inner;
90 }
91
92 let title_h = self.style.font.line_height() + 3;
93 Rect::new(
94 inner.x,
95 inner.y + title_h as i32,
96 inner.w,
97 inner.h.saturating_sub(title_h),
98 )
99 }
100
101 pub fn render<D, C>(
102 self,
103 rect: impl Into<Rect>,
104 ctx: &mut RenderCtx<'_, D, C>,
105 ) -> Result<(), D::Error>
106 where
107 D: DrawTarget<Color = Rgb565>,
108 C: Compositor<D>,
109 {
110 let rect = rect.into();
111 if let Some(shadow) = self.style.shadow {
112 let spread = ctx.shadow_spread_for(shadow.spread);
113 let mut i = 0u8;
114 while i < spread {
115 let grow = i as i32;
116 let shadow_rect = Rect::new(
117 rect.x + shadow.offset_x as i32 - grow,
118 rect.y + shadow.offset_y as i32 - grow,
119 rect.w.saturating_add((i as u32) * 2),
120 rect.h.saturating_add((i as u32) * 2),
121 );
122 let fade = spread as u16;
123 let opacity = ((shadow.opacity as u16) * (fade - i as u16) / fade) as u8;
124 let radius = self.style.corner_radius.saturating_add(i);
125 ctx.fill_rounded_rect_alpha(shadow_rect, radius, shadow.color, opacity)?;
126 i += 1;
127 }
128 }
129
130 if let Some(gradient) = self.style.gradient {
131 ctx.fill_rounded_rect_gradient_alpha(
132 rect,
133 self.style.corner_radius,
134 gradient,
135 self.style.opacity,
136 )?;
137 } else if let Some(bg) = self.style.background {
138 ctx.fill_rounded_rect_alpha(rect, self.style.corner_radius, bg, self.style.opacity)?;
139 }
140 ctx.stroke_rounded_rect_alpha(
141 rect,
142 self.style.corner_radius,
143 self.border,
144 self.style.opacity,
145 )?;
146
147 if let Some(title) = self.title {
148 let title_rect = self.title_area(rect).unwrap_or(Rect::empty());
149 ctx.draw_text_in(
150 title_rect,
151 title,
152 TextStyle::new(self.style.accent)
153 .with_font(self.style.font)
154 .with_align(self.title_align)
155 .with_opacity(self.style.opacity),
156 )?;
157 }
158
159 Ok(())
160 }
161}
162
163impl Default for Block<'_> {
164 fn default() -> Self {
165 Self::new()
166 }
167}