// SPDX-License-Identifier: LGPL-2.1-only // Copyright (C) 2026 Liberux Labs, S. L. //! Building the runtime's state for an app, and the event sources every backend installs on its loop. use std::collections::HashMap; use crate::a11y::A11yState; use crate::app::App; use crate::platform::Platform; use crate::types::Point; use super::{ AppData, RunError, SurfaceFocus, SurfaceState }; use super::perf::PerfState; use super::sources::Sources; impl AppData { /// `app` on `platform` with `main` as its only surface, nothing focused, pressed or queued. pub( crate ) fn new( app: A, platform: P, main: SurfaceState, sources: Sources, builtin_osk: bool, a11y: Option ) -> Self { let fullscreen = app.start_fullscreen(); Self { app, platform, pointer_pos: Point::default(), current_cursor_shape: None, builtin_osk, text_input_secure: false, text_input_purpose: crate::osk::Purpose::Normal, external_text_input: None, primary_selection: String::new(), primary_field: None, pending_primary_paste: None, a11y, shift_pressed: false, ctrl_pressed: false, alt_pressed: false, sources, compositor_repeat_rate: 0, compositor_repeat_delay: 0, key_repeat: None, button_repeat: None, clipboard: String::new(), last_press_time: None, last_press_pos: None, debug_layout: std::env::var( "LTK_DEBUG_LAYOUT" ).is_ok(), pending_msgs: Vec::new(), pending_drag_inits: Vec::new(), tooltip_pending: None, tooltip_visible: None, exit_requested: false, applied_fullscreen: Some( fullscreen ), main, overlays: HashMap::new(), secondaries: Vec::new(), pointer_focus: SurfaceFocus::Main, keyboard_focus: SurfaceFocus::Main, press_on_focused: false, text_scrollbar_grab: None, reported_cursors: HashMap::new(), touch_focus: HashMap::new(), cached_view: None, cached_overlays: None, view_dirty: true, overlays_dirty: true, first_frame_committed: false, focus_retry: None, perf: PerfState::new(), } } /// Let the app send messages from any thread, follow the desktop's text scale and wake on [`App::poll_interval`]. pub( crate ) fn install_sources( &mut self ) -> Result<(), RunError> { // Only queued: the run loop's `App::invalidate_after` decides which surfaces the message redraws. let sender = self.sources.channel( |msg, data: &mut Self| data.pending_msgs.push( msg ) ) .map_err( |e| RunError::EventLoop( format!( "channel insert_source: {e}" ) ) )?; self.app.set_channel_sender( sender ); // Font resolution multiplies by `text_scale()` at paint time, so a new factor only needs a repaint. #[ cfg( not( target_arch = "wasm32" ) ) ] { let tx = self.sources.channel( |factor: f32, data: &mut Self| { if ( factor - crate::types::text_scale() ).abs() < f32::EPSILON { return; } crate::types::set_text_scale( factor ); data.dirty_caches(); data.main.request_redraw(); for ss in data.overlays.values_mut() { ss.request_redraw(); } } ) .map_err( |e| RunError::EventLoop( format!( "text-scale insert_source: {e}" ) ) )?; #[ cfg( unix ) ] super::text_scale::spawn_watcher( tx ); #[ cfg( feature = "windows" ) ] crate::platform::windows::text_scale::spawn_watcher( tx ); } if let Some( dur ) = self.app.poll_interval() { self.perf.warn_poll_interval( dur ); self.sources.insert_timer( dur, |data: &mut Self| { let msgs = data.app.poll_external(); data.pending_msgs.extend( msgs ); Some( data.app.poll_interval().unwrap_or( std::time::Duration::from_secs( 1 ) ) ) } ) .map_err( |e| RunError::EventLoop( format!( "poll timer insert_source: {e}" ) ) )?; } Ok( () ) } }