diff --git a/src/event_loop/handlers.rs b/src/event_loop/handlers.rs index 910afba..823ad62 100644 --- a/src/event_loop/handlers.rs +++ b/src/event_loop/handlers.rs @@ -154,7 +154,13 @@ impl CompositorHandler for AppData // exclusively by input events, poll_external messages, and configure events. } - fn surface_enter( &mut self, _: &Connection, _: &QueueHandle, _: &WlSurface, _: &WlOutput ) {} + fn surface_enter( &mut self, _: &Connection, _: &QueueHandle, surface: &WlSurface, output: &WlOutput ) + { + if self.main.surface.try_wl_surface() == Some( surface ) + { + self.publish_output_size( Some( output ) ); + } + } fn surface_leave( &mut self, _: &Connection, _: &QueueHandle, _: &WlSurface, _: &WlOutput ) {} } @@ -333,6 +339,7 @@ impl OutputHandler for AppData fn new_output( &mut self, _: &Connection, qh: &QueueHandle, output: WlOutput ) { + self.publish_output_size( None ); // If we were waiting for an output to assign the layer surface to, create it now. // Same for any overlays that were created before an output existed. if let Some( ref layer_shell ) = self.layer_shell @@ -394,7 +401,10 @@ impl OutputHandler for AppData } } - fn update_output( &mut self, _: &Connection, _: &QueueHandle, _: WlOutput ) {} + fn update_output( &mut self, _: &Connection, _: &QueueHandle, _: WlOutput ) + { + self.publish_output_size( None ); + } fn output_destroyed( &mut self, _: &Connection, _: &QueueHandle, output: WlOutput ) { @@ -403,6 +413,7 @@ impl OutputHandler for AppData { self.main_output = None; } + self.publish_output_size( None ); } } diff --git a/src/event_loop/osk.rs b/src/event_loop/osk.rs index 47ab184..e24540a 100644 --- a/src/event_loop/osk.rs +++ b/src/event_loop/osk.rs @@ -19,7 +19,7 @@ use crate::types::{ Point, Rect }; pub( crate ) fn place_osk( osk: &mut Option, pw: u32, ph: u32, sf: f32 ) -> f32 { let Some( panel ) = osk.as_mut() else { return 0.0 }; - let h = panel.height( pw as f32, sf ).min( ph as f32 ); + let h = panel.height_within( pw as f32, sf, ph as f32 ); panel.set_rect( Rect { x: 0.0, y: ph as f32 - h, width: pw as f32, height: h }, sf ); h } diff --git a/src/event_loop/window_fit.rs b/src/event_loop/window_fit.rs index 9f9379a..61e0b2b 100644 --- a/src/event_loop/window_fit.rs +++ b/src/event_loop/window_fit.rs @@ -3,6 +3,7 @@ //! Keeps [`App::window_size_hint`](crate::App::window_size_hint) inside the screen. +use smithay_client_toolkit::reexports::client::protocol::wl_output::WlOutput; use smithay_client_toolkit::shell::xdg::window::Window; use crate::app::App; @@ -42,15 +43,29 @@ impl AppData fn output_bounds( &self ) -> Option<( u32, u32 )> { - let info = self.output_state.outputs().find_map( |o| self.output_state.info( &o ) )?; + let ( w, h ) = self.output_logical_size( None )?; + Some( ( ( w as f32 * OUTPUT_SHARE ) as u32, ( h as f32 * OUTPUT_SHARE ) as u32 ) ) + } + + fn output_logical_size( &self, preferred: Option<&WlOutput> ) -> Option<( u32, u32 )> + { + let info = preferred + .or( self.main_output.as_ref() ) + .and_then( |o| self.output_state.info( o ) ) + .or_else( || self.output_state.outputs().find_map( |o| self.output_state.info( &o ) ) )?; let ( w, h ) = info.logical_size.or_else( || { let mode = info.modes.iter().find( |m| m.current )?; let scale = info.scale_factor.max( 1 ); Some( ( mode.dimensions.0 / scale, mode.dimensions.1 / scale ) ) } )?; - if w <= 0 || h <= 0 { return None; } - Some( ( ( w as f32 * OUTPUT_SHARE ) as u32, ( h as f32 * OUTPUT_SHARE ) as u32 ) ) + ( w > 0 && h > 0 ).then_some( ( w as u32, h as u32 ) ) + } + + pub( crate ) fn publish_output_size( &self, preferred: Option<&WlOutput> ) + { + let ( w, h ) = self.output_logical_size( preferred ).unwrap_or( ( 0, 0 ) ); + crate::types::set_output_logical_size( w, h ); } } diff --git a/src/lib.rs b/src/lib.rs index 40efce1..dc4be94 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -358,6 +358,7 @@ pub use types::{ density, set_density }; pub use types::{ set_text_scale, text_scale }; pub use types::{ widget_scaling, set_widget_scaling }; pub use types::{ Orientation, orientation, viewport_size, set_viewport_size }; +pub use types::{ output_logical_size, set_output_logical_size }; pub use widget::{ Element, button, icon_button, text_edit, image as img_widget, text, container }; pub use text_shaping::measure_text; pub use widget::rich_text::{ rich_text, RichText, LinkSpan }; diff --git a/src/osk/mod.rs b/src/osk/mod.rs index 6119461..807f75b 100644 --- a/src/osk/mod.rs +++ b/src/osk/mod.rs @@ -24,6 +24,7 @@ const SHIFT_DOUBLE_TAP: Duration = Duration::from_millis( 350 ); const GAP: f32 = 6.0; const ROW_GAP: f32 = 2.0 * GAP; const PADDING: f32 = 6.0; +const MAX_HEIGHT_FRACTION: f32 = 0.4; /// What the field being typed into is for; picks the extra row above the layout. #[ derive( Clone, Copy, Debug, PartialEq, Eq, Default ) ] @@ -228,6 +229,12 @@ impl Panel rows * self.key_unit( width, scale ) + ( ( rows - 1.0 ).max( 0.0 ) * ROW_GAP + 2.0 * PADDING ) * scale } + /// [`Self::height`] kept within 40 % of `screen_height`, so a wide screen gets short keys instead of a keyboard that covers it. + pub fn height_within( &self, width: f32, scale: f32, screen_height: f32 ) -> f32 + { + self.height( width, scale ).min( screen_height * MAX_HEIGHT_FRACTION ) + } + /// Width of one key unit of the layout's rows, `width` pixels wide: the /// widest row fills the panel. Keys are as tall as one unit is wide. fn key_unit( &self, width: f32, scale: f32 ) -> f32 @@ -250,7 +257,10 @@ impl Panel let gap = GAP * scale; let row_gap = ROW_GAP * scale; let pad = PADDING * scale; - let key_h = self.key_unit( rect.width, scale ); + let unit = self.key_unit( rect.width, scale ); + let rows_n = ( self.bar_rows().len() + self.layout().rows( &self.layer ).len() ) as f32; + let fit = ( rect.height - 2.0 * pad - ( rows_n - 1.0 ).max( 0.0 ) * row_gap ) / rows_n.max( 1.0 ); + let key_h = unit.min( fit.max( 1.0 ) ); let avail = rect.width - 2.0 * pad; let mut y = rect.y + pad; @@ -266,7 +276,6 @@ impl Panel let rows: Vec> = self.layout().rows( &self.layer ).iter() .map( |row| row.iter().map( |k| self.labelled( k ) ).collect() ) .collect(); - let unit = key_h; for row in &rows { let units: f32 = row.iter().map( |k| k.width ).sum(); diff --git a/src/types.rs b/src/types.rs index ec76a60..e23f26f 100644 --- a/src/types.rs +++ b/src/types.rs @@ -790,6 +790,27 @@ pub fn viewport_size() -> ( u32, u32 ) ( VIEWPORT_W.load( Ordering::Relaxed ), VIEWPORT_H.load( Ordering::Relaxed ) ) } +static OUTPUT_W: AtomicU32 = AtomicU32::new( 0 ); +static OUTPUT_H: AtomicU32 = AtomicU32::new( 0 ); + +/// Record the logical size of the output the main surface is on. The +/// runtime calls this when outputs appear, change or go away, and when the +/// main surface enters one. `( 0, 0 )` clears it. +pub fn set_output_logical_size( width: u32, height: u32 ) +{ + OUTPUT_W.store( width, Ordering::Relaxed ); + OUTPUT_H.store( height, Ordering::Relaxed ); +} + +/// Logical size of the screen the main surface is on, or `None` before the +/// compositor has described one. Unlike [`viewport_size`] it does not follow +/// the window, so it can size things relative to the display. +pub fn output_logical_size() -> Option<( u32, u32 )> +{ + let size = ( OUTPUT_W.load( Ordering::Relaxed ), OUTPUT_H.load( Ordering::Relaxed ) ); + ( size.0 > 0 && size.1 > 0 ).then_some( size ) +} + /// Orientation of the main surface: [`Orientation::Landscape`] when wider /// than tall, [`Orientation::Portrait`] otherwise (square counts as /// portrait, matching [`Length::orient`]'s resolution rule). Usable