1 /*!
2  * Layout-related data.
3  *
4  * The `View` contains `Row`s and each `Row` contains `Button`s.
5  * They carry data relevant to their positioning only,
6  * except the Button, which also carries some data
7  * about its appearance and function.
8  *
9  * The layout is determined bottom-up, by measuring `Button` sizes,
10  * deriving `Row` sizes from them, and then centering them within the `View`.
11  *
12  * That makes the `View` position immutable,
13  * and therefore different than the other positions.
14  *
15  * Note that it might be a better idea
16  * to make `View` position depend on its contents,
17  * and let the renderer scale and center it within the widget.
18  */
19 
20 use std::cell::RefCell;
21 use std::collections::{ HashMap, HashSet };
22 use std::ffi::CString;
23 use std::fmt;
24 use std::rc::Rc;
25 use std::vec::Vec;
26 
27 use ::action::Action;
28 use ::drawing;
29 use ::keyboard::KeyState;
30 use ::logging;
31 use ::manager;
32 use ::submission::{ Submission, SubmitData, Timestamp };
33 use ::util::find_max_double;
34 
35 use ::imservice::ContentPurpose;
36 
37 // Traits
38 use std::borrow::Borrow;
39 use ::logging::Warn;
40 
41 /// Gathers stuff defined in C or called by C
42 pub mod c {
43     use super::*;
44 
45     use gtk_sys;
46     use std::os::raw::c_void;
47     use crate::submission::c::Submission as CSubmission;
48 
49     use std::ops::{ Add, Sub };
50 
51     // The following defined in C
52     #[repr(transparent)]
53     #[derive(Copy, Clone)]
54     pub struct EekGtkKeyboard(pub *const gtk_sys::GtkWidget);
55 
56     extern "C" {
57         #[allow(improper_ctypes)]
eek_gtk_keyboard_emit_feedback( keyboard: EekGtkKeyboard, )58         pub fn eek_gtk_keyboard_emit_feedback(
59             keyboard: EekGtkKeyboard,
60         );
61     }
62 
63     /// Defined in eek-types.h
64     #[repr(C)]
65     #[derive(Clone, Debug, PartialEq)]
66     pub struct Point {
67         pub x: f64,
68         pub y: f64,
69     }
70 
71     impl Add for Point {
72         type Output = Self;
add(self, other: Self) -> Self73         fn add(self, other: Self) -> Self {
74             &self + other
75         }
76     }
77 
78     impl Add<Point> for &Point {
79         type Output = Point;
add(self, other: Point) -> Point80         fn add(self, other: Point) -> Point {
81             Point {
82                 x: self.x + other.x,
83                 y: self.y + other.y,
84             }
85         }
86     }
87 
88     impl Sub<&Point> for Point {
89         type Output = Point;
sub(self, other: &Point) -> Point90         fn sub(self, other: &Point) -> Point {
91             Point {
92                 x: self.x - other.x,
93                 y: self.y - other.y,
94             }
95         }
96     }
97 
98     /// Defined in eek-types.h
99     #[repr(C)]
100     #[derive(Clone, Debug, PartialEq)]
101     pub struct Bounds {
102         pub x: f64,
103         pub y: f64,
104         pub width: f64,
105         pub height: f64
106     }
107 
108     impl Bounds {
contains(&self, point: &Point) -> bool109         pub fn contains(&self, point: &Point) -> bool {
110             point.x > self.x && point.x < self.x + self.width
111                 && point.y > self.y && point.y < self.y + self.height
112         }
113     }
114 
115     /// Translate and then scale
116     #[repr(C)]
117     pub struct Transformation {
118         pub origin_x: f64,
119         pub origin_y: f64,
120         pub scale: f64,
121     }
122 
123     impl Transformation {
124         /// Applies the new transformation after this one
chain(self, next: Transformation) -> Transformation125         pub fn chain(self, next: Transformation) -> Transformation {
126             Transformation {
127                 origin_x: self.origin_x + self.scale * next.origin_x,
128                 origin_y: self.origin_y + self.scale * next.origin_y,
129                 scale: self.scale * next.scale,
130             }
131         }
forward(&self, p: Point) -> Point132         fn forward(&self, p: Point) -> Point {
133             Point {
134                 x: (p.x - self.origin_x) / self.scale,
135                 y: (p.y - self.origin_y) / self.scale,
136             }
137         }
reverse(&self, p: Point) -> Point138         fn reverse(&self, p: Point) -> Point {
139             Point {
140                 x: p.x * self.scale + self.origin_x,
141                 y: p.y * self.scale + self.origin_y,
142             }
143         }
reverse_bounds(&self, b: Bounds) -> Bounds144         pub fn reverse_bounds(&self, b: Bounds) -> Bounds {
145             let start = self.reverse(Point { x: b.x, y: b.y });
146             let end = self.reverse(Point {
147                 x: b.x + b.width,
148                 y: b.y + b.height,
149             });
150             Bounds {
151                 x: start.x,
152                 y: start.y,
153                 width: end.x - start.x,
154                 height: end.y - start.y,
155             }
156         }
157     }
158 
159     // This is constructed only in C, no need for warnings
160     #[allow(dead_code)]
161     #[repr(transparent)]
162     pub struct LevelKeyboard(*const c_void);
163 
164     // The following defined in Rust. TODO: wrap naked pointers to Rust data inside RefCells to prevent multiple writers
165 
166     /// Positions the layout contents within the available space.
167     /// The origin of the transformation is the point inside the margins.
168     #[no_mangle]
169     pub extern "C"
squeek_layout_calculate_transformation( layout: *const Layout, allocation_width: f64, allocation_height: f64, ) -> Transformation170     fn squeek_layout_calculate_transformation(
171         layout: *const Layout,
172         allocation_width: f64,
173         allocation_height: f64,
174     ) -> Transformation {
175         let layout = unsafe { &*layout };
176         layout.calculate_transformation(Size {
177             width: allocation_width,
178             height: allocation_height,
179         })
180     }
181 
182     #[no_mangle]
183     pub extern "C"
squeek_layout_get_kind(layout: *const Layout) -> u32184     fn squeek_layout_get_kind(layout: *const Layout) -> u32 {
185         let layout = unsafe { &*layout };
186         layout.kind.clone() as u32
187     }
188 
189     #[no_mangle]
190     pub extern "C"
squeek_layout_get_purpose(layout: *const Layout) -> u32191     fn squeek_layout_get_purpose(layout: *const Layout) -> u32 {
192         let layout = unsafe { &*layout };
193         layout.purpose.clone() as u32
194     }
195 
196     #[no_mangle]
197     pub extern "C"
squeek_layout_free(layout: *mut Layout)198     fn squeek_layout_free(layout: *mut Layout) {
199         unsafe { Box::from_raw(layout) };
200     }
201 
202     /// Entry points for more complex procedures and algorithms which span multiple modules
203     pub mod procedures {
204         use super::*;
205 
206         /// Release pointer in the specified position
207         #[no_mangle]
208         pub extern "C"
squeek_layout_release( layout: *mut Layout, submission: CSubmission, widget_to_layout: Transformation, time: u32, manager: manager::c::Manager, ui_keyboard: EekGtkKeyboard, )209         fn squeek_layout_release(
210             layout: *mut Layout,
211             submission: CSubmission,
212             widget_to_layout: Transformation,
213             time: u32,
214             manager: manager::c::Manager,
215             ui_keyboard: EekGtkKeyboard,
216         ) {
217             let time = Timestamp(time);
218             let layout = unsafe { &mut *layout };
219             let submission = submission.clone_ref();
220             let mut submission = submission.borrow_mut();
221             let ui_backend = UIBackend {
222                 widget_to_layout,
223                 keyboard: ui_keyboard,
224             };
225 
226             // The list must be copied,
227             // because it will be mutated in the loop
228             for key in layout.pressed_keys.clone() {
229                 let key: &Rc<RefCell<KeyState>> = key.borrow();
230                 seat::handle_release_key(
231                     layout,
232                     &mut submission,
233                     Some(&ui_backend),
234                     time,
235                     Some(manager),
236                     key,
237                 );
238             }
239             drawing::queue_redraw(ui_keyboard);
240         }
241 
242         /// Release all buttons but don't redraw
243         #[no_mangle]
244         pub extern "C"
squeek_layout_release_all_only( layout: *mut Layout, submission: CSubmission, time: u32, )245         fn squeek_layout_release_all_only(
246             layout: *mut Layout,
247             submission: CSubmission,
248             time: u32,
249         ) {
250             let layout = unsafe { &mut *layout };
251             let submission = submission.clone_ref();
252             let mut submission = submission.borrow_mut();
253             // The list must be copied,
254             // because it will be mutated in the loop
255             for key in layout.pressed_keys.clone() {
256                 let key: &Rc<RefCell<KeyState>> = key.borrow();
257                 seat::handle_release_key(
258                     layout,
259                     &mut submission,
260                     None, // don't update UI
261                     Timestamp(time),
262                     None, // don't switch layouts
263                     &mut key.clone(),
264                 );
265             }
266         }
267 
268         #[no_mangle]
269         pub extern "C"
squeek_layout_depress( layout: *mut Layout, submission: CSubmission, x_widget: f64, y_widget: f64, widget_to_layout: Transformation, time: u32, ui_keyboard: EekGtkKeyboard, )270         fn squeek_layout_depress(
271             layout: *mut Layout,
272             submission: CSubmission,
273             x_widget: f64, y_widget: f64,
274             widget_to_layout: Transformation,
275             time: u32,
276             ui_keyboard: EekGtkKeyboard,
277         ) {
278             let layout = unsafe { &mut *layout };
279             let submission = submission.clone_ref();
280             let mut submission = submission.borrow_mut();
281             let point = widget_to_layout.forward(
282                 Point { x: x_widget, y: y_widget }
283             );
284 
285             let state = layout.find_button_by_position(point)
286                 .map(|place| place.button.state.clone());
287 
288             if let Some(state) = state {
289                 seat::handle_press_key(
290                     layout,
291                     &mut submission,
292                     Timestamp(time),
293                     &state,
294                 );
295                 // maybe TODO: draw on the display buffer here
296                 drawing::queue_redraw(ui_keyboard);
297                 unsafe {
298                     eek_gtk_keyboard_emit_feedback(ui_keyboard);
299                 }
300             };
301         }
302 
303         // FIXME: this will work funny
304         // when 2 touch points are on buttons and moving one after another
305         // Solution is to have separate pressed lists for each point
306         #[no_mangle]
307         pub extern "C"
squeek_layout_drag( layout: *mut Layout, submission: CSubmission, x_widget: f64, y_widget: f64, widget_to_layout: Transformation, time: u32, manager: manager::c::Manager, ui_keyboard: EekGtkKeyboard, )308         fn squeek_layout_drag(
309             layout: *mut Layout,
310             submission: CSubmission,
311             x_widget: f64, y_widget: f64,
312             widget_to_layout: Transformation,
313             time: u32,
314             manager: manager::c::Manager,
315             ui_keyboard: EekGtkKeyboard,
316         ) {
317             let time = Timestamp(time);
318             let layout = unsafe { &mut *layout };
319             let submission = submission.clone_ref();
320             let mut submission = submission.borrow_mut();
321             let ui_backend = UIBackend {
322                 widget_to_layout,
323                 keyboard: ui_keyboard,
324             };
325             let point = ui_backend.widget_to_layout.forward(
326                 Point { x: x_widget, y: y_widget }
327             );
328 
329             let pressed = layout.pressed_keys.clone();
330             let button_info = {
331                 let place = layout.find_button_by_position(point);
332                 place.map(|place| {(
333                     place.button.state.clone(),
334                     place.button.clone(),
335                     place.offset,
336                 )})
337             };
338 
339             if let Some((state, _button, _view_position)) = button_info {
340                 let mut found = false;
341                 for wrapped_key in pressed {
342                     let key: &Rc<RefCell<KeyState>> = wrapped_key.borrow();
343                     if Rc::ptr_eq(&state, &wrapped_key.0) {
344                         found = true;
345                     } else {
346                         seat::handle_release_key(
347                             layout,
348                             &mut submission,
349                             Some(&ui_backend),
350                             time,
351                             Some(manager),
352                             key,
353                         );
354                     }
355                 }
356                 if !found {
357                     seat::handle_press_key(
358                         layout,
359                         &mut submission,
360                         time,
361                         &state,
362                     );
363                     // maybe TODO: draw on the display buffer here
364                     unsafe {
365                         eek_gtk_keyboard_emit_feedback(ui_keyboard);
366                     }
367                 }
368             } else {
369                 for wrapped_key in pressed {
370                     let key: &Rc<RefCell<KeyState>> = wrapped_key.borrow();
371                     seat::handle_release_key(
372                         layout,
373                         &mut submission,
374                         Some(&ui_backend),
375                         time,
376                         Some(manager),
377                         key,
378                     );
379                 }
380             }
381             drawing::queue_redraw(ui_keyboard);
382         }
383 
384         #[cfg(test)]
385         mod test {
386             use super::*;
387 
near(a: f64, b: f64) -> bool388             fn near(a: f64, b: f64) -> bool {
389                 (a - b).abs() < ((a + b) * 0.001f64).abs()
390             }
391 
392             #[test]
transform_back()393             fn transform_back() {
394                 let transform = Transformation {
395                     origin_x: 10f64,
396                     origin_y: 11f64,
397                     scale: 12f64,
398                 };
399                 let point = Point { x: 1f64, y: 1f64 };
400                 let transformed = transform.reverse(transform.forward(point.clone()));
401                 assert!(near(point.x, transformed.x));
402                 assert!(near(point.y, transformed.y));
403             }
404         }
405     }
406 }
407 
408 pub struct ButtonPlace<'a> {
409     button: &'a Button,
410     offset: c::Point,
411 }
412 
413 #[derive(Debug, Clone, PartialEq)]
414 pub struct Size {
415     pub width: f64,
416     pub height: f64,
417 }
418 
419 #[derive(Debug, Clone, PartialEq)]
420 pub enum Label {
421     /// Text used to display the symbol
422     Text(CString),
423     /// Icon name used to render the symbol
424     IconName(CString),
425 }
426 
427 /// The graphical representation of a button
428 #[derive(Clone, Debug)]
429 pub struct Button {
430     /// ID string, e.g. for CSS
431     pub name: CString,
432     /// Label to display to the user
433     pub label: Label,
434     pub size: Size,
435     /// The name of the visual class applied
436     pub outline_name: CString,
437     /// current state, shared with other buttons
438     pub state: Rc<RefCell<KeyState>>,
439 }
440 
441 impl Button {
get_bounds(&self) -> c::Bounds442     pub fn get_bounds(&self) -> c::Bounds {
443         c::Bounds {
444             x: 0.0, y: 0.0,
445             width: self.size.width, height: self.size.height,
446         }
447     }
448 }
449 
450 /// The graphical representation of a row of buttons
451 #[derive(Clone, Debug)]
452 pub struct Row {
453     /// Buttons together with their offset from the left relative to the row.
454     /// ie. the first button always start at 0.
455     buttons: Vec<(f64, Box<Button>)>,
456 
457     /// Total size of the row
458     size: Size,
459 }
460 
461 impl Row {
new(buttons: Vec<(f64, Box<Button>)>) -> Row462     pub fn new(buttons: Vec<(f64, Box<Button>)>) -> Row {
463         // Make sure buttons are sorted by offset.
464         debug_assert!({
465             let mut sorted = buttons.clone();
466             sorted.sort_by(|(f1, _), (f2, _)| f1.partial_cmp(f2).unwrap());
467 
468             sorted.iter().map(|(f, _)| *f).collect::<Vec<_>>()
469                 == buttons.iter().map(|(f, _)| *f).collect::<Vec<_>>()
470         });
471 
472         let width = buttons.iter().next_back()
473             .map(|(x_offset, button)| button.size.width + x_offset)
474             .unwrap_or(0.0);
475 
476         let height = find_max_double(
477             buttons.iter(),
478             |(_offset, button)| button.size.height,
479         );
480 
481         Row { buttons, size: Size { width, height } }
482     }
483 
get_size(&self) -> Size484     pub fn get_size(&self) -> Size {
485         self.size.clone()
486     }
487 
get_buttons(&self) -> &Vec<(f64, Box<Button>)>488     pub fn get_buttons(&self) -> &Vec<(f64, Box<Button>)> {
489         &self.buttons
490     }
491 
492     /// Finds the first button that covers the specified point
493     /// relative to row's position's origin
find_button_by_position(&self, x: f64) -> &(f64, Box<Button>)494     fn find_button_by_position(&self, x: f64) -> &(f64, Box<Button>)
495     {
496         // Buttons are sorted so we can use a binary search to find the clicked
497         // button. Note this doesn't check whether the point is actually within
498         // a button. This is on purpose as we want a click past the left edge of
499         // the left-most button to register as a click.
500         let result = self.buttons.binary_search_by(
501             |&(f, _)| f.partial_cmp(&x).unwrap()
502         );
503 
504         let index = result.unwrap_or_else(|r| r);
505         let index = if index > 0 { index - 1 } else { 0 };
506 
507         &self.buttons[index]
508     }
509 }
510 
511 #[derive(Clone, Debug)]
512 pub struct Spacing {
513     pub row: f64,
514     pub button: f64,
515 }
516 
517 #[derive(Clone)]
518 pub struct View {
519     /// Rows together with their offsets from the top left
520     rows: Vec<(c::Point, Row)>,
521 
522     /// Total size of the view
523     size: Size,
524 }
525 
526 impl View {
new(rows: Vec<(f64, Row)>) -> View527     pub fn new(rows: Vec<(f64, Row)>) -> View {
528         // Make sure rows are sorted by offset.
529         debug_assert!({
530             let mut sorted = rows.clone();
531             sorted.sort_by(|(f1, _), (f2, _)| f1.partial_cmp(f2).unwrap());
532 
533             sorted.iter().map(|(f, _)| *f).collect::<Vec<_>>()
534                 == rows.iter().map(|(f, _)| *f).collect::<Vec<_>>()
535         });
536 
537         // No need to call `get_rows()`,
538         // as the biggest row is the most far reaching in both directions
539         // because they are all centered.
540         let width = find_max_double(rows.iter(), |(_offset, row)| row.size.width);
541 
542         let height = rows.iter().next_back()
543             .map(|(y_offset, row)| row.size.height + y_offset)
544             .unwrap_or(0.0);
545 
546         // Center the rows
547         let rows = rows.into_iter().map(|(y_offset, row)| {(
548                 c::Point {
549                     x: (width - row.size.width) / 2.0,
550                     y: y_offset,
551                 },
552                 row,
553             )}).collect::<Vec<_>>();
554 
555         View { rows, size: Size { width, height } }
556     }
557     /// Finds the first button that covers the specified point
558     /// relative to view's position's origin
find_button_by_position(&self, point: c::Point) -> Option<ButtonPlace>559     fn find_button_by_position(&self, point: c::Point)
560         -> Option<ButtonPlace>
561     {
562         // Only test bounds of the view here, letting rows/column search extend
563         // to the edges of these bounds.
564         let bounds = c::Bounds {
565             x: 0.0,
566             y: 0.0,
567             width: self.size.width,
568             height: self.size.height,
569         };
570         if !bounds.contains(&point) {
571             return None;
572         }
573 
574         // Rows are sorted so we can use a binary search to find the row.
575         let result = self.rows.binary_search_by(
576             |(f, _)| f.y.partial_cmp(&point.y).unwrap()
577         );
578 
579         let index = result.unwrap_or_else(|r| r);
580         let index = if index > 0 { index - 1 } else { 0 };
581 
582         let row = &self.rows[index];
583         let button = row.1.find_button_by_position(point.x - row.0.x);
584 
585         Some(ButtonPlace {
586             button: &button.1,
587             offset: &row.0 + c::Point { x: button.0, y: 0.0 },
588         })
589     }
590 
get_size(&self) -> Size591     pub fn get_size(&self) -> Size {
592         self.size.clone()
593     }
594 
595     /// Returns positioned rows, with appropriate x offsets (centered)
get_rows(&self) -> &Vec<(c::Point, Row)>596     pub fn get_rows(&self) -> &Vec<(c::Point, Row)> {
597         &self.rows
598     }
599 
600     /// Returns a size which contains all the views
601     /// if they are all centered on the same point.
calculate_super_size(views: Vec<&View>) -> Size602     pub fn calculate_super_size(views: Vec<&View>) -> Size {
603         Size {
604             height: find_max_double(
605                 views.iter(),
606                 |view| view.size.height,
607             ),
608             width: find_max_double(
609                 views.iter(),
610                 |view| view.size.width,
611             ),
612         }
613     }
614 }
615 
616 /// The physical characteristic of layout for the purpose of styling
617 #[derive(Clone, Copy, PartialEq, Debug)]
618 pub enum ArrangementKind {
619     Base = 0,
620     Wide = 1,
621 }
622 
623 #[derive(Debug, PartialEq)]
624 pub struct Margins {
625     pub top: f64,
626     pub bottom: f64,
627     pub left: f64,
628     pub right: f64,
629 }
630 
631 #[derive(Clone, Debug, PartialEq)]
632 pub enum LatchedState {
633     /// Holds view to return to.
634     FromView(String),
635     Not,
636 }
637 
638 // TODO: split into sth like
639 // Arrangement (views) + details (keymap) + State (keys)
640 /// State of the UI, contains the backend as well
641 pub struct Layout {
642     pub margins: Margins,
643     pub kind: ArrangementKind,
644     pub purpose: ContentPurpose,
645     pub current_view: String,
646 
647     // If current view is latched,
648     // clicking any button that emits an action (erase, submit, set modifier)
649     // will cause lock buttons to unlatch.
650     view_latched: LatchedState,
651 
652     // Views own the actual buttons which have state
653     // Maybe they should own UI only,
654     // and keys should be owned by a dedicated non-UI-State?
655     /// Point is the offset within the layout
656     pub views: HashMap<String, (c::Point, View)>,
657 
658     // Non-UI stuff
659     /// xkb keymaps applicable to the contained keys. Unchangeable
660     pub keymaps: Vec<CString>,
661     // Changeable state
662     // a Vec would be enough, but who cares, this will be small & fast enough
663     // TODO: turn those into per-input point *_buttons to track dragging.
664     // The renderer doesn't need the list of pressed keys any more,
665     // because it needs to iterate
666     // through all buttons of the current view anyway.
667     // When the list tracks actual location,
668     // it becomes possible to place popovers and other UI accurately.
669     pub pressed_keys: HashSet<::util::Pointer<RefCell<KeyState>>>,
670 }
671 
672 /// A builder structure for picking up layout data from storage
673 pub struct LayoutData {
674     /// Point is the offset within layout
675     pub views: HashMap<String, (c::Point, View)>,
676     pub keymaps: Vec<CString>,
677     pub margins: Margins,
678 }
679 
680 #[derive(Debug)]
681 struct NoSuchView;
682 
683 impl fmt::Display for NoSuchView {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result684     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
685         write!(f, "No such view")
686     }
687 }
688 
689 // Unfortunately, changes are not atomic due to mutability :(
690 // An error will not be recoverable
691 // The usage of &mut on Rc<RefCell<KeyState>> doesn't mean anything special.
692 // Cloning could also be used.
693 impl Layout {
new(data: LayoutData, kind: ArrangementKind, purpose: ContentPurpose) -> Layout694     pub fn new(data: LayoutData, kind: ArrangementKind, purpose: ContentPurpose) -> Layout {
695         Layout {
696             kind,
697             current_view: "base".to_owned(),
698             view_latched: LatchedState::Not,
699             views: data.views,
700             keymaps: data.keymaps,
701             pressed_keys: HashSet::new(),
702             margins: data.margins,
703             purpose,
704         }
705     }
706 
get_current_view_position(&self) -> &(c::Point, View)707     pub fn get_current_view_position(&self) -> &(c::Point, View) {
708         &self.views
709             .get(&self.current_view).expect("Selected nonexistent view")
710     }
711 
get_current_view(&self) -> &View712     pub fn get_current_view(&self) -> &View {
713         &self.views.get(&self.current_view).expect("Selected nonexistent view").1
714     }
715 
set_view(&mut self, view: String) -> Result<(), NoSuchView>716     fn set_view(&mut self, view: String) -> Result<(), NoSuchView> {
717         if self.views.contains_key(&view) {
718             self.current_view = view;
719             Ok(())
720         } else {
721             Err(NoSuchView)
722         }
723     }
724 
725     // Layout is passed around mutably,
726     // so better keep the field away from direct access.
get_view_latched(&self) -> &LatchedState727     pub fn get_view_latched(&self) -> &LatchedState {
728         &self.view_latched
729     }
730 
731     /// Calculates size without margins
calculate_inner_size(&self) -> Size732     fn calculate_inner_size(&self) -> Size {
733         View::calculate_super_size(
734             self.views.iter().map(|(_, (_offset, v))| v).collect()
735         )
736     }
737 
738     /// Size including margins
calculate_size(&self) -> Size739     fn calculate_size(&self) -> Size {
740         let inner_size = self.calculate_inner_size();
741         Size {
742             width: self.margins.left + inner_size.width + self.margins.right,
743             height: (
744                 self.margins.top
745                 + inner_size.height
746                 + self.margins.bottom
747             ),
748         }
749     }
750 
calculate_transformation( &self, available: Size, ) -> c::Transformation751     pub fn calculate_transformation(
752         &self,
753         available: Size,
754     ) -> c::Transformation {
755         let size = self.calculate_size();
756         let h_scale = available.width / size.width;
757         let v_scale = available.height / size.height;
758         let scale = if h_scale < v_scale { h_scale } else { v_scale };
759         let outside_margins = c::Transformation {
760             origin_x: (available.width - (scale * size.width)) / 2.0,
761             origin_y: (available.height - (scale * size.height)) / 2.0,
762             scale: scale,
763         };
764         outside_margins.chain(c::Transformation {
765             origin_x: self.margins.left,
766             origin_y: self.margins.top,
767             scale: 1.0,
768         })
769     }
770 
find_button_by_position(&self, point: c::Point) -> Option<ButtonPlace>771     fn find_button_by_position(&self, point: c::Point) -> Option<ButtonPlace> {
772         let (offset, layout) = self.get_current_view_position();
773         layout.find_button_by_position(point - offset)
774     }
775 
foreach_visible_button<F>(&self, mut f: F) where F: FnMut(c::Point, &Box<Button>)776     pub fn foreach_visible_button<F>(&self, mut f: F)
777         where F: FnMut(c::Point, &Box<Button>)
778     {
779         let (view_offset, view) = self.get_current_view_position();
780         for (row_offset, row) in view.get_rows() {
781             for (x_offset, button) in &row.buttons {
782                 let offset = view_offset
783                     + row_offset.clone()
784                     + c::Point { x: *x_offset, y: 0.0 };
785                 f(offset, button);
786             }
787         }
788     }
789 
apply_view_transition( &mut self, action: &Action, )790     fn apply_view_transition(
791         &mut self,
792         action: &Action,
793     ) {
794         let (transition, new_latched) = Layout::process_action_for_view(
795             action,
796             &self.current_view,
797             &self.view_latched,
798         );
799 
800         match transition {
801             ViewTransition::UnlatchAll => self.unstick_locks(),
802             ViewTransition::ChangeTo(view) => try_set_view(self, view.into()),
803             ViewTransition::NoChange => {},
804         };
805 
806         self.view_latched = new_latched;
807     }
808 
809     /// Unlatch all latched keys,
810     /// so that the new view is the one before first press.
unstick_locks(&mut self)811     fn unstick_locks(&mut self) {
812         if let LatchedState::FromView(name) = self.view_latched.clone() {
813             match self.set_view(name.clone()) {
814                 Ok(_) => { self.view_latched = LatchedState::Not; }
815                 Err(e) => log_print!(
816                     logging::Level::Bug,
817                     "Bad view {}, can't unlatch ({:?})",
818                     name,
819                     e,
820                 ),
821             }
822         }
823     }
824 
825     /// Last bool is new latch state.
826     /// It doesn't make sense when the result carries UnlatchAll,
827     /// but let's not be picky.
828     ///
829     /// Although the state is not defined at the keys
830     /// (it's in the relationship between view and action),
831     /// keys go through the following stages when clicked repeatedly:
832     /// unlocked+unlatched -> locked+latched -> locked+unlatched
833     /// -> unlocked+unlatched
process_action_for_view<'a>( action: &'a Action, current_view: &str, latched: &LatchedState, ) -> (ViewTransition<'a>, LatchedState)834     fn process_action_for_view<'a>(
835         action: &'a Action,
836         current_view: &str,
837         latched: &LatchedState,
838     ) -> (ViewTransition<'a>, LatchedState) {
839         match action {
840             Action::Submit { text: _, keys: _ }
841                 | Action::Erase
842                 | Action::ApplyModifier(_)
843             => {
844                 let t = match latched {
845                     LatchedState::FromView(_) => ViewTransition::UnlatchAll,
846                     LatchedState::Not => ViewTransition::NoChange,
847                 };
848                 (t, LatchedState::Not)
849             },
850             Action::SetView(view) => (
851                 ViewTransition::ChangeTo(view),
852                 LatchedState::Not,
853             ),
854             Action::LockView { lock, unlock, latches, looks_locked_from: _ } => {
855                 use self::ViewTransition as VT;
856                 let locked = action.is_locked(current_view);
857                 match (locked, latched, latches) {
858                     // Was unlocked, now make locked but latched.
859                     (false, LatchedState::Not, true) => (
860                         VT::ChangeTo(lock),
861                         LatchedState::FromView(current_view.into()),
862                     ),
863                     // Layout is latched for reason other than this button.
864                     (false, LatchedState::FromView(view), true) => (
865                         VT::ChangeTo(lock),
866                         LatchedState::FromView(view.clone()),
867                     ),
868                     // Was latched, now only locked.
869                     (true, LatchedState::FromView(_), true)
870                         => (VT::NoChange, LatchedState::Not),
871                     // Was unlocked, can't latch so now make fully locked.
872                     (false, _, false)
873                         => (VT::ChangeTo(lock), LatchedState::Not),
874                     // Was locked, now make unlocked.
875                     (true, _, _)
876                         => (VT::ChangeTo(unlock), LatchedState::Not),
877                 }
878             },
879             _ => (ViewTransition::NoChange, latched.clone()),
880         }
881     }
882 }
883 
884 #[derive(Debug, PartialEq)]
885 enum ViewTransition<'a> {
886     ChangeTo(&'a str),
887     UnlatchAll,
888     NoChange,
889 }
890 
try_set_view(layout: &mut Layout, view_name: &str)891 fn try_set_view(layout: &mut Layout, view_name: &str) {
892     layout.set_view(view_name.into())
893         .or_print(
894             logging::Problem::Bug,
895             &format!("Bad view {}, ignoring", view_name),
896         );
897 }
898 
899 
900 mod procedures {
901     use super::*;
902 
903     type Place<'v> = (c::Point, &'v Box<Button>);
904 
905     /// Finds all buttons referring to the key in `state`,
906     /// together with their offsets within the view.
find_key_places<'v, 's>( view: &'v View, state: &'s Rc<RefCell<KeyState>> ) -> Vec<Place<'v>>907     pub fn find_key_places<'v, 's>(
908         view: &'v View,
909         state: &'s Rc<RefCell<KeyState>>
910     ) -> Vec<Place<'v>> {
911         view.get_rows().iter().flat_map(|(row_offset, row)| {
912             row.buttons.iter()
913                 .filter_map(move |(x_offset, button)| {
914                     if Rc::ptr_eq(&button.state, state) {
915                         Some((
916                             row_offset + c::Point { x: *x_offset, y: 0.0 },
917                             button,
918                         ))
919                     } else {
920                         None
921                     }
922                 })
923         }).collect()
924     }
925 
926     #[cfg(test)]
927     mod test {
928         use super::*;
929 
930         use ::layout::test::*;
931 
932         /// Checks whether the path points to the same boxed instances.
933         /// The instance constraint will be droppable
934         /// when C stops holding references to the data
935         #[test]
view_has_button()936         fn view_has_button() {
937             fn as_ptr<T>(v: &Box<T>) -> *const T {
938                 v.as_ref() as *const T
939             }
940 
941             let state = make_state();
942             let state_clone = state.clone();
943 
944             let button = make_button_with_state("1".into(), state);
945             let button_ptr = as_ptr(&button);
946 
947             let row = Row::new(vec!((0.1, button)));
948 
949             let view = View::new(vec!((1.2, row)));
950 
951             assert_eq!(
952                 find_key_places(&view, &state_clone.clone()).into_iter()
953                     .map(|(place, button)| { (place, as_ptr(button)) })
954                     .collect::<Vec<_>>(),
955                 vec!(
956                     (c::Point { x: 0.1, y: 1.2 }, button_ptr)
957                 )
958             );
959 
960             let view = View::new(vec![]);
961             assert_eq!(
962                 find_key_places(&view, &state_clone.clone()).is_empty(),
963                 true
964             );
965         }
966     }
967 }
968 
969 pub struct UIBackend {
970     widget_to_layout: c::Transformation,
971     keyboard: c::EekGtkKeyboard,
972 }
973 
974 /// Top level procedures, dispatching to everything
975 mod seat {
976     use super::*;
977 
handle_press_key( layout: &mut Layout, submission: &mut Submission, time: Timestamp, rckey: &Rc<RefCell<KeyState>>, )978     pub fn handle_press_key(
979         layout: &mut Layout,
980         submission: &mut Submission,
981         time: Timestamp,
982         rckey: &Rc<RefCell<KeyState>>,
983     ) {
984         if !layout.pressed_keys.insert(::util::Pointer(rckey.clone())) {
985             log_print!(
986                 logging::Level::Bug,
987                 "Key {:?} was already pressed", rckey,
988             );
989         }
990         let key: KeyState = {
991             RefCell::borrow(rckey).clone()
992         };
993         let action = key.action.clone();
994         match action {
995             Action::Submit {
996                 text: Some(text),
997                 keys: _,
998             } => submission.handle_press(
999                 KeyState::get_id(rckey),
1000                 SubmitData::Text(&text),
1001                 &key.keycodes,
1002                 time,
1003             ),
1004             Action::Submit {
1005                 text: None,
1006                 keys: _,
1007             } => submission.handle_press(
1008                 KeyState::get_id(rckey),
1009                 SubmitData::Keycodes,
1010                 &key.keycodes,
1011                 time,
1012             ),
1013             Action::Erase => submission.handle_press(
1014                 KeyState::get_id(rckey),
1015                 SubmitData::Erase,
1016                 &key.keycodes,
1017                 time,
1018             ),
1019             _ => {},
1020         };
1021         RefCell::replace(rckey, key.into_pressed());
1022     }
1023 
handle_release_key( layout: &mut Layout, submission: &mut Submission, ui: Option<&UIBackend>, time: Timestamp, manager: Option<manager::c::Manager>, rckey: &Rc<RefCell<KeyState>>, )1024     pub fn handle_release_key(
1025         layout: &mut Layout,
1026         submission: &mut Submission,
1027         ui: Option<&UIBackend>,
1028         time: Timestamp,
1029         manager: Option<manager::c::Manager>,
1030         rckey: &Rc<RefCell<KeyState>>,
1031     ) {
1032         let key: KeyState = {
1033             RefCell::borrow(rckey).clone()
1034         };
1035         let action = key.action.clone();
1036 
1037         layout.apply_view_transition(&action);
1038 
1039         // update
1040         let key = key.into_released();
1041 
1042         // process non-view switching
1043         match action {
1044             Action::Submit { text: _, keys: _ }
1045                 | Action::Erase
1046             => {
1047                 submission.handle_release(KeyState::get_id(rckey), time);
1048             },
1049             Action::ApplyModifier(modifier) => {
1050                 // FIXME: key id is unneeded with stateless locks
1051                 let key_id = KeyState::get_id(rckey);
1052                 let gets_locked = !submission.is_modifier_active(modifier);
1053                 match gets_locked {
1054                     true => submission.handle_add_modifier(
1055                         key_id,
1056                         modifier, time,
1057                     ),
1058                     false => submission.handle_drop_modifier(key_id, time),
1059                 }
1060             }
1061             // only show when UI is present
1062             Action::ShowPreferences => if let Some(ui) = &ui {
1063                 // only show when layout manager is available
1064                 if let Some(manager) = manager {
1065                     let view = layout.get_current_view();
1066                     let places = ::layout::procedures::find_key_places(
1067                         view, &rckey,
1068                     );
1069                     // Getting first item will cause mispositioning
1070                     // with more than one button with the same key
1071                     // on the keyboard.
1072                     if let Some((position, button)) = places.get(0) {
1073                         let bounds = c::Bounds {
1074                             x: position.x,
1075                             y: position.y,
1076                             width: button.size.width,
1077                             height: button.size.height,
1078                         };
1079                         ::popover::show(
1080                             ui.keyboard,
1081                             ui.widget_to_layout.reverse_bounds(bounds),
1082                             manager,
1083                         );
1084                     }
1085                 }
1086             },
1087             // Other keys are handled in view switcher before.
1088             _ => {}
1089         };
1090 
1091         let pointer = ::util::Pointer(rckey.clone());
1092         // Apply state changes
1093         layout.pressed_keys.remove(&pointer);
1094         // Commit activated button state changes
1095         RefCell::replace(rckey, key);
1096     }
1097 }
1098 
1099 #[cfg(test)]
1100 mod test {
1101     use super::*;
1102 
1103     use std::ffi::CString;
1104     use ::keyboard::PressType;
1105 
make_state_with_action(action: Action) -> Rc<RefCell<::keyboard::KeyState>>1106     pub fn make_state_with_action(action: Action)
1107         -> Rc<RefCell<::keyboard::KeyState>>
1108     {
1109         Rc::new(RefCell::new(::keyboard::KeyState {
1110             pressed: PressType::Released,
1111             keycodes: Vec::new(),
1112             action,
1113         }))
1114     }
1115 
make_state() -> Rc<RefCell<::keyboard::KeyState>>1116     pub fn make_state() -> Rc<RefCell<::keyboard::KeyState>> {
1117         make_state_with_action(Action::SetView("default".into()))
1118     }
1119 
make_button_with_state( name: String, state: Rc<RefCell<::keyboard::KeyState>>, ) -> Box<Button>1120     pub fn make_button_with_state(
1121         name: String,
1122         state: Rc<RefCell<::keyboard::KeyState>>,
1123     ) -> Box<Button> {
1124         Box::new(Button {
1125             name: CString::new(name.clone()).unwrap(),
1126             size: Size { width: 0f64, height: 0f64 },
1127             outline_name: CString::new("test").unwrap(),
1128             label: Label::Text(CString::new(name).unwrap()),
1129             state: state,
1130         })
1131     }
1132 
1133     #[test]
latch_lock_unlock()1134     fn latch_lock_unlock() {
1135         let action = Action::LockView {
1136             lock: "lock".into(),
1137             unlock: "unlock".into(),
1138             latches: true,
1139             looks_locked_from: vec![],
1140         };
1141 
1142         assert_eq!(
1143             Layout::process_action_for_view(&action, "unlock", &LatchedState::Not),
1144             (ViewTransition::ChangeTo("lock"), LatchedState::FromView("unlock".into())),
1145         );
1146 
1147         assert_eq!(
1148             Layout::process_action_for_view(&action, "lock", &LatchedState::FromView("unlock".into())),
1149             (ViewTransition::NoChange, LatchedState::Not),
1150         );
1151 
1152         assert_eq!(
1153             Layout::process_action_for_view(&action, "lock", &LatchedState::Not),
1154             (ViewTransition::ChangeTo("unlock"), LatchedState::Not),
1155         );
1156 
1157         assert_eq!(
1158             Layout::process_action_for_view(&Action::Erase, "lock", &LatchedState::FromView("base".into())),
1159             (ViewTransition::UnlatchAll, LatchedState::Not),
1160         );
1161     }
1162 
1163     #[test]
latch_pop_layout()1164     fn latch_pop_layout() {
1165         let switch = Action::LockView {
1166             lock: "locked".into(),
1167             unlock: "base".into(),
1168             latches: true,
1169             looks_locked_from: vec![],
1170         };
1171 
1172         let submit = Action::Erase;
1173 
1174         let view = View::new(vec![(
1175             0.0,
1176             Row::new(vec![
1177                 (
1178                     0.0,
1179                     make_button_with_state(
1180                         "switch".into(),
1181                         make_state_with_action(switch.clone())
1182                     ),
1183                 ),
1184                 (
1185                     1.0,
1186                     make_button_with_state(
1187                         "submit".into(),
1188                         make_state_with_action(submit.clone())
1189                     ),
1190                 ),
1191             ]),
1192         )]);
1193 
1194         let mut layout = Layout {
1195             current_view: "base".into(),
1196             view_latched: LatchedState::Not,
1197             keymaps: Vec::new(),
1198             kind: ArrangementKind::Base,
1199             pressed_keys: HashSet::new(),
1200             margins: Margins {
1201                 top: 0.0,
1202                 left: 0.0,
1203                 right: 0.0,
1204                 bottom: 0.0,
1205             },
1206             views: hashmap! {
1207                 // Both can use the same structure.
1208                 // Switching doesn't depend on the view shape
1209                 // as long as the switching button is present.
1210                 "base".into() => (c::Point { x: 0.0, y: 0.0 }, view.clone()),
1211                 "locked".into() => (c::Point { x: 0.0, y: 0.0 }, view),
1212             },
1213             purpose: ContentPurpose::Normal,
1214         };
1215 
1216         // Basic cycle
1217         layout.apply_view_transition(&switch);
1218         assert_eq!(&layout.current_view, "locked");
1219         layout.apply_view_transition(&switch);
1220         assert_eq!(&layout.current_view, "locked");
1221         layout.apply_view_transition(&submit);
1222         assert_eq!(&layout.current_view, "locked");
1223         layout.apply_view_transition(&switch);
1224         assert_eq!(&layout.current_view, "base");
1225         layout.apply_view_transition(&switch);
1226         // Unlatch
1227         assert_eq!(&layout.current_view, "locked");
1228         layout.apply_view_transition(&submit);
1229         assert_eq!(&layout.current_view, "base");
1230     }
1231 
1232     #[test]
reverse_unlatch_layout()1233     fn reverse_unlatch_layout() {
1234         let switch = Action::LockView {
1235             lock: "locked".into(),
1236             unlock: "base".into(),
1237             latches: true,
1238             looks_locked_from: vec![],
1239         };
1240 
1241         let unswitch = Action::LockView {
1242             lock: "locked".into(),
1243             unlock: "unlocked".into(),
1244             latches: false,
1245             looks_locked_from: vec![],
1246         };
1247 
1248         let submit = Action::Erase;
1249 
1250         let view = View::new(vec![(
1251             0.0,
1252             Row::new(vec![
1253                 (
1254                     0.0,
1255                     make_button_with_state(
1256                         "switch".into(),
1257                         make_state_with_action(switch.clone())
1258                     ),
1259                 ),
1260                 (
1261                     1.0,
1262                     make_button_with_state(
1263                         "submit".into(),
1264                         make_state_with_action(submit.clone())
1265                     ),
1266                 ),
1267             ]),
1268         )]);
1269 
1270         let mut layout = Layout {
1271             current_view: "base".into(),
1272             view_latched: LatchedState::Not,
1273             keymaps: Vec::new(),
1274             kind: ArrangementKind::Base,
1275             pressed_keys: HashSet::new(),
1276             margins: Margins {
1277                 top: 0.0,
1278                 left: 0.0,
1279                 right: 0.0,
1280                 bottom: 0.0,
1281             },
1282             views: hashmap! {
1283                 // Both can use the same structure.
1284                 // Switching doesn't depend on the view shape
1285                 // as long as the switching button is present.
1286                 "base".into() => (c::Point { x: 0.0, y: 0.0 }, view.clone()),
1287                 "locked".into() => (c::Point { x: 0.0, y: 0.0 }, view.clone()),
1288                 "unlocked".into() => (c::Point { x: 0.0, y: 0.0 }, view),
1289             },
1290             purpose: ContentPurpose::Normal,
1291         };
1292 
1293         layout.apply_view_transition(&switch);
1294         assert_eq!(&layout.current_view, "locked");
1295         layout.apply_view_transition(&unswitch);
1296         assert_eq!(&layout.current_view, "unlocked");
1297     }
1298 
1299     #[test]
latch_twopop_layout()1300     fn latch_twopop_layout() {
1301         let switch = Action::LockView {
1302             lock: "locked".into(),
1303             unlock: "base".into(),
1304             latches: true,
1305             looks_locked_from: vec![],
1306         };
1307 
1308         let switch_again = Action::LockView {
1309             lock: "ĄĘ".into(),
1310             unlock: "locked".into(),
1311             latches: true,
1312             looks_locked_from: vec![],
1313         };
1314 
1315         let submit = Action::Erase;
1316 
1317         let view = View::new(vec![(
1318             0.0,
1319             Row::new(vec![
1320                 (
1321                     0.0,
1322                     make_button_with_state(
1323                         "switch".into(),
1324                         make_state_with_action(switch.clone())
1325                     ),
1326                 ),
1327                 (
1328                     1.0,
1329                     make_button_with_state(
1330                         "submit".into(),
1331                         make_state_with_action(submit.clone())
1332                     ),
1333                 ),
1334             ]),
1335         )]);
1336 
1337         let mut layout = Layout {
1338             current_view: "base".into(),
1339             view_latched: LatchedState::Not,
1340             keymaps: Vec::new(),
1341             kind: ArrangementKind::Base,
1342             pressed_keys: HashSet::new(),
1343             margins: Margins {
1344                 top: 0.0,
1345                 left: 0.0,
1346                 right: 0.0,
1347                 bottom: 0.0,
1348             },
1349             views: hashmap! {
1350                 // All can use the same structure.
1351                 // Switching doesn't depend on the view shape
1352                 // as long as the switching button is present.
1353                 "base".into() => (c::Point { x: 0.0, y: 0.0 }, view.clone()),
1354                 "locked".into() => (c::Point { x: 0.0, y: 0.0 }, view.clone()),
1355                 "ĄĘ".into() => (c::Point { x: 0.0, y: 0.0 }, view),
1356             },
1357             purpose: ContentPurpose::Normal,
1358         };
1359 
1360         // Latch twice, then Ąto-unlatch across 2 levels
1361         layout.apply_view_transition(&switch);
1362         println!("{:?}", layout.view_latched);
1363         assert_eq!(&layout.current_view, "locked");
1364         layout.apply_view_transition(&switch_again);
1365         println!("{:?}", layout.view_latched);
1366         assert_eq!(&layout.current_view, "ĄĘ");
1367         layout.apply_view_transition(&submit);
1368         println!("{:?}", layout.view_latched);
1369         assert_eq!(&layout.current_view, "base");
1370     }
1371 
1372     #[test]
check_centering()1373     fn check_centering() {
1374         //    A B
1375         // ---bar---
1376         let view = View::new(vec![
1377             (
1378                 0.0,
1379                 Row::new(vec![
1380                     (
1381                         0.0,
1382                         Box::new(Button {
1383                             size: Size { width: 5.0, height: 10.0 },
1384                             ..*make_button_with_state("A".into(), make_state())
1385                         }),
1386                     ),
1387                     (
1388                         5.0,
1389                         Box::new(Button {
1390                             size: Size { width: 5.0, height: 10.0 },
1391                             ..*make_button_with_state("B".into(), make_state())
1392                         }),
1393                     ),
1394                 ]),
1395             ),
1396             (
1397                 10.0,
1398                 Row::new(vec![
1399                     (
1400                         0.0,
1401                         Box::new(Button {
1402                             size: Size { width: 30.0, height: 10.0 },
1403                             ..*make_button_with_state("bar".into(), make_state())
1404                         }),
1405                     ),
1406                 ]),
1407             )
1408         ]);
1409         assert!(
1410             view.find_button_by_position(c::Point { x: 5.0, y: 5.0 })
1411                 .unwrap().button.name.to_str().unwrap() == "A"
1412         );
1413         assert!(
1414             view.find_button_by_position(c::Point { x: 14.99, y: 5.0 })
1415                 .unwrap().button.name.to_str().unwrap() == "A"
1416         );
1417         assert!(
1418             view.find_button_by_position(c::Point { x: 15.01, y: 5.0 })
1419                 .unwrap().button.name.to_str().unwrap() == "B"
1420         );
1421         assert!(
1422             view.find_button_by_position(c::Point { x: 25.0, y: 5.0 })
1423                 .unwrap().button.name.to_str().unwrap() == "B"
1424         );
1425     }
1426 
1427     #[test]
check_bottom_margin()1428     fn check_bottom_margin() {
1429         // just one button
1430         let view = View::new(vec![
1431             (
1432                 0.0,
1433                 Row::new(vec![(
1434                     0.0,
1435                     Box::new(Button {
1436                         size: Size { width: 1.0, height: 1.0 },
1437                         ..*make_button_with_state("foo".into(), make_state())
1438                     }),
1439                 )]),
1440             ),
1441         ]);
1442         let layout = Layout {
1443             current_view: String::new(),
1444             view_latched: LatchedState::Not,
1445             keymaps: Vec::new(),
1446             kind: ArrangementKind::Base,
1447             pressed_keys: HashSet::new(),
1448             // Lots of bottom margin
1449             margins: Margins {
1450                 top: 0.0,
1451                 left: 0.0,
1452                 right: 0.0,
1453                 bottom: 1.0,
1454             },
1455             views: hashmap! {
1456                 String::new() => (c::Point { x: 0.0, y: 0.0 }, view),
1457             },
1458             purpose: ContentPurpose::Normal,
1459         };
1460         assert_eq!(
1461             layout.calculate_inner_size(),
1462             Size { width: 1.0, height: 1.0 }
1463         );
1464         assert_eq!(
1465             layout.calculate_size(),
1466             Size { width: 1.0, height: 2.0 }
1467         );
1468         // Don't change those values randomly!
1469         // They take advantage of incidental precise float representation
1470         // to even be comparable.
1471         let transformation = layout.calculate_transformation(
1472             Size { width: 2.0, height: 2.0 }
1473         );
1474         assert_eq!(transformation.scale, 1.0);
1475         assert_eq!(transformation.origin_x, 0.5);
1476         assert_eq!(transformation.origin_y, 0.0);
1477     }
1478 }
1479