#![cfg_attr(not(winit), allow(unused))]
use linear_map::LinearMap;
use smallvec::SmallVec;
use std::collections::{BTreeMap, HashMap, VecDeque};
use std::future::Future;
use std::ops::{Deref, DerefMut};
use std::pin::Pin;
use std::time::Instant;
use std::u16;
use super::config::WindowConfig;
use super::*;
use crate::cast::Cast;
use crate::geom::{Coord, Offset};
use crate::shell::ShellWindow;
use crate::util::WidgetHierarchy;
use crate::LayoutExt;
use crate::{Action, Erased, ErasedStack, NavAdvance, Node, Widget, WidgetId, WindowId};
mod config;
mod cx_pub;
mod cx_shell;
mod press;
pub use config::ConfigCx;
pub use press::{GrabBuilder, Press, PressSource};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum GrabMode {
Click,
Grab,
PanFull,
PanScale,
PanRotate,
PanOnly,
}
impl GrabMode {
fn is_pan(self) -> bool {
use GrabMode::*;
matches!(self, PanFull | PanScale | PanRotate | PanOnly)
}
}
#[derive(Clone, Debug)]
struct MouseGrab {
button: MouseButton,
repetitions: u32,
start_id: WidgetId,
cur_id: Option<WidgetId>,
depress: Option<WidgetId>,
mode: GrabMode,
pan_grab: (u16, u16),
coord: Coord,
delta: Offset,
}
impl<'a> EventCx<'a> {
fn flush_mouse_grab_motion(&mut self, widget: Node<'_>) {
if let Some(grab) = self.mouse_grab.as_mut() {
let delta = grab.delta;
if delta == Offset::ZERO {
return;
}
grab.delta = Offset::ZERO;
match grab.mode {
GrabMode::Click => {
if grab.start_id == grab.cur_id {
if grab.depress != grab.cur_id {
grab.depress = grab.cur_id.clone();
self.action |= Action::REDRAW;
}
} else if grab.depress.is_some() {
grab.depress = None;
self.action |= Action::REDRAW;
}
}
GrabMode::Grab => {
let target = grab.start_id.clone();
let press = Press {
source: PressSource::Mouse(grab.button, grab.repetitions),
id: grab.cur_id.clone(),
coord: grab.coord,
};
let event = Event::PressMove { press, delta };
self.send_event(widget, target, event);
}
_ => (),
}
}
}
}
#[derive(Clone, Debug)]
struct TouchGrab {
id: u64,
start_id: WidgetId,
depress: Option<WidgetId>,
cur_id: Option<WidgetId>,
last_move: Coord,
coord: Coord,
mode: GrabMode,
pan_grab: (u16, u16),
}
impl TouchGrab {
fn flush_click_move(&mut self) -> Action {
if self.mode == GrabMode::Click && self.last_move != self.coord {
self.last_move = self.coord;
if self.start_id == self.cur_id {
if self.depress != self.cur_id {
self.depress = self.cur_id.clone();
return Action::REDRAW;
}
} else if self.depress.is_some() {
self.depress = None;
return Action::REDRAW;
}
}
Action::empty()
}
fn flush_grab_move(&mut self) -> Option<(WidgetId, Event)> {
if self.mode == GrabMode::Grab && self.last_move != self.coord {
let delta = self.coord - self.last_move;
let target = self.start_id.clone();
let press = Press {
source: PressSource::Touch(self.id),
id: self.cur_id.clone(),
coord: self.coord,
};
let event = Event::PressMove { press, delta };
self.last_move = self.coord;
Some((target, event))
} else {
None
}
}
}
const MAX_PAN_GRABS: usize = 2;
#[derive(Clone, Debug)]
struct PanGrab {
id: WidgetId,
mode: GrabMode,
source_is_touch: bool,
n: u16,
coords: [(Coord, Coord); MAX_PAN_GRABS],
}
#[derive(Clone, Debug)]
#[allow(clippy::enum_variant_names)] enum Pending {
Configure(WidgetId),
Update(WidgetId),
Send(WidgetId, Event),
SetRect(WidgetId),
NextNavFocus {
target: Option<WidgetId>,
reverse: bool,
source: FocusSource,
},
}
type AccessLayer = (bool, HashMap<Key, WidgetId>);
pub struct EventState {
config: WindowConfig,
disabled: Vec<WidgetId>,
window_has_focus: bool,
modifiers: ModifiersState,
key_focus: bool,
sel_focus: Option<WidgetId>,
nav_focus: Option<WidgetId>,
nav_fallback: Option<WidgetId>,
hover: Option<WidgetId>,
hover_icon: CursorIcon,
key_depress: LinearMap<KeyCode, WidgetId>,
last_mouse_coord: Coord,
last_click_button: MouseButton,
last_click_repetitions: u32,
last_click_timeout: Instant,
mouse_grab: Option<MouseGrab>,
touch_grab: SmallVec<[TouchGrab; 8]>,
pan_grab: SmallVec<[PanGrab; 4]>,
access_layers: BTreeMap<WidgetId, AccessLayer>,
popups: SmallVec<[(WindowId, crate::PopupDescriptor, Option<WidgetId>); 16]>,
popup_removed: SmallVec<[(WidgetId, WindowId); 16]>,
time_updates: Vec<(Instant, WidgetId, u64)>,
fut_messages: Vec<(WidgetId, Pin<Box<dyn Future<Output = Erased>>>)>,
pending: VecDeque<Pending>,
#[cfg_attr(not(feature = "internal_doc"), doc(hidden))]
#[cfg_attr(doc_cfg, doc(cfg(internal_doc)))]
pub action: Action,
}
impl EventState {
#[inline]
fn key_focus(&self) -> Option<WidgetId> {
if self.key_focus {
self.sel_focus.clone()
} else {
None
}
}
fn set_pan_on(
&mut self,
id: WidgetId,
mode: GrabMode,
source_is_touch: bool,
coord: Coord,
) -> (u16, u16) {
for (gi, grab) in self.pan_grab.iter_mut().enumerate() {
if grab.id == id {
if grab.source_is_touch != source_is_touch {
self.remove_pan(gi);
break;
}
let index = grab.n;
if usize::from(index) < MAX_PAN_GRABS {
grab.coords[usize::from(index)] = (coord, coord);
}
grab.n = index + 1;
return (gi.cast(), index);
}
}
let gj = self.pan_grab.len().cast();
let n = 1;
let mut coords: [(Coord, Coord); MAX_PAN_GRABS] = Default::default();
coords[0] = (coord, coord);
log::trace!("set_pan_on: index={}, id={id}", self.pan_grab.len());
self.pan_grab.push(PanGrab {
id,
mode,
source_is_touch,
n,
coords,
});
(gj, 0)
}
fn remove_pan(&mut self, index: usize) {
log::trace!("remove_pan: index={index}");
self.pan_grab.remove(index);
if let Some(grab) = &mut self.mouse_grab {
let p0 = grab.pan_grab.0;
if usize::from(p0) >= index && p0 != u16::MAX {
grab.pan_grab.0 = p0 - 1;
}
}
for grab in self.touch_grab.iter_mut() {
let p0 = grab.pan_grab.0;
if usize::from(p0) >= index && p0 != u16::MAX {
grab.pan_grab.0 = p0 - 1;
}
}
}
fn remove_pan_grab(&mut self, g: (u16, u16)) {
if let Some(grab) = self.pan_grab.get_mut(usize::from(g.0)) {
grab.n -= 1;
if grab.n == 0 {
return self.remove_pan(g.0.into());
}
assert!(grab.source_is_touch);
for i in (usize::from(g.1))..(usize::from(grab.n) - 1) {
grab.coords[i] = grab.coords[i + 1];
}
} else {
return;
}
for grab in self.touch_grab.iter_mut() {
if grab.pan_grab.0 == g.0 && grab.pan_grab.1 > g.1 {
grab.pan_grab.1 -= 1;
if usize::from(grab.pan_grab.1) == MAX_PAN_GRABS - 1 {
let v = grab.coord;
self.pan_grab[usize::from(g.0)].coords[usize::from(grab.pan_grab.1)] = (v, v);
}
}
}
}
#[inline]
fn get_touch(&mut self, touch_id: u64) -> Option<&mut TouchGrab> {
self.touch_grab.iter_mut().find(|grab| grab.id == touch_id)
}
fn remove_touch(&mut self, touch_id: u64) -> Option<TouchGrab> {
for i in 0..self.touch_grab.len() {
if self.touch_grab[i].id == touch_id {
let grab = self.touch_grab.remove(i);
log::trace!(
"remove_touch: touch_id={touch_id}, start_id={}",
grab.start_id
);
self.send_action(Action::REDRAW); self.remove_pan_grab(grab.pan_grab);
return Some(grab);
}
}
None
}
fn clear_key_focus(&mut self) {
if let Some(id) = self.key_focus() {
log::trace!("clear_key_focus");
self.key_focus = false;
self.pending
.push_back(Pending::Send(id, Event::LostKeyFocus));
}
}
fn set_sel_focus(&mut self, wid: WidgetId, key_focus: bool) {
log::trace!("set_sel_focus: wid={wid}, key_focus={key_focus}");
self.set_nav_focus(wid.clone(), FocusSource::Synthetic);
if wid == self.sel_focus {
self.key_focus = self.key_focus || key_focus;
return;
}
if let Some(id) = self.sel_focus.clone() {
if self.key_focus {
self.pending
.push_back(Pending::Send(id.clone(), Event::LostKeyFocus));
}
self.pending
.push_back(Pending::Send(id, Event::LostSelFocus));
}
self.key_focus = key_focus;
self.sel_focus = Some(wid);
}
fn set_hover(&mut self, mut w_id: Option<WidgetId>) {
if let Some(ref id) = w_id {
if let Some(popup) = self.popups.last() {
if !popup.1.id.is_ancestor_of(id) {
w_id = None;
}
}
}
if self.hover != w_id {
log::trace!("set_hover: w_id={w_id:?}");
if let Some(id) = self.hover.take() {
self.pending
.push_back(Pending::Send(id, Event::MouseHover(false)));
}
self.hover = w_id.clone();
if let Some(id) = w_id {
self.pending
.push_back(Pending::Send(id, Event::MouseHover(true)));
}
}
}
}
#[must_use]
pub struct EventCx<'a> {
state: &'a mut EventState,
shell: &'a mut dyn ShellWindow,
messages: &'a mut ErasedStack,
last_child: Option<usize>,
scroll: Scroll,
}
impl<'a> Deref for EventCx<'a> {
type Target = EventState;
fn deref(&self) -> &Self::Target {
self.state
}
}
impl<'a> DerefMut for EventCx<'a> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.state
}
}
impl<'a> EventCx<'a> {
fn start_key_event(&mut self, mut widget: Node<'_>, vkey: Key, code: KeyCode) {
log::trace!(
"start_key_event: widget={}, vkey={vkey:?}, physical_key={code:?}",
widget.id()
);
let opt_command = self
.config
.shortcuts(|s| s.try_match(self.modifiers, &vkey));
if let Some(cmd) = opt_command {
let mut targets = vec![];
let mut send = |_self: &mut Self, id: WidgetId, cmd| -> bool {
if !targets.contains(&id) {
let event = Event::Command(cmd, Some(code));
let used = _self.send_event(widget.re(), id.clone(), event);
targets.push(id);
used
} else {
false
}
};
if self.key_focus || cmd.suitable_for_sel_focus() {
if let Some(id) = self.sel_focus.clone() {
if send(self, id, cmd) {
return;
}
}
}
if !self.modifiers.alt_key() {
if let Some(id) = self.nav_focus.clone() {
if send(self, id, cmd) {
return;
}
}
}
if let Some(id) = self.popups.last().map(|popup| popup.1.id.clone()) {
if send(self, id, cmd) {
return;
}
}
if let Some(id) = self.nav_fallback.clone() {
if send(self, id, cmd) {
return;
}
}
if matches!(cmd, Command::Debug) {
if let Some(ref id) = self.hover {
if let Some(w) = widget.as_layout().find_widget(id) {
let hier = WidgetHierarchy::new(w);
log::debug!("Widget heirarchy (from mouse): {hier}");
}
} else {
let hier = WidgetHierarchy::new(widget.as_layout());
log::debug!("Widget heirarchy (whole window): {hier}");
}
return;
}
}
let mut target = None;
for id in (self.popups.iter().rev())
.map(|(_, popup, _)| popup.id.clone())
.chain(std::iter::once(widget.id()))
{
if let Some(layer) = self.access_layers.get(&id) {
if self.modifiers == ModifiersState::ALT
|| layer.0 && self.modifiers == ModifiersState::empty()
{
if let Some(id) = layer.1.get(&vkey).cloned() {
target = Some(id);
break;
}
}
}
}
if let Some(id) = target {
if let Some(id) = widget._nav_next(self, Some(&id), NavAdvance::None) {
self.set_nav_focus(id, FocusSource::Key);
}
let event = Event::Command(Command::Activate, Some(code));
self.send_event(widget, id, event);
} else if self.config.nav_focus && vkey == Key::Tab {
self.clear_key_focus();
let shift = self.modifiers.shift_key();
self.next_nav_focus_impl(widget.re(), None, shift, FocusSource::Key);
} else if vkey == Key::Escape {
if let Some(id) = self.popups.last().map(|(id, _, _)| *id) {
self.close_window(id);
}
}
}
fn remove_mouse_grab(&mut self, success: bool) -> Option<(WidgetId, Event)> {
if let Some(grab) = self.mouse_grab.take() {
log::trace!("remove_mouse_grab: start_id={}", grab.start_id);
self.shell.set_cursor_icon(self.hover_icon);
self.send_action(Action::REDRAW); if grab.mode.is_pan() {
self.remove_pan_grab(grab.pan_grab);
None
} else {
let press = Press {
source: PressSource::Mouse(grab.button, grab.repetitions),
id: self.hover.clone(),
coord: self.last_mouse_coord,
};
let event = Event::PressEnd { press, success };
Some((grab.start_id, event))
}
} else {
None
}
}
pub(crate) fn assert_post_steal_unused(&self) {
if self.scroll != Scroll::None || self.messages.has_any() {
panic!("steal_event affected EventCx and returned Unused");
}
}
pub(crate) fn post_send(&mut self, index: usize) -> Option<Scroll> {
self.last_child = Some(index);
(self.scroll != Scroll::None).then_some(self.scroll)
}
fn replay(&mut self, mut widget: Node<'_>, id: WidgetId, msg: Erased) {
debug_assert!(self.scroll == Scroll::None);
debug_assert!(self.last_child.is_none());
self.messages.set_base();
log::trace!(target: "kas_core::event", "replay: id={id}: {msg:?}");
widget._replay(self, id, msg);
self.last_child = None;
self.scroll = Scroll::None;
}
#[inline]
fn send_event(&mut self, widget: Node<'_>, id: WidgetId, event: Event) -> bool {
self.messages.set_base();
let used = self.send_event_impl(widget, id, event);
self.last_child = None;
self.scroll = Scroll::None;
used
}
fn send_event_impl(&mut self, mut widget: Node<'_>, mut id: WidgetId, event: Event) -> bool {
debug_assert!(self.scroll == Scroll::None);
debug_assert!(self.last_child.is_none());
self.messages.set_base();
log::trace!(target: "kas_core::event", "send_event: id={id}: {event:?}");
let mut disabled = false;
if !event.pass_when_disabled() {
for d in &self.disabled {
if d.is_ancestor_of(&id) {
id = d.clone();
disabled = true;
}
}
if disabled {
log::trace!(target: "kas_core::event", "target is disabled; sending to ancestor {id}");
}
}
widget._send(self, id, disabled, event) == Used
}
fn send_popup_first(&mut self, mut widget: Node<'_>, id: Option<WidgetId>, event: Event) {
while let Some(pid) = self.popups.last().map(|(_, p, _)| p.id.clone()) {
let mut target = pid;
if let Some(id) = id.clone() {
if target.is_ancestor_of(&id) {
target = id;
}
}
log::trace!("send_popup_first: id={target}: {event:?}");
if self.send_event(widget.re(), target, event.clone()) {
return;
}
}
if let Some(id) = id {
self.send_event(widget, id, event);
}
}
pub fn next_nav_focus_impl(
&mut self,
mut widget: Node,
target: Option<WidgetId>,
reverse: bool,
source: FocusSource,
) {
if !self.config.nav_focus || (target.is_some() && target == self.nav_focus) {
return;
}
if let Some(id) = self.popups.last().map(|(_, p, _)| p.id.clone()) {
if id.is_ancestor_of(widget.id_ref()) {
} else if let Some(r) = widget.find_node(&id, |node| {
self.next_nav_focus_impl(node, target, reverse, source)
}) {
return r;
} else {
log::warn!(
target: "kas_core::event",
"next_nav_focus: have open pop-up which is not a child of widget",
);
return;
}
}
self.send_action(Action::REDRAW);
let advance = if !reverse {
NavAdvance::Forward(target.is_some())
} else {
NavAdvance::Reverse(target.is_some())
};
let focus = target.or_else(|| self.nav_focus.clone());
let restart = focus.is_some();
let mut opt_id = widget._nav_next(self, focus.as_ref(), advance);
if restart && opt_id.is_none() {
opt_id = widget._nav_next(self, None, advance);
}
log::trace!(
target: "kas_core::event",
"next_nav_focus: nav_focus={opt_id:?}",
);
if opt_id == self.nav_focus {
return;
}
if let Some(id) = self.nav_focus.clone() {
self.pending
.push_back(Pending::Send(id, Event::LostNavFocus));
}
if self.sel_focus != opt_id {
self.clear_key_focus();
}
self.nav_focus = opt_id.clone();
if let Some(id) = opt_id {
log::debug!(target: "kas_core::event", "nav_focus = Some({id})");
self.pending
.push_back(Pending::Send(id, Event::NavFocus(source)));
} else {
log::debug!(target: "kas_core::event", "nav_focus = None");
}
}
}