use std::{
cell::UnsafeCell,
hash::{Hash, Hasher},
};
use ribir_algo::Sc;
use smallvec::{SmallVec, smallvec};
use widget_id::RenderQueryable;
use crate::{
data_widget::AnonymousAttacher,
pipe::DynInfo,
prelude::*,
render_helper::{PureRender, RenderProxy},
window::WindowId,
};
#[derive(Default)]
pub struct Classes {
pub(crate) store: ahash::HashMap<ClassName, ClassImpl>,
}
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
pub struct ClassName(&'static str);
pub type ClassImpl = fn(Widget) -> Widget;
#[derive(Default)]
pub struct Class {
pub class: Option<ClassName>,
}
#[simple_declare]
#[derive(Eq)]
pub struct OverrideClass {
pub name: ClassName,
pub class_impl: ClassImpl,
}
#[macro_export]
macro_rules! class_names {
($(
$(#[$outer:meta])?
$name:ident
),* $(,)?) => {
$(
$(#[$outer])?
pub const $name: ClassName = ClassName::new(stringify!($name));
)*
};
}
impl ClassName {
pub const fn new(name: &'static str) -> Self { ClassName(name) }
}
impl Classes {
#[inline]
pub fn insert(&mut self, cls: ClassName, f: ClassImpl) -> Option<ClassImpl> {
self.store.insert(cls, f)
}
}
impl Declare for Class {
type Builder = FatObj<()>;
#[inline]
fn declarer() -> Self::Builder { FatObj::new(()) }
}
impl ComposeChild<'static> for Classes {
type Child = GenWidget;
fn compose_child(this: impl StateWriter<Value = Self>, child: Self::Child) -> Widget<'static> {
Provider::new(Box::new(this.clone_writer()))
.with_child(fn_widget! { pipe!($this;).map(move |_| child.gen_widget())})
.into_widget()
}
}
impl<'c> ComposeChild<'c> for OverrideClass {
type Child = FnWidget<'c>;
fn compose_child(this: impl StateWriter<Value = Self>, child: Self::Child) -> Widget<'c> {
let cls_override = this.try_into_value().unwrap_or_else(|_| {
panic!("Attempting to use `OverrideClass` as a reader or writer is not allowed.")
});
Provider::new(Box::new(Queryable(cls_override)))
.with_child(child)
.into_widget()
}
}
impl<'c> ComposeChild<'c> for Class {
type Child = Widget<'c>;
fn compose_child(this: impl StateWriter<Value = Self>, child: Self::Child) -> Widget<'c> {
let f = move || match this.try_into_value() {
Ok(c) => c.apply_style(child),
Err(this) => {
let this2 = this.clone_watcher();
let cls_child = ClassChild::new(BuildCtx::get().tree().dummy_id());
let cls_child2 = cls_child.clone();
let child = child.on_build(move |orig_id| {
let tree = BuildCtx::get_mut().tree_mut();
let mut orig_child = OrigChild::from_id(orig_id, tree);
cls_child2.inner().orig_id = orig_id;
let orig_child2 = orig_child.clone();
let wnd_id = tree.window().id();
let u = this2
.raw_modifies()
.filter(|s| s.contains(ModifyScope::FRAMEWORK))
.sample(AppCtx::frame_ticks().clone())
.subscribe(move |_| cls_child2.update(&orig_child2, &this2.read(), wnd_id))
.unsubscribe_when_dropped();
orig_child.attach_subscription(u);
});
this
.read()
.apply_style(child)
.on_build(move |child_id| cls_child.set_child_id(child_id))
}
};
f.into_widget()
}
}
impl Class {
pub fn apply_style<'a>(&self, w: Widget<'a>) -> Widget<'a> {
if let Some(cls_impl) = self.class_impl() { cls_impl(w) } else { w }
}
fn class_impl(&self) -> Option<QueryRef<ClassImpl>> {
let cls = self.class?;
let override_cls_id = QueryId::of::<OverrideClass>();
let classes_id = QueryId::of::<Classes>();
let (id, handle) = BuildCtx::get().all_providers().find_map(|p| {
p.query_match(&[override_cls_id, classes_id], &|id, h| {
if id == &override_cls_id {
h.downcast_ref::<OverrideClass>()
.map_or(false, |c| c.name == cls)
} else {
h.downcast_ref::<Classes>()
.map_or(false, |c| c.store.contains_key(&cls))
}
})
})?;
if id == override_cls_id {
handle
.into_ref::<OverrideClass>()
.map(|cls| QueryRef::map(cls, |c| &c.class_impl))
} else {
let classes = handle.into_ref::<Classes>()?;
QueryRef::filter_map(classes, |c| c.store.get(&cls)).ok()
}
}
}
#[macro_export]
macro_rules! multi_class_impl {
($($class: expr),*) => {
move |mut w: Widget| {
$(w = $class(w);)*
w
}
};
}
#[derive(Clone)]
struct OrigChild(Sc<UnsafeCell<Box<dyn RenderQueryable>>>);
#[derive(Clone)]
struct ClassChild(Sc<UnsafeCell<InnerClassChild>>);
struct InnerClassChild {
child: Box<dyn RenderQueryable>,
child_id: WidgetId,
orig_id: WidgetId,
}
impl ClassChild {
fn new(id: WidgetId) -> Self {
let inner = InnerClassChild { child: Box::new(PureRender(Void)), child_id: id, orig_id: id };
Self(Sc::new(UnsafeCell::new(inner)))
}
fn set_child_id(&self, id: WidgetId) {
let inner = self.inner();
inner.child_id = id;
id.wrap_node(BuildCtx::get_mut().tree_mut(), |node| {
inner.child = node;
Box::new(self.clone())
});
}
fn update(&self, orig: &OrigChild, class: &Class, wnd_id: WindowId) {
let wnd =
AppCtx::get_window(wnd_id).expect("This handle is not valid because the window is closed");
let InnerClassChild { child, child_id, orig_id } = self.inner();
let _guard = BuildCtx::init_for(*child_id, wnd.tree);
let n_orig = BuildCtx::get_mut().alloc(Box::new(orig.clone()));
let tree = BuildCtx::get_mut().tree_mut();
let cls_holder = child_id.place_holder(tree);
let child_node = self.take_inner();
let mut new_id = class.apply_style(Widget::from_id(n_orig)).build();
let class_node = std::mem::replace(child_id.get_node_mut(tree).unwrap(), child_node);
let [new, old] = tree.get_many_mut(&[n_orig, *orig_id]);
std::mem::swap(new, old);
if new_id == n_orig {
new_id = *orig_id;
} else {
n_orig.insert_after(*orig_id, tree);
n_orig.dispose_subtree(tree);
}
new_id.wrap_node(tree, |node| {
*child = node;
class_node
});
if new_id != *child_id {
let old_rg = *child_id..=*orig_id;
let new_rg = new_id..=*orig_id;
new_id
.query_all_iter::<DynInfo>(tree)
.rev()
.for_each(|info| {
info
.borrow_mut()
.single_range_replace(&old_rg, &new_rg)
});
cls_holder.replace(new_id, tree);
}
if orig_id != child_id {
child_id.dispose_subtree(tree);
}
let mut stack: SmallVec<[WidgetId; 1]> = smallvec![new_id];
while let Some(w) = stack.pop() {
if w != *orig_id {
w.on_mounted_subtree(tree);
stack.extend(w.children(tree).rev());
}
}
*child_id = new_id;
tree.mark_dirty(new_id);
tree.mark_dirty(*orig_id);
}
#[allow(clippy::mut_from_ref)]
fn inner(&self) -> &mut InnerClassChild { unsafe { &mut *self.0.get() } }
fn take_inner(&self) -> Box<dyn RenderQueryable> {
std::mem::replace(&mut self.inner().child, Box::new(PureRender(Void)))
}
}
impl OrigChild {
fn from_id(id: WidgetId, tree: &mut WidgetTree) -> Self {
let mut orig = None;
id.wrap_node(tree, |node| {
let c = OrigChild(Sc::new(UnsafeCell::new(node)));
orig = Some(c.clone());
Box::new(c)
});
orig.unwrap()
}
fn attach_subscription(&mut self, guard: impl Any) {
let inner = self.node_mut();
let child = unsafe { Box::from_raw(inner.as_mut()) };
let child = Box::new(AnonymousAttacher::new(child, Box::new(guard)));
let tmp = std::mem::replace(inner, child);
std::mem::forget(tmp);
}
fn node(&self) -> &dyn RenderQueryable { unsafe { &*(*self.0.get()) } }
#[allow(clippy::mut_from_ref)]
fn node_mut(&self) -> &mut Box<dyn RenderQueryable> { unsafe { &mut (*self.0.get()) } }
}
impl RenderProxy for OrigChild {
fn proxy(&self) -> impl Deref<Target = impl Render + ?Sized> { self.node() }
}
impl RenderProxy for ClassChild {
fn proxy(&self) -> impl Deref<Target = impl Render + ?Sized> { self.inner().child.as_ref() }
}
impl Query for OrigChild {
fn query_all<'q>(
&'q self, type_id: &QueryId, out: &mut smallvec::SmallVec<[QueryHandle<'q>; 1]>,
) {
self.node().query_all(type_id, out)
}
fn query(&self, type_id: &QueryId) -> Option<QueryHandle> { self.node().query(type_id) }
fn query_match(
&self, ids: &[QueryId], filter: &dyn Fn(&QueryId, &QueryHandle) -> bool,
) -> Option<(QueryId, QueryHandle)> {
self.node().query_match(ids, filter)
}
fn query_write(&self, type_id: &QueryId) -> Option<QueryHandle> {
self.node_mut().query_write(type_id)
}
}
impl Query for ClassChild {
fn query_all<'q>(
&'q self, type_id: &QueryId, out: &mut smallvec::SmallVec<[QueryHandle<'q>; 1]>,
) {
self.inner().child.query_all(type_id, out)
}
fn query(&self, type_id: &QueryId) -> Option<QueryHandle> { self.inner().child.query(type_id) }
fn query_match(
&self, ids: &[QueryId], filter: &dyn Fn(&QueryId, &QueryHandle) -> bool,
) -> Option<(QueryId, QueryHandle)> {
self.inner().child.query_match(ids, filter)
}
fn query_write(&self, type_id: &QueryId) -> Option<QueryHandle> {
self.inner().child.query_write(type_id)
}
}
impl PartialEq for OverrideClass {
fn eq(&self, other: &Self) -> bool { self.name == other.name }
}
impl Hash for OverrideClass {
fn hash<H: Hasher>(&self, state: &mut H) { self.name.hash(state); }
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
reset_test_env,
test_helper::{MockBox, MockMulti, TestWindow, split_value},
};
class_names!(MARGIN, BOX_200, CLAMP_50, EMPTY);
fn initd_classes() -> Classes {
let mut classes = Classes::default();
classes.insert(MARGIN, style_class!(margin: EdgeInsets::all(10.)));
classes.insert(BOX_200, |w| {
fn_widget! {
@MockBox {
size: Size::new(200., 200.),
@ { w }
}
}
.into_widget()
});
classes.insert(CLAMP_50, style_class! {
clamp: BoxClamp::fixed_size(Size::new(50., 50.))
});
classes
}
#[test]
fn switch_class() {
reset_test_env!();
let (cls, w_cls) = split_value(MARGIN);
let mut wnd = TestWindow::new(fn_widget! {
let cls = cls.clone_watcher();
initd_classes().with_child(fn_widget! {
@Container {
size: Size::new(100., 100.),
class: pipe!(*$cls),
}
})
});
wnd.draw_frame();
wnd.assert_root_size(Size::new(120., 120.));
*w_cls.write() = BOX_200;
wnd.draw_frame();
wnd.assert_root_size(Size::new(200., 200.));
}
#[test]
#[should_panic(expected = "on_disposed called")]
fn on_disposed_of_class_nodes() {
reset_test_env!();
class_names!(ON_DISPOSED);
let (cls, w_cls) = split_value(ON_DISPOSED);
let mut wnd = TestWindow::new(fn_widget! {
let cls = cls.clone_watcher();
let mut classes = initd_classes();
classes.insert(ON_DISPOSED, |w| {
fn_widget! {
@MockBox {
size: Size::zero(),
on_disposed: move |_| panic!("on_disposed called"),
@ { w }
}
}
.into_widget()
});
classes.with_child(fn_widget! {
@Container {
size: Size::new(100., 100.),
class: pipe!(*$cls),
}
})
});
wnd.draw_frame();
*w_cls.write() = MARGIN;
wnd.draw_frame();
}
#[test]
fn fix_crash_for_class_impl_may_have_multi_child() {
reset_test_env!();
class_names!(MULTI);
let (cls, w_cls) = split_value(MARGIN);
let mut wnd = TestWindow::new(fn_widget! {
let cls = cls.clone_watcher();
let mut classes = initd_classes();
classes.insert(MULTI, |w| {
fn_widget! {
@MockMulti {
@MockBox { size: Size::new(100., 100.) }
@MockBox { size: Size::new(100., 200.) }
@ { w }
}
}
.into_widget()
});
classes.with_child(fn_widget! {
@Container {
size: Size::new(100., 100.),
class: pipe!(*$cls),
}
})
});
wnd.draw_frame();
wnd.assert_root_size(Size::new(120., 120.));
*w_cls.write() = MULTI;
wnd.draw_frame();
wnd.assert_root_size(Size::new(300., 200.));
}
#[test]
#[should_panic(expected = "0")]
fn fix_provider_in_pipe_class() {
reset_test_env!();
class_names!(PROVIDER_CLS);
let mut wnd = TestWindow::new(fn_widget! {
let trigger = Stateful::new(true);
let mut classes = Classes::default();
classes.insert(PROVIDER_CLS, |w| {
Provider::new(Box::new(Queryable(0)))
.with_child(fn_widget! { w })
.into_widget()
});
classes.with_child(fn_widget! {
@Container {
size: Size::new(100., 100.),
class: pipe!($trigger; PROVIDER_CLS),
on_performed_layout: |e| {
panic!("{}", *e.query::<i32>().unwrap());
}
}
})
});
wnd.draw_frame();
}
#[test]
fn fix_not_mounted_class_node() {
reset_test_env!();
let (cls, w_cls) = split_value(EMPTY);
let mut wnd = TestWindow::new(fn_widget! {
let cls = cls.clone_watcher();
initd_classes().with_child(fn_widget! {
@Container {
size: Size::new(100., 100.),
class: pipe!(*$cls),
}
})
});
wnd.draw_frame();
wnd.assert_root_size(Size::new(100., 100.));
*w_cls.write() = BOX_200;
wnd.draw_frame();
wnd.assert_root_size(Size::new(200., 200.));
}
#[test]
fn fix_style_class_switch() {
reset_test_env!();
let (cls, w_cls) = split_value(EMPTY);
let mut wnd = TestWindow::new(fn_widget! {
let cls = cls.clone_watcher();
initd_classes().with_child(fn_widget! {
@Container {
size: Size::new(100., 100.),
class: pipe!(*$cls),
}
})
});
wnd.draw_frame();
wnd.assert_root_size(Size::new(100., 100.));
*w_cls.write() = CLAMP_50;
wnd.draw_frame();
wnd.assert_root_size(Size::new(50., 50.));
}
#[test]
fn override_class() {
reset_test_env!();
let mut wnd = TestWindow::new(fn_widget! {
initd_classes().with_child(fn_widget! {
@OverrideClass {
name: MARGIN,
class_impl: style_class! {
clamp: BoxClamp::fixed_size(Size::new(66., 66.))
} as ClassImpl,
@container! {
size: Size::new(100., 100.),
class: MARGIN,
}
}
})
});
wnd.draw_frame();
wnd.assert_root_size(Size::new(66., 66.));
}
}