orchestrator
This commit is contained in:
75
examples/simple_usage.rs
Normal file
75
examples/simple_usage.rs
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
extern crate alloc;
|
||||||
|
|
||||||
|
use tui_orchestrator::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
enum Focus {
|
||||||
|
Username,
|
||||||
|
Password,
|
||||||
|
LoginButton,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FocusId for Focus {}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
enum AppEvent {
|
||||||
|
LoginAttempt {
|
||||||
|
username: alloc::string::String,
|
||||||
|
password: alloc::string::String,
|
||||||
|
},
|
||||||
|
Quit,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct LoginPage {
|
||||||
|
username: alloc::string::String,
|
||||||
|
password: alloc::string::String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for LoginPage {
|
||||||
|
type Focus = Focus;
|
||||||
|
type Action = ComponentAction;
|
||||||
|
type Event = AppEvent;
|
||||||
|
|
||||||
|
fn targets(&self) -> &[Self::Focus] {
|
||||||
|
&[Focus::Username, Focus::Password, Focus::LoginButton]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle(
|
||||||
|
&mut self,
|
||||||
|
focus: &Self::Focus,
|
||||||
|
action: Self::Action,
|
||||||
|
) -> Result<Option<Self::Event>, ComponentError> {
|
||||||
|
match (focus, action) {
|
||||||
|
(Focus::LoginButton, ComponentAction::Select) => Ok(Some(AppEvent::LoginAttempt {
|
||||||
|
username: self.username.clone(),
|
||||||
|
password: self.password.clone(),
|
||||||
|
})),
|
||||||
|
(Focus::Username, ComponentAction::TypeChar(c)) => {
|
||||||
|
self.username.push(c);
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
(Focus::Password, ComponentAction::TypeChar(c)) => {
|
||||||
|
self.password.push(c);
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
_ => Ok(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<(), ComponentError> {
|
||||||
|
let mut orch = Orchestrator::new();
|
||||||
|
|
||||||
|
orch.bind(Key::enter(), ComponentAction::Select);
|
||||||
|
orch.bind(Key::tab(), ComponentAction::Next);
|
||||||
|
orch.bind(Key::shift_tab(), ComponentAction::Prev);
|
||||||
|
|
||||||
|
let login_page = LoginPage {
|
||||||
|
username: alloc::string::String::new(),
|
||||||
|
password: alloc::string::String::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
orch.register_page(alloc::string::String::from("login"), login_page);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum ComponentError {
|
pub enum ComponentError {
|
||||||
EmptyTargets,
|
InvalidAction,
|
||||||
InvalidFocus,
|
InvalidFocus,
|
||||||
|
NoComponent,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
use super::action::{Action, ComponentAction};
|
use super::action::{Action, ComponentAction};
|
||||||
use super::key::Key;
|
use super::key::Key;
|
||||||
|
|
||||||
#[cfg(feature = "alloc")]
|
|
||||||
use hashbrown::HashSet;
|
|
||||||
|
|
||||||
/// Maps keys to actions.
|
/// Maps keys to actions.
|
||||||
///
|
///
|
||||||
/// When `alloc` feature is enabled, uses HashMap for O(1) lookup.
|
/// When `alloc` feature is enabled, uses HashMap for O(1) lookup.
|
||||||
@@ -96,6 +93,11 @@ impl<A: Action> Bindings<A> {
|
|||||||
pub fn iter(&self) -> impl Iterator<Item = &(Key, A)> {
|
pub fn iter(&self) -> impl Iterator<Item = &(Key, A)> {
|
||||||
self.bindings.iter()
|
self.bindings.iter()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
pub fn iter(&self) -> impl Iterator<Item = (&Key, &A)> {
|
||||||
|
self.bindings.iter()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<A: Action> Default for Bindings<A> {
|
impl<A: Action> Default for Bindings<A> {
|
||||||
|
|||||||
@@ -5,9 +5,15 @@ extern crate alloc;
|
|||||||
pub mod component;
|
pub mod component;
|
||||||
pub mod focus;
|
pub mod focus;
|
||||||
pub mod input;
|
pub mod input;
|
||||||
|
pub mod orchestrator;
|
||||||
|
|
||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
pub use crate::component::{Component, ComponentAction, ComponentError};
|
pub use crate::component::{Component, ComponentAction, ComponentError};
|
||||||
pub use crate::focus::{FocusError, FocusId, FocusManager, FocusQuery, Focusable};
|
pub use crate::focus::{FocusError, FocusId, FocusManager, FocusQuery, Focusable};
|
||||||
pub use crate::input::{Action, Bindings, Key, KeyCode, KeyModifiers, MatchResult};
|
pub use crate::input::{Action, Bindings, Key, KeyCode, KeyModifiers, MatchResult};
|
||||||
|
pub use crate::orchestrator::{
|
||||||
|
ActionResolver, CommandHandler, CommandResult, DefaultActionResolver,
|
||||||
|
DefaultCommandHandler, DefaultStateCoordinator, EventBus, EventHandler, ModeName,
|
||||||
|
ModeStack, Orchestrator, Router, RouterEvent, StateCoordinator, StateSync,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
21
src/orchestrator/action_resolver.rs
Normal file
21
src/orchestrator/action_resolver.rs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
// path_from_the_root: src/orchestrator/action_resolver.rs
|
||||||
|
|
||||||
|
use crate::component::Component;
|
||||||
|
|
||||||
|
pub struct ResolveContext<'a, C: Component> {
|
||||||
|
pub component: &'a C,
|
||||||
|
pub focus: &'a C::Focus,
|
||||||
|
pub action: C::Action,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ActionResolver<C: Component> {
|
||||||
|
fn resolve(&mut self, ctx: ResolveContext<C>) -> C::Action;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DefaultActionResolver;
|
||||||
|
|
||||||
|
impl<C: Component> ActionResolver<C> for DefaultActionResolver {
|
||||||
|
fn resolve(&mut self, ctx: ResolveContext<C>) -> C::Action {
|
||||||
|
ctx.action
|
||||||
|
}
|
||||||
|
}
|
||||||
47
src/orchestrator/command_handler.rs
Normal file
47
src/orchestrator/command_handler.rs
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
// path_from_the_root: src/orchestrator/command_handler.rs
|
||||||
|
|
||||||
|
use crate::input::Key;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub enum CommandResult<A> {
|
||||||
|
Resolved(A),
|
||||||
|
Incomplete,
|
||||||
|
Unknown,
|
||||||
|
Exit,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait CommandHandler<A: Clone> {
|
||||||
|
fn is_active(&self) -> bool;
|
||||||
|
|
||||||
|
fn handle(&mut self, key: Key) -> CommandResult<A>;
|
||||||
|
|
||||||
|
fn enter(&mut self);
|
||||||
|
|
||||||
|
fn exit(&mut self);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DefaultCommandHandler<A: Clone> {
|
||||||
|
_phantom: core::marker::PhantomData<A>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A: Clone> Default for DefaultCommandHandler<A> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
_phantom: core::marker::PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A: Clone> CommandHandler<A> for DefaultCommandHandler<A> {
|
||||||
|
fn is_active(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle(&mut self, _key: Key) -> CommandResult<A> {
|
||||||
|
CommandResult::Exit
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enter(&mut self) {}
|
||||||
|
|
||||||
|
fn exit(&mut self) {}
|
||||||
|
}
|
||||||
231
src/orchestrator/core.rs
Normal file
231
src/orchestrator/core.rs
Normal file
@@ -0,0 +1,231 @@
|
|||||||
|
// path_from_the_root: src/orchestrator/core.rs
|
||||||
|
|
||||||
|
extern crate alloc;
|
||||||
|
|
||||||
|
use alloc::boxed::Box;
|
||||||
|
use alloc::string::String;
|
||||||
|
use alloc::vec::Vec;
|
||||||
|
|
||||||
|
use crate::component::{Component, ComponentError};
|
||||||
|
use crate::input::{Bindings, Key};
|
||||||
|
use crate::orchestrator::{
|
||||||
|
ActionResolver, CommandHandler, CommandResult, DefaultActionResolver, DefaultCommandHandler,
|
||||||
|
DefaultStateCoordinator, EventBus, ModeName, ModeStack, ResolveContext, Router, RouterEvent,
|
||||||
|
StateCoordinator,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct Orchestrator<C: Component> {
|
||||||
|
router: Router<C>,
|
||||||
|
bindings: Bindings<C::Action>,
|
||||||
|
action_resolver: Box<dyn ActionResolver<C>>,
|
||||||
|
command_handler: Box<dyn CommandHandler<C::Action>>,
|
||||||
|
state_coordinator: Box<dyn StateCoordinator<C>>,
|
||||||
|
modes: ModeStack,
|
||||||
|
event_bus: EventBus<C::Event>,
|
||||||
|
running: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C: Component> Default for Orchestrator<C>
|
||||||
|
where
|
||||||
|
C::Action: Clone + 'static,
|
||||||
|
{
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C: Component> Orchestrator<C>
|
||||||
|
where
|
||||||
|
C::Action: Clone + 'static,
|
||||||
|
C::Event: Clone,
|
||||||
|
{
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
router: Router::new(),
|
||||||
|
bindings: Bindings::new(),
|
||||||
|
action_resolver: Box::new(DefaultActionResolver),
|
||||||
|
command_handler: Box::new(DefaultCommandHandler::default()),
|
||||||
|
state_coordinator: Box::new(DefaultStateCoordinator),
|
||||||
|
modes: ModeStack::new(),
|
||||||
|
event_bus: EventBus::new(),
|
||||||
|
running: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn register_page(&mut self, id: String, page: C) {
|
||||||
|
self.router.register(id, page);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn navigate_to(&mut self, id: String) -> Result<(), ComponentError> {
|
||||||
|
if let Some(RouterEvent::Navigated { from, to }) = self
|
||||||
|
.router
|
||||||
|
.navigate(id.clone())
|
||||||
|
.map_err(|_| ComponentError::InvalidFocus)?
|
||||||
|
{
|
||||||
|
let _ = self.state_coordinator.on_navigate(from, to);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn back(&mut self) -> Result<(), ComponentError> {
|
||||||
|
if let Some(RouterEvent::Back { to }) = self
|
||||||
|
.router
|
||||||
|
.back()
|
||||||
|
.map_err(|_| ComponentError::InvalidFocus)?
|
||||||
|
{
|
||||||
|
let _ = self.state_coordinator.on_navigate(Some(to.clone()), to);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn forward(&mut self) -> Result<(), ComponentError> {
|
||||||
|
if let Some(RouterEvent::Forward { to }) = self
|
||||||
|
.router
|
||||||
|
.forward()
|
||||||
|
.map_err(|_| ComponentError::InvalidFocus)?
|
||||||
|
{
|
||||||
|
let _ = self.state_coordinator.on_navigate(Some(to.clone()), to);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bind(&mut self, key: Key, action: C::Action) {
|
||||||
|
self.bindings.bind(key, action);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn process_frame(&mut self, key: Key) -> Result<Vec<C::Event>, ComponentError> {
|
||||||
|
if !self.running {
|
||||||
|
return Ok(Vec::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut events = Vec::new();
|
||||||
|
|
||||||
|
if self.command_handler.is_active() {
|
||||||
|
match self.command_handler.handle(key) {
|
||||||
|
CommandResult::Resolved(action) => {
|
||||||
|
if let Some(event) = self.handle_action(action)? {
|
||||||
|
events.push(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CommandResult::Incomplete | CommandResult::Unknown => {}
|
||||||
|
CommandResult::Exit => {
|
||||||
|
self.command_handler.exit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if let Some(action) = self.bindings.get(&key) {
|
||||||
|
let action = action.clone();
|
||||||
|
|
||||||
|
if let Some(_) = self.router.current_id() {
|
||||||
|
let component = self.router.current().ok_or(ComponentError::NoComponent)?;
|
||||||
|
let focus = self.router.focus_manager().current().ok_or(ComponentError::NoComponent)?;
|
||||||
|
|
||||||
|
let ctx = ResolveContext {
|
||||||
|
component,
|
||||||
|
focus,
|
||||||
|
action,
|
||||||
|
};
|
||||||
|
let resolved_action = self.action_resolver.resolve(ctx);
|
||||||
|
|
||||||
|
if let Some(event) = self.handle_action(resolved_action)? {
|
||||||
|
events.push(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for event in &events {
|
||||||
|
self.event_bus.emit(event.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(events)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_action(&mut self, action: C::Action) -> Result<Option<C::Event>, ComponentError> {
|
||||||
|
let focus = self.router.focus_manager().current().cloned();
|
||||||
|
|
||||||
|
if let Some(focus) = focus {
|
||||||
|
let old_focus = self.router.focus_manager().current().cloned();
|
||||||
|
|
||||||
|
let result = {
|
||||||
|
let component = self.router.current_mut().ok_or(ComponentError::NoComponent)?;
|
||||||
|
component.handle(&focus, action)?
|
||||||
|
};
|
||||||
|
|
||||||
|
let new_focus = self.router.focus_manager().current().cloned();
|
||||||
|
|
||||||
|
if old_focus != new_focus {
|
||||||
|
let _ = self.state_coordinator.on_focus_change(old_focus, new_focus);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn current(&self) -> Option<&C> {
|
||||||
|
self.router.current()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn current_mut(&mut self) -> Option<&mut C> {
|
||||||
|
self.router.current_mut()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn focus_manager(&self) -> &crate::focus::FocusManager<C::Focus> {
|
||||||
|
self.router.focus_manager()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn focus_manager_mut(&mut self) -> &mut crate::focus::FocusManager<C::Focus> {
|
||||||
|
self.router.focus_manager_mut()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn modes(&self) -> &ModeStack {
|
||||||
|
&self.modes
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn modes_mut(&mut self) -> &mut ModeStack {
|
||||||
|
&mut self.modes
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_mode(&mut self, mode: ModeName) {
|
||||||
|
self.modes.push(mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pop_mode(&mut self) -> Option<ModeName> {
|
||||||
|
self.modes.pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn event_bus(&self) -> &EventBus<C::Event> {
|
||||||
|
&self.event_bus
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn event_bus_mut(&mut self) -> &mut EventBus<C::Event> {
|
||||||
|
&mut self.event_bus
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_action_resolver<R: ActionResolver<C> + 'static>(&mut self, resolver: R) {
|
||||||
|
self.action_resolver = Box::new(resolver);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_command_handler<H: CommandHandler<C::Action> + 'static>(
|
||||||
|
&mut self,
|
||||||
|
handler: H,
|
||||||
|
) {
|
||||||
|
self.command_handler = Box::new(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_state_coordinator<S: StateCoordinator<C> + 'static>(&mut self, coordinator: S) {
|
||||||
|
self.state_coordinator = Box::new(coordinator);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_running(&self) -> bool {
|
||||||
|
self.running
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stop(&mut self) {
|
||||||
|
self.running = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn start(&mut self) {
|
||||||
|
self.running = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
46
src/orchestrator/event_bus.rs
Normal file
46
src/orchestrator/event_bus.rs
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
// path_from_the_root: src/orchestrator/event_bus.rs
|
||||||
|
|
||||||
|
extern crate alloc;
|
||||||
|
|
||||||
|
use alloc::boxed::Box;
|
||||||
|
use alloc::vec::Vec;
|
||||||
|
|
||||||
|
pub trait EventHandler<E> {
|
||||||
|
fn handle(&mut self, event: E);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct EventBus<E: Clone> {
|
||||||
|
handlers: Vec<Box<dyn EventHandler<E>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: Clone> Default for EventBus<E> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: Clone> EventBus<E> {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
handlers: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn register(&mut self, handler: Box<dyn EventHandler<E>>) {
|
||||||
|
self.handlers.push(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn emit(&mut self, event: E) {
|
||||||
|
for handler in &mut self.handlers {
|
||||||
|
handler.handle(event.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handler_count(&self) -> usize {
|
||||||
|
self.handlers.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.handlers.is_empty()
|
||||||
|
}
|
||||||
|
}
|
||||||
17
src/orchestrator/mod.rs
Normal file
17
src/orchestrator/mod.rs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
// path_from_the_root: src/orchestrator/mod.rs
|
||||||
|
|
||||||
|
pub mod action_resolver;
|
||||||
|
pub mod command_handler;
|
||||||
|
pub mod core;
|
||||||
|
pub mod event_bus;
|
||||||
|
pub mod mode;
|
||||||
|
pub mod router;
|
||||||
|
pub mod state_coordinator;
|
||||||
|
|
||||||
|
pub use action_resolver::{ActionResolver, DefaultActionResolver, ResolveContext};
|
||||||
|
pub use command_handler::{CommandHandler, CommandResult, DefaultCommandHandler};
|
||||||
|
pub use core::Orchestrator;
|
||||||
|
pub use event_bus::{EventBus, EventHandler};
|
||||||
|
pub use mode::{ModeName, ModeStack};
|
||||||
|
pub use router::{Router, RouterEvent};
|
||||||
|
pub use state_coordinator::{DefaultStateCoordinator, StateCoordinator, StateSync};
|
||||||
83
src/orchestrator/mode.rs
Normal file
83
src/orchestrator/mode.rs
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
// path_from_the_root: src/orchestrator/mode.rs
|
||||||
|
|
||||||
|
use alloc::collections::BTreeMap;
|
||||||
|
use alloc::string::String;
|
||||||
|
use alloc::vec::Vec;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub enum ModeName {
|
||||||
|
General,
|
||||||
|
Navigation,
|
||||||
|
Editing,
|
||||||
|
Command,
|
||||||
|
Custom(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
pub struct ModeStack {
|
||||||
|
modes: Vec<ModeName>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ModeStack {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self { modes: Vec::new() }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push(&mut self, mode: ModeName) {
|
||||||
|
self.modes.push(mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pop(&mut self) -> Option<ModeName> {
|
||||||
|
self.modes.pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn current(&self) -> Option<&ModeName> {
|
||||||
|
self.modes.last()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.modes.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.modes.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn contains(&self, mode: &ModeName) -> bool {
|
||||||
|
self.modes.contains(mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear(&mut self) {
|
||||||
|
self.modes.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset(&mut self, mode: ModeName) {
|
||||||
|
self.modes.clear();
|
||||||
|
self.modes.push(mode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
pub struct ModeResolver {
|
||||||
|
mappings: BTreeMap<String, Vec<ModeName>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ModeResolver {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
mappings: BTreeMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn register(&mut self, key: String, modes: Vec<ModeName>) {
|
||||||
|
self.mappings.insert(key, modes);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resolve(&self, key: &str) -> Option<&Vec<ModeName>> {
|
||||||
|
self.mappings.get(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.mappings.is_empty()
|
||||||
|
}
|
||||||
|
}
|
||||||
256
src/orchestrator/router.rs
Normal file
256
src/orchestrator/router.rs
Normal file
@@ -0,0 +1,256 @@
|
|||||||
|
// path_from_the_root: src/orchestrator/router.rs
|
||||||
|
|
||||||
|
extern crate alloc;
|
||||||
|
|
||||||
|
use alloc::string::String;
|
||||||
|
use alloc::vec::Vec;
|
||||||
|
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
use hashbrown::HashMap;
|
||||||
|
|
||||||
|
use crate::component::Component;
|
||||||
|
use crate::focus::{FocusError, FocusManager};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum RouterEvent {
|
||||||
|
Navigated { from: Option<String>, to: String },
|
||||||
|
Back { to: String },
|
||||||
|
Forward { to: String },
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Router<C: Component> {
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
pages: HashMap<String, C>,
|
||||||
|
#[cfg(not(feature = "alloc"))]
|
||||||
|
pages: Vec<(String, C)>,
|
||||||
|
current: Option<String>,
|
||||||
|
history: Vec<String>,
|
||||||
|
future: Vec<String>,
|
||||||
|
focus: FocusManager<C::Focus>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C: Component> Default for Router<C> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C: Component> Router<C> {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
pages: HashMap::new(),
|
||||||
|
#[cfg(not(feature = "alloc"))]
|
||||||
|
pages: Vec::new(),
|
||||||
|
current: None,
|
||||||
|
history: Vec::new(),
|
||||||
|
future: Vec::new(),
|
||||||
|
focus: FocusManager::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn register(&mut self, id: String, mut page: C) {
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
{
|
||||||
|
if self.current.as_ref() == Some(&id) {
|
||||||
|
let _ = page.on_enter();
|
||||||
|
let targets = page.targets();
|
||||||
|
self.focus.set_targets(targets.to_vec());
|
||||||
|
}
|
||||||
|
self.pages.insert(id, page);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "alloc"))]
|
||||||
|
{
|
||||||
|
if self.current.as_ref() == Some(&id) {
|
||||||
|
let _ = page.on_enter();
|
||||||
|
let targets = page.targets();
|
||||||
|
self.focus.set_targets(targets.to_vec());
|
||||||
|
}
|
||||||
|
self.pages.push((id, page));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_page_mut(&mut self, id: &str) -> Option<&mut C> {
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
{
|
||||||
|
self.pages.get_mut(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "alloc"))]
|
||||||
|
{
|
||||||
|
self.pages
|
||||||
|
.iter_mut()
|
||||||
|
.find(|(page_id, _)| page_id == id)
|
||||||
|
.map(|(_, page)| page)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_page(&self, id: &str) -> Option<&C> {
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
{
|
||||||
|
self.pages.get(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "alloc"))]
|
||||||
|
{
|
||||||
|
self.pages
|
||||||
|
.iter()
|
||||||
|
.find(|(page_id, _)| page_id == id)
|
||||||
|
.map(|(_, page)| page)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn navigate(&mut self, id: String) -> Result<Option<RouterEvent>, FocusError> {
|
||||||
|
let result = if let Some(current_id) = self.current.take() {
|
||||||
|
if let Some(current_page) = self.get_page_mut(¤t_id) {
|
||||||
|
let _ = current_page.on_exit();
|
||||||
|
}
|
||||||
|
self.history.push(current_id.clone());
|
||||||
|
self.future.clear();
|
||||||
|
Some(RouterEvent::Navigated {
|
||||||
|
from: Some(current_id),
|
||||||
|
to: id.clone(),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Some(RouterEvent::Navigated {
|
||||||
|
from: None,
|
||||||
|
to: id.clone(),
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
let targets = if let Some(page) = self.get_page_mut(&id) {
|
||||||
|
let _ = page.on_enter();
|
||||||
|
Some(page.targets().to_vec())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(targets) = targets {
|
||||||
|
self.focus.set_targets(targets);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.current = Some(id);
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn back(&mut self) -> Result<Option<RouterEvent>, FocusError> {
|
||||||
|
if let Some(current) = self.current.take() {
|
||||||
|
if let Some(from) = self.history.pop() {
|
||||||
|
self.future.push(current.clone());
|
||||||
|
let to = from.clone();
|
||||||
|
|
||||||
|
if let Some(current_page) = self.get_page_mut(¤t) {
|
||||||
|
let _ = current_page.on_exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.current = Some(to.clone());
|
||||||
|
|
||||||
|
let targets = if let Some(page) = self.get_page_mut(&to) {
|
||||||
|
let _ = page.on_enter();
|
||||||
|
Some(page.targets().to_vec())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(targets) = targets {
|
||||||
|
self.focus.set_targets(targets);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Some(RouterEvent::Back { to }))
|
||||||
|
} else {
|
||||||
|
self.current = Some(current);
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn forward(&mut self) -> Result<Option<RouterEvent>, FocusError> {
|
||||||
|
if let Some(current) = self.current.take() {
|
||||||
|
if let Some(from) = self.future.pop() {
|
||||||
|
self.history.push(current.clone());
|
||||||
|
let to = from.clone();
|
||||||
|
|
||||||
|
if let Some(current_page) = self.get_page_mut(¤t) {
|
||||||
|
let _ = current_page.on_exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.current = Some(to.clone());
|
||||||
|
|
||||||
|
let targets = if let Some(page) = self.get_page_mut(&to) {
|
||||||
|
let _ = page.on_enter();
|
||||||
|
Some(page.targets().to_vec())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(targets) = targets {
|
||||||
|
self.focus.set_targets(targets);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Some(RouterEvent::Forward { to }))
|
||||||
|
} else {
|
||||||
|
self.current = Some(current);
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn current(&self) -> Option<&C> {
|
||||||
|
self.current.as_ref().and_then(|id| self.get_page(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn current_mut(&mut self) -> Option<&mut C> {
|
||||||
|
if let Some(id) = self.current.clone() {
|
||||||
|
self.get_page_mut(&id)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn current_id(&self) -> Option<&String> {
|
||||||
|
self.current.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn focus_manager(&self) -> &FocusManager<C::Focus> {
|
||||||
|
&self.focus
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn focus_manager_mut(&mut self) -> &mut FocusManager<C::Focus> {
|
||||||
|
&mut self.focus
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn history(&self) -> &[String] {
|
||||||
|
&self.history
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn future(&self) -> &[String] {
|
||||||
|
&self.future
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn page_count(&self) -> usize {
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
{
|
||||||
|
self.pages.len()
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "alloc"))]
|
||||||
|
{
|
||||||
|
self.pages.len()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn has_page(&self, id: &str) -> bool {
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
{
|
||||||
|
self.pages.contains_key(id)
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "alloc"))]
|
||||||
|
{
|
||||||
|
self.pages.iter().any(|(page_id, _)| page_id == id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
48
src/orchestrator/state_coordinator.rs
Normal file
48
src/orchestrator/state_coordinator.rs
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
// path_from_the_root: src/orchestrator/state_coordinator.rs
|
||||||
|
|
||||||
|
use alloc::string::String;
|
||||||
|
use alloc::vec::Vec;
|
||||||
|
|
||||||
|
use crate::component::Component;
|
||||||
|
|
||||||
|
pub enum StateSync {
|
||||||
|
Synced,
|
||||||
|
Conflict { details: String },
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait StateCoordinator<C: Component> {
|
||||||
|
fn on_navigate(&mut self, from: Option<String>, to: String) -> Result<StateSync, String>;
|
||||||
|
|
||||||
|
fn on_focus_change(
|
||||||
|
&mut self,
|
||||||
|
old: Option<C::Focus>,
|
||||||
|
new: Option<C::Focus>,
|
||||||
|
) -> Result<StateSync, String>;
|
||||||
|
|
||||||
|
fn on_mode_change(&mut self, _old: Vec<String>, _new: Vec<String>)
|
||||||
|
-> Result<StateSync, String>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DefaultStateCoordinator;
|
||||||
|
|
||||||
|
impl<C: Component> StateCoordinator<C> for DefaultStateCoordinator {
|
||||||
|
fn on_navigate(&mut self, _from: Option<String>, _to: String) -> Result<StateSync, String> {
|
||||||
|
Ok(StateSync::Synced)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_focus_change(
|
||||||
|
&mut self,
|
||||||
|
_old: Option<C::Focus>,
|
||||||
|
_new: Option<C::Focus>,
|
||||||
|
) -> Result<StateSync, String> {
|
||||||
|
Ok(StateSync::Synced)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_mode_change(
|
||||||
|
&mut self,
|
||||||
|
_old: Vec<String>,
|
||||||
|
_new: Vec<String>,
|
||||||
|
) -> Result<StateSync, String> {
|
||||||
|
Ok(StateSync::Synced)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user