1use std::fmt::Display;
7use std::hash::{Hash, Hasher};
8
9use yog_api::ui::{StateFlags, Units, Widget};
10
11#[derive(Debug, Clone, Default)]
13pub struct Slot<T>(Option<T>);
14
15impl<T: PartialEq> Slot<T> {
16 pub const fn new() -> Self {
17 Slot(None)
18 }
19 pub fn set(&mut self, v: T) -> bool {
22 if self.0.as_ref() == Some(&v) {
23 return false;
24 }
25 self.0 = Some(v);
26 true
27 }
28 pub fn get(&self) -> &T {
30 self.0.as_ref().expect("yog-kit: binding read before it was set")
31 }
32 pub fn reset(&mut self) {
34 self.0 = None;
35 }
36}
37
38#[diagnostic::on_unimplemented(
40 message = "`{Self}` can't be shown as text in a .yui binding",
41 note = "bind a value that implements Display, or call a view-model method returning String"
42)]
43pub trait BindText {
44 fn bind_text(&self) -> String;
45}
46
47impl<T: Display + ?Sized> BindText for T {
48 fn bind_text(&self) -> String {
49 self.to_string()
50 }
51}
52
53#[diagnostic::on_unimplemented(
55 message = "`{Self}` can't be used as a colour in a .yui binding",
56 note = "colours are u32 values 0xAARRGGBB (a .yres colour, or a view-model method returning u32)"
57)]
58pub trait BindColor {
59 fn bind_color(&self) -> u32;
60}
61
62impl BindColor for u32 {
63 fn bind_color(&self) -> u32 {
64 *self
65 }
66}
67
68impl<T: BindColor + ?Sized> BindColor for &T {
69 fn bind_color(&self) -> u32 {
70 (**self).bind_color()
71 }
72}
73
74pub fn text<T: BindText + ?Sized>(v: &T) -> String {
76 v.bind_text()
77}
78
79pub fn color<T: BindColor>(v: T) -> u32 {
81 v.bind_color()
82}
83
84pub fn flag(v: impl Into<bool>) -> bool {
86 v.into()
87}
88
89pub fn units(v: impl Into<Units>) -> Units {
91 v.into()
92}
93
94pub fn number(v: impl Into<f64>) -> f32 {
96 v.into() as f32
97}
98
99pub fn count<T: TryInto<u32>>(v: T) -> u32 {
101 v.try_into().unwrap_or(0)
102}
103
104pub fn key<K: Hash + ?Sized>(k: &K) -> u64 {
106 let mut h = std::collections::hash_map::DefaultHasher::new();
107 k.hash(&mut h);
108 h.finish()
109}
110
111pub fn is_selected<S: PartialEq<K>, K: ?Sized>(selected: &Option<S>, key: &K) -> bool {
113 selected.as_ref().is_some_and(|s| s == key)
114}
115
116pub fn set_placeholder(w: &mut Widget, text: &str) -> bool {
118 match &mut w.kind {
119 yog_api::ui::widget::WidgetKind::TextBox(tb) => {
120 tb.placeholder = text.to_owned();
121 true
122 }
123 _ => false,
124 }
125}
126
127pub fn set_item(w: &mut Widget, item: &str) -> bool {
129 use yog_api::ui::widget::WidgetKind;
130 match &mut w.kind {
131 WidgetKind::ItemSlot(id) | WidgetKind::InvSlot { item_id: id, .. } => {
132 *id = item.to_owned();
133 true
134 }
135 _ => false,
136 }
137}
138
139pub fn set_count(w: &mut Widget, n: u32) -> bool {
141 match &mut w.kind {
142 yog_api::ui::widget::WidgetKind::InvSlot { count, .. } => {
143 *count = n;
144 true
145 }
146 _ => false,
147 }
148}
149
150pub fn set_source(w: &mut Widget, source: &str) -> bool {
152 use yog_api::ui::widget::WidgetKind;
153 match &mut w.kind {
154 WidgetKind::Image(img) => {
155 img.source = source.to_owned();
156 true
157 }
158 _ => false,
159 }
160}
161
162pub fn set_checked(w: &mut Widget, on: bool) {
164 w.set_state_inherited(StateFlags::CHECKED, on);
165}
166
167#[cfg(test)]
168mod tests {
169 use super::*;
170
171 #[test]
172 fn slot_reports_changes_only() {
173 let mut s = Slot::new();
174 assert!(s.set(1));
175 assert!(!s.set(1));
176 assert!(s.set(2));
177 assert_eq!(*s.get(), 2);
178 s.reset();
179 assert!(s.set(2));
180 }
181
182 #[test]
183 fn conversions() {
184 assert_eq!(text(&5), "5");
185 assert_eq!(text("x"), "x");
186 assert_eq!(color(0xFF00FF00u32), 0xFF00FF00);
187 let seven: &u32 = &7;
188 assert_eq!(color(seven), 7);
189 assert_eq!(count(-1i32), 0);
190 assert_eq!(count(3usize), 3);
191 assert_eq!(key("a"), key(&"a".to_string()));
192 let sel: Option<&str> = Some("b");
193 assert!(is_selected(&sel, &"b".to_string()));
194 assert!(!is_selected(&None::<&str>, &"b".to_string()));
195 }
196}