ltk/event_loop/
overlays_reconcile.rsuse std::collections::HashSet;
use smithay_client_toolkit::
{
compositor::Surface,
shell::
{
WaylandSurface,
xdg::
{
XdgPositioner, XdgSurface,
popup::Popup,
},
},
};
use wayland_protocols::xdg::shell::client::xdg_positioner::
{
Anchor as PositionerAnchor,
ConstraintAdjustment,
Gravity,
};
use crate::app::App;
use crate::types::Rect;
use super::{ AppData, LayerConfig, SurfaceFocus, SurfaceKind, SurfaceState };
pub( super ) fn reconcile_overlays<A: App>( data: &mut AppData<A> )
{
let mut specs = data.app.overlays();
if let Some( ts ) = data.tooltip_overlay() { specs.push( ts ); }
let next_ids: Vec<_> = specs.iter().map( |s| s.id ).collect();
let wanted: HashSet<crate::app::OverlayId> = next_ids.iter().copied().collect();
let removed: Vec<_> = data.overlays.keys()
.filter( |id| !wanted.contains( id ) )
.copied()
.collect();
for id in removed
{
if let Some( ss ) = data.overlays.remove( &id )
{
if ss.gesture.long_press_fired
{
data.main.gesture.long_press_fired = true;
data.main.gesture.long_press_origin = ss.gesture.long_press_origin;
if data.main.primary_touch_id.is_none()
{
data.main.primary_touch_id = ss.primary_touch_id;
}
}
}
}
if let SurfaceFocus::Overlay( id ) = data.pointer_focus
{
if !data.overlays.contains_key( &id ) { data.pointer_focus = SurfaceFocus::Main; }
}
if let SurfaceFocus::Overlay( id ) = data.keyboard_focus
{
if !data.overlays.contains_key( &id ) { data.keyboard_focus = SurfaceFocus::Main; }
}
for f in data.touch_focus.values_mut()
{
if let SurfaceFocus::Overlay( id ) = *f
{
if !data.overlays.contains_key( &id ) { *f = SurfaceFocus::Main; }
}
}
let layer_shell_opt = data.layer_shell.as_ref();
let xdg_shell_opt = data.xdg_shell.as_ref();
let output_opt = data.output_state.outputs().next();
let cs = &data.compositor_state;
let qh = &data.qh;
let grab_seat = data.seat_state.seats().next();
let grab_serial = data.last_input_serial;
let parent_xdg = match data.main.surface
{
SurfaceKind::Window( ref w ) => Some( w.xdg_surface().clone() ),
_ => None,
};
let parent_scale_i = data.main.scale_factor.max( 1 );
let parent_scale = parent_scale_i as f32;
let to_logical_size = move | ( w, h ): ( u32, u32 ) | -> ( u32, u32 )
{
(
( w as f32 / parent_scale ).round() as u32,
( h as f32 / parent_scale ).round() as u32,
)
};
let main_widget_rects = &data.main.widget_rects;
let overlays_m = &mut data.overlays;
for spec in &specs
{
if let Some( ss ) = overlays_m.get_mut( &spec.id )
{
if spec.size != ss.last_requested_size
{
if let SurfaceKind::Layer( ref layer_surface ) = ss.surface
{
let ( lw, lh ) = to_logical_size( spec.size );
layer_surface.set_size( lw, lh );
layer_surface.commit();
ss.last_requested_size = spec.size;
}
}
if let SurfaceKind::Popup( ref popup ) = ss.surface
{
if let ( Some( anchor_id ), Some( xdg_shell ) ) = ( spec.anchor_widget_id, xdg_shell_opt )
{
if let Some( anchor_rect ) = main_widget_rects.iter()
.find( |w| w.id == Some( anchor_id ) )
.map( |w| w.rect )
{
let to_logical = | r: Rect |
{
(
( r.x / parent_scale ).round() as i32,
( r.y / parent_scale ).round() as i32,
( r.width / parent_scale ).round().max( 1.0 ) as i32,
( r.height / parent_scale ).round().max( 1.0 ) as i32,
)
};
let new_q = to_logical( anchor_rect );
let moved = ss.last_popup_anchor.map( |r| to_logical( r ) != new_q ).unwrap_or( true );
if moved
{
if let Ok( positioner ) = XdgPositioner::new( xdg_shell )
{
let ( ax, ay, aw, ah ) = new_q;
let ( spec_w, spec_h ) = spec.size;
let popup_w = if spec_w == 0 { aw } else { spec_w.max( 1 ) as i32 };
let popup_h = spec_h.max( 1 ) as i32;
positioner.set_size( popup_w, popup_h );
positioner.set_anchor_rect( ax, ay, aw, ah );
positioner.set_anchor( PositionerAnchor::Bottom );
positioner.set_gravity( Gravity::Bottom );
positioner.set_constraint_adjustment(
ConstraintAdjustment::FlipY | ConstraintAdjustment::SlideX,
);
ss.popup_reposition_token = ss.popup_reposition_token.wrapping_add( 1 );
popup.reposition( &positioner, ss.popup_reposition_token );
ss.last_popup_anchor = Some( anchor_rect );
}
}
}
}
}
continue;
}
if let Some( anchor_id ) = spec.anchor_widget_id
{
let Some( xdg_shell ) = xdg_shell_opt else
{
eprintln!( "ltk: ignoring popup overlay {:?} — main surface is not an xdg-shell window", spec.id );
continue;
};
let Some( ref parent ) = parent_xdg else
{
eprintln!( "ltk: ignoring popup overlay {:?} — no xdg parent surface", spec.id );
continue;
};
let Some( anchor_rect ) = main_widget_rects.iter()
.find( |w| w.id == Some( anchor_id ) )
.map( |w| w.rect )
else
{
eprintln!( "ltk: popup overlay {:?} could not find anchor widget id {:?}", spec.id, anchor_id );
continue;
};
let positioner = match XdgPositioner::new( xdg_shell )
{
Ok( p ) => p,
Err( e ) =>
{
eprintln!( "ltk: XdgPositioner::new failed for popup {:?}: {e}", spec.id );
continue;
}
};
let ax = ( anchor_rect.x / parent_scale ).round() as i32;
let ay = ( anchor_rect.y / parent_scale ).round() as i32;
let aw = ( anchor_rect.width / parent_scale ).round().max( 1.0 ) as i32;
let ah = ( anchor_rect.height / parent_scale ).round().max( 1.0 ) as i32;
let ( spec_w, spec_h ) = spec.size;
let popup_w = if spec_w == 0 { aw } else { spec_w.max( 1 ) as i32 };
let popup_h = spec_h.max( 1 ) as i32;
positioner.set_size( popup_w, popup_h );
positioner.set_anchor_rect( ax, ay, aw, ah );
positioner.set_anchor( PositionerAnchor::Bottom );
positioner.set_gravity( Gravity::Bottom );
positioner.set_constraint_adjustment(
ConstraintAdjustment::FlipY | ConstraintAdjustment::SlideX,
);
let surface = match Surface::new( cs, qh )
{
Ok( s ) => s,
Err( e ) =>
{
eprintln!( "ltk: Surface::new failed for popup overlay {:?}: {e}", spec.id );
continue;
}
};
let popup = match Popup::from_surface( Some( parent ), &positioner, qh, surface, xdg_shell )
{
Ok( p ) => p,
Err( e ) =>
{
eprintln!( "ltk: Popup::from_surface failed for overlay {:?}: {e}", spec.id );
continue;
}
};
if let Some( ref seat ) = grab_seat
{
popup.xdg_popup().grab( seat, grab_serial );
}
popup.wl_surface().commit();
let mut ss = SurfaceState::<A::Message>::new( SurfaceKind::Popup( popup ), 0.0, String::new() );
ss.last_requested_size = spec.size;
ss.last_popup_anchor = Some( anchor_rect );
overlays_m.insert( spec.id, ss );
continue;
}
let Some( layer_shell ) = layer_shell_opt else
{
eprintln!( "ltk: ignoring layer-shell overlay {:?} — wlr-layer-shell not available", spec.id );
continue;
};
let cfg = LayerConfig
{
layer: spec.layer.to_wlr_layer(),
exclusive_zone: spec.exclusive_zone,
anchor: spec.anchor,
size: to_logical_size( spec.size ),
keyboard_exclusive: spec.keyboard_exclusive,
namespace: "ltk-overlay",
};
let mut surface = SurfaceKind::Pending( cfg );
if let Some( ref output ) = output_opt
{
surface.materialize( cs, layer_shell, qh, output );
}
let mut ss = SurfaceState::<A::Message>::new( surface, 0.0, String::new() );
ss.scale_factor = parent_scale_i;
ss.last_requested_size = spec.size;
ss.layer_anchor = Some( spec.anchor );
overlays_m.insert( spec.id, ss );
}
}