Merge branch 'phys_size' into 'master'

Physically-based sizing

See merge request World/Phosh/squeekboard!543
This commit is contained in:
dcz
2022-04-08 17:30:04 +00:00
3 changed files with 224 additions and 43 deletions

View File

@ -4,9 +4,11 @@
/*! Managing Wayland outputs */ /*! Managing Wayland outputs */
use std::ops;
use std::vec::Vec; use std::vec::Vec;
use crate::event_loop; use crate::event_loop;
use ::logging; use ::logging;
use crate::util::DivCeil;
// traits // traits
use ::logging::Warn; use ::logging::Warn;
@ -126,7 +128,7 @@ pub mod c {
outputs: COutputs, outputs: COutputs,
wl_output: WlOutput, wl_output: WlOutput,
_x: i32, _y: i32, _x: i32, _y: i32,
_phys_width: i32, _phys_height: i32, phys_width: i32, phys_height: i32,
_subpixel: i32, _subpixel: i32,
_make: *const c_char, _model: *const c_char, _make: *const c_char, _model: *const c_char,
transform: i32, transform: i32,
@ -144,7 +146,19 @@ pub mod c {
.find_output_mut(wl_output) .find_output_mut(wl_output)
.map(|o| &mut o.pending); .map(|o| &mut o.pending);
match output_state { match output_state {
Some(state) => { state.transform = Some(transform) }, Some(state) => {
fn maybe_mm(value: i32) -> Option<Millimeter> {
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!( None => log_print!(
logging::Level::Warning, logging::Level::Warning,
"Got geometry on unknown output", "Got geometry on unknown output",
@ -286,24 +300,51 @@ pub mod c {
// TODO: handle unregistration // TODO: handle unregistration
} }
/// Generic size /// Generic size
#[derive(Clone)] #[derive(Clone, Copy, Debug)]
pub struct Size { pub struct Size<Unit> {
pub width: u32, pub width: Unit,
pub height: u32, pub height: Unit,
} }
pub type PixelSize = Size<u32>;
/// wl_output mode /// wl_output mode
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct Mode { pub struct Mode {
width: i32, pub width: i32,
height: i32, pub height: i32,
}
#[derive(Clone, Copy, Debug)]
pub struct Millimeter(pub i32);
impl DivCeil<i32> for Millimeter {
type Output = Millimeter;
fn div_ceil(self, other: i32) -> Self {
Self(self.0.div_ceil(other))
}
}
impl ops::Mul<i32> 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<Option<Millimeter>>,
} }
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct OutputState { pub struct OutputState {
pub current_mode: Option<Mode>, pub current_mode: Option<Mode>,
pub transform: Option<c::Transform>, pub geometry: Option<Geometry>,
pub scale: i32, pub scale: i32,
} }
@ -317,33 +358,56 @@ impl OutputState {
fn uninitialized() -> OutputState { fn uninitialized() -> OutputState {
OutputState { OutputState {
current_mode: None, current_mode: None,
transform: None, geometry: None,
scale: 1, scale: 1,
} }
} }
pub fn get_pixel_size(&self) -> Option<Size> { fn transform_size<T>(
width: T,
height: T,
transform: self::c::Transform,
) -> Size<T> {
use self::c::Transform; 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<PixelSize> {
match self { match self {
OutputState { OutputState {
current_mode: Some(Mode { width, height } ), current_mode: Some(Mode { width, height } ),
transform: Some(transform), geometry: Some(Geometry { transform, .. } ),
scale: _, scale: _,
} => Some( } => Some(Self::transform_size(*width as u32, *height as u32, *transform)),
match transform { OutputState {
Transform::Normal current_mode: Some(Mode { width, height } ),
| Transform::Rotated180 ..
| Transform::Flipped } => Some(PixelSize { width: *width as u32, height: *height as u32 } ),
| Transform::FlippedRotated180 => Size { _ => None,
width: *width as u32, }
height: *height as u32, }
},
_ => Size { /// Return physical dimensions adjusted for current transform
width: *height as u32, pub fn get_physical_size(&self) -> Option<Size<Option<Millimeter>>> {
height: *width as u32, match self {
}, OutputState {
} geometry: Some(Geometry { transform, phys_size } ),
), ..
} => Some(Self::transform_size(phys_size.width, phys_size.height, *transform)),
_ => None, _ => None,
} }
} }

View File

@ -9,7 +9,8 @@ use crate::animation;
use crate::imservice::{ ContentHint, ContentPurpose }; use crate::imservice::{ ContentHint, ContentPurpose };
use crate::main::{ Commands, PanelCommand, PixelSize }; use crate::main::{ Commands, PanelCommand, PixelSize };
use crate::outputs; use crate::outputs;
use crate::outputs::{OutputId, OutputState}; use crate::outputs::{Millimeter, OutputId, OutputState};
use crate::util::Rational;
use std::cmp; use std::cmp;
use std::collections::HashMap; use std::collections::HashMap;
use std::time::Instant; use std::time::Instant;
@ -241,23 +242,65 @@ impl Application {
fn get_preferred_height(output: &OutputState) -> Option<PixelSize> { fn get_preferred_height(output: &OutputState) -> Option<PixelSize> {
output.get_pixel_size() output.get_pixel_size()
.map(|px_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<Millimeter> = Rational {
numerator: Millimeter(948),
denominator: 100,
};
// TODO: calculate based on selected layout
const ROW_COUNT: u32 = 4;
let height = { let height = {
if px_size.width > px_size.height { let ideal_height = IDEAL_TARGET_SIZE * ROW_COUNT as i32;
px_size.width / 5 let ideal_height_px = (ideal_height * density).ceil().0 as u32;
} else {
let abstract_width // Reduce height to match what the layout can fill.
= PixelSize { // For this, we need to guess if normal or wide will be picked up.
scale_factor: output.scale as u32, // This must match `eek_gtk_keyboard.c::get_type`.
pixels: px_size.width, // TODO: query layout database and choose one directly
} let abstract_width
.as_scaled_ceiling(); = PixelSize {
if (abstract_width < 540) && (px_size.width > 0) { scale_factor: output.scale as u32,
px_size.width * 7 / 12 // to match 360×210 pixels: px_size.width,
} else {
// Here we switch to wide layout, less height needed
px_size.width * 7 / 22
} }
} .as_scaled_ceiling();
let height_as_widths = {
if abstract_width < 540 {
// Normal
Rational {
numerator: 210,
denominator: 360,
}
} else {
// Wide
Rational {
numerator: 172,
denominator: 540,
}
}
};
cmp::min(
ideal_height_px,
(height_as_widths * px_size.width as i32).ceil() as u32,
)
}; };
PixelSize { PixelSize {
scale_factor: output.scale as u32, scale_factor: output.scale as u32,
@ -341,7 +384,7 @@ pub mod test {
id, id,
OutputState { OutputState {
current_mode: None, current_mode: None,
transform: None, geometry: None,
scale: 1, 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,
}),
);
}
} }

View File

@ -7,6 +7,7 @@ use ::float_ord::FloatOrd;
use std::borrow::Borrow; use std::borrow::Borrow;
use std::hash::{ Hash, Hasher }; use std::hash::{ Hash, Hasher };
use std::iter::FromIterator; use std::iter::FromIterator;
use std::ops::Mul;
pub mod c { pub mod c {
use super::*; use super::*;
@ -157,6 +158,54 @@ pub fn find_max_double<T, I, F>(iterator: I, get: F)
.0 .0
} }
pub trait DivCeil<Rhs = Self> {
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<T> {
pub numerator: T,
pub denominator: u32,
}
impl<U, T: DivCeil<i32, Output=U>> Rational<T> {
pub fn ceil(self) -> U {
self.numerator.div_ceil(self.denominator as i32)
}
}
impl<T: Mul<i32, Output=T>> Mul<i32> for Rational<T> {
type Output = Self;
fn mul(self, m: i32) -> Self {
Self {
numerator: self.numerator * m,
denominator: self.denominator,
}
}
}
impl<U, T: Mul<U, Output=T>> Mul<Rational<U>> for Rational<T> {
type Output = Self;
fn mul(self, m: Rational<U>) -> Self {
Self {
numerator: self.numerator * m.numerator,
denominator: self.denominator * m.denominator,
}
}
}
/// Compares pointers but not internal values of Rc /// Compares pointers but not internal values of Rc
pub struct Pointer<T>(pub Rc<T>); pub struct Pointer<T>(pub Rc<T>);