wgui: Refactoring, various changes (see desc)

- use parking_lot for mutex (less restrictive and easier to use)
- simplify event callbacks and widget type casting
- defer component initialization at start (required for setting the initial state of sliders)
- fix non-working scroll events
- update testbed.xml
- replace slider with the real one in bar.xml
- show slider text on its handle
This commit is contained in:
Aleksander
2025-07-29 22:12:01 +02:00
parent f950273a2d
commit 4e46c45bcf
21 changed files with 450 additions and 334 deletions

1
Cargo.lock generated
View File

@@ -5644,6 +5644,7 @@ dependencies = [
"log",
"lru",
"ouroboros",
"parking_lot",
"regex",
"resvg",
"roxmltree 0.20.0",

View File

@@ -11,7 +11,7 @@
<rectangle position="absolute" color="#333333" width="100%" height="100%" />
<!-- left/right separator (menu and rest) -->
<div flex_direction="row" width="100%" height="100%" gap="8">
<div flex_direction="row" gap="8" width="950" height="550">
<!-- LEFT MENU -->
<div id="menu" width="48" min_width="48" max_width="48" height="100%" align_items="center" justify_content="center">
<rectangle

View File

@@ -6,82 +6,92 @@
</theme>
<elements>
<div overflow_y="scroll" overflow_x="scroll">
<!-- 1/3 red rect -->
<rectangle width="100%" padding="16" box_sizing="content_box" flex_wrap="wrap" gap="16" align_content="flex_start" color="#223344">
<rectangle color="#99AAFF" round="100%" width="128" height="64" align_items="center" justify_content="center" border="8" border_color="#FFFFFF">
<label text="I'm centered" color="#FF0000" weight="bold" />
</rectangle>
<div flex_direction="column">
<label text="I'm a text!" color="~aqua1" />
<label text="I'm a bold text!" color="~aqua2" weight="bold" />
<label text="I'm a BIG text!" color="~aqua3" weight="bold" size="20" />
</div>
<rectangle round="4" id="my_div_parent" color="#FFCC99" align_items="center" align_content="flex_start" justify_content="center" flex_wrap="wrap" gap="8" padding="16" border="2" border_color="#ffffff">
<!-- filled-in at runtime -->
</rectangle>
<rectangle width="400" round="8" padding="2" align_items="center" border_color="#ffffff">
<include src="testbed/icons.xml" />
</rectangle>
<rectangle width="750" height="550" color="#FFFFFF" flex_direction="column" overflow_y="scroll">
<div flex_direction="column" padding="16">
<label weight="bold" text="Try these environment variables:" />
<label text="TESTBED=bar" />
<label text="TESTBED=various_widgets" />
<label text="TESTBED=watch" />
<label text="TESTBED=dashboard" />
</div>
<!-- Embed sprites -->
<sprite width="64" height="64" src="raster.png" />
<sprite width="64" height="64" src="dashboard/wayvr_dashboard.svg" />
</rectangle>
<!-- 2/3 green rects -->
<div width="100%" height="100%" flex_direction="column">
<rectangle color="#44aa33" color2="#3344aa" round="4" gradient="radial" height="100%" border="2" border_color="#ffffff">
<div margin="16" width="100%" height="100%" align_items="center" justify_content="center" flex_wrap="wrap" align_content="center">
<rectangle width="64" height="64" color="#000000" />
<rectangle width="64" height="64" color="#002222" />
<rectangle width="64" height="64" color="#004444" />
<rectangle width="64" height="64" color="#006666" />
<div flex_direction="column">
<!-- 1/3 red rect -->
<rectangle padding="16" box_sizing="content_box" flex_wrap="wrap" gap="16" align_content="flex_start" color="#223344">
<rectangle color="#99AAFF" round="100%" width="128" height="48" align_items="center" justify_content="center" border="8" border_color="#FFFFFF">
<label text="I'm centered" color="#FF0000" weight="bold" />
</rectangle>
<div flex_direction="column">
<label text="I'm a text!" color="~aqua1" />
<label text="I'm a bold text!" color="~aqua2" weight="bold" />
<label text="I'm a BIG text!" color="~aqua3" weight="bold" size="20" />
</div>
<rectangle round="4" id="my_div_parent" color="#FFCC99" align_items="center" align_content="flex_start" justify_content="center" flex_wrap="wrap" gap="8" padding="16" border="2" border_color="#ffffff">
<!-- filled-in at runtime -->
</rectangle>
<rectangle width="400" round="8" padding="2" align_items="center" border_color="#ffffff">
<include src="testbed/icons.xml" />
</rectangle>
<!-- Embed sprites -->
<sprite width="64" height="64" src="raster.png" />
<sprite width="64" height="64" src="dashboard/wayvr_dashboard.svg" />
</rectangle>
<rectangle color="#aaff88" color2="#88aaff" round="4" gradient="vertical" height="100%">
<div margin="16" width="100%" height="100%" flex_wrap="wrap" gap="16" align_content="flex_start">
<rectangle width="64" height="32" color="#ff0000" />
<rectangle width="64" height="32" color="#ff0000" border="2" border_color="#000000" />
<rectangle width="64" height="32" color="#ff0000" border="4" border_color="#000000" />
<rectangle width="64" height="32" color="#ff0000" border="6" border_color="#000000" />
<rectangle width="64" height="32" color="#ff0000" border="8" border_color="#000000" />
<rectangle width="64" height="64" color="#000000" border="2" round="100%" border_color="#FFFF00" />
<rectangle width="64" height="64" color="#000000" border="8" round="100%" border_color="#FFFF00" />
<rectangle width="64" height="64" color="#000000" border="16" round="100%" border_color="#FFFF00" />
<rectangle width="64" height="64" color="#000000" border="30" round="100%" border_color="#FFFF00" />
<rectangle width="64" height="64" color="#000000" border="2" round="50%" border_color="#FFFF00" />
<rectangle width="64" height="64" color="#000000" border="8" round="50%" border_color="#FFFF00" />
<rectangle width="64" height="64" color="#000000" border="12" round="50%" border_color="#FFFF00" />
<rectangle width="64" height="64" color="#000000" border="13" round="50%" border_color="#FFFF00" />
<rectangle width="64" height="64" color="#000000" border="14" round="50%" border_color="#FFFF00" />
<rectangle width="64" height="64" color="#000000" border="15" round="50%" border_color="#FFFF00" />
<rectangle width="64" height="64" color="#000000" border="16.5" round="50%" border_color="#FFFF00" />
<rectangle width="64" height="64" color="#000000" border="30" round="50%" border_color="#FFFF00" />
<rectangle color="#000000">
<rectangle width="200" height="100" color="#ffffff" margin="2" />
</rectangle>
<!-- 2/3 green rects -->
<div flex_direction="column">
<rectangle color="#44aa33" color2="#3344aa" round="4" gradient="radial" height="100%" border="2" border_color="#ffffff">
<div margin="16" width="100%" height="100%" align_items="center" justify_content="center" flex_wrap="wrap" align_content="center">
<rectangle width="64" height="64" color="#000000" />
<rectangle width="64" height="64" color="#002222" />
<rectangle width="64" height="64" color="#004444" />
<rectangle width="64" height="64" color="#006666" />
</div>
</rectangle>
<rectangle color="#aaff88" color2="#88aaff" round="4" gradient="vertical" height="100%">
<div margin="16" width="100%" height="100%" flex_wrap="wrap" gap="16" align_content="flex_start">
<rectangle width="64" height="32" color="#ff0000" />
<rectangle width="64" height="32" color="#ff0000" border="2" border_color="#000000" />
<rectangle width="64" height="32" color="#ff0000" border="4" border_color="#000000" />
<rectangle width="64" height="32" color="#ff0000" border="6" border_color="#000000" />
<rectangle width="64" height="32" color="#ff0000" border="8" border_color="#000000" />
<rectangle width="64" height="64" color="#000000" border="2" round="100%" border_color="#FFFF00" />
<rectangle width="64" height="64" color="#000000" border="8" round="100%" border_color="#FFFF00" />
<rectangle width="64" height="64" color="#000000" border="16" round="100%" border_color="#FFFF00" />
<rectangle width="64" height="64" color="#000000" border="30" round="100%" border_color="#FFFF00" />
<rectangle width="64" height="64" color="#000000" border="2" round="50%" border_color="#FFFF00" />
<rectangle width="64" height="64" color="#000000" border="8" round="50%" border_color="#FFFF00" />
<rectangle width="64" height="64" color="#000000" border="12" round="50%" border_color="#FFFF00" />
<rectangle width="64" height="64" color="#000000" border="13" round="50%" border_color="#FFFF00" />
<rectangle width="64" height="64" color="#000000" border="14" round="50%" border_color="#FFFF00" />
<rectangle width="64" height="64" color="#000000" border="15" round="50%" border_color="#FFFF00" />
<rectangle width="64" height="64" color="#000000" border="16.5" round="50%" border_color="#FFFF00" />
<rectangle width="64" height="64" color="#000000" border="30" round="50%" border_color="#FFFF00" />
<rectangle color="#000000">
<rectangle width="200" height="100" color="#ffffff" margin="2" />
</rectangle>
</div>
</rectangle>
</div>
<!-- 3/3 yellow rect -->
<rectangle color="#ffff99" flex_direction="column" padding="16">
<div height="100%" flex_direction="column">
<label text="Multi line test #1&#xA;This is aligned to the left." align="left" size="20" color="#330000" />
<rectangle width="100%" height="1" />
</div>
<div height="100%" flex_direction="column">
<label text="Multi line test #2&#xA;This is aligned to the right." align="right" size="20" color="#330000" />
<rectangle width="100%" height="1" />
</div>
<div height="100%" flex_direction="column">
<label text="Multi line test #3&#xA;This is aligned to the center.&#xA;The longer lines are still aligned to the center." align="center" size="20" color="#330000" />
<rectangle width="100%" height="1" />
</div>
<div height="100%" flex_direction="column">
<label text="Multi line test #4&#xA;This is justified alignment.&#xA;The longer lines are the same length as the shorter lines." align="justified" size="20" color="#330000" />
<rectangle width="100%" height="1" />
</div>
</rectangle>
</div>
<!-- 3/3 yellow rect -->
<rectangle color="#ffff99" width="100%" height="100%" flex_direction="column" padding="16">
<div height="100%" flex_direction="column">
<label text="Multi line test #1&#xA;This is aligned to the left." align="left" size="20" color="#330000" />
<rectangle width="100%" height="1" />
</div>
<div height="100%" flex_direction="column">
<label text="Multi line test #2&#xA;This is aligned to the right." align="right" size="20" color="#330000" />
<rectangle width="100%" height="1" />
</div>
<div height="100%" flex_direction="column">
<label text="Multi line test #3&#xA;This is aligned to the center.&#xA;The longer lines are still alinged to the center." align="center" size="20" color="#330000" />
<rectangle width="100%" height="1" />
</div>
<div height="100%" flex_direction="column">
<label text="Multi line test #4&#xA;This is justified alignment.&#xA;The longer lines are the same length as the shorter lines." align="justified" size="20" color="#330000" />
<rectangle width="100%" height="1" />
</div>
</rectangle>
</div>
</rectangle>
</elements>
</layout>

View File

@@ -4,8 +4,5 @@
<var key="bg_color_active" value="#44475a" />
<var key="device_color" value="#666666" />
<var key="text_color" value="#dddddd" />
<var key="slider_bg_color" value="#666666" />
<var key="slider_fg_color" value="#ffffff" />
<var key="slider_text_color" value="#000000" />
</theme>
</layout>

View File

@@ -18,6 +18,7 @@ image = { version = "0.25.6", default-features = false, features = [
log = { workspace = true }
lru = "0.14.0"
ouroboros = "0.18.5"
parking_lot = "0.12.4"
regex = "1.11.1"
resvg = { version = "0.45.1", default-features = false }
roxmltree = "0.20.0"

View File

@@ -2,7 +2,7 @@ use glam::{FloatExt, Vec2};
use crate::{
event::{CallbackDataCommon, EventAlterables, EventRefs},
layout::{WidgetID, WidgetMap, WidgetNodeMap},
layout::WidgetID,
widget::{WidgetData, WidgetObj},
};
@@ -90,14 +90,14 @@ impl Animation {
}
fn call(&self, refs: &EventRefs, alterables: &mut EventAlterables, pos: f32) {
let Some(widget) = refs.widget_map.get(self.target_widget).cloned() else {
let Some(widget) = refs.widgets.get(self.target_widget).cloned() else {
return; // failed
};
let widget_node = *refs.widget_node_map.get(self.target_widget).unwrap();
let widget_node = *refs.nodes.get(self.target_widget).unwrap();
let layout = refs.tree.layout(widget_node).unwrap(); // should always succeed
let mut widget = widget.lock().unwrap();
let mut widget = widget.lock();
let (data, obj) = widget.get_data_obj_mut();

View File

@@ -3,7 +3,7 @@ use taffy::{AlignItems, JustifyContent, prelude::length};
use crate::{
animation::{Animation, AnimationEasing},
components::Component,
components::{Component, InitData},
drawing::{self, Color},
event::{CallbackDataCommon, EventListenerCollection, EventListenerKind, ListenerHandleVec},
layout::{Layout, WidgetID},
@@ -50,15 +50,20 @@ pub struct Button {
listener_handles: ListenerHandleVec,
}
impl Component for Button {}
impl Component for Button {
fn init(&self, _data: &mut InitData) {}
}
impl Button {
pub fn set_text<C>(&self, callback_data: &mut CallbackDataCommon, text: &str) {
callback_data.call_on_widget(self.data.text_id, |label: &mut TextLabel| {
label.set_text(text);
});
callback_data.mark_redraw();
callback_data.mark_dirty(self.data.text_node);
pub fn set_text<C>(&self, common: &mut CallbackDataCommon, text: &str) {
common
.refs
.widgets
.call(self.data.text_id, |label: &mut TextLabel| {
label.set_text(text);
});
common.alterables.mark_redraw();
common.alterables.mark_dirty(self.data.text_node);
}
}
@@ -77,12 +82,12 @@ fn anim_hover(rect: &mut Rectangle, data: &Data, pos: f32) {
fn anim_hover_in(data: Rc<Data>, widget_id: WidgetID) -> Animation {
Animation::new(
widget_id,
10,
5,
AnimationEasing::OutQuad,
Box::new(move |common, anim_data| {
let rect = anim_data.obj.get_as_mut::<Rectangle>();
anim_hover(rect, &data, anim_data.pos);
common.mark_redraw();
common.alterables.mark_redraw();
}),
)
}
@@ -90,12 +95,12 @@ fn anim_hover_in(data: Rc<Data>, widget_id: WidgetID) -> Animation {
fn anim_hover_out(data: Rc<Data>, widget_id: WidgetID) -> Animation {
Animation::new(
widget_id,
15,
8,
AnimationEasing::OutQuad,
Box::new(move |common, anim_data| {
let rect = anim_data.obj.get_as_mut::<Rectangle>();
anim_hover(rect, &data, 1.0 - anim_data.pos);
common.mark_redraw();
common.alterables.mark_redraw();
}),
)
}
@@ -164,7 +169,9 @@ pub fn construct<U1, U2>(
rect_id,
EventListenerKind::MouseEnter,
Box::new(move |common, event_data, _, _| {
common.animate(anim_hover_in(data.clone(), event_data.widget_id));
common
.alterables
.animate(anim_hover_in(data.clone(), event_data.widget_id));
}),
);
@@ -174,12 +181,17 @@ pub fn construct<U1, U2>(
rect_id,
EventListenerKind::MouseLeave,
Box::new(move |common, event_data, _, _| {
common.animate(anim_hover_out(data.clone(), event_data.widget_id));
common
.alterables
.animate(anim_hover_out(data.clone(), event_data.widget_id));
}),
);
Ok(Rc::new(Button {
let button = Rc::new(Button {
data: _data.clone(),
listener_handles,
}))
});
layout.defer_component_init(button.clone());
Ok(button)
}

View File

@@ -1,6 +1,20 @@
use crate::any::AnyTrait;
use taffy::TaffyTree;
use crate::{
any::AnyTrait,
event::EventAlterables,
layout::{WidgetID, WidgetMap},
};
pub mod button;
pub mod slider;
pub trait Component: AnyTrait {}
pub struct InitData<'a> {
pub alterables: &'a mut EventAlterables,
pub widgets: &'a WidgetMap,
pub tree: &'a TaffyTree<WidgetID>,
}
pub trait Component: AnyTrait {
fn init(&self, data: &mut InitData);
}

View File

@@ -1,23 +1,28 @@
use std::{
cell::{RefCell, RefMut},
rc::Rc,
};
use std::{cell::RefCell, rc::Rc};
use glam::{Mat4, Vec2, Vec3};
use taffy::prelude::{length, percent};
use taffy::{
TaffyTree,
prelude::{length, percent},
};
use crate::{
animation::{Animation, AnimationEasing},
components::Component,
components::{Component, InitData},
drawing::{self},
event::{
self, CallbackDataCommon, EventListenerCollection, EventListenerKind, ListenerHandleVec,
self, CallbackDataCommon, EventAlterables, EventListenerCollection, EventListenerKind,
ListenerHandleVec,
},
layout::{Layout, WidgetID, WidgetMap},
renderer_vk::{
text::{FontWeight, HorizontalAlign, TextStyle},
util,
},
layout::{Layout, WidgetID},
renderer_vk::util,
widget::{
div::Div,
rectangle::{Rectangle, RectangleParams},
text::{TextLabel, TextParams},
util::WLength,
},
};
@@ -53,8 +58,8 @@ pub struct SliderState {
struct Data {
body: WidgetID, // Div
slider_handle_id: WidgetID, // Div
slider_handle_rect_id: WidgetID, // Rectangle
slider_text_id: WidgetID, // Text
slider_handle_node: taffy::NodeId,
slider_body_node: taffy::NodeId,
}
@@ -62,10 +67,24 @@ struct Data {
pub struct Slider {
data: Rc<Data>,
state: Rc<RefCell<SliderState>>,
#[allow(dead_code)]
listener_handles: ListenerHandleVec,
}
impl Component for Slider {}
impl Component for Slider {
fn init(&self, init_data: &mut InitData) {
let mut state = self.state.borrow_mut();
let value = state.values.value;
state.set_value(
&self.data,
init_data.alterables,
init_data.widgets,
init_data.tree,
value,
);
}
}
// NOTICE: this can be re-used in the future
fn map_mouse_x_to_normalized(mouse_x_rel: f32, widget_width: f32) -> f32 {
@@ -109,34 +128,44 @@ impl SliderState {
let norm = map_mouse_x_to_normalized(
mouse_pos.x - HANDLE_WIDTH / 2.0,
get_width(data.slider_body_node, common.get_tree()) - HANDLE_WIDTH,
get_width(data.slider_body_node, common.refs.tree) - HANDLE_WIDTH,
);
let target_value = self.values.get_from_normalized(norm);
let val = target_value;
self.set_value(data, common, val);
self.set_value(
data,
common.alterables,
common.refs.widgets,
common.refs.tree,
val,
);
}
fn set_value(&mut self, data: &Data, common: &mut CallbackDataCommon, value: f32) {
fn update_text(&self, text: &mut TextLabel, value: f32) {
// round displayed value, should be sufficient for now
text.set_text(&format!("{}", value.round()));
}
fn set_value(
&mut self,
data: &Data,
alterables: &mut EventAlterables,
widgets: &WidgetMap,
tree: &TaffyTree<WidgetID>,
value: f32,
) {
//common.call_on_widget(data.slider_handle_id, |_div: &mut Div| {});
self.values.value = value;
let mut style = common
.refs
.tree
.style(data.slider_handle_node)
.unwrap()
.clone();
conf_handle_style(
&self.values,
data.slider_body_node,
&mut style,
common.get_tree(),
);
common.mark_dirty(data.slider_handle_node);
common.mark_redraw();
common.set_style(data.slider_handle_node, style);
let mut style = tree.style(data.slider_handle_node).unwrap().clone();
conf_handle_style(&self.values, data.slider_body_node, &mut style, tree);
alterables.mark_dirty(data.slider_handle_node);
alterables.mark_redraw();
alterables.set_style(data.slider_handle_node, style);
widgets.call(data.slider_text_id, |label: &mut TextLabel| {
self.update_text(label, value);
});
}
}
@@ -162,21 +191,21 @@ fn anim_rect(rect: &mut Rectangle, pos: f32) {
}
fn on_enter_anim(common: &mut event::CallbackDataCommon, handle_id: WidgetID) {
common.animate(Animation::new(
common.alterables.animate(Animation::new(
handle_id,
5,
AnimationEasing::OutQuad,
20,
AnimationEasing::OutBack,
Box::new(move |common, data| {
let rect = data.obj.get_as_mut::<Rectangle>();
data.data.transform = get_anim_transform(data.pos, data.widget_size);
anim_rect(rect, data.pos);
common.mark_redraw();
common.alterables.mark_redraw();
}),
));
}
fn on_leave_anim(common: &mut event::CallbackDataCommon, handle_id: WidgetID) {
common.animate(Animation::new(
common.alterables.animate(Animation::new(
handle_id,
10,
AnimationEasing::OutQuad,
@@ -184,7 +213,7 @@ fn on_leave_anim(common: &mut event::CallbackDataCommon, handle_id: WidgetID) {
let rect = data.obj.get_as_mut::<Rectangle>();
data.data.transform = get_anim_transform(1.0 - data.pos, data.widget_size);
anim_rect(rect, 1.0 - data.pos);
common.mark_redraw();
common.alterables.mark_redraw();
}),
));
}
@@ -200,7 +229,7 @@ fn register_event_mouse_enter<U1, U2>(
data.body,
EventListenerKind::MouseEnter,
Box::new(move |common, _data, _, _| {
common.trigger_haptics();
common.alterables.trigger_haptics();
state.borrow_mut().hovered = true;
on_enter_anim(common, data.slider_handle_rect_id);
}),
@@ -218,7 +247,7 @@ fn register_event_mouse_leave<U1, U2>(
data.body,
EventListenerKind::MouseLeave,
Box::new(move |common, _data, _, _| {
common.trigger_haptics();
common.alterables.trigger_haptics();
state.borrow_mut().hovered = false;
on_leave_anim(common, data.slider_handle_rect_id);
}),
@@ -256,7 +285,7 @@ fn register_event_mouse_press<U1, U2>(
data.body,
EventListenerKind::MousePress,
Box::new(move |common, event_data, _, _| {
common.trigger_haptics();
common.alterables.trigger_haptics();
let mut state = state.borrow_mut();
if state.hovered {
@@ -278,7 +307,7 @@ fn register_event_mouse_release<U1, U2>(
data.body,
EventListenerKind::MouseRelease,
Box::new(move |common, _data, _, _| {
common.trigger_haptics();
common.alterables.trigger_haptics();
let mut state = state.borrow_mut();
if state.dragging {
@@ -340,14 +369,6 @@ pub fn construct<U1, U2>(
..Default::default()
};
// TODO: dispatch style config after this taffy tree did a re-layout
/*conf_handle_style(
&params.values,
slider_body_node,
&mut slider_handle_style,
&layout.tree,
);*/
// invisible outer handle body
let (slider_handle_id, slider_handle_node) =
layout.add_child(body_id, Div::create()?, slider_handle_style)?;
@@ -371,19 +392,34 @@ pub fn construct<U1, U2>(
},
)?;
let state = SliderState {
dragging: false,
hovered: false,
values: params.values,
};
let (slider_text_id, _) = layout.add_child(
slider_handle_id,
TextLabel::create(TextParams {
content: String::new(),
style: TextStyle {
weight: Some(FontWeight::Bold),
align: Some(HorizontalAlign::Center),
..Default::default()
},
})?,
Default::default(),
)?;
let data = Rc::new(Data {
body: body_id,
slider_handle_node,
slider_handle_rect_id,
slider_handle_id,
slider_body_node,
slider_text_id,
});
let state = Rc::new(RefCell::new(SliderState {
dragging: false,
hovered: false,
values: params.values,
}));
let state = Rc::new(RefCell::new(state));
let mut lhandles = ListenerHandleVec::default();
@@ -400,5 +436,6 @@ pub fn construct<U1, U2>(
listener_handles: lhandles,
});
layout.defer_component_init(slider.clone());
Ok(slider)
}

View File

@@ -116,7 +116,7 @@ fn draw_widget(
return;
};
let mut widget_state = widget.lock().unwrap();
let mut widget_state = widget.lock();
let transform = widget_state.data.transform * *parent_transform;

View File

@@ -88,8 +88,8 @@ impl Event {
}
pub struct EventRefs<'a> {
pub widget_map: &'a WidgetMap,
pub widget_node_map: &'a WidgetNodeMap,
pub widgets: &'a WidgetMap,
pub nodes: &'a WidgetNodeMap,
pub tree: &'a taffy::tree::TaffyTree<WidgetID>,
}
@@ -103,53 +103,33 @@ pub struct EventAlterables {
pub trigger_haptics: bool,
}
pub struct CallbackDataCommon<'a> {
pub refs: &'a EventRefs<'a>,
pub alterables: &'a mut EventAlterables,
}
impl CallbackDataCommon<'_> {
pub fn call_on_widget<WIDGET, FUNC>(&self, widget_id: WidgetID, func: FUNC)
where
WIDGET: WidgetObj,
FUNC: FnOnce(&mut WIDGET),
{
let Some(widget) = self.refs.widget_map.get(widget_id) else {
debug_assert!(false);
return;
};
let mut lock = widget.lock().unwrap();
let m = lock.obj.get_as_mut::<WIDGET>();
func(m);
}
impl EventAlterables {
pub fn mark_redraw(&mut self) {
self.alterables.needs_redraw = true;
self.needs_redraw = true;
}
pub fn set_style(&mut self, node_id: taffy::NodeId, style: taffy::Style) {
self.alterables.style_set_requests.push((node_id, style));
self.style_set_requests.push((node_id, style));
}
pub fn mark_dirty(&mut self, node_id: taffy::NodeId) {
self.alterables.dirty_nodes.push(node_id);
self.dirty_nodes.push(node_id);
}
pub fn trigger_haptics(&mut self) {
self.alterables.trigger_haptics = true;
}
pub fn get_tree(&self) -> &taffy::TaffyTree<WidgetID> {
self.refs.tree
self.trigger_haptics = true;
}
pub fn animate(&mut self, animation: Animation) {
self.alterables.animations.push(animation);
self.animations.push(animation);
}
}
pub struct CallbackDataCommon<'a> {
pub refs: &'a EventRefs<'a>,
pub alterables: &'a mut EventAlterables,
}
pub struct CallbackData<'a> {
pub obj: &'a mut dyn WidgetObj,
pub widget_data: &'a mut WidgetData,
@@ -234,6 +214,7 @@ impl<U1, U2> EventListener<U1, U2> {
}
}
#[derive(Default)]
pub struct EventListenerVec<U1, U2>(Vec<EventListener<U1, U2>>);
impl<U1, U2> EventListenerVec<U1, U2> {

View File

@@ -1,14 +1,16 @@
use std::sync::{Arc, Mutex};
use std::{collections::VecDeque, rc::Rc, sync::Arc};
use crate::{
animation::{self, Animations},
animation::Animations,
assets::AssetProvider,
components::{Component, InitData},
event::{self, EventAlterables, EventListenerCollection, EventRefs},
transform_stack::{Transform, TransformStack},
widget::{self, EventParams, WidgetState, div::Div},
transform_stack::Transform,
widget::{self, EventParams, WidgetObj, WidgetState, div::Div},
};
use glam::{Vec2, vec2};
use parking_lot::{MappedMutexGuard, Mutex, MutexGuard};
use slotmap::{HopSlotMap, SecondaryMap, new_key_type};
use taffy::{TaffyTree, TraversePartialTree};
@@ -17,13 +19,52 @@ new_key_type! {
}
pub type BoxWidget = Arc<Mutex<WidgetState>>;
pub type WidgetMap = HopSlotMap<WidgetID, BoxWidget>;
pub struct WidgetMap(HopSlotMap<WidgetID, BoxWidget>);
pub type WidgetNodeMap = SecondaryMap<WidgetID, taffy::NodeId>;
impl WidgetMap {
fn new() -> Self {
Self(HopSlotMap::with_key())
}
pub fn get_as<T: 'static>(&self, handle: WidgetID) -> Option<MappedMutexGuard<T>> {
let widget = self.0.get(handle)?;
Some(MutexGuard::map(widget.lock(), |w| w.obj.get_as_mut::<T>()))
}
pub fn get(&self, handle: WidgetID) -> Option<&BoxWidget> {
self.0.get(handle)
}
pub fn insert(&mut self, obj: BoxWidget) -> WidgetID {
self.0.insert(obj)
}
// cast to specific widget type, does nothing if widget ID is expired
// panics in case if the widget type is wrong
pub fn call<WIDGET, FUNC>(&self, widget_id: WidgetID, func: FUNC)
where
WIDGET: WidgetObj,
FUNC: FnOnce(&mut WIDGET),
{
let Some(widget) = self.get(widget_id) else {
debug_assert!(false);
return;
};
let mut lock = widget.lock();
let m = lock.obj.get_as_mut::<WIDGET>();
func(m);
}
}
pub struct Layout {
pub tree: TaffyTree<WidgetID>,
pub assets: Box<dyn AssetProvider>,
pub components_to_init: VecDeque<Rc<dyn Component>>,
pub widget_map: WidgetMap,
pub widget_node_map: WidgetNodeMap,
@@ -82,6 +123,26 @@ impl Layout {
)
}
fn process_pending_components(&mut self) -> anyhow::Result<()> {
let mut alterables = EventAlterables::default();
while let Some(c) = self.components_to_init.pop_front() {
c.init(&mut InitData {
widgets: &self.widget_map,
alterables: &mut alterables,
tree: &self.tree,
});
}
self.process_alterables(alterables)?;
Ok(())
}
pub fn defer_component_init(&mut self, component: Rc<dyn Component>) {
self.components_to_init.push_back(component);
}
fn push_event_children<U1, U2>(
&self,
listeners: &EventListenerCollection<U1, U2>,
@@ -117,7 +178,7 @@ impl Layout {
anyhow::bail!("invalid widget");
};
let mut widget = widget.lock().unwrap();
let mut widget = widget.lock();
let transform = Transform {
pos: Vec2::new(l.location.x, l.location.y),
@@ -132,8 +193,8 @@ impl Layout {
let mut params = EventParams {
refs: &EventRefs {
tree: &self.tree,
widget_map: &self.widget_map,
widget_node_map: &self.widget_node_map,
widgets: &self.widget_map,
nodes: &self.widget_node_map,
},
layout: l,
alterables,
@@ -141,20 +202,27 @@ impl Layout {
style,
};
if let Some(listeners) = listeners.get(widget_id) {
match widget.process_event(widget_id, listeners, node_id, event, user_data, &mut params) {
widget::EventResult::Pass => {
// go on
}
widget::EventResult::Consumed => {
iter_children = false;
}
widget::EventResult::Outside => {
iter_children = false;
}
widget::EventResult::Unused => {
iter_children = false;
}
let listeners_vec = listeners.get(widget_id);
match widget.process_event(
widget_id,
listeners_vec,
node_id,
event,
user_data,
&mut params,
) {
widget::EventResult::Pass => {
// go on
}
widget::EventResult::Consumed => {
iter_children = false;
}
widget::EventResult::Outside => {
iter_children = false;
}
widget::EventResult::Unused => {
iter_children = false;
}
}
@@ -213,7 +281,7 @@ impl Layout {
pub fn new(assets: Box<dyn AssetProvider>) -> anyhow::Result<Self> {
let mut tree = TaffyTree::new();
let mut widget_node_map = WidgetNodeMap::default();
let mut widget_map = HopSlotMap::with_key();
let mut widget_map = WidgetMap::new();
let (root_widget, root_node) = add_child_internal(
&mut tree,
@@ -239,6 +307,7 @@ impl Layout {
haptics_triggered: false,
animations: Animations::default(),
assets,
components_to_init: VecDeque::new(),
})
}
@@ -247,8 +316,8 @@ impl Layout {
let refs = EventRefs {
tree: &self.tree,
widget_map: &self.widget_map,
widget_node_map: &self.widget_node_map,
widgets: &self.widget_map,
nodes: &self.widget_node_map,
};
self
@@ -280,10 +349,7 @@ impl Layout {
None => taffy::Size::ZERO,
Some(h) => {
if let Some(w) = self.widget_map.get(*h) {
w.lock()
.unwrap()
.obj
.measure(known_dimensions, available_space)
w.lock().obj.measure(known_dimensions, available_space)
} else {
taffy::Size::ZERO
}
@@ -309,12 +375,13 @@ impl Layout {
let refs = EventRefs {
tree: &self.tree,
widget_map: &self.widget_map,
widget_node_map: &self.widget_node_map,
widgets: &self.widget_map,
nodes: &self.widget_node_map,
};
self.animations.tick(&refs, &mut alterables);
self.process_alterables(alterables)?;
self.process_pending_components()?;
Ok(())
}

View File

@@ -53,8 +53,8 @@ impl RendererPass<'_> {
self.rect_renderer.render(gfx, viewport, cmd_buf)?;
{
let mut font_system = FONT_SYSTEM.lock().unwrap();
let mut swash_cache = SWASH_CACHE.lock().unwrap();
let mut font_system = FONT_SYSTEM.lock();
let mut swash_cache = SWASH_CACHE.lock();
self.text_renderer.prepare(
&mut font_system,

View File

@@ -3,11 +3,7 @@ mod shaders;
pub mod text_atlas;
pub mod text_renderer;
use std::{
cell::RefCell,
rc::Rc,
sync::{LazyLock, Mutex},
};
use std::{cell::RefCell, rc::Rc, sync::LazyLock};
use cosmic_text::{
Align, Attrs, Buffer, Color, FontSystem, Metrics, Style, SwashCache, Weight, Wrap,
@@ -15,6 +11,7 @@ use cosmic_text::{
use custom_glyph::{ContentType, CustomGlyph};
use etagere::AllocId;
use glam::Mat4;
use parking_lot::Mutex;
use crate::drawing::{self};

View File

@@ -320,7 +320,7 @@ impl WidgetState {
pub fn process_event<'a, U1, U2>(
&mut self,
widget_id: WidgetID,
listeners: &EventListenerVec<U1, U2>,
listeners: Option<&EventListenerVec<U1, U2>>,
node_id: taffy::NodeId,
event: &Event,
user_data: &mut (&mut U1, &mut U2),
@@ -331,52 +331,92 @@ impl WidgetState {
match &event {
Event::MouseDown(e) => {
if hovered && self.data.set_device_pressed(e.device, true) {
call_event!(
self,
listeners,
widget_id,
node_id,
params,
MousePress,
user_data,
CallbackMetadata::MouseButton(event::MouseButton {
index: e.index,
pos: e.pos
})
);
}
}
Event::MouseUp(e) => {
if self.data.set_device_pressed(e.device, false) {
call_event!(
self,
listeners,
widget_id,
node_id,
params,
MouseRelease,
user_data,
CallbackMetadata::MouseButton(event::MouseButton {
index: e.index,
pos: e.pos,
})
);
}
}
Event::MouseMotion(e) => {
if self.data.set_device_hovered(e.device, hovered) {
if self.data.is_hovered() {
if let Some(listeners) = &listeners {
call_event!(
self,
listeners,
widget_id,
node_id,
params,
MouseEnter,
MousePress,
user_data,
CallbackMetadata::None
CallbackMetadata::MouseButton(event::MouseButton {
index: e.index,
pos: e.pos
})
);
} else {
}
}
}
Event::MouseUp(e) => {
if self.data.set_device_pressed(e.device, false) {
if let Some(listeners) = listeners {
call_event!(
self,
listeners,
widget_id,
node_id,
params,
MouseRelease,
user_data,
CallbackMetadata::MouseButton(event::MouseButton {
index: e.index,
pos: e.pos,
})
);
}
}
}
Event::MouseMotion(e) => {
let hover_state_changed = self.data.set_device_hovered(e.device, hovered);
if let Some(listeners) = &listeners {
if hover_state_changed {
if self.data.is_hovered() {
call_event!(
self,
listeners,
widget_id,
node_id,
params,
MouseEnter,
user_data,
CallbackMetadata::None
);
} else {
call_event!(
self,
listeners,
widget_id,
node_id,
params,
MouseLeave,
user_data,
CallbackMetadata::None
);
}
}
call_event!(
self,
listeners,
widget_id,
node_id,
params,
MouseMotion,
user_data,
CallbackMetadata::MousePosition(event::MousePosition { pos: e.pos })
);
}
}
Event::MouseWheel(e) => {
if hovered && self.process_wheel(params, e) {
return EventResult::Consumed;
}
}
Event::MouseLeave(e) => {
if self.data.set_device_hovered(e.device, false) {
if let Some(listeners) = &listeners {
call_event!(
self,
listeners,
@@ -389,49 +429,21 @@ impl WidgetState {
);
}
}
call_event!(
self,
listeners,
widget_id,
node_id,
params,
MouseMotion,
user_data,
CallbackMetadata::MousePosition(event::MousePosition { pos: e.pos })
);
}
Event::MouseWheel(e) => {
if hovered && self.process_wheel(params, e) {
return EventResult::Consumed;
}
}
Event::MouseLeave(e) => {
if self.data.set_device_hovered(e.device, false) {
Event::InternalStateChange(e) => {
if let Some(listeners) = &listeners {
call_event!(
self,
listeners,
widget_id,
node_id,
params,
MouseLeave,
InternalStateChange,
user_data,
CallbackMetadata::None
CallbackMetadata::Custom(e.metadata)
);
}
}
Event::InternalStateChange(e) => {
call_event!(
self,
listeners,
widget_id,
node_id,
params,
InternalStateChange,
user_data,
CallbackMetadata::Custom(e.metadata)
);
}
}
EventResult::Pass
}

View File

@@ -61,7 +61,7 @@ impl WidgetObj for SpriteBox {
let mut buffer = Buffer::new_empty(DEFAULT_METRICS);
{
let mut font_system = FONT_SYSTEM.lock().unwrap(); // safe unwrap
let mut font_system = FONT_SYSTEM.lock();
let mut buffer = buffer.borrow_with(&mut font_system);
let attrs = Attrs::new()
.color(Color::rgb(255, 0, 255))

View File

@@ -30,7 +30,7 @@ impl TextLabel {
let mut buffer = Buffer::new_empty(metrics);
{
let mut font_system = FONT_SYSTEM.lock().unwrap(); // safe unwrap
let mut font_system = FONT_SYSTEM.lock();
let mut buffer = buffer.borrow_with(&mut font_system);
buffer.set_wrap(wrap);
@@ -56,7 +56,7 @@ impl TextLabel {
self.params.content = String::from(text);
let attrs = Attrs::from(&self.params.style);
let mut font_system = FONT_SYSTEM.lock().unwrap(); // safe unwrap
let mut font_system = FONT_SYSTEM.lock();
let mut buffer = self.buffer.borrow_mut();
buffer.set_rich_text(
@@ -79,7 +79,7 @@ impl WidgetObj for TextLabel {
if self.last_boundary != boundary {
self.last_boundary = boundary;
let mut font_system = FONT_SYSTEM.lock().unwrap(); // safe unwrap
let mut font_system = FONT_SYSTEM.lock();
let mut buffer = self.buffer.borrow_mut();
buffer.set_size(
&mut font_system,
@@ -108,7 +108,7 @@ impl WidgetObj for TextLabel {
AvailableSpace::Definite(width) => Some(width),
});
let mut font_system = FONT_SYSTEM.lock().unwrap(); // safe unwrap
let mut font_system = FONT_SYSTEM.lock();
let mut buffer = self.buffer.borrow_mut();
buffer.set_size(&mut font_system, width_constraint, None);

View File

@@ -8,8 +8,8 @@
</template>
<elements>
<div box_sizing="content_box" flex_direction="column" justify_content="center" align_content="center">
<rectangle padding="10" gap="8" round="100%" color="~bg_color" justify_content="center" align_content="center">
<div box_sizing="content_box" flex_direction="column" justify_content="center">
<rectangle padding="10" gap="8" round="100%" color="~bg_color" justify_content="center">
<TopButton id="lock" src="bar/lock_open.svg" />
<TopButton id="anchor" src="bar/anchor.svg" />
<TopButton id="mouse" src="bar/mouse.svg" />
@@ -19,17 +19,12 @@
<TopButton id="inout" src="bar/inout.svg" />
<TopButton id="delete" src="bar/delete.svg" />
</rectangle>
<rectangle padding="8" gap="8" round="100%" color="~bg_color_active" justify_content="center" align_content="center">
<rectangle padding="8" gap="8" round="100%" color="~bg_color_active" justify_content="center" align_items="center">
<label size="18" text="Opacity" color="~text_color" />
<label size="18" text="100%" color="~text_color" weight="bold" />
<rectangle width="200" height="20" round="100%" color="~slider_bg_color">
<div width="150" />
<rectangle width="50" round="100%" color="~slider_fg_color" justify_content="center" align_content="center">
</rectangle>
</rectangle>
<slider width="150" height="24" min_value="0" max_value="100" value="100" />
<label size="18" text="Additive:" color="~text_color" />
<sprite color="~device_color" width="20" height="20" src="bar/checkbox-checked.svg" />
</rectangle>
</div>
</elements>
</layout>
</layout>

View File

@@ -5,8 +5,8 @@ use vulkano::{command_buffer::CommandBufferUsage, image::view::ImageView};
use wgui::{
event::{
Event as WguiEvent, EventListenerCollection, InternalStateChangeEvent, ListenerHandleVec,
MouseButton, MouseButtonIndex, MouseDownEvent, MouseLeaveEvent, MouseMotionEvent,
MouseUpEvent, MouseWheelEvent,
MouseButtonIndex, MouseDownEvent, MouseLeaveEvent, MouseMotionEvent, MouseUpEvent,
MouseWheelEvent,
},
layout::Layout,
parser::ParserState,

View File

@@ -167,16 +167,12 @@ where
if let Some(widget_id) = gui_state_key.ids.get(&*my_id) {
let key_state = {
let widget = panel
let rect = panel
.layout
.widget_map
.get(*widget_id)
.unwrap() // want panic
.lock()
.get_as::<Rectangle>(*widget_id)
.unwrap(); // want panic
let rect = widget.obj.get_as::<Rectangle>();
Rc::new(KeyState {
button_state: key.button_state,
color: rect.params.color,
@@ -193,7 +189,7 @@ where
Box::new({
let k = key_state.clone();
move |common, data, _app, _state| {
common.trigger_haptics();
common.alterables.trigger_haptics();
on_enter_anim(k.clone(), common, data);
}
}),
@@ -205,7 +201,7 @@ where
Box::new({
let k = key_state.clone();
move |common, data, _app, _state| {
common.trigger_haptics();
common.alterables.trigger_haptics();
on_leave_anim(k.clone(), common, data);
}
}),
@@ -310,7 +306,7 @@ fn on_enter_anim(
common: &mut event::CallbackDataCommon,
data: &event::CallbackData,
) {
common.animate(Animation::new(
common.alterables.animate(Animation::new(
data.widget_id,
10,
AnimationEasing::OutBack,
@@ -318,7 +314,7 @@ fn on_enter_anim(
let rect = data.obj.get_as_mut::<Rectangle>();
set_anim_color(&key_state, rect, data.pos);
data.data.transform = get_anim_transform(data.pos, data.widget_size);
common.mark_redraw();
common.alterables.mark_redraw();
}),
));
}
@@ -328,7 +324,7 @@ fn on_leave_anim(
common: &mut event::CallbackDataCommon,
data: &event::CallbackData,
) {
common.animate(Animation::new(
common.alterables.animate(Animation::new(
data.widget_id,
15,
AnimationEasing::OutQuad,
@@ -336,7 +332,7 @@ fn on_leave_anim(
let rect = data.obj.get_as_mut::<Rectangle>();
set_anim_color(&key_state, rect, 1.0 - data.pos);
data.data.transform = get_anim_transform(1.0 - data.pos, data.widget_size);
common.mark_redraw();
common.alterables.mark_redraw();
}),
));
}
@@ -351,7 +347,7 @@ fn on_press_anim(
}
let rect = data.obj.get_as_mut::<Rectangle>();
rect.params.border_color = Color::new(1.0, 1.0, 1.0, 1.0);
common.mark_redraw();
common.alterables.mark_redraw();
key_state.drawn_state.set(true);
}
@@ -365,6 +361,6 @@ fn on_release_anim(
}
let rect = data.obj.get_as_mut::<Rectangle>();
rect.params.border_color = key_state.border_color;
common.mark_redraw();
common.alterables.mark_redraw();
key_state.drawn_state.set(false);
}

View File

@@ -41,15 +41,11 @@ where
.flatten();
let role = cap.get(2).unwrap().as_str();
let mut widget = panel
let mut label = panel
.layout
.widget_map
.get_mut(*widget_id)
.unwrap() // want panic
.lock()
.unwrap(); // want panic
let label = widget.obj.get_as_mut::<TextLabel>();
.get_as::<TextLabel>(*widget_id)
.unwrap();
let format = match role {
"tz" => {