292 lines
8.5 KiB
Rust
292 lines
8.5 KiB
Rust
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<C: 'static>(self) -> bool {
|
|
self.kind == TypeId::of::<C>()
|
|
}
|
|
}
|
|
|
|
#[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<Self>) -> Box<dyn Any>;
|
|
}
|
|
|
|
impl<T: Any> ControllerValue for T {
|
|
fn into_any(self: Box<Self>) -> Box<dyn Any> {
|
|
self
|
|
}
|
|
}
|
|
|
|
pub trait Controller<Rsc>: ControllerValue {
|
|
fn command(&mut self, _command: Command, _rsc: &mut Rsc) -> CommandResult {
|
|
CommandResult::Unused
|
|
}
|
|
|
|
fn blocks_prior_input(&self) -> bool {
|
|
false
|
|
}
|
|
}
|
|
|
|
pub struct ControllerManager<Rsc> {
|
|
by_widget: HashMap<WidgetId, HashMap<TypeId, Box<dyn Controller<Rsc>>>>,
|
|
parents: HashMap<WidgetId, Option<WidgetId>>,
|
|
borrowed: HashSet<ControllerId>,
|
|
removed_while_borrowed: HashSet<WidgetId>,
|
|
command_target: Option<ControllerId>,
|
|
command_target_revision: u64,
|
|
command_boundary: Option<WidgetId>,
|
|
}
|
|
|
|
impl<Rsc> Default for ControllerManager<Rsc> {
|
|
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<Rsc: 'static> ControllerManager<Rsc> {
|
|
#[track_caller]
|
|
pub fn register<C: Controller<Rsc>>(&mut self, host: WidgetId, controller: C) {
|
|
let kind = TypeId::of::<C>();
|
|
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::<C>()
|
|
);
|
|
}
|
|
|
|
pub fn id<C: Controller<Rsc>>(&self, host: WidgetId) -> Option<ControllerId> {
|
|
let kind = TypeId::of::<C>();
|
|
self.by_widget
|
|
.get(&host)?
|
|
.contains_key(&kind)
|
|
.then_some(ControllerId { host, kind })
|
|
}
|
|
|
|
pub fn nearest_id<C: Controller<Rsc>>(&self, mut origin: WidgetId) -> Option<ControllerId> {
|
|
let kind = TypeId::of::<C>();
|
|
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::<C>(origin) {
|
|
return Some(id);
|
|
}
|
|
origin = self.parents.get(&origin).copied().flatten()?;
|
|
}
|
|
}
|
|
|
|
pub fn path_to<C: Controller<Rsc>>(
|
|
&self,
|
|
mut origin: WidgetId,
|
|
) -> Option<(ControllerId, Vec<WidgetId>)> {
|
|
let mut path = Vec::new();
|
|
loop {
|
|
path.push(origin);
|
|
if let Some(id) = self.id::<C>(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<C: Controller<Rsc>>(&mut self, id: ControllerId) -> Option<C> {
|
|
if id.kind != TypeId::of::<C>() {
|
|
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<C: Controller<Rsc>>(&mut self, id: ControllerId, controller: C) {
|
|
debug_assert_eq!(id.kind, TypeId::of::<C>());
|
|
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<Box<dyn Controller<Rsc>>> {
|
|
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<dyn Controller<Rsc>>) {
|
|
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<ControllerId>) {
|
|
self.command_target = target;
|
|
self.command_target_revision = self.command_target_revision.wrapping_add(1);
|
|
}
|
|
|
|
pub fn command_target(&self) -> Option<ControllerId> {
|
|
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<WidgetId> {
|
|
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<WidgetId>) {
|
|
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<dyn Controller<Rsc>>)> {
|
|
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<dyn Controller<Rsc>>) {
|
|
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
|
|
}
|
|
}
|