/*! * 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::cmp; use std::collections::HashMap; use std::ffi::CString; use std::fmt; use std::vec::Vec; use crate::action::Action; use crate::actors; use crate::drawing; use crate::float_ord::FloatOrd; use crate::keyboard::{KeyState, KeyCode, PressType}; use crate::logging; use crate::popover; use crate::receiver; use crate::submission::{ Submission, SubmitData, Timestamp }; use crate::util::find_max_double; use crate::imservice::ContentPurpose; // Traits use crate::logging::Warn; /// Gathers stuff defined in C or called by C pub mod c { use super::*; use crate::receiver; use crate::submission::c::Submission as CSubmission; use gtk_sys; use std::ops::{ Add, Sub }; use std::os::raw::c_void; use crate::util::CloneOwned; // 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_x: f64, pub scale_y: 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_x * next.origin_x, origin_y: self.origin_y + self.scale_y * next.origin_y, scale_x: self.scale_x * next.scale_x, scale_y: self.scale_y * next.scale_y, } } fn forward(&self, p: Point) -> Point { Point { x: (p.x - self.origin_x) / self.scale_x, y: (p.y - self.origin_y) / self.scale_y, } } fn reverse(&self, p: Point) -> Point { Point { x: p.x * self.scale_x + self.origin_x, y: p.y * self.scale_y + 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.shape.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.shape.kind.clone() as u32 } #[no_mangle] pub extern "C" fn squeek_layout_get_purpose(layout: *const Layout) -> u32 { let layout = unsafe { &*layout }; layout.shape.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, popover: actors::popover::c::Actor, app_state: receiver::c::State, ui_keyboard: EekGtkKeyboard, ) { let time = Timestamp(time); let layout = unsafe { &mut *layout }; let submission = submission.clone_ref(); let mut submission = submission.borrow_mut(); let app_state = app_state.clone_owned(); let popover_state = popover.clone_owned(); let ui_backend = UIBackend { widget_to_layout, keyboard: ui_keyboard, }; // The list must be copied, // because it will be mutated in the loop let pressed_buttons = layout.state.active_buttons.clone(); for (button, _key_state) in pressed_buttons.iter_pressed() { seat::handle_release_key( layout, &mut submission, Some(&ui_backend), time, Some((&popover_state, app_state.clone())), button, ); } 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 let pressed_buttons = layout.state.active_buttons.clone(); for (button, _key_state) in pressed_buttons.iter_pressed() { seat::handle_release_key( layout, &mut submission, None, // don't update UI Timestamp(time), None, // don't switch layouts button, ); } } #[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 index = layout.find_index_by_position(point); if let Some((row, position_in_row)) = index { let button = ButtonPosition { view: layout.state.current_view.clone(), row, position_in_row, }; seat::handle_press_key( layout, &mut submission, Timestamp(time), &button, ); // 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, popover: actors::popover::c::Actor, app_state: receiver::c::State, ui_keyboard: EekGtkKeyboard, ) { let time = Timestamp(time); let layout = unsafe { &mut *layout }; let submission = submission.clone_ref(); let mut submission = submission.borrow_mut(); // We only need to query state here, not update. // A copy is enough. let popover_state = popover.clone_owned(); let app_state = app_state.clone_owned(); 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_buttons = layout.state.active_buttons.clone(); let pressed_buttons = pressed_buttons.iter_pressed(); let button_info = layout.find_index_by_position(point); if let Some((row, position_in_row)) = button_info { let current_pos = ButtonPosition { view: layout.state.current_view.clone(), row, position_in_row, }; let mut found = false; for (button, _key_state) in pressed_buttons { if button == ¤t_pos { found = true; } else { seat::handle_release_key( layout, &mut submission, Some(&ui_backend), time, Some((&popover_state, app_state.clone())), button, ); } } if !found { let button = ButtonPosition { view: layout.state.current_view.clone(), row, position_in_row, }; seat::handle_press_key( layout, &mut submission, time, &button, ); // maybe TODO: draw on the display buffer here unsafe { eek_gtk_keyboard_emit_feedback(ui_keyboard); } } } else { for (button, _key_state) in pressed_buttons { seat::handle_release_key( layout, &mut submission, Some(&ui_backend), time, Some((&popover_state, app_state.clone())), button, ); } } 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_x: 12f64, scale_y: 13f64, }; 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)); } } } } #[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 definition of an interactive button #[derive(Clone, Debug, PartialEq)] 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, // action-related stuff /// A cache of raw keycodes derived from Action::Submit given a keymap pub keycodes: Vec, /// Static description of what the key does when pressed or released pub action: Action, } 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 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