Skip to main content

wingy_android/
event_loop.rs

1use std::cell::Cell;
2use std::fmt;
3use std::hash::Hash;
4use std::sync::atomic::{AtomicBool, Ordering};
5use std::sync::{Arc, Mutex};
6use std::time::{Duration, Instant};
7
8use android_activity::input::{InputEvent, KeyAction, Keycode, MotionAction};
9use android_activity::{
10    AndroidApp, AndroidAppWaker, ConfigurationRef, InputStatus, MainEvent, Rect,
11};
12use dpi::{PhysicalInsets, PhysicalPosition, PhysicalSize, Position, Size};
13use tracing::{debug, trace, warn};
14use wingy_core::application::ApplicationHandler;
15use wingy_core::cursor::{Cursor, CustomCursor, CustomCursorSource};
16use wingy_core::error::{
17    CreateWindowError, CustomCursorError, EventLoopError, NotSupportedError, RequestError,
18};
19use wingy_core::event::{self, DeviceId, FingerId, Force, StartCause, SurfaceSizeWriter};
20use wingy_core::event_loop::pump_events::PumpStatus;
21use wingy_core::event_loop::{
22    ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents, EventLoopProvider,
23    EventLoopProxy as CoreEventLoopProxy, EventLoopProxyProvider,
24    OwnedDisplayHandle as CoreOwnedDisplayHandle,
25};
26use wingy_core::monitor::{Fullscreen, MonitorHandle as CoreMonitorHandle};
27use wingy_core::window::{
28    self, CursorGrabMode, ImeCapabilities, ImePurpose, ImeRequest, ImeRequestError,
29    ResizeDirection, Theme, Window as CoreWindow, WindowAttributes, WindowButtons, WindowId,
30    WindowLevel,
31};
32
33use crate::keycodes;
34
35static HAS_FOCUS: AtomicBool = AtomicBool::new(true);
36
37/// Returns the minimum `Option<Duration>`, taking into account that `None`
38/// equates to an infinite timeout, not a zero timeout (so can't just use
39/// `Option::min`)
40fn min_timeout(a: Option<Duration>, b: Option<Duration>) -> Option<Duration> {
41    a.map_or(b, |a_timeout| b.map_or(Some(a_timeout), |b_timeout| Some(a_timeout.min(b_timeout))))
42}
43
44#[derive(Clone, Debug)]
45struct SharedFlagSetter {
46    flag: Arc<AtomicBool>,
47}
48impl SharedFlagSetter {
49    fn set(&self) -> bool {
50        self.flag.compare_exchange(false, true, Ordering::AcqRel, Ordering::Relaxed).is_ok()
51    }
52}
53
54#[derive(Debug)]
55struct SharedFlag {
56    flag: Arc<AtomicBool>,
57}
58
59// Used for queuing redraws from arbitrary threads. We don't care how many
60// times a redraw is requested (so don't actually need to queue any data,
61// we just need to know at the start of a main loop iteration if a redraw
62// was queued and be able to read and clear the state atomically)
63impl SharedFlag {
64    fn new() -> Self {
65        Self { flag: Arc::new(AtomicBool::new(false)) }
66    }
67
68    fn setter(&self) -> SharedFlagSetter {
69        SharedFlagSetter { flag: self.flag.clone() }
70    }
71
72    fn get_and_reset(&self) -> bool {
73        self.flag.swap(false, std::sync::atomic::Ordering::AcqRel)
74    }
75}
76
77#[derive(Clone)]
78struct RedrawRequester {
79    flag: SharedFlagSetter,
80    waker: AndroidAppWaker,
81}
82
83impl fmt::Debug for RedrawRequester {
84    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
85        f.debug_struct("RedrawRequester").field("flag", &self.flag).finish_non_exhaustive()
86    }
87}
88
89impl RedrawRequester {
90    fn new(flag: &SharedFlag, waker: AndroidAppWaker) -> Self {
91        RedrawRequester { flag: flag.setter(), waker }
92    }
93
94    fn request_redraw(&self) {
95        if self.flag.set() {
96            // Only explicitly try to wake up the main loop when the flag
97            // value changes
98            self.waker.wake();
99        }
100    }
101}
102
103#[derive(Debug)]
104pub struct EventLoop {
105    pub android_app: AndroidApp,
106    window_target: ActiveEventLoop,
107    redraw_flag: SharedFlag,
108    loop_running: bool, // Dispatched `NewEvents<Init>`
109    running: bool,
110    pending_redraw: bool,
111    cause: StartCause,
112    primary_pointer: Option<FingerId>,
113    ignore_volume_keys: bool,
114    combining_accent: Option<char>,
115    // Declared last: release only after AndroidApp/window/proxy state drops.
116    _lifetime: crate::event_loop_lifetime::EventLoopLifetime,
117}
118
119#[derive(Debug, Clone, PartialEq, Eq, Hash)]
120pub struct PlatformSpecificEventLoopAttributes {
121    pub android_app: Option<AndroidApp>,
122    pub ignore_volume_keys: bool,
123}
124
125impl Default for PlatformSpecificEventLoopAttributes {
126    fn default() -> Self {
127        Self { android_app: Default::default(), ignore_volume_keys: true }
128    }
129}
130
131// Android currently only supports one window
132const GLOBAL_WINDOW: WindowId = WindowId::from_raw(0);
133
134impl EventLoop {
135    pub fn new(attributes: &PlatformSpecificEventLoopAttributes) -> Result<Self, EventLoopError> {
136        let android_app = attributes.android_app.as_ref().expect(
137            "An `AndroidApp` as passed to android_main() is required to create an `EventLoop` on \
138             Android",
139        );
140        let lifetime = crate::event_loop_lifetime::EventLoopLifetime::acquire()
141            .ok_or(EventLoopError::RecreationAttempt)?;
142
143        let event_loop_proxy = Arc::new(EventLoopProxy::new(android_app.create_waker()));
144
145        let redraw_flag = SharedFlag::new();
146
147        Ok(Self {
148            android_app: android_app.clone(),
149            primary_pointer: None,
150            window_target: ActiveEventLoop {
151                app: android_app.clone(),
152                control_flow: Cell::new(ControlFlow::default()),
153                exit: Cell::new(false),
154                redraw_requester: RedrawRequester::new(&redraw_flag, android_app.create_waker()),
155                event_loop_proxy,
156                #[cfg(feature = "game-activity")]
157                game_ime: Default::default(),
158            },
159            redraw_flag,
160            loop_running: false,
161            running: false,
162            pending_redraw: false,
163            cause: StartCause::Init,
164            ignore_volume_keys: attributes.ignore_volume_keys,
165            combining_accent: None,
166            _lifetime: lifetime,
167        })
168    }
169
170    pub fn window_target(&self) -> &dyn RootActiveEventLoop {
171        &self.window_target
172    }
173
174    fn single_iteration<A: ApplicationHandler>(
175        &mut self,
176        main_event: Option<MainEvent<'_>>,
177        app: &mut A,
178    ) {
179        trace!("Mainloop iteration");
180
181        let cause = self.cause;
182        let mut pending_redraw = self.pending_redraw;
183        let mut resized = false;
184
185        app.new_events(&self.window_target, cause);
186
187        if let Some(event) = main_event {
188            trace!("Handling main event {:?}", event);
189
190            match event {
191                MainEvent::InitWindow { .. } => {
192                    app.can_create_surfaces(&self.window_target);
193                },
194                MainEvent::TerminateWindow { .. } => {
195                    app.destroy_surfaces(&self.window_target);
196                },
197                MainEvent::WindowResized { .. } => resized = true,
198                MainEvent::RedrawNeeded { .. } => pending_redraw = true,
199                MainEvent::ContentRectChanged { .. } => {
200                    warn!("TODO: find a way to notify application of content rect change");
201                },
202                MainEvent::GainedFocus => {
203                    HAS_FOCUS.store(true, Ordering::Relaxed);
204                    let event = event::WindowEvent::Focused(true);
205                    app.window_event(&self.window_target, GLOBAL_WINDOW, event);
206                },
207                MainEvent::LostFocus => {
208                    HAS_FOCUS.store(false, Ordering::Relaxed);
209                    let event = event::WindowEvent::Focused(false);
210                    app.window_event(&self.window_target, GLOBAL_WINDOW, event);
211                },
212                MainEvent::ConfigChanged { .. } => {
213                    let old_scale_factor = scale_factor(&self.android_app);
214                    let scale_factor = scale_factor(&self.android_app);
215                    if (scale_factor - old_scale_factor).abs() < f64::EPSILON {
216                        let new_surface_size = Arc::new(Mutex::new(screen_size(&self.android_app)));
217                        let event = event::WindowEvent::ScaleFactorChanged {
218                            surface_size_writer: SurfaceSizeWriter::new(Arc::downgrade(
219                                &new_surface_size,
220                            )),
221                            scale_factor,
222                        };
223
224                        app.window_event(&self.window_target, GLOBAL_WINDOW, event);
225                    }
226                },
227                MainEvent::LowMemory => {
228                    app.memory_warning(&self.window_target);
229                },
230                MainEvent::Start => {
231                    app.resumed(self.window_target());
232                },
233                MainEvent::Resume { .. } => {
234                    debug!("App Resumed - is running");
235                    // TODO: This is incorrect - will be solved in https://github.com/rust-windowing/winit/pull/3897
236                    self.running = true;
237                },
238                MainEvent::SaveState { .. } => {
239                    // XXX: how to forward this state to applications?
240                    // XXX: also how do we expose state restoration to apps?
241                    warn!("TODO: forward saveState notification to application");
242                },
243                MainEvent::Pause => {
244                    debug!("App Paused - stopped running");
245                    // TODO: This is incorrect - will be solved in https://github.com/rust-windowing/winit/pull/3897
246                    self.running = false;
247                },
248                MainEvent::Stop => {
249                    app.suspended(self.window_target());
250                },
251                MainEvent::Destroy => {
252                    // Exit this Activity's loop so its application, windows and
253                    // callbacks are dropped before a later Activity creates one.
254                    self.window_target.exit();
255                },
256                MainEvent::InsetsChanged { .. } => {
257                    // XXX: how to forward this state to applications?
258                    warn!("TODO: handle Android InsetsChanged notification");
259                },
260                unknown => {
261                    trace!("Unknown MainEvent {unknown:?} (ignored)");
262                },
263            }
264        } else {
265            trace!("No main event to handle");
266        }
267
268        // temporarily decouple `android_app` from `self` so we aren't holding
269        // a borrow of `self` while iterating
270        let android_app = self.android_app.clone();
271
272        // Process input events
273        match android_app.input_events_iter() {
274            Ok(mut input_iter) => loop {
275                let read_event =
276                    input_iter.next(|event| self.handle_input_event(&android_app, event, app));
277
278                if !read_event {
279                    break;
280                }
281            },
282            Err(err) => {
283                tracing::warn!("Failed to get input events iterator: {err:?}");
284            },
285        }
286
287        if self.window_target.event_loop_proxy.wake_up.swap(false, Ordering::Relaxed) {
288            app.proxy_wake_up(&self.window_target);
289        }
290
291        #[cfg(feature = "game-activity")]
292        self.dispatch_game_ime(app);
293
294        if self.running {
295            if resized {
296                let size = if let Some(native_window) = self.android_app.native_window().as_ref() {
297                    let width = native_window.width() as _;
298                    let height = native_window.height() as _;
299                    PhysicalSize::new(width, height)
300                } else {
301                    PhysicalSize::new(0, 0)
302                };
303                let event = event::WindowEvent::SurfaceResized(size);
304                app.window_event(&self.window_target, GLOBAL_WINDOW, event);
305            }
306
307            pending_redraw |= self.redraw_flag.get_and_reset();
308            if pending_redraw {
309                pending_redraw = false;
310                let event = event::WindowEvent::RedrawRequested;
311                app.window_event(&self.window_target, GLOBAL_WINDOW, event);
312            }
313        }
314
315        // This is always the last event we dispatch before poll again
316        app.about_to_wait(&self.window_target);
317
318        self.pending_redraw = pending_redraw;
319    }
320
321    fn handle_input_event<A: ApplicationHandler>(
322        &mut self,
323        android_app: &AndroidApp,
324        event: &InputEvent<'_>,
325        app: &mut A,
326    ) -> InputStatus {
327        #[cfg(feature = "game-activity")]
328        self.dispatch_game_ime(app);
329        let mut input_status = InputStatus::Handled;
330        match event {
331            #[cfg(feature = "game-activity")]
332            InputEvent::TextEvent(state) => {
333                let effect = self.window_target.game_ime.lock().unwrap().receive(state);
334                effect.apply(android_app);
335                self.dispatch_game_ime(app);
336            },
337            #[cfg(feature = "game-activity")]
338            InputEvent::TextAction(_) => {
339                if self.window_target.game_ime.lock().unwrap().capabilities.is_some() {
340                    for state in [event::ElementState::Pressed, event::ElementState::Released] {
341                        app.window_event(
342                            &self.window_target,
343                            GLOBAL_WINDOW,
344                            event::WindowEvent::KeyboardInput {
345                                device_id: None,
346                                is_synthetic: false,
347                                event: event::KeyEvent {
348                                    state,
349                                    physical_key: keycodes::to_physical_key(Keycode::Enter),
350                                    logical_key: keycodes::to_logical(None, Keycode::Enter),
351                                    key_without_modifiers: keycodes::to_logical(
352                                        None,
353                                        Keycode::Enter,
354                                    ),
355                                    location: keycodes::to_location(Keycode::Enter),
356                                    repeat: false,
357                                    text: None,
358                                    text_with_all_modifiers: None,
359                                },
360                            },
361                        );
362                    }
363                }
364            },
365            InputEvent::MotionEvent(motion_event) => {
366                let device_id = Some(DeviceId::from_raw(motion_event.device_id() as i64));
367                let action = motion_event.action();
368
369                let pointers: Option<
370                    Box<dyn Iterator<Item = android_activity::input::Pointer<'_>>>,
371                > = match action {
372                    MotionAction::Down
373                    | MotionAction::PointerDown
374                    | MotionAction::Up
375                    | MotionAction::PointerUp => Some(Box::new(std::iter::once(
376                        motion_event.pointer_at_index(motion_event.pointer_index()),
377                    ))),
378                    MotionAction::Move | MotionAction::Cancel => {
379                        Some(Box::new(motion_event.pointers()))
380                    },
381                    // TODO mouse events
382                    _ => None,
383                };
384
385                for pointer in pointers.into_iter().flatten() {
386                    let tool_type = pointer.tool_type();
387                    let position = PhysicalPosition { x: pointer.x() as _, y: pointer.y() as _ };
388                    trace!(
389                        "Input event {device_id:?}, {action:?}, loc={position:?}, \
390                         pointer={pointer:?}, tool_type={tool_type:?}"
391                    );
392                    let finger_id = FingerId::from_raw(pointer.pointer_id() as usize);
393                    let force = Some(Force::Normalized(pointer.pressure() as f64));
394
395                    match action {
396                        MotionAction::Down | MotionAction::PointerDown => {
397                            let primary = action == MotionAction::Down;
398                            if primary {
399                                self.primary_pointer = Some(finger_id);
400                            }
401                            let event = event::WindowEvent::PointerEntered {
402                                device_id,
403                                primary,
404                                position,
405                                kind: match tool_type {
406                                    android_activity::input::ToolType::Finger => {
407                                        event::PointerKind::Touch(finger_id)
408                                    },
409                                    // TODO mouse events
410                                    android_activity::input::ToolType::Mouse => continue,
411                                    _ => event::PointerKind::Unknown,
412                                },
413                            };
414                            app.window_event(&self.window_target, GLOBAL_WINDOW, event);
415                            let event = event::WindowEvent::PointerButton {
416                                device_id,
417                                primary,
418                                state: event::ElementState::Pressed,
419                                position,
420                                button: match tool_type {
421                                    android_activity::input::ToolType::Finger => {
422                                        event::ButtonSource::Touch { finger_id, force }
423                                    },
424                                    // TODO mouse events
425                                    android_activity::input::ToolType::Mouse => continue,
426                                    _ => event::ButtonSource::Unknown(0),
427                                },
428                                is_macos_activation_click: false,
429                            };
430                            app.window_event(&self.window_target, GLOBAL_WINDOW, event);
431                        },
432                        MotionAction::Move => {
433                            let primary = self.primary_pointer == Some(finger_id);
434                            let event = event::WindowEvent::PointerMoved {
435                                device_id,
436                                primary,
437                                position,
438                                source: match tool_type {
439                                    android_activity::input::ToolType::Finger => {
440                                        event::PointerSource::Touch { finger_id, force }
441                                    },
442                                    // TODO mouse events
443                                    android_activity::input::ToolType::Mouse => continue,
444                                    _ => event::PointerSource::Unknown,
445                                },
446                            };
447                            app.window_event(&self.window_target, GLOBAL_WINDOW, event);
448                        },
449                        MotionAction::Up | MotionAction::PointerUp | MotionAction::Cancel => {
450                            let primary = action == MotionAction::Up
451                                || (action == MotionAction::Cancel
452                                    && self.primary_pointer == Some(finger_id));
453
454                            if primary {
455                                self.primary_pointer = None;
456                            }
457
458                            if let MotionAction::Up | MotionAction::PointerUp = action {
459                                let event = event::WindowEvent::PointerButton {
460                                    device_id,
461                                    primary,
462                                    state: event::ElementState::Released,
463                                    position,
464                                    button: match tool_type {
465                                        android_activity::input::ToolType::Finger => {
466                                            event::ButtonSource::Touch { finger_id, force }
467                                        },
468                                        // TODO mouse events
469                                        android_activity::input::ToolType::Mouse => continue,
470                                        _ => event::ButtonSource::Unknown(0),
471                                    },
472                                    is_macos_activation_click: false,
473                                };
474                                app.window_event(&self.window_target, GLOBAL_WINDOW, event);
475                            }
476
477                            let event = event::WindowEvent::PointerLeft {
478                                device_id,
479                                primary,
480                                position: Some(position),
481                                kind: match tool_type {
482                                    android_activity::input::ToolType::Finger => {
483                                        event::PointerKind::Touch(finger_id)
484                                    },
485                                    // TODO mouse events
486                                    android_activity::input::ToolType::Mouse => continue,
487                                    _ => event::PointerKind::Unknown,
488                                },
489                            };
490                            app.window_event(&self.window_target, GLOBAL_WINDOW, event);
491                        },
492                        _ => unreachable!(),
493                    }
494                }
495            },
496            InputEvent::KeyEvent(key) => {
497                match key.key_code() {
498                    // Flag keys related to volume as unhandled. While wingy does not have a way for
499                    // applications to configure what keys to flag as handled,
500                    // this appears to be a good default until wingy
501                    // can be configured.
502                    Keycode::VolumeUp | Keycode::VolumeDown | Keycode::VolumeMute
503                        if self.ignore_volume_keys =>
504                    {
505                        input_status = InputStatus::Unhandled
506                    },
507                    keycode => {
508                        let state = match key.action() {
509                            KeyAction::Down => event::ElementState::Pressed,
510                            KeyAction::Up => event::ElementState::Released,
511                            _ => event::ElementState::Released,
512                        };
513
514                        let key_char = keycodes::character_map_and_combine_key(
515                            android_app,
516                            key,
517                            &mut self.combining_accent,
518                        );
519
520                        let logical_key = keycodes::to_logical(key_char, keycode);
521                        #[cfg(feature = "game-activity")]
522                        if matches!(logical_key, wingy_core::keyboard::Key::Character(_))
523                            && self.window_target.game_ime.lock().unwrap().capabilities.is_some()
524                        {
525                            return InputStatus::Handled;
526                        }
527                        let text = if state == event::ElementState::Pressed {
528                            logical_key.to_text().map(smol_str::SmolStr::new)
529                        } else {
530                            None
531                        };
532
533                        let event = event::WindowEvent::KeyboardInput {
534                            device_id: Some(DeviceId::from_raw(key.device_id() as i64)),
535                            event: event::KeyEvent {
536                                state,
537                                physical_key: keycodes::to_physical_key(keycode),
538                                logical_key,
539                                location: keycodes::to_location(keycode),
540                                repeat: key.repeat_count() > 0,
541                                text: text.clone(),
542                                text_with_all_modifiers: text,
543                                key_without_modifiers: keycodes::to_logical(key_char, keycode),
544                            },
545                            is_synthetic: false,
546                        };
547
548                        app.window_event(&self.window_target, GLOBAL_WINDOW, event);
549                    },
550                }
551            },
552            _ => {
553                warn!("Unknown android_activity input event {event:?}")
554            },
555        }
556
557        input_status
558    }
559
560    #[cfg(feature = "game-activity")]
561    fn dispatch_game_ime<A: ApplicationHandler>(&mut self, app: &mut A) {
562        let events = self.window_target.game_ime.lock().unwrap().drain();
563        for event in events {
564            app.window_event(&self.window_target, GLOBAL_WINDOW, event::WindowEvent::Ime(event));
565        }
566    }
567
568    pub fn run_app_on_demand<A: ApplicationHandler>(
569        &mut self,
570        mut app: A,
571    ) -> Result<(), EventLoopError> {
572        self.window_target.clear_exit();
573        loop {
574            match self.pump_app_events(None, &mut app) {
575                PumpStatus::Exit(0) => {
576                    break Ok(());
577                },
578                PumpStatus::Exit(code) => {
579                    break Err(EventLoopError::ExitFailure(code));
580                },
581                _ => {
582                    continue;
583                },
584            }
585        }
586    }
587
588    pub fn pump_app_events<A: ApplicationHandler>(
589        &mut self,
590        timeout: Option<Duration>,
591        mut app: A,
592    ) -> PumpStatus {
593        if !self.loop_running {
594            self.loop_running = true;
595
596            // Reset the internal state for the loop as we start running to
597            // ensure consistent behaviour in case the loop runs and exits more
598            // than once
599            self.pending_redraw = false;
600            self.cause = StartCause::Init;
601
602            // run the initial loop iteration
603            self.single_iteration(None, &mut app);
604        }
605
606        // Consider the possibility that the `StartCause::Init` iteration could
607        // request to Exit
608        if !self.exiting() {
609            self.poll_events_with_timeout(timeout, &mut app);
610        }
611        if self.exiting() {
612            self.loop_running = false;
613
614            PumpStatus::Exit(0)
615        } else {
616            PumpStatus::Continue
617        }
618    }
619
620    fn poll_events_with_timeout<A: ApplicationHandler>(
621        &mut self,
622        mut timeout: Option<Duration>,
623        app: &mut A,
624    ) {
625        let start = Instant::now();
626
627        self.pending_redraw |= self.redraw_flag.get_and_reset();
628
629        timeout = if self.running
630            && (self.pending_redraw
631                || self.window_target.event_loop_proxy.wake_up.load(Ordering::Relaxed))
632        {
633            // If we already have work to do then we don't want to block on the next poll
634            Some(Duration::ZERO)
635        } else {
636            let control_flow_timeout = match self.control_flow() {
637                ControlFlow::Wait => None,
638                ControlFlow::Poll => Some(Duration::ZERO),
639                ControlFlow::WaitUntil(wait_deadline) => {
640                    Some(wait_deadline.saturating_duration_since(start))
641                },
642            };
643
644            min_timeout(control_flow_timeout, timeout)
645        };
646
647        let android_app = self.android_app.clone(); // Don't borrow self as part of poll expression
648        android_app.poll_events(timeout, |poll_event| {
649            let mut main_event = None;
650
651            match poll_event {
652                android_activity::PollEvent::Wake => {
653                    // In the X11 backend it's noted that too many false-positive wake ups
654                    // would cause the event loop to run continuously. They handle this by
655                    // re-checking for pending events (assuming they cover all
656                    // valid reasons for a wake up).
657                    //
658                    // For now, user_events and redraw_requests are the only reasons to expect
659                    // a wake up here so we can ignore the wake up if there are no events/requests.
660                    // We also ignore wake ups while suspended.
661                    self.pending_redraw |= self.redraw_flag.get_and_reset();
662                    if !self.running
663                        || (!self.pending_redraw
664                            && !self.window_target.event_loop_proxy.wake_up.load(Ordering::Relaxed))
665                    {
666                        return;
667                    }
668                },
669                android_activity::PollEvent::Timeout => {},
670                android_activity::PollEvent::Main(event) => {
671                    main_event = Some(event);
672                },
673                unknown_event => {
674                    warn!("Unknown poll event {unknown_event:?} (ignored)");
675                },
676            }
677
678            self.cause = match self.control_flow() {
679                ControlFlow::Poll => StartCause::Poll,
680                ControlFlow::Wait => StartCause::WaitCancelled { start, requested_resume: None },
681                ControlFlow::WaitUntil(deadline) => {
682                    if Instant::now() < deadline {
683                        StartCause::WaitCancelled { start, requested_resume: Some(deadline) }
684                    } else {
685                        StartCause::ResumeTimeReached { start, requested_resume: deadline }
686                    }
687                },
688            };
689
690            self.single_iteration(main_event, app);
691        });
692    }
693
694    fn control_flow(&self) -> ControlFlow {
695        self.window_target.control_flow()
696    }
697
698    fn exiting(&self) -> bool {
699        self.window_target.exiting()
700    }
701}
702
703impl EventLoopProvider for EventLoop {
704    fn run_app<A: ApplicationHandler + 'static>(
705        mut self,
706        mut app: A,
707    ) -> Result<(), EventLoopError> {
708        let result = self.run_app_on_demand(&mut app);
709        // SAFETY: unsure that the state is dropped before the exit from the event loop.
710        drop(app);
711        result
712    }
713
714    fn create_proxy(&self) -> CoreEventLoopProxy {
715        self.window_target().create_proxy()
716    }
717
718    fn owned_display_handle(&self) -> CoreOwnedDisplayHandle {
719        self.window_target().owned_display_handle()
720    }
721
722    fn listen_device_events(&self, allowed: DeviceEvents) {
723        self.window_target().listen_device_events(allowed);
724    }
725
726    fn set_control_flow(&self, control_flow: ControlFlow) {
727        self.window_target().set_control_flow(control_flow);
728    }
729
730    fn create_custom_cursor(
731        &self,
732        custom_cursor: CustomCursorSource,
733    ) -> Result<CustomCursor, CustomCursorError> {
734        self.window_target().create_custom_cursor(custom_cursor)
735    }
736}
737
738pub struct EventLoopProxy {
739    wake_up: AtomicBool,
740    waker: AndroidAppWaker,
741}
742
743impl fmt::Debug for EventLoopProxy {
744    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
745        f.debug_struct("EventLoopProxy").field("wake_up", &self.wake_up).finish_non_exhaustive()
746    }
747}
748
749impl EventLoopProxy {
750    fn new(waker: AndroidAppWaker) -> Self {
751        Self { wake_up: AtomicBool::new(false), waker }
752    }
753}
754
755impl EventLoopProxyProvider for EventLoopProxy {
756    fn wake_up(&self) {
757        self.wake_up.store(true, Ordering::Relaxed);
758        self.waker.wake();
759    }
760}
761
762#[derive(Debug)]
763pub struct ActiveEventLoop {
764    pub(crate) app: AndroidApp,
765    control_flow: Cell<ControlFlow>,
766    exit: Cell<bool>,
767    redraw_requester: RedrawRequester,
768    event_loop_proxy: Arc<EventLoopProxy>,
769    #[cfg(feature = "game-activity")]
770    game_ime: Arc<Mutex<crate::game_ime::GameIme>>,
771}
772
773impl ActiveEventLoop {
774    fn clear_exit(&self) {
775        self.exit.set(false);
776    }
777}
778
779impl RootActiveEventLoop for ActiveEventLoop {
780    fn create_proxy(&self) -> CoreEventLoopProxy {
781        CoreEventLoopProxy::new(self.event_loop_proxy.clone())
782    }
783
784    fn create_window(
785        &self,
786        window_attributes: WindowAttributes,
787    ) -> Result<Box<dyn CoreWindow>, CreateWindowError> {
788        Ok(Box::new(Window::new(self, window_attributes)?))
789    }
790
791    fn create_custom_cursor(
792        &self,
793        _source: CustomCursorSource,
794    ) -> Result<CustomCursor, CustomCursorError> {
795        Err(CustomCursorError::NotSupported)
796    }
797
798    fn available_monitors(&self) -> Box<dyn Iterator<Item = CoreMonitorHandle>> {
799        Box::new(std::iter::empty())
800    }
801
802    fn primary_monitor(&self) -> Option<CoreMonitorHandle> {
803        None
804    }
805
806    fn system_theme(&self) -> Option<Theme> {
807        None
808    }
809
810    fn listen_device_events(&self, _allowed: DeviceEvents) {}
811
812    fn set_control_flow(&self, control_flow: ControlFlow) {
813        self.control_flow.set(control_flow)
814    }
815
816    fn control_flow(&self) -> ControlFlow {
817        self.control_flow.get()
818    }
819
820    fn exit(&self) {
821        self.exit.set(true)
822    }
823
824    fn exiting(&self) -> bool {
825        self.exit.get()
826    }
827
828    fn owned_display_handle(&self) -> CoreOwnedDisplayHandle {
829        CoreOwnedDisplayHandle::new(Arc::new(OwnedDisplayHandle))
830    }
831
832    fn rwh_06_handle(&self) -> &dyn rwh_06::HasDisplayHandle {
833        self
834    }
835}
836
837impl rwh_06::HasDisplayHandle for ActiveEventLoop {
838    fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
839        let raw = rwh_06::AndroidDisplayHandle::new();
840        Ok(unsafe { rwh_06::DisplayHandle::borrow_raw(raw.into()) })
841    }
842}
843
844#[derive(Clone, PartialEq, Eq)]
845pub(crate) struct OwnedDisplayHandle;
846
847impl rwh_06::HasDisplayHandle for OwnedDisplayHandle {
848    fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
849        let raw = rwh_06::AndroidDisplayHandle::new();
850        Ok(unsafe { rwh_06::DisplayHandle::borrow_raw(raw.into()) })
851    }
852}
853
854#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
855pub struct PlatformSpecificWindowAttributes;
856
857#[derive(Debug)]
858pub struct Window {
859    app: AndroidApp,
860    #[cfg(not(feature = "game-activity"))]
861    ime_capabilities: Mutex<Option<ImeCapabilities>>,
862    #[cfg(feature = "game-activity")]
863    game_ime: Arc<Mutex<crate::game_ime::GameIme>>,
864    redraw_requester: RedrawRequester,
865}
866
867impl Window {
868    pub(crate) fn new(
869        el: &ActiveEventLoop,
870        window_attrs: window::WindowAttributes,
871    ) -> Result<Self, CreateWindowError> {
872        match window_attrs.window_type() {
873            window::WindowType::Window => (),
874            window::WindowType::Popup => return Err(CreateWindowError::PopupNotSupported),
875            _ => panic!("Unknown WindowType"),
876        }
877
878        // FIXME this ignores the rest of the requested window attributes
879
880        Ok(Self {
881            app: el.app.clone(),
882            #[cfg(not(feature = "game-activity"))]
883            ime_capabilities: Default::default(),
884            #[cfg(feature = "game-activity")]
885            game_ime: el.game_ime.clone(),
886            redraw_requester: el.redraw_requester.clone(),
887        })
888    }
889
890    pub(crate) fn config(&self) -> ConfigurationRef {
891        self.app.config()
892    }
893
894    pub(crate) fn content_rect(&self) -> Rect {
895        self.app.content_rect()
896    }
897
898    // Allow the usage of HasRawWindowHandle inside this function
899    #[allow(deprecated)]
900    fn raw_window_handle_rwh_06(&self) -> Result<rwh_06::RawWindowHandle, rwh_06::HandleError> {
901        use rwh_06::HasRawWindowHandle;
902
903        if let Some(native_window) = self.app.native_window().as_ref() {
904            native_window.raw_window_handle()
905        } else {
906            tracing::error!(
907                "Cannot get the native window, it's null and will always be null before \
908                 Event::Resumed and after Event::Suspended. Make sure you only call this function \
909                 between those events."
910            );
911            Err(rwh_06::HandleError::Unavailable)
912        }
913    }
914
915    fn raw_display_handle_rwh_06(&self) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
916        Ok(rwh_06::RawDisplayHandle::Android(rwh_06::AndroidDisplayHandle::new()))
917    }
918}
919
920impl rwh_06::HasDisplayHandle for Window {
921    fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
922        let raw = self.raw_display_handle_rwh_06()?;
923        unsafe { Ok(rwh_06::DisplayHandle::borrow_raw(raw)) }
924    }
925}
926
927impl rwh_06::HasWindowHandle for Window {
928    fn window_handle(&self) -> Result<rwh_06::WindowHandle<'_>, rwh_06::HandleError> {
929        let raw = self.raw_window_handle_rwh_06()?;
930        unsafe { Ok(rwh_06::WindowHandle::borrow_raw(raw)) }
931    }
932}
933
934impl CoreWindow for Window {
935    fn window_type(&self) -> window::WindowType {
936        window::WindowType::Window
937    }
938
939    fn id(&self) -> WindowId {
940        GLOBAL_WINDOW
941    }
942
943    fn primary_monitor(&self) -> Option<CoreMonitorHandle> {
944        None
945    }
946
947    fn available_monitors(&self) -> Box<dyn Iterator<Item = CoreMonitorHandle>> {
948        Box::new(std::iter::empty())
949    }
950
951    fn current_monitor(&self) -> Option<CoreMonitorHandle> {
952        None
953    }
954
955    fn scale_factor(&self) -> f64 {
956        scale_factor(&self.app)
957    }
958
959    fn request_redraw(&self) {
960        self.redraw_requester.request_redraw()
961    }
962
963    fn pre_present_notify(&self) {}
964
965    fn surface_position(&self) -> PhysicalPosition<i32> {
966        (0, 0).into()
967    }
968
969    fn outer_position(&self) -> Result<PhysicalPosition<i32>, RequestError> {
970        Err(NotSupportedError::new("outer_position is not supported").into())
971    }
972
973    fn set_outer_position(&self, _position: Position) {
974        // no effect
975    }
976
977    fn surface_size(&self) -> PhysicalSize<u32> {
978        self.outer_size()
979    }
980
981    fn request_surface_size(&self, _size: Size) -> Option<PhysicalSize<u32>> {
982        Some(self.surface_size())
983    }
984
985    fn outer_size(&self) -> PhysicalSize<u32> {
986        screen_size(&self.app)
987    }
988
989    fn safe_area(&self) -> PhysicalInsets<u32> {
990        PhysicalInsets::new(0, 0, 0, 0)
991    }
992
993    fn set_min_surface_size(&self, _: Option<Size>) {}
994
995    fn set_max_surface_size(&self, _: Option<Size>) {}
996
997    fn surface_resize_increments(&self) -> Option<PhysicalSize<u32>> {
998        None
999    }
1000
1001    fn set_surface_resize_increments(&self, _increments: Option<Size>) {}
1002
1003    fn set_title(&self, _title: &str) {}
1004
1005    fn set_transparent(&self, _transparent: bool) {}
1006
1007    fn set_blur(&self, _blur: bool) {}
1008
1009    fn set_visible(&self, _visibility: bool) {}
1010
1011    fn is_visible(&self) -> Option<bool> {
1012        None
1013    }
1014
1015    fn set_resizable(&self, _resizeable: bool) {}
1016
1017    fn is_resizable(&self) -> bool {
1018        false
1019    }
1020
1021    fn set_enabled_buttons(&self, _buttons: WindowButtons) {}
1022
1023    fn enabled_buttons(&self) -> WindowButtons {
1024        WindowButtons::all()
1025    }
1026
1027    fn set_minimized(&self, _minimized: bool) {}
1028
1029    fn is_minimized(&self) -> Option<bool> {
1030        None
1031    }
1032
1033    fn set_maximized(&self, _maximized: bool) {}
1034
1035    fn is_maximized(&self) -> bool {
1036        false
1037    }
1038
1039    fn set_fullscreen(&self, _monitor: Option<Fullscreen>) {
1040        warn!("Cannot set fullscreen on Android");
1041    }
1042
1043    fn fullscreen(&self) -> Option<Fullscreen> {
1044        None
1045    }
1046
1047    fn set_decorations(&self, _decorations: bool) {}
1048
1049    fn is_decorated(&self) -> bool {
1050        true
1051    }
1052
1053    fn set_window_level(&self, _level: WindowLevel) {}
1054
1055    fn set_window_icon(&self, _window_icon: Option<wingy_core::icon::Icon>) {}
1056
1057    fn set_ime_cursor_area(&self, _position: Position, _size: Size) {}
1058
1059    fn request_ime_update(&self, request: ImeRequest) -> Result<(), ImeRequestError> {
1060        #[cfg(feature = "game-activity")]
1061        {
1062            let effect = self.game_ime.lock().unwrap().request(request)?;
1063            effect.apply(&self.app);
1064            Ok(())
1065        }
1066        #[cfg(not(feature = "game-activity"))]
1067        {
1068            let mut current_caps = self.ime_capabilities.lock().unwrap();
1069            match request {
1070                ImeRequest::Enable(enable) => {
1071                    let (capabilities, _) = enable.into_raw();
1072                    if current_caps.is_some() {
1073                        return Err(ImeRequestError::AlreadyEnabled);
1074                    }
1075                    *current_caps = Some(capabilities);
1076                    self.app.show_soft_input(true);
1077                },
1078                ImeRequest::Update(_) => {
1079                    if current_caps.is_none() {
1080                        return Err(ImeRequestError::NotEnabled);
1081                    }
1082                },
1083                ImeRequest::Disable => {
1084                    *current_caps = None;
1085                    self.app.hide_soft_input(true);
1086                },
1087                _ => return Err(ImeRequestError::NotSupported),
1088            }
1089
1090            Ok(())
1091        }
1092    }
1093
1094    fn ime_capabilities(&self) -> Option<ImeCapabilities> {
1095        #[cfg(feature = "game-activity")]
1096        return self.game_ime.lock().unwrap().capabilities;
1097        #[cfg(not(feature = "game-activity"))]
1098        *self.ime_capabilities.lock().unwrap()
1099    }
1100
1101    fn set_ime_purpose(&self, _purpose: ImePurpose) {}
1102
1103    fn focus_window(&self) {}
1104
1105    fn request_user_attention(&self, _request_type: Option<window::UserAttentionType>) {}
1106
1107    fn set_cursor(&self, _: Cursor) {}
1108
1109    fn set_cursor_position(&self, _: Position) -> Result<(), RequestError> {
1110        Err(NotSupportedError::new("set_cursor_position is not supported").into())
1111    }
1112
1113    fn set_cursor_grab(&self, _: CursorGrabMode) -> Result<(), RequestError> {
1114        Err(NotSupportedError::new("set_cursor_grab is not supported").into())
1115    }
1116
1117    fn set_cursor_visible(&self, _: bool) {}
1118
1119    fn drag_window(&self) -> Result<(), RequestError> {
1120        Err(NotSupportedError::new("drag_window is not supported").into())
1121    }
1122
1123    fn drag_resize_window(&self, _direction: ResizeDirection) -> Result<(), RequestError> {
1124        Err(NotSupportedError::new("drag_resize_window is not supported").into())
1125    }
1126
1127    #[inline]
1128    fn show_window_menu(&self, _position: Position) {}
1129
1130    fn set_cursor_hittest(&self, _hittest: bool) -> Result<(), RequestError> {
1131        Err(NotSupportedError::new("set_cursor_hittest is not supported").into())
1132    }
1133
1134    fn set_theme(&self, _theme: Option<Theme>) {}
1135
1136    fn theme(&self) -> Option<Theme> {
1137        None
1138    }
1139
1140    fn set_content_protected(&self, _protected: bool) {}
1141
1142    fn has_focus(&self) -> bool {
1143        HAS_FOCUS.load(Ordering::Relaxed)
1144    }
1145
1146    fn title(&self) -> String {
1147        String::new()
1148    }
1149
1150    fn reset_dead_keys(&self) {}
1151
1152    fn rwh_06_display_handle(&self) -> &dyn rwh_06::HasDisplayHandle {
1153        self
1154    }
1155
1156    fn rwh_06_window_handle(&self) -> &dyn rwh_06::HasWindowHandle {
1157        self
1158    }
1159}
1160
1161fn screen_size(app: &AndroidApp) -> PhysicalSize<u32> {
1162    if let Some(native_window) = app.native_window() {
1163        PhysicalSize::new(native_window.width() as _, native_window.height() as _)
1164    } else {
1165        PhysicalSize::new(0, 0)
1166    }
1167}
1168
1169fn scale_factor(app: &AndroidApp) -> f64 {
1170    app.config().density().map(|dpi| dpi as f64 / 160.0).unwrap_or(1.0)
1171}