/*! * 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::rc::Rc; use std::vec::Vec; use ::action::Action; use ::drawing; use ::float_ord::FloatOrd; use ::keyboard::{ KeyState, PressType }; use ::submission::{ Timestamp, VirtualKeyboard }; use ::util::find_max_double; use std::borrow::Borrow; /// Gathers stuff defined in C or called by C pub mod c { use super::*; use gtk_sys; use std::ffi::CStr; use std::os::raw::{ c_char, c_void }; use std::ptr; use std::ops::{ Add, Sub }; // The following defined in C #[repr(transparent)] #[derive(Copy, Clone)] pub struct EekGtkKeyboard(pub *const gtk_sys::GtkWidget); /// 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) } } /// Scale + translate #[repr(C)] pub struct Transformation { pub origin_x: f64, pub origin_y: f64, pub scale: f64, } impl Transformation { 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, } } } // The following defined in Rust. TODO: wrap naked pointers to Rust data inside RefCells to prevent multiple writers #[no_mangle] pub extern "C" fn squeek_button_get_bounds(button: *const ::layout::Button) -> Bounds { let button = unsafe { &*button }; Bounds { x: 0.0, y: 0.0, width: button.size.width, height: button.size.height } } #[no_mangle] pub extern "C" fn squeek_button_get_label( button: *const ::layout::Button ) -> *const c_char { let button = unsafe { &*button }; match &button.label { Label::Text(text) => text.as_ptr(), // returning static strings to C is a bit cumbersome Label::IconName(_) => unsafe { // CStr doesn't allocate anything, so it only points to // the 'static str, avoiding a memory leak CStr::from_bytes_with_nul_unchecked(b"icon\0") }.as_ptr(), } } #[no_mangle] pub extern "C" fn squeek_button_get_icon_name(button: *const Button) -> *const c_char { let button = unsafe { &*button }; match &button.label { Label::Text(_) => ptr::null(), Label::IconName(name) => name.as_ptr(), } } #[no_mangle] pub extern "C" fn squeek_button_get_name(button: *const Button) -> *const c_char { let button = unsafe { &*button }; button.name.as_ptr() } #[no_mangle] pub extern "C" fn squeek_button_get_outline_name(button: *const Button) -> *const c_char { let button = unsafe { &*button }; button.outline_name.as_ptr() } #[no_mangle] pub extern "C" fn squeek_button_print(button: *const ::layout::Button) { let button = unsafe { &*button }; println!("{:?}", button); } /// Positions the layout within the available space #[no_mangle] pub extern "C" fn squeek_layout_calculate_transformation( layout: *const Layout, allocation_width: f64, allocation_height: f64, ) -> Transformation { let layout = unsafe { &*layout }; let size = layout.calculate_size(); let h_scale = allocation_width / size.width; let v_scale = allocation_height / size.height; let scale = if h_scale < v_scale { h_scale } else { v_scale }; Transformation { origin_x: (allocation_width - (scale * size.width)) / 2.0, origin_y: (allocation_height - (scale * size.height)) / 2.0, scale: scale, } } #[no_mangle] pub extern "C" fn squeek_layout_get_keymap(layout: *const Layout) -> *const c_char { let layout = unsafe { &*layout }; layout.keymap_str.as_ptr() } #[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_free(layout: *mut Layout) { unsafe { Box::from_raw(layout) }; } /// Entry points for more complex procedures and algoithms which span multiple modules pub mod procedures { use super::*; use ::submission::c::ZwpVirtualKeyboardV1; #[repr(C)] #[derive(PartialEq, Debug)] pub struct CButtonPlace { row: *const Row, button: *const Button, } impl<'a> From> for CButtonPlace { fn from(value: ButtonPlace<'a>) -> CButtonPlace { CButtonPlace { row: value.row as *const Row, button: value.button as *const Button, } } } // This is constructed only in C, no need for warnings #[allow(dead_code)] #[repr(transparent)] pub struct LevelKeyboard(*const c_void); /// Release pointer in the specified position #[no_mangle] pub extern "C" fn squeek_layout_release( layout: *mut Layout, virtual_keyboard: ZwpVirtualKeyboardV1, // TODO: receive a reference to the backend widget_to_layout: Transformation, time: u32, ui_keyboard: EekGtkKeyboard, ) { let time = Timestamp(time); let layout = unsafe { &mut *layout }; let virtual_keyboard = VirtualKeyboard(virtual_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, &virtual_keyboard, &widget_to_layout, time, ui_keyboard, key, ); } drawing::queue_redraw(ui_keyboard); } /// Release all buittons but don't redraw #[no_mangle] pub extern "C" fn squeek_layout_release_all_only( layout: *mut Layout, virtual_keyboard: ZwpVirtualKeyboardV1, // TODO: receive a reference to the backend time: u32, ) { let layout = unsafe { &mut *layout }; let virtual_keyboard = VirtualKeyboard(virtual_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(); layout.release_key( &virtual_keyboard, &mut key.clone(), Timestamp(time) ); } } #[no_mangle] pub extern "C" fn squeek_layout_depress( layout: *mut Layout, virtual_keyboard: ZwpVirtualKeyboardV1, // TODO: receive a reference to the backend x_widget: f64, y_widget: f64, widget_to_layout: Transformation, time: u32, ui_keyboard: EekGtkKeyboard, ) { let layout = unsafe { &mut *layout }; let point = widget_to_layout.forward( Point { x: x_widget, y: y_widget } ); if let Some(position) = layout.get_button_at_point(point) { let mut state = position.button.state.clone(); layout.press_key( &VirtualKeyboard(virtual_keyboard), &mut state, Timestamp(time), ); // maybe TODO: draw on the display buffer here drawing::queue_redraw(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, virtual_keyboard: ZwpVirtualKeyboardV1, // TODO: receive a reference to the backend x_widget: f64, y_widget: f64, widget_to_layout: Transformation, time: u32, ui_keyboard: EekGtkKeyboard, ) { let time = Timestamp(time); let layout = unsafe { &mut *layout }; let virtual_keyboard = VirtualKeyboard(virtual_keyboard); let point = widget_to_layout.forward( Point { x: x_widget, y: y_widget } ); let pressed = layout.pressed_keys.clone(); let button_info = { let view = layout.get_current_view(); let place = view.find_button_by_position(point); place.map(|place| {( place.button.state.clone(), place.button.clone(), place.offset, )}) }; if let Some((mut 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, &virtual_keyboard, &widget_to_layout, time, ui_keyboard, key, ); } } if !found { layout.press_key(&virtual_keyboard, &mut state, time); // maybe TODO: draw on the display buffer here } } else { for wrapped_key in pressed { let key: &Rc> = wrapped_key.borrow(); seat::handle_release_key( layout, &virtual_keyboard, &widget_to_layout, time, ui_keyboard, 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)); } } } } /// Relative to `View` struct ButtonPosition { view_position: c::Point, button: Button, } pub struct ButtonPlace<'a> { button: &'a Button, row: &'a Row, offset: c::Point, } #[derive(Debug, Clone)] 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>, } /// The graphical representation of a row of buttons pub struct Row { /// Buttons together with their offset from the left pub buttons: Vec<(f64, Box