Skip to content

Commit

Permalink
refactor(core): 💡 let KeyWidget has a clearer responsibility
Browse files Browse the repository at this point in the history
  • Loading branch information
M-Adoo committed Aug 22, 2023
1 parent bdb877b commit cffbf1f
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 104 deletions.
115 changes: 39 additions & 76 deletions core/src/builtin_widgets/key.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::{impl_query_self_only, prelude::*};
use std::{
cell::Cell,
cmp::{Eq, Ord, PartialOrd},
fmt::Debug,
};
Expand Down Expand Up @@ -29,66 +30,43 @@ pub enum Key {
K32([u8; 32]),
}

/// The KeyStatus is the status of the Key, not the status of the KeyWidget.
/// The change of Key status is determined by its associated KeyWidget state
/// and its own state.
///
/// The KeyStatus has four status:
/// The first status: The KeyWidget associated with the Key was constructed, the
/// Key didn't exist in DynWidget Key List, the key status is `KeyStatus::Init`.
///
/// The second status: The KeyWidget associated with the Key was mounted, and
/// now its status is `KeyStatus::Init`, the key status will be changed
/// `KeyStatus::Mounted`.
///
/// The third status: The KeyWidget associated with the Key was disposed, and
/// the same key has anther associated KeyWidget was mounted, the key status
/// will be changed `KeyStatus::Updated`.
///
/// The last status: The KeyWidget associated with the Key was disposed, the
/// same key don't has anther associated KeyWidget, the key status will be
/// changed `KeyStatus::Disposed`.
#[derive(PartialEq, Debug, Clone, Copy)]
pub enum KeyStatus {
Init,
Mounted,
Updated,
Disposed,
}

impl Default for KeyStatus {
fn default() -> Self { Self::Init }
}

#[derive(Clone, Debug, PartialEq, Copy)]
pub struct KeyChange<V>(pub Option<V>, pub Option<V>);
pub struct KeyChange<V>(pub Option<V>, pub V);

impl<V> Default for KeyChange<V> {
fn default() -> Self { KeyChange(None, None) }
impl<V: Default> Default for KeyChange<V> {
fn default() -> Self { KeyChange(None, V::default()) }
}
#[derive(Declare)]
pub struct KeyWidget<V = ()> {

/// A widget that can be used to track if the widget is the same widget in two
/// frames by its key. If two widget has same parent and key in two frames, the
/// new widget in the next frame will be treated as the same widget in the last
/// frame.
#[derive(Declare, Declare2)]
pub struct KeyWidget<V: Default + 'static = ()> {
#[declare(convert=into)]
pub key: Key,
pub value: Option<V>,

#[declare(default)]
#[declare(default, strict)]
pub value: V,
#[declare(skip)]
before_value: Option<V>,
#[declare(default)]
status: KeyStatus,
#[declare(skip)]
has_successor: Cell<bool>,
}

/// A trait for `keyWidget` that use to record information of the previous and
/// next key widget.
pub(crate) trait AnyKey: Any {
fn key(&self) -> Key;
fn record_before_value(&self, key: &dyn AnyKey);
fn disposed(&self);
fn mounted(&self);
/// Record the previous KeyWidget associated with the same key.
fn record_prev_key_widget(&self, key: &dyn AnyKey);
/// Record the next KeyWidget associated with the same key.
fn record_next_key_widget(&self, key: &dyn AnyKey);
fn as_any(&self) -> &dyn Any;
}

impl<V> AnyKey for State<KeyWidget<V>>
where
V: Clone + PartialEq,
V: Default + Clone + PartialEq,
Self: Any,
{
fn key(&self) -> Key {
Expand All @@ -98,7 +76,7 @@ where
}
}

fn record_before_value(&self, key: &dyn AnyKey) {
fn record_prev_key_widget(&self, key: &dyn AnyKey) {
assert_eq!(self.key(), key.key());
let Some(key) = key.as_any().downcast_ref::<Self>() else {
log::warn!("Different value type for same key.");
Expand All @@ -116,28 +94,12 @@ where
}
}

fn disposed(&self) {
match self {
State::Stateless(_) => (),
State::Stateful(this) => {
this.state_ref().status = KeyStatus::Disposed;
}
}
}

fn mounted(&self) {
match self {
State::Stateless(_) => {}
State::Stateful(this) => {
this.state_ref().status = KeyStatus::Mounted;
}
}
}
fn record_next_key_widget(&self, _: &dyn AnyKey) { self.as_ref().has_successor.set(true); }

fn as_any(&self) -> &dyn Any { self }
}

impl<V: 'static + Clone + PartialEq> ComposeChild for KeyWidget<V> {
impl<V: 'static + Default + Clone + PartialEq> ComposeChild for KeyWidget<V> {
type Child = Widget;
#[inline]
fn compose_child(this: State<Self>, child: Self::Child) -> Widget {
Expand All @@ -150,24 +112,25 @@ impl_query_self_only!(Box<dyn AnyKey>);

impl<V> KeyWidget<V>
where
V: Clone + PartialEq,
V: Default + Clone + PartialEq,
{
fn record_before_value(&mut self, value: Option<V>) {
self.status = KeyStatus::Updated;
self.before_value = value;
}
/// Detect if the key widget is a new widget, there is not predecessor widget
/// that has same key. Usually used in `on_mounted` callback.
pub fn is_enter(&self) -> bool { self.before_value.is_none() }
/// Detect if the key widget is really be disposed, there is not successor
/// widget has same key. Usually used in `on_disposed` callback.
pub fn is_leave(&self) -> bool { !self.has_successor.get() }

pub fn is_enter(&self) -> bool { self.status == KeyStatus::Mounted }

pub fn is_modified(&self) -> bool { self.status == KeyStatus::Updated }

pub fn is_changed(&self) -> bool { self.is_modified() && self.before_value != self.value }

pub fn is_disposed(&self) -> bool { self.status == KeyStatus::Disposed }
/// Detect if the value of the key widget is changed
pub fn is_changed(&self) -> bool { self.before_value.as_ref() != Some(&self.value) }

pub fn get_change(&self) -> KeyChange<V> {
KeyChange(self.before_value.clone(), self.value.clone())
}

pub fn before_value(&self) -> Option<&V> { self.before_value.as_ref() }

fn record_before_value(&mut self, value: V) { self.before_value = Some(value); }
}

impl_query_self_only!(Key);
Expand Down
24 changes: 9 additions & 15 deletions core/src/dynamic_widget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -345,18 +345,12 @@ impl<D: 'static> DynRender<D> {
let key = &new_key_widget.key();
if let Some(wid) = old_key_list.get(key) {
inspect_key(wid, arena, |old_key_widget: &dyn AnyKey| {
new_key_widget.record_before_value(old_key_widget);
new_key_widget.record_prev_key_widget(old_key_widget);
old_key_widget.record_next_key_widget(new_key_widget);
});
old_key_list.remove(key);
} else {
new_key_widget.mounted();
}
});
});

