rendering: Simplify Cairo context usage, remove unneeded calls.

Moved Cairo context usage to Rust, and rearranged ctx setup (position) to happen in one place.

Removed render calls that were overwritten on each draw call anyway.
This commit is contained in:
Dorota Czaplejewicz
2019-12-02 16:45:25 +00:00
parent 6e32a2ef41
commit f9fbd3fb2d
11 changed files with 310 additions and 204 deletions

154
src/drawing.rs Normal file
View File

@ -0,0 +1,154 @@
/*! Drawing the UI */
use cairo;
use std::cell::RefCell;
use ::keyboard;
use ::layout;
use ::layout::{ Button, Layout };
use ::layout::c::{ EekGtkKeyboard, Point };
use gdk::{ WindowExt, DrawingContextExt };
use glib::translate::FromGlibPtrNone;
use gtk::WidgetExt;
mod c {
use super::*;
use cairo_sys;
use std::os::raw::c_void;
// This is constructed only in C, no need for warnings
#[allow(dead_code)]
#[repr(transparent)]
#[derive(Clone, Copy)]
pub struct EekRenderer(*const c_void);
#[no_mangle]
extern "C" {
// Button and View inside CButtonPlace are safe to pass to C
// as long as they don't outlive the call
// and nothing dereferences them
#[allow(improper_ctypes)]
pub fn eek_render_button(
renderer: EekRenderer,
cr: *mut cairo_sys::cairo_t,
button: *const Button,
pressed: u64,
locked: u64,
);
pub fn eek_gtk_keyboard_get_renderer(
keyboard: EekGtkKeyboard,
) -> EekRenderer;
pub fn eek_renderer_get_transformation(
renderer: EekRenderer,
) -> layout::c::Transformation;
}
#[no_mangle]
pub extern "C"
fn squeek_layout_draw_all_changed(
layout: *mut Layout,
renderer: EekRenderer,
cr: *mut cairo_sys::cairo_t,
) {
let layout = unsafe { &mut *layout };
let cr = unsafe { cairo::Context::from_raw_none(cr) };
let view = layout.get_current_view();
let view_position = view.bounds.get_position();
for row in &view.rows {
for button in &row.buttons {
let state = RefCell::borrow(&button.state).clone();
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(
renderer, &cr,
position, button.as_ref(),
state.pressed, state.locked,
);
}
}
}
}
}
/// Renders a button at a position (button's own bounds ignored)
pub fn render_button_at_position(
renderer: c::EekRenderer,
cr: &cairo::Context,
position: Point,
button: &Button,
pressed: keyboard::PressType,
locked: bool,
) {
cr.save();
cr.translate(position.x, position.y);
cr.rectangle(
0.0, 0.0,
button.bounds.width, button.bounds.height
);
cr.clip();
unsafe {
c::eek_render_button(
renderer,
cairo::Context::to_raw_none(&cr),
button as *const Button,
pressed as u64,
locked as u64,
)
};
cr.restore();
}
pub fn queue_redraw(keyboard: EekGtkKeyboard) {
let widget = unsafe { gtk::Widget::from_glib_none(keyboard.0) };
widget.queue_draw();
}
/// Renders a single frame
/// Opens a frame on `keyboard`'s `GdkWindow`, attempt to get a drawing context,
/// calls `f`, closes the frame.
/// If the drawing context was successfully retrieved, returns `f` call result.
pub fn render_as_frame<F, T>(keyboard: EekGtkKeyboard, mut f: F) -> Option<T>
where F: FnMut(c::EekRenderer, &cairo::Context) -> T
{
let renderer = unsafe { c::eek_gtk_keyboard_get_renderer(keyboard) };
let widget = unsafe { gtk::Widget::from_glib_none(keyboard.0) };
widget.get_window()
.and_then(|window| {
// Need to split the `.and_then` chain here
// because `window` needs to be in scope
// for the references deeper inside.
window.get_clip_region()
// contrary to the docs, `Region` gets destroyed automatically
.and_then(|region| window.begin_draw_frame(&region))
.and_then(|drawctx| {
let ret: Option<T> = drawctx.get_cairo_context()
.map(|cr| {
let transformation = unsafe {
c::eek_renderer_get_transformation(renderer)
};
cr.translate(
transformation.origin_x,
transformation.origin_y,
);
cr.scale(
transformation.scale,
transformation.scale,
);
queue_redraw(keyboard);
f(renderer, &cr) // finally!
});
// This must always happen after `begin_draw_frame`,
// enven if `get_cairo_context` fails.
window.end_draw_frame(&drawctx);
ret
})
})
}

