Commit 0eb9d89d authored by Dorota Czaplejewicz's avatar Dorota Czaplejewicz
Browse files

Merge branch 'hints' into 'master'

layout: Take into account text purpose again

Closes #277

See merge request !448
parents c23b6f5f 19087690
Pipeline #66552 passed with stages
in 77 minutes and 16 seconds
...@@ -69,8 +69,12 @@ pub mod c { ...@@ -69,8 +69,12 @@ pub mod c {
let overlay_str = as_str(&overlay) let overlay_str = as_str(&overlay)
.expect("Bad overlay name") .expect("Bad overlay name")
.expect("Empty overlay name"); .expect("Empty overlay name");
let overlay_str = match overlay_str {
"" => None,
other => Some(other),
};
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); let layout = ::layout::Layout::new(layout, kind);
Box::into_raw(Box::new(layout)) Box::into_raw(Box::new(layout))
} }
...@@ -113,97 +117,156 @@ impl fmt::Display for DataSource { ...@@ -113,97 +117,156 @@ impl fmt::Display for DataSource {
} }
} }
type LayoutSource = (ArrangementKind, DataSource); /* All functions in this family carry around ArrangementKind,
* because it's not guaranteed to be preserved,
/// Lists possible sources, with 0 as the most preferred one * and the resulting layout needs to know which version was loaded.
/// Trying order: native lang of the right kind, native base, * See `squeek_layout_get_kind`.
/// fallback lang of the right kind, fallback base * Possible TODO: since this is used only in styling,
/// The `purpose` argument is not ContentPurpose, * and makes the below code nastier than needed, maybe it should go.
/// but rather ContentPurpose and overlay in one. */
fn list_layout_sources(
name: &str, /// Returns ordered names treating `name` as the base name,
kind: ArrangementKind, /// ignoring any `+` inside.
purpose: &str, fn _get_arrangement_names(name: &str, arrangement: ArrangementKind)
keyboards_path: Option<PathBuf>, -> Vec<(ArrangementKind, String)>
) -> Vec<LayoutSource> { {
// Just a simplification of often called code. let name_with_arrangement = match arrangement {
let add_by_name = | ArrangementKind::Base => name.into(),
mut ret: Vec<LayoutSource>, ArrangementKind::Wide => format!("{}_wide", name),
purpose: &str,
name: &str,
kind: &ArrangementKind,
| -> Vec<LayoutSource> {
let name = if purpose == "" { name.into() }
else { format!("{}/{}", purpose, name) };
if let Some(path) = keyboards_path.clone() {
ret.push((
kind.clone(),
DataSource::File(
path.join(name.clone())
.with_extension("yaml")
)
))
}
ret.push((
kind.clone(),
DataSource::Resource(name)
));
ret
};
// Another grouping.
let add_by_kind = |ret, purpose: &str, name: &str, kind| {
let ret = match kind {
&ArrangementKind::Base => ret,
kind => add_by_name(
ret,
purpose,
&name_with_arrangement(name.into(), kind),
kind,
),
};
add_by_name(ret, purpose, name, &ArrangementKind::Base)
}; };
fn name_with_arrangement( let mut ret = Vec::new();
name: String, if name_with_arrangement != name {
kind: &ArrangementKind, ret.push((arrangement, name_with_arrangement));
) -> String {
match kind {
ArrangementKind::Base => name,
ArrangementKind::Wide => name + "_wide",
}
} }
ret.push((ArrangementKind::Base, name.into()));
ret
}
let ret = Vec::new(); /// Returns names accounting for any `+` in the `name`,
/// including the fallback to the default layout.
// Name as given takes priority. fn get_preferred_names(name: &str, kind: ArrangementKind)
let ret = add_by_kind(ret, purpose, name, &kind); -> Vec<(ArrangementKind, String)>
{
// Then try non-alternative name if applicable (`us` for `us+colemak`). let mut ret = _get_arrangement_names(name, kind);
let ret = {
let base_name_preferences = {
let mut parts = name.splitn(2, '+'); let mut parts = name.splitn(2, '+');
match parts.next() { match parts.next() {
Some(base) => { Some(base) => {
// The name is already equal to base, so it was already added. // The name is already equal to base, so nothing to add
if base == name { ret } if base == name {
else { vec![]
add_by_kind(ret, purpose, base, &kind) } else {
_get_arrangement_names(base, kind)
} }
}, },
// The layout's base name starts with a "+". Weird but OK. // The layout's base name starts with a "+". Weird but OK.
None => { None => {
log_print!(logging::Level::Surprise, "Base layout name is empty: {}", name); log_print!(logging::Level::Surprise, "Base layout name is empty: {}", name);
ret vec![]
} }
} }
}; };
ret.extend(base_name_preferences.into_iter());
let fallback_names = _get_arrangement_names(FALLBACK_LAYOUT_NAME, kind);
ret.extend(fallback_names.into_iter());
ret
}
/// Includes the subdirectory before the forward slash.
type LayoutPath = String;
// This is only used inside iter_fallbacks_with_meta.
// Placed at the top scope
// because `use LayoutPurpose::*;`
// complains about "not in scope" otherwise.
// This seems to be a Rust 2015 edition problem.
/// Helper for determining where to look up the layout.
enum LayoutPurpose<'a> {
Default,
Special(&'a str),
}
/// Returns the directory string
/// where the layout should be looked up, including the slash.
fn get_directory_string(
content_purpose: ContentPurpose,
overlay: Option<&str>) -> String
{
use self::LayoutPurpose::*;
let layout_purpose = match overlay {
None => match content_purpose {
ContentPurpose::Number => Special("number"),
ContentPurpose::Digits => Special("number"),
ContentPurpose::Phone => Special("number"),
ContentPurpose::Terminal => Special("terminal"),
_ => Default,
},
Some(overlay) => Special(overlay),
};
// For intuitiveness,
// default purpose layouts are stored in the root directory,
// as they correspond to typical text
// and are seen the most often.
match layout_purpose {
Default => "".into(),
Special(purpose) => format!("{}/", purpose),
}
}
/// Returns an iterator over all fallback paths.
fn to_layout_paths(
name_fallbacks: Vec<(ArrangementKind, String)>,
content_purpose: ContentPurpose,
overlay: Option<&str>,
) -> impl Iterator<Item=(ArrangementKind, LayoutPath)> {
let prepend_directory = get_directory_string(content_purpose, overlay);
name_fallbacks.into_iter()
.map(move |(arrangement, name)|
(arrangement, format!("{}{}", prepend_directory, name))
)
}
type LayoutSource = (ArrangementKind, DataSource);
add_by_kind(ret, purpose, FALLBACK_LAYOUT_NAME.into(), &kind) fn to_layout_sources(
layout_paths: impl Iterator<Item=(ArrangementKind, LayoutPath)>,
filesystem_path: Option<PathBuf>,
) -> impl Iterator<Item=LayoutSource> {
layout_paths.flat_map(move |(arrangement, layout_path)| {
let mut sources = Vec::new();
if let Some(path) = &filesystem_path {
sources.push((
arrangement,
DataSource::File(
path.join(&layout_path)
.with_extension("yaml")
)
));
};
sources.push((arrangement, DataSource::Resource(layout_path.clone())));
sources.into_iter()
})
}
/// Returns possible sources, with first as the most preferred one.
/// Trying order: native lang of the right kind, native base,
/// fallback lang of the right kind, fallback base
fn iter_layout_sources(
name: &str,
arrangement: ArrangementKind,
purpose: ContentPurpose,
ui_overlay: Option<&str>,
layout_storage: Option<PathBuf>,
) -> impl Iterator<Item=LayoutSource> {
let names = get_preferred_names(name, arrangement);
let paths = to_layout_paths(names, purpose, ui_overlay);
to_layout_sources(paths, layout_storage)
} }
fn load_layout_data(source: DataSource) fn load_layout_data(source: DataSource)
...@@ -231,7 +294,7 @@ fn load_layout_data_with_fallback( ...@@ -231,7 +294,7 @@ fn load_layout_data_with_fallback(
name: &str, name: &str,
kind: ArrangementKind, kind: ArrangementKind,
purpose: ContentPurpose, purpose: ContentPurpose,
overlay: &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
...@@ -239,13 +302,7 @@ fn load_layout_data_with_fallback( ...@@ -239,13 +302,7 @@ fn load_layout_data_with_fallback(
.map(PathBuf::from) .map(PathBuf::from)
.or_else(|| xdg::data_path("squeekboard/keyboards")); .or_else(|| xdg::data_path("squeekboard/keyboards"));
log_print!( for (kind, source) in iter_layout_sources(&name, kind, purpose, overlay, path) {
logging::Level::Debug,
"load_layout_data_with_fallback() -> name:{}, purpose:{:?}, overlay:{}, layout_name:{}",
name, purpose, overlay, &name
);
for (kind, source) in list_layout_sources(&name, kind, overlay, path) {
let layout = load_layout_data(source.clone()); let layout = load_layout_data(source.clone());
match layout { match layout {
Err(e) => match (e, source) { Err(e) => match (e, source) {
...@@ -977,11 +1034,11 @@ mod tests { ...@@ -977,11 +1034,11 @@ mod tests {
/// First fallback should be to builtin, not to FALLBACK_LAYOUT_NAME /// First fallback should be to builtin, not to FALLBACK_LAYOUT_NAME
#[test] #[test]
fn fallbacks_order() { fn test_fallback_basic_builtin() {
let sources = list_layout_sources("nb", ArrangementKind::Base, "", None); let sources = iter_layout_sources("nb", ArrangementKind::Base, ContentPurpose::Normal, None, None);
assert_eq!( assert_eq!(
sources, sources.collect::<Vec<_>>(),
vec!( vec!(
(ArrangementKind::Base, DataSource::Resource("nb".into())), (ArrangementKind::Base, DataSource::Resource("nb".into())),
( (
...@@ -991,14 +1048,36 @@ mod tests { ...@@ -991,14 +1048,36 @@ mod tests {
) )
); );
} }
/// Prefer loading from file system before builtin.
#[test]
fn test_preferences_order_path() {
let sources = iter_layout_sources("nb", ArrangementKind::Base, ContentPurpose::Normal, None, Some(".".into()));
assert_eq!(
sources.collect::<Vec<_>>(),
vec!(
(ArrangementKind::Base, DataSource::File("./nb.yaml".into())),
(ArrangementKind::Base, DataSource::Resource("nb".into())),
(
ArrangementKind::Base,
DataSource::File("./us.yaml".into())
),
(
ArrangementKind::Base,
DataSource::Resource("us".into())
),
)
);
}
/// If layout contains a "+", it should reach for what's in front of it too. /// If layout contains a "+", it should reach for what's in front of it too.
#[test] #[test]
fn fallbacks_order_base() { fn test_preferences_order_base() {
let sources = list_layout_sources("nb+aliens", ArrangementKind::Base, "", None); let sources = iter_layout_sources("nb+aliens", ArrangementKind::Base, ContentPurpose::Normal, None, None);
assert_eq!( assert_eq!(
sources, sources.collect::<Vec<_>>(),
vec!( vec!(
(ArrangementKind::Base, DataSource::Resource("nb+aliens".into())), (ArrangementKind::Base, DataSource::Resource("nb+aliens".into())),
(ArrangementKind::Base, DataSource::Resource("nb".into())), (ArrangementKind::Base, DataSource::Resource("nb".into())),
...@@ -1011,22 +1090,58 @@ mod tests { ...@@ -1011,22 +1090,58 @@ mod tests {
} }
#[test] #[test]
fn fallbacks_terminal_order_base() { fn test_preferences_order_arrangement() {
let sources = list_layout_sources("nb+aliens", ArrangementKind::Base, "terminal", None); let sources = iter_layout_sources("nb", ArrangementKind::Wide, ContentPurpose::Normal, None, None);
assert_eq!( assert_eq!(
sources, sources.collect::<Vec<_>>(),
vec!(
(ArrangementKind::Wide, DataSource::Resource("nb_wide".into())),
(ArrangementKind::Base, DataSource::Resource("nb".into())),
(
ArrangementKind::Wide,
DataSource::Resource("us_wide".into())
),
(
ArrangementKind::Base,
DataSource::Resource("us".into())
),
)
);
}
#[test]
fn test_preferences_order_overlay() {
let sources = iter_layout_sources("nb", ArrangementKind::Base, ContentPurpose::Normal, Some("terminal"), None);
assert_eq!(
sources.collect::<Vec<_>>(),
vec!( vec!(
(ArrangementKind::Base, DataSource::Resource("terminal/nb+aliens".into())),
(ArrangementKind::Base, DataSource::Resource("terminal/nb".into())), (ArrangementKind::Base, DataSource::Resource("terminal/nb".into())),
( (
ArrangementKind::Base, ArrangementKind::Base,
DataSource::Resource(format!("terminal/{}", FALLBACK_LAYOUT_NAME)) DataSource::Resource("terminal/us".into())
), ),
) )
); );
} }
#[test]
fn test_preferences_order_hint() {
let sources = iter_layout_sources("nb", ArrangementKind::Base, ContentPurpose::Terminal, None, None);
assert_eq!(
sources.collect::<Vec<_>>(),
vec!(
(ArrangementKind::Base, DataSource::Resource("terminal/nb".into())),
(
ArrangementKind::Base,
DataSource::Resource("terminal/us".into())
),
)
);
}
#[test] #[test]
fn unicode_keysym() { fn unicode_keysym() {
let keysym = xkb::keysym_from_name( let keysym = xkb::keysym_from_name(
......
...@@ -600,7 +600,7 @@ impl View { ...@@ -600,7 +600,7 @@ impl View {
} }
/// The physical characteristic of layout for the purpose of styling /// The physical characteristic of layout for the purpose of styling
#[derive(Clone, PartialEq, Debug)] #[derive(Clone, Copy, PartialEq, Debug)]
pub enum ArrangementKind { pub enum ArrangementKind {
Base = 0, Base = 0,
Wide = 1, Wide = 1,
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment