/*! * Layout-related data. * * The `View` contains `Row`s and each `Row` contains `Button`s. * They carry data relevant to their positioning only, * except the Button, which also carries some data * about its appearance and function. * * The layout is determined bottom-up, by measuring `Button` sizes, * deriving `Row` sizes from them, and then centering them within the `View`. * * That makes the `View` position immutable, * and therefore different than the other positions. * * Note that it might be a better idea * to make `View` position depend on its contents, * and let the renderer scale and center it within the widget. */ use std::cell::RefCell; use std::collections::{ HashMap, HashSet }; use std::ffi::CString; use std::fmt; use std::rc::Rc; use std::vec::Vec; use ::action::Action; use ::drawing; use ::keyboard::KeyState; use ::logging; use ::manager; use ::submission::{ Submission, SubmitData, Timestamp }; use ::util::find_max_double; use ::imservice::ContentPurpose; // Traits use std::borrow::Borrow; use ::logging::Warn; /// Gathers stuff defined in C or called by C pub mod c { use super::*; use gtk_sys; use std::os::raw::c_void; use crate::submission::c::Submission as CSubmission; use std::ops::{ Add, Sub }; // The following defined in C #[repr(transparent)] #[derive(Copy, Clone)] pub struct EekGtkKeyboard(pub *const gtk_sys::GtkWidget); extern "C" { #[allow(improper_ctypes)] pub fn eek_gtk_keyboard_emit_feedback( keyboard: EekGtkKeyboard, ); } /// Defined in eek-types.h #[repr(C)] #[derive(Clone, Debug, PartialEq)] pub struct Point { pub x: f64, pub y: f64, } impl Add for Point { type Output = Self; fn add(self, other: Self) -> Self { &self + other } } impl Add for &Point { type Output = Point; fn add(self, other: Point) -> Point { Point { x: self.x + other.x, y: self.y + other.y, } } } impl Sub<&Point> for Point { type Output = Point; fn sub(self, other: &Point) -> Point { Point { x: self.x - other.x, y: self.y - other.y, } } } /// Defined in eek-types.h #[repr(C)] #[derive(Clone, Debug, PartialEq)] pub struct Bounds { pub x: f64, pub y: f64, pub width: f64, pub height: f64 } impl Bounds { pub fn contains(&self, point: &Point) -> bool { point.x > self.x && point.x < self.x + self.width && point.y > self.y && point.y < self.y + self.height } } /// Translate and then scale #[repr(C)] pub struct Transformation { pub origin_x: f64, pub origin_y: f64, pub scale: f64, } impl Transformation { /// Applies the new transformation after this one pub fn chain(self, next: Transformation) -> Transformation { Transformation { origin_x: self.origin_x + self.scale * next.origin_x, origin_y: self.origin_y + self.scale * next.origin_y, scale: self.scale * next.scale, } } fn forward(&self, p: Point) -> Point { Point { x: (p.x - self.origin_x) / self.scale, y: (p.y - self.origin_y) / self.scale, } } fn reverse(&self, p: Point) -> Point { Point { x: p.x * self.scale + self.origin_x, y: p.y * self.scale + self.origin_y, } } pub fn reverse_bounds(&self, b: Bounds) -> Bounds { let start = self.reverse(Point { x: b.x, y: b.y }); let end = self.reverse(Point { x: b.x + b.width, y: b.y + b.height, }); Bounds { x: start.x, y: start.y, width: end.x - start.x, height: end.y - start.y, } } } // This is constructed only in C, no need for warnings #[allow(dead_code)] #[repr(transparent)] pub struct LevelKeyboard(*const c_void); // The following defined in Rust. TODO: wrap naked pointers to Rust data inside RefCells to prevent multiple writers /// Positions the layout contents within the available space. /// The origin of the transformation is the point inside the margins. #[no_mangle] pub extern "C" fn squeek_layout_calculate_transformation( layout: *const Layout, allocation_width: f64, allocation_height: f64, ) -> Transformation { let layout = unsafe { &*layout }; layout.calculate_transformation(Size { width: allocation_width, height: allocation_height, }) } #[no_mangle] pub extern "C" fn squeek_layout_get_kind(layout: *const Layout) -> u32 { let layout = unsafe { &*layout }; layout.kind.clone() as u32 } #[no_mangle] pub extern "C" fn squeek_layout_get_purpose(layout: *const Layout) -> u32 { let layout = unsafe { &*layout }; layout.purpose.clone() as u32 } #[no_mangle] pub extern "C" fn squeek_layout_free(layout: *mut Layout) { unsafe { Box::from_raw(layout) }; } /// Entry points for more complex procedures and algorithms which span multiple modules pub mod procedures { use super::*; /// Release pointer in the specified position #[no_mangle] pub extern "C" fn squeek_layout_release( layout: *mut Layout, submission: CSubmission, widget_to_layout: Transformation, time: u32, manager: manager::c::Manager, ui_keyboard: EekGtkKeyboard, ) { let time = Timestamp(time); let layout = unsafe { &mut *layout }; let submission = submission.clone_ref(); let mut submission = submission.borrow_mut(); let ui_backend = UIBackend { widget_to_layout, keyboard: ui_keyboard, }; // The list must be copied, // because it will be mutated in the loop for key in layout.pressed_keys.clone() { let key: &Rc> = key.borrow(); seat::handle_release_key( layout, &mut submission, Some(&ui_backend), time, Some(manager), key, ); } drawing::queue_redraw(ui_keyboard); } /// Release all buttons but don't redraw #[no_mangle] pub extern "C" fn squeek_layout_release_all_only( layout: *mut Layout, submission: CSubmission, time: u32, ) { let layout = unsafe { &mut *layout }; let submission = submission.clone_ref(); let mut submission = submission.borrow_mut(); // The list must be copied, // because it will be mutated in the loop for key in layout.pressed_keys.clone() { let key: &Rc> = key.borrow(); seat::handle_release_key( layout, &mut submission, None, // don't update UI Timestamp(time), None, // don't switch layouts &mut key.clone(), ); } } #[no_mangle] pub extern "C" fn squeek_layout_depress( layout: *mut Layout, submission: CSubmission, x_widget: f64, y_widget: f64, widget_to_layout: Transformation, time: u32, ui_keyboard: EekGtkKeyboard, ) { let layout = unsafe { &mut *layout }; let submission = submission.clone_ref(); let mut submission = submission.borrow_mut(); let point = widget_to_layout.forward( Point { x: x_widget, y: y_widget } ); let state = layout.find_button_by_position(point) .map(|place| place.button.state.clone()); if let Some(state) = state { seat::handle_press_key( layout, &mut submission, Timestamp(time), &state, ); // maybe TODO: draw on the display buffer here drawing::queue_redraw(ui_keyboard); unsafe { eek_gtk_keyboard_emit_feedback(ui_keyboard); } }; } // FIXME: this will work funny // when 2 touch points are on buttons and moving one after another // Solution is to have separate pressed lists for each point #[no_mangle] pub extern "C" fn 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, ) { let time = Timestamp(time); let layout = unsafe { &mut *layout }; let submission = submission.clone_ref(); let mut submission = submission.borrow_mut(); let ui_backend = UIBackend { widget_to_layout, keyboard: ui_keyboard, }; let point = ui_backend.widget_to_layout.forward( Point { x: x_widget, y: y_widget } ); let pressed = layout.pressed_keys.clone(); let button_info = { let place = layout.find_button_by_position(point); place.map(|place| {( place.button.state.clone(), place.button.clone(), place.offset, )}) }; if let Some((state, _button, _view_position)) = button_info { let mut found = false; for wrapped_key in pressed { let key: &Rc> = wrapped_key.borrow(); if Rc::ptr_eq(&state, &wrapped_key.0) { found = true; } else { seat::handle_release_key( layout, &mut submission, Some(&ui_backend), time, Some(manager), key, ); } } if !found { seat::handle_press_key( layout, &mut submission, time, &state, ); // maybe TODO: draw on the display buffer here unsafe { eek_gtk_keyboard_emit_feedback(ui_keyboard); } } } else { for wrapped_key in pressed { let key: &Rc> = wrapped_key.borrow(); seat::handle_release_key( layout, &mut submission, Some(&ui_backend), time, Some(manager), key, ); } } drawing::queue_redraw(ui_keyboard); } #[cfg(test)] mod test { use super::*; fn near(a: f64, b: f64) -> bool { (a - b).abs() < ((a + b) * 0.001f64).abs() } #[test] fn transform_back() { let transform = Transformation { origin_x: 10f64, origin_y: 11f64, scale: 12f64, }; let point = Point { x: 1f64, y: 1f64 }; let transformed = transform.reverse(transform.forward(point.clone())); assert!(near(point.x, transformed.x)); assert!(near(point.y, transformed.y)); } } } } pub struct ButtonPlace<'a> { button: &'a Button, offset: c::Point, } #[derive(Debug, Clone, PartialEq)] pub struct Size { pub width: f64, pub height: f64, } #[derive(Debug, Clone, PartialEq)] pub enum Label { /// Text used to display the symbol Text(CString), /// Icon name used to render the symbol IconName(CString), } /// The graphical representation of a button #[derive(Clone, Debug)] pub struct Button { /// ID string, e.g. for CSS pub name: CString, /// Label to display to the user pub label: Label, pub size: Size, /// The name of the visual class applied pub outline_name: CString, /// current state, shared with other buttons pub state: Rc>, } impl Button { pub fn get_bounds(&self) -> c::Bounds { c::Bounds { x: 0.0, y: 0.0, width: self.size.width, height: self.size.height, } } } /// The graphical representation of a row of buttons #[derive(Clone, Debug)] pub struct Row { /// Buttons together with their offset from the left relative to the row. /// ie. the first button always start at 0. buttons: Vec<(f64, Box