Merge branch 'positioning' into 'master'

Positioning

See merge request Librem5/squeekboard!274
This commit is contained in:
Dorota Czaplejewicz
2019-12-15 16:51:35 +00:00
8 changed files with 275 additions and 385 deletions

View File

@ -498,53 +498,6 @@ eek_renderer_get_icon_surface (const gchar *icon_name,
return surface; return surface;
} }
static gboolean
sign (EekPoint *p1, EekPoint *p2, EekPoint *p3)
{
// FIXME: what is this actually checking?
return (p1->x - p3->x) * (p2->y - p3->y) -
(p2->x - p3->x) * (p1->y - p3->y);
}
uint32_t
eek_are_bounds_inside (EekBounds bounds, EekPoint point, EekPoint origin, int32_t angle)
{
EekPoint points[4];
gboolean b1, b2, b3;
points[0].x = bounds.x;
points[0].y = bounds.y;
points[1].x = points[0].x + bounds.width;
points[1].y = points[0].y;
points[2].x = points[1].x;
points[2].y = points[1].y + bounds.height;
points[3].x = points[0].x;
points[3].y = points[2].y;
for (unsigned i = 0; i < G_N_ELEMENTS(points); i++) {
eek_point_rotate (&points[i], angle);
points[i].x += origin.x;
points[i].y += origin.y;
}
b1 = sign (&point, &points[0], &points[1]) < 0.0;
b2 = sign (&point, &points[1], &points[2]) < 0.0;
b3 = sign (&point, &points[2], &points[0]) < 0.0;
if (b1 == b2 && b2 == b3) {
return 1;
}
b1 = sign (&point, &points[2], &points[3]) < 0.0;
b2 = sign (&point, &points[3], &points[0]) < 0.0;
b3 = sign (&point, &points[0], &points[2]) < 0.0;
if (b1 == b2 && b2 == b3) {
return 1;
}
return 0;
}
struct transformation struct transformation
eek_renderer_get_transformation (EekRenderer *renderer) { eek_renderer_get_transformation (EekRenderer *renderer) {
struct transformation failed = {0}; struct transformation failed = {0};

View File

@ -34,6 +34,5 @@ eek_xml_layout_real_create_keyboard (const char *keyboard_type,
enum squeek_arrangement_kind t) enum squeek_arrangement_kind t)
{ {
struct squeek_layout *layout = squeek_load_layout(keyboard_type, t); struct squeek_layout *layout = squeek_load_layout(keyboard_type, t);
squeek_layout_place_contents(layout);
return level_keyboard_new(manager, layout); return level_keyboard_new(manager, layout);
} }

View File

@ -17,6 +17,7 @@ use ::keyboard::{
KeyState, PressType, KeyState, PressType,
generate_keymap, generate_keycodes, FormattingError generate_keymap, generate_keycodes, FormattingError
}; };
use ::layout;
use ::layout::ArrangementKind; use ::layout::ArrangementKind;
use ::resources; use ::resources;
use ::util::c::as_str; use ::util::c::as_str;
@ -215,6 +216,7 @@ fn load_layout_data_with_fallback(
#[derive(Debug, Deserialize, PartialEq)] #[derive(Debug, Deserialize, PartialEq)]
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
pub struct Layout { pub struct Layout {
/// FIXME: deprecate in favor of margins
bounds: Bounds, bounds: Bounds,
views: HashMap<String, Vec<ButtonIds>>, views: HashMap<String, Vec<ButtonIds>>,
#[serde(default)] #[serde(default)]
@ -269,6 +271,7 @@ enum Action {
#[derive(Debug, Clone, Deserialize, PartialEq)] #[derive(Debug, Clone, Deserialize, PartialEq)]
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
struct Outline { struct Outline {
/// FIXME: replace with Size
bounds: Bounds, bounds: Bounds,
} }
@ -303,6 +306,20 @@ impl From<io::Error> for Error {
} }
} }
pub fn add_offsets<'a, I: 'a, T, F: 'a>(iterator: I, get_size: F)
-> impl Iterator<Item=(f64, T)> + 'a
where I: Iterator<Item=T>,
F: Fn(&T) -> f64,
{
let mut offset = 0.0;
iterator.map(move |item| {
let size = get_size(&item);
let value = (offset, item);
offset += size;
value
})
}
impl Layout { impl Layout {
pub fn from_resource(name: &str) -> Result<Layout, LoadError> { pub fn from_resource(name: &str) -> Result<Layout, LoadError> {
let data = resources::get_keyboard(name) let data = resources::get_keyboard(name)
@ -403,20 +420,10 @@ impl Layout {
); );
let views = HashMap::from_iter( let views = HashMap::from_iter(
self.views.iter().map(|(name, view)| {( self.views.iter().map(|(name, view)| {
name.clone(), let rows = view.iter().map(|row| {
::layout::View { let buttons = row.split_ascii_whitespace()
bounds: ::layout::c::Bounds { .map(|name| {
x: self.bounds.x,
y: self.bounds.y,
width: self.bounds.width,
height: self.bounds.height,
},
rows: view.iter().map(|row| {
::layout::Row {
angle: 0,
bounds: None,
buttons: row.split_ascii_whitespace().map(|name| {
Box::new(create_button( Box::new(create_button(
&self.buttons, &self.buttons,
&self.outlines, &self.outlines,
@ -426,11 +433,22 @@ impl Layout {
.clone(), .clone(),
&mut warning_handler, &mut warning_handler,
)) ))
}).collect(), });
::layout::Row {
angle: 0,
buttons: add_offsets(
buttons,
|button| button.size.width,
).collect()
} }
}).collect(), });
} let rows = add_offsets(rows, |row| row.get_height())
)}) .collect();
(
name.clone(),
layout::View::new(rows)
)
})
); );
( (
@ -440,6 +458,13 @@ impl Layout {
CString::new(keymap_str) CString::new(keymap_str)
.expect("Invalid keymap string generated") .expect("Invalid keymap string generated")
}, },
// FIXME: use a dedicated field
margins: layout::Margins {
top: self.bounds.x,
left: self.bounds.y,
bottom: 0.0,
right: self.bounds.y,
},
}), }),
warning_handler, warning_handler,
) )
@ -629,13 +654,11 @@ fn create_button<H: WarningHandler>(
} }
}); });
::layout::Button { layout::Button {
name: cname, name: cname,
outline_name: CString::new(outline_name).expect("Bad outline"), outline_name: CString::new(outline_name).expect("Bad outline"),
// TODO: do layout before creating buttons // TODO: do layout before creating buttons
bounds: ::layout::c::Bounds { size: layout::Size {
x: outline.bounds.x,
y: outline.bounds.y,
width: outline.bounds.width, width: outline.bounds.width,
height: outline.bounds.height, height: outline.bounds.height,
}, },
@ -734,8 +757,8 @@ mod tests {
.unwrap(); .unwrap();
assert_eq!( assert_eq!(
out.views["base"] out.views["base"]
.rows[0] .get_rows()[0].1
.buttons[0] .buttons[0].1
.label, .label,
::layout::Label::Text(CString::new("test").unwrap()) ::layout::Label::Text(CString::new("test").unwrap())
); );
@ -749,8 +772,8 @@ mod tests {
.unwrap(); .unwrap();
assert_eq!( assert_eq!(
out.views["base"] out.views["base"]
.rows[0] .get_rows()[0].1
.buttons[0] .buttons[0].1
.label, .label,
::layout::Label::Text(CString::new("test").unwrap()) ::layout::Label::Text(CString::new("test").unwrap())
); );
@ -765,8 +788,8 @@ mod tests {
.unwrap(); .unwrap();
assert_eq!( assert_eq!(
out.views["base"] out.views["base"]
.rows[0] .get_rows()[0].1
.buttons[0] .buttons[0].1
.state.borrow() .state.borrow()
.keycodes.len(), .keycodes.len(),
2 2

View File

@ -48,17 +48,14 @@ mod c {
let cr = unsafe { cairo::Context::from_raw_none(cr) }; let cr = unsafe { cairo::Context::from_raw_none(cr) };
let view = layout.get_current_view(); let view = layout.get_current_view();
let view_position = view.bounds.get_position(); for (row_offset, row) in &view.get_rows() {
for row in &view.rows { for (x_offset, button) in &row.buttons {
for button in &row.buttons {
let state = RefCell::borrow(&button.state).clone(); let state = RefCell::borrow(&button.state).clone();
if state.pressed == keyboard::PressType::Pressed || state.locked { if state.pressed == keyboard::PressType::Pressed || state.locked {
let position = &view_position
+ row.bounds.clone().unwrap().get_position()
+ button.bounds.get_position();
render_button_at_position( render_button_at_position(
renderer, &cr, renderer, &cr,
position, button.as_ref(), row_offset + Point { x: *x_offset, y: 0.0 },
button.as_ref(),
state.pressed, state.locked, state.pressed, state.locked,
); );
} }
@ -76,15 +73,12 @@ mod c {
let layout = unsafe { &mut *layout }; let layout = unsafe { &mut *layout };
let cr = unsafe { cairo::Context::from_raw_none(cr) }; let cr = unsafe { cairo::Context::from_raw_none(cr) };
let view = layout.get_current_view(); let view = layout.get_current_view();
let view_position = view.bounds.get_position(); for (row_offset, row) in &view.get_rows() {
for row in &view.rows { for (x_offset, button) in &row.buttons {
for button in &row.buttons {
let position = &view_position
+ row.bounds.clone().unwrap().get_position()
+ button.bounds.get_position();
render_button_at_position( render_button_at_position(
renderer, &cr, renderer, &cr,
position, button.as_ref(), row_offset + Point { x: *x_offset, y: 0.0 },
button.as_ref(),
keyboard::PressType::Released, false, keyboard::PressType::Released, false,
); );
} }
@ -105,7 +99,7 @@ pub fn render_button_at_position(
cr.translate(position.x, position.y); cr.translate(position.x, position.y);
cr.rectangle( cr.rectangle(
0.0, 0.0, 0.0, 0.0,
button.bounds.width, button.bounds.height button.size.width, button.size.height
); );
cr.clip(); cr.clip();
unsafe { unsafe {

View File

@ -28,9 +28,6 @@ struct transformation squeek_layout_calculate_transformation(
const struct squeek_layout *layout, const struct squeek_layout *layout,
double allocation_width, double allocation_size); double allocation_width, double allocation_size);
void
squeek_layout_place_contents(struct squeek_layout*);
struct squeek_layout *squeek_load_layout(const char *name, uint32_t type); struct squeek_layout *squeek_load_layout(const char *name, uint32_t type);
const char *squeek_layout_get_keymap(const struct squeek_layout*); const char *squeek_layout_get_keymap(const struct squeek_layout*);
enum squeek_arrangement_kind squeek_layout_get_kind(const struct squeek_layout *); enum squeek_arrangement_kind squeek_layout_get_kind(const struct squeek_layout *);

View File

@ -25,9 +25,9 @@ use std::vec::Vec;
use ::action::Action; use ::action::Action;
use ::drawing; use ::drawing;
use ::float_ord::FloatOrd;
use ::keyboard::{ KeyState, PressType }; use ::keyboard::{ KeyState, PressType };
use ::submission::{ Timestamp, VirtualKeyboard }; use ::submission::{ Timestamp, VirtualKeyboard };
use ::util::find_max_double;
use std::borrow::Borrow; use std::borrow::Borrow;
@ -40,7 +40,7 @@ pub mod c {
use std::os::raw::{ c_char, c_void }; use std::os::raw::{ c_char, c_void };
use std::ptr; use std::ptr;
use std::ops::Add; use std::ops::{ Add, Sub };
// The following defined in C // The following defined in C
#[repr(transparent)] #[repr(transparent)]
@ -49,7 +49,7 @@ pub mod c {
/// Defined in eek-types.h /// Defined in eek-types.h
#[repr(C)] #[repr(C)]
#[derive(Clone, Debug)] #[derive(Clone, Debug, PartialEq)]
pub struct Point { pub struct Point {
pub x: f64, pub x: f64,
pub y: f64, pub y: f64,
@ -72,6 +72,16 @@ pub mod c {
} }
} }
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 /// Defined in eek-types.h
#[repr(C)] #[repr(C)]
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
@ -83,11 +93,9 @@ pub mod c {
} }
impl Bounds { impl Bounds {
pub fn get_position(&self) -> Point { pub fn contains(&self, point: &Point) -> bool {
Point { (point.x > self.x && point.x < self.x + self.width
x: self.x, && point.y > self.y && point.y < self.y + self.height)
y: self.y,
}
} }
} }
@ -133,7 +141,10 @@ pub mod c {
pub extern "C" pub extern "C"
fn squeek_button_get_bounds(button: *const ::layout::Button) -> Bounds { fn squeek_button_get_bounds(button: *const ::layout::Button) -> Bounds {
let button = unsafe { &*button }; let button = unsafe { &*button };
button.bounds.clone() Bounds {
x: 0.0, y: 0.0,
width: button.size.width, height: button.size.height
}
} }
#[no_mangle] #[no_mangle]
@ -193,13 +204,13 @@ pub mod c {
allocation_height: f64, allocation_height: f64,
) -> Transformation { ) -> Transformation {
let layout = unsafe { &*layout }; let layout = unsafe { &*layout };
let bounds = &layout.get_current_view().bounds; let size = layout.calculate_size();
let h_scale = allocation_width / bounds.width; let h_scale = allocation_width / size.width;
let v_scale = allocation_height / bounds.height; let v_scale = allocation_height / size.height;
let scale = if h_scale > v_scale { h_scale } else { v_scale }; let scale = if h_scale < v_scale { h_scale } else { v_scale };
Transformation { Transformation {
origin_x: allocation_width - (scale * bounds.width) / 2.0, origin_x: (allocation_width - (scale * size.width)) / 2.0,
origin_y: allocation_height - (scale * bounds.height) / 2.0, origin_y: (allocation_height - (scale * size.height)) / 2.0,
scale: scale, scale: scale,
} }
} }
@ -230,58 +241,11 @@ pub mod c {
use ::submission::c::ZwpVirtualKeyboardV1; use ::submission::c::ZwpVirtualKeyboardV1;
#[repr(C)]
#[derive(PartialEq, Debug)]
pub struct CButtonPlace {
row: *const Row,
button: *const Button,
}
impl<'a> From<ButtonPlace<'a>> 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 // This is constructed only in C, no need for warnings
#[allow(dead_code)] #[allow(dead_code)]
#[repr(transparent)] #[repr(transparent)]
pub struct LevelKeyboard(*const c_void); 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<Vec<Bounds>> = 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 /// Release pointer in the specified position
#[no_mangle] #[no_mangle]
pub extern "C" pub extern "C"
@ -348,8 +312,13 @@ pub mod c {
Point { x: x_widget, y: y_widget } Point { x: x_widget, y: y_widget }
); );
if let Some(position) = layout.get_button_at_point(point) { let state = {
let mut state = position.button.state.clone(); let view = layout.get_current_view();
view.find_button_by_position(point)
.map(|place| place.button.state.clone())
};
if let Some(mut state) = state {
layout.press_key( layout.press_key(
&VirtualKeyboard(virtual_keyboard), &VirtualKeyboard(virtual_keyboard),
&mut state, &mut state,
@ -357,7 +326,7 @@ pub mod c {
); );
// maybe TODO: draw on the display buffer here // maybe TODO: draw on the display buffer here
drawing::queue_redraw(ui_keyboard); drawing::queue_redraw(ui_keyboard);
} };
} }
// FIXME: this will work funny // FIXME: this will work funny
@ -388,9 +357,7 @@ pub mod c {
place.map(|place| {( place.map(|place| {(
place.button.state.clone(), place.button.state.clone(),
place.button.clone(), place.button.clone(),
view.bounds.get_position() place.offset,
+ place.row.bounds.clone().unwrap().get_position()
+ place.button.bounds.get_position(),
)}) )})
}; };
@ -455,18 +422,12 @@ pub mod c {
} }
} }
/// Relative to `View`
struct ButtonPosition {
view_position: c::Point,
button: Button,
}
pub struct ButtonPlace<'a> { pub struct ButtonPlace<'a> {
button: &'a Button, button: &'a Button,
row: &'a Row, offset: c::Point,
} }
#[derive(Debug)] #[derive(Debug, Clone)]
pub struct Size { pub struct Size {
pub width: f64, pub width: f64,
pub height: f64, pub height: f64,
@ -487,9 +448,7 @@ pub struct Button {
pub name: CString, pub name: CString,
/// Label to display to the user /// Label to display to the user
pub label: Label, pub label: Label,
/// TODO: position the buttons before they get initial bounds pub size: Size,
/// Position relative to some origin (i.e. parent/row)
pub bounds: c::Bounds,
/// The name of the visual class applied /// The name of the visual class applied
pub outline_name: CString, pub outline_name: CString,
/// current state, shared with other buttons /// current state, shared with other buttons
@ -498,66 +457,39 @@ pub struct Button {
/// The graphical representation of a row of buttons /// The graphical representation of a row of buttons
pub struct Row { pub struct Row {
pub buttons: Vec<Box<Button>>, /// Buttons together with their offset from the left
pub buttons: Vec<(f64, Box<Button>)>,
/// Angle is not really used anywhere... /// Angle is not really used anywhere...
pub angle: i32, pub angle: i32,
/// Position relative to some origin (i.e. parent/view origin)
pub bounds: Option<c::Bounds>,
} }
impl Row { impl Row {
fn last(positions: &Vec<c::Bounds>) -> Option<&c::Bounds> { pub fn get_height(&self) -> f64 {
let len = positions.len(); find_max_double(
match len { self.buttons.iter(),
0 => None, |(_offset, button)| button.size.height,
l => Some(&positions[l - 1]) )
}
} }
fn calculate_button_positions(outlines: Vec<c::Bounds>) -> Vec<c::Bounds> { fn get_width(&self) -> f64 {
let mut x_offset = 0f64; self.buttons.iter().next_back()
outlines.iter().map(|outline| { .map(|(x_offset, button)| button.size.width + x_offset)
x_offset += outline.x; // account for offset outlines .unwrap_or(0.0)
let position = c::Bounds {
x: x_offset,
..outline.clone()
};
x_offset += outline.width;
position
}).collect()
}
fn calculate_row_size(positions: &Vec<c::Bounds>) -> 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 /// Finds the first button that covers the specified point
/// relative to row's position's origin /// relative to row's position's origin
fn find_button_by_position(&self, point: c::Point) fn find_button_by_position(&self, point: c::Point)
-> Option<&Box<Button>> -> Option<(f64, &Box<Button>)>
{ {
let row_bounds = self.bounds.as_ref().expect("Missing bounds on row"); self.buttons.iter().find(|(x_offset, button)| {
let origin = c::Point { c::Bounds {
x: row_bounds.x, x: *x_offset, y: 0.0,
y: row_bounds.y, width: button.size.width,
}; height: button.size.height,
let angle = self.angle; }.contains(&point)
self.buttons.iter().find(|button| {
let bounds = button.bounds.clone();
let point = point.clone();
let origin = origin.clone();
procedures::is_point_inside(bounds, point, origin, angle)
}) })
.map(|(x_offset, button)| (*x_offset, button))
} }
} }
@ -568,84 +500,56 @@ pub struct Spacing {
} }
pub struct View { pub struct View {
/// Position relative to keyboard origin /// Rows together with their offsets from the top
pub bounds: c::Bounds, rows: Vec<(f64, Row)>,
pub rows: Vec<Row>,
} }
impl View { impl View {
/// Determines the positions of rows based on their sizes pub fn new(rows: Vec<(f64, Row)>) -> View {
/// Each row will be centered horizontally View { rows }
/// The collection of rows will not be altered vertically
/// (TODO: maybe make view bounds a constraint,
/// and derive a scaling factor that lets contents fit into view)
/// (or TODO: blow up view bounds to match contents
/// and then scale the entire thing)
fn calculate_row_positions(&self, sizes: Vec<Size>) -> Vec<c::Bounds> {
let mut y_offset = self.bounds.y;
sizes.into_iter().map(|size| {
let position = c::Bounds {
x: (self.bounds.width - size.width) / 2f64,
y: y_offset,
width: size.width,
height: size.height,
};
y_offset += size.height;
position
}).collect()
} }
/// Uses button outline information to place all buttons and rows inside.
/// The view itself will not be affected by the sizes
fn place_buttons_with_sizes(
&mut self,
button_outlines: Vec<Vec<c::Bounds>>,
) {
// Determine all positions
let button_positions: Vec<_>
= button_outlines.into_iter()
.map(|outlines| {
Row::calculate_button_positions(outlines)
})
.collect();
let row_sizes = button_positions.iter()
.map(Row::calculate_row_size)
.collect();
let row_positions
= self.calculate_row_positions(row_sizes);
// Apply all positions
for ((mut row, row_position), button_positions)
in self.rows.iter_mut()
.zip(row_positions)
.zip(button_positions) {
row.bounds = Some(row_position);
for (mut button, button_position)
in row.buttons.iter_mut()
.zip(button_positions) {
button.bounds = button_position;
}
}
}
/// Finds the first button that covers the specified point /// Finds the first button that covers the specified point
/// relative to view's position's origin /// relative to view's position's origin
fn find_button_by_position(&self, point: c::Point) fn find_button_by_position(&self, point: c::Point)
-> Option<ButtonPlace> -> Option<ButtonPlace>
{ {
// make point relative to the inside of the view, self.get_rows().iter().find_map(|(row_offset, row)| {
// which is the origin of all rows // make point relative to the inside of the row
let point = c::Point { row.find_button_by_position({
x: point.x - self.bounds.x, c::Point { x: point.x, y: point.y } - row_offset
y: point.y - self.bounds.y, }).map(|(button_x_offset, button)| ButtonPlace {
}; button,
offset: row_offset + c::Point {
self.rows.iter().find_map(|row| { x: button_x_offset,
row.find_button_by_position(point.clone()) y: 0.0,
.map(|button| ButtonPlace {row, button}) },
}) })
})
}
fn get_width(&self) -> f64 {
// No need to call `get_rows()`,
// as the biggest row is the most far reaching in both directions
// because they are all centered.
find_max_double(self.rows.iter(), |(_offset, row)| row.get_width())
}
fn get_height(&self) -> f64 {
self.rows.iter().next_back()
.map(|(y_offset, row)| row.get_height() + y_offset)
.unwrap_or(0.0)
}
/// Returns positioned rows, with appropriate x offsets (centered)
pub fn get_rows(&self) -> Vec<(c::Point, &Row)> {
let available_width = self.get_width();
self.rows.iter().map(|(y_offset, row)| {(
c::Point {
x: (available_width - row.get_width()) / 2.0,
y: *y_offset,
},
row,
)}).collect()
} }
} }
@ -656,10 +560,18 @@ pub enum ArrangementKind {
Wide = 1, Wide = 1,
} }
pub struct Margins {
pub top: f64,
pub bottom: f64,
pub left: f64,
pub right: f64,
}
// TODO: split into sth like // TODO: split into sth like
// Arrangement (views) + details (keymap) + State (keys) // Arrangement (views) + details (keymap) + State (keys)
/// State of the UI, contains the backend as well /// State of the UI, contains the backend as well
pub struct Layout { pub struct Layout {
pub margins: Margins,
pub kind: ArrangementKind, pub kind: ArrangementKind,
pub current_view: String, pub current_view: String,
// Views own the actual buttons which have state // Views own the actual buttons which have state
@ -686,6 +598,7 @@ pub struct Layout {
pub struct LayoutData { pub struct LayoutData {
pub views: HashMap<String, View>, pub views: HashMap<String, View>,
pub keymap_str: CString, pub keymap_str: CString,
pub margins: Margins,
} }
struct NoSuchView; struct NoSuchView;
@ -703,6 +616,7 @@ impl Layout {
keymap_str: data.keymap_str, keymap_str: data.keymap_str,
pressed_keys: HashSet::new(), pressed_keys: HashSet::new(),
locked_keys: HashSet::new(), locked_keys: HashSet::new(),
margins: data.margins,
} }
} }
@ -799,54 +713,46 @@ impl Layout {
}; };
} }
fn get_button_at_point(&self, point: c::Point) -> Option<ButtonPosition> { fn calculate_size(&self) -> Size {
let view = self.get_current_view(); Size {
let place = view.find_button_by_position(point); height: find_max_double(
place.map(|place| ButtonPosition { self.views.iter(),
button: place.button.clone(), |(_name, view)| view.get_height(),
// Rows have no business being inside a view ),
// if they have no valid bounds. width: find_max_double(
view_position: place.row.bounds.clone().unwrap().get_position() self.views.iter(),
+ place.button.bounds.get_position(), |(_name, view)| view.get_width(),
}) ),
}
} }
} }
mod procedures { mod procedures {
use super::*; use super::*;
type Path<'v> = (&'v Row, &'v Box<Button>); type Place<'v> = (c::Point, &'v Box<Button>);
/// Finds all `(row, button)` paths that refer to the specified key `state` /// Finds all buttons referring to the key in `state`,
pub fn find_key_paths<'v, 's>( /// together with their offsets within the view.
pub fn find_key_places<'v, 's>(
view: &'v View, view: &'v View,
state: &'s Rc<RefCell<KeyState>> state: &'s Rc<RefCell<KeyState>>
) -> Vec<Path<'v>> { ) -> Vec<Place<'v>> {
view.rows.iter().flat_map(|row| { view.get_rows().iter().flat_map(|(row_offset, row)| {
let row_paths: Vec<Path> = row.buttons.iter().filter_map(|button| { row.buttons.iter()
.filter_map(move |(x_offset, button)| {
if Rc::ptr_eq(&button.state, state) { if Rc::ptr_eq(&button.state, state) {
Some((row, button)) Some((
row_offset + c::Point { x: *x_offset, y: 0.0 },
button,
))
} else { } else {
None None
} }
}).collect(); // collecting not to let row references outlive the function })
row_paths.into_iter()
}).collect() }).collect()
} }
/// Checks if point is inside bounds which are rotated by angle.
/// FIXME: what's origin about?
pub fn is_point_inside(
bounds: c::Bounds,
point: c::Point,
origin: c::Point,
angle: i32
) -> bool {
(unsafe {
c::procedures::eek_are_bounds_inside(bounds, point, origin, angle)
}) == 1
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
@ -868,62 +774,33 @@ mod procedures {
let button = make_button_with_state("1".into(), state); let button = make_button_with_state("1".into(), state);
let button_ptr = as_ptr(&button); let button_ptr = as_ptr(&button);
let row_bounds = Some(c::Bounds {
x: 0.1, y: 2.3,
width: 4.5, height: 6.7,
});
let row = Row { let row = Row {
buttons: vec!(button), buttons: vec!((0.1, button)),
angle: 0, angle: 0,
bounds: row_bounds.clone(),
}; };
let view = View { let view = View {
bounds: c::Bounds { rows: vec!((1.2, row)),
x: 0f64, y: 0f64,
width: 0f64, height: 0f64
},
rows: vec!(row),
}; };
assert_eq!( assert_eq!(
find_key_paths(&view, &state_clone.clone()).iter() find_key_places(&view, &state_clone.clone()).into_iter()
.map(|(row, button)| { (row.bounds.clone(), as_ptr(button)) }) .map(|(place, button)| { (place, as_ptr(button)) })
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
vec!( vec!(
(row_bounds, button_ptr) (c::Point { x: 0.1, y: 1.2 }, button_ptr)
) )
); );
let view = View { let view = View {
bounds: c::Bounds {
x: 0f64, y: 0f64,
width: 0f64, height: 0f64
},
rows: Vec::new(), rows: Vec::new(),
}; };
assert_eq!( assert_eq!(
find_key_paths(&view, &state_clone.clone()).is_empty(), find_key_places(&view, &state_clone.clone()).is_empty(),
true true
); );
} }
} }
pub fn get_button_bounds(
view: &View,
row: &Row,
button: &Button
) -> Option<c::Bounds> {
match &row.bounds {
Some(row) => Some(c::Bounds {
x: view.bounds.x + row.x + button.bounds.x,
y: view.bounds.y + row.y + button.bounds.y,
width: button.bounds.width,
height: button.bounds.height,
}),
_ => None,
}
}
} }
/// Top level procedures, dispatching to everything /// Top level procedures, dispatching to everything
@ -944,19 +821,18 @@ mod seat {
let view = layout.get_current_view(); let view = layout.get_current_view();
let action = RefCell::borrow(key).action.clone(); let action = RefCell::borrow(key).action.clone();
if let Action::ShowPreferences = action { if let Action::ShowPreferences = action {
let paths = ::layout::procedures::find_key_paths( let places = ::layout::procedures::find_key_places(
view, key view, key
); );
// getting first item will cause mispositioning // getting first item will cause mispositioning
// with more than one button with the same key // with more than one button with the same key
// on the keyboard // on the keyboard
if let Some((row, button)) = paths.get(0) { if let Some((offset, button)) = places.get(0) {
let bounds = ::layout::procedures::get_button_bounds( let bounds = c::Bounds {
view, row, button x: offset.x, y: offset.y,
).unwrap_or_else(|| { width: button.size.width,
eprintln!("BUG: Clicked button has no position?"); height: button.size.height,
c::Bounds { x: 0f64, y: 0f64, width: 0f64, height: 0f64 } };
});
::popover::show( ::popover::show(
ui_keyboard, ui_keyboard,
widget_to_layout.reverse_bounds(bounds) widget_to_layout.reverse_bounds(bounds)
@ -987,12 +863,48 @@ mod test {
) -> Box<Button> { ) -> Box<Button> {
Box::new(Button { Box::new(Button {
name: CString::new(name.clone()).unwrap(), name: CString::new(name.clone()).unwrap(),
bounds: c::Bounds { size: Size { width: 0f64, height: 0f64 },
x: 0f64, y: 0f64, width: 0f64, height: 0f64
},
outline_name: CString::new("test").unwrap(), outline_name: CString::new("test").unwrap(),
label: Label::Text(CString::new(name).unwrap()), label: Label::Text(CString::new(name).unwrap()),
state: state, state: state,
}) })
} }
#[test]
fn check_centering() {
// foo
// ---bar---
let view = View::new(vec![
(
0.0,
Row {
angle: 0,
buttons: vec![(
0.0,
Box::new(Button {
size: Size { width: 10.0, height: 10.0 },
..*make_button_with_state("foo".into(), make_state())
}),
)]
},
),
(
10.0,
Row {
angle: 0,
buttons: vec![(
0.0,
Box::new(Button {
size: Size { width: 30.0, height: 10.0 },
..*make_button_with_state("bar".into(), make_state())
}),
)]
},
)
]);
assert!(
view.find_button_by_position(c::Point { x: 5.0, y: 5.0 })
.is_none()
);
}
} }

View File

@ -56,8 +56,8 @@ fn check_layout(layout: Layout) {
// "Press" each button with keysyms // "Press" each button with keysyms
for view in layout.views.values() { for view in layout.views.values() {
for row in &view.rows { for (_y, row) in &view.get_rows() {
for button in &row.buttons { for (_x, button) in &row.buttons {
let keystate = button.state.borrow(); let keystate = button.state.borrow();
for keycode in &keystate.keycodes { for keycode in &keystate.keycodes {
match state.key_get_one_sym(*keycode) { match state.key_get_one_sym(*keycode) {

View File

@ -2,6 +2,8 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::rc::Rc; use std::rc::Rc;
use ::float_ord::FloatOrd;
use std::borrow::Borrow; use std::borrow::Borrow;
use std::hash::{ Hash, Hasher }; use std::hash::{ Hash, Hasher };
use std::iter::FromIterator; use std::iter::FromIterator;
@ -142,6 +144,16 @@ pub fn hash_map_map<K, V, F, K1, V1>(map: HashMap<K, V>, mut f: F)
) )
} }
pub fn find_max_double<T, I, F>(iterator: I, get: F)
-> f64
where I: Iterator<Item=T>,
F: Fn(&T) -> f64
{
iterator.map(|value| FloatOrd(get(&value)))
.max().unwrap_or(FloatOrd(0f64))
.0
}
/// Compares pointers but not internal values of Rc /// Compares pointers but not internal values of Rc
pub struct Pointer<T>(pub Rc<T>); pub struct Pointer<T>(pub Rc<T>);