use super::{Component, RenderContext};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum NavigationMode {
#[default]
Push,
Replace,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum RouterMessage<S: Clone + PartialEq> {
Navigate(S),
NavigateWith(S, NavigationMode),
Replace(S),
Back,
ClearHistory,
Reset(S),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum RouterOutput<S: Clone + PartialEq> {
ScreenChanged {
from: S,
to: S,
},
NavigatedBack {
to: S,
},
NoPreviousScreen,
Reset(S),
HistoryCleared,
}
#[derive(Clone, Debug)]
#[cfg_attr(
feature = "serialization",
derive(serde::Serialize, serde::Deserialize)
)]
pub struct RouterState<S: Clone + PartialEq> {
current: S,
history: Vec<S>,
max_history: usize,
}
impl<S: Default + Clone + PartialEq> Default for RouterState<S> {
fn default() -> Self {
Self::new(S::default())
}
}
impl<S: Clone + PartialEq> PartialEq for RouterState<S> {
fn eq(&self, other: &Self) -> bool {
self.current == other.current
&& self.history == other.history
&& self.max_history == other.max_history
}
}
impl<S: Clone + PartialEq> RouterState<S> {
pub fn new(initial: S) -> Self {
Self {
current: initial,
history: Vec::new(),
max_history: 0,
}
}
pub fn with_max_history(mut self, max: usize) -> Self {
self.max_history = max;
self
}
pub fn current(&self) -> &S {
&self.current
}
pub fn can_go_back(&self) -> bool {
!self.history.is_empty()
}
pub fn history_len(&self) -> usize {
self.history.len()
}
pub fn history(&self) -> &[S] {
&self.history
}
pub fn max_history(&self) -> usize {
self.max_history
}
pub fn set_max_history(&mut self, max: usize) {
self.max_history = max;
self.enforce_history_limit();
}
pub fn previous(&self) -> Option<&S> {
self.history.last()
}
pub fn is_at(&self, screen: &S) -> bool {
&self.current == screen
}
pub fn clear_history(&mut self) {
self.history.clear();
}
fn enforce_history_limit(&mut self) {
if self.max_history > 0 && self.history.len() > self.max_history {
let excess = self.history.len() - self.max_history;
self.history.drain(0..excess);
}
}
}
pub struct Router<S: Clone + PartialEq>(std::marker::PhantomData<S>);
impl<S: Clone + PartialEq> Router<S> {
pub fn update(state: &mut RouterState<S>, msg: RouterMessage<S>) -> Option<RouterOutput<S>> {
match msg {
RouterMessage::Navigate(screen) => {
if state.current == screen {
return None; }
let from = state.current.clone();
state.history.push(state.current.clone());
state.current = screen.clone();
state.enforce_history_limit();
Some(RouterOutput::ScreenChanged { from, to: screen })
}
RouterMessage::NavigateWith(screen, mode) => match mode {
NavigationMode::Push => Self::update(state, RouterMessage::Navigate(screen)),
NavigationMode::Replace => Self::update(state, RouterMessage::Replace(screen)),
},
RouterMessage::Replace(screen) => {
if state.current == screen {
return None;
}
let from = state.current.clone();
state.current = screen.clone();
Some(RouterOutput::ScreenChanged { from, to: screen })
}
RouterMessage::Back => {
if let Some(previous) = state.history.pop() {
state.current = previous.clone();
Some(RouterOutput::NavigatedBack { to: previous })
} else {
Some(RouterOutput::NoPreviousScreen)
}
}
RouterMessage::ClearHistory => {
if state.history.is_empty() {
None
} else {
state.history.clear();
Some(RouterOutput::HistoryCleared)
}
}
RouterMessage::Reset(screen) => {
state.history.clear();
state.current = screen.clone();
Some(RouterOutput::Reset(screen))
}
}
}
pub fn view(_state: &RouterState<S>, _ctx: &mut RenderContext<'_, '_>) {
}
}
impl<S: Clone + PartialEq + Default> Component for Router<S> {
type State = RouterState<S>;
type Message = RouterMessage<S>;
type Output = RouterOutput<S>;
fn init() -> Self::State {
RouterState::new(S::default())
}
fn update(state: &mut Self::State, msg: Self::Message) -> Option<Self::Output> {
Router::update(state, msg)
}
fn view(state: &Self::State, ctx: &mut RenderContext<'_, '_>) {
Router::view(state, ctx)
}
}
#[cfg(test)]
mod snapshot_tests;
#[cfg(test)]
mod tests;