use crate::composer_context;
use crate::{Composer, ComposerCore, RecomposeScope};
use std::cell::RefCell;
use std::rc::Rc;
pub struct ParamState<T> {
pub(crate) value: Option<T>,
}
impl<T> ParamState<T> {
pub fn update(&mut self, new_value: &T) -> bool
where
T: PartialEq + Clone,
{
match self.value.as_mut() {
Some(old) if old == new_value => false,
Some(old) => {
old.clone_from(new_value);
true
}
None => {
self.value = Some(new_value.clone());
true
}
}
}
pub fn value(&self) -> Option<T>
where
T: Clone,
{
self.value.clone()
}
}
pub struct ParamSlot<T> {
val: RefCell<Option<T>>,
}
impl<T> Default for ParamSlot<T> {
fn default() -> Self {
Self {
val: RefCell::new(None),
}
}
}
impl<T> ParamSlot<T> {
pub fn set(&self, v: T) {
*self.val.borrow_mut() = Some(v);
}
pub fn take(&self) -> T {
self.val
.borrow_mut()
.take()
.expect("ParamSlot take() called before set")
}
}
type CallbackCell = Rc<RefCell<Option<Box<dyn FnMut()>>>>;
type CallbackScopeCell = Rc<RefCell<Option<RecomposeScope>>>;
struct CallbackScopeGuard {
core: Rc<ComposerCore>,
}
impl CallbackScopeGuard {
fn push(composer: &Composer, scope: RecomposeScope) -> Self {
composer.core.scope_stack.borrow_mut().push(scope);
Self {
core: composer.clone_core(),
}
}
}
impl Drop for CallbackScopeGuard {
fn drop(&mut self) {
self.core.scope_stack.borrow_mut().pop();
}
}
fn with_callback_scope<R>(scope: &CallbackScopeCell, f: impl FnOnce() -> R) -> R {
let captured_scope = scope.borrow().clone();
let mut f = Some(f);
if let Some(saved_scope) = captured_scope {
if let Some(result) = composer_context::try_with_composer(|composer| {
let _scope_guard = CallbackScopeGuard::push(composer, saved_scope);
f.take().expect("callback closure already taken")()
}) {
return result;
}
}
f.take().expect("callback closure already taken")()
}
fn callback_owner_scope(composer: &Composer) -> Option<RecomposeScope> {
composer.core.scope_stack.borrow().last().cloned()
}
#[derive(Clone)]
pub struct CallbackHolder {
rc: CallbackCell,
creator_scope: CallbackScopeCell,
}
impl CallbackHolder {
pub fn new() -> Self {
Self::default()
}
pub fn update<F>(&self, f: F)
where
F: FnMut() + 'static,
{
*self.rc.borrow_mut() = Some(Box::new(f));
*self.creator_scope.borrow_mut() =
composer_context::try_with_composer(callback_owner_scope).flatten();
}
pub fn clone_rc(&self) -> impl Fn() + 'static {
let rc = self.rc.clone();
let creator_scope = self.creator_scope.clone();
move || {
with_callback_scope(&creator_scope, || {
if let Some(callback) = rc.borrow_mut().as_mut() {
callback();
}
});
}
}
}
impl Default for CallbackHolder {
fn default() -> Self {
Self {
rc: Rc::new(RefCell::new(None)),
creator_scope: Rc::new(RefCell::new(None)),
}
}
}
#[derive(Clone)]
pub struct CallbackHolder1<A: 'static> {
#[allow(clippy::type_complexity)]
rc: Rc<RefCell<Option<Box<dyn FnMut(A)>>>>,
creator_scope: CallbackScopeCell,
}
impl<A: 'static> CallbackHolder1<A> {
pub fn new() -> Self {
Self::default()
}
pub fn update<F>(&self, f: F)
where
F: FnMut(A) + 'static,
{
*self.rc.borrow_mut() = Some(Box::new(f));
*self.creator_scope.borrow_mut() =
composer_context::try_with_composer(callback_owner_scope).flatten();
}
pub fn clone_rc(&self) -> impl Fn(A) + 'static {
let rc = self.rc.clone();
let creator_scope = self.creator_scope.clone();
move |arg| {
with_callback_scope(&creator_scope, || {
if let Some(callback) = rc.borrow_mut().as_mut() {
callback(arg);
}
});
}
}
}
impl<A: 'static> Default for CallbackHolder1<A> {
fn default() -> Self {
Self {
rc: Rc::new(RefCell::new(None)),
creator_scope: Rc::new(RefCell::new(None)),
}
}
}
pub struct ReturnSlot<T> {
value: Option<T>,
}
impl<T: Clone> ReturnSlot<T> {
pub fn store(&mut self, value: T) {
self.value = Some(value);
}
pub fn get(&self) -> Option<T> {
self.value.clone()
}
}
impl<T> Default for ParamState<T> {
fn default() -> Self {
Self { value: None }
}
}
impl<T> Default for ReturnSlot<T> {
fn default() -> Self {
Self { value: None }
}
}
#[cfg(test)]
mod callback_holder_tests {
use super::{CallbackHolder, CallbackHolder1};
use std::cell::Cell;
use std::rc::Rc;
#[test]
fn callback_holder_default_forwarder_is_noop() {
let forwarder = CallbackHolder::new().clone_rc();
forwarder();
}
#[test]
fn callback_holder_forwarder_uses_latest_callback() {
let holder = CallbackHolder::new();
let total = Rc::new(Cell::new(0));
let forwarder = holder.clone_rc();
let first_total = Rc::clone(&total);
holder.update(move || first_total.set(first_total.get() + 1));
forwarder();
let second_total = Rc::clone(&total);
holder.update(move || second_total.set(second_total.get() + 10));
forwarder();
assert_eq!(total.get(), 11);
}
#[test]
fn callback_holder1_default_forwarder_is_noop() {
let forwarder = CallbackHolder1::<i32>::new().clone_rc();
forwarder(7);
}
#[test]
fn callback_holder1_forwarder_uses_latest_callback() {
let holder = CallbackHolder1::<i32>::new();
let total = Rc::new(Cell::new(0));
let forwarder = holder.clone_rc();
let first_total = Rc::clone(&total);
holder.update(move |value| first_total.set(first_total.get() + value));
forwarder(2);
let second_total = Rc::clone(&total);
holder.update(move |value| second_total.set(second_total.get() + value * 5));
forwarder(3);
assert_eq!(total.get(), 17);
}
}