1use denise::Pen;
4use denise::{Radius, Role};
5
6use crate::widget::{PaintCtx, Widget};
7use crate::widgets::describe::{
8 Describe, DynDescribe, Group, Mismatch, Property, PropertyKind, RADII, ROLES, Value,
9 role_from_name,
10};
11
12#[derive(Clone, Copy, Debug)]
18pub struct Panel {
19 pub fill: Option<Role>,
21 pub border: Option<Role>,
23 pub border_width: i32,
25 pub radius: Radius,
27 pub backdrop: bool,
31}
32
33impl Default for Panel {
34 fn default() -> Self {
35 Self {
36 fill: Some(Role::Base200),
37 border: Some(Role::Base300),
38 border_width: 1,
39 radius: Radius::Box,
40 backdrop: false,
41 }
42 }
43}
44
45impl Panel {
46 pub const fn filled(role: Role) -> Self {
48 Self {
49 fill: Some(role),
50 border: None,
51 border_width: 0,
52 radius: Radius::Box,
53 backdrop: false,
54 }
55 }
56
57 pub const fn bare() -> Self {
71 Self {
72 fill: None,
73 border: None,
74 border_width: 0,
75 radius: Radius::Box,
76 backdrop: false,
77 }
78 }
79
80 #[must_use]
93 pub const fn backdrop(mut self) -> Self {
94 self.backdrop = true;
95 self
96 }
97
98 pub const fn with_radius(mut self, radius: Radius) -> Self {
100 self.radius = radius;
101 self
102 }
103
104 pub const fn with_border(mut self, role: Role, width: i32) -> Self {
106 self.border = Some(role);
107 self.border_width = width;
108 self
109 }
110}
111
112impl<M: 'static> Widget<M> for Panel {
113 fn describe(&self) -> Option<&dyn DynDescribe> {
114 Some(self)
115 }
116
117 fn describe_mut(&mut self) -> Option<&mut dyn DynDescribe> {
118 Some(self)
119 }
120 fn accepts_pointer(&self) -> bool {
121 self.backdrop
122 }
123
124 fn preserves_focus(&self) -> bool {
127 self.backdrop
128 }
129
130 fn paint(&self, ctx: &mut PaintCtx<'_>, canvas: &mut Pen<'_>) {
131 let radius = ctx.theme.radius(self.radius);
132 if let Some(role) = self.fill {
133 canvas.fill_rounded_rect(ctx.bounds, radius, ctx.theme.color(role));
134 }
135 if let Some(role) = self.border
136 && self.border_width > 0
137 {
138 canvas.stroke_rounded_rect(
139 ctx.bounds,
140 radius,
141 self.border_width,
142 ctx.theme.color(role),
143 );
144 }
145 }
146}
147
148const NONE: &str = "none";
150
151const fn roles_or_none() -> [&'static str; ROLES.len() + 1] {
159 let mut names = [NONE; ROLES.len() + 1];
160 let mut i = 0;
161 while i < ROLES.len() {
162 names[i] = ROLES[i];
163 i += 1;
164 }
165 names
167}
168
169const ROLES_OR_NONE: &[&str] = &roles_or_none();
171
172fn role_or_none(value: Value) -> Result<Option<Role>, Mismatch> {
174 let name = value.as_name()?;
175 if name == NONE {
176 return Ok(None);
177 }
178 role_from_name(name).map(Some).ok_or(Mismatch::WrongType {
179 expected: PropertyKind::Enum(ROLES_OR_NONE),
180 })
181}
182
183impl Describe for Panel {
184 const KIND: &'static str = "panel";
185 const DOC: &'static str = "A themed rectangle: the background other widgets sit on.";
186 const GROUP: Group = Group::Container;
187 const ICON: &'static denise::icon::Icon = &super::icons::PANEL;
188
189 const PROPERTIES: &'static [Property] = &[
190 Property::new(
191 "fill",
192 PropertyKind::Enum(ROLES_OR_NONE),
193 "Surface colour. `none` leaves what is underneath alone.",
194 ),
195 Property::new(
196 "border",
197 PropertyKind::Enum(ROLES_OR_NONE),
198 "Border colour. `none` for no border.",
199 ),
200 Property::new(
201 "border-width",
202 PropertyKind::Int { min: 0, max: 16 },
203 "Border thickness in pixels, drawn inside the bounds.",
204 )
205 .in_pixels(),
206 Property::new(
207 "radius",
208 PropertyKind::Enum(RADII),
209 "Corner rounding token. The theme decides the pixels.",
210 ),
211 Property::new(
212 "backdrop",
213 PropertyKind::Bool,
214 "This panel absorbs presses rather than letting them fall through, and leaves the focus where it is. What the sheet under an on-screen keyboard is.",
215 ),
216 ];
217
218 fn get(&self, name: &str) -> Option<Value> {
219 Some(match name {
220 "fill" => Value::role(self.fill?),
223 "border" => Value::role(self.border?),
224 "border-width" => Value::Int(self.border_width),
225 "radius" => Value::radius(self.radius),
226 "backdrop" => Value::Bool(self.backdrop),
227 _ => return None,
228 })
229 }
230
231 fn apply(&mut self, name: &str, value: Value) -> Result<(), Mismatch> {
232 match name {
233 "fill" => self.fill = role_or_none(value)?,
234 "border" => self.border = role_or_none(value)?,
235 "border-width" => self.border_width = value.as_int()?.max(0),
238 "radius" => self.radius = value.as_radius()?,
239 "backdrop" => self.backdrop = value.as_bool()?,
240 _ => return Err(Mismatch::Unknown),
241 }
242 Ok(())
243 }
244}