edit overlay progress

This commit is contained in:
galister
2025-11-14 17:58:23 +09:00
parent 70be748da1
commit 3daee83838
46 changed files with 687 additions and 352 deletions

View File

@@ -1,46 +0,0 @@
use glam::{Affine3A, Vec3};
use crate::{
gui::panel::GuiPanel,
state::AppState,
windowing::window::{OverlayWindowConfig, OverlayWindowState},
};
pub const BAR_NAME: &str = "bar";
struct BarState {}
#[allow(clippy::significant_drop_tightening)]
#[allow(clippy::for_kv_map)] // TODO: remove later
#[allow(clippy::match_same_arms)] // TODO: remove later
pub fn create_bar(app: &mut AppState) -> anyhow::Result<OverlayWindowConfig> {
let state = BarState {};
let mut panel = GuiPanel::new_from_template(app, "gui/bar.xml", state, Default::default())?;
for (id, _widget_id) in &panel.parser_state.data.ids {
match id.as_ref() {
"lock" => {}
"anchor" => {}
"mouse" => {}
"fade" => {}
"move" => {}
"resize" => {}
"inout" => {}
"delete" => {}
_ => {}
}
}
panel.update_layout()?;
Ok(OverlayWindowConfig {
name: BAR_NAME.into(),
default_state: OverlayWindowState {
interactable: true,
transform: Affine3A::from_scale(Vec3::ONE * 0.15),
..OverlayWindowState::default()
},
global: true,
..OverlayWindowConfig::from_backend(Box::new(panel))
})
}

View File

@@ -0,0 +1,111 @@
use glam::FloatExt;
use wgui::{
animation::{Animation, AnimationEasing},
event::CallbackDataCommon,
layout::WidgetID,
parser::Fetchable,
widget::rectangle::WidgetRectangle,
};
use crate::{backend::task::OverlayTask, overlays::edit::EditModeWrapPanel, state::AppState};
#[derive(Default)]
pub(super) struct InteractLockHandler {
id: WidgetID,
color: wgui::drawing::Color,
interactable: bool,
}
impl InteractLockHandler {
pub fn new(panel: &mut EditModeWrapPanel) -> anyhow::Result<Self> {
let id = panel.parser_state.get_widget_id("shadow")?;
let shadow_rect = panel
.layout
.state
.widgets
.get_as::<WidgetRectangle>(id)
.ok_or_else(|| anyhow::anyhow!("Element with id=\"shadow\" must be a <rectangle>"))?;
Ok(Self {
id,
color: shadow_rect.params.color,
interactable: true,
})
}
pub fn reset(&mut self, common: &mut CallbackDataCommon, interactable: bool) {
self.interactable = interactable;
let mut rect = common
.state
.widgets
.get_as::<WidgetRectangle>(self.id)
.unwrap(); // can only fail if set_up_rect has issues
let globals = common.state.globals.get();
if interactable {
set_anim_color(&mut rect, 0.0, self.color, globals.defaults.danger_color);
} else {
set_anim_color(&mut rect, 0.2, self.color, globals.defaults.danger_color);
}
}
pub fn toggle(
&mut self,
common: &mut CallbackDataCommon,
app: &mut AppState,
) -> Box<OverlayTask> {
let defaults = app.wgui_globals.get().defaults.clone();
let rect_color = self.color;
self.interactable = !self.interactable;
let anim = if self.interactable {
Animation::new(
self.id,
10,
AnimationEasing::OutQuad,
Box::new(move |common, data| {
let rect = data.obj.get_as_mut::<WidgetRectangle>().unwrap();
set_anim_color(
rect,
0.2 - (data.pos * 0.2),
rect_color,
defaults.danger_color,
);
common.alterables.mark_redraw();
}),
)
} else {
Animation::new(
self.id,
10,
AnimationEasing::OutBack,
Box::new(move |common, data| {
let rect = data.obj.get_as_mut::<WidgetRectangle>().unwrap();
set_anim_color(rect, data.pos * 0.2, rect_color, defaults.danger_color);
common.alterables.mark_redraw();
}),
)
};
common.alterables.animate(anim);
let interactable = self.interactable;
Box::new(move |_app, owc| {
let state = owc.active_state.as_mut().unwrap(); //want panic
state.interactable = interactable;
})
}
}
fn set_anim_color(
rect: &mut WidgetRectangle,
pos: f32,
rect_color: wgui::drawing::Color,
target_color: wgui::drawing::Color,
) {
// rect to target_color
rect.params.color.r = rect_color.r.lerp(target_color.r, pos);
rect.params.color.g = rect_color.g.lerp(target_color.g, pos);
rect.params.color.b = rect_color.b.lerp(target_color.b, pos);
}

