pub struct UiProperty<T: Clone + PartialEq> {
value: T,
dirty: bool,
}
impl<T: Clone + PartialEq> UiProperty<T> {
pub fn new(value: T) -> Self {
Self {
value,
dirty: false,
}
}
pub fn get(&self) -> &T {
&self.value
}
pub fn set(&mut self, value: T) {
if self.value != value {
self.value = value;
self.dirty = true;
}
}
pub fn dirty(&self) -> bool {
self.dirty
}
pub fn take_dirty(&mut self) -> bool {
let was_dirty = self.dirty;
self.dirty = false;
was_dirty
}
pub fn clear_dirty(&mut self) {
self.dirty = false;
}
pub fn map<U>(&self, transform: impl FnOnce(&T) -> U) -> U {
transform(&self.value)
}
pub fn derived<U: Clone + PartialEq>(
&self,
transform: impl Fn(&T) -> U + 'static,
) -> UiDerivedProperty<T, U> {
let initial = transform(&self.value);
UiDerivedProperty {
source_value: self.value.clone(),
derived_value: initial,
transform: Box::new(transform),
}
}
}
impl<T: Clone + PartialEq> std::ops::Deref for UiProperty<T> {
type Target = T;
fn deref(&self) -> &T {
&self.value
}
}
impl<T: Clone + PartialEq + Default> Default for UiProperty<T> {
fn default() -> Self {
Self::new(T::default())
}
}
pub struct UiDerivedProperty<S: Clone + PartialEq, T: Clone + PartialEq> {
source_value: S,
derived_value: T,
transform: Box<dyn Fn(&S) -> T>,
}
impl<S: Clone + PartialEq, T: Clone + PartialEq> UiDerivedProperty<S, T> {
pub fn get(&self) -> &T {
&self.derived_value
}
pub fn update(&mut self, source: &UiProperty<S>) -> bool {
if *source.get() != self.source_value {
self.source_value = source.get().clone();
let new_value = (self.transform)(&self.source_value);
if new_value != self.derived_value {
self.derived_value = new_value;
return true;
}
}
false
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn property_new_not_dirty() {
let prop = UiProperty::new(42);
assert_eq!(*prop.get(), 42);
assert!(!prop.dirty());
}
#[test]
fn property_set_different_marks_dirty() {
let mut prop = UiProperty::new(0.0_f32);
prop.set(1.0);
assert_eq!(*prop.get(), 1.0);
assert!(prop.dirty());
}
#[test]
fn property_set_same_stays_clean() {
let mut prop = UiProperty::new(5);
prop.set(5);
assert!(!prop.dirty());
}
#[test]
fn property_take_dirty_clears() {
let mut prop = UiProperty::new(0);
prop.set(1);
assert!(prop.take_dirty());
assert!(!prop.dirty());
assert!(!prop.take_dirty());
}
#[test]
fn property_clear_dirty() {
let mut prop = UiProperty::new(0);
prop.set(1);
prop.clear_dirty();
assert!(!prop.dirty());
}
#[test]
fn property_deref() {
let prop = UiProperty::new("hello".to_string());
assert_eq!(prop.len(), 5);
}
#[test]
fn property_default() {
let prop: UiProperty<i32> = UiProperty::default();
assert_eq!(*prop.get(), 0);
assert!(!prop.dirty());
}
#[test]
fn property_map() {
let prop = UiProperty::new(10);
let doubled = prop.map(|v| v * 2);
assert_eq!(doubled, 20);
}
#[test]
fn derived_initial_value() {
let source = UiProperty::new(10);
let derived = source.derived(|v| v * 2);
assert_eq!(*derived.get(), 20);
}
#[test]
fn derived_update_changes_value() {
let mut source = UiProperty::new(10);
let mut derived = source.derived(|v| v * 2);
source.set(20);
let changed = derived.update(&source);
assert!(changed);
assert_eq!(*derived.get(), 40);
}
#[test]
fn derived_update_no_source_change() {
let source = UiProperty::new(10);
let mut derived = source.derived(|v| v * 2);
let changed = derived.update(&source);
assert!(!changed);
assert_eq!(*derived.get(), 20);
}
#[test]
fn derived_update_same_derived_value() {
let mut source = UiProperty::new(10);
let mut derived = source.derived(|v| v / 5);
source.set(12);
let changed = derived.update(&source);
assert!(!changed);
assert_eq!(*derived.get(), 2);
}
#[test]
fn property_multiple_sets() {
let mut prop = UiProperty::new(0);
prop.set(1);
prop.set(2);
prop.set(3);
assert_eq!(*prop.get(), 3);
assert!(prop.dirty());
prop.take_dirty();
prop.set(3);
assert!(!prop.dirty());
}
}