Skip to content

Commit

Permalink
feat(core): 🎸 introduce OverrideClass to override a single class wi…
Browse files Browse the repository at this point in the history
…thin a subtree
  • Loading branch information
M-Adoo committed Nov 16, 2024
1 parent 8bf7f2c commit 4138df8
Show file tree
Hide file tree
Showing 9 changed files with 201 additions and 10 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Please only add new entries below the [Unreleased](#unreleased---releasedate) he

- **core**: The `keyframes!` macro has been introduced to manage the intermediate steps of animation states. (#653 @M-Adoo)
- **core**: Added `QueryId` as a replacement for `TypeId` to facilitate querying types by Provider across different binaries. (#656 @M-Adoo)
- **core**: Added `OverrideClass` to override a single class within a subtree. (#pr @M-Adoo)
- **widgets**: Added `LinearProgress` and `SpinnerProgress` widgets along with their respective material themes. (#630 @wjian23 @M-Adoo)

### Fixed
Expand Down
105 changes: 97 additions & 8 deletions core/src/builtin_widgets/class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@
//! App::run(w).with_app_theme(theme);
//! ```
use std::cell::UnsafeCell;
use std::{
cell::UnsafeCell,
hash::{Hash, Hasher},
};

use ribir_algo::Sc;
use smallvec::{SmallVec, smallvec};
Expand Down Expand Up @@ -77,6 +80,16 @@ pub struct Class {
pub class: Option<ClassName>,
}

/// This widget overrides the class implementation of a `ClassName`, offering a
/// lighter alternative to `Classes` when you only need to override a single
/// class.
#[simple_declare]
#[derive(Eq)]
pub struct OverrideClass {
pub name: ClassName,
pub class_impl: ClassImpl,
}

/// This macro is utilized to define class names; ensure that your name is
/// unique within the application.
#[macro_export]
Expand Down Expand Up @@ -119,6 +132,19 @@ impl ComposeChild<'static> for Classes {
}
}

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>;

Expand Down Expand Up @@ -157,12 +183,34 @@ impl<'c> ComposeChild<'c> for Class {

impl Class {
pub fn apply_style<'a>(&self, w: Widget<'a>) -> Widget<'a> {
let class = self.class.and_then(|cls| {
BuildCtx::get()
.all_providers::<Classes>()
.find_map(|c| QueryRef::filter_map(c, |c| c.store.get(&cls)).ok())
});
if let Some(c) = class { c(w) } else { w }
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()
}
}
}

Expand Down Expand Up @@ -328,6 +376,12 @@ impl Query for OrigChild {

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)
}
Expand All @@ -342,14 +396,26 @@ impl Query for ClassChild {

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,
Expand Down Expand Up @@ -543,4 +609,27 @@ mod tests {
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.));
}
}
8 changes: 8 additions & 0 deletions core/src/builtin_widgets/provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,14 @@ impl<const M: usize> Query for [Box<dyn Query>; M] {
self.iter().find_map(|q| q.query_write(query_id))
}

fn query_match(
&self, ids: &[QueryId], filter: &dyn Fn(&QueryId, &QueryHandle) -> bool,
) -> Option<(QueryId, QueryHandle)> {
self
.iter()
.find_map(|q| q.query_match(ids, filter))
}

fn queryable(&self) -> bool { true }
}

Expand Down
10 changes: 10 additions & 0 deletions core/src/builtin_widgets/theme.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,16 @@ impl<T: StateWriter<Value = Theme>> Query for ThemeQuerier<T> {
.map(QueryHandle::from_write_ref)
}

fn query_match(
&self, ids: &[QueryId], filter: &dyn Fn(&QueryId, &QueryHandle) -> bool,
) -> Option<(QueryId, QueryHandle)> {
ids.iter().find_map(|id| {
self
.query(id)
.filter(|h| filter(id, h))
.map(|h| (*id, h))
})
}
fn queryable(&self) -> bool { true }
}

Expand Down
15 changes: 15 additions & 0 deletions core/src/data_widget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,15 @@ impl Query for DataAttacher {
}

fn queryable(&self) -> bool { true }

fn query_match(
&self, ids: &[QueryId], filter: &dyn Fn(&QueryId, &QueryHandle) -> bool,
) -> Option<(QueryId, QueryHandle)> {
self
.render
.query_match(ids, filter)
.or_else(|| self.data.query_match(ids, filter))
}
}

impl Query for AnonymousAttacher {
Expand All @@ -74,6 +83,12 @@ impl Query for AnonymousAttacher {
}

fn queryable(&self) -> bool { self.render.queryable() }

fn query_match(
&self, ids: &[QueryId], filter: &dyn Fn(&QueryId, &QueryHandle) -> bool,
) -> Option<(QueryId, QueryHandle)> {
self.render.query_match(ids, filter)
}
}