View File

@ -11,7 +11,7 @@ use ::action::Action;
use std::io::Write;
use std::iter::{ FromIterator, IntoIterator };
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum PressType {
Released = 0,
Pressed = 1,

View File

@ -5,6 +5,7 @@
#include <glib.h>
#include "eek/eek-element.h"
#include "eek/eek-gtk-keyboard.h"
#include "eek/eek-renderer.h"
#include "eek/eek-types.h"
#include "virtual-keyboard-unstable-v1-client-protocol.h"
@ -13,17 +14,9 @@ enum squeek_arrangement_kind {
ARRANGEMENT_KIND_WIDE = 1,
};
struct squeek_button;
struct squeek_row;
struct squeek_view;
struct squeek_layout;
/// Represents the path to the button within a view
struct button_place {
const struct squeek_row *row;
const struct squeek_button *button;
};
int32_t squeek_row_get_angle(const struct squeek_row*);
EekBounds squeek_row_get_bounds(const struct squeek_row*);
@ -71,5 +64,5 @@ void squeek_layout_drag(struct squeek_layout *layout, struct zwp_virtual_keyboar
double x_widget, double y_widget,
struct transformation widget_to_layout,
uint32_t timestamp, EekGtkKeyboard *ui_keyboard);
void squeek_layout_draw_all_changed(struct squeek_layout *layout, EekGtkKeyboard *ui_keyboard);
void squeek_layout_draw_all_changed(struct squeek_layout *layout, EekRenderer* renderer, cairo_t *cr);
#endif

View File

@ -24,6 +24,7 @@ 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 };
@ -34,10 +35,12 @@ use std::borrow::Borrow;
pub mod c {
use super::*;
use gtk_sys;
use std::ffi::CStr;
use std::os::raw::{ c_char, c_void };
use std::ptr;
use gtk_sys;
use std::ops::Add;
// The following defined in C
@ -55,6 +58,23 @@ pub mod c {
pub x: f64,
pub y: f64,
}
impl Add for Point {
type Output = Self;
fn add(self, other: Self) -> Self {
&self + other
}
}
impl Add<Point> 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)]
@ -66,6 +86,51 @@ pub mod c {
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,
}
}
}
type ButtonCallback = unsafe extern "C" fn(button: *mut ::layout::Button, data: *mut UserData);
type RowCallback = unsafe extern "C" fn(row: *mut ::layout::Row, data: *mut UserData);
@ -212,7 +277,7 @@ pub mod c {
use super::*;
use ::submission::c::ZwpVirtualKeyboardV1;
#[repr(C)]
#[derive(PartialEq, Debug)]
pub struct CButtonPlace {
@ -228,42 +293,6 @@ pub mod c {
}
}
}
/// Scale + translate
#[repr(C)]
pub struct Transformation {
origin_x: f64,
origin_y: f64,
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,
}
}
}
// This is constructed only in C, no need for warnings
#[allow(dead_code)]
@ -289,24 +318,6 @@ pub mod c {
view: *const View,
keyboard: EekGtkKeyboard,
);
// Button and View inside CButtonPlace are safe to pass to C
// as long as they don't outlive the call
// and nothing dereferences them
#[allow(improper_ctypes)]
pub fn eek_gtk_on_button_pressed(
place: CButtonPlace,
keyboard: EekGtkKeyboard,
);
// Button and View inside CButtonPlace are safe to pass to C
// as long as they don't outlive the call
// and nothing dereferences them
#[allow(improper_ctypes)]
pub fn eek_gtk_render_locked_button(
keyboard: EekGtkKeyboard,
place: CButtonPlace,
);
}
/// Places each button in order, starting from 0 on the left,
@ -346,15 +357,16 @@ pub mod c {
// because it will be mutated in the loop
for key in layout.pressed_keys.clone() {
let key: &Rc<RefCell<KeyState>> = key.borrow();
ui::release_key(
seat::handle_release_key(
layout,
&virtual_keyboard,
&widget_to_layout,
time,
ui_keyboard,
key
key,
);
}
drawing::queue_redraw(ui_keyboard);
}
/// Release all buittons but don't redraw
@ -393,27 +405,16 @@ pub mod c {
let point = widget_to_layout.forward(
Point { x: x_widget, y: y_widget }
);
// the immutable reference to `layout` through `view`
// must be dropped
// before `layout.press_key` borrows it mutably again
let state_place = {
let view = layout.get_current_view();
let place = view.find_button_by_position(point);
place.map(|place| {(
place.button.state.clone(),
place.into(),
)})
};
if let Some((mut state, c_place)) = state_place {
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),
);
unsafe { eek_gtk_on_button_pressed(c_place, ui_keyboard) };
// maybe TODO: draw on the display buffer here
drawing::queue_redraw(ui_keyboard);
}
}
@ -439,23 +440,26 @@ pub mod c {
);
let pressed = layout.pressed_keys.clone();
let state_place = {
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.into(),
place.button.clone(),
view.bounds.get_position()
+ place.row.bounds.clone().unwrap().get_position()
+ place.button.bounds.get_position(),
)})
};
if let Some((mut state, c_place)) = state_place {
if let Some((mut state, _button, _view_position)) = button_info {
let mut found = false;
for wrapped_key in pressed {
let key: &Rc<RefCell<KeyState>> = wrapped_key.borrow();
if Rc::ptr_eq(&state, &wrapped_key.0) {
found = true;
} else {
ui::release_key(
seat::handle_release_key(
layout,
&virtual_keyboard,
&widget_to_layout,
@ -467,12 +471,12 @@ pub mod c {
}
if !found {
layout.press_key(&virtual_keyboard, &mut state, time);
unsafe { eek_gtk_on_button_pressed(c_place, ui_keyboard) };
// maybe TODO: draw on the display buffer here
}
} else {
for wrapped_key in pressed {
let key: &Rc<RefCell<KeyState>> = wrapped_key.borrow();
ui::release_key(
seat::handle_release_key(
layout,
&virtual_keyboard,
&widget_to_layout,
@ -482,33 +486,7 @@ pub mod c {
);
}
}
}
#[no_mangle]
pub extern "C"
fn squeek_layout_draw_all_changed(
layout: *mut Layout,
ui_keyboard: EekGtkKeyboard,
) {
let layout = unsafe { &mut *layout };
for row in &layout.get_current_view().rows {
for button in &row.buttons {
let c_place = CButtonPlace::from(
ButtonPlace { row, button }
);
let state = RefCell::borrow(&button.state);
match (state.pressed, state.locked) {
(PressType::Released, false) => {}
(PressType::Pressed, _) => unsafe {
eek_gtk_on_button_pressed(c_place, ui_keyboard)
},
(_, true) => unsafe {
eek_gtk_render_locked_button(ui_keyboard, c_place)
},
}
}
}
drawing::queue_redraw(ui_keyboard);
}
#[cfg(test)]
@ -535,6 +513,12 @@ pub mod c {
}
}
/// Relative to `View`
struct ButtonPosition {
view_position: c::Point,
button: Button,
}
pub struct ButtonPlace<'a> {
button: &'a Button,
row: &'a Row,
@ -779,7 +763,8 @@ impl Layout {
locked_keys: HashSet::new(),
}
}
fn get_current_view(&self) -> &Box<View> {
pub fn get_current_view(&self) -> &Box<View> {
self.views.get(&self.current_view).expect("Selected nonexistent view")
}
@ -871,6 +856,18 @@ impl Layout {
};
};
}
fn get_button_at_point(&self, point: c::Point) -> Option<ButtonPosition> {
let view = self.get_current_view();
let place = view.find_button_by_position(point);
place.map(|place| ButtonPosition {
button: place.button.clone(),
// Rows have no business being inside a view
// if they have no valid bounds.
view_position: place.row.bounds.clone().unwrap().get_position()
+ place.button.bounds.get_position(),
})
}
}
mod procedures {
@ -1002,15 +999,15 @@ mod procedures {
}
}
/// Top level UI procedures
mod ui {
/// Top level procedures, dispatching to everything
mod seat {
use super::*;
// TODO: turn into release_button
pub fn release_key(
pub fn handle_release_key(
layout: &mut Layout,
virtual_keyboard: &VirtualKeyboard,
widget_to_layout: &c::procedures::Transformation,
widget_to_layout: &c::Transformation,
time: Timestamp,
ui_keyboard: c::EekGtkKeyboard,
key: &Rc<RefCell<KeyState>>,
@ -1040,6 +1037,7 @@ mod ui {
}
}
// TODO: move one level up; multiple buttons might have been released
procedures::release_ui_buttons(view, key, ui_keyboard);
}
}

View File

@ -1,5 +1,8 @@
#[macro_use]
extern crate bitflags;
extern crate cairo;
extern crate cairo_sys;
extern crate gdk;
extern crate gio;
extern crate glib;
extern crate glib_sys;
@ -14,6 +17,7 @@ extern crate xkbcommon;
mod action;
pub mod data;
mod drawing;
pub mod float_ord;
pub mod imservice;
mod keyboard;