Add retained paints and shared text selection
This commit is contained in:
1 parent
1e6d3b1edd
commit
a33fbca966
42 files changed
+2424
-470
No files matched your search
@@ -0,0 +1,261 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
#[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
|
||||
}
|
||||
}
|
||||
|
||||
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(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 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
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user