state: Decide panel arrangement
Combines arrangement with layout to get panel contents as outcome. Includes some path syntax changes for 2018 compatibility.
This commit is contained in:
@ -8,16 +8,26 @@ use std::time::Duration;
|
|||||||
|
|
||||||
use crate::outputs::OutputId;
|
use crate::outputs::OutputId;
|
||||||
use crate::panel::PixelSize;
|
use crate::panel::PixelSize;
|
||||||
|
use crate::layout::ArrangementKind;
|
||||||
|
|
||||||
/// The keyboard should hide after this has elapsed to prevent flickering.
|
/// The keyboard should hide after this has elapsed to prevent flickering.
|
||||||
pub const HIDING_TIMEOUT: Duration = Duration::from_millis(200);
|
pub const HIDING_TIMEOUT: Duration = Duration::from_millis(200);
|
||||||
|
|
||||||
|
/// Panel contents
|
||||||
|
#[derive(PartialEq, Clone, Debug)]
|
||||||
|
pub struct Contents {
|
||||||
|
pub name: String,
|
||||||
|
pub kind: ArrangementKind,
|
||||||
|
pub overlay_name: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
/// The outwardly visible state of visibility
|
/// The outwardly visible state of visibility
|
||||||
#[derive(PartialEq, Debug, Clone)]
|
#[derive(PartialEq, Debug, Clone)]
|
||||||
pub enum Outcome {
|
pub enum Outcome {
|
||||||
Visible {
|
Visible {
|
||||||
output: OutputId,
|
output: OutputId,
|
||||||
height: PixelSize,
|
height: PixelSize,
|
||||||
|
contents: Contents,
|
||||||
},
|
},
|
||||||
Hidden,
|
Hidden,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,6 +12,7 @@ use std::convert::TryFrom;
|
|||||||
use super::{ Error, LoadError };
|
use super::{ Error, LoadError };
|
||||||
use super::parsing;
|
use super::parsing;
|
||||||
|
|
||||||
|
use crate::layout;
|
||||||
use ::layout::ArrangementKind;
|
use ::layout::ArrangementKind;
|
||||||
use ::logging;
|
use ::logging;
|
||||||
use ::util::c::as_str;
|
use ::util::c::as_str;
|
||||||
@ -33,8 +34,10 @@ pub mod c {
|
|||||||
name: *const c_char, // name of the keyboard
|
name: *const c_char, // name of the keyboard
|
||||||
type_: u32, // type like Wide
|
type_: u32, // type like Wide
|
||||||
variant: u32, // purpose variant like numeric, terminal...
|
variant: u32, // purpose variant like numeric, terminal...
|
||||||
overlay: *const c_char, // the overlay (looking for "terminal")
|
// Overlay forces a variant other than specified
|
||||||
) -> *mut ::layout::Layout {
|
// (typically "terminal", "emoji")
|
||||||
|
overlay: *const c_char,
|
||||||
|
) -> *mut layout::Layout {
|
||||||
let type_ = match type_ {
|
let type_ = match type_ {
|
||||||
0 => ArrangementKind::Base,
|
0 => ArrangementKind::Base,
|
||||||
1 => ArrangementKind::Wide,
|
1 => ArrangementKind::Wide,
|
||||||
@ -60,8 +63,10 @@ pub mod c {
|
|||||||
other => Some(other),
|
other => Some(other),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
dbg!(&name, type_, variant, overlay_str);
|
||||||
|
|
||||||
let (kind, layout) = load_layout_data_with_fallback(&name, type_, variant, overlay_str);
|
let (kind, layout) = load_layout_data_with_fallback(&name, type_, variant, overlay_str);
|
||||||
let layout = ::layout::Layout::new(layout, kind, variant);
|
let layout = layout::Layout::new(layout, kind, variant);
|
||||||
Box::into_raw(Box::new(layout))
|
Box::into_raw(Box::new(layout))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -265,7 +270,7 @@ fn load_layout_data_with_fallback(
|
|||||||
kind: ArrangementKind,
|
kind: ArrangementKind,
|
||||||
purpose: ContentPurpose,
|
purpose: ContentPurpose,
|
||||||
overlay: Option<&str>,
|
overlay: Option<&str>,
|
||||||
) -> (ArrangementKind, ::layout::LayoutData) {
|
) -> (ArrangementKind, layout::LayoutData) {
|
||||||
|
|
||||||
// Build the path to the right keyboard layout subdirectory
|
// Build the path to the right keyboard layout subdirectory
|
||||||
let path = env::var_os("SQUEEKBOARD_KEYBOARDSDIR")
|
let path = env::var_os("SQUEEKBOARD_KEYBOARDSDIR")
|
||||||
|
|||||||
165
src/state.rs
165
src/state.rs
@ -8,6 +8,7 @@
|
|||||||
use crate::animation;
|
use crate::animation;
|
||||||
use crate::debug;
|
use crate::debug;
|
||||||
use crate::imservice::{ ContentHint, ContentPurpose };
|
use crate::imservice::{ ContentHint, ContentPurpose };
|
||||||
|
use crate::layout::ArrangementKind;
|
||||||
use crate::main::Commands;
|
use crate::main::Commands;
|
||||||
use crate::outputs;
|
use crate::outputs;
|
||||||
use crate::outputs::{Millimeter, OutputId, OutputState};
|
use crate::outputs::{Millimeter, OutputId, OutputState};
|
||||||
@ -113,9 +114,8 @@ pub mod visibility {
|
|||||||
/// The outwardly visible state.
|
/// The outwardly visible state.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Outcome {
|
pub struct Outcome {
|
||||||
pub visibility: animation::Outcome,
|
pub panel: animation::Outcome,
|
||||||
pub im: InputMethod,
|
pub im: InputMethod,
|
||||||
pub layout: LayoutChoice,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Outcome {
|
impl Outcome {
|
||||||
@ -127,13 +127,13 @@ impl Outcome {
|
|||||||
pub fn get_commands_to_reach(&self, new_state: &Self) -> Commands {
|
pub fn get_commands_to_reach(&self, new_state: &Self) -> Commands {
|
||||||
let layout_hint_set = match new_state {
|
let layout_hint_set = match new_state {
|
||||||
Outcome {
|
Outcome {
|
||||||
visibility: animation::Outcome::Visible{..},
|
panel: animation::Outcome::Visible{..},
|
||||||
im: InputMethod::Active(hints),
|
im: InputMethod::Active(hints),
|
||||||
..
|
..
|
||||||
} => Some(hints.clone()),
|
} => Some(hints.clone()),
|
||||||
|
|
||||||
Outcome {
|
Outcome {
|
||||||
visibility: animation::Outcome::Visible{..},
|
panel: animation::Outcome::Visible{..},
|
||||||
im: InputMethod::InactiveSince(_),
|
im: InputMethod::InactiveSince(_),
|
||||||
..
|
..
|
||||||
} => Some(InputMethodDetails {
|
} => Some(InputMethodDetails {
|
||||||
@ -142,13 +142,13 @@ impl Outcome {
|
|||||||
}),
|
}),
|
||||||
|
|
||||||
Outcome {
|
Outcome {
|
||||||
visibility: animation::Outcome::Hidden,
|
panel: animation::Outcome::Hidden,
|
||||||
..
|
..
|
||||||
} => None,
|
} => None,
|
||||||
};
|
};
|
||||||
// FIXME: handle switching outputs
|
// FIXME: handle switching outputs
|
||||||
let (dbus_visible_set, panel_visibility) = match new_state.visibility {
|
let (dbus_visible_set, panel_visibility) = match new_state.panel {
|
||||||
animation::Outcome::Visible{output, height}
|
animation::Outcome::Visible{output, height, ..}
|
||||||
=> (Some(true), Some(panel::Command::Show{output, height})),
|
=> (Some(true), Some(panel::Command::Show{output, height})),
|
||||||
animation::Outcome::Hidden => (Some(false), Some(panel::Command::Hide)),
|
animation::Outcome::Hidden => (Some(false), Some(panel::Command::Hide)),
|
||||||
};
|
};
|
||||||
@ -325,7 +325,9 @@ Outcome:
|
|||||||
state
|
state
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_preferred_height(output: &OutputState) -> Option<PixelSize> {
|
fn get_preferred_height_and_arrangement(output: &OutputState)
|
||||||
|
-> Option<(PixelSize, ArrangementKind)>
|
||||||
|
{
|
||||||
output.get_pixel_size()
|
output.get_pixel_size()
|
||||||
.map(|px_size| {
|
.map(|px_size| {
|
||||||
// Assume isotropy.
|
// Assume isotropy.
|
||||||
@ -353,61 +355,91 @@ Outcome:
|
|||||||
// TODO: calculate based on selected layout
|
// TODO: calculate based on selected layout
|
||||||
const ROW_COUNT: u32 = 4;
|
const ROW_COUNT: u32 = 4;
|
||||||
|
|
||||||
let height = {
|
let ideal_height = IDEAL_TARGET_SIZE * ROW_COUNT as i32;
|
||||||
let ideal_height = IDEAL_TARGET_SIZE * ROW_COUNT as i32;
|
let ideal_height_px = (ideal_height * density).ceil().0 as u32;
|
||||||
let ideal_height_px = (ideal_height * density).ceil().0 as u32;
|
|
||||||
|
|
||||||
// Reduce height to match what the layout can fill.
|
// Reduce height to match what the layout can fill.
|
||||||
// For this, we need to guess if normal or wide will be picked up.
|
// For this, we need to guess if normal or wide will be picked up.
|
||||||
// This must match `eek_gtk_keyboard.c::get_type`.
|
// This must match `eek_gtk_keyboard.c::get_type`.
|
||||||
// TODO: query layout database and choose one directly
|
// TODO: query layout database and choose one directly
|
||||||
let abstract_width
|
let abstract_width
|
||||||
= PixelSize {
|
= PixelSize {
|
||||||
scale_factor: output.scale as u32,
|
scale_factor: output.scale as u32,
|
||||||
pixels: px_size.width,
|
pixels: px_size.width,
|
||||||
}
|
}
|
||||||
.as_scaled_ceiling();
|
.as_scaled_ceiling();
|
||||||
|
|
||||||
let height_as_widths = {
|
let (arrangement, height_as_widths) = {
|
||||||
if abstract_width < 540 {
|
if abstract_width < 540 {(
|
||||||
// Normal
|
ArrangementKind::Base,
|
||||||
Rational {
|
Rational {
|
||||||
numerator: 210,
|
numerator: 210,
|
||||||
denominator: 360,
|
denominator: 360,
|
||||||
}
|
},
|
||||||
} else {
|
)} else {(
|
||||||
// Wide
|
ArrangementKind::Wide,
|
||||||
Rational {
|
Rational {
|
||||||
numerator: 172,
|
numerator: 172,
|
||||||
denominator: 540,
|
denominator: 540,
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
)}
|
||||||
cmp::min(
|
};
|
||||||
|
|
||||||
|
let height
|
||||||
|
= cmp::min(
|
||||||
ideal_height_px,
|
ideal_height_px,
|
||||||
(height_as_widths * px_size.width as i32).ceil() as u32,
|
(height_as_widths * px_size.width as i32).ceil() as u32,
|
||||||
)
|
);
|
||||||
};
|
|
||||||
PixelSize {
|
(
|
||||||
scale_factor: output.scale as u32,
|
PixelSize {
|
||||||
pixels: cmp::min(height, px_size.height / 2),
|
scale_factor: output.scale as u32,
|
||||||
}
|
pixels: cmp::min(height, px_size.height / 2),
|
||||||
|
},
|
||||||
|
arrangement,
|
||||||
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns layout name, overlay name
|
||||||
|
fn get_layout_names(&self) -> (String, Option<String>) {
|
||||||
|
(
|
||||||
|
String::from(match &self.overlay_layout {
|
||||||
|
Some(popover::LayoutId::System { name, .. }) => name,
|
||||||
|
_ => &self.layout_choice.name,
|
||||||
|
}),
|
||||||
|
match &self.overlay_layout {
|
||||||
|
Some(popover::LayoutId::Local(name)) => Some(name.clone()),
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_outcome(&self, now: Instant) -> Outcome {
|
pub fn get_outcome(&self, now: Instant) -> Outcome {
|
||||||
// FIXME: include physical keyboard presence
|
// FIXME: include physical keyboard presence
|
||||||
Outcome {
|
Outcome {
|
||||||
visibility: match self.preferred_output {
|
panel: match self.preferred_output {
|
||||||
None => animation::Outcome::Hidden,
|
None => animation::Outcome::Hidden,
|
||||||
Some(output) => {
|
Some(output) => {
|
||||||
// Hoping that this will get optimized out on branches not using `visible`.
|
let (height, arrangement) = Self::get_preferred_height_and_arrangement(self.outputs.get(&output).unwrap())
|
||||||
let height = Self::get_preferred_height(self.outputs.get(&output).unwrap())
|
.unwrap_or((
|
||||||
.unwrap_or(PixelSize{pixels: 0, scale_factor: 1});
|
PixelSize{pixels: 0, scale_factor: 1},
|
||||||
|
ArrangementKind::Base,
|
||||||
|
));
|
||||||
|
let (layout_name, overlay) = self.get_layout_names();
|
||||||
|
|
||||||
// TODO: Instead of setting size to 0 when the output is invalid,
|
// TODO: Instead of setting size to 0 when the output is invalid,
|
||||||
// simply go invisible.
|
// simply go invisible.
|
||||||
let visible = animation::Outcome::Visible{ output, height };
|
let visible = animation::Outcome::Visible{
|
||||||
|
output,
|
||||||
|
height,
|
||||||
|
contents: animation::Contents {
|
||||||
|
kind: arrangement,
|
||||||
|
name: layout_name,
|
||||||
|
overlay_name: overlay,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
match (self.physical_keyboard, self.visibility_override) {
|
match (self.physical_keyboard, self.visibility_override) {
|
||||||
(_, visibility::State::ForcedHidden) => animation::Outcome::Hidden,
|
(_, visibility::State::ForcedHidden) => animation::Outcome::Hidden,
|
||||||
(_, visibility::State::ForcedVisible) => visible,
|
(_, visibility::State::ForcedVisible) => visible,
|
||||||
@ -423,7 +455,6 @@ Outcome:
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
im: self.im.clone(),
|
im: self.im.clone(),
|
||||||
layout: self.layout_choice.clone(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -499,7 +530,7 @@ pub mod test {
|
|||||||
for _i in 0..100 {
|
for _i in 0..100 {
|
||||||
now += Duration::from_millis(1);
|
now += Duration::from_millis(1);
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
state.get_outcome(now).visibility,
|
state.get_outcome(now).panel,
|
||||||
animation::Outcome::Visible{..},
|
animation::Outcome::Visible{..},
|
||||||
"Hidden when it should remain visible: {:?}",
|
"Hidden when it should remain visible: {:?}",
|
||||||
now.saturating_duration_since(start),
|
now.saturating_duration_since(start),
|
||||||
@ -508,7 +539,10 @@ pub mod test {
|
|||||||
|
|
||||||
let state = state.apply_event(Event::InputMethod(InputMethod::Active(imdetails_new())), now);
|
let state = state.apply_event(Event::InputMethod(InputMethod::Active(imdetails_new())), now);
|
||||||
|
|
||||||
assert_matches!(state.get_outcome(now).visibility, animation::Outcome::Visible{..});
|
assert_matches!(
|
||||||
|
state.get_outcome(now).panel,
|
||||||
|
animation::Outcome::Visible{..}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Make sure that hiding works when input method goes away
|
/// Make sure that hiding works when input method goes away
|
||||||
@ -525,7 +559,7 @@ pub mod test {
|
|||||||
|
|
||||||
let state = state.apply_event(Event::InputMethod(InputMethod::InactiveSince(now)), now);
|
let state = state.apply_event(Event::InputMethod(InputMethod::InactiveSince(now)), now);
|
||||||
|
|
||||||
while let animation::Outcome::Visible{..} = state.get_outcome(now).visibility {
|
while let animation::Outcome::Visible{..} = state.get_outcome(now).panel {
|
||||||
now += Duration::from_millis(1);
|
now += Duration::from_millis(1);
|
||||||
assert!(
|
assert!(
|
||||||
now < start + Duration::from_millis(250),
|
now < start + Duration::from_millis(250),
|
||||||
@ -555,7 +589,7 @@ pub mod test {
|
|||||||
let state = state.apply_event(Event::InputMethod(InputMethod::Active(imdetails_new())), now);
|
let state = state.apply_event(Event::InputMethod(InputMethod::Active(imdetails_new())), now);
|
||||||
let state = state.apply_event(Event::InputMethod(InputMethod::InactiveSince(now)), now);
|
let state = state.apply_event(Event::InputMethod(InputMethod::InactiveSince(now)), now);
|
||||||
|
|
||||||
while let animation::Outcome::Visible{..} = state.get_outcome(now).visibility {
|
while let animation::Outcome::Visible{..} = state.get_outcome(now).panel {
|
||||||
now += Duration::from_millis(1);
|
now += Duration::from_millis(1);
|
||||||
assert!(
|
assert!(
|
||||||
now < start + Duration::from_millis(250),
|
now < start + Duration::from_millis(250),
|
||||||
@ -568,7 +602,7 @@ pub mod test {
|
|||||||
for _i in 0..1000 {
|
for _i in 0..1000 {
|
||||||
now += Duration::from_millis(1);
|
now += Duration::from_millis(1);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
state.get_outcome(now).visibility,
|
state.get_outcome(now).panel,
|
||||||
animation::Outcome::Hidden,
|
animation::Outcome::Hidden,
|
||||||
"Appeared unnecessarily: {:?}",
|
"Appeared unnecessarily: {:?}",
|
||||||
now.saturating_duration_since(start),
|
now.saturating_duration_since(start),
|
||||||
@ -590,7 +624,7 @@ pub mod test {
|
|||||||
|
|
||||||
let state = state.apply_event(Event::Visibility(visibility::Event::ForceVisible), now);
|
let state = state.apply_event(Event::Visibility(visibility::Event::ForceVisible), now);
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
state.get_outcome(now).visibility,
|
state.get_outcome(now).panel,
|
||||||
animation::Outcome::Visible{..},
|
animation::Outcome::Visible{..},
|
||||||
"Failed to show: {:?}",
|
"Failed to show: {:?}",
|
||||||
now.saturating_duration_since(start),
|
now.saturating_duration_since(start),
|
||||||
@ -603,7 +637,7 @@ pub mod test {
|
|||||||
now += Duration::from_secs(1);
|
now += Duration::from_secs(1);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
state.get_outcome(now).visibility,
|
state.get_outcome(now).panel,
|
||||||
animation::Outcome::Hidden,
|
animation::Outcome::Hidden,
|
||||||
"Failed to release forced visibility: {:?}",
|
"Failed to release forced visibility: {:?}",
|
||||||
now.saturating_duration_since(start),
|
now.saturating_duration_since(start),
|
||||||
@ -624,7 +658,7 @@ pub mod test {
|
|||||||
|
|
||||||
let state = state.apply_event(Event::PhysicalKeyboard(Presence::Present), now);
|
let state = state.apply_event(Event::PhysicalKeyboard(Presence::Present), now);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
state.get_outcome(now).visibility,
|
state.get_outcome(now).panel,
|
||||||
animation::Outcome::Hidden,
|
animation::Outcome::Hidden,
|
||||||
"Failed to hide: {:?}",
|
"Failed to hide: {:?}",
|
||||||
now.saturating_duration_since(start),
|
now.saturating_duration_since(start),
|
||||||
@ -636,7 +670,7 @@ pub mod test {
|
|||||||
let state = state.apply_event(Event::InputMethod(InputMethod::Active(imdetails_new())), now);
|
let state = state.apply_event(Event::InputMethod(InputMethod::Active(imdetails_new())), now);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
state.get_outcome(now).visibility,
|
state.get_outcome(now).panel,
|
||||||
animation::Outcome::Hidden,
|
animation::Outcome::Hidden,
|
||||||
"Failed to remain hidden: {:?}",
|
"Failed to remain hidden: {:?}",
|
||||||
now.saturating_duration_since(start),
|
now.saturating_duration_since(start),
|
||||||
@ -646,7 +680,7 @@ pub mod test {
|
|||||||
let state = state.apply_event(Event::PhysicalKeyboard(Presence::Missing), now);
|
let state = state.apply_event(Event::PhysicalKeyboard(Presence::Missing), now);
|
||||||
|
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
state.get_outcome(now).visibility,
|
state.get_outcome(now).panel,
|
||||||
animation::Outcome::Visible{..},
|
animation::Outcome::Visible{..},
|
||||||
"Failed to appear: {:?}",
|
"Failed to appear: {:?}",
|
||||||
now.saturating_duration_since(start),
|
now.saturating_duration_since(start),
|
||||||
@ -658,7 +692,7 @@ pub mod test {
|
|||||||
fn size_l5() {
|
fn size_l5() {
|
||||||
use crate::outputs::{Mode, Geometry, c, Size};
|
use crate::outputs::{Mode, Geometry, c, Size};
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Application::get_preferred_height(&OutputState {
|
Application::get_preferred_height_and_arrangement(&OutputState {
|
||||||
current_mode: Some(Mode {
|
current_mode: Some(Mode {
|
||||||
width: 720,
|
width: 720,
|
||||||
height: 1440,
|
height: 1440,
|
||||||
@ -672,10 +706,13 @@ pub mod test {
|
|||||||
}),
|
}),
|
||||||
scale: 2,
|
scale: 2,
|
||||||
}),
|
}),
|
||||||
Some(PixelSize {
|
Some((
|
||||||
scale_factor: 2,
|
PixelSize {
|
||||||
pixels: 420,
|
scale_factor: 2,
|
||||||
}),
|
pixels: 420,
|
||||||
|
},
|
||||||
|
ArrangementKind::Base,
|
||||||
|
)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user