285 lines
8.6 KiB
Rust
285 lines
8.6 KiB
Rust
//! Raw input invalidation and focus checks at widget-operation execution time.
|
|
use iced::advanced::{Clipboard, Layout, Shell, Widget, layout, overlay, renderer, widget};
|
|
use iced::{Element, Event, Length, Rectangle, Size, Task, Vector, keyboard, mouse, touch};
|
|
use std::sync::{
|
|
Arc,
|
|
atomic::{AtomicU64, Ordering},
|
|
};
|
|
|
|
/// Exhaustion fails closed: a saturated clock can never authorize navigation.
|
|
/// Replace the clock with a new workspace/controller, rather than wrapping it.
|
|
pub fn is_current(clock: &AtomicU64, expected: u64) -> bool {
|
|
expected != u64::MAX && clock.load(Ordering::Acquire) == expected
|
|
}
|
|
|
|
fn invalidate(clock: &AtomicU64, event: &Event) {
|
|
if matches!(
|
|
event,
|
|
Event::Keyboard(keyboard::Event::KeyPressed { .. })
|
|
| Event::Mouse(mouse::Event::ButtonPressed(_) | mouse::Event::WheelScrolled { .. })
|
|
| Event::Touch(touch::Event::FingerPressed { .. })
|
|
| Event::Window(iced::window::Event::Unfocused)
|
|
) {
|
|
let _ = clock.fetch_update(Ordering::AcqRel, Ordering::Acquire, |value| {
|
|
Some(value.saturating_add(1))
|
|
});
|
|
}
|
|
}
|
|
|
|
/// Transparent, including overlays: invalidate before the child can capture input.
|
|
pub fn observe<'a, Message: 'a, Theme: 'a, Renderer: renderer::Renderer + 'a>(
|
|
content: Element<'a, Message, Theme, Renderer>,
|
|
clock: Arc<AtomicU64>,
|
|
) -> Element<'a, Message, Theme, Renderer> {
|
|
Element::new(Observed { content, clock })
|
|
}
|
|
|
|
struct Observed<'a, Message, Theme, Renderer> {
|
|
content: Element<'a, Message, Theme, Renderer>,
|
|
clock: Arc<AtomicU64>,
|
|
}
|
|
|
|
impl<Message, Theme, Renderer: renderer::Renderer> Widget<Message, Theme, Renderer>
|
|
for Observed<'_, Message, Theme, Renderer>
|
|
{
|
|
fn size(&self) -> Size<Length> {
|
|
self.content.as_widget().size()
|
|
}
|
|
fn size_hint(&self) -> Size<Length> {
|
|
self.content.as_widget().size_hint()
|
|
}
|
|
fn tag(&self) -> widget::tree::Tag {
|
|
self.content.as_widget().tag()
|
|
}
|
|
fn state(&self) -> widget::tree::State {
|
|
self.content.as_widget().state()
|
|
}
|
|
fn children(&self) -> Vec<widget::Tree> {
|
|
self.content.as_widget().children()
|
|
}
|
|
fn diff(&self, tree: &mut widget::Tree) {
|
|
self.content.as_widget().diff(tree);
|
|
}
|
|
fn layout(
|
|
&mut self,
|
|
tree: &mut widget::Tree,
|
|
renderer: &Renderer,
|
|
limits: &layout::Limits,
|
|
) -> layout::Node {
|
|
self.content.as_widget_mut().layout(tree, renderer, limits)
|
|
}
|
|
fn draw(
|
|
&self,
|
|
tree: &widget::Tree,
|
|
renderer: &mut Renderer,
|
|
theme: &Theme,
|
|
style: &renderer::Style,
|
|
layout: Layout<'_>,
|
|
cursor: mouse::Cursor,
|
|
viewport: &Rectangle,
|
|
) {
|
|
self.content
|
|
.as_widget()
|
|
.draw(tree, renderer, theme, style, layout, cursor, viewport);
|
|
}
|
|
fn operate(
|
|
&mut self,
|
|
tree: &mut widget::Tree,
|
|
layout: Layout<'_>,
|
|
renderer: &Renderer,
|
|
operation: &mut dyn widget::Operation,
|
|
) {
|
|
self.content
|
|
.as_widget_mut()
|
|
.operate(tree, layout, renderer, operation);
|
|
}
|
|
fn update(
|
|
&mut self,
|
|
tree: &mut widget::Tree,
|
|
event: &Event,
|
|
layout: Layout<'_>,
|
|
cursor: mouse::Cursor,
|
|
renderer: &Renderer,
|
|
clipboard: &mut dyn Clipboard,
|
|
shell: &mut Shell<'_, Message>,
|
|
viewport: &Rectangle,
|
|
) {
|
|
invalidate(&self.clock, event);
|
|
self.content.as_widget_mut().update(
|
|
tree, event, layout, cursor, renderer, clipboard, shell, viewport,
|
|
);
|
|
}
|
|
fn mouse_interaction(
|
|
&self,
|
|
tree: &widget::Tree,
|
|
layout: Layout<'_>,
|
|
cursor: mouse::Cursor,
|
|
viewport: &Rectangle,
|
|
renderer: &Renderer,
|
|
) -> mouse::Interaction {
|
|
self.content
|
|
.as_widget()
|
|
.mouse_interaction(tree, layout, cursor, viewport, renderer)
|
|
}
|
|
fn overlay<'a>(
|
|
&'a mut self,
|
|
tree: &'a mut widget::Tree,
|
|
layout: Layout<'a>,
|
|
renderer: &Renderer,
|
|
viewport: &Rectangle,
|
|
translation: Vector,
|
|
) -> Option<overlay::Element<'a, Message, Theme, Renderer>> {
|
|
let clock = self.clock.clone();
|
|
self.content
|
|
.as_widget_mut()
|
|
.overlay(tree, layout, renderer, viewport, translation)
|
|
.map(|content| overlay::Element::new(Box::new(ObservedOverlay { content, clock })))
|
|
}
|
|
}
|
|
|
|
struct ObservedOverlay<'a, Message, Theme, Renderer> {
|
|
content: overlay::Element<'a, Message, Theme, Renderer>,
|
|
clock: Arc<AtomicU64>,
|
|
}
|
|
impl<Message, Theme, Renderer: renderer::Renderer> overlay::Overlay<Message, Theme, Renderer>
|
|
for ObservedOverlay<'_, Message, Theme, Renderer>
|
|
{
|
|
fn layout(&mut self, renderer: &Renderer, bounds: Size) -> layout::Node {
|
|
self.content.as_overlay_mut().layout(renderer, bounds)
|
|
}
|
|
fn draw(
|
|
&self,
|
|
renderer: &mut Renderer,
|
|
theme: &Theme,
|
|
style: &renderer::Style,
|
|
layout: Layout<'_>,
|
|
cursor: mouse::Cursor,
|
|
) {
|
|
self.content
|
|
.as_overlay()
|
|
.draw(renderer, theme, style, layout, cursor);
|
|
}
|
|
fn operate(
|
|
&mut self,
|
|
layout: Layout<'_>,
|
|
renderer: &Renderer,
|
|
operation: &mut dyn widget::Operation,
|
|
) {
|
|
self.content
|
|
.as_overlay_mut()
|
|
.operate(layout, renderer, operation);
|
|
}
|
|
fn update(
|
|
&mut self,
|
|
event: &Event,
|
|
layout: Layout<'_>,
|
|
cursor: mouse::Cursor,
|
|
renderer: &Renderer,
|
|
clipboard: &mut dyn Clipboard,
|
|
shell: &mut Shell<'_, Message>,
|
|
) {
|
|
invalidate(&self.clock, event);
|
|
self.content
|
|
.as_overlay_mut()
|
|
.update(event, layout, cursor, renderer, clipboard, shell);
|
|
}
|
|
fn mouse_interaction(
|
|
&self,
|
|
layout: Layout<'_>,
|
|
cursor: mouse::Cursor,
|
|
renderer: &Renderer,
|
|
) -> mouse::Interaction {
|
|
self.content
|
|
.as_overlay()
|
|
.mouse_interaction(layout, cursor, renderer)
|
|
}
|
|
fn overlay<'a>(
|
|
&'a mut self,
|
|
layout: Layout<'a>,
|
|
renderer: &Renderer,
|
|
) -> Option<overlay::Element<'a, Message, Theme, Renderer>> {
|
|
let clock = self.clock.clone();
|
|
self.content
|
|
.as_overlay_mut()
|
|
.overlay(layout, renderer)
|
|
.map(|content| overlay::Element::new(Box::new(ObservedOverlay { content, clock })))
|
|
}
|
|
fn index(&self) -> f32 {
|
|
self.content.as_overlay().index()
|
|
}
|
|
}
|
|
|
|
/// Inspect all focus owners, then focus only the mounted target. Iced executes
|
|
/// Operation::Chain synchronously, without an intervening input event. Both
|
|
/// traversals recheck the raw clock; no result message authorizes later focus.
|
|
pub fn guarded_focus<Message: Send + 'static>(
|
|
target: widget::Id,
|
|
clock: Arc<AtomicU64>,
|
|
expected: u64,
|
|
) -> Task<Message> {
|
|
widget::operate(GuardedFocus::new(target, clock, expected))
|
|
}
|
|
|
|
struct GuardedFocus {
|
|
target: widget::Id,
|
|
clock: Arc<AtomicU64>,
|
|
expected: u64,
|
|
target_count: usize,
|
|
foreign_focus: bool,
|
|
apply: bool,
|
|
}
|
|
impl GuardedFocus {
|
|
fn new(target: widget::Id, clock: Arc<AtomicU64>, expected: u64) -> Self {
|
|
Self {
|
|
target,
|
|
clock,
|
|
expected,
|
|
target_count: 0,
|
|
foreign_focus: false,
|
|
apply: false,
|
|
}
|
|
}
|
|
}
|
|
impl<Message: 'static> widget::Operation<Message> for GuardedFocus {
|
|
fn traverse(&mut self, visit: &mut dyn FnMut(&mut dyn widget::Operation<Message>)) {
|
|
if is_current(&self.clock, self.expected) {
|
|
visit(self);
|
|
}
|
|
}
|
|
fn focusable(
|
|
&mut self,
|
|
id: Option<&widget::Id>,
|
|
_: Rectangle,
|
|
state: &mut dyn widget::operation::Focusable,
|
|
) {
|
|
if !is_current(&self.clock, self.expected) {
|
|
return;
|
|
}
|
|
if id == Some(&self.target) {
|
|
self.target_count += 1;
|
|
if self.apply {
|
|
state.focus();
|
|
}
|
|
} else if state.is_focused() {
|
|
self.foreign_focus = true;
|
|
}
|
|
}
|
|
fn finish(&self) -> widget::operation::Outcome<Message> {
|
|
if !self.apply
|
|
&& self.target_count == 1
|
|
&& !self.foreign_focus
|
|
&& is_current(&self.clock, self.expected)
|
|
{
|
|
widget::operation::Outcome::Chain(Box::new(Self {
|
|
apply: true,
|
|
..Self::new(self.target.clone(), self.clock.clone(), self.expected)
|
|
}))
|
|
} else {
|
|
widget::operation::Outcome::None
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(all(test, debug_assertions))]
|
|
mod tests;
|