old_key_list
.values()
.for_each(|wid| inspect_key(wid, arena, |old_key_widget| old_key_widget.disposed()));
}
}

Expand Down Expand Up @@ -588,23 +582,23 @@ mod tests {
KeyWidget {
id: key,
key: Key::from(i),
value: Some(c),
value: c,

MockBox {
size: Size::zero(),
on_mounted: move |_| {
if key.is_enter() {
(*enter_list).push(key.value.unwrap());
(*enter_list).push(key.value);
}

if key.is_changed() {
(*update_list).push(key.value.unwrap());
(*update_list).push(key.value);
*key_change = key.get_change();
}
},
on_disposed: move |_| {
if key.is_disposed() {
(*leave_list).push(key.value.unwrap());
if key.is_leave() {
(*leave_list).push(key.value);
}
}
}
Expand Down Expand Up @@ -661,7 +655,7 @@ mod tests {
.iter()
.all(|item| expect_vec.contains(item))
);
assert_eq!(*key_change.state_ref(), KeyChange(Some('2'), Some('b')));
assert_eq!(*key_change.state_ref(), KeyChange(Some('2'), 'b'));
(*update_list.state_ref()).clear();

// 4. remove the second item
Expand Down Expand Up @@ -690,7 +684,7 @@ mod tests {
.iter()
.all(|item| expect_vec.contains(item))
);
assert_eq!(*key_change.state_ref(), KeyChange(Some('1'), Some('a')));
assert_eq!(*key_change.state_ref(), KeyChange(Some('1'), 'a'));
(*update_list.state_ref()).clear();
}

Expand Down
18 changes: 5 additions & 13 deletions core/src/pipe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,10 +207,8 @@ fn update_key_status_single(new: WidgetId, old: WidgetId, ctx: &BuildCtx) {
inspect_key(old, ctx, |old_key| {
inspect_key(new, ctx, |new_key| {
if old_key.key() == new_key.key() {
new_key.record_before_value(old_key);
} else {
old_key.disposed();
new_key.mounted();
new_key.record_prev_key_widget(old_key);
old_key.record_next_key_widget(new_key);
}
})
})
Expand All @@ -230,19 +228,13 @@ fn update_key_state_multi(old: &[WidgetId], new: &[WidgetId], ctx: &BuildCtx) {
inspect_key(*n, ctx, |new_key: &dyn AnyKey| {
let key = &new_key.key();
if let Some(o) = old_key_list.get(key) {
inspect_key(*o, ctx, |old_key_widget: &dyn AnyKey| {
new_key.record_before_value(old_key_widget)
inspect_key(*o, ctx, |old_key: &dyn AnyKey| {
new_key.record_prev_key_widget(old_key);
old_key.record_next_key_widget(new_key);
});
old_key_list.remove(key);
} else {
new_key.mounted();
}
});
}

old_key_list
.values()
.for_each(|o| inspect_key(*o, ctx, |old_key| old_key.disposed()));
}

fn inspect_key(id: WidgetId, ctx: &BuildCtx, mut cb: impl FnMut(&dyn AnyKey)) {
Expand Down
3 changes: 3 additions & 0 deletions macros/src/declare_derive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ mod kw {
custom_keyword!(into);
custom_keyword!(custom);
custom_keyword!(skip);
custom_keyword!(strict);
custom_keyword!(strip_option);
}

Expand Down Expand Up @@ -129,6 +130,8 @@ impl Parse for DeclareAttr {
attr.default = Some(input.parse()?);
} else if lookahead.peek(kw::skip) {
attr.skip = Some(input.parse()?);
} else if lookahead.peek(kw::strict) {
let _ = input.parse::<kw::strict>();
} else {
return Err(lookahead.error());
}
Expand Down

0 comments on commit cffbf1f

Please sign in to comment.