left, right & middle-specific press-release actions on buttons
This commit is contained in:
@@ -81,6 +81,11 @@ Supported events:
|
||||
<button _press="..." _release="..." />
|
||||
```
|
||||
|
||||
Laser-color-specific variants are also available:
|
||||
- `_press_left` & `_release_left` for blue laser
|
||||
- `_press_right` & `_release_right` for orange laser
|
||||
- `_press_middle` & `_release_middle` for purple laser
|
||||
|
||||
#### Supported button actions
|
||||
|
||||
##### `::ShellExec <command> [args ..]`
|
||||
|
||||
@@ -10,7 +10,9 @@ use std::{
|
||||
use anyhow::Context;
|
||||
use wgui::{
|
||||
components::button::ComponentButton,
|
||||
event::{self, EventCallback, EventListenerKind},
|
||||
event::{
|
||||
self, CallbackData, CallbackMetadata, EventCallback, EventListenerKind, MouseButtonIndex,
|
||||
},
|
||||
i18n::Translation,
|
||||
layout::Layout,
|
||||
parser::CustomAttribsInfoOwned,
|
||||
@@ -31,18 +33,72 @@ use crate::{
|
||||
#[cfg(feature = "wayvr")]
|
||||
use crate::backend::wayvr::WayVRAction;
|
||||
|
||||
pub const BUTTON_EVENTS: [(&str, EventListenerKind); 2] = [
|
||||
("_press", EventListenerKind::MousePress),
|
||||
("_release", EventListenerKind::MouseRelease),
|
||||
pub const BUTTON_EVENTS: [(&str, EventListenerKind, fn(&mut CallbackData) -> bool); 8] = [
|
||||
("_press", EventListenerKind::MousePress, any_button),
|
||||
("_release", EventListenerKind::MouseRelease, any_button),
|
||||
("_press_left", EventListenerKind::MousePress, left_button),
|
||||
(
|
||||
"_release_left",
|
||||
EventListenerKind::MouseRelease,
|
||||
left_button,
|
||||
),
|
||||
("_press_right", EventListenerKind::MousePress, right_button),
|
||||
(
|
||||
"_release_right",
|
||||
EventListenerKind::MouseRelease,
|
||||
right_button,
|
||||
),
|
||||
(
|
||||
"_press_middle",
|
||||
EventListenerKind::MousePress,
|
||||
middle_button,
|
||||
),
|
||||
(
|
||||
"_release_middle",
|
||||
EventListenerKind::MouseRelease,
|
||||
middle_button,
|
||||
),
|
||||
];
|
||||
|
||||
fn any_button(_: &mut CallbackData) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn left_button(data: &mut CallbackData) -> bool {
|
||||
if let CallbackMetadata::MouseButton(b) = data.metadata
|
||||
&& let MouseButtonIndex::Left = b.index
|
||||
{
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
fn right_button(data: &mut CallbackData) -> bool {
|
||||
if let CallbackMetadata::MouseButton(b) = data.metadata
|
||||
&& let MouseButtonIndex::Right = b.index
|
||||
{
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
fn middle_button(data: &mut CallbackData) -> bool {
|
||||
if let CallbackMetadata::MouseButton(b) = data.metadata
|
||||
&& let MouseButtonIndex::Middle = b.index
|
||||
{
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn setup_custom_button<S: 'static>(
|
||||
layout: &mut Layout,
|
||||
attribs: &CustomAttribsInfoOwned,
|
||||
_app: &AppState,
|
||||
button: Rc<ComponentButton>,
|
||||
) {
|
||||
for (name, kind) in &BUTTON_EVENTS {
|
||||
for (name, kind, test_btn) in &BUTTON_EVENTS {
|
||||
let Some(action) = attribs.get_value(name) else {
|
||||
continue;
|
||||
};
|
||||
@@ -54,7 +110,11 @@ pub(super) fn setup_custom_button<S: 'static>(
|
||||
|
||||
let callback: EventCallback<AppState, S> = match command {
|
||||
#[cfg(feature = "wayvr")]
|
||||
"::DashToggle" => Box::new(move |_common, _data, app, _| {
|
||||
"::DashToggle" => Box::new(move |_common, data, app, _| {
|
||||
if !test_btn(data) {
|
||||
return Ok(EventResult::Pass);
|
||||
}
|
||||
|
||||
app.tasks
|
||||
.enqueue(TaskType::WayVR(WayVRAction::ToggleDashboard));
|
||||
Ok(EventResult::Consumed)
|
||||
@@ -65,7 +125,11 @@ pub(super) fn setup_custom_button<S: 'static>(
|
||||
log::error!("{command} has invalid argument: \"{arg}\"");
|
||||
return;
|
||||
};
|
||||
Box::new(move |_common, _data, app, _| {
|
||||
Box::new(move |_common, data, app, _| {
|
||||
if !test_btn(data) {
|
||||
return Ok(EventResult::Pass);
|
||||
}
|
||||
|
||||
app.tasks
|
||||
.enqueue(TaskType::Overlay(OverlayTask::ToggleSet(set_idx)));
|
||||
Ok(EventResult::Consumed)
|
||||
@@ -77,7 +141,11 @@ pub(super) fn setup_custom_button<S: 'static>(
|
||||
return;
|
||||
};
|
||||
|
||||
Box::new(move |_common, _data, app, _| {
|
||||
Box::new(move |_common, data, app, _| {
|
||||
if !test_btn(data) {
|
||||
return Ok(EventResult::Pass);
|
||||
}
|
||||
|
||||
app.tasks.enqueue(TaskType::Overlay(OverlayTask::Modify(
|
||||
OverlaySelector::Name(arg.clone()),
|
||||
Box::new(move |app, owc| {
|
||||
@@ -91,13 +159,21 @@ pub(super) fn setup_custom_button<S: 'static>(
|
||||
Ok(EventResult::Consumed)
|
||||
})
|
||||
}
|
||||
"::EditToggle" => Box::new(move |_common, _data, app, _| {
|
||||
"::EditToggle" => Box::new(move |_common, data, app, _| {
|
||||
if !test_btn(data) {
|
||||
return Ok(EventResult::Pass);
|
||||
}
|
||||
|
||||
app.tasks
|
||||
.enqueue(TaskType::Overlay(OverlayTask::ToggleEditMode));
|
||||
Ok(EventResult::Consumed)
|
||||
}),
|
||||
#[cfg(feature = "wayland")]
|
||||
"::NewMirror" => Box::new(move |_common, _data, app, _| {
|
||||
"::NewMirror" => Box::new(move |_common, data, app, _| {
|
||||
if !test_btn(data) {
|
||||
return Ok(EventResult::Pass);
|
||||
}
|
||||
|
||||
let name = crate::overlays::mirror::new_mirror_name();
|
||||
app.tasks.enqueue(TaskType::Overlay(OverlayTask::Create(
|
||||
OverlaySelector::Name(name.clone()),
|
||||
@@ -107,21 +183,37 @@ pub(super) fn setup_custom_button<S: 'static>(
|
||||
)));
|
||||
Ok(EventResult::Consumed)
|
||||
}),
|
||||
"::CleanupMirrors" => Box::new(move |_common, _data, app, _| {
|
||||
"::CleanupMirrors" => Box::new(move |_common, data, app, _| {
|
||||
if !test_btn(data) {
|
||||
return Ok(EventResult::Pass);
|
||||
}
|
||||
|
||||
app.tasks
|
||||
.enqueue(TaskType::Overlay(OverlayTask::CleanupMirrors));
|
||||
Ok(EventResult::Consumed)
|
||||
}),
|
||||
"::PlayspaceReset" => Box::new(move |_common, _data, app, _| {
|
||||
"::PlayspaceReset" => Box::new(move |_common, data, app, _| {
|
||||
if !test_btn(data) {
|
||||
return Ok(EventResult::Pass);
|
||||
}
|
||||
|
||||
app.tasks.enqueue(TaskType::Playspace(PlayspaceTask::Reset));
|
||||
Ok(EventResult::Consumed)
|
||||
}),
|
||||
"::PlayspaceRecenter" => Box::new(move |_common, _data, app, _| {
|
||||
"::PlayspaceRecenter" => Box::new(move |_common, data, app, _| {
|
||||
if !test_btn(data) {
|
||||
return Ok(EventResult::Pass);
|
||||
}
|
||||
|
||||
app.tasks
|
||||
.enqueue(TaskType::Playspace(PlayspaceTask::Recenter));
|
||||
Ok(EventResult::Consumed)
|
||||
}),
|
||||
"::PlayspaceFixFloor" => Box::new(move |_common, _data, app, _| {
|
||||
"::PlayspaceFixFloor" => Box::new(move |_common, data, app, _| {
|
||||
if !test_btn(data) {
|
||||
return Ok(EventResult::Pass);
|
||||
}
|
||||
|
||||
for i in 0..5 {
|
||||
Toast::new(
|
||||
ToastTopic::System,
|
||||
@@ -138,7 +230,11 @@ pub(super) fn setup_custom_button<S: 'static>(
|
||||
);
|
||||
Ok(EventResult::Consumed)
|
||||
}),
|
||||
"::Shutdown" => Box::new(move |_common, _data, _app, _| {
|
||||
"::Shutdown" => Box::new(move |_common, data, _app, _| {
|
||||
if !test_btn(data) {
|
||||
return Ok(EventResult::Pass);
|
||||
}
|
||||
|
||||
RUNNING.store(false, Ordering::Relaxed);
|
||||
Ok(EventResult::Consumed)
|
||||
}),
|
||||
@@ -155,7 +251,11 @@ pub(super) fn setup_custom_button<S: 'static>(
|
||||
log::error!("{command} has bad/missing arguments");
|
||||
return;
|
||||
};
|
||||
Box::new(move |_common, _data, app, _| {
|
||||
Box::new(move |_common, data, app, _| {
|
||||
if !test_btn(data) {
|
||||
return Ok(EventResult::Pass);
|
||||
}
|
||||
|
||||
app.hid_provider.send_key_routed(key, down);
|
||||
Ok(EventResult::Consumed)
|
||||
})
|
||||
@@ -182,7 +282,11 @@ pub(super) fn setup_custom_button<S: 'static>(
|
||||
}),
|
||||
);
|
||||
|
||||
Box::new(move |_common, _data, _app, _| {
|
||||
Box::new(move |_common, data, _app, _| {
|
||||
if !test_btn(data) {
|
||||
return Ok(EventResult::Pass);
|
||||
}
|
||||
|
||||
let _ = shell_on_action(&state).inspect_err(|e| log::error!("{e:?}"));
|
||||
Ok(EventResult::Consumed)
|
||||
})
|
||||
@@ -206,7 +310,11 @@ pub(super) fn setup_custom_button<S: 'static>(
|
||||
osc_args.push(osc_arg);
|
||||
}
|
||||
|
||||
Box::new(move |_common, _data, app, _| {
|
||||
Box::new(move |_common, data, app, _| {
|
||||
if !test_btn(data) {
|
||||
return Ok(EventResult::Pass);
|
||||
}
|
||||
|
||||
let Some(sender) = app.osc_sender.as_mut() else {
|
||||
log::error!("OscSend: sender is not available.");
|
||||
return Ok(EventResult::Consumed);
|
||||
|
||||
@@ -283,7 +283,7 @@ fn make_edit_panel(app: &mut AppState) -> anyhow::Result<EditModeWrapPanel> {
|
||||
};
|
||||
|
||||
let on_custom_attrib: OnCustomAttribFunc = Box::new(move |layout, attribs, _app| {
|
||||
for (name, kind) in &BUTTON_EVENTS {
|
||||
for (name, kind, test_btn) in &BUTTON_EVENTS {
|
||||
let Some(action) = attribs.get_value(name) else {
|
||||
continue;
|
||||
};
|
||||
@@ -294,14 +294,22 @@ fn make_edit_panel(app: &mut AppState) -> anyhow::Result<EditModeWrapPanel> {
|
||||
};
|
||||
|
||||
let callback: EventCallback<AppState, EditModeState> = match command {
|
||||
"::EditModeToggleLock" => Box::new(move |common, _data, app, state| {
|
||||
"::EditModeToggleLock" => Box::new(move |common, data, app, state| {
|
||||
if !test_btn(data) {
|
||||
return Ok(EventResult::Pass);
|
||||
}
|
||||
|
||||
let sel = OverlaySelector::Id(*state.id.borrow());
|
||||
let task = state.lock.toggle(common, app);
|
||||
app.tasks
|
||||
.enqueue(TaskType::Overlay(OverlayTask::Modify(sel, task)));
|
||||
Ok(EventResult::Consumed)
|
||||
}),
|
||||
"::EditModeToggleGrab" => Box::new(move |_common, _data, app, state| {
|
||||
"::EditModeToggleGrab" => Box::new(move |_common, data, app, state| {
|
||||
if !test_btn(data) {
|
||||
return Ok(EventResult::Pass);
|
||||
}
|
||||
|
||||
let sel = OverlaySelector::Id(*state.id.borrow());
|
||||
app.tasks.enqueue(TaskType::Overlay(OverlayTask::Modify(
|
||||
sel,
|
||||
@@ -314,14 +322,22 @@ fn make_edit_panel(app: &mut AppState) -> anyhow::Result<EditModeWrapPanel> {
|
||||
}),
|
||||
"::EditModeTab" => {
|
||||
let tab_name = args.next().unwrap().to_owned();
|
||||
Box::new(move |common, _data, _app, state| {
|
||||
Box::new(move |common, data, _app, state| {
|
||||
if !test_btn(data) {
|
||||
return Ok(EventResult::Pass);
|
||||
}
|
||||
|
||||
state.tabs.tab_button_clicked(common, &tab_name);
|
||||
Ok(EventResult::Consumed)
|
||||
})
|
||||
}
|
||||
"::EditModeSetPos" => {
|
||||
let key = args.next().unwrap().to_owned();
|
||||
Box::new(move |common, _data, app, state| {
|
||||
Box::new(move |common, data, app, state| {
|
||||
if !test_btn(data) {
|
||||
return Ok(EventResult::Pass);
|
||||
}
|
||||
|
||||
let sel = OverlaySelector::Id(*state.id.borrow());
|
||||
let task = state.pos.button_clicked(common, &key);
|
||||
app.tasks
|
||||
@@ -331,7 +347,11 @@ fn make_edit_panel(app: &mut AppState) -> anyhow::Result<EditModeWrapPanel> {
|
||||
}
|
||||
"::EditModeSetStereo" => {
|
||||
let key = args.next().unwrap().to_owned();
|
||||
Box::new(move |common, _data, app, state| {
|
||||
Box::new(move |common, data, app, state| {
|
||||
if !test_btn(data) {
|
||||
return Ok(EventResult::Pass);
|
||||
}
|
||||
|
||||
let sel = OverlaySelector::Id(*state.id.borrow());
|
||||
let task = state.stereo.button_clicked(common, &key);
|
||||
app.tasks
|
||||
@@ -341,7 +361,11 @@ fn make_edit_panel(app: &mut AppState) -> anyhow::Result<EditModeWrapPanel> {
|
||||
}
|
||||
"::EditModeSetMouse" => {
|
||||
let key = args.next().unwrap().to_owned();
|
||||
Box::new(move |common, _data, app, state| {
|
||||
Box::new(move |common, data, app, state| {
|
||||
if !test_btn(data) {
|
||||
return Ok(EventResult::Pass);
|
||||
}
|
||||
|
||||
let sel = OverlaySelector::Id(*state.id.borrow());
|
||||
let task = state.mouse.button_clicked(common, &key);
|
||||
app.tasks
|
||||
@@ -349,12 +373,20 @@ fn make_edit_panel(app: &mut AppState) -> anyhow::Result<EditModeWrapPanel> {
|
||||
Ok(EventResult::Consumed)
|
||||
})
|
||||
}
|
||||
"::EditModeDeletePress" => Box::new(move |_common, _data, _app, state| {
|
||||
"::EditModeDeletePress" => Box::new(move |_common, data, _app, state| {
|
||||
if !test_btn(data) {
|
||||
return Ok(EventResult::Pass);
|
||||
}
|
||||
|
||||
state.delete.pressed = Instant::now();
|
||||
// TODO: animate to light up button after 2s
|
||||
Ok(EventResult::Consumed)
|
||||
}),
|
||||
"::EditModeDeleteRelease" => Box::new(move |_common, _data, app, state| {
|
||||
"::EditModeDeleteRelease" => Box::new(move |_common, data, app, state| {
|
||||
if !test_btn(data) {
|
||||
return Ok(EventResult::Pass);
|
||||
}
|
||||
|
||||
if state.delete.pressed.elapsed() < Duration::from_secs(1) {
|
||||
return Ok(EventResult::Pass);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ use std::{
|
||||
};
|
||||
|
||||
use glam::{Affine3A, Quat, Vec3, Vec3A, vec3};
|
||||
use idmap::{DirectIdMap, ordered::Keys};
|
||||
use idmap::DirectIdMap;
|
||||
use slotmap::SecondaryMap;
|
||||
use wgui::{
|
||||
components::button::ComponentButton,
|
||||
@@ -79,7 +79,7 @@ pub fn create_watch(app: &mut AppState) -> anyhow::Result<OverlayWindowConfig> {
|
||||
let state = WatchState::default();
|
||||
|
||||
let on_custom_attrib: OnCustomAttribFunc = Box::new(move |layout, attribs, _app| {
|
||||
for (name, kind) in &BUTTON_EVENTS {
|
||||
for (name, kind, test_btn) in &BUTTON_EVENTS {
|
||||
let Some(action) = attribs.get_value(name) else {
|
||||
continue;
|
||||
};
|
||||
@@ -90,11 +90,19 @@ pub fn create_watch(app: &mut AppState) -> anyhow::Result<OverlayWindowConfig> {
|
||||
};
|
||||
|
||||
let callback: EventCallback<AppState, WatchState> = match command {
|
||||
"::EditModeDeleteDown" => Box::new(move |_common, _data, _app, state| {
|
||||
"::EditModeDeleteDown" => Box::new(move |_common, data, _app, state| {
|
||||
if !test_btn(data) {
|
||||
return Ok(EventResult::Pass);
|
||||
}
|
||||
|
||||
state.delete.pressed = Instant::now();
|
||||
Ok(EventResult::Consumed)
|
||||
}),
|
||||
"::EditModeDeleteUp" => Box::new(move |_common, _data, app, state| {
|
||||
"::EditModeDeleteUp" => Box::new(move |_common, data, app, state| {
|
||||
if !test_btn(data) {
|
||||
return Ok(EventResult::Pass);
|
||||
}
|
||||
|
||||
if state.delete.pressed.elapsed() < Duration::from_secs(1) {
|
||||
return Ok(EventResult::Consumed);
|
||||
}
|
||||
@@ -102,7 +110,11 @@ pub fn create_watch(app: &mut AppState) -> anyhow::Result<OverlayWindowConfig> {
|
||||
.enqueue(TaskType::Overlay(OverlayTask::DeleteActiveSet));
|
||||
Ok(EventResult::Consumed)
|
||||
}),
|
||||
"::EditModeAddSet" => Box::new(move |_common, _data, app, _state| {
|
||||
"::EditModeAddSet" => Box::new(move |_common, data, app, _state| {
|
||||
if !test_btn(data) {
|
||||
return Ok(EventResult::Pass);
|
||||
}
|
||||
|
||||
app.tasks.enqueue(TaskType::Overlay(OverlayTask::AddSet));
|
||||
Ok(EventResult::Consumed)
|
||||
}),
|
||||
@@ -112,7 +124,11 @@ pub fn create_watch(app: &mut AppState) -> anyhow::Result<OverlayWindowConfig> {
|
||||
log::error!("{command} has invalid argument: \"{arg}\"");
|
||||
return;
|
||||
};
|
||||
Box::new(move |_common, _data, app, state| {
|
||||
Box::new(move |_common, data, app, state| {
|
||||
if !test_btn(data) {
|
||||
return Ok(EventResult::Pass);
|
||||
}
|
||||
|
||||
let Some(overlay) = state.overlay_metas.get(idx) else {
|
||||
log::error!("No overlay at index {idx}.");
|
||||
return Ok(EventResult::Consumed);
|
||||
@@ -137,7 +153,11 @@ pub fn create_watch(app: &mut AppState) -> anyhow::Result<OverlayWindowConfig> {
|
||||
log::error!("{command} has invalid argument: \"{arg}\"");
|
||||
return;
|
||||
};
|
||||
Box::new(move |_common, _data, app, state| {
|
||||
Box::new(move |_common, data, app, state| {
|
||||
if !test_btn(data) {
|
||||
return Ok(EventResult::Pass);
|
||||
}
|
||||
|
||||
let Some(overlay) = state.overlay_metas.get(idx) else {
|
||||
log::error!("No overlay at index {idx}.");
|
||||
return Ok(EventResult::Consumed);
|
||||
|
||||
Reference in New Issue
Block a user