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