watch: highlight current set

This commit is contained in:
galister
2025-11-24 14:18:04 +09:00
parent 5b40032bc3
commit 2d7714d423
13 changed files with 141 additions and 25 deletions

View File

@@ -1,6 +1,6 @@
use crate::{ use crate::{
animation::{Animation, AnimationEasing}, animation::{Animation, AnimationEasing},
components::{self, Component, ComponentBase, ComponentTrait, RefreshData, tooltip::ComponentTooltip}, components::{self, tooltip::ComponentTooltip, Component, ComponentBase, ComponentTrait, RefreshData},
drawing::{self, Boundary, Color}, drawing::{self, Boundary, Color},
event::{CallbackDataCommon, EventListenerCollection, EventListenerID, EventListenerKind}, event::{CallbackDataCommon, EventListenerCollection, EventListenerID, EventListenerKind},
i18n::Translation, i18n::Translation,
@@ -10,15 +10,15 @@ use crate::{
util::centered_matrix, util::centered_matrix,
}, },
widget::{ widget::{
ConstructEssentials, EventResult, WidgetData,
label::{WidgetLabel, WidgetLabelParams}, label::{WidgetLabel, WidgetLabelParams},
rectangle::{WidgetRectangle, WidgetRectangleParams}, rectangle::{WidgetRectangle, WidgetRectangleParams},
util::WLength, util::WLength,
ConstructEssentials, EventResult, WidgetData,
}, },
}; };
use glam::{Mat4, Vec3}; use glam::{Mat4, Vec3};
use std::{cell::RefCell, rc::Rc}; use std::{cell::RefCell, rc::Rc};
use taffy::{AlignItems, JustifyContent, prelude::length}; use taffy::{prelude::length, AlignItems, JustifyContent};
pub struct Params { pub struct Params {
pub text: Option<Translation>, // if unset, label will not be populated pub text: Option<Translation>, // if unset, label will not be populated

View File

@@ -26,7 +26,7 @@
</template> </template>
<template name="Set"> <template name="Set">
<Button macro="button_style" _press="::SetToggle ${handle}" tooltip="Switch to set" tooltip_side="top"> <Button macro="button_style" id="set_${handle}" _press="::SetToggle ${handle}" tooltip="Switch to set" tooltip_side="top">
<sprite width="40" height="40" color="~set_color" src="watch/set2.svg" /> <sprite width="40" height="40" color="~set_color" src="watch/set2.svg" />
<div position="absolute" margin_top="9"> <div position="absolute" margin_top="9">
<label text="${display}" size="24" color="#00050F" weight="bold" /> <label text="${display}" size="24" color="#00050F" weight="bold" />
@@ -77,4 +77,4 @@
</rectangle> </rectangle>
</div> </div>
</elements> </elements>
</layout> </layout>

View File

@@ -2,7 +2,7 @@ use std::f32::consts::PI;
use std::process::{Child, Command}; use std::process::{Child, Command};
use std::{collections::VecDeque, time::Instant}; use std::{collections::VecDeque, time::Instant};
use glam::{Affine3A, Vec2, Vec3, Vec3A, Vec3Swizzles}; use glam::{Affine3A, Vec2, Vec3A, Vec3Swizzles};
use smallvec::{smallvec, SmallVec}; use smallvec::{smallvec, SmallVec};
@@ -14,7 +14,7 @@ use crate::windowing::manager::OverlayWindowManager;
use crate::windowing::window::{realign, OverlayWindowData, OverlayWindowState, Positioning}; use crate::windowing::window::{realign, OverlayWindowData, OverlayWindowState, Positioning};
use crate::windowing::{OverlayID, OverlaySelector}; use crate::windowing::{OverlayID, OverlaySelector};
use super::task::{TaskContainer, TaskType}; use super::task::TaskType;
#[derive(Clone, Default)] #[derive(Clone, Default)]
pub struct HoverResult { pub struct HoverResult {

View File

@@ -25,7 +25,9 @@ use wgui::gfx::WGfx;
use crate::backend::input::{HoverResult, PointerHit}; use crate::backend::input::{HoverResult, PointerHit};
use crate::state::AppState; use crate::state::AppState;
use crate::subsystem::hid::WheelDelta; use crate::subsystem::hid::WheelDelta;
use crate::windowing::backend::{FrameMeta, OverlayBackend, RenderResources, ShouldRender}; use crate::windowing::backend::{
FrameMeta, OverlayBackend, OverlayEventData, RenderResources, ShouldRender,
};
use crate::windowing::window::{OverlayWindowConfig, OverlayWindowData}; use crate::windowing::window::{OverlayWindowConfig, OverlayWindowData};
use crate::windowing::Z_ORDER_LINES; use crate::windowing::Z_ORDER_LINES;
@@ -198,6 +200,10 @@ impl OverlayBackend for LineBackend {
}) })
} }
fn notify(&mut self, _: &mut AppState, _: OverlayEventData) -> anyhow::Result<()> {
unreachable!()
}
fn on_hover(&mut self, _: &mut AppState, _: &PointerHit) -> HoverResult { fn on_hover(&mut self, _: &mut AppState, _: &PointerHit) -> HoverResult {
HoverResult::default() HoverResult::default()
} }

