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)]
61pub enum FocusStyle {
62 Outline,
64 Fill,
66 None,
68}
69
70impl Default for FocusStyle {
71 fn default() -> Self {
72 FocusStyle::Outline
73 }
74}
75
76#[derive(Debug, Clone, Copy, PartialEq)]
80pub enum Units {
81 Auto,
82 Px(f32),
83 Percent(f32),
85}
86
87impl Default for Units {
88 fn default() -> Self {
89 Units::Auto
90 }
91}
92
93impl From<f32> for Units {
94 fn from(v: f32) -> Self {
97 if v <= 0.0 {
98 Units::Auto
99 } else {
100 Units::Px(v)
101 }
102 }
103}
104
105impl Units {
106 pub fn resolve(self, avail: f32) -> Option<f32> {
109 match self {
110 Units::Auto => None,
111 Units::Px(v) => Some(v),
112 Units::Percent(p) => Some(avail * p / 100.0),
113 }
114 }
115
116 pub fn is_auto(self) -> bool {
117 matches!(self, Units::Auto)
118 }
119}
120
121#[derive(Debug, Clone)]
123pub struct Style {
124 pub w: Units, pub h: Units, pub min_w: f32,
127 pub min_h: f32,
128 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,
136 pub font_scale: f32,
137 pub text_shadow: bool, pub focus_style: FocusStyle,
139 pub focus_color: u32, pub no_wrap: bool,
145}
146
147impl Default for Style {
148 fn default() -> Self {
149 Self {
150 w: Units::Auto,
151 h: Units::Auto,
152 min_w: 4.0,
153 min_h: 4.0,
154 flex: 0.0,
155 dock: Dock::None,
156 gap: 2.0,
157 pad: [0.0; 4],
158 margin: [0.0; 4],
159 bg: 0,
160 color: 0xFF_CCCCAA,
161 align: Align::Start,
162 font_scale: 1.0,
163 text_shadow: true,
164 focus_style: FocusStyle::default(),
165 focus_color: 0,
166 no_wrap: false,
167 }
168 }
169}
170
171impl Widget {
174 fn new(kind: WidgetKind) -> Self {
175 let flex_dir = if let WidgetKind::Panel(dir) = &kind {
176 *dir
177 } else {
178 FlexDir::Column
179 };
180 Self {
181 kind,
182 id: None,
183 style: Style::default(),
184 flex_dir,
185 children: Vec::new(),
186 on_click: None,
187 enabled: true,
188 focused: false,
189 }
190 }
191
192 pub fn id(mut self, id: impl Into<String>) -> Self {
193 self.id = Some(id.into());
194 self
195 }
196 pub fn on_click(mut self, ev: impl Into<String>) -> Self {
197 self.on_click = Some(ev.into());
198 self
199 }
200 pub fn child(mut self, w: Widget) -> Self {
201 self.children.push(w);
202 self
203 }
204
205 pub fn w(mut self, w: impl Into<Units>) -> Self {
208 self.style.w = w.into();
209 self
210 }
211 pub fn h(mut self, h: impl Into<Units>) -> Self {
212 self.style.h = h.into();
213 self
214 }
215 pub fn min_w(mut self, v: f32) -> Self {
216 self.style.min_w = v;
217 self
218 }
219 pub fn min_h(mut self, v: f32) -> Self {
220 self.style.min_h = v;
221 self
222 }
223 pub fn flex(mut self, v: f32) -> Self {
224 self.style.flex = v;
225 self
226 }
227 pub fn flex_dir(mut self, v: FlexDir) -> Self {
228 self.flex_dir = v;
229 self
230 }
231 pub fn gap(mut self, v: f32) -> Self {
232 self.style.gap = v;
233 self
234 }
235 pub fn bg(mut self, v: u32) -> Self {
236 self.style.bg = v;
237 self
238 }
239 pub fn color(mut self, v: u32) -> Self {
240 self.style.color = v;
241 self
242 }
243 pub fn align(mut self, v: Align) -> Self {
244 self.style.align = v;
245 self
246 }
247 pub fn font_scale(mut self, v: f32) -> Self {
248 self.style.font_scale = v;
249 self
250 }
251 pub fn shadow(mut self, v: bool) -> Self {
252 self.style.text_shadow = v;
253 self
254 }
255 pub fn no_wrap(mut self) -> Self {
256 self.style.no_wrap = true;
257 self
258 }
259 pub fn focus_style(mut self, v: FocusStyle) -> Self {
260 self.style.focus_style = v;
261 self
262 }
263 pub fn focus_color(mut self, v: u32) -> Self {
264 self.style.focus_color = v;
265 self
266 }
267 pub fn dock(mut self, v: Dock) -> Self {
268 self.style.dock = v;
269 self
270 }
271 pub fn enabled(mut self, v: bool) -> Self {
272 self.enabled = v;
273 self
274 }
275 pub fn focused(mut self, v: bool) -> Self {
276 self.focused = v;
277 self
278 }
279 pub fn padding(mut self, top: f32, right: f32, bottom: f32, left: f32) -> Self {
280 self.style.pad = [top, right, bottom, left];
281 self
282 }
283 pub fn margin(mut self, top: f32, right: f32, bottom: f32, left: f32) -> Self {
284 self.style.margin = [top, right, bottom, left];
285 self
286 }
287}
288
289pub fn panel(dir: FlexDir) -> Widget {
292 Widget::new(WidgetKind::Panel(dir))
293}
294pub fn label(text: impl Into<String>) -> Widget {
295 Widget::new(WidgetKind::Label(text.into()))
296}
297pub fn button(text: impl Into<String>) -> Widget {
298 Widget::new(WidgetKind::Button(text.into()))
299}
300pub fn item_slot(item_id: impl Into<String>) -> Widget {
301 Widget::new(WidgetKind::ItemSlot(item_id.into()))
302}
303pub fn inv_slot(item_id: impl Into<String>, count: u32) -> Widget {
307 Widget::new(WidgetKind::InvSlot {
308 item_id: item_id.into(),
309 count,
310 })
311 .w(18.0)
312 .h(18.0)
313}
314pub fn mc_image(id: impl Into<String>, img_w: f32, img_h: f32) -> Widget {
315 Widget::new(WidgetKind::McImage {
316 id: id.into(),
317 img_w,
318 img_h,
319 })
320 .w(img_w)
321 .h(img_h)
322}
323pub fn spacer() -> Widget {
324 Widget::new(WidgetKind::Spacer)
325}