watch toolbox to support various overlay types

This commit is contained in:
galister
2025-11-26 17:16:19 +09:00
parent 3f907180f8
commit d5c5d06b3a
12 changed files with 148 additions and 51 deletions

View File

@@ -33,8 +33,8 @@
</Button> </Button>
</template> </template>
<template name="Screen"> <template name="Overlay">
<Button macro="button_style" padding="4" id="screen_${idx}" text="DISP-${idx}" tooltip="Toggle for current set" _press="::EditModeScreenToggle ${idx}" /> <Button macro="button_style" padding="4" id="overlay_${idx}" text="WLX-${idx}" tooltip="Toggle for current set" _press="::EditModeOverlayToggle ${idx}" />
</template> </template>
<template name="Set"> <template name="Set">
@@ -82,6 +82,7 @@
</div> </div>
<div flex_direction="row" flex_wrap="wrap" padding="4" align_items="center" justify_content="center" id="toolbox"> <div flex_direction="row" flex_wrap="wrap" padding="4" align_items="center" justify_content="center" id="toolbox">
<Button macro="button_style" padding="4" translation="Keyboard" tooltip="Toggle for current set" _press="::OverlayToggle kbd" /> <Button macro="button_style" padding="4" translation="Keyboard" tooltip="Toggle for current set" _press="::OverlayToggle kbd" />
<!-- Will populate <Overlay> tags at runtime -->
</div> </div>
</div> </div>
<div width="100%" flex_direction="row"> <div width="100%" flex_direction="row">

View File

