use std::fmt::Display;
use std::hash::{Hash, Hasher};
use yog_api::ui::{StateFlags, Units, Widget};
#[derive(Debug, Clone, Default)]
pub struct Slot<T>(Option<T>);
impl<T: PartialEq> Slot<T> {
pub const fn new() -> Self {
Slot(None)
}
pub fn set(&mut self, v: T) -> bool {
if self.0.as_ref() == Some(&v) {
return false;
}
self.0 = Some(v);
true
}
pub fn get(&self) -> &T {
self.0
.as_ref()
.expect("yog-kit: binding read before it was set")
}
pub fn reset(&mut self) {
self.0 = None;
}
}
#[diagnostic::on_unimplemented(
message = "`{Self}` can't be shown as text in a .yui binding",
note = "bind a value that implements Display, or call a view-model method returning String"
)]
pub trait BindText {
fn bind_text(&self) -> String;
}
impl<T: Display + ?Sized> BindText for T {
fn bind_text(&self) -> String {
self.to_string()
}
}
#[diagnostic::on_unimplemented(
message = "`{Self}` can't be used as a colour in a .yui binding",
note = "colours are u32 values 0xAARRGGBB (a .yres colour, or a view-model method returning u32)"
)]
pub trait BindColor {
fn bind_color(&self) -> u32;
}
impl BindColor for u32 {
fn bind_color(&self) -> u32 {
*self
}
}
impl<T: BindColor + ?Sized> BindColor for &T {
fn bind_color(&self) -> u32 {
(**self).bind_color()
}
}
pub fn text<T: BindText + ?Sized>(v: &T) -> String {
v.bind_text()
}
pub fn color<T: BindColor>(v: T) -> u32 {
v.bind_color()
}
pub fn flag(v: impl Into<bool>) -> bool {
v.into()
}
pub fn units(v: impl Into<Units>) -> Units {
v.into()
}
pub fn number(v: impl Into<f64>) -> f32 {
v.into() as f32
}
pub fn count<T: TryInto<u32>>(v: T) -> u32 {
v.try_into().unwrap_or(0)
}
pub fn key<K: Hash + ?Sized>(k: &K) -> u64 {
let mut h = std::collections::hash_map::DefaultHasher::new();
k.hash(&mut h);
h.finish()
}
pub fn is_selected<S: PartialEq<K>, K: ?Sized>(selected: &Option<S>, key: &K) -> bool {
selected.as_ref().is_some_and(|s| s == key)
}
pub fn set_placeholder(w: &mut Widget, text: &str) -> bool {
match &mut w.kind {
yog_api::ui::widget::WidgetKind::TextBox(tb) => {
tb.placeholder = text.to_owned();
true
}
_ => false,
}
}
pub fn set_item(w: &mut Widget, item: &str) -> bool {
use yog_api::ui::widget::WidgetKind;
match &mut w.kind {
WidgetKind::ItemSlot(id) | WidgetKind::InvSlot { item_id: id, .. } => {
*id = item.to_owned();
true
}
_ => false,
}
}
pub fn set_count(w: &mut Widget, n: u32) -> bool {
match &mut w.kind {
yog_api::ui::widget::WidgetKind::InvSlot { count, .. } => {
*count = n;
true
}
_ => false,
}
}
pub fn set_source(w: &mut Widget, source: &str) -> bool {
use yog_api::ui::widget::WidgetKind;
match &mut w.kind {
WidgetKind::Image(img) => {
img.source = source.to_owned();
true
}
WidgetKind::Surface(id) => {
*id = source.to_owned();
true
}
_ => false,
}
}
pub fn set_checked(w: &mut Widget, on: bool) {
w.set_state_inherited(StateFlags::CHECKED, on);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn slot_reports_changes_only() {
let mut s = Slot::new();
assert!(s.set(1));
assert!(!s.set(1));
assert!(s.set(2));
assert_eq!(*s.get(), 2);
s.reset();
assert!(s.set(2));
}
#[test]
fn conversions() {
assert_eq!(text(&5), "5");
assert_eq!(text("x"), "x");
assert_eq!(color(0xFF00FF00u32), 0xFF00FF00);
let seven: &u32 = &7;
assert_eq!(color(seven), 7);
assert_eq!(count(-1i32), 0);
assert_eq!(count(3usize), 3);
assert_eq!(key("a"), key(&"a".to_string()));
let sel: Option<&str> = Some("b");
assert!(is_selected(&sel, &"b".to_string()));
assert!(!is_selected(&None::<&str>, &"b".to_string()));
}
}