View File

@@ -22,7 +22,9 @@ use crate::{
backend::input::{Haptics, HoverResult, PointerHit, PointerMode}, backend::input::{Haptics, HoverResult, PointerHit, PointerMode},
state::AppState, state::AppState,
subsystem::hid::WheelDelta, subsystem::hid::WheelDelta,
windowing::backend::{ui_transform, FrameMeta, OverlayBackend, RenderResources, ShouldRender}, windowing::backend::{
ui_transform, FrameMeta, OverlayBackend, OverlayEventData, RenderResources, ShouldRender,
},
}; };
use super::{timer::GuiTimer, timestep::Timestep}; use super::{timer::GuiTimer, timestep::Timestep};
@@ -35,6 +37,9 @@ const DEFAULT_MAX_SIZE: f32 = 2048.0;
const COLOR_ERR: drawing::Color = drawing::Color::new(1., 0., 1., 1.); const COLOR_ERR: drawing::Color = drawing::Color::new(1., 0., 1., 1.);
pub type OnNotifyFunc<S> =
Box<dyn Fn(&mut GuiPanel<S>, &mut AppState, OverlayEventData) -> anyhow::Result<()>>;
pub struct GuiPanel<S> { pub struct GuiPanel<S> {
pub layout: Layout, pub layout: Layout,
pub state: S, pub state: S,
@@ -42,31 +47,33 @@ pub struct GuiPanel<S> {
pub parser_state: ParserState, pub parser_state: ParserState,
pub max_size: Vec2, pub max_size: Vec2,
pub gui_scale: f32, pub gui_scale: f32,
pub on_notify: Option<OnNotifyFunc<S>>,
interaction_transform: Option<Affine2>, interaction_transform: Option<Affine2>,
context: WguiContext, context: WguiContext,
timestep: Timestep, timestep: Timestep,
} }
pub type OnCustomIdFunc = Box< pub type OnCustomIdFunc<S> = Box<
dyn Fn( dyn Fn(
Rc<str>, Rc<str>,
WidgetID, WidgetID,
&wgui::parser::ParseDocumentParams, &wgui::parser::ParseDocumentParams,
&mut Layout, &mut Layout,
&mut ParserState, &mut ParserState,
&mut S,
) -> anyhow::Result<()>, ) -> anyhow::Result<()>,
>; >;
pub type OnCustomAttribFunc = Box<dyn Fn(&mut Layout, &CustomAttribsInfoOwned, &AppState)>; pub type OnCustomAttribFunc = Box<dyn Fn(&mut Layout, &CustomAttribsInfoOwned, &AppState)>;
pub struct NewGuiPanelParams { pub struct NewGuiPanelParams<S> {
pub on_custom_id: Option<OnCustomIdFunc>, // used only in `new_from_template` pub on_custom_id: Option<OnCustomIdFunc<S>>, // used only in `new_from_template`
pub on_custom_attrib: Option<OnCustomAttribFunc>, // used only in `new_from_template` pub on_custom_attrib: Option<OnCustomAttribFunc>, // used only in `new_from_template`
pub resize_to_parent: bool, pub resize_to_parent: bool,
pub gui_scale: f32, pub gui_scale: f32,
} }
impl Default for NewGuiPanelParams { impl<S> Default for NewGuiPanelParams<S> {
fn default() -> Self { fn default() -> Self {
Self { Self {
on_custom_id: None, on_custom_id: None,
@@ -81,8 +88,8 @@ impl<S: 'static> GuiPanel<S> {
pub fn new_from_template( pub fn new_from_template(
app: &mut AppState, app: &mut AppState,
path: &str, path: &str,
state: S, mut state: S,
params: NewGuiPanelParams, params: NewGuiPanelParams<S>,
) -> anyhow::Result<Self> { ) -> anyhow::Result<Self> {
let custom_elems = Rc::new(RefCell::new(vec![])); let custom_elems = Rc::new(RefCell::new(vec![]));
@@ -117,6 +124,7 @@ impl<S: 'static> GuiPanel<S> {
&doc_params, &doc_params,
&mut layout, &mut layout,
&mut parser_state, &mut parser_state,
&mut state,
)?; )?;
} }
} }
@@ -156,6 +164,7 @@ impl<S: 'static> GuiPanel<S> {
max_size: vec2(DEFAULT_MAX_SIZE as _, DEFAULT_MAX_SIZE as _), max_size: vec2(DEFAULT_MAX_SIZE as _, DEFAULT_MAX_SIZE as _),
timers: vec![], timers: vec![],
interaction_transform: None, interaction_transform: None,
on_notify: None,
gui_scale: params.gui_scale, gui_scale: params.gui_scale,
}) })
} }
@@ -163,7 +172,7 @@ impl<S: 'static> GuiPanel<S> {
pub fn new_blank( pub fn new_blank(
app: &mut AppState, app: &mut AppState,
state: S, state: S,
params: NewGuiPanelParams, params: NewGuiPanelParams<S>,
) -> anyhow::Result<Self> { ) -> anyhow::Result<Self> {
let layout = Layout::new( let layout = Layout::new(
app.wgui_globals.clone(), app.wgui_globals.clone(),
@@ -183,6 +192,7 @@ impl<S: 'static> GuiPanel<S> {
parser_state: ParserState::default(), parser_state: ParserState::default(),
max_size: vec2(DEFAULT_MAX_SIZE as _, DEFAULT_MAX_SIZE as _), max_size: vec2(DEFAULT_MAX_SIZE as _, DEFAULT_MAX_SIZE as _),
timers: vec![], timers: vec![],
on_notify: None,
interaction_transform: None, interaction_transform: None,
gui_scale: params.gui_scale, gui_scale: params.gui_scale,
}) })
@@ -295,6 +305,15 @@ impl<S: 'static> OverlayBackend for GuiPanel<S> {
}) })
} }
fn notify(&mut self, app: &mut AppState, data: OverlayEventData) -> anyhow::Result<()> {
let Some(on_notify) = self.on_notify.take() else {
return Ok(());
};
on_notify(self, app, data)?;
self.on_notify = Some(on_notify);
Ok(())
}
fn on_scroll(&mut self, app: &mut AppState, hit: &PointerHit, delta: WheelDelta) { fn on_scroll(&mut self, app: &mut AppState, hit: &PointerHit, delta: WheelDelta) {
let e = WguiEvent::MouseWheel(MouseWheelEvent { let e = WguiEvent::MouseWheel(MouseWheelEvent {
delta: vec2(delta.x, delta.y), delta: vec2(delta.x, delta.y),

View File

@@ -26,7 +26,7 @@ use crate::{
state::AppState, state::AppState,
subsystem::hid::WheelDelta, subsystem::hid::WheelDelta,
windowing::{ windowing::{
backend::{DummyBackend, OverlayBackend, RenderResources, ShouldRender}, backend::{DummyBackend, OverlayBackend, OverlayEventData, RenderResources, ShouldRender},
window::OverlayWindowConfig, window::OverlayWindowConfig,
OverlayID, OverlayID,
}, },
@@ -204,6 +204,9 @@ impl OverlayBackend for EditModeBackendWrapper {
) { ) {
self.panel.on_scroll(app, hit, delta); self.panel.on_scroll(app, hit, delta);
} }
fn notify(&mut self, app: &mut AppState, event_data: OverlayEventData) -> anyhow::Result<()> {
self.panel.notify(app, event_data)
}
fn get_interaction_transform(&mut self) -> Option<glam::Affine2> { fn get_interaction_transform(&mut self) -> Option<glam::Affine2> {
self.inner.get_interaction_transform() self.inner.get_interaction_transform()
} }

View File

@@ -12,8 +12,10 @@ use crate::{
backend::input::{HoverResult, PointerHit}, backend::input::{HoverResult, PointerHit},
gui::panel::GuiPanel, gui::panel::GuiPanel,
state::AppState, state::AppState,
subsystem::hid::{ALT, CTRL, KeyModifier, META, SHIFT, SUPER, VirtualKey, WheelDelta}, subsystem::hid::{KeyModifier, VirtualKey, WheelDelta, ALT, CTRL, META, SHIFT, SUPER},
windowing::backend::{FrameMeta, OverlayBackend, RenderResources, ShouldRender}, windowing::backend::{
FrameMeta, OverlayBackend, OverlayEventData, RenderResources, ShouldRender,
},
}; };
pub mod builder; pub mod builder;
@@ -53,6 +55,10 @@ impl OverlayBackend for KeyboardBackend {
Ok(()) Ok(())
} }
fn notify(&mut self, app: &mut AppState, event_data: OverlayEventData) -> anyhow::Result<()> {
self.panel.notify(app, event_data)
}
fn on_pointer(&mut self, app: &mut AppState, hit: &PointerHit, pressed: bool) { fn on_pointer(&mut self, app: &mut AppState, hit: &PointerHit, pressed: bool) {
self.panel.on_pointer(app, hit, pressed); self.panel.on_pointer(app, hit, pressed);
self.panel.push_event( self.panel.push_event(

View File

@@ -15,7 +15,10 @@ use crate::{
state::{AppSession, AppState}, state::{AppSession, AppState},
subsystem::hid::WheelDelta, subsystem::hid::WheelDelta,
windowing::{ windowing::{
backend::{ui_transform, FrameMeta, OverlayBackend, RenderResources, ShouldRender}, backend::{
ui_transform, FrameMeta, OverlayBackend, OverlayEventData, RenderResources,
ShouldRender,
},
window::{OverlayWindowConfig, OverlayWindowState}, window::{OverlayWindowConfig, OverlayWindowState},
OverlaySelector, OverlaySelector,
}, },
@@ -124,6 +127,13 @@ impl OverlayBackend for MirrorBackend {
self.renderer.as_mut().and_then(ScreenBackend::frame_meta) self.renderer.as_mut().and_then(ScreenBackend::frame_meta)
} }
fn notify(&mut self, app: &mut AppState, event_data: OverlayEventData) -> anyhow::Result<()> {
let Some(renderer) = self.renderer.as_mut() else {
return Ok(());
};
renderer.notify(app, event_data)
}
fn on_hover(&mut self, _: &mut AppState, _: &PointerHit) -> HoverResult { fn on_hover(&mut self, _: &mut AppState, _: &PointerHit) -> HoverResult {
HoverResult { HoverResult {
consume: true, consume: true,

View File

@@ -11,7 +11,9 @@ use crate::{
graphics::ExtentExt, graphics::ExtentExt,
state::AppState, state::AppState,
subsystem::hid::{WheelDelta, MOUSE_LEFT, MOUSE_MIDDLE, MOUSE_RIGHT}, subsystem::hid::{WheelDelta, MOUSE_LEFT, MOUSE_MIDDLE, MOUSE_RIGHT},
windowing::backend::{FrameMeta, OverlayBackend, RenderResources, ShouldRender}, windowing::backend::{
FrameMeta, OverlayBackend, OverlayEventData, RenderResources, ShouldRender,
},
}; };
use super::capture::{receive_callback, ScreenPipeline, WlxCaptureIn, WlxCaptureOut}; use super::capture::{receive_callback, ScreenPipeline, WlxCaptureIn, WlxCaptureOut};
@@ -199,6 +201,10 @@ impl OverlayBackend for ScreenBackend {
self.meta self.meta
} }
fn notify(&mut self, _app: &mut AppState, _event_data: OverlayEventData) -> anyhow::Result<()> {
todo!();
}
fn on_hover(&mut self, app: &mut AppState, hit: &PointerHit) -> HoverResult { fn on_hover(&mut self, app: &mut AppState, hit: &PointerHit) -> HoverResult {
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
log::trace!("Hover: {:?}", hit.uv); log::trace!("Hover: {:?}", hit.uv);

View File

@@ -1,6 +1,11 @@
use std::{collections::HashMap, rc::Rc, time::Duration}; use std::{collections::HashMap, rc::Rc, time::Duration};
use glam::{Affine3A, Vec3, Vec3A}; use glam::{Affine3A, Vec3, Vec3A};
use wgui::{
components::button::ComponentButton,
event::{CallbackDataCommon, EventAlterables},
parser::Fetchable,
};
use crate::{ use crate::{
gui::{ gui::{
@@ -9,6 +14,7 @@ use crate::{
}, },
state::AppState, state::AppState,
windowing::{ windowing::{
backend::OverlayEventData,
window::{OverlayWindowConfig, OverlayWindowData, OverlayWindowState, Positioning}, window::{OverlayWindowConfig, OverlayWindowData, OverlayWindowState, Positioning},
Z_ORDER_WATCH, Z_ORDER_WATCH,
}, },
@@ -16,18 +22,22 @@ use crate::{
pub const WATCH_NAME: &str = "watch"; pub const WATCH_NAME: &str = "watch";
struct WatchState {} #[derive(Default)]
struct WatchState {
current_set: Option<usize>,
set_buttons: Vec<Rc<ComponentButton>>,
}
#[allow(clippy::significant_drop_tightening)] #[allow(clippy::significant_drop_tightening)]
pub fn create_watch(app: &mut AppState, num_sets: usize) -> anyhow::Result<OverlayWindowConfig> { pub fn create_watch(app: &mut AppState, num_sets: usize) -> anyhow::Result<OverlayWindowConfig> {
let state = WatchState {}; let state = WatchState::default();
let mut panel = GuiPanel::new_from_template( let mut panel = GuiPanel::new_from_template(
app, app,
"gui/watch.xml", "gui/watch.xml",
state, state,
NewGuiPanelParams { NewGuiPanelParams {
on_custom_id: Some(Box::new( on_custom_id: Some(Box::new(
move |id, widget, doc_params, layout, parser_state| { move |id, widget, doc_params, layout, parser_state, state| {
if &*id != "sets" { if &*id != "sets" {
return Ok(()); return Ok(());
} }
@@ -38,6 +48,11 @@ pub fn create_watch(app: &mut AppState, num_sets: usize) -> anyhow::Result<Overl
params.insert("handle".into(), idx.to_string().into()); params.insert("handle".into(), idx.to_string().into());
parser_state parser_state
.instantiate_template(doc_params, "Set", layout, widget, params)?; .instantiate_template(doc_params, "Set", layout, widget, params)?;
let button_id = format!("set_{idx}");
let component =
parser_state.fetch_component_as::<ComponentButton>(&button_id)?;
state.set_buttons.push(component);
} }
Ok(()) Ok(())
}, },
@@ -46,6 +61,27 @@ pub fn create_watch(app: &mut AppState, num_sets: usize) -> anyhow::Result<Overl
}, },
)?; )?;
panel.on_notify = Some(Box::new(|panel, _app, event_data| {
match event_data {
OverlayEventData::SetChanged(current_set) => {
let mut alterables = EventAlterables::default();
let mut common = CallbackDataCommon {
alterables: &mut alterables,
state: &panel.layout.state,
};
if let Some(old_set) = panel.state.current_set.take() {
panel.state.set_buttons[old_set].set_sticky_state(&mut common, false);
}
if let Some(new_set) = current_set {
panel.state.set_buttons[new_set].set_sticky_state(&mut common, true);
}
panel.state.current_set = current_set;
panel.layout.process_alterables(alterables)?;
}
}
Ok(())
}));
panel panel
.timers .timers
.push(GuiTimer::new(Duration::from_millis(100), 0)); .push(GuiTimer::new(Duration::from_millis(100), 0));

View File

@@ -30,7 +30,10 @@ use crate::{
state::{self, AppState}, state::{self, AppState},
subsystem::{hid::WheelDelta, input::KeyboardFocus}, subsystem::{hid::WheelDelta, input::KeyboardFocus},
windowing::{ windowing::{
backend::{ui_transform, FrameMeta, OverlayBackend, RenderResources, ShouldRender}, backend::{
ui_transform, FrameMeta, OverlayBackend, OverlayEventData, RenderResources,
ShouldRender,
},
manager::OverlayWindowManager, manager::OverlayWindowManager,
window::{OverlayWindowConfig, OverlayWindowData, OverlayWindowState}, window::{OverlayWindowConfig, OverlayWindowData, OverlayWindowState},
OverlayID, OverlaySelector, Z_ORDER_DASHBOARD, OverlayID, OverlaySelector, Z_ORDER_DASHBOARD,
@@ -703,6 +706,14 @@ impl OverlayBackend for WayVRBackend {
}) })
} }
fn notify(
&mut self,
_app: &mut state::AppState,
_event_data: OverlayEventData,
) -> anyhow::Result<()> {
Ok(())
}
fn on_hover(&mut self, _app: &mut state::AppState, hit: &input::PointerHit) -> HoverResult { fn on_hover(&mut self, _app: &mut state::AppState, hit: &input::PointerHit) -> HoverResult {
let ctx = self.context.borrow(); let ctx = self.context.borrow();

View File

@@ -64,6 +64,10 @@ impl RenderResources {
} }
} }
pub enum OverlayEventData {
SetChanged(Option<usize>),
}
pub trait OverlayBackend: Any { pub trait OverlayBackend: Any {
/// Called once, before the first frame is rendered /// Called once, before the first frame is rendered
fn init(&mut self, app: &mut AppState) -> anyhow::Result<()>; fn init(&mut self, app: &mut AppState) -> anyhow::Result<()>;
@@ -82,6 +86,8 @@ pub trait OverlayBackend: Any {
/// Must be Some if should_render was Should or Can on the same frame. /// Must be Some if should_render was Should or Can on the same frame.
fn frame_meta(&mut self) -> Option<FrameMeta>; fn frame_meta(&mut self) -> Option<FrameMeta>;
fn notify(&mut self, app: &mut AppState, event_data: OverlayEventData) -> anyhow::Result<()>;
fn on_hover(&mut self, app: &mut AppState, hit: &PointerHit) -> HoverResult; fn on_hover(&mut self, app: &mut AppState, hit: &PointerHit) -> HoverResult;
fn on_left(&mut self, app: &mut AppState, pointer: usize); fn on_left(&mut self, app: &mut AppState, pointer: usize);
fn on_pointer(&mut self, app: &mut AppState, hit: &PointerHit, pressed: bool); fn on_pointer(&mut self, app: &mut AppState, hit: &PointerHit, pressed: bool);
@@ -125,6 +131,10 @@ impl OverlayBackend for DummyBackend {
unreachable!() unreachable!()
} }
fn notify(&mut self, _: &mut AppState, _event_data: OverlayEventData) -> anyhow::Result<()> {
unreachable!()
}
fn on_hover(&mut self, _: &mut AppState, _: &PointerHit) -> HoverResult { fn on_hover(&mut self, _: &mut AppState, _: &PointerHit) -> HoverResult {
HoverResult::default() HoverResult::default()
} }

View File

@@ -10,6 +10,7 @@ use crate::{
}, },
state::AppState, state::AppState,
windowing::{ windowing::{
backend::OverlayEventData,
set::{OverlayWindowSet, SerializedWindowSet}, set::{OverlayWindowSet, SerializedWindowSet},
snap_upright, snap_upright,
window::OverlayWindowData, window::OverlayWindowData,
@@ -320,6 +321,14 @@ impl<T> OverlayWindowManager<T> {
self.restore_set = new_set; self.restore_set = new_set;
} }
self.current_set = new_set; self.current_set = new_set;
if let Some(watch) = self.mut_by_id(self.watch_id) {
watch
.config
.backend
.notify(app, OverlayEventData::SetChanged(new_set))
.unwrap(); // TODO: handle this
}
} }
pub fn show_hide(&mut self, app: &mut AppState) { pub fn show_hide(&mut self, app: &mut AppState) {