@@ -241,7 +241,7 @@ pub fn openvr_run(
&& o.birthframe < cur_frame && o.birthframe < cur_frame
{ {
o.destroy(&mut overlay_mgr); o.destroy(&mut overlay_mgr);
overlays.remove_by_selector(&sel); overlays.remove_by_selector(&sel, &mut app);
} }
} }
TaskType::System(task) => match task { TaskType::System(task) => match task {

View File

@@ -517,7 +517,7 @@ pub fn openxr_run(
&& o.birthframe < cur_frame && o.birthframe < cur_frame
{ {
log::debug!("{}: destroy", o.config.name); log::debug!("{}: destroy", o.config.name);
if let Some(o) = overlays.remove_by_selector(&sel) { if let Some(o) = overlays.remove_by_selector(&sel, &mut app) {
// set for deletion after all images are done showing // set for deletion after all images are done showing
delete_queue.push((o, cur_frame + 5)); delete_queue.push((o, cur_frame + 5));
} }

View File

@@ -1,9 +1,13 @@
use std::sync::Arc; use std::sync::Arc;
use glam::{Affine3A, Quat, Vec3, vec3}; use glam::{vec3, Affine3A, Quat, Vec3};
use wlx_common::windowing::OverlayWindowState; use wlx_common::windowing::OverlayWindowState;
use crate::{gui::panel::GuiPanel, state::AppState, windowing::window::OverlayWindowConfig}; use crate::{
gui::panel::GuiPanel,
state::AppState,
windowing::window::{OverlayCategory, OverlayWindowConfig},
};
const SETTINGS_NAME: &str = "settings"; const SETTINGS_NAME: &str = "settings";
@@ -20,6 +24,7 @@ pub fn create_custom(app: &mut AppState, name: Arc<str>) -> Option<OverlayWindow
Some(OverlayWindowConfig { Some(OverlayWindowConfig {
name, name,
category: OverlayCategory::PanelCustom,
default_state: OverlayWindowState { default_state: OverlayWindowState {
interactable: true, interactable: true,
grabbable: true, grabbable: true,

View File

@@ -271,7 +271,7 @@ fn make_edit_panel(app: &mut AppState) -> anyhow::Result<EditModeWrapPanel> {
Ok(EventResult::Consumed) Ok(EventResult::Consumed)
}), }),
"::EditModeDeleteRelease" => Box::new(move |_common, _data, app, state| { "::EditModeDeleteRelease" => Box::new(move |_common, _data, app, state| {
if state.delete.pressed.elapsed() < Duration::from_secs(2) { if state.delete.pressed.elapsed() < Duration::from_secs(1) {
return Ok(EventResult::Pass); return Ok(EventResult::Pass);
} }
app.tasks.enqueue(TaskType::Overlay( app.tasks.enqueue(TaskType::Overlay(

View File

@@ -5,7 +5,7 @@ use std::{
use futures::{Future, FutureExt}; use futures::{Future, FutureExt};
use glam::{Affine2, Affine3A, Vec3}; use glam::{Affine2, Affine3A, Vec3};
use wlx_capture::pipewire::{PipewireCapture, PipewireSelectScreenResult, pipewire_select_screen}; use wlx_capture::pipewire::{pipewire_select_screen, PipewireCapture, PipewireSelectScreenResult};
use wlx_common::windowing::OverlayWindowState; use wlx_common::windowing::OverlayWindowState;
use crate::{ use crate::{
@@ -16,12 +16,12 @@ use crate::{
state::{AppSession, AppState}, state::{AppSession, AppState},
subsystem::hid::WheelDelta, subsystem::hid::WheelDelta,
windowing::{ windowing::{
OverlaySelector,
backend::{ backend::{
FrameMeta, OverlayBackend, OverlayEventData, RenderResources, ShouldRender, ui_transform, FrameMeta, OverlayBackend, OverlayEventData, RenderResources,
ui_transform, ShouldRender,
}, },
window::OverlayWindowConfig, window::{OverlayCategory, OverlayWindowConfig},
OverlaySelector,
}, },
}; };
@@ -152,6 +152,7 @@ impl OverlayBackend for MirrorBackend {
pub fn new_mirror(name: Arc<str>, session: &AppSession) -> OverlayWindowConfig { pub fn new_mirror(name: Arc<str>, session: &AppSession) -> OverlayWindowConfig {
OverlayWindowConfig { OverlayWindowConfig {
name: name.clone(), name: name.clone(),
category: OverlayCategory::Mirror,
default_state: OverlayWindowState { default_state: OverlayWindowState {
interactable: true, interactable: true,
grabbable: true, grabbable: true,

View File

@@ -1,13 +1,16 @@
use std::{f32::consts::PI, sync::Arc}; use std::{f32::consts::PI, sync::Arc};
use glam::{Affine3A, Quat, Vec3, vec3}; use glam::{vec3, Affine3A, Quat, Vec3};
use wlx_capture::frame::Transform; use wlx_capture::frame::Transform;
use wlx_common::windowing::{OverlayWindowState, Positioning}; use wlx_common::windowing::{OverlayWindowState, Positioning};
use crate::{ use crate::{
state::{AppSession, AppState, ScreenMeta}, state::{AppSession, AppState, ScreenMeta},
subsystem::{hid::XkbKeymap, input::KeyboardFocus}, subsystem::{hid::XkbKeymap, input::KeyboardFocus},
windowing::{backend::OverlayBackend, window::OverlayWindowConfig}, windowing::{
backend::OverlayBackend,
window::{OverlayCategory, OverlayWindowConfig},
},
}; };
pub mod backend; pub mod backend;
@@ -38,6 +41,7 @@ fn create_screen_from_backend(
OverlayWindowConfig { OverlayWindowConfig {
name, name,
category: OverlayCategory::Screen,
default_state: OverlayWindowState { default_state: OverlayWindowState {
grabbable: true, grabbable: true,
positioning: Positioning::Anchored, positioning: Positioning::Anchored,

View File

@@ -25,7 +25,7 @@ use crate::{
overlays::edit::LongPressButtonState, overlays::edit::LongPressButtonState,
state::AppState, state::AppState,
windowing::{ windowing::{
backend::OverlayEventData, backend::{OverlayEventData, OverlayMeta},
manager::MAX_OVERLAY_SETS, manager::MAX_OVERLAY_SETS,
window::{OverlayWindowConfig, OverlayWindowData}, window::{OverlayWindowConfig, OverlayWindowData},
OverlaySelector, Z_ORDER_WATCH, OverlaySelector, Z_ORDER_WATCH,
@@ -33,12 +33,14 @@ use crate::{
}; };
pub const WATCH_NAME: &str = "watch"; pub const WATCH_NAME: &str = "watch";
const MAX_TOOLBOX_BUTTONS: usize = 16;
#[derive(Default)] #[derive(Default)]
struct WatchState { struct WatchState {
current_set: Option<usize>, current_set: Option<usize>,
set_buttons: Vec<Rc<ComponentButton>>, set_buttons: Vec<Rc<ComponentButton>>,
screen_buttons: Vec<Rc<ComponentButton>>, overlay_buttons: Vec<Rc<ComponentButton>>,
overlay_metas: Vec<OverlayMeta>,
edit_mode_widgets: Vec<(WidgetID, bool)>, edit_mode_widgets: Vec<(WidgetID, bool)>,
edit_add_widget: WidgetID, edit_add_widget: WidgetID,
num_sets: usize, num_sets: usize,
@@ -66,7 +68,7 @@ pub fn create_watch(app: &mut AppState) -> anyhow::Result<OverlayWindowConfig> {
Ok(EventResult::Consumed) Ok(EventResult::Consumed)
}), }),
"::EditModeDeleteUp" => Box::new(move |_common, _data, app, state| { "::EditModeDeleteUp" => Box::new(move |_common, _data, app, state| {
if state.delete.pressed.elapsed() < Duration::from_secs(2) { if state.delete.pressed.elapsed() < Duration::from_secs(1) {
return Ok(EventResult::Consumed); return Ok(EventResult::Consumed);
} }
app.tasks app.tasks
@@ -77,20 +79,20 @@ pub fn create_watch(app: &mut AppState) -> anyhow::Result<OverlayWindowConfig> {
app.tasks.enqueue(TaskType::Manager(ManagerTask::AddSet)); app.tasks.enqueue(TaskType::Manager(ManagerTask::AddSet));
Ok(EventResult::Consumed) Ok(EventResult::Consumed)
}), }),
"::EditModeScreenToggle" => { "::EditModeOverlayToggle" => {
let arg = args.next().unwrap_or_default(); let arg = args.next().unwrap_or_default();
let Ok(idx) = arg.parse::<usize>() else { let Ok(idx) = arg.parse::<usize>() else {
log::error!("{command} has invalid argument: \"{arg}\""); log::error!("{command} has invalid argument: \"{arg}\"");
return; return;
}; };
Box::new(move |_common, _data, app, _state| { Box::new(move |_common, _data, app, state| {
let Some(screen) = app.screens.get(idx) else { let Some(overlay) = state.overlay_metas.get(idx) else {
log::error!("No screen at index {idx}."); log::error!("No overlay at index {idx}.");
return Ok(EventResult::Consumed); return Ok(EventResult::Consumed);
}; };
app.tasks.enqueue(TaskType::Overlay( app.tasks.enqueue(TaskType::Overlay(
OverlaySelector::Name(screen.name.clone()), OverlaySelector::Id(overlay.id.clone()),
Box::new(move |app, owc| { Box::new(move |app, owc| {
if owc.active_state.is_none() { if owc.active_state.is_none() {
owc.activate(app); owc.activate(app);
@@ -137,17 +139,17 @@ pub fn create_watch(app: &mut AppState) -> anyhow::Result<OverlayWindowConfig> {
state.set_buttons.push(component); state.set_buttons.push(component);
} }
} else if &*id == "toolbox" { } else if &*id == "toolbox" {
for idx in 0..9 { for idx in 0..MAX_TOOLBOX_BUTTONS {
let screen_id = format!("screen_{idx}"); let overlay_id = format!("overlay_{idx}");
let mut params: HashMap<Rc<str>, Rc<str>> = HashMap::new(); let mut params: HashMap<Rc<str>, Rc<str>> = HashMap::new();
params.insert("idx".into(), idx.to_string().into()); params.insert("idx".into(), idx.to_string().into());
parser_state.instantiate_template( parser_state.instantiate_template(
doc_params, "Screen", layout, widget, params, doc_params, "Overlay", layout, widget, params,
)?; )?;
let component = let component =
parser_state.fetch_component_as::<ComponentButton>(&screen_id)?; parser_state.fetch_component_as::<ComponentButton>(&overlay_id)?;
state.screen_buttons.push(component); state.overlay_buttons.push(component);
} }
} }
Ok(()) Ok(())
@@ -158,7 +160,7 @@ pub fn create_watch(app: &mut AppState) -> anyhow::Result<OverlayWindowConfig> {
}, },
)?; )?;
panel.on_notify = Some(Box::new(|panel, app, event_data| { panel.on_notify = Some(Box::new(|panel, _app, event_data| {
let mut alterables = EventAlterables::default(); let mut alterables = EventAlterables::default();
let mut common = CallbackDataCommon { let mut common = CallbackDataCommon {
alterables: &mut alterables, alterables: &mut alterables,
@@ -218,10 +220,12 @@ pub fn create_watch(app: &mut AppState) -> anyhow::Result<OverlayWindowConfig> {
&mut common.alterables, &mut common.alterables,
); );
} }
OverlayEventData::ScreensChanged => { OverlayEventData::OverlaysChanged(metas) => {
for (idx, btn) in panel.state.screen_buttons.iter().enumerate() { panel.state.overlay_metas = metas;
let display = if let Some(screen) = app.screens.get(idx) { for (idx, btn) in panel.state.overlay_buttons.iter().enumerate() {
btn.set_text(&mut common, Translation::from_raw_text(&screen.name)); let display = if let Some(meta) = panel.state.overlay_metas.get(idx) {
btn.set_text(&mut common, Translation::from_raw_text(&meta.name));
//TODO: add category icons
taffy::Display::Flex taffy::Display::Flex
} else { } else {
taffy::Display::None taffy::Display::None

View File

@@ -1,17 +1,17 @@
use glam::{Affine2, Affine3A, Quat, Vec3, vec3}; use glam::{vec3, Affine2, Affine3A, Quat, Vec3};
use smallvec::smallvec; use smallvec::smallvec;
use std::{cell::RefCell, collections::HashMap, rc::Rc, sync::Arc}; use std::{cell::RefCell, collections::HashMap, rc::Rc, sync::Arc};
use vulkano::{ use vulkano::{
buffer::{BufferUsage, Subbuffer}, buffer::{BufferUsage, Subbuffer},
command_buffer::CommandBufferUsage, command_buffer::CommandBufferUsage,
format::Format, format::Format,
image::{Image, ImageTiling, SubresourceLayout, view::ImageView}, image::{view::ImageView, Image, ImageTiling, SubresourceLayout},
}; };
use wayvr_ipc::packet_server::{self, PacketServer, WvrStateChanged}; use wayvr_ipc::packet_server::{self, PacketServer, WvrStateChanged};
use wgui::gfx::{ use wgui::gfx::{
WGfx,
pass::WGfxPass, pass::WGfxPass,
pipeline::{WGfxPipeline, WPipelineCreateInfo}, pipeline::{WGfxPipeline, WPipelineCreateInfo},
WGfx,
}; };
use wlx_capture::frame::{DmabufFrame, FourCC, FrameFormat, FramePlane}; use wlx_capture::frame::{DmabufFrame, FourCC, FrameFormat, FramePlane};
use wlx_common::windowing::OverlayWindowState; use wlx_common::windowing::OverlayWindowState;
@@ -21,22 +21,23 @@ use crate::{
input::{self, HoverResult}, input::{self, HoverResult},
task::TaskType, task::TaskType,
wayvr::{ wayvr::{
self, WayVR, WayVRAction, WayVRDisplayClickAction, display, self, display,
server_ipc::{gen_args_vec, gen_env_vec}, server_ipc::{gen_args_vec, gen_env_vec},
WayVR, WayVRAction, WayVRDisplayClickAction,
}, },
}, },
config_wayvr, config_wayvr,
graphics::{Vert2Uv, dmabuf::WGfxDmabuf}, graphics::{dmabuf::WGfxDmabuf, Vert2Uv},
state::{self, AppState}, state::{self, AppState},
subsystem::{hid::WheelDelta, input::KeyboardFocus}, subsystem::{hid::WheelDelta, input::KeyboardFocus},
windowing::{ windowing::{
OverlayID, OverlaySelector, Z_ORDER_DASHBOARD,
backend::{ backend::{
FrameMeta, OverlayBackend, OverlayEventData, RenderResources, ShouldRender, ui_transform, FrameMeta, OverlayBackend, OverlayEventData, RenderResources,
ui_transform, ShouldRender,
}, },
manager::OverlayWindowManager, manager::OverlayWindowManager,
window::{OverlayWindowConfig, OverlayWindowData}, window::{OverlayCategory, OverlayWindowConfig, OverlayWindowData},
OverlayID, OverlaySelector, Z_ORDER_DASHBOARD,
}, },
}; };
@@ -45,7 +46,7 @@ use super::toast::error_toast;
// Hard-coded for now // Hard-coded for now
const DASHBOARD_WIDTH: u16 = 1920; const DASHBOARD_WIDTH: u16 = 1920;
const DASHBOARD_HEIGHT: u16 = 1080; const DASHBOARD_HEIGHT: u16 = 1080;
const DASHBOARD_DISPLAY_NAME: &str = "_DASHBOARD"; const DASHBOARD_DISPLAY_NAME: &str = "DASHBOARD";
pub struct WayVRContext { pub struct WayVRContext {
wayvr: Rc<RefCell<WayVRData>>, wayvr: Rc<RefCell<WayVRData>>,
@@ -794,9 +795,16 @@ pub fn create_wayvr_display_overlay(
[display_width, display_height], [display_width, display_height],
)?); )?);
let category = if name == DASHBOARD_DISPLAY_NAME {
OverlayCategory::Internal
} else {
OverlayCategory::WayVR
};
Ok(OverlayWindowConfig { Ok(OverlayWindowConfig {
name: format!("WayVR - {name}").into(), name: format!("WVR-{name}").into(),
keyboard_focus: Some(KeyboardFocus::WayVR), keyboard_focus: Some(KeyboardFocus::WayVR),
category,
default_state: OverlayWindowState { default_state: OverlayWindowState {
interactable: true, interactable: true,
grabbable: true, grabbable: true,

View File

@@ -16,6 +16,7 @@ use crate::{
graphics::ExtentExt, graphics::ExtentExt,
state::AppState, state::AppState,
subsystem::hid::WheelDelta, subsystem::hid::WheelDelta,
windowing::{window::OverlayCategory, OverlayID},
}; };
#[derive(Default, Clone, Copy)] #[derive(Default, Clone, Copy)]
@@ -64,12 +65,17 @@ impl RenderResources {
} }
} }
pub struct OverlayMeta {
pub id: OverlayID,
pub name: Arc<str>,
pub category: OverlayCategory,
}
pub enum OverlayEventData { pub enum OverlayEventData {
ActiveSetChanged(Option<usize>), ActiveSetChanged(Option<usize>),
NumSetsChanged(usize), NumSetsChanged(usize),
EditModeChanged(bool), EditModeChanged(bool),
///TODO: this only gets fired at startup OverlaysChanged(Vec<OverlayMeta>),
ScreensChanged,
} }
pub trait OverlayBackend: Any { pub trait OverlayBackend: Any {

View File

@@ -12,7 +12,10 @@ use crate::{
}, },
state::AppState, state::AppState,
windowing::{ windowing::{
backend::OverlayEventData, set::OverlayWindowSet, snap_upright, window::OverlayWindowData, backend::{OverlayEventData, OverlayMeta},
set::OverlayWindowSet,
snap_upright,
window::{OverlayCategory, OverlayWindowData},
OverlayID, OverlaySelector, OverlayID, OverlaySelector,
}, },
}; };
@@ -101,7 +104,6 @@ where
let mut watch = OverlayWindowData::from_config(create_watch(app)?); let mut watch = OverlayWindowData::from_config(create_watch(app)?);
for ev in [ for ev in [
OverlayEventData::ScreensChanged,
OverlayEventData::NumSetsChanged(me.sets.len()), OverlayEventData::NumSetsChanged(me.sets.len()),
OverlayEventData::EditModeChanged(false), OverlayEventData::EditModeChanged(false),
] { ] {
@@ -109,6 +111,8 @@ where
} }
me.watch_id = me.add(watch, app); me.watch_id = me.add(watch, app);
me.overlays_changed(app)?;
// overwrite default layout with saved layout, if exists // overwrite default layout with saved layout, if exists
me.restore_layout(app); me.restore_layout(app);
@@ -238,13 +242,28 @@ impl<T> OverlayWindowManager<T> {
pub fn remove_by_selector( pub fn remove_by_selector(
&mut self, &mut self,
selector: &OverlaySelector, selector: &OverlaySelector,
app: &mut AppState,
) -> Option<OverlayWindowData<T>> { ) -> Option<OverlayWindowData<T>> {
match selector { let id = match selector {
OverlaySelector::Id(id) => self.overlays.remove(*id), OverlaySelector::Id(id) => *id,
OverlaySelector::Name(name) => { OverlaySelector::Name(name) => {
self.lookup(name).and_then(|id| self.overlays.remove(id)) let Some(id) = self.lookup(name) else {
return None;
};
id
} }
};
let ret_val = self.overlays.remove(id);
let internal = ret_val
.as_ref()
.is_some_and(|o| matches!(o.config.category, OverlayCategory::Internal));
if !internal && let Err(e) = self.overlays_changed(app) {
log::error!("Error while removing overlay: {e:?}")
} }
ret_val
} }
pub fn get_by_id(&mut self, id: OverlayID) -> Option<&OverlayWindowData<T>> { pub fn get_by_id(&mut self, id: OverlayID) -> Option<&OverlayWindowData<T>> {
@@ -279,10 +298,24 @@ impl<T> OverlayWindowManager<T> {
} }
pub fn add(&mut self, mut overlay: OverlayWindowData<T>, app: &mut AppState) -> OverlayID { pub fn add(&mut self, mut overlay: OverlayWindowData<T>, app: &mut AppState) -> OverlayID {
let internal = matches!(overlay.config.category, OverlayCategory::Internal);
while self.lookup(&overlay.config.name).is_some() {
log::error!(
"An overlay with name {} already exists. Deduplicating, but things may break!",
overlay.config.name
);
overlay.config.name = format!("{}_2", overlay.config.name).into();
}
if overlay.config.show_on_spawn { if overlay.config.show_on_spawn {
overlay.config.activate(app); overlay.config.activate(app);
} }
self.overlays.insert(overlay) let ret_val = self.overlays.insert(overlay);
if !internal && let Err(e) = self.overlays_changed(app) {
log::error!("Error while adding overlay: {e:?}");
}
ret_val
} }
pub fn switch_or_toggle_set(&mut self, app: &mut AppState, set: usize) { pub fn switch_or_toggle_set(&mut self, app: &mut AppState, set: usize) {
@@ -419,4 +452,27 @@ impl<T> OverlayWindowManager<T> {
Ok(()) Ok(())
} }
fn overlays_changed(&mut self, app: &mut AppState) -> anyhow::Result<()> {
let mut meta = Vec::with_capacity(self.overlays.len());
for (id, data) in self.overlays.iter() {
if matches!(data.config.category, OverlayCategory::Internal) {
continue;
}
meta.push(OverlayMeta {
id: id.clone(),
name: data.config.name.clone(),
category: data.config.category,
});
}
if let Some(watch) = self.mut_by_id(self.watch_id) {
watch
.config
.backend
.notify(app, OverlayEventData::OverlaysChanged(meta))?;
}
Ok(())
}
} }

View File

@@ -49,6 +49,15 @@ impl<T> OverlayWindowData<T> {
} }
} }
#[derive(Clone, Copy)]
pub enum OverlayCategory {
Internal,
PanelCustom,
Screen,
Mirror,
WayVR,
}
pub struct OverlayWindowConfig { pub struct OverlayWindowConfig {
pub name: Arc<str>, pub name: Arc<str>,
pub backend: Box<dyn OverlayBackend>, pub backend: Box<dyn OverlayBackend>,
@@ -60,6 +69,8 @@ pub struct OverlayWindowConfig {
pub z_order: u32, pub z_order: u32,
/// If set, hovering this overlay will cause the HID provider to switch focus. /// If set, hovering this overlay will cause the HID provider to switch focus.
pub keyboard_focus: Option<KeyboardFocus>, pub keyboard_focus: Option<KeyboardFocus>,
/// Category of the overlay, used by toolbox on the watch.
pub category: OverlayCategory,
/// Should the overlay be displayed on the next frame? /// Should the overlay be displayed on the next frame?
pub show_on_spawn: bool, pub show_on_spawn: bool,
/// Does not belong to any set; switching sets does not affect this overlay. /// Does not belong to any set; switching sets does not affect this overlay.
@@ -82,6 +93,7 @@ impl OverlayWindowConfig {
active_state: None, active_state: None,
z_order: 0, z_order: 0,
keyboard_focus: None, keyboard_focus: None,
category: OverlayCategory::Internal,
show_on_spawn: false, show_on_spawn: false,
global: false, global: false,
dirty: true, dirty: true,