294 lines
8.1 KiB
Rust
294 lines
8.1 KiB
Rust
/*! Drawing the UI */
|
|
|
|
use cairo;
|
|
|
|
use crate::action::{ Action, Modifier };
|
|
use crate::keyboard;
|
|
use crate::layout::{ Button, ButtonPosition, Label, LatchedState, Layout };
|
|
use crate::layout::c::{ Bounds, EekGtkKeyboard, Point };
|
|
use crate::submission::c::Submission as CSubmission;
|
|
|
|
use glib::translate::FromGlibPtrNone;
|
|
use gtk::prelude::WidgetExt;
|
|
|
|
use std::collections::HashSet;
|
|
use std::ffi::CStr;
|
|
use std::ptr;
|
|
|
|
mod c {
|
|
use super::*;
|
|
|
|
use cairo_sys;
|
|
use std::os::raw::{ c_char, 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);
|
|
|
|
// This is constructed only in C, no need for warnings
|
|
/// Just don't clone this for no reason.
|
|
#[allow(dead_code)]
|
|
#[repr(transparent)]
|
|
#[derive(Clone, Copy)]
|
|
pub struct GtkStyleContext(*const c_void);
|
|
|
|
|
|
extern "C" {
|
|
#[allow(improper_ctypes)]
|
|
pub fn eek_renderer_get_scale_factor(
|
|
renderer: EekRenderer,
|
|
) -> u32;
|
|
|
|
#[allow(improper_ctypes)]
|
|
pub fn eek_render_button_in_context(
|
|
scale_factor: u32,
|
|
cr: *mut cairo_sys::cairo_t,
|
|
ctx: GtkStyleContext,
|
|
bounds: Bounds,
|
|
icon_name: *const c_char,
|
|
label: *const c_char,
|
|
);
|
|
|
|
#[allow(improper_ctypes)]
|
|
pub fn eek_get_style_context_for_button(
|
|
renderer: EekRenderer,
|
|
name: *const c_char,
|
|
outline_name: *const c_char,
|
|
locked_class: *const c_char,
|
|
pressed: u64,
|
|
) -> GtkStyleContext;
|
|
|
|
#[allow(improper_ctypes)]
|
|
pub fn eek_put_style_context_for_button(
|
|
ctx: GtkStyleContext,
|
|
outline_name: *const c_char,
|
|
locked_class: *const c_char,
|
|
);
|
|
}
|
|
|
|
/// Draws all buttons that are not in the base state
|
|
#[no_mangle]
|
|
pub extern "C"
|
|
fn squeek_layout_draw_all_changed(
|
|
layout: *mut Layout,
|
|
renderer: EekRenderer,
|
|
cr: *mut cairo_sys::cairo_t,
|
|
submission: CSubmission,
|
|
) {
|
|
let layout = unsafe { &mut *layout };
|
|
let submission = submission.clone_ref();
|
|
let submission = submission.borrow();
|
|
let cr = unsafe { cairo::Context::from_raw_none(cr) };
|
|
let active_modifiers = submission.get_active_modifiers();
|
|
|
|
layout.foreach_visible_button(|offset, button, (row, position_in_row)| {
|
|
// TODO: this iterator copies string indices way too much.
|
|
// For efficiency, it would be better to draw pressed buttons from the list first,
|
|
// and then iterate the rest without having to look up their indices.
|
|
let state = layout.state.active_buttons.get(&ButtonPosition {
|
|
view: layout.state.current_view.clone(),
|
|
row,
|
|
position_in_row,
|
|
});
|
|
|
|
let locked = LockedStyle::from_action(
|
|
&button.action,
|
|
&active_modifiers,
|
|
layout.get_view_latched(),
|
|
&layout.state.current_view,
|
|
);
|
|
if state.pressed == keyboard::PressType::Pressed
|
|
|| locked != LockedStyle::Free
|
|
{
|
|
render_button_at_position(
|
|
renderer, &cr,
|
|
offset,
|
|
button,
|
|
state.pressed, locked,
|
|
);
|
|
}
|
|
})
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "C"
|
|
fn squeek_draw_layout_base_view(
|
|
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) };
|
|
|
|
layout.foreach_visible_button(|offset, button, _index| {
|
|
render_button_at_position(
|
|
renderer, &cr,
|
|
offset,
|
|
button,
|
|
keyboard::PressType::Released,
|
|
LockedStyle::Free,
|
|
);
|
|
})
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, PartialEq, Debug)]
|
|
enum LockedStyle {
|
|
Free,
|
|
Latched,
|
|
Locked,
|
|
}
|
|
|
|
impl LockedStyle {
|
|
fn from_action(
|
|
action: &Action,
|
|
mods: &HashSet<Modifier>,
|
|
latched_view: &LatchedState,
|
|
current_view: &str,
|
|
) -> LockedStyle {
|
|
let active_mod = match action {
|
|
Action::ApplyModifier(m) => mods.contains(m),
|
|
_ => false,
|
|
};
|
|
|
|
let active_view = action.is_active(current_view);
|
|
let latched_button = match latched_view {
|
|
LatchedState::Not => false,
|
|
LatchedState::FromView(view) => !action.has_locked_appearance_from(view),
|
|
};
|
|
match (active_mod, active_view, latched_button) {
|
|
// Modifiers don't latch.
|
|
(true, _, _) => LockedStyle::Locked,
|
|
(false, true, false) => LockedStyle::Locked,
|
|
(false, true, true) => LockedStyle::Latched,
|
|
_ => LockedStyle::Free,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Renders a button at a position (button's own bounds ignored)
|
|
fn render_button_at_position(
|
|
renderer: c::EekRenderer,
|
|
cr: &cairo::Context,
|
|
position: Point,
|
|
button: &Button,
|
|
pressed: keyboard::PressType,
|
|
locked: LockedStyle,
|
|
) {
|
|
cr.save().unwrap();
|
|
cr.translate(position.x, position.y);
|
|
cr.rectangle(
|
|
0.0, 0.0,
|
|
button.size.width, button.size.height
|
|
);
|
|
cr.clip();
|
|
|
|
let scale_factor = unsafe {
|
|
c::eek_renderer_get_scale_factor(renderer)
|
|
};
|
|
let bounds = button.get_bounds();
|
|
let (label_c, icon_name_c) = match &button.label {
|
|
Label::Text(text) => (text.as_ptr(), ptr::null()),
|
|
Label::IconName(name) => {
|
|
let l = 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")
|
|
};
|
|
(l.as_ptr(), name.as_ptr())
|
|
},
|
|
};
|
|
|
|
with_button_context(
|
|
renderer,
|
|
button,
|
|
pressed,
|
|
locked,
|
|
|ctx| unsafe {
|
|
// TODO: split into separate procedures:
|
|
// draw outline, draw label, draw icon.
|
|
c::eek_render_button_in_context(
|
|
scale_factor,
|
|
cairo::Context::to_raw_none(&cr),
|
|
*ctx,
|
|
bounds,
|
|
icon_name_c,
|
|
label_c,
|
|
)
|
|
}
|
|
);
|
|
|
|
cr.restore().unwrap();
|
|
}
|
|
|
|
fn with_button_context<R, F: FnOnce(&c::GtkStyleContext) -> R>(
|
|
renderer: c::EekRenderer,
|
|
button: &Button,
|
|
pressed: keyboard::PressType,
|
|
locked: LockedStyle,
|
|
operation: F,
|
|
) -> R {
|
|
let outline_name_c = button.outline_name.as_ptr();
|
|
let locked_class_c = match locked {
|
|
LockedStyle::Free => ptr::null(),
|
|
LockedStyle::Locked => unsafe {
|
|
CStr::from_bytes_with_nul_unchecked(b"locked\0").as_ptr()
|
|
},
|
|
LockedStyle::Latched => unsafe {
|
|
CStr::from_bytes_with_nul_unchecked(b"latched\0").as_ptr()
|
|
},
|
|
};
|
|
|
|
let ctx = unsafe {
|
|
c::eek_get_style_context_for_button(
|
|
renderer,
|
|
button.name.as_ptr(),
|
|
outline_name_c,
|
|
locked_class_c,
|
|
pressed as u64,
|
|
)
|
|
};
|
|
|
|
let r = operation(&ctx);
|
|
|
|
unsafe {
|
|
c::eek_put_style_context_for_button(
|
|
ctx,
|
|
outline_name_c,
|
|
locked_class_c,
|
|
)
|
|
};
|
|
|
|
r
|
|
}
|
|
|
|
pub fn queue_redraw(keyboard: EekGtkKeyboard) {
|
|
let widget = unsafe { gtk::Widget::from_glib_none(keyboard.0) };
|
|
widget.queue_draw();
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_exit_only() {
|
|
assert_eq!(
|
|
LockedStyle::from_action(
|
|
&Action::LockView {
|
|
lock: "ab".into(),
|
|
unlock: "a".into(),
|
|
latches: true,
|
|
looks_locked_from: vec!["b".into()],
|
|
},
|
|
&HashSet::new(),
|
|
&LatchedState::FromView("b".into()),
|
|
"ab",
|
|
),
|
|
LockedStyle::Locked,
|
|
);
|
|
}
|
|
}
|