opacity & additive & curvature works

This commit is contained in:
galister
2025-11-13 15:40:08 +09:00
parent d435fdb473
commit 595324814a
13 changed files with 397 additions and 56 deletions

View File

@@ -1,23 +1,52 @@
use std::{
any::Any,
cell::RefCell,
mem::{self, ManuallyDrop},
sync::Arc,
rc::Rc,
time::{Duration, Instant},
};
use glam::{UVec2, vec2};
use glam::{vec2, FloatExt, UVec2};
use slotmap::Key;
use wgui::{
animation::{Animation, AnimationEasing},
components::{checkbox::ComponentCheckbox, slider::ComponentSlider},
event::EventCallback,
layout::{Layout, WidgetID},
parser::{CustomAttribsInfoOwned, Fetchable},
widget::{rectangle::WidgetRectangle, EventResult},
};
#[cfg(feature = "wayvr")]
use crate::{backend::task::TaskType, windowing::OverlaySelector};
use crate::{
backend::input::HoverResult,
gui::panel::{GuiPanel, NewGuiPanelParams},
backend::{input::HoverResult, task::TaskContainer},
gui::panel::{button::BUTTON_EVENTS, GuiPanel, NewGuiPanelParams},
state::AppState,
subsystem::hid::WheelDelta,
windowing::{
backend::{DummyBackend, OverlayBackend, RenderResources, ShouldRender},
window::OverlayWindowConfig,
window::{OverlayWindowConfig, Positioning},
OverlayID,
},
};
type EditModeWrapPanel = GuiPanel<Arc<str>>;
struct LongPressButtonState {
pressed: Instant,
}
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,
}
type EditModeWrapPanel = GuiPanel<EditModeState>;
#[derive(Default)]
pub struct EditWrapperManager {
@@ -28,6 +57,7 @@ pub struct EditWrapperManager {
impl EditWrapperManager {
pub fn wrap_edit_mode(
&mut self,
id: OverlayID,
owc: &mut OverlayWindowConfig,
app: &mut AppState,
) -> anyhow::Result<()> {
@@ -50,7 +80,8 @@ impl EditWrapperManager {
)?);
}
let mut panel = panel.unwrap();
panel.state = owc.name.clone();
panel_new_assignment(&mut panel, id, owc, app)?;
owc.backend = Box::new(EditModeBackendWrapper {
inner: ManuallyDrop::new(inner),
panel: ManuallyDrop::new(panel),
@@ -102,6 +133,11 @@ impl OverlayBackend for EditModeBackendWrapper {
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)
@@ -188,15 +224,278 @@ fn make_edit_panel(
overlay_resolution.y
);
let panel = GuiPanel::new_from_template(
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(),
};
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| {
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;
}),
));
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::DropOverlay(OverlaySelector::Id(
state.id.borrow().clone(),
)));
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",
"".into(),
state,
NewGuiPanelParams {
on_custom_attrib: Some(on_custom_attrib),
resize_to_parent: true,
..Default::default()
},
)?;
set_up_shadow(&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 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)
};
}
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().clone()),
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().clone()),
Box::new(move |app, owc| callback(app, owc, e_checked)),
));
Ok(())
}));
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);
}