use std::{
collections::{HashMap, HashSet},
fmt, mem,
};
use zng_app::{
event::{event, event_args},
update::{EventUpdate, UPDATES},
view_process::{
raw_device_events::DeviceId,
raw_events::{
RAW_FRAME_RENDERED_EVENT, RAW_MOUSE_INPUT_EVENT, RAW_MOUSE_MOVED_EVENT, RAW_TOUCH_EVENT, RAW_WINDOW_CLOSE_EVENT,
RAW_WINDOW_FOCUS_EVENT,
},
VIEW_PROCESS_INITED_EVENT,
},
widget::{
info::{InteractionPath, WidgetInfoTree, WidgetPath, WIDGET_INFO_CHANGED_EVENT},
WidgetId, WIDGET,
},
window::{WindowId, WINDOW},
AppExtension,
};
use zng_app_context::app_local;
use zng_ext_window::{NestedWindowWidgetInfoExt, WINDOWS};
use zng_layout::unit::{DipPoint, DipToPx};
use zng_var::{impl_from_and_into_var, var, ArcVar, ReadOnlyArcVar, Var};
use zng_view_api::{
mouse::{ButtonState, MouseButton},
touch::{TouchId, TouchPhase},
};
#[derive(Default)]
pub struct PointerCaptureManager {
mouse_position: HashMap<(WindowId, DeviceId), DipPoint>,
mouse_down: HashSet<(WindowId, DeviceId, MouseButton)>,
touch_down: HashSet<(WindowId, DeviceId, TouchId)>,
capture: Option<CaptureInfo>,
}
impl AppExtension for PointerCaptureManager {
fn event(&mut self, update: &mut EventUpdate) {
if let Some(args) = RAW_FRAME_RENDERED_EVENT.on(update) {
if let Some(c) = &self.capture {
if c.target.window_id() == args.window_id {
if let Ok(info) = WINDOWS.widget_tree(args.window_id) {
self.continue_capture(&info);
}
}
}
} else if let Some(args) = RAW_MOUSE_MOVED_EVENT.on(update) {
self.mouse_position.insert((args.window_id, args.device_id), args.position);
} else if let Some(args) = RAW_MOUSE_INPUT_EVENT.on(update) {
match args.state {
ButtonState::Pressed => {
if self.mouse_down.insert((args.window_id, args.device_id, args.button))
&& self.mouse_down.len() == 1
&& self.touch_down.is_empty()
{
self.on_first_down(
args.window_id,
self.mouse_position
.get(&(args.window_id, args.device_id))
.copied()
.unwrap_or_default(),
);
}
}
ButtonState::Released => {
if self.mouse_down.remove(&(args.window_id, args.device_id, args.button))
&& self.mouse_down.is_empty()
&& self.touch_down.is_empty()
{
self.on_last_up();
}
}
}
} else if let Some(args) = RAW_TOUCH_EVENT.on(update) {
for touch in &args.touches {
match touch.phase {
TouchPhase::Start => {
if self.touch_down.insert((args.window_id, args.device_id, touch.touch))
&& self.touch_down.len() == 1
&& self.mouse_down.is_empty()
{
self.on_first_down(args.window_id, touch.position);
}
}
TouchPhase::End | TouchPhase::Cancel => {
if self.touch_down.remove(&(args.window_id, args.device_id, touch.touch))
&& self.touch_down.is_empty()
&& self.mouse_down.is_empty()
{
self.on_last_up();
}
}
TouchPhase::Move => {}
}
}
} else if let Some(args) = WIDGET_INFO_CHANGED_EVENT.on(update) {
if let Some(c) = &self.capture {
if c.target.window_id() == args.window_id {
self.continue_capture(&args.tree);
}
}
} else if let Some(args) = RAW_WINDOW_CLOSE_EVENT.on(update) {
self.remove_window(args.window_id);
} else if let Some(args) = RAW_WINDOW_FOCUS_EVENT.on(update) {
let actual_prev = args.prev_focus.map(|id| WINDOWS.nest_parent(id).map(|(p, _)| p).unwrap_or(id));
let actual_new = args.new_focus.map(|id| WINDOWS.nest_parent(id).map(|(p, _)| p).unwrap_or(id));
if actual_prev == actual_new {
return;
}
if let Some(w) = actual_prev {
self.remove_window(w);
}
} else if let Some(args) = VIEW_PROCESS_INITED_EVENT.on(update) {
if args.is_respawn && (!self.mouse_down.is_empty() || !self.touch_down.is_empty()) {
self.mouse_down.clear();
self.touch_down.clear();
self.on_last_up();
}
}
}
fn update(&mut self) {
if let Some(current) = &self.capture {
let mut cap = POINTER_CAPTURE_SV.write();
if let Some((widget_id, mode)) = cap.capture_request.take() {
let is_win_focused = match WINDOWS.is_focused(current.target.window_id()) {
Ok(mut f) => {
if !f {
if let Some(parent) = WINDOWS.nest_parent(current.target.window_id()).map(|(p, _)| p) {
f = WINDOWS.is_focused(parent) == Ok(true);
}
}
f
}
Err(_) => false,
};
if is_win_focused {
if let Some(widget) = WINDOWS.widget_tree(current.target.window_id()).unwrap().get(widget_id) {
self.set_capture(&mut cap, widget.interaction_path(), mode);
}
}
} else if mem::take(&mut cap.release_requested) && current.mode != CaptureMode::Window {
let target = current.target.root_path();
self.set_capture(&mut cap, InteractionPath::from_enabled(target.into_owned()), CaptureMode::Window);
}
}
}
}
impl PointerCaptureManager {
fn remove_window(&mut self, window_id: WindowId) {
self.mouse_position.retain(|(w, _), _| *w != window_id);
if !self.mouse_down.is_empty() || !self.touch_down.is_empty() {
self.mouse_down.retain(|(w, _, _)| *w != window_id);
self.touch_down.retain(|(w, _, _)| *w != window_id);
if self.mouse_down.is_empty() && self.touch_down.is_empty() {
self.on_last_up();
}
}
}
fn on_first_down(&mut self, window_id: WindowId, point: DipPoint) {
if let Ok(mut info) = WINDOWS.widget_tree(window_id) {
let mut cap = POINTER_CAPTURE_SV.write();
cap.release_requested = false;
let mut point = point.to_px(info.scale_factor());
if let Some(t) = info.root().hit_test(point).target() {
if let Some(w) = info.get(t.widget_id) {
if let Some(t) = w.nested_window_tree() {
info = t;
point = w
.inner_transform()
.inverse()
.and_then(|t| t.transform_point(point))
.unwrap_or(point);
}
}
}
if let Some((widget_id, mode)) = cap.capture_request.take() {
if let Some(w_info) = info.get(widget_id) {
if w_info.hit_test(point).contains(widget_id) {
self.set_capture(&mut cap, w_info.interaction_path(), mode);
return;
}
}
}
self.set_capture(&mut cap, info.root().interaction_path(), CaptureMode::Window);
}
}
fn on_last_up(&mut self) {
let mut cap = POINTER_CAPTURE_SV.write();
cap.release_requested = false;
cap.capture_request = None;
self.unset_capture(&mut cap);
}
fn continue_capture(&mut self, info: &WidgetInfoTree) {
let current = self.capture.as_ref().unwrap();
if let Some(widget) = info.get(current.target.widget_id()) {
if let Some(new_path) = widget.new_interaction_path(&InteractionPath::from_enabled(current.target.clone())) {
let mode = current.mode;
self.set_capture(&mut POINTER_CAPTURE_SV.write(), new_path, mode);
}
} else {
self.set_capture(&mut POINTER_CAPTURE_SV.write(), info.root().interaction_path(), CaptureMode::Window);
}
}
fn set_capture(&mut self, cap: &mut PointerCaptureService, target: InteractionPath, mode: CaptureMode) {
let new = target.enabled().map(|target| CaptureInfo { target, mode });
if new.is_none() {
self.unset_capture(cap);
return;
}
if new != self.capture {
let prev = self.capture.take();
self.capture.clone_from(&new);
cap.capture_value.clone_from(&new);
cap.capture.set(new.clone());
POINTER_CAPTURE_EVENT.notify(PointerCaptureArgs::now(prev, new));
}
}
fn unset_capture(&mut self, cap: &mut PointerCaptureService) {
if self.capture.is_some() {
let prev = self.capture.take();
cap.capture_value = None;
cap.capture.set(None);
POINTER_CAPTURE_EVENT.notify(PointerCaptureArgs::now(prev, None));
}
}
}
#[expect(non_camel_case_types)]
pub struct POINTER_CAPTURE;
impl POINTER_CAPTURE {
pub fn current_capture(&self) -> ReadOnlyArcVar<Option<CaptureInfo>> {
POINTER_CAPTURE_SV.read().capture.read_only()
}
pub fn capture_widget(&self, widget_id: WidgetId) {
let mut m = POINTER_CAPTURE_SV.write();
m.capture_request = Some((widget_id, CaptureMode::Widget));
UPDATES.update(None);
}
pub fn capture_subtree(&self, widget_id: WidgetId) {
let mut m = POINTER_CAPTURE_SV.write();
m.capture_request = Some((widget_id, CaptureMode::Subtree));
UPDATES.update(None);
}
pub fn release_capture(&self) {
let mut m = POINTER_CAPTURE_SV.write();
m.release_requested = true;
UPDATES.update(None);
}
pub(crate) fn current_capture_value(&self) -> Option<CaptureInfo> {
POINTER_CAPTURE_SV.read().capture_value.clone()
}
}
#[derive(Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum CaptureMode {
Window,
Subtree,
Widget,
}
impl fmt::Debug for CaptureMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if f.alternate() {
write!(f, "CaptureMode::")?;
}
match self {
CaptureMode::Window => write!(f, "Window"),
CaptureMode::Subtree => write!(f, "Subtree"),
CaptureMode::Widget => write!(f, "Widget"),
}
}
}
impl Default for CaptureMode {
fn default() -> Self {
CaptureMode::Window
}
}
impl_from_and_into_var! {
fn from(widget: bool) -> CaptureMode {
if widget {
CaptureMode::Widget
} else {
CaptureMode::Window
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CaptureInfo {
pub target: WidgetPath,
pub mode: CaptureMode,
}
impl CaptureInfo {
pub fn allows(&self) -> bool {
match self.mode {
CaptureMode::Window => self.target.window_id() == WINDOW.id(),
CaptureMode::Widget => self.target.widget_id() == WIDGET.id(),
CaptureMode::Subtree => {
let tree = WINDOW.info();
if let Some(wgt) = tree.get(WIDGET.id()) {
for wgt in wgt.self_and_ancestors() {
if wgt.id() == self.target.widget_id() {
return true;
}
}
}
false
}
}
}
}
app_local! {
static POINTER_CAPTURE_SV: PointerCaptureService = PointerCaptureService {
capture_value: None,
capture: var(None),
capture_request: None,
release_requested: false,
};
}
struct PointerCaptureService {
capture_value: Option<CaptureInfo>,
capture: ArcVar<Option<CaptureInfo>>,
capture_request: Option<(WidgetId, CaptureMode)>,
release_requested: bool,
}
event! {
pub static POINTER_CAPTURE_EVENT: PointerCaptureArgs;
}
event_args! {
pub struct PointerCaptureArgs {
pub prev_capture: Option<CaptureInfo>,
pub new_capture: Option<CaptureInfo>,
..
fn delivery_list(&self, list: &mut UpdateDeliveryList) {
if let Some(p) = &self.prev_capture {
list.insert_wgt(&p.target);
}
if let Some(p) = &self.new_capture {
list.insert_wgt(&p.target);
}
}
}
}
impl PointerCaptureArgs {
pub fn is_widget_move(&self) -> bool {
match (&self.prev_capture, &self.new_capture) {
(Some(prev), Some(new)) => prev.target.widget_id() == new.target.widget_id() && prev.target != new.target,
_ => false,
}
}
pub fn is_mode_change(&self) -> bool {
match (&self.prev_capture, &self.new_capture) {
(Some(prev), Some(new)) => prev.target.widget_id() == new.target.widget_id() && prev.mode != new.mode,
_ => false,
}
}
pub fn is_lost(&self, widget_id: WidgetId) -> bool {
match (&self.prev_capture, &self.new_capture) {
(None, _) => false,
(Some(p), None) => p.target.widget_id() == widget_id,
(Some(prev), Some(new)) => prev.target.widget_id() == widget_id && new.target.widget_id() != widget_id,
}
}
pub fn is_got(&self, widget_id: WidgetId) -> bool {
match (&self.prev_capture, &self.new_capture) {
(_, None) => false,
(None, Some(p)) => p.target.widget_id() == widget_id,
(Some(prev), Some(new)) => prev.target.widget_id() != widget_id && new.target.widget_id() == widget_id,
}
}
}