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