View File

@@ -6,15 +6,14 @@ use std::{
time::{Duration, Instant},
};
use glam::{vec2, FloatExt, UVec2};
use glam::vec2;
use slotmap::Key;
use wgui::{
animation::{Animation, AnimationEasing},
components::{checkbox::ComponentCheckbox, slider::ComponentSlider},
event::EventCallback,
layout::{Layout, WidgetID},
event::{CallbackDataCommon, EventAlterables, EventCallback},
layout::Layout,
parser::{CustomAttribsInfoOwned, Fetchable},
widget::{rectangle::WidgetRectangle, EventResult},
widget::EventResult,
};
#[cfg(feature = "wayvr")]
@@ -22,15 +21,22 @@ use crate::{backend::task::TaskType, windowing::OverlaySelector};
use crate::{
backend::{input::HoverResult, task::TaskContainer},
gui::panel::{button::BUTTON_EVENTS, GuiPanel, NewGuiPanelParams},
overlays::edit::{
lock::InteractLockHandler, pos::PositioningHandler, tab::ButtonPaneTabSwitcher,
},
state::AppState,
subsystem::hid::WheelDelta,
windowing::{
backend::{DummyBackend, OverlayBackend, RenderResources, ShouldRender},
window::{OverlayWindowConfig, Positioning},
window::OverlayWindowConfig,
OverlayID,
},
};
mod lock;
mod pos;
mod tab;
struct LongPressButtonState {
pressed: Instant,
}
@@ -38,12 +44,10 @@ struct LongPressButtonState {
struct EditModeState {
tasks: Rc<RefCell<TaskContainer>>,
id: Rc<RefCell<OverlayID>>,
interact_lock: bool,
positioning: Positioning,
delete: LongPressButtonState,
rect_id: WidgetID,
rect_color: wgui::drawing::Color,
border_color: wgui::drawing::Color,
tabs: ButtonPaneTabSwitcher,
lock: InteractLockHandler,
pos: PositioningHandler,
}
type EditModeWrapPanel = GuiPanel<EditModeState>;
@@ -65,22 +69,14 @@ impl EditWrapperManager {
return Ok(());
}
let Some(meta) = owc.backend.frame_meta() else {
log::error!("META NULL");
return Ok(());
};
log::debug!("EditMode wrap on {}", owc.name);
let inner = mem::replace(&mut owc.backend, Box::new(DummyBackend {}));
let mut panel = self.panel_pool.pop();
if panel.is_none() {
panel = Some(make_edit_panel(
app,
UVec2::new(meta.extent[0], meta.extent[1]),
)?);
panel = Some(make_edit_panel(app)?);
}
let mut panel = panel.unwrap();
panel_new_assignment(&mut panel, id, owc, app)?;
reset_panel(&mut panel, id, owc)?;
owc.backend = Box::new(EditModeBackendWrapper {
inner: ManuallyDrop::new(inner),
@@ -214,27 +210,16 @@ impl OverlayBackend for EditModeBackendWrapper {
}
}
fn make_edit_panel(
app: &mut AppState,
overlay_resolution: UVec2,
) -> anyhow::Result<EditModeWrapPanel> {
log::error!(
"overlay res {} {}",
overlay_resolution.x,
overlay_resolution.y
);
fn make_edit_panel(app: &mut AppState) -> anyhow::Result<EditModeWrapPanel> {
let state = EditModeState {
id: Rc::new(RefCell::new(OverlayID::null())),
interact_lock: false,
positioning: Positioning::Static,
tasks: Rc::new(RefCell::new(TaskContainer::new())),
delete: LongPressButtonState {
pressed: Instant::now(),
},
rect_id: WidgetID::null(),
rect_color: wgui::drawing::Color::default(),
border_color: wgui::drawing::Color::default(),
tabs: ButtonPaneTabSwitcher::default(),
lock: InteractLockHandler::default(),
pos: PositioningHandler::default(),
};
let on_custom_attrib: Box<dyn Fn(&mut Layout, &CustomAttribsInfoOwned, &AppState)> =
@@ -251,58 +236,27 @@ fn make_edit_panel(
let callback: EventCallback<AppState, EditModeState> = match command {
"::EditModeToggleLock" => Box::new(move |common, _data, app, state| {
state.interact_lock = !state.interact_lock;
let defaults = app.wgui_globals.get().defaults.clone();
let rect_color = state.rect_color.clone();
let border_color = state.border_color.clone();
if state.interact_lock {
common.alterables.animate(Animation::new(
state.rect_id,
10,
AnimationEasing::OutBack,
Box::new(move |common, data| {
let rect = data.obj.get_as_mut::<WidgetRectangle>().unwrap();
set_anim_color(
rect,
data.pos * 0.2,
rect_color,
border_color,
defaults.danger_color,
);
common.alterables.mark_redraw();
}),
));
} else {
common.alterables.animate(Animation::new(
state.rect_id,
10,
AnimationEasing::OutQuad,
Box::new(move |common, data| {
let rect = data.obj.get_as_mut::<WidgetRectangle>().unwrap();
set_anim_color(
rect,
0.2 - (data.pos * 0.2),
rect_color,
border_color,
defaults.danger_color,
);
common.alterables.mark_redraw();
}),
));
};
let interactable = !state.interact_lock;
app.tasks.enqueue(TaskType::Overlay(
OverlaySelector::Id(state.id.borrow().clone()),
Box::new(move |_app, owc| {
let state = owc.active_state.as_mut().unwrap(); //want panic
state.interactable = interactable;
}),
));
let sel = OverlaySelector::Id(*state.id.borrow());
let task = state.lock.toggle(common, app);
app.tasks.enqueue(TaskType::Overlay(sel, task));
Ok(EventResult::Consumed)
}),
"::EditModeTab" => {
let tab_name = args.next().unwrap().to_owned();
Box::new(move |common, _data, _app, state| {
state.tabs.tab_button_clicked(common, &tab_name);
Ok(EventResult::Consumed)
})
}
"::EditModeSetPos" => {
let pos_key = args.next().unwrap().to_owned();
Box::new(move |common, _data, app, state| {
let sel = OverlaySelector::Id(*state.id.borrow());
let task = state.pos.pos_button_clicked(common, &pos_key);
app.tasks.enqueue(TaskType::Overlay(sel, task));
Ok(EventResult::Consumed)
})
}
"::EditModeDeletePress" => Box::new(move |_common, _data, _app, state| {
state.delete.pressed = Instant::now();
// TODO: animate to light up button after 2s
@@ -312,9 +266,12 @@ fn make_edit_panel(
if state.delete.pressed.elapsed() > Duration::from_secs(2) {
return Ok(EventResult::Pass);
}
app.tasks.enqueue(TaskType::DropOverlay(OverlaySelector::Id(
state.id.borrow().clone(),
)));
app.tasks.enqueue(TaskType::Overlay(
OverlaySelector::Id(*state.id.borrow()),
Box::new(move |_app, owc| {
owc.active_state = None;
}),
));
Ok(EventResult::Consumed)
}),
_ => return,
@@ -336,7 +293,10 @@ fn make_edit_panel(
},
)?;
set_up_shadow(&mut panel)?;
panel.state.pos = PositioningHandler::new(&mut panel)?;
panel.state.lock = InteractLockHandler::new(&mut panel)?;
panel.state.tabs = ButtonPaneTabSwitcher::new(&mut panel)?;
set_up_checkbox(&mut panel, "additive_box", cb_assign_additive)?;
set_up_slider(&mut panel, "alpha_slider", cb_assign_alpha)?;
set_up_slider(&mut panel, "curve_slider", cb_assign_curve)?;
@@ -344,7 +304,45 @@ fn make_edit_panel(
Ok(panel)
}
fn cb_assign_alpha(_app: &mut AppState, owc: &mut OverlayWindowConfig, alpha: f32) {
fn reset_panel(
panel: &mut EditModeWrapPanel,
id: OverlayID,
owc: &mut OverlayWindowConfig,
) -> anyhow::Result<()> {
*panel.state.id.borrow_mut() = id;
let state = owc.active_state.as_mut().unwrap();
let mut alterables = EventAlterables::default();
let mut common = CallbackDataCommon {
alterables: &mut alterables,
state: &panel.layout.state,
};
let c = panel
.parser_state
.fetch_component_as::<ComponentSlider>("alpha_slider")?;
c.set_value(&mut common, state.alpha);
let c = panel
.parser_state
.fetch_component_as::<ComponentSlider>("curve_slider")?;
c.set_value(&mut common, state.curvature.unwrap_or(0.0));
let c = panel
.parser_state
.fetch_component_as::<ComponentCheckbox>("additive_box")?;
c.set_checked(&mut common, state.additive);
panel.state.pos.reset(&mut common, state.positioning);
panel.state.lock.reset(&mut common, state.interactable);
panel.state.tabs.reset(&mut common);
panel.layout.process_alterables(alterables)?;
Ok(())
}
const fn cb_assign_alpha(_app: &mut AppState, owc: &mut OverlayWindowConfig, alpha: f32) {
owc.dirty = true;
owc.active_state.as_mut().unwrap().alpha = alpha;
}
@@ -358,7 +356,7 @@ fn cb_assign_curve(_app: &mut AppState, owc: &mut OverlayWindowConfig, curvature
};
}
fn cb_assign_additive(_app: &mut AppState, owc: &mut OverlayWindowConfig, additive: bool) {
const fn cb_assign_additive(_app: &mut AppState, owc: &mut OverlayWindowConfig, additive: bool) {
owc.dirty = true;
owc.active_state.as_mut().unwrap().additive = additive;
}
@@ -378,7 +376,7 @@ fn set_up_slider(
let e_value = e.value;
tasks.enqueue(TaskType::Overlay(
OverlaySelector::Id(overlay_id.borrow().clone()),
OverlaySelector::Id(*overlay_id.borrow()),
Box::new(move |app, owc| callback(app, owc, e_value)),
));
Ok(())
@@ -402,7 +400,7 @@ fn set_up_checkbox(
let e_checked = e.checked;
tasks.enqueue(TaskType::Overlay(
OverlaySelector::Id(overlay_id.borrow().clone()),
OverlaySelector::Id(*overlay_id.borrow()),
Box::new(move |app, owc| callback(app, owc, e_checked)),
));
Ok(())
@@ -410,92 +408,3 @@ fn set_up_checkbox(
Ok(())
}
fn set_up_shadow(panel: &mut EditModeWrapPanel) -> anyhow::Result<()> {
panel.state.rect_id = panel.parser_state.get_widget_id("shadow")?;
let shadow_rect = panel
.layout
.state
.widgets
.get_as::<WidgetRectangle>(panel.state.rect_id)
.ok_or_else(|| anyhow::anyhow!("Element with id=\"shadow\" must be a <rectangle>"))?;
panel.state.rect_color = shadow_rect.params.color;
panel.state.border_color = shadow_rect.params.border_color;
Ok(())
}
fn panel_new_assignment(
panel: &mut EditModeWrapPanel,
id: OverlayID,
owc: &mut OverlayWindowConfig,
app: &mut AppState,
) -> anyhow::Result<()> {
*panel.state.id.borrow_mut() = id;
let active_state = owc.active_state.as_mut().unwrap();
panel.state.interact_lock = !active_state.interactable;
panel.state.positioning = active_state.positioning;
let alpha = active_state.alpha;
let c = panel
.parser_state
.fetch_component_as::<ComponentSlider>("alpha_slider")?;
panel.component_make_call(c, Box::new(move |c, cdc| c.set_value(cdc, alpha)))?;
let curve = active_state.curvature.unwrap_or(0.0);
let c = panel
.parser_state
.fetch_component_as::<ComponentSlider>("curve_slider")?;
panel.component_make_call(c, Box::new(move |c, cdc| c.set_value(cdc, curve)))?;
let additive = active_state.additive;
let c = panel
.parser_state
.fetch_component_as::<ComponentCheckbox>("additive_box")?;
panel.component_make_call(c, Box::new(move |c, cdc| c.set_checked(cdc, additive)))?;
let mut rect = panel
.layout
.state
.widgets
.get_as::<WidgetRectangle>(panel.state.rect_id)
.unwrap(); // can only fail if set_up_rect has issues
if active_state.interactable {
set_anim_color(
&mut rect,
0.0,
panel.state.rect_color,
panel.state.border_color,
app.wgui_globals.get().defaults.danger_color,
);
} else {
set_anim_color(
&mut rect,
0.2,
panel.state.rect_color,
panel.state.border_color,
app.wgui_globals.get().defaults.danger_color,
);
}
Ok(())
}
fn set_anim_color(
rect: &mut WidgetRectangle,
pos: f32,
rect_color: wgui::drawing::Color,
border_color: wgui::drawing::Color,
target_color: wgui::drawing::Color,
) {
// rect to target_color
rect.params.color.r = rect_color.r.lerp(target_color.r, pos);
rect.params.color.g = rect_color.g.lerp(target_color.g, pos);
rect.params.color.b = rect_color.b.lerp(target_color.b, pos);
// border to white
rect.params.border_color.r = border_color.r.lerp(1.0, pos);
rect.params.border_color.g = border_color.g.lerp(1.0, pos);
rect.params.border_color.b = border_color.b.lerp(1.0, pos);
rect.params.border_color.a = border_color.a.lerp(1.0, pos);
}

View File

@@ -0,0 +1,154 @@
use std::{collections::HashMap, rc::Rc};
use wgui::{
components::button::ComponentButton, event::CallbackDataCommon, layout::WidgetID,
parser::Fetchable, renderer_vk::text::custom_glyph::CustomGlyphData,
widget::sprite::WidgetSprite,
};
use crate::{
backend::task::OverlayTask, overlays::edit::EditModeWrapPanel, state::LeftRight,
windowing::window::Positioning,
};
static POS_NAMES: [&str; 6] = ["static", "anchored", "floating", "hmd", "hand_l", "hand_r"];
struct PosButtonState {
name: &'static str,
sprite: CustomGlyphData,
component: Rc<ComponentButton>,
positioning: Positioning,
}
#[derive(Default)]
pub(super) struct PositioningHandler {
top_sprite_id: WidgetID,
buttons: HashMap<&'static str, Rc<PosButtonState>>,
active_button: Option<Rc<PosButtonState>>,
}
impl PositioningHandler {
pub fn new(panel: &mut EditModeWrapPanel) -> anyhow::Result<Self> {
let mut buttons = HashMap::new();
for name in &POS_NAMES {
let button_id = format!("pos_{name}");
let component = panel.parser_state.fetch_component_as(&button_id)?;
let sprite_id = format!("{button_id}_sprite");
let id = panel.parser_state.get_widget_id(&sprite_id)?;
let sprite_w = panel
.layout
.state
.widgets
.get_as::<WidgetSprite>(id)
.ok_or_else(|| {
anyhow::anyhow!("Element with id=\"{sprite_id}\" must be a <sprite>")
})?;
let sprite = sprite_w.params.glyph_data.clone().ok_or_else(|| {
anyhow::anyhow!("Element with id=\"{sprite_id}\" must have a valid src!")
})?;
buttons.insert(
*name,
Rc::new(PosButtonState {
component,
name,
sprite,
positioning: key_to_pos(name),
}),
);
}
let top_sprite_id = panel.parser_state.get_widget_id("top_pos_sprite")?;
Ok(Self {
buttons,
active_button: None,
top_sprite_id,
})
}
fn change_highlight(&mut self, common: &mut CallbackDataCommon, key: &str) {
if let Some(old) = self.active_button.take() {
old.component.set_sticky_state(common, false);
}
let new = self.buttons.get_mut(key).unwrap();
new.component.set_sticky_state(common, true);
self.active_button = Some(new.clone());
// change top sprite
if let Some(mut sprite) = common
.state
.widgets
.get_as::<WidgetSprite>(self.top_sprite_id)
{
sprite.params.glyph_data = Some(new.sprite.clone());
}
}
pub fn pos_button_clicked(
&mut self,
common: &mut CallbackDataCommon,
key: &str,
) -> Box<OverlayTask> {
self.change_highlight(common, key);
let pos = key_to_pos(key);
Box::new(move |app, owc| {
let state = owc.active_state.as_mut().unwrap(); //want panic
state.positioning = pos;
state.save_transform(app);
})
}
pub fn reset(&mut self, common: &mut CallbackDataCommon, pos: Positioning) {
let key = pos_to_key(pos);
self.change_highlight(common, key);
}
}
fn key_to_pos(key: &str) -> Positioning {
match key {
"static" => Positioning::Static,
"anchored" => Positioning::Anchored,
"floating" => Positioning::Floating,
"hmd" => Positioning::FollowHead { lerp: 1.0 },
"hand_l" => Positioning::FollowHand {
hand: LeftRight::Left,
lerp: 1.0,
},
"hand_r" => Positioning::FollowHand {
hand: LeftRight::Right,
lerp: 1.0,
},
_ => {
panic!("cannot translate to positioning: {key}")
}
}
}
const fn pos_to_key(pos: Positioning) -> &'static str {
match pos {
Positioning::Static => "static",
Positioning::Anchored => "anchored",
Positioning::Floating => "floating",
Positioning::FollowHead { .. } | Positioning::FollowHeadPaused { .. } => "hmd",
Positioning::FollowHand {
hand: LeftRight::Left,
..
}
| Positioning::FollowHandPaused {
hand: LeftRight::Left,
..
} => "hand_l",
Positioning::FollowHand {
hand: LeftRight::Right,
..
}
| Positioning::FollowHandPaused {
hand: LeftRight::Right,
..
} => "hand_r",
}
}

View File

@@ -0,0 +1,96 @@
use std::{collections::HashMap, rc::Rc};
use wgui::{
components::button::ComponentButton,
event::CallbackDataCommon,
layout::WidgetID,
parser::Fetchable,
taffy::{Display, Style},
};
use crate::overlays::edit::EditModeWrapPanel;
static TABS: [&str; 4] = ["none", "pos", "alpha", "curve"];
static BUTTON_PREFIX: &str = "top_";
static PANE_PREFIX: &str = "tab_";
#[derive(Clone)]
struct TabData {
button: Option<Rc<ComponentButton>>,
pane: WidgetID,
name: &'static str,
}
#[derive(Default)]
pub(super) struct ButtonPaneTabSwitcher {
tabs: HashMap<&'static str, Rc<TabData>>,
active_tab: Option<Rc<TabData>>,
}
impl ButtonPaneTabSwitcher {
pub fn new(panel: &mut EditModeWrapPanel) -> anyhow::Result<Self> {
let mut tabs = HashMap::new();
for tab_name in &TABS {
let name = format!("{BUTTON_PREFIX}{tab_name}");
let button = panel.parser_state.fetch_component_as(&name).ok();
let name = format!("{PANE_PREFIX}{tab_name}");
let pane = panel.parser_state.get_widget_id(&name)?;
tabs.insert(
*tab_name,
Rc::new(TabData {
button: button.clone(),
pane,
name: tab_name,
}),
);
}
Ok(Self {
tabs,
active_tab: None,
})
}
pub fn tab_button_clicked(&mut self, common: &mut CallbackDataCommon, mut tab: &str) {
// deactivate active tab
if let Some(old_tab) = self.active_tab.take() {
set_tab_active(common, &old_tab, false);
if old_tab.name == tab {
// close current tab
tab = "none";
}
}
let data = self.tabs[tab].clone();
set_tab_active(common, &data, true);
self.active_tab = Some(data);
}
pub fn reset(&mut self, common: &mut CallbackDataCommon) {
if let Some(data) = self.active_tab.take() {
set_tab_active(common, &data, false);
}
let data = self.tabs["none"].clone();
set_tab_active(common, &data, true);
self.active_tab = Some(data);
}
}
fn set_tab_active(common: &mut CallbackDataCommon, data: &TabData, active: bool) {
let pane_node = common.state.nodes[data.pane];
let style = Style {
display: if active {
Display::Block
} else {
Display::None
},
..Default::default()
};
common.alterables.set_style(pane_node, style);
if let Some(button) = data.button.as_ref() {
button.set_sticky_state(common, active);
}
}

View File

@@ -1,5 +1,4 @@
pub mod anchor;
pub mod bar;
pub mod custom;
pub mod edit;
pub mod keyboard;

View File

@@ -9,8 +9,8 @@ use crate::{
},
state::AppState,
windowing::{
Z_ORDER_WATCH,
window::{OverlayWindowConfig, OverlayWindowData, OverlayWindowState, Positioning},
Z_ORDER_WATCH,
},
};