layout: Add stateless view switching
Tested, but not yet plugged in.
This commit is contained in:
300
src/layout.rs
300
src/layout.rs
@ -552,6 +552,7 @@ pub struct Spacing {
|
||||
pub button: f64,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct View {
|
||||
/// Rows together with their offsets from the top left
|
||||
rows: Vec<(c::Point, Row)>,
|
||||
@ -825,10 +826,141 @@ impl Layout {
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
|
||||
fn apply_view_transition(
|
||||
&mut self,
|
||||
action: &Action,
|
||||
) {
|
||||
let (transition, new_latched) = Layout::process_action_for_view(
|
||||
action,
|
||||
&self.current_view,
|
||||
self.view_latched,
|
||||
);
|
||||
|
||||
self.view_latched = new_latched;
|
||||
match transition {
|
||||
ViewTransition::UnlatchAll => unstick_locks(self).apply(),
|
||||
ViewTransition::ChangeTo(view) => {
|
||||
self.current_view = view.into();
|
||||
},
|
||||
ViewTransition::NoChange => {},
|
||||
};
|
||||
}
|
||||
|
||||
/// Last bool is new latch state.
|
||||
/// It doesn't make sense when the result carries UnlatchAll,
|
||||
/// but let's not be picky.
|
||||
///
|
||||
/// Although the state is not defined at the keys
|
||||
/// (it's in the relationship between view and action),
|
||||
/// keys go through the following stages when clicked repeatedly:
|
||||
/// unlocked+unlatched -> locked+latched -> locked+unlatched
|
||||
/// -> unlocked+unlatched
|
||||
fn process_action_for_view<'a>(
|
||||
action: &'a Action,
|
||||
current_view: &str,
|
||||
is_latched: bool,
|
||||
) -> (ViewTransition<'a>, bool) {
|
||||
match action {
|
||||
Action::Submit { text: _, keys: _ }
|
||||
| Action::Erase
|
||||
| Action::ApplyModifier(_)
|
||||
=> {
|
||||
let t = match is_latched {
|
||||
true => ViewTransition::UnlatchAll,
|
||||
false => ViewTransition::NoChange,
|
||||
};
|
||||
(t, false)
|
||||
},
|
||||
Action::SetView(view) => (ViewTransition::ChangeTo(view), false),
|
||||
Action::LockView { lock, unlock } => {
|
||||
use self::ViewTransition as VT;
|
||||
let locked = action.is_locked(current_view);
|
||||
match (locked, is_latched) {
|
||||
// Was unlocked, now make locked but latched. (false)
|
||||
// OR (true)
|
||||
// Layout is latched for reason other than this button.
|
||||
(false, _) => (VT::ChangeTo(lock), true),
|
||||
// Was latched, now only locked.
|
||||
(true, true) => (VT::NoChange, false),
|
||||
// Was locked, now make unlocked.
|
||||
(true, false) => (VT::ChangeTo(unlock), false),
|
||||
}
|
||||
},
|
||||
_ => (ViewTransition::NoChange, is_latched),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum ViewTransition<'a> {
|
||||
ChangeTo(&'a str),
|
||||
UnlatchAll,
|
||||
NoChange,
|
||||
}
|
||||
|
||||
fn try_set_view(layout: &mut Layout, view_name: String) {
|
||||
layout.set_view(view_name.clone())
|
||||
.or_print(
|
||||
logging::Problem::Bug,
|
||||
&format!("Bad view {}, ignoring", view_name),
|
||||
);
|
||||
}
|
||||
|
||||
/// A vessel holding an obligation to switch view.
|
||||
/// Use with #[must_use]
|
||||
struct ViewChange<'a> {
|
||||
layout: &'a mut Layout,
|
||||
view_name: Option<String>,
|
||||
}
|
||||
|
||||
impl<'a> ViewChange<'a> {
|
||||
fn choose_view(self, view_name: String) -> ViewChange<'a> {
|
||||
ViewChange {
|
||||
view_name: Some(view_name),
|
||||
..self
|
||||
}
|
||||
}
|
||||
fn apply(self) {
|
||||
if let Some(name) = self.view_name {
|
||||
try_set_view(self.layout, name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Find all impermanent view changes and undo them in an arbitrary order.
|
||||
/// Return an obligation to actually switch the view.
|
||||
/// The final view is the "unlock" view
|
||||
/// from one of the currently stuck keys.
|
||||
// As long as only one stuck button is used, this should be fine.
|
||||
// This is guaranteed because pressing a lock button unlocks all others.
|
||||
// TODO: Make some broader guarantee about the resulting view,
|
||||
// e.g. by maintaining a stack of stuck keys.
|
||||
#[must_use]
|
||||
fn unstick_locks(layout: &mut Layout) -> ViewChange {
|
||||
let mut new_view = None;
|
||||
for key in layout.get_locked_keys().clone() {
|
||||
let key: &Rc<RefCell<KeyState>> = key.borrow();
|
||||
let key = RefCell::borrow(key);
|
||||
match &key.action {
|
||||
Action::LockView { lock: _, unlock: view } => {
|
||||
new_view = Some(view.clone());
|
||||
},
|
||||
a => log_print!(
|
||||
logging::Level::Bug,
|
||||
"Non-locking action {:?} was found inside locked keys",
|
||||
a,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
ViewChange {
|
||||
layout,
|
||||
view_name: new_view,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
mod procedures {
|
||||
use super::*;
|
||||
|
||||
@ -907,67 +1039,6 @@ pub struct UIBackend {
|
||||
mod seat {
|
||||
use super::*;
|
||||
|
||||
fn try_set_view(layout: &mut Layout, view_name: String) {
|
||||
layout.set_view(view_name.clone())
|
||||
.or_print(
|
||||
logging::Problem::Bug,
|
||||
&format!("Bad view {}, ignoring", view_name),
|
||||
);
|
||||
}
|
||||
|
||||
/// A vessel holding an obligation to switch view.
|
||||
/// Use with #[must_use]
|
||||
struct ViewChange<'a> {
|
||||
layout: &'a mut Layout,
|
||||
view_name: Option<String>,
|
||||
}
|
||||
|
||||
impl<'a> ViewChange<'a> {
|
||||
fn choose_view(self, view_name: String) -> ViewChange<'a> {
|
||||
ViewChange {
|
||||
view_name: Some(view_name),
|
||||
..self
|
||||
}
|
||||
}
|
||||
fn apply(self) {
|
||||
if let Some(name) = self.view_name {
|
||||
try_set_view(self.layout, name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Find all impermanent view changes and undo them in an arbitrary order.
|
||||
/// Return an obligation to actually switch the view.
|
||||
/// The final view is the "unlock" view
|
||||
/// from one of the currently stuck keys.
|
||||
// As long as only one stuck button is used, this should be fine.
|
||||
// This is guaranteed because pressing a lock button unlocks all others.
|
||||
// TODO: Make some broader guarantee about the resulting view,
|
||||
// e.g. by maintaining a stack of stuck keys.
|
||||
#[must_use]
|
||||
fn unstick_locks(layout: &mut Layout) -> ViewChange {
|
||||
let mut new_view = None;
|
||||
for key in layout.get_locked_keys().clone() {
|
||||
let key: &Rc<RefCell<KeyState>> = key.borrow();
|
||||
let key = RefCell::borrow(key);
|
||||
match &key.action {
|
||||
Action::LockView { lock: _, unlock: view } => {
|
||||
new_view = Some(view.clone());
|
||||
},
|
||||
a => log_print!(
|
||||
logging::Level::Bug,
|
||||
"Non-locking action {:?} was found inside locked keys",
|
||||
a,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
ViewChange {
|
||||
layout,
|
||||
view_name: new_view,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_press_key(
|
||||
layout: &mut Layout,
|
||||
submission: &mut Submission,
|
||||
@ -1027,6 +1098,12 @@ mod seat {
|
||||
};
|
||||
let action = key.action.clone();
|
||||
|
||||
let (view_transition, latch_state) = Layout::process_action_for_view(
|
||||
&action,
|
||||
&layout.current_view,
|
||||
layout.view_latched,
|
||||
);
|
||||
|
||||
// update
|
||||
let key = key.into_released();
|
||||
|
||||
@ -1120,14 +1197,20 @@ mod test {
|
||||
use std::ffi::CString;
|
||||
use ::keyboard::PressType;
|
||||
|
||||
pub fn make_state() -> Rc<RefCell<::keyboard::KeyState>> {
|
||||
pub fn make_state_with_action(action: Action)
|
||||
-> Rc<RefCell<::keyboard::KeyState>>
|
||||
{
|
||||
Rc::new(RefCell::new(::keyboard::KeyState {
|
||||
pressed: PressType::Released,
|
||||
keycodes: Vec::new(),
|
||||
action: Action::SetView("default".into()),
|
||||
action,
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn make_state() -> Rc<RefCell<::keyboard::KeyState>> {
|
||||
make_state_with_action(Action::SetView("default".into()))
|
||||
}
|
||||
|
||||
pub fn make_button_with_state(
|
||||
name: String,
|
||||
state: Rc<RefCell<::keyboard::KeyState>>,
|
||||
@ -1141,6 +1224,95 @@ mod test {
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn latch_lock_unlock() {
|
||||
let action = Action::LockView {
|
||||
lock: "lock".into(),
|
||||
unlock: "unlock".into(),
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
Layout::process_action_for_view(&action, "unlock", false),
|
||||
(ViewTransition::ChangeTo("lock"), true),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Layout::process_action_for_view(&action, "lock", true),
|
||||
(ViewTransition::NoChange, false),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Layout::process_action_for_view(&action, "lock", false),
|
||||
(ViewTransition::ChangeTo("unlock"), false),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn latch_pop_layout() {
|
||||
let switch = Action::LockView {
|
||||
lock: "locked".into(),
|
||||
unlock: "base".into(),
|
||||
};
|
||||
|
||||
let submit = Action::Erase;
|
||||
|
||||
let view = View::new(vec![(
|
||||
0.0,
|
||||
Row::new(vec![
|
||||
(
|
||||
0.0,
|
||||
make_button_with_state(
|
||||
"switch".into(),
|
||||
make_state_with_action(switch.clone())
|
||||
),
|
||||
),
|
||||
(
|
||||
1.0,
|
||||
make_button_with_state(
|
||||
"submit".into(),
|
||||
make_state_with_action(submit.clone())
|
||||
),
|
||||
),
|
||||
]),
|
||||
)]);
|
||||
|
||||
let mut layout = Layout {
|
||||
current_view: "base".into(),
|
||||
view_latched: false,
|
||||
keymaps: Vec::new(),
|
||||
kind: ArrangementKind::Base,
|
||||
pressed_keys: HashSet::new(),
|
||||
margins: Margins {
|
||||
top: 0.0,
|
||||
left: 0.0,
|
||||
right: 0.0,
|
||||
bottom: 0.0,
|
||||
},
|
||||
views: hashmap! {
|
||||
// Both can use the same structure.
|
||||
// Switching doesn't depend on the view shape
|
||||
// as long as the switching button is present.
|
||||
"base".into() => (c::Point { x: 0.0, y: 0.0 }, view.clone()),
|
||||
"locked".into() => (c::Point { x: 0.0, y: 0.0 }, view),
|
||||
},
|
||||
};
|
||||
|
||||
// Basic cycle
|
||||
layout.apply_view_transition(&switch);
|
||||
assert_eq!(&layout.current_view, "locked");
|
||||
layout.apply_view_transition(&switch);
|
||||
assert_eq!(&layout.current_view, "locked");
|
||||
layout.apply_view_transition(&submit);
|
||||
assert_eq!(&layout.current_view, "locked");
|
||||
layout.apply_view_transition(&switch);
|
||||
assert_eq!(&layout.current_view, "base");
|
||||
layout.apply_view_transition(&switch);
|
||||
// Unlatch
|
||||
assert_eq!(&layout.current_view, "locked");
|
||||
layout.apply_view_transition(&submit);
|
||||
assert_eq!(&layout.current_view, "base");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_centering() {
|
||||
// A B
|
||||
|
||||
Reference in New Issue
Block a user