1mod button;
2pub use button::*;
3
4mod stackpanel;
5pub use stackpanel::*;
6
7mod textbox;
8pub use textbox::*;
9
10mod label;
11pub use label::*;
12
13mod scrollviewer;
14pub use scrollviewer::*;
15
16mod checkbox;
17pub use checkbox::*;
18
19mod combobox;
20pub use combobox::*;
21
22mod tabview;
23pub use tabview::*;
24
25mod numeric;
26pub use numeric::*;
27
28mod datagrid;
29pub use datagrid::*;
30
31mod dockpanel;
32pub use dockpanel::*;
33
34mod textblock;
35pub use textblock::*;
36
37use arrayvec::ArrayVec;
38use vector2d::Vector2D;
39
40use crate::{common::*, window::*};
41use std::num::NonZero;
42
43pub trait Widget: WindowLayout + Default {
45 fn set_alignment(&mut self, horizontal: HorizontalAlignment, vertical: VerticalAlignment);
46
47 fn set_visibility(&mut self, visibility: bool);
48
49 fn set_width(&mut self, width: Size);
50
51 fn set_height(&mut self, height: Size);
52
53 fn set_width_constraint(&mut self, width: SizeConstraint);
54
55 fn set_height_constraint(&mut self, height: SizeConstraint);
56
57 fn set_margin(&mut self, margin: Thickness);
58
59 fn set_border(&mut self, border: BorderStyle);
60
61 fn set_enabled_state(&mut self, is_enabled: bool);
63}
64
65pub trait WidgetColors: Widget {
66 fn set_disabled_color(&mut self, color: Option<Color>);
67
68 fn set_base_fg_color(&mut self, color: Option<Color>);
69
70 fn set_base_bg_color(&mut self, color: Option<Color>);
71}
72
73
74pub trait ItemCollection<T>: Widget {
76 type Index: Copy;
78
79 fn add_item(&mut self, item: T);
81
82 fn remove_item(&mut self, item: &T);
84
85 fn remove_at(&mut self, index: Self::Index) -> T;
88
89 fn get_item(&mut self, index: Self::Index) -> Option<&T>;
91
92 fn clear_items(&mut self);
94
95 fn items(&self) -> Self::Index;
97}
98
99#[derive(Default)]
100pub struct ColorBase {
101 pub disabled: Option<Color>,
102 pub base_bg: Option<Color>,
103 pub base_fg: Option<Color>,
104}
105
106impl ColorBase {}
107
108pub struct WidgetBase {
110 pub colors: ColorBase,
111 pub visibility: bool,
112 pub size: Vector2D<Size>,
113 pub constraints: Vector2D<SizeConstraint>,
114 pub margin: Thickness,
115 pub border: BorderStyle,
116 pub alignment: (HorizontalAlignment, VerticalAlignment),
117 pub enabled: bool,
118 pub uid: WindowUID,
119}
120
121impl Default for WidgetBase {
122 fn default() -> Self {
123 Self {
124 colors: ColorBase::default(),
125 visibility: true,
126 enabled: true,
127 uid: WindowUID::new(),
128 size: Vector2D::new(Size::default(), Size::default()),
129 constraints: Default::default(),
130 margin: Thickness::default(),
131 border: BorderStyle::default(),
132 alignment: (HorizontalAlignment::default(), VerticalAlignment::default()),
133 }
134 }
135}
136
137impl WidgetBase {
138 pub const fn state_color(&self, is_enabled: bool) -> Option<Color> {
140 match is_enabled {
141 true => None,
142 false => self.colors.disabled,
143 }
144 }
145
146 pub fn desired_size(&self, available_size: TPoint) -> TPoint {
147 Vector2D::new(
148 Self::get_size(available_size.x, self.constraints.x, self.size.x),
149 Self::get_size(available_size.y, self.constraints.y, self.size.y),
150 )
151 }
152
153 fn get_size(available_size: TSize, constraint: SizeConstraint, size: Size) -> TSize {
154 if available_size >= constraint.min.get() as TSize {
155 match size {
156 Size::Relative(Percent::DEFAULT) => constraint.max.get_size(available_size),
157 v => v.get_size(available_size),
158 }
159 }
160 else {
161 TSize::MIN
162 }
163 }
164}
165
166
167#[derive(Debug, Clone, Copy, PartialEq)]
168pub enum Size {
169 Fixed(NonZero<TSize>),
170 Relative(Percent),
171}
172
173impl Default for Size {
174 fn default() -> Self {
175 Self::Relative(Percent::default())
176 }
177}
178
179impl Size {
180 pub fn get_size(&self, relative_to: TSize) -> TSize {
184 match *self {
185 Size::Fixed(u) => {
186 let size = u.get();
187 if relative_to >= size {
188 size
189 }
190 else {
191 relative_to
192 }
193 }
194 Size::Relative(p) => p.multiply(relative_to),
195 }
196 }
197}
198
199impl From<Percent> for Size {
200 fn from(value: Percent) -> Self {
201 Size::Relative(value)
202 }
203}
204
205
206#[derive(PartialEq, Debug, Copy, Clone)]
209pub struct SizeConstraint {
210 pub min: NonZero<TSize>,
211 pub max: Size,
212}
213
214impl Default for SizeConstraint {
215 fn default() -> Self {
216 Self {
217 min: NonZero::<TSize>::MIN,
218 max: Size::Relative(Percent::default()),
219 }
220 }
221}
222
223pub struct DisplayValue<T: PartialEq> {
224 value: T,
225 display: String,
226}
227
228impl<T: PartialEq> Default for DisplayValue<T>
229where T: Default
230{
231 fn default() -> Self {
232 Self {
233 value: T::default(),
234 display: String::default(),
235 }
236 }
237}
238
239impl<T: PartialEq> DisplayValue<T> {
240 pub fn new<S: ToString>(value: T, display: S) -> Self {
241 Self {
242 value,
243 display: display.to_string(),
244 }
245 }
246
247 pub fn from(value: T, display: String) -> Self {
248 Self { value, display }
249 }
250
251 pub fn value(&self) -> &T {
253 &self.value
254 }
255
256 pub fn display(&self) -> &str {
258 &self.display
259 }
260}
261
262#[repr(usize)]
263#[derive(Debug, Clone, Copy, PartialEq, Default)]
264pub enum Radix {
265 Binary = 2,
266 Octal = 8,
267 #[default]
268 Decimal = 10,
269 Hexadecimal = 16,
270}
271
272impl Radix {
273 pub const LITERALS: [Grapheme; 16] = [
274 Grapheme::new_unchecked("0", GlyphWidth::Half),
275 Grapheme::new_unchecked("1", GlyphWidth::Half),
276 Grapheme::new_unchecked("2", GlyphWidth::Half),
277 Grapheme::new_unchecked("3", GlyphWidth::Half),
278 Grapheme::new_unchecked("4", GlyphWidth::Half),
279 Grapheme::new_unchecked("5", GlyphWidth::Half),
280 Grapheme::new_unchecked("6", GlyphWidth::Half),
281 Grapheme::new_unchecked("7", GlyphWidth::Half),
282 Grapheme::new_unchecked("8", GlyphWidth::Half),
283 Grapheme::new_unchecked("9", GlyphWidth::Half),
284 Grapheme::new_unchecked("A", GlyphWidth::Half),
285 Grapheme::new_unchecked("B", GlyphWidth::Half),
286 Grapheme::new_unchecked("C", GlyphWidth::Half),
287 Grapheme::new_unchecked("D", GlyphWidth::Half),
288 Grapheme::new_unchecked("E", GlyphWidth::Half),
289 Grapheme::new_unchecked("F", GlyphWidth::Half),
290 ];
291 pub const NEGATIVE_INDICATOR: Grapheme = Grapheme::new_unchecked("-", GlyphWidth::Half);
292 pub const PREFIX_BINARY: [Grapheme; 2] = [
293 Grapheme::new_unchecked("0", GlyphWidth::Half),
294 Grapheme::new_unchecked("b", GlyphWidth::Half),
295 ];
296 pub const PREFIX_OCTAL: [Grapheme; 2] = [
297 Grapheme::new_unchecked("0", GlyphWidth::Half),
298 Grapheme::new_unchecked("o", GlyphWidth::Half),
299 ];
300 pub const PREFIX_HEX: [Grapheme; 2] = [
301 Grapheme::new_unchecked("0", GlyphWidth::Half),
302 Grapheme::new_unchecked("x", GlyphWidth::Half),
303 ];
304
305 pub fn iter_float(&self, number: f32) -> RenderFloatIterator {
306 RenderFloatIterator::new(*self, number)
307 }
308
309 pub fn iter_integer(&self, number: i32) -> RenderIntegerIterator {
310 RenderIntegerIterator::new(*self, number)
311 }
312
313 pub fn first_literal(&self, number: usize) -> Grapheme {
314 Self::LITERALS[number % *self as usize]
315 }
316
317 pub const fn prefix_glyphs(&self) -> &[Grapheme] {
318 match *self {
319 Self::Binary => &Self::PREFIX_BINARY,
320 Self::Octal => &Self::PREFIX_OCTAL,
321 Self::Decimal => &[],
322 Self::Hexadecimal => &Self::PREFIX_HEX,
323 }
324 }
325}
326
327pub struct RenderFloatIterator {
328 dot: bool,
329 fract: f32,
330 nums: ArrayVec<u8, { f32::MANTISSA_DIGITS as usize }>,
331 f: f32,
332 r: Radix,
333 cnt: u8,
334}
335
336impl RenderFloatIterator {
337 const F_NAN: [Grapheme; 3] = [
338 Grapheme::new_unchecked("N", GlyphWidth::Half),
339 Grapheme::new_unchecked("a", GlyphWidth::Half),
340 Grapheme::new_unchecked("N", GlyphWidth::Half),
341 ];
342 const F_INF: Grapheme = Grapheme::new_unchecked("∞", GlyphWidth::Half);
343 const F_DOT: Grapheme = Grapheme::new_unchecked(".", GlyphWidth::Half);
344
345 fn new(r: Radix, number: f32) -> Self {
346 let mut n = number.abs().trunc() as i32;
347 let mut vec = ArrayVec::new();
348 while n != 0 {
349 vec.push((n % r as i32) as u8);
350 n /= r as i32;
351 }
352 Self {
353 dot: false,
354 fract: number.fract(),
355 nums: vec,
356 f: number,
357 r,
358 cnt: 0,
359 }
360 }
361}
362
363impl Iterator for RenderFloatIterator {
364 type Item = Grapheme;
365
366 fn next(&mut self) -> Option<Self::Item> {
367 if self.f.is_nan() {
368 if self.cnt as usize > Self::F_NAN.len() - 1 {
369 None
370 }
371 else {
372 let v = Self::F_NAN[self.cnt as usize];
373 self.cnt += 1;
374 Some(v)
375 }
376 }
377 else if self.f.is_sign_negative() && self.f != 0.0 {
378 self.f = self.f.abs();
379 Some(Radix::NEGATIVE_INDICATOR)
380 }
381 else if self.f.is_infinite() {
382 if self.cnt != 0 {
383 None
384 }
385 else {
386 self.cnt = 1;
387 Some(Self::F_INF)
388 }
389 }
390 else if self.r != Radix::Decimal && (self.cnt as usize) < self.r.prefix_glyphs().len() {
391 let v = self.r.prefix_glyphs()[self.cnt as usize];
392 self.cnt += 1;
393 Some(v)
394 }
395 else {
396 if !self.nums.is_empty() {
397 self.nums.pop().map(|v| Radix::LITERALS[v as usize])
398 }
399 else if !self.dot {
400 self.dot = true;
401 Some(Self::F_DOT)
402 }
403 else if self.fract != 0.0 {
404 let rem = self.fract * self.r as usize as f32;
405 let v = Radix::LITERALS[rem.trunc() as usize];
406 self.fract = rem.fract();
407 Some(v)
408 }
409 else {
410 None
411 }
412 }
413 }
414}
415
416pub struct RenderIntegerIterator {
417 i: i32,
418 nums: ArrayVec<u8, { i32::BITS as usize }>,
419 r: Radix,
420 cnt: u8,
421}
422
423impl RenderIntegerIterator {
424 fn new(r: Radix, number: i32) -> Self {
425 let mut n = number.abs();
426 let mut vec = ArrayVec::new();
427 while n != 0 {
428 vec.push((n % r as i32) as u8);
429 n /= r as i32;
430 }
431 Self {
432 i: number,
433 r,
434 cnt: 0,
435 nums: vec,
436 }
437 }
438}
439
440impl Iterator for RenderIntegerIterator {
441 type Item = Grapheme;
442
443 fn next(&mut self) -> Option<Self::Item> {
444 if self.i.is_negative() {
445 self.i = self.i.abs();
446 Some(Radix::NEGATIVE_INDICATOR)
447 }
448 else if self.r != Radix::Decimal && (self.cnt as usize) < self.r.prefix_glyphs().len() {
449 let v = self.r.prefix_glyphs()[self.cnt as usize];
450 self.cnt += 1;
451 Some(v)
452 }
453 else {
454 self.nums.pop().map(|v| Radix::LITERALS[v as usize])
455 }
456 }
457}
458
459#[cfg(test)]
460mod tests {
461 use super::*;
462
463 #[test]
464 fn size_fixed() {
465 let value = NonZero::new(20).unwrap();
466 let fixed = Size::Fixed(value);
467
468 assert_eq!(fixed.get_size(25), value.get());
469 assert_eq!(fixed.get_size(value.get()), value.get());
470 assert_eq!(fixed.get_size(12), 12);
471 }
472
473 #[test]
474 fn size_relative() {
475 let value = 25;
476 let rel = Size::Relative(Percent::from_int(25));
477
478 assert_eq!(rel.get_size(100), value);
479 assert_eq!(rel.get_size(200), value * 2);
480 assert_eq!(rel.get_size(50), value / 2);
481 assert_eq!(rel.get_size(0), 0);
482 }
483}