diff --git a/src/outputs.rs b/src/outputs.rs index 13c1b5d3..a6a8944d 100644 --- a/src/outputs.rs +++ b/src/outputs.rs @@ -4,9 +4,11 @@ /*! Managing Wayland outputs */ +use std::ops; use std::vec::Vec; use crate::event_loop; use ::logging; +use crate::util::DivCeil; // traits use ::logging::Warn; @@ -126,7 +128,7 @@ pub mod c { outputs: COutputs, wl_output: WlOutput, _x: i32, _y: i32, - _phys_width: i32, _phys_height: i32, + phys_width: i32, phys_height: i32, _subpixel: i32, _make: *const c_char, _model: *const c_char, transform: i32, @@ -144,7 +146,19 @@ pub mod c { .find_output_mut(wl_output) .map(|o| &mut o.pending); match output_state { - Some(state) => { state.transform = Some(transform) }, + Some(state) => { + fn maybe_mm(value: i32) -> Option { + if value == 0 { None } + else { Some(Millimeter(value)) } + } + state.geometry = Some(Geometry { + phys_size: Size { + width: maybe_mm(phys_width), + height: maybe_mm(phys_height), + }, + transform, + }); + }, None => log_print!( logging::Level::Warning, "Got geometry on unknown output", @@ -286,24 +300,51 @@ pub mod c { // TODO: handle unregistration } + /// Generic size -#[derive(Clone)] -pub struct Size { - pub width: u32, - pub height: u32, +#[derive(Clone, Copy, Debug)] +pub struct Size { + pub width: Unit, + pub height: Unit, } +pub type PixelSize = Size; + /// wl_output mode #[derive(Clone, Copy, Debug)] pub struct Mode { - width: i32, - height: i32, + pub width: i32, + pub height: i32, +} + +#[derive(Clone, Copy, Debug)] +pub struct Millimeter(pub i32); + +impl DivCeil for Millimeter { + type Output = Millimeter; + fn div_ceil(self, other: i32) -> Self { + Self(self.0.div_ceil(other)) + } +} + +impl ops::Mul for Millimeter { + type Output = Self; + fn mul(self, m: i32) -> Self { + Self(self.0 * m as i32) + } +} + +/// All geometry parameters +#[derive(Clone, Copy, Debug)] +pub struct Geometry { + pub transform: c::Transform, + pub phys_size: Size>, } #[derive(Clone, Copy, Debug)] pub struct OutputState { pub current_mode: Option, - pub transform: Option, + pub geometry: Option, pub scale: i32, } @@ -317,33 +358,56 @@ impl OutputState { fn uninitialized() -> OutputState { OutputState { current_mode: None, - transform: None, + geometry: None, scale: 1, } } - pub fn get_pixel_size(&self) -> Option { + fn transform_size( + width: T, + height: T, + transform: self::c::Transform, + ) -> Size { use self::c::Transform; + + match transform { + Transform::Normal + | Transform::Rotated180 + | Transform::Flipped + | Transform::FlippedRotated180 => Size { + width, + height, + }, + _ => Size { + width: height, + height: width, + }, + } + } + + /// Return resolution adjusted for current transform + pub fn get_pixel_size(&self) -> Option { match self { OutputState { current_mode: Some(Mode { width, height } ), - transform: Some(transform), + geometry: Some(Geometry { transform, .. } ), scale: _, - } => Some( - match transform { - Transform::Normal - | Transform::Rotated180 - | Transform::Flipped - | Transform::FlippedRotated180 => Size { - width: *width as u32, - height: *height as u32, - }, - _ => Size { - width: *height as u32, - height: *width as u32, - }, - } - ), + } => Some(Self::transform_size(*width as u32, *height as u32, *transform)), + OutputState { + current_mode: Some(Mode { width, height } ), + .. + } => Some(PixelSize { width: *width as u32, height: *height as u32 } ), + _ => None, + } + } + + /// Return physical dimensions adjusted for current transform + pub fn get_physical_size(&self) -> Option>> { + match self { + OutputState { + geometry: Some(Geometry { transform, phys_size } ), + .. + } => Some(Self::transform_size(phys_size.width, phys_size.height, *transform)), _ => None, } } diff --git a/src/state.rs b/src/state.rs index 5254a940..d5d4437a 100644 --- a/src/state.rs +++ b/src/state.rs @@ -9,7 +9,8 @@ use crate::animation; use crate::imservice::{ ContentHint, ContentPurpose }; use crate::main::{ Commands, PanelCommand, PixelSize }; use crate::outputs; -use crate::outputs::{OutputId, OutputState}; +use crate::outputs::{Millimeter, OutputId, OutputState}; +use crate::util::Rational; use std::cmp; use std::collections::HashMap; use std::time::Instant; @@ -241,23 +242,65 @@ impl Application { fn get_preferred_height(output: &OutputState) -> Option { output.get_pixel_size() .map(|px_size| { + // Assume isotropy. + // Pixels/mm. + let density = output.get_physical_size() + .and_then(|size| size.width) + .map(|width| Rational { + numerator: px_size.width as i32, + denominator: width.0 as u32, + }) + // Whatever the Librem 5 has, + // as a good default. + .unwrap_or(Rational { + numerator: 720, + denominator: 65, + }); + + // Based on what works on the L5. + // Exceeding that probably wastes space. Reducing makes typing harder. + const IDEAL_TARGET_SIZE: Rational = Rational { + numerator: Millimeter(948), + denominator: 100, + }; + + // TODO: calculate based on selected layout + const ROW_COUNT: u32 = 4; + let height = { - if px_size.width > px_size.height { - px_size.width / 5 - } else { - let abstract_width - = PixelSize { - scale_factor: output.scale as u32, - pixels: px_size.width, - } - .as_scaled_ceiling(); - if (abstract_width < 540) && (px_size.width > 0) { - px_size.width * 7 / 12 // to match 360×210 + let ideal_height = IDEAL_TARGET_SIZE * ROW_COUNT as i32; + let ideal_height_px = (ideal_height * density).ceil().0 as u32; + + // Reduce height to match what the layout can fill. + // For this, we need to guess if normal or wide will be picked up. + // This must match `eek_gtk_keyboard.c::get_type`. + // TODO: query layout database and choose one directly + let abstract_width + = PixelSize { + scale_factor: output.scale as u32, + pixels: px_size.width, + } + .as_scaled_ceiling(); + + let height_as_widths = { + if abstract_width < 540 { + // Normal + Rational { + numerator: 210, + denominator: 360, + } } else { - // Here we switch to wide layout, less height needed - px_size.width * 7 / 22 + // Wide + Rational { + numerator: 172, + denominator: 540, + } } - } + }; + cmp::min( + ideal_height_px, + (height_as_widths * px_size.width as i32).ceil() as u32, + ) }; PixelSize { scale_factor: output.scale as u32, @@ -341,7 +384,7 @@ pub mod test { id, OutputState { current_mode: None, - transform: None, + geometry: None, scale: 1, }, ); @@ -523,4 +566,29 @@ pub mod test { ); } + + #[test] + fn size_l5() { + use crate::outputs::{Mode, Geometry, c, Size}; + assert_eq!( + Application::get_preferred_height(&OutputState { + current_mode: Some(Mode { + width: 720, + height: 1440, + }), + geometry: Some(Geometry{ + transform: c::Transform::Normal, + phys_size: Size { + width: Some(Millimeter(65)), + height: Some(Millimeter(130)), + }, + }), + scale: 2, + }), + Some(PixelSize { + scale_factor: 2, + pixels: 420, + }), + ); + } } diff --git a/src/util.rs b/src/util.rs index d5109066..4a8305ed 100644 --- a/src/util.rs +++ b/src/util.rs @@ -7,6 +7,7 @@ use ::float_ord::FloatOrd; use std::borrow::Borrow; use std::hash::{ Hash, Hasher }; use std::iter::FromIterator; +use std::ops::Mul; pub mod c { use super::*; @@ -157,6 +158,54 @@ pub fn find_max_double(iterator: I, get: F) .0 } +pub trait DivCeil { + type Output; + fn div_ceil(self, rhs: Rhs) -> Self::Output; +} + +/// Newer Rust introduces this natively, +/// but we don't always have newer Rust. +impl DivCeil for i32 { + type Output = Self; + fn div_ceil(self, other: i32) -> Self::Output { + let d = self / other; + let m = self % other; + if m == 0 { d } else { d + 1} + } +} + +#[derive(Debug, Clone, Copy)] +pub struct Rational { + pub numerator: T, + pub denominator: u32, +} + +impl> Rational { + pub fn ceil(self) -> U { + self.numerator.div_ceil(self.denominator as i32) + } +} + +impl> Mul for Rational { + type Output = Self; + fn mul(self, m: i32) -> Self { + Self { + numerator: self.numerator * m, + denominator: self.denominator, + } + } +} + +impl> Mul> for Rational { + type Output = Self; + fn mul(self, m: Rational) -> Self { + Self { + numerator: self.numerator * m.numerator, + denominator: self.denominator * m.denominator, + } + } +} + /// Compares pointers but not internal values of Rc pub struct Pointer(pub Rc);