/*! * 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 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; // 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)] 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, } } } /// 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 get_position(&self) -> Point { Point { x: self.x, y: self.y, } } } /// 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 }; button.bounds.clone() } #[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 bounds = &layout.get_current_view().bounds; let h_scale = allocation_width / bounds.width; let v_scale = allocation_height / bounds.height; let scale = if h_scale > v_scale { h_scale } else { v_scale }; Transformation { origin_x: allocation_width - (scale * bounds.width) / 2.0, origin_y: allocation_height - (scale * bounds.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); #[no_mangle] extern "C" { /// Checks if point falls within bounds, /// which are relative to origin and rotated by angle (I think) pub fn eek_are_bounds_inside (bounds: Bounds, point: Point, origin: Point, angle: i32 ) -> u32; } /// Places each button in order, starting from 0 on the left, /// keeping the spacing. /// Sizes each button according to outline dimensions. /// Places each row in order, starting from 0 on the top, /// keeping the spacing. /// Sets button and row sizes according to their contents. #[no_mangle] pub extern "C" fn squeek_layout_place_contents(layout: *mut Layout) { let layout = unsafe { &mut *layout }; for view in layout.views.values_mut() { let sizes: Vec> = view.rows.iter().map(|row| { row.buttons.iter() .map(|button| button.bounds.clone()) .collect() }).collect(); view.place_buttons_with_sizes(sizes); } } /// 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(), view.bounds.get_position() + place.row.bounds.clone().unwrap().get_position() + place.button.bounds.get_position(), )}) }; 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, } #[derive(Debug)] 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, /// TODO: position the buttons before they get initial bounds /// Position relative to some origin (i.e. parent/row) pub bounds: c::Bounds, /// 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 { pub buttons: Vec>, /// Angle is not really used anywhere... pub angle: i32, /// Position relative to some origin (i.e. parent/view origin) pub bounds: Option, } impl Row { fn last(positions: &Vec) -> Option<&c::Bounds> { let len = positions.len(); match len { 0 => None, l => Some(&positions[l - 1]) } } fn calculate_button_positions(outlines: Vec) -> Vec { let mut x_offset = 0f64; outlines.iter().map(|outline| { x_offset += outline.x; // account for offset outlines let position = c::Bounds { x: x_offset, ..outline.clone() }; x_offset += outline.width; position }).collect() } fn calculate_row_size(positions: &Vec) -> Size { let max_height = positions.iter().map( |bounds| FloatOrd(bounds.height) ).max() .unwrap_or(FloatOrd(0f64)) .0; let total_width = match Row::last(positions) { Some(position) => position.x + position.width, None => 0f64, }; Size { width: total_width, height: max_height } } /// Finds the first button that covers the specified point /// relative to row's position's origin fn find_button_by_position(&self, point: c::Point) -> Option<&Box