mirror of https://github.com/wayvr-org/wayvr.git
411 lines
14 KiB
Rust
411 lines
14 KiB
Rust
use std::{collections::HashMap, rc::Rc, time::Duration};
|
|
|
|
use crate::{
|
|
app_misc,
|
|
gui::{
|
|
panel::{GuiPanel, NewGuiPanelParams},
|
|
timer::GuiTimer,
|
|
},
|
|
state::AppState,
|
|
subsystem::hid::XkbKeymap,
|
|
};
|
|
use anyhow::Context;
|
|
use glam::{FloatExt, Mat4, Vec2, vec2, vec3};
|
|
use wgui::{
|
|
animation::{Animation, AnimationEasing},
|
|
assets::AssetPath,
|
|
drawing::{self, Color},
|
|
event::{self, CallbackMetadata, EventAlterables, EventListenerKind},
|
|
layout::LayoutUpdateParams,
|
|
parser::{Fetchable, ParseDocumentParams},
|
|
renderer_vk::util,
|
|
taffy::{self, prelude::length},
|
|
widget::{EventResult, div::WidgetDiv, rectangle::WidgetRectangle},
|
|
};
|
|
|
|
use super::{
|
|
KeyButtonData, KeyState, KeyboardState, handle_press, handle_release,
|
|
layout::{self, KeyCapType},
|
|
};
|
|
|
|
const PIXELS_PER_UNIT: f32 = 60.;
|
|
|
|
fn new_doc_params(panel: &mut GuiPanel<KeyboardState>) -> ParseDocumentParams<'static> {
|
|
ParseDocumentParams {
|
|
globals: panel.layout.state.globals.clone(),
|
|
path: AssetPath::FileOrBuiltIn("gui/keyboard.xml"),
|
|
extra: panel.doc_extra.take().unwrap_or_default(),
|
|
}
|
|
}
|
|
|
|
#[allow(clippy::too_many_lines, clippy::significant_drop_tightening)]
|
|
pub(super) fn create_keyboard_panel(
|
|
app: &mut AppState,
|
|
keymap: Option<&XkbKeymap>,
|
|
state: KeyboardState,
|
|
layout: &layout::Layout,
|
|
) -> anyhow::Result<GuiPanel<KeyboardState>> {
|
|
let mut panel =
|
|
GuiPanel::new_from_template(app, "gui/keyboard.xml", state, NewGuiPanelParams::default())?;
|
|
|
|
let doc_params = new_doc_params(&mut panel);
|
|
|
|
let globals = app.wgui_globals.clone();
|
|
let accent_color = globals.get().defaults.accent_color;
|
|
|
|
let anim_mult = globals.defaults().animation_mult;
|
|
|
|
let root = panel
|
|
.parser_state
|
|
.get_widget_id("keyboard_root")
|
|
.context("Element with id 'keyboard_root' not found; keyboard.xml may be out of date.")?;
|
|
|
|
let has_altgr = keymap.as_ref().is_some_and(|m| XkbKeymap::has_altgr(m));
|
|
|
|
for row in 0..layout.key_sizes.len() {
|
|
let (div, _) = panel.layout.add_child(
|
|
root,
|
|
WidgetDiv::create(),
|
|
taffy::Style {
|
|
flex_direction: taffy::FlexDirection::Row,
|
|
..Default::default()
|
|
},
|
|
)?;
|
|
|
|
for col in 0..layout.key_sizes[row].len() {
|
|
let my_size_f32 = layout.key_sizes[row][col];
|
|
|
|
let key_width = PIXELS_PER_UNIT * my_size_f32;
|
|
let key_height = PIXELS_PER_UNIT;
|
|
|
|
let taffy_size = taffy::Size {
|
|
width: length(key_width),
|
|
height: length(PIXELS_PER_UNIT),
|
|
};
|
|
|
|
let Some(key) = layout.get_key_data(keymap, has_altgr, col, row) else {
|
|
let _ = panel.layout.add_child(
|
|
div.id,
|
|
WidgetDiv::create(),
|
|
taffy::Style {
|
|
size: taffy_size,
|
|
min_size: taffy_size,
|
|
max_size: taffy_size,
|
|
..Default::default()
|
|
},
|
|
)?;
|
|
continue;
|
|
};
|
|
|
|
let my_id: Rc<str> = Rc::from(format!("key-{row}-{col}"));
|
|
|
|
let my_modifier = match key.button_state {
|
|
KeyButtonData::Modifier { modifier, .. } => Some(modifier),
|
|
_ => None,
|
|
};
|
|
|
|
// todo: make this easier to maintain somehow
|
|
let mut params: HashMap<Rc<str>, Rc<str>> = HashMap::new();
|
|
params.insert(Rc::from("id"), my_id.clone());
|
|
params.insert(Rc::from("width"), Rc::from(key_width.to_string()));
|
|
params.insert(Rc::from("height"), Rc::from(key_height.to_string()));
|
|
|
|
let mut label = key.label.into_iter();
|
|
label
|
|
.next()
|
|
.and_then(|s| params.insert("text".into(), s.into()));
|
|
|
|
match key.cap_type {
|
|
KeyCapType::LetterAltGr => {
|
|
label
|
|
.next()
|
|
.and_then(|s| params.insert("text_altgr".into(), s.into()));
|
|
}
|
|
KeyCapType::Symbol => {
|
|
label
|
|
.next()
|
|
.and_then(|s| params.insert("text_shift".into(), s.into()));
|
|
}
|
|
KeyCapType::SymbolAltGr => {
|
|
label
|
|
.next()
|
|
.and_then(|s| params.insert("text_shift".into(), s.into()));
|
|
label
|
|
.next()
|
|
.and_then(|s| params.insert("text_altgr".into(), s.into()));
|
|
}
|
|
_ => {}
|
|
}
|
|
|
|
let template_key = format!("Key{:?}", key.cap_type);
|
|
panel.parser_state.instantiate_template(
|
|
&doc_params,
|
|
&template_key,
|
|
&mut panel.layout,
|
|
div.id,
|
|
params,
|
|
)?;
|
|
|
|
if let Ok(widget_id) = panel.parser_state.get_widget_id(&my_id) {
|
|
let key_state = {
|
|
let rect = panel
|
|
.layout
|
|
.state
|
|
.widgets
|
|
.get_as::<WidgetRectangle>(widget_id)
|
|
.unwrap(); // want panic
|
|
|
|
Rc::new(KeyState {
|
|
button_state: key.button_state,
|
|
color: rect.params.color,
|
|
color2: rect.params.color2,
|
|
border_color: rect.params.border_color,
|
|
border: rect.params.border,
|
|
drawn_state: false.into(),
|
|
})
|
|
};
|
|
|
|
let width_mul = 1. / my_size_f32;
|
|
|
|
panel.add_event_listener(
|
|
widget_id,
|
|
EventListenerKind::MouseEnter,
|
|
Box::new({
|
|
let k = key_state.clone();
|
|
move |common, data, _app, _state| {
|
|
common.alterables.trigger_haptics();
|
|
on_enter_anim(
|
|
k.clone(),
|
|
common,
|
|
data,
|
|
accent_color,
|
|
anim_mult,
|
|
width_mul,
|
|
);
|
|
Ok(EventResult::Pass)
|
|
}
|
|
}),
|
|
);
|
|
panel.add_event_listener(
|
|
widget_id,
|
|
EventListenerKind::MouseLeave,
|
|
Box::new({
|
|
let k = key_state.clone();
|
|
move |common, data, _app, _state| {
|
|
common.alterables.trigger_haptics();
|
|
on_leave_anim(
|
|
k.clone(),
|
|
common,
|
|
data,
|
|
accent_color,
|
|
anim_mult,
|
|
width_mul,
|
|
);
|
|
Ok(EventResult::Pass)
|
|
}
|
|
}),
|
|
);
|
|
panel.add_event_listener(
|
|
widget_id,
|
|
EventListenerKind::MousePress,
|
|
Box::new({
|
|
let k = key_state.clone();
|
|
move |common, data, app, state| {
|
|
let CallbackMetadata::MouseButton(button) = data.metadata else {
|
|
panic!("CallbackMetadata should contain MouseButton!");
|
|
};
|
|
|
|
handle_press(app, &k, state, button);
|
|
on_press_anim(k.clone(), common, data);
|
|
Ok(EventResult::Pass)
|
|
}
|
|
}),
|
|
);
|
|
panel.add_event_listener(
|
|
widget_id,
|
|
EventListenerKind::MouseRelease,
|
|
Box::new({
|
|
let k = key_state.clone();
|
|
move |common, data, app, state| {
|
|
if handle_release(app, &k, state) {
|
|
on_release_anim(k.clone(), common, data);
|
|
}
|
|
Ok(EventResult::Pass)
|
|
}
|
|
}),
|
|
);
|
|
|
|
if let Some(modifier) = my_modifier {
|
|
panel.add_event_listener(
|
|
widget_id,
|
|
EventListenerKind::InternalStateChange,
|
|
Box::new({
|
|
let k = key_state.clone();
|
|
move |common, data, _app, state| {
|
|
if (state.modifiers & modifier) != 0 {
|
|
on_press_anim(k.clone(), common, data);
|
|
} else {
|
|
on_release_anim(k.clone(), common, data);
|
|
}
|
|
Ok(EventResult::Pass)
|
|
}
|
|
}),
|
|
);
|
|
}
|
|
} else {
|
|
log::warn!("No ID for key at ({row}, {col})");
|
|
}
|
|
}
|
|
}
|
|
|
|
panel.on_notify = Some(Box::new(move |panel, app, event_data| {
|
|
let mut alterables = EventAlterables::default();
|
|
|
|
let mut elems_changed = panel.state.overlay_list.on_notify(
|
|
&mut panel.layout,
|
|
&mut panel.parser_state,
|
|
&event_data,
|
|
&mut alterables,
|
|
&doc_params,
|
|
)?;
|
|
|
|
elems_changed |= panel.state.set_list.on_notify(
|
|
&mut panel.layout,
|
|
&mut panel.parser_state,
|
|
&event_data,
|
|
&mut alterables,
|
|
&doc_params,
|
|
)?;
|
|
|
|
if elems_changed {
|
|
panel.process_custom_elems(app);
|
|
}
|
|
|
|
panel.layout.process_alterables(alterables)?;
|
|
Ok(())
|
|
}));
|
|
|
|
panel
|
|
.timers
|
|
.push(GuiTimer::new(Duration::from_millis(100), 0));
|
|
|
|
app_misc::process_layout_result(
|
|
app,
|
|
panel.layout.update(&mut LayoutUpdateParams {
|
|
size: vec2(2048., 2048.),
|
|
timestep_alpha: 0.0,
|
|
})?,
|
|
);
|
|
|
|
Ok(panel)
|
|
}
|
|
|
|
const BUTTON_HOVER_SCALE: f32 = 0.1;
|
|
|
|
fn get_anim_transform(pos: f32, widget_size: Vec2, width_mult: f32) -> Mat4 {
|
|
let scale = vec3(
|
|
(BUTTON_HOVER_SCALE * width_mult).mul_add(pos, 1.0),
|
|
BUTTON_HOVER_SCALE.mul_add(pos, 1.0),
|
|
1.0,
|
|
);
|
|
|
|
util::centered_matrix(widget_size, &Mat4::from_scale(scale))
|
|
}
|
|
|
|
fn set_anim_color(
|
|
key_state: &KeyState,
|
|
rect: &mut WidgetRectangle,
|
|
pos: f32,
|
|
accent_color: drawing::Color,
|
|
) {
|
|
// fade to accent color
|
|
rect.params.color.r = key_state.color.r.lerp(accent_color.r, pos);
|
|
rect.params.color.g = key_state.color.g.lerp(accent_color.g, pos);
|
|
rect.params.color.b = key_state.color.b.lerp(accent_color.b, pos);
|
|
|
|
// fade to accent color
|
|
rect.params.color2.r = key_state.color2.r.lerp(accent_color.r, pos);
|
|
rect.params.color2.g = key_state.color2.g.lerp(accent_color.g, pos);
|
|
rect.params.color2.b = key_state.color2.b.lerp(accent_color.b, pos);
|
|
|
|
// fade to white
|
|
rect.params.border_color.r = key_state.border_color.r.lerp(1.0, pos);
|
|
rect.params.border_color.g = key_state.border_color.g.lerp(1.0, pos);
|
|
rect.params.border_color.b = key_state.border_color.b.lerp(1.0, pos);
|
|
rect.params.border_color.a = key_state.border_color.a.lerp(1.0, pos);
|
|
|
|
rect.params.border = key_state.border.lerp(key_state.border * 1.5, pos);
|
|
}
|
|
|
|
fn on_enter_anim(
|
|
key_state: Rc<KeyState>,
|
|
common: &mut event::CallbackDataCommon,
|
|
data: &event::CallbackData,
|
|
accent_color: drawing::Color,
|
|
anim_mult: f32,
|
|
width_mult: f32,
|
|
) {
|
|
common.alterables.animate(Animation::new(
|
|
data.widget_id,
|
|
(10. * anim_mult) as _,
|
|
AnimationEasing::OutBack,
|
|
Box::new(move |common, data| {
|
|
let rect = data.obj.get_as_mut::<WidgetRectangle>().unwrap();
|
|
set_anim_color(&key_state, rect, data.pos, accent_color);
|
|
data.data.transform =
|
|
get_anim_transform(data.pos, data.widget_boundary.size, width_mult);
|
|
common.alterables.mark_redraw();
|
|
}),
|
|
));
|
|
}
|
|
|
|
fn on_leave_anim(
|
|
key_state: Rc<KeyState>,
|
|
common: &mut event::CallbackDataCommon,
|
|
data: &event::CallbackData,
|
|
accent_color: drawing::Color,
|
|
anim_mult: f32,
|
|
width_mult: f32,
|
|
) {
|
|
common.alterables.animate(Animation::new(
|
|
data.widget_id,
|
|
(15. * anim_mult) as _,
|
|
AnimationEasing::OutQuad,
|
|
Box::new(move |common, data| {
|
|
let rect = data.obj.get_as_mut::<WidgetRectangle>().unwrap();
|
|
set_anim_color(&key_state, rect, 1.0 - data.pos, accent_color);
|
|
data.data.transform =
|
|
get_anim_transform(1.0 - data.pos, data.widget_boundary.size, width_mult);
|
|
common.alterables.mark_redraw();
|
|
}),
|
|
));
|
|
}
|
|
|
|
fn on_press_anim(
|
|
key_state: Rc<KeyState>,
|
|
common: &mut event::CallbackDataCommon,
|
|
data: &mut event::CallbackData,
|
|
) {
|
|
if key_state.drawn_state.get() {
|
|
return;
|
|
}
|
|
let rect = data.obj.get_as_mut::<WidgetRectangle>().unwrap();
|
|
rect.params.border_color = Color::new(1.0, 1.0, 1.0, 1.0);
|
|
common.alterables.mark_redraw();
|
|
key_state.drawn_state.set(true);
|
|
}
|
|
|
|
fn on_release_anim(
|
|
key_state: Rc<KeyState>,
|
|
common: &mut event::CallbackDataCommon,
|
|
data: &mut event::CallbackData,
|
|
) {
|
|
if !key_state.drawn_state.get() {
|
|
return;
|
|
}
|
|
let rect = data.obj.get_as_mut::<WidgetRectangle>().unwrap();
|
|
rect.params.border_color = key_state.border_color;
|
|
common.alterables.mark_redraw();
|
|
key_state.drawn_state.set(false);
|
|
}
|