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