use gdk4::prelude::ActionExt;
use std::{cell::Cell, collections::HashMap, sync::Mutex};
use crate::print_perf;
mod action;
pub use action::*;
mod widgets;
pub use widgets::*;
pub struct Store<S: std::fmt::Debug + Clone + Default + PartialEq + Eq + 'static> {
state: S,
middlewares: Vec<Box<dyn Middleware<S>>>,
reducer: Box<dyn Fn(&Action, &mut S) + 'static>,
selectors: HashMap<SelectorId, Selector<S>>,
reduce_lock: Mutex<u8>,
reduce_depth: Cell<u64>,
}
impl<S: std::fmt::Debug + Clone + Default + PartialEq + Eq + 'static> std::fmt::Debug for Store<S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Store")
.field("state", &self.state)
.field("selectors", &self.selectors)
.finish()
}
}
#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct SelectorId(u128);
impl<S: std::fmt::Debug + Clone + Default + PartialEq + Eq> Store<S> {
pub fn new(
default_state: S,
reducer: impl Fn(&Action, &mut S) + 'static,
middlewares: Vec<Box<dyn Middleware<S>>>,
) -> Self {
Store {
state: default_state,
middlewares,
reducer: Box::new(reducer),
selectors: HashMap::default(),
reduce_lock: Default::default(),
reduce_depth: Cell::new(0),
}
}
fn select_internal(
&mut self,
name: &str,
selector: impl Fn(&S) -> S + 'static,
callback: impl Fn(&S) + 'static,
select_full_state: bool,
) -> u128 {
callback(&self.state);
let last_selector_state = selector(&self.state);
trace!("Adding selector '{}'.", name);
let id = uuid::Uuid::new_v4().as_u128();
self.selectors.insert(
SelectorId(id),
Selector::new(
name,
select_full_state,
selector,
last_selector_state,
callback,
),
);
id
}
pub fn select(
&mut self,
name: &str,
selector: impl Fn(&S) -> S + 'static,
callback: impl Fn(&S) + 'static,
) -> SelectorId {
let id;
print_perf!(
id = self.select_internal(name, selector, callback, false);
format!("Call selector {}", name)
);
SelectorId(id)
}
pub fn deselect(&mut self, id: SelectorId) {
if let Some(selector) = self.selectors.remove(&id) {
trace!("deselect {:?} {}", id, selector.name);
}
}
pub fn select_full(
&mut self,
name: &str,
selector: impl Fn(&S) -> S + 'static,
callback: impl Fn(&S) + 'static,
) -> SelectorId {
let id;
print_perf!(
id = self.select_internal(name, selector, callback, true);
format!("Call full state selector {}", name)
);
SelectorId(id)
}
pub fn dispatch(&mut self, action: String, argument: Option<glib::Variant>) {
self.reduce_depth.set(self.reduce_depth.take() + 1);
let message = format!("Dispatch action {:?} with argument {:?}", action, argument);
print_perf! (
{
let action = Action::new(action, argument);
trace!(
"Reduce action {:?} for state.",
action.name().to_string(),
);
print_perf!(
for middleware in &self.middlewares {
middleware.pre_reduce(&action, &self.state);
};
format!("Call {} middlewares pre_reduce", self.middlewares.len())
);
{
let lock = self.reduce_lock.try_lock();
if lock.is_err() {
error!("Can not dispatch '{}' during reduce", action.name());
return;
}
let reducer = &self.reducer;
print_perf!(
reducer(&action, &mut self.state);
format!("Reduce action {:?}", action.name())
);
trace!("Reduced to: {:?}", &self.state);
drop(lock);
print_perf!(
{
for (_sid, selector) in self.selectors.iter_mut() {
let sel = &selector.selector;
let selected_state = sel(&self.state);
if selector.last_state != selected_state {
let callb = &selector.callback;
if selector.full {
callb(&self.state);
} else {
callb(&selected_state);
}
selector.last_state = selected_state;
}
}
};
format!("Determine n of {} selectors for action {:?}", self.selectors.len(), action.name())
);
}
print_perf!(
for middleware in &self.middlewares {
middleware.post_reduce(&action, &self.state);
};
format!("Call {} middlewares post_reduce", self.middlewares.len())
);
};
message
);
self.reduce_depth.set(self.reduce_depth.take() - 1);
}
pub fn is_last_reduce_phase(&self) -> bool {
self.reduce_depth.get() == 1
}
pub fn delegate(
&'static mut self,
) -> glib::Sender<(gdk4::gio::SimpleAction, Option<glib::Variant>)> {
let (sender, receiver) = glib::MainContext::channel::<(
gdk4::gio::SimpleAction,
Option<glib::Variant>,
)>(glib::PRIORITY_HIGH);
receiver.attach(None, move |(action, argument)| {
debug!("Delegate gtk action {:?} {:?}.", action, argument);
self.dispatch(action.name().to_string(), argument);
glib::Continue(true)
});
sender
}
}
struct Selector<S: std::fmt::Debug + Clone + Default + 'static> {
name: String,
full: bool,
selector: Box<dyn Fn(&S) -> S + 'static>,
last_state: S,
callback: Box<dyn Fn(&S) + 'static>,
}
impl<S: std::fmt::Debug + Clone + Default + 'static> Selector<S> {
fn new(
name: &str,
full: bool,
selector: impl Fn(&S) -> S + 'static,
last_state: S,
callback: impl Fn(&S) + 'static,
) -> Self {
Selector {
name: name.to_string(),
full,
selector: Box::new(selector),
last_state,
callback: Box::new(callback),
}
}
}
impl<S: std::fmt::Debug + Clone + Default + 'static> std::fmt::Debug for Selector<S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Selector")
.field("name", &self.name)
.finish()
}
}
pub trait Middleware<S: std::fmt::Debug + Clone + Default + PartialEq + Eq + 'static> {
fn pre_reduce(&self, _: &Action, _: &S) {}
fn post_reduce(&self, _: &Action, _: &S) {}
}
#[macro_export]
macro_rules! dispatch {
(
$action:expr $(, $argument:expr)?
) => {{
let action = $action;
let mut argument = None;
$(
argument = Some(glib::ToVariant::to_variant(&$argument));
)?
store().dispatch(action.to_string(), argument);
}};
}
#[macro_export]
macro_rules! select_state {
(
$name:expr, $selector:expr, $callback:expr
) => {
store().select($name, $selector, $callback);
};
}
#[macro_export]
macro_rules! select {
(
|$state:ident| $($selector:expr),*$(,)? => $callback:expr
) => {
#[allow(clippy::redundant_closure_call)]
store().select(
vec![
$(
stringify!($selector)
),*
].join(", ").as_str(),
|s| {
let mut $state: State = Default::default();
$(
let sel = |$state: &State| $selector.clone();
$selector = sel(s);
)*
$state
},
$callback,
);
};
}
#[macro_export]
macro_rules! store {
(
$state:ty
) => {
pub type Store = gstore::Store<$state>;
static mut STORE: Option<Store> = None;
pub fn init_store(
default_state: $state,
reducer: impl Fn(&gstore::Action, &mut $state) + 'static,
middlewares: Vec<Box<dyn gstore::Middleware<$state>>>,
) {
unsafe {
STORE = Some(Store::new(default_state, reducer, middlewares));
STORE.as_mut().unwrap().dispatch(gstore::INIT.into(), None);
}
}
pub fn store() -> &'static mut Store {
unsafe { STORE.as_mut().expect("Store is not initialized!") }
}
};
}