impl RenderProxy for AnonymousAttacher {
Expand Down
15 changes: 15 additions & 0 deletions core/src/pipe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -820,6 +820,21 @@ impl Query for PipeNode {
self.as_ref().data.query_write(query_id)
}

fn query_match(
&self, ids: &[QueryId], filter: &dyn Fn(&QueryId, &QueryHandle) -> bool,
) -> Option<(QueryId, QueryHandle)> {
let p = self.as_ref();
p.data.query_match(ids, filter).or_else(|| {
let dyn_info_id = QueryId::of::<DynInfo>();
(ids.contains(&&dyn_info_id))
.then(|| {
let h = QueryHandle::new(&p.dyn_info);
filter(&dyn_info_id, &h).then_some((dyn_info_id, h))
})
.flatten()
})
}

fn queryable(&self) -> bool { true }
}

Expand Down
45 changes: 43 additions & 2 deletions core/src/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,16 @@ pub trait Query: Any {
fn query(&self, query_id: &QueryId) -> Option<QueryHandle>;

/// Queries the reference of the writer that matches the provided type id.
fn query_write(&self, _: &QueryId) -> Option<QueryHandle>;
fn query_write(&self, id: &QueryId) -> Option<QueryHandle>;

/// Query multiple types sequentially from inside to outside and return the
/// first one that matches the `filter` function.
///
/// It's not equivalent to query the id one by one, it query all types level
/// by level.
fn query_match(
&self, ids: &[QueryId], filter: &dyn Fn(&QueryId, &QueryHandle) -> bool,
) -> Option<(QueryId, QueryHandle)>;

/// Hint this is a non-queryable type.
fn queryable(&self) -> bool { true }
Expand Down Expand Up @@ -206,6 +215,17 @@ impl<T: Any> Query for Queryable<T> {

fn query_write(&self, _: &QueryId) -> Option<QueryHandle> { None }

fn query_match(
&self, ids: &[QueryId], filter: &dyn Fn(&QueryId, &QueryHandle) -> bool,
) -> Option<(QueryId, QueryHandle)> {
ids.iter().find_map(|id| {
self
.query(&id)
.filter(|h| filter(id, h))
.map(|h| (*id, h))
})
}

fn queryable(&self) -> bool { true }
}

Expand Down Expand Up @@ -243,6 +263,16 @@ where
}
}

fn query_match(
&self, ids: &[QueryId], filter: &dyn Fn(&QueryId, &QueryHandle) -> bool,
) -> Option<(QueryId, QueryHandle)> {
ids.iter().find_map(|id| {
self
.query(&id)
.filter(|h| filter(&id, h))
.map(|h| (*id, h))
})
}
fn queryable(&self) -> bool { true }
}

Expand All @@ -269,6 +299,17 @@ macro_rules! impl_query_for_reader {

fn query_write(&self, _: &QueryId) -> Option<QueryHandle> { None }

fn query_match(
&self, ids: &[QueryId], filter: &dyn Fn(&QueryId, &QueryHandle) -> bool,
) -> Option<(QueryId, QueryHandle)> {
ids.iter().find_map(|id| {
self
.query(id)
.filter(|h| filter(id, h))
.map(move |q| (*id, q))
})
}

fn queryable(&self) -> bool { true }
};
}
Expand Down Expand Up @@ -299,7 +340,7 @@ impl<V: Any> Query for Reader<V> {
/// for a secondary check as `TypeId` is not unique between binaries.
///
/// Retain the `TypeId` for efficient comparisons.
#[derive(Debug, Eq)]
#[derive(Debug, Eq, Clone, Copy)]
pub struct QueryId {
type_id: TypeId,
info: fn() -> TypeInfo,
Expand Down
6 changes: 6 additions & 0 deletions core/src/render_helper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ impl<R: Render> Query for PureRender<R> {

fn query_write(&self, _: &QueryId) -> Option<QueryHandle> { None }

fn query_match(
&self, _: &[QueryId], _: &dyn Fn(&QueryId, &QueryHandle) -> bool,
) -> Option<(QueryId, QueryHandle)> {
None
}

fn queryable(&self) -> bool { false }
}

Expand Down
6 changes: 6 additions & 0 deletions core/src/wrap_render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@ impl Query for RenderPair {
self.host.query_write(query_id)
}

fn query_match(
&self, ids: &[QueryId], filter: &dyn Fn(&QueryId, &QueryHandle) -> bool,
) -> Option<(QueryId, QueryHandle)> {
self.host.query_match(ids, filter)
}

fn queryable(&self) -> bool { self.host.queryable() }
}

Expand Down

0 comments on commit 4138df8

Please sign in to comment.