event_loop, widget, input: pointer-dwell tooltips, global drag coords, foreign-toplevel name cascade
`Button::tooltip( text )` registers a hint string that fires after a 600 ms pointer dwell. `LaidOutWidget` gains a `tooltip: Option<String>` field, `Element::tooltip()` exposes it to the input layer, and the existing pointer-hover path now calls `arm_tooltip` on hover-enter and `cancel_tooltip` on hover-leave / touch. The deadline is polled alongside `next_long_press_wakeup` in `try_run` so an idle pointer still gets a wake-up at the firing instant; on fire, `tooltip_overlay()` synthesises an `OverlaySpec` — a rounded `text_primary @ 95%` pill drawn with `bg` text — anchored above the hovered widget, flipping below or clamping inside the screen if it would clip, and pushed alongside the app's own overlays both in the redraw path and in `reconcile_overlays` so the layer surface is created the same frame the tooltip becomes visible. Pointer-only by design: touch events explicitly cancel because a tap-and-release should never linger into a hint. The `showcase` example wires `.tooltip(..)` on the three button variants as a smoke test. The drag pipeline now reports positions in main-surface (global) coordinates instead of per-surface. `surface_offset_for( focus )` derives the top-left of an overlay surface from `SurfaceState::layer_anchor` — newly stored at `reconcile_overlays` time from `OverlaySpec::anchor` — combined with the main surface's dimensions; `on_drag_move`, `on_drop`, the synthetic move emitted on drag-promotion in `pointer.rs`, and the `pending_drag_inits` push site in `gesture::start_drag` all translate before handing coordinates to the app. The motion and release paths additionally `request_redraw()` every overlay so a dock-style drop target painted on an `Anchor::Bottom` layer surface gets repainted as the drag moves — without that, the visible drop indicator only updates when the cursor re-enters the main surface. Drops still target whichever surface fired the release; only the coordinates are unified. `ForeignToplevelListHandler` previously read `app_id` directly via `ForeignToplevelList::info()`. Clients that never set `app_id` (some winit-windowed compositors, simple test clients) were silently invisible to crustace-style docks because the empty string fell through `unwrap_or_default()` and the dock then keyed entries off `""`. `toplevel_display_id()` cascades: prefer `app_id` for desktop-entry matching, fall back to `title` for human-readable identification, and finally to the protocol-issued `identifier` which is always present and unique per handle. Applied to both `new_toplevel` and `update_toplevel`. `theme::system_fontdb()` lazily loads the system font database once via `OnceLock` and reuses the `Arc` for every `decode_svg_bytes` call. resvg's default `Options::fontdb` is empty, so any SVG containing `<text>` rendered with the built-in fallback font or no font at all; with the system DB attached, icons and decorative SVGs with embedded labels now resolve glyphs correctly. Cached because `load_system_fonts()` walks every font path on the system and is comfortably tens of milliseconds on a cold cache — not something to repeat per icon decode. `themes/default/theme.json` tweaks one variant's slot palette: `surface-alt` from `@indigo/D9` to `@white/D9` and `text-primary` from `@white` to `@navy`, plus a cosmetic re-alignment of the `"value"` columns in the same slots block.
This commit is contained in:
@@ -65,6 +65,26 @@ impl<A: App> AppData<A>
|
||||
|
||||
impl<A: App> AppData<A>
|
||||
{
|
||||
/// Top-left of `focus` in main-surface (global) coordinates. Used to
|
||||
/// translate per-surface pointer coords into a single coordinate
|
||||
/// space the app can reason about during drag-and-drop across
|
||||
/// surfaces.
|
||||
pub( crate ) fn surface_offset_for( &self, focus: SurfaceFocus ) -> ( f32, f32 )
|
||||
{
|
||||
let SurfaceFocus::Overlay( id ) = focus else { return ( 0.0, 0.0 ); };
|
||||
let Some( ss ) = self.overlays.get( &id ) else { return ( 0.0, 0.0 ); };
|
||||
let Some( anchor ) = ss.layer_anchor else { return ( 0.0, 0.0 ); };
|
||||
let ( sw, sh ) = ( self.main.width as f32, self.main.height as f32 );
|
||||
let ( w, h ) = ( ss.width as f32, ss.height as f32 );
|
||||
let x = if anchor.left { 0.0 }
|
||||
else if anchor.right { sw - w }
|
||||
else { ( sw - w ) / 2.0 };
|
||||
let y = if anchor.top { 0.0 }
|
||||
else if anchor.bottom { sh - h }
|
||||
else { ( sh - h ) / 2.0 };
|
||||
( x, y )
|
||||
}
|
||||
|
||||
/// Apply the outcome of a motion event. Non-blocking side-effects
|
||||
/// only: push messages, call app callbacks, set redraw flags.
|
||||
pub( super ) fn apply_move_outcome
|
||||
@@ -79,10 +99,15 @@ impl<A: App> AppData<A>
|
||||
MoveOutcome::Idle => {}
|
||||
MoveOutcome::Drag { pos } =>
|
||||
{
|
||||
self.app.on_drag_move( pos.x, pos.y );
|
||||
let ( ox, oy ) = self.surface_offset_for( focus );
|
||||
self.app.on_drag_move( pos.x + ox, pos.y + oy );
|
||||
self.dirty_caches();
|
||||
self.surface_mut( focus ).request_redraw();
|
||||
self.main.request_redraw();
|
||||
for ss in self.overlays.values_mut()
|
||||
{
|
||||
ss.request_redraw();
|
||||
}
|
||||
}
|
||||
MoveOutcome::Slider { msg } =>
|
||||
{
|
||||
@@ -141,7 +166,8 @@ impl<A: App> AppData<A>
|
||||
{
|
||||
ReleaseEvent::Drop { pos } =>
|
||||
{
|
||||
if let Some( msg ) = self.app.on_drop( pos.x, pos.y )
|
||||
let ( ox, oy ) = self.surface_offset_for( focus );
|
||||
if let Some( msg ) = self.app.on_drop( pos.x + ox, pos.y + oy )
|
||||
{
|
||||
self.pending_msgs.push( msg );
|
||||
}
|
||||
@@ -149,6 +175,10 @@ impl<A: App> AppData<A>
|
||||
self.dirty_caches();
|
||||
self.main.request_redraw();
|
||||
self.main.frame_pending = false;
|
||||
for ss in self.overlays.values_mut()
|
||||
{
|
||||
ss.request_redraw();
|
||||
}
|
||||
}
|
||||
ReleaseEvent::SwipeLeft =>
|
||||
{
|
||||
|
||||
@@ -641,6 +641,7 @@ mod tests
|
||||
handlers: WidgetHandlers::Button { on_press, on_long_press, on_drag_start, on_escape: None, repeating: false },
|
||||
keyboard_focusable: true,
|
||||
cursor: crate::types::CursorShape::Default,
|
||||
tooltip: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -84,9 +84,16 @@ impl<A: App> PointerHandler for AppData<A>
|
||||
{
|
||||
let redraw = hover_affects_paint( &self.surface( focus ).widget_rects, old_hover )
|
||||
|| hover_affects_paint( &self.surface( focus ).widget_rects, new_hover );
|
||||
let ss = self.surface_mut( focus );
|
||||
ss.hovered_idx = new_hover;
|
||||
if redraw { ss.needs_redraw = true; }
|
||||
{
|
||||
let ss = self.surface_mut( focus );
|
||||
ss.hovered_idx = new_hover;
|
||||
if redraw { ss.needs_redraw = true; }
|
||||
}
|
||||
match new_hover
|
||||
{
|
||||
Some( idx ) => self.arm_tooltip( focus, idx ),
|
||||
None => self.cancel_tooltip(),
|
||||
}
|
||||
}
|
||||
|
||||
// Mouse drag-promotion: a left-button press whose hit
|
||||
@@ -137,7 +144,8 @@ impl<A: App> PointerHandler for AppData<A>
|
||||
( m, o )
|
||||
};
|
||||
self.app.update( ds_msg );
|
||||
self.app.on_drag_move( origin.x, origin.y );
|
||||
let ( ox, oy ) = self.surface_offset_for( focus );
|
||||
self.app.on_drag_move( origin.x + ox, origin.y + oy );
|
||||
self.dirty_caches();
|
||||
self.stop_button_repeat();
|
||||
}
|
||||
@@ -228,6 +236,7 @@ impl<A: App> PointerHandler for AppData<A>
|
||||
{
|
||||
self.last_pointer_serial = serial;
|
||||
self.last_input_serial = serial;
|
||||
self.cancel_tooltip();
|
||||
let pos = self.surface( focus ).to_physical( event.position.0, event.position.1 );
|
||||
self.pointer_pos = pos;
|
||||
self.app.on_pointer_move( pos.x, pos.y );
|
||||
|
||||
Reference in New Issue
Block a user