Skip to content

Instantly share code, notes, and snippets.

@ealmloff
Last active July 11, 2023 19:09
Show Gist options
  • Save ealmloff/74301fde7f6120d1b1b06adc7e6c40ea to your computer and use it in GitHub Desktop.
Save ealmloff/74301fde7f6120d1b1b06adc7e6c40ea to your computer and use it in GitHub Desktop.
#![allow(non_snake_case)]
use dioxus::prelude::*;
use std::cell::{Ref, RefCell, RefMut};
use std::collections::HashSet;
use std::fmt::Display;
use std::rc::Rc;
#[derive(Clone)]
struct SplitSubscriptions {
a: LocalSubscription<i32>,
b: LocalSubscription<i32>,
}
fn use_split_subscriptions_root(cx: &ScopeState) -> &SplitSubscriptions {
use_context_provider(cx, || {
let a = LocalSubscription::create(cx, 0);
let b = LocalSubscription::create(cx, 0);
SplitSubscriptions { a, b }
})
}
fn use_split_subscriptions(cx: &ScopeState) -> &SplitSubscriptions {
use_context(cx).expect("No SplitSubscriptions found")
}
fn main() {
dioxus_desktop::launch(app);
}
fn app(cx: Scope) -> Element {
println!("app rendered");
// This does not subscribe to the any state
let state = use_split_subscriptions_root(cx);
// This subscribes to just the state of `a`
let count = state.a.use_state(cx);
cx.render(rsx! {
h1 { "High-Five counter1: {count}" }
button { onclick: move |_| *count.write() += 1, "Up high!" }
button { onclick: move |_| *count.write() -= 1, "Down low!" }
Comp {}
})
}
fn Comp(cx: Scope) -> Element {
println!("comp rendered");
let state = use_split_subscriptions(cx);
let count = state.b.use_state(cx);
cx.render(rsx! {
h1 { "High-Five counter2: {count}" }
button { onclick: move |_| *count.write() += 1, "Up high!" }
button { onclick: move |_| *count.write() -= 1, "Down low!" }
})
}
#[derive(Clone)]
pub struct LocalSubscription<T> {
inner: Rc<RefCell<T>>,
subscribed: Rc<RefCell<HashSet<ScopeId>>>,
update: Rc<dyn Fn()>,
}
impl<T: 'static> LocalSubscription<T> {
pub fn create(cx: &ScopeState, inner: T) -> Self {
let inner = Rc::new(RefCell::new(inner));
let update_any = cx.schedule_update_any();
let subscribed: Rc<RefCell<HashSet<ScopeId>>> = Default::default();
let update = Rc::new({
to_owned![subscribed];
move || {
for id in subscribed.borrow().iter() {
update_any(*id);
}
}
});
Self {
inner,
subscribed,
update,
}
}
pub fn use_state<'a>(&self, cx: &'a ScopeState) -> &'a UseLocal<T> {
cx.use_hook(|| {
let id = cx.scope_id();
self.subscribed.borrow_mut().insert(id);
UseLocal {
inner: self.inner.clone(),
update: self.update.clone(),
}
})
}
pub fn use_write_only<'a>(&self, cx: &'a ScopeState) -> &'a LocalWrite<T> {
cx.use_hook(|| LocalWrite {
inner: self.inner.clone(),
update: self.update.clone(),
})
}
// This should only be used outside of components. This will not subscribe to any state.
pub fn write(&self) -> RefMut<T> {
(self.update)();
self.inner.borrow_mut()
}
// This should only be used outside of components. This will not subscribe to any state.
pub fn read(&self) -> Ref<T> {
self.inner.borrow()
}
}
/// A read/write version of the local state. This allows mutating the state and reading state.
pub struct UseLocal<T> {
inner: Rc<RefCell<T>>,
update: Rc<dyn Fn()>,
}
impl<T> UseLocal<T> {
pub fn read(&self) -> Ref<T> {
self.inner.borrow()
}
pub fn write(&self) -> RefMut<T> {
(self.update)();
self.inner.borrow_mut()
}
}
impl<T: Display> Display for UseLocal<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.inner.borrow().fmt(f)
}
}
/// A write only version of the local state. This only allows mutating the state, not reading state because you can only access the inner type in a impl Fn(&mut T) closure.
pub struct LocalWrite<T> {
inner: Rc<RefCell<T>>,
update: Rc<dyn Fn()>,
}
impl<T> LocalWrite<T> {
pub fn with_mut(&self, f: impl Fn(&mut T)) {
f(&mut *self.inner.borrow_mut());
(self.update)();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment