use crate::{ ActiveData, WidgetId, util::{HashMap, HashSet}, }; use std::any::{Any, TypeId}; #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] pub struct ControllerId { host: WidgetId, kind: TypeId, } impl ControllerId { pub fn host(self) -> WidgetId { self.host } pub fn is(self) -> bool { self.kind == TypeId::of::() } } #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum Command { Copy, SelectAll, Escape, } #[derive(Debug, Eq, PartialEq)] pub enum CommandResult { Unused, Used, Copy(String), } pub trait ControllerValue: Any { fn into_any(self: Box) -> Box; } impl ControllerValue for T { fn into_any(self: Box) -> Box { self } } pub trait Controller: ControllerValue { fn command(&mut self, _command: Command, _rsc: &mut Rsc) -> CommandResult { CommandResult::Unused } fn blocks_prior_input(&self) -> bool { false } } pub struct ControllerManager { by_widget: HashMap>>>, parents: HashMap>, borrowed: HashSet, removed_while_borrowed: HashSet, command_target: Option, command_target_revision: u64, command_boundary: Option, } impl Default for ControllerManager { fn default() -> Self { Self { by_widget: Default::default(), parents: Default::default(), borrowed: Default::default(), removed_while_borrowed: Default::default(), command_target: None, command_target_revision: 0, command_boundary: None, } } } impl ControllerManager { #[track_caller] pub fn register>(&mut self, host: WidgetId, controller: C) { let kind = TypeId::of::(); let id = ControllerId { host, kind }; assert!( !self.borrowed.contains(&id), "a controller cannot be replaced while it is handling input" ); assert!( !self.removed_while_borrowed.contains(&host), "a controller cannot be attached to a removed widget" ); let old = self .by_widget .entry(host) .or_default() .insert(kind, Box::new(controller)); assert!( old.is_none(), "a widget cannot have two controllers of type {}", std::any::type_name::() ); } pub fn id>(&self, host: WidgetId) -> Option { let kind = TypeId::of::(); self.by_widget .get(&host)? .contains_key(&kind) .then_some(ControllerId { host, kind }) } pub fn nearest_id>(&self, mut origin: WidgetId) -> Option { let kind = TypeId::of::(); loop { let candidate = ControllerId { host: origin, kind }; assert!( !self.borrowed.contains(&candidate), "a controller cannot re-enter itself while it is handling input" ); if let Some(id) = self.id::(origin) { return Some(id); } origin = self.parents.get(&origin).copied().flatten()?; } } pub fn path_to>( &self, mut origin: WidgetId, ) -> Option<(ControllerId, Vec)> { let mut path = Vec::new(); loop { path.push(origin); if let Some(id) = self.id::(origin) { return Some((id, path)); } origin = self.parents.get(&origin).copied().flatten()?; } } pub fn draw(&mut self, active: &ActiveData) { self.parents.insert(active.id, active.parent); } pub fn undraw(&mut self, active: &ActiveData) { self.parents.remove(&active.id); } pub fn take>(&mut self, id: ControllerId) -> Option { if id.kind != TypeId::of::() { return None; } assert!( !self.borrowed.contains(&id), "a controller cannot re-enter itself while it is handling input" ); let boxed = self.by_widget.get_mut(&id.host)?.remove(&id.kind)?; self.borrowed.insert(id); let boxed = boxed.into_any(); boxed.downcast().ok().map(|boxed| *boxed) } pub fn put>(&mut self, id: ControllerId, controller: C) { debug_assert_eq!(id.kind, TypeId::of::()); assert!( self.borrowed.remove(&id), "restored an unborrowed controller" ); if self.finish_removed_host(id.host) { return; } let old = self .by_widget .entry(id.host) .or_default() .insert(id.kind, Box::new(controller)); debug_assert!(old.is_none(), "a controller was re-entered while borrowed"); } fn take_dyn(&mut self, id: ControllerId) -> Option>> { assert!( !self.borrowed.contains(&id), "a controller cannot re-enter itself while it is handling input" ); let controller = self.by_widget.get_mut(&id.host)?.remove(&id.kind)?; self.borrowed.insert(id); Some(controller) } fn put_dyn(&mut self, id: ControllerId, controller: Box>) { assert!( self.borrowed.remove(&id), "restored an unborrowed controller" ); if self.finish_removed_host(id.host) { return; } let old = self .by_widget .entry(id.host) .or_default() .insert(id.kind, controller); debug_assert!(old.is_none(), "a controller was re-entered while borrowed"); } pub fn set_command_target(&mut self, target: Option) { self.command_target = target; self.command_target_revision = self.command_target_revision.wrapping_add(1); } pub fn command_target(&self) -> Option { self.command_target } pub fn command_target_blocks_input(&self) -> bool { let Some(id) = self.command_target else { return false; }; self.by_widget .get(&id.host) .and_then(|controllers| controllers.get(&id.kind)) .is_some_and(|controller| controller.blocks_prior_input()) } pub(crate) fn command_target_revision(&self) -> u64 { self.command_target_revision } pub(crate) fn command_boundary(&self) -> Option { self.command_boundary } pub(crate) fn is_below(&self, mut widget: WidgetId, ancestor: WidgetId) -> bool { loop { let Some(parent) = self.parents.get(&widget).copied().flatten() else { return false; }; if parent == ancestor { return true; } widget = parent; } } pub(crate) fn set_command_boundary(&mut self, boundary: Option) { self.command_boundary = boundary; } pub fn remove(&mut self, host: WidgetId) { self.by_widget.remove(&host); self.parents.remove(&host); if self.borrowed.iter().any(|id| id.host == host) { self.removed_while_borrowed.insert(host); } if self.command_target.is_some_and(|id| id.host == host) { self.command_target = None; } } pub(crate) fn take_command_target( &mut self, ) -> Option<(ControllerId, Box>)> { let id = self.command_target?; match self.take_dyn(id) { Some(controller) => Some((id, controller)), None => { self.command_target = None; None } } } pub(crate) fn restore(&mut self, id: ControllerId, controller: Box>) { self.put_dyn(id, controller); } /// Returns true when a host disappeared during its controller callback, /// in which case restoring the temporarily extracted value would revive /// state belonging to a dead widget generation. fn finish_removed_host(&mut self, host: WidgetId) -> bool { if !self.removed_while_borrowed.contains(&host) { return false; } if !self.borrowed.iter().any(|id| id.host == host) { self.removed_while_borrowed.remove(&host); } true } }