diff --git a/CHANGELOG.md b/CHANGELOG.md index d8fc91f..40ecd65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ All notable changes to `ltk` are documented here. The format is based on [Keep a ### Added +- **`App::claims_raw_touch_at( x, y )`** — per-press variant of `claims_raw_touch`: a finger landing where the app returns `true` rides the raw `on_touch_*` stream through the auxiliary-slot path and clears the widget focus, while fingers landing elsewhere keep driving the gesture machine. Lets an app embed a self-contained input consumer (a WebView) next to regular widgets. Only consulted for the main surface; default `false`. +- **`App::fullscreen()`** — desired fullscreen state of the toplevel at runtime, polled after every batch of `update`s; a change is forwarded as `xdg_toplevel.set_fullscreen` / `unset_fullscreen`. The built-in titlebar is not painted while the compositor reports the window as fullscreen. Default `None` (window state left alone). - **`PatternGrid::size( impl Into )`** — explicit side for the square grid, resolved through `Canvas::resolve_geom` like the other geometry overrides; without it the theme's fluid default still applies. A lock screen can now size the grid from the surface (a share of the width in portrait, of the free height in landscape) instead of being capped at the default. - **`xdg-session-management-v1` (client)** — before the first commit of a `ShellMode::Window` toplevel the runtime binds `xdg_session_manager_v1`, issues `get_session( reason, stored_id )` and `restore_toplevel( toplevel, "main" )`, so a supporting compositor restores window geometry on every launch. Bindings are generated in-tree from the vendored XML under `protocols/` with `wayland-scanner` (`src/protocol/`). - **Runtime session persistence** — `$XDG_STATE_HOME//session.json` (compositor session id, clean-exit marker, pid) plus `state.bin` (the bytes from `App::save_state`), written atomically with mode `0600`; saved every 30 s when the bytes changed, on close and on signal; handed back through `App::restore_state` before the first frame only on a session restore (`LTK_SESSION_RESTORE=1`) or after an unclean exit. Module `src/session_state.rs`. diff --git a/src/app.rs b/src/app.rs index 4f129f6..e64ef23 100644 --- a/src/app.rs +++ b/src/app.rs @@ -811,6 +811,15 @@ pub trait App: 'static /// WebView, a game canvas) that needs every finger verbatim. fn claims_raw_touch( &self ) -> bool { false } + /// Per-press variant of [`Self::claims_raw_touch`]: return `true` to + /// take the finger landing at `(x, y)` (physical pixels of the main + /// surface) through the raw [`Self::on_touch_down`] / move / up stream, + /// while fingers landing elsewhere keep driving the widget gesture + /// machine. For apps that embed a self-contained input consumer (a + /// WebView) next to regular widgets. A claimed press clears the widget + /// focus. Only consulted for the main surface. + fn claims_raw_touch_at( &self, _x: f32, _y: f32 ) -> bool { false } + /// Pointer of a cross-application drag-and-drop entered or moved /// inside the main surface. `(x, y)` is in logical pixels. fn on_drop_motion( &mut self, _x: f32, _y: f32 ) {} @@ -1058,6 +1067,17 @@ pub trait App: 'static /// **Default**: `false`. fn start_fullscreen( &self ) -> bool { false } + /// Desired fullscreen state of the toplevel while the app runs. + /// + /// Polled after every batch of `update`s: when the returned value + /// changes, ltk calls `xdg_toplevel.set_fullscreen` / + /// `unset_fullscreen`. While the compositor reports the window as + /// fullscreen the built-in titlebar is not painted. `None` (the + /// default) leaves the window state alone. + /// + /// Only applies to [`ShellMode::Window`] surfaces. + fn fullscreen( &self ) -> Option { None } + /// Specify the exclusive zone for layer-shell surfaces. /// /// The exclusive zone reserves screen space for this surface. diff --git a/src/event_loop/app_data.rs b/src/event_loop/app_data.rs index cc3e800..eec788f 100644 --- a/src/event_loop/app_data.rs +++ b/src/event_loop/app_data.rs @@ -215,6 +215,8 @@ pub struct AppData pub exit_requested: bool, /// First-configure latch for `App::start_fullscreen`. pub pending_fullscreen: bool, + /// Last value of `App::fullscreen` sent to the compositor. + pub applied_fullscreen: Option, /// First-configure latch: clears `max_size` after the compositor /// has honoured the pinned initial size. pub pending_size_hint_unpin: bool, @@ -283,6 +285,21 @@ pub struct AppData impl AppData { + /// Forward a change of [`App::fullscreen`] to the main toplevel. + pub( crate ) fn sync_fullscreen( &mut self ) + { + let Some( wanted ) = self.app.fullscreen() else { return }; + if self.applied_fullscreen == Some( wanted ) { return; } + let super::SurfaceKind::Window( ref window ) = self.main.surface else { return }; + if wanted + { + window.set_fullscreen( None ); + } else { + window.unset_fullscreen(); + } + self.applied_fullscreen = Some( wanted ); + } + /// Find which [`SurfaceFocus`] owns the given `WlSurface`, or `None` if it /// does not correspond to any tracked surface. Safe even when the main /// surface is still `Pending` (returns `None`). diff --git a/src/event_loop/handlers.rs b/src/event_loop/handlers.rs index 24f1dce..103946d 100644 --- a/src/event_loop/handlers.rs +++ b/src/event_loop/handlers.rs @@ -292,7 +292,7 @@ impl WindowHandler for AppData window.set_max_size( None ); self.pending_size_hint_unpin = false; } - let tb = if configure.decoration_mode == DecorationMode::Server { 0.0 } + let tb = if configure.decoration_mode == DecorationMode::Server || configure.is_fullscreen() { 0.0 } else { self.main.titlebar_base }; if self.main.titlebar_height != tb { diff --git a/src/event_loop/run.rs b/src/event_loop/run.rs index f30b025..d312527 100644 --- a/src/event_loop/run.rs +++ b/src/event_loop/run.rs @@ -347,6 +347,7 @@ pub( crate ) fn try_run( mut app: A ) -> Result<(), RunError> last_input_serial: 0, exit_requested: false, pending_fullscreen, + applied_fullscreen: Some( pending_fullscreen ), pending_size_hint_unpin, main: SurfaceState::::new( surface_kind, titlebar_height, titlebar_title ), overlays: std::collections::HashMap::new(), @@ -599,6 +600,8 @@ pub( crate ) fn try_run( mut app: A ) -> Result<(), RunError> data.exit_requested = true; } + data.sync_fullscreen(); + // Seed `on_drag_move` with the long-press origin for any drag that // just started. Must run *after* `update()` so the app's drag state // has already been set up by the paired long-press message — the diff --git a/src/input/touch/mod.rs b/src/input/touch/mod.rs index 8f6aa6d..a59d508 100644 --- a/src/input/touch/mod.rs +++ b/src/input/touch/mod.rs @@ -67,7 +67,9 @@ impl TouchHandler for AppData // A second finger landing on a plain press means the first was a // tap: commit it at its last position and hand the slot over, so // alternating thumbs never lose a key. - if !self.app.claims_raw_touch() + let pos = self.surface( focus ).to_physical( position.0, position.1 ); + let raw_here = matches!( focus, SurfaceFocus::Main ) && self.app.claims_raw_touch_at( pos.x, pos.y ); + if !self.app.claims_raw_touch() && !raw_here { let takeover = { @@ -80,7 +82,6 @@ impl TouchHandler for AppData } } - let pos = self.surface( focus ).to_physical( position.0, position.1 ); self.pointer_pos = pos; // Raw-touch apps take every finger verbatim; the gesture @@ -93,6 +94,16 @@ impl TouchHandler for AppData return; } + // Region claimed through `claims_raw_touch_at`: the finger rides the + // auxiliary-slot path, so `motion` / `up` need no special casing. + if raw_here + { + self.set_focus( focus, None, qh ); + self.surface_mut( focus ).touch_slots.insert( id, pos ); + self.app.on_touch_down( id as i64, pos.x, pos.y ); + return; + } + // Compositor re-grab after the origin surface died mid-drag: this // slot is already our primary and the drag state was migrated here, // so treat the redundant `down` as a continuation, not a restart.