1use crate::layout::{Align, FlexDir};
4
5#[derive(Debug, Clone)]
7pub struct Widget {
8 pub kind: WidgetKind,
9 pub id: Option<String>,
10 pub style: Style,
11 pub flex_dir: FlexDir,
12 pub children: Vec<Widget>,
13 pub on_click: Option<String>,
14 pub enabled: bool,
15 pub focused: bool,
16}
17
18#[derive(Debug, Clone)]
19pub enum WidgetKind {
20 Panel(FlexDir),
22 Label(String),
24 Button(String),
26 ItemSlot(String),
28 InvSlot { item_id: String, count: u32 },
34 McImage { id: String, img_w: f32, img_h: f32 },
36 Spacer,
38}
39
40#[derive(Debug, Clone, Copy, PartialEq, Default)]
49pub enum Dock {
50 #[default]
51 None,
52 Fill,
53 Left,
54 Right,
55 Top,
56 Bottom,
57}
58
59#[derive(Debug, Clone, Copy, PartialEq, Default)]
61pub enum FocusStyle {
62 #[default]
64 Outline,
65 Fill,
67 None,
69}
70
71#[derive(Debug, Clone, Copy, PartialEq, Default)]
75pub enum Units {
76 #[default]
77 Auto,
78 Px(f32),
79 Percent(f32),
81}
82
83impl From<f32> for Units {
84 fn from(v: f32) -> Self {
87 if v <= 0.0 {
88 Units::Auto
89 } else {
90 Units::Px(v)
91 }
92 }
93}
94
95impl Units {
96 pub fn resolve(self, avail: f32) -> Option<f32> {
99 match self {
100 Units::Auto => None,
101 Units::Px(v) => Some(v),
102 Units::Percent(p) => Some(avail * p / 100.0),
103 }
104 }
105
106 pub fn is_auto(self) -> bool {
107 matches!(self, Units::Auto)
108 }
109}
110
111#[derive(Debug, Clone)]
113pub struct Style {
114 pub w: Units, pub h: Units, pub min_w: f32,
117 pub min_h: f32,
118 pub flex: f32, pub dock: Dock, pub gap: f32, pub pad: [f32; 4], pub margin: [f32; 4], pub bg: u32, pub color: u32, pub align: Align,
126 pub font_scale: f32,
127 pub text_shadow: bool, pub focus_style: FocusStyle,
129 pub focus_color: u32, pub no_wrap: bool,
135 pub pos: Option<(f32, f32)>,
141}
142
143impl Default for Style {
144 fn default() -> Self {
145 Self {
146 w: Units::Auto,
147 h: Units::Auto,
148 min_w: 4.0,
149 min_h: 4.0,
150 flex: 0.0,
151 dock: Dock::None,
152 gap: 2.0,
153 pad: [0.0; 4],
154 margin: [0.0; 4],
155 bg: 0,
156 color: 0xFF_CCCCAA,
157 align: Align::Start,
158 font_scale: 1.0,
159 text_shadow: true,
160 focus_style: FocusStyle::default(),
161 focus_color: 0,
162 no_wrap: false,
163 pos: None,
164 }
165 }
166}
167
168impl Widget {
171 fn new(kind: WidgetKind) -> Self {
172 let flex_dir = if let WidgetKind::Panel(dir) = &kind {
173 *dir
174 } else {
175 FlexDir::Column
176 };
177 Self {
178 kind,
179 id: None,
180 style: Style::default(),
181 flex_dir,
182 children: Vec::new(),
183 on_click: None,
184 enabled: true,
185 focused: false,
186 }
187 }
188
189 pub fn id(mut self, id: impl Into<String>) -> Self {
190 self.id = Some(id.into());
191 self
192 }
193 pub fn on_click(mut self, ev: impl Into<String>) -> Self {
194 self.on_click = Some(ev.into());
195 self
196 }
197 pub fn child(mut self, w: Widget) -> Self {
198 self.children.push(w);
199 self
200 }
201
202 pub fn w(mut self, w: impl Into<Units>) -> Self {
205 self.style.w = w.into();
206 self
207 }
208 pub fn h(mut self, h: impl Into<Units>) -> Self {
209 self.style.h = h.into();
210 self
211 }
212 pub fn min_w(mut self, v: f32) -> Self {
213 self.style.min_w = v;
214 self
215 }
216 pub fn min_h(mut self, v: f32) -> Self {
217 self.style.min_h = v;
218 self
219 }
220 pub fn flex(mut self, v: f32) -> Self {
221 self.style.flex = v;
222 self
223 }
224 pub fn flex_dir(mut self, v: FlexDir) -> Self {
225 self.flex_dir = v;
226 self
227 }
228 pub fn gap(mut self, v: f32) -> Self {
229 self.style.gap = v;
230 self
231 }
232 pub fn bg(mut self, v: u32) -> Self {
233 self.style.bg = v;
234 self
235 }
236 pub fn color(mut self, v: u32) -> Self {
237 self.style.color = v;
238 self
239 }
240 pub fn align(mut self, v: Align) -> Self {
241 self.style.align = v;
242 self
243 }
244 pub fn font_scale(mut self, v: f32) -> Self {
245 self.style.font_scale = v;
246 self
247 }
248 pub fn shadow(mut self, v: bool) -> Self {
249 self.style.text_shadow = v;
250 self
251 }
252 pub fn no_wrap(mut self) -> Self {
253 self.style.no_wrap = true;
254 self
255 }
256 pub fn focus_style(mut self, v: FocusStyle) -> Self {
257 self.style.focus_style = v;
258 self
259 }
260 pub fn focus_color(mut self, v: u32) -> Self {
261 self.style.focus_color = v;
262 self
263 }
264 pub fn dock(mut self, v: Dock) -> Self {
265 self.style.dock = v;
266 self
267 }
268 pub fn enabled(mut self, v: bool) -> Self {
269 self.enabled = v;
270 self
271 }
272 pub fn focused(mut self, v: bool) -> Self {
273 self.focused = v;
274 self
275 }
276 pub fn at(mut self, x: f32, y: f32) -> Self {
278 self.style.pos = Some((x, y));
279 self
280 }
281 pub fn children(mut self, children: impl IntoIterator<Item = Widget>) -> Self {
283 self.children.extend(children);
284 self
285 }
286
287 pub fn find_mut(&mut self, id: &str) -> Option<&mut Widget> {
291 if self.id.as_deref() == Some(id) {
292 return Some(self);
293 }
294 self.children.iter_mut().find_map(|c| c.find_mut(id))
295 }
296
297 pub fn set_text(&mut self, text: impl Into<String>) -> bool {
299 match &mut self.kind {
300 WidgetKind::Label(t) | WidgetKind::Button(t) => {
301 *t = text.into();
302 true
303 }
304 _ => false,
305 }
306 }
307
308 pub fn padding(mut self, top: f32, right: f32, bottom: f32, left: f32) -> Self {
309 self.style.pad = [top, right, bottom, left];
310 self
311 }
312 pub fn margin(mut self, top: f32, right: f32, bottom: f32, left: f32) -> Self {
313 self.style.margin = [top, right, bottom, left];
314 self
315 }
316}
317
318pub fn panel(dir: FlexDir) -> Widget {
321 Widget::new(WidgetKind::Panel(dir))
322}
323pub fn label(text: impl Into<String>) -> Widget {
324 Widget::new(WidgetKind::Label(text.into()))
325}
326pub fn button(text: impl Into<String>) -> Widget {
327 Widget::new(WidgetKind::Button(text.into()))
328}
329pub fn item_slot(item_id: impl Into<String>) -> Widget {
330 Widget::new(WidgetKind::ItemSlot(item_id.into()))
331}
332pub fn inv_slot(item_id: impl Into<String>, count: u32) -> Widget {
336 Widget::new(WidgetKind::InvSlot {
337 item_id: item_id.into(),
338 count,
339 })
340 .w(18.0)
341 .h(18.0)
342}
343pub fn mc_image(id: impl Into<String>, img_w: f32, img_h: f32) -> Widget {
344 Widget::new(WidgetKind::McImage {
345 id: id.into(),
346 img_w,
347 img_h,
348 })
349 .w(img_w)
350 .h(img_h)
351}
352pub fn spacer() -> Widget {
353 Widget::new(WidgetKind::Spacer)
354}