edit overlay progress
This commit is contained in:
111
wlx-overlay-s/src/overlays/edit/lock.rs
Normal file
111
wlx-overlay-s/src/overlays/edit/lock.rs
Normal 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);
|
||||
}
|
||||
410
wlx-overlay-s/src/overlays/edit/mod.rs
Normal file
410
wlx-overlay-s/src/overlays/edit/mod.rs
Normal file
@@ -0,0 +1,410 @@
|
||||
use std::{
|
||||
any::Any,
|
||||
cell::RefCell,
|
||||
mem::{self, ManuallyDrop},
|
||||
rc::Rc,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use glam::vec2;
|
||||
use slotmap::Key;
|
||||
use wgui::{
|
||||
components::{checkbox::ComponentCheckbox, slider::ComponentSlider},
|
||||
event::{CallbackDataCommon, EventAlterables, EventCallback},
|
||||
layout::Layout,
|
||||
parser::{CustomAttribsInfoOwned, Fetchable},
|
||||
widget::EventResult,
|
||||
};
|
||||
|
||||
#[cfg(feature = "wayvr")]
|
||||
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,
|
||||
OverlayID,
|
||||
},
|
||||
};
|
||||
|
||||
mod lock;
|
||||
mod pos;
|
||||
mod tab;
|
||||
|
||||
struct LongPressButtonState {
|
||||
pressed: Instant,
|
||||
}
|
||||
|
||||
struct EditModeState {
|
||||
tasks: Rc<RefCell<TaskContainer>>,
|
||||
id: Rc<RefCell<OverlayID>>,
|
||||
delete: LongPressButtonState,
|
||||
tabs: ButtonPaneTabSwitcher,
|
||||
lock: InteractLockHandler,
|
||||
pos: PositioningHandler,
|
||||
}
|
||||
|
||||
type EditModeWrapPanel = GuiPanel<EditModeState>;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct EditWrapperManager {
|
||||
edit_mode: bool,
|
||||
panel_pool: Vec<EditModeWrapPanel>,
|
||||
}
|
||||
|
||||
impl EditWrapperManager {
|
||||
pub fn wrap_edit_mode(
|
||||
&mut self,
|
||||
id: OverlayID,
|
||||
owc: &mut OverlayWindowConfig,
|
||||
app: &mut AppState,
|
||||
) -> anyhow::Result<()> {
|
||||
if owc.editing {
|
||||
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)?);
|
||||
}
|
||||
let mut panel = panel.unwrap();
|
||||
reset_panel(&mut panel, id, owc)?;
|
||||
|
||||
owc.backend = Box::new(EditModeBackendWrapper {
|
||||
inner: ManuallyDrop::new(inner),
|
||||
panel: ManuallyDrop::new(panel),
|
||||
});
|
||||
owc.editing = true;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn unwrap_edit_mode(&mut self, owc: &mut OverlayWindowConfig) {
|
||||
if !owc.editing {
|
||||
return;
|
||||
}
|
||||
|
||||
log::debug!("EditMode unwrap on {}", owc.name);
|
||||
let wrapper = mem::replace(&mut owc.backend, Box::new(DummyBackend {}));
|
||||
let mut wrapper: Box<dyn Any> = wrapper;
|
||||
let wrapper = wrapper
|
||||
.downcast_mut::<EditModeBackendWrapper>()
|
||||
.expect("Wrong type to unwrap");
|
||||
|
||||
let panel = unsafe { ManuallyDrop::take(&mut wrapper.panel) };
|
||||
self.panel_pool.push(panel);
|
||||
|
||||
let inner = unsafe { ManuallyDrop::take(&mut wrapper.inner) };
|
||||
owc.backend = inner;
|
||||
owc.editing = false;
|
||||
|
||||
// wrapper is destroyed with nothing left inside
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EditModeBackendWrapper {
|
||||
panel: ManuallyDrop<EditModeWrapPanel>,
|
||||
inner: ManuallyDrop<Box<dyn OverlayBackend>>,
|
||||
}
|
||||
|
||||
impl OverlayBackend for EditModeBackendWrapper {
|
||||
fn init(&mut self, app: &mut crate::state::AppState) -> anyhow::Result<()> {
|
||||
self.inner.init(app)?;
|
||||
self.panel.init(app)
|
||||
}
|
||||
fn pause(&mut self, app: &mut crate::state::AppState) -> anyhow::Result<()> {
|
||||
self.inner.pause(app)?;
|
||||
self.panel.pause(app)
|
||||
}
|
||||
fn resume(&mut self, app: &mut crate::state::AppState) -> anyhow::Result<()> {
|
||||
self.inner.resume(app)?;
|
||||
self.panel.resume(app)
|
||||
}
|
||||
fn should_render(&mut self, app: &mut crate::state::AppState) -> anyhow::Result<ShouldRender> {
|
||||
{
|
||||
let mut local_tasks = self.panel.state.tasks.borrow_mut();
|
||||
app.tasks.transfer_from(&mut local_tasks);
|
||||
}
|
||||
|
||||
let i = self.inner.should_render(app)?;
|
||||
|
||||
if !matches!(i, ShouldRender::Unable)
|
||||
&& let Some(ref frame_meta) = self.inner.frame_meta()
|
||||
{
|
||||
let (width_px, height_px) = (frame_meta.extent[0], frame_meta.extent[1]);
|
||||
|
||||
let new_size = vec2(width_px as _, height_px as _);
|
||||
if self.panel.max_size != new_size {
|
||||
log::debug!("EditWrapperGui size {} → {new_size}", self.panel.max_size);
|
||||
self.panel.max_size = new_size;
|
||||
|
||||
let gui_scale = width_px.min(height_px) as f32 / 550.0;
|
||||
self.panel.gui_scale = gui_scale;
|
||||
self.panel.update_layout()?;
|
||||
}
|
||||
} else {
|
||||
return Ok(ShouldRender::Unable);
|
||||
}
|
||||
|
||||
let p = self.panel.should_render(app)?;
|
||||
|
||||
#[allow(clippy::match_same_arms)]
|
||||
Ok(match (i, p) {
|
||||
(ShouldRender::Should, ShouldRender::Should) => ShouldRender::Should,
|
||||
(ShouldRender::Should, ShouldRender::Can) => ShouldRender::Should,
|
||||
(ShouldRender::Can, ShouldRender::Should) => ShouldRender::Should,
|
||||
(ShouldRender::Can, ShouldRender::Can) => ShouldRender::Can,
|
||||
_ => ShouldRender::Unable,
|
||||
})
|
||||
}
|
||||
fn render(
|
||||
&mut self,
|
||||
app: &mut crate::state::AppState,
|
||||
rdr: &mut RenderResources,
|
||||
) -> anyhow::Result<()> {
|
||||
self.inner.render(app, rdr)?;
|
||||
self.panel.render(app, rdr)
|
||||
}
|
||||
fn frame_meta(&mut self) -> Option<crate::windowing::backend::FrameMeta> {
|
||||
self.inner.frame_meta()
|
||||
}
|
||||
fn on_hover(
|
||||
&mut self,
|
||||
app: &mut crate::state::AppState,
|
||||
hit: &crate::backend::input::PointerHit,
|
||||
) -> HoverResult {
|
||||
// pass through hover events to force pipewire to capture frames for us
|
||||
let _ = self.inner.on_hover(app, hit);
|
||||
self.panel.on_hover(app, hit)
|
||||
}
|
||||
fn on_left(&mut self, app: &mut crate::state::AppState, pointer: usize) {
|
||||
self.inner.on_left(app, pointer);
|
||||
self.panel.on_left(app, pointer);
|
||||
}
|
||||
fn on_pointer(
|
||||
&mut self,
|
||||
app: &mut crate::state::AppState,
|
||||
hit: &crate::backend::input::PointerHit,
|
||||
pressed: bool,
|
||||
) {
|
||||
self.panel.on_pointer(app, hit, pressed);
|
||||
}
|
||||
fn on_scroll(
|
||||
&mut self,
|
||||
app: &mut crate::state::AppState,
|
||||
hit: &crate::backend::input::PointerHit,
|
||||
delta: WheelDelta,
|
||||
) {
|
||||
self.panel.on_scroll(app, hit, delta);
|
||||
}
|
||||
fn get_interaction_transform(&mut self) -> Option<glam::Affine2> {
|
||||
self.inner.get_interaction_transform()
|
||||
}
|
||||
}
|
||||
|
||||
fn make_edit_panel(app: &mut AppState) -> anyhow::Result<EditModeWrapPanel> {
|
||||
let state = EditModeState {
|
||||
id: Rc::new(RefCell::new(OverlayID::null())),
|
||||
tasks: Rc::new(RefCell::new(TaskContainer::new())),
|
||||
delete: LongPressButtonState {
|
||||
pressed: Instant::now(),
|
||||
},
|
||||
tabs: ButtonPaneTabSwitcher::default(),
|
||||
lock: InteractLockHandler::default(),
|
||||
pos: PositioningHandler::default(),
|
||||
};
|
||||
|
||||
let on_custom_attrib: Box<dyn Fn(&mut Layout, &CustomAttribsInfoOwned, &AppState)> =
|
||||
Box::new(move |layout, attribs, _app| {
|
||||
for (name, kind) in &BUTTON_EVENTS {
|
||||
let Some(action) = attribs.get_value(name) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let mut args = action.split_whitespace();
|
||||
let Some(command) = args.next() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let callback: EventCallback<AppState, EditModeState> = match command {
|
||||
"::EditModeToggleLock" => Box::new(move |common, _data, app, state| {
|
||||
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
|
||||
Ok(EventResult::Consumed)
|
||||
}),
|
||||
"::EditModeDeleteRelease" => Box::new(move |_common, _data, app, state| {
|
||||
if state.delete.pressed.elapsed() > Duration::from_secs(2) {
|
||||
return Ok(EventResult::Pass);
|
||||
}
|
||||
app.tasks.enqueue(TaskType::Overlay(
|
||||
OverlaySelector::Id(*state.id.borrow()),
|
||||
Box::new(move |_app, owc| {
|
||||
owc.active_state = None;
|
||||
}),
|
||||
));
|
||||
Ok(EventResult::Consumed)
|
||||
}),
|
||||
_ => return,
|
||||
};
|
||||
|
||||
let id = layout.add_event_listener(attribs.widget_id, *kind, callback);
|
||||
log::debug!("Registered {action} on {:?} as {id:?}", attribs.widget_id);
|
||||
}
|
||||
});
|
||||
|
||||
let mut panel = GuiPanel::new_from_template(
|
||||
app,
|
||||
"gui/edit.xml",
|
||||
state,
|
||||
NewGuiPanelParams {
|
||||
on_custom_attrib: Some(on_custom_attrib),
|
||||
resize_to_parent: true,
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
|
||||
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)?;
|
||||
|
||||
Ok(panel)
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
fn cb_assign_curve(_app: &mut AppState, owc: &mut OverlayWindowConfig, curvature: f32) {
|
||||
owc.dirty = true;
|
||||
owc.active_state.as_mut().unwrap().curvature = if curvature < 0.005 {
|
||||
None
|
||||
} else {
|
||||
Some(curvature)
|
||||
};
|
||||
}
|
||||
|
||||
const fn cb_assign_additive(_app: &mut AppState, owc: &mut OverlayWindowConfig, additive: bool) {
|
||||
owc.dirty = true;
|
||||
owc.active_state.as_mut().unwrap().additive = additive;
|
||||
}
|
||||
|
||||
fn set_up_slider(
|
||||
panel: &mut EditModeWrapPanel,
|
||||
id: &str,
|
||||
callback: fn(&mut AppState, &mut OverlayWindowConfig, f32),
|
||||
) -> anyhow::Result<()> {
|
||||
let slider = panel
|
||||
.parser_state
|
||||
.fetch_component_as::<ComponentSlider>(id)?;
|
||||
let tasks = panel.state.tasks.clone();
|
||||
let overlay_id = panel.state.id.clone();
|
||||
slider.on_value_changed(Box::new(move |_common, e| {
|
||||
let mut tasks = tasks.borrow_mut();
|
||||
let e_value = e.value;
|
||||
|
||||
tasks.enqueue(TaskType::Overlay(
|
||||
OverlaySelector::Id(*overlay_id.borrow()),
|
||||
Box::new(move |app, owc| callback(app, owc, e_value)),
|
||||
));
|
||||
Ok(())
|
||||
}));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_up_checkbox(
|
||||
panel: &mut EditModeWrapPanel,
|
||||
id: &str,
|
||||
callback: fn(&mut AppState, &mut OverlayWindowConfig, bool),
|
||||
) -> anyhow::Result<()> {
|
||||
let checkbox = panel
|
||||
.parser_state
|
||||
.fetch_component_as::<ComponentCheckbox>(id)?;
|
||||
let tasks = panel.state.tasks.clone();
|
||||
let overlay_id = panel.state.id.clone();
|
||||
checkbox.on_toggle(Box::new(move |_common, e| {
|
||||
let mut tasks = tasks.borrow_mut();
|
||||
let e_checked = e.checked;
|
||||
|
||||
tasks.enqueue(TaskType::Overlay(
|
||||
OverlaySelector::Id(*overlay_id.borrow()),
|
||||
Box::new(move |app, owc| callback(app, owc, e_checked)),
|
||||
));
|
||||
Ok(())
|
||||
}));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
154
wlx-overlay-s/src/overlays/edit/pos.rs
Normal file
154
wlx-overlay-s/src/overlays/edit/pos.rs
Normal 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",
|
||||
}
|
||||
}
|
||||
96
wlx-overlay-s/src/overlays/edit/tab.rs
Normal file
96
wlx-overlay-s/src/overlays/edit/tab.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user