Merge pull request #443 from wlx-team/feat-editbox

Editbox, wgui fixes, doc
This commit is contained in:
oo8dev 2026-02-21 12:23:49 +01:00 committed by GitHub
commit b84d565b1e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 825 additions and 189 deletions

View File

@ -93,19 +93,18 @@
position="absolute" width="100%" height="100%"
gradient="radial" color="#44BBFF11" color2="#00000000" />
<div
id="content"
flex_direction="column"
overflow_x="scroll"
overflow_y="scroll"
padding="16"
gap="8"
width="100%"
min_height="100%"
>
<!-- filled-in at runtime -->
<div overflow_x="scroll" overflow_y="scroll" width="100%" height="100%">
<div
id="content"
flex_direction="column"
padding="16"
gap="8"
width="100%"
height="100%"
>
<!-- filled-in at runtime -->
</div>
</div>
<div position="absolute" id="popup_manager" width="100%" height="100%" />
</rectangle>
<!-- BOTTOM PANEL -->

View File

@ -44,7 +44,6 @@
flex_direction="row"
flex_wrap="wrap"
gap="4"
overflow_y="scroll"
/>
</elements>
</layout>

View File

@ -1,6 +1,6 @@
<layout>
<elements>
<div id="running_games_list_parent" align_self="center" />
<div id="game_list_parent" align_items="center" flex_direction="column" gap="8" overflow_y="scroll" />
<div id="game_list_parent" align_items="center" flex_direction="column" gap="8" />
</elements>
</layout>

View File

@ -5,6 +5,7 @@ use wgui::{
widget::label::{WidgetLabel, WidgetLabelParams},
};
#[allow(dead_code)]
pub fn create_label(layout: &mut Layout, parent: WidgetID, content: Translation) -> anyhow::Result<()> {
let label = WidgetLabel::create(
&mut layout.state.globals.get(),

View File

@ -50,7 +50,6 @@ enum CompositorMode {
enum Task {
SetCompositor(CompositorMode),
SetRes(ResMode),
SetPos(PosMode), // TODO?
SetOrientation(OrientationMode),
SetAutoStart(bool),
Launch,
@ -295,7 +294,6 @@ impl View {
match task {
Task::SetCompositor(mode) => self.compositor_mode = mode,
Task::SetRes(mode) => self.res_mode = mode,
Task::SetPos(mode) => self.pos_mode = mode,
Task::SetOrientation(mode) => self.orientation_mode = mode,
Task::SetAutoStart(auto_start) => self.auto_start = auto_start,
Task::Launch => self.action_launch(interface, data),

View File

@ -59,10 +59,18 @@
</div>
</rectangle>
<rectangle macro="rect">
<label text="Context menu test" />
<Button id="button_context_menu" text="Show context menu" />
</rectangle>
<div>
<rectangle macro="rect">
<label text="Context menu test" />
<Button id="button_context_menu" text="Show context menu" />
</rectangle>
<rectangle macro="rect">
<label text="Editbox test" />
<EditBox text="Initial text. Test, abcdef." width="400" />
<EditBox margin_left="42" margin_top="42" margin_bottom="40" width="200" />
</rectangle>
</div>
<rectangle macro="rect">
<label text="visibility test" weight="bold" />

View File

@ -1,25 +1,25 @@
use glam::{vec2, Vec2};
use std::sync::Arc;
use testbed::{testbed_any::TestbedAny, Testbed};
use glam::{Vec2, vec2};
use std::{rc::Rc, sync::Arc};
use testbed::{Testbed, testbed_any::TestbedAny};
use tracing_subscriber::EnvFilter;
use tracing_subscriber::filter::LevelFilter;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
use tracing_subscriber::EnvFilter;
use vulkan::init_window;
use vulkano::{
Validated, VulkanError,
command_buffer::CommandBufferUsage,
format::Format,
image::{view::ImageView, ImageUsage},
image::{ImageUsage, view::ImageView},
swapchain::{
acquire_next_image, ColorSpace, CompositeAlpha, PresentMode, Surface, SurfaceInfo, Swapchain,
SwapchainCreateInfo, SwapchainPresentInfo,
ColorSpace, CompositeAlpha, PresentMode, Surface, SurfaceInfo, Swapchain, SwapchainCreateInfo,
SwapchainPresentInfo, acquire_next_image,
},
sync::GpuFuture,
Validated, VulkanError,
};
use wgui::{
event::{MouseButtonIndex, MouseDownEvent, MouseMotionEvent, MouseUpEvent, MouseWheelEvent},
gfx::{cmd::WGfxClearMode, WGfx},
event::{MouseButtonEvent, MouseButtonIndex, MouseMotionEvent, MouseWheelEvent},
gfx::{WGfx, cmd::WGfxClearMode},
renderer_vk::{self},
};
use winit::{
@ -32,7 +32,7 @@ use wlx_common::{audio, timestep::Timestep};
use crate::{
rate_limiter::RateLimiter,
testbed::{
testbed_dashboard::TestbedDashboard, testbed_generic::TestbedGeneric, TestbedUpdateParams,
TestbedUpdateParams, testbed_dashboard::TestbedDashboard, testbed_generic::TestbedGeneric,
},
};
@ -174,7 +174,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
testbed
.layout()
.push_event(
&wgui::event::Event::MouseDown(MouseDownEvent {
&wgui::event::Event::MouseDown(MouseButtonEvent {
pos: mouse / scale,
index: MouseButtonIndex::Left,
device: 0,
@ -187,7 +187,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
testbed
.layout()
.push_event(
&wgui::event::Event::MouseUp(MouseUpEvent {
&wgui::event::Event::MouseUp(MouseButtonEvent {
pos: mouse / scale,
index: MouseButtonIndex::Left,
device: 0,
@ -221,33 +221,46 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
..
} => {
if event.state == ElementState::Pressed {
if event.physical_key == PhysicalKey::Code(KeyCode::F10) {
debug_draw_enabled = !debug_draw_enabled;
if debug_draw_enabled {
log::info!(
"Debug draw enabled\n\tAqua: widget boundary\n\tMagenta: Scissoring (separate render pass)"
);
if !event.repeat {
if event.physical_key == PhysicalKey::Code(KeyCode::F10) {
debug_draw_enabled = !debug_draw_enabled;
if debug_draw_enabled {
log::info!(
"Debug draw enabled\n\tAqua: widget boundary\n\tMagenta: Scissoring (separate render pass)"
);
}
testbed.layout().mark_redraw();
}
if event.physical_key == PhysicalKey::Code(KeyCode::F11) {
testbed.layout().print_tree();
}
if event.physical_key == PhysicalKey::Code(KeyCode::Equal) {
scale *= 1.25;
render_context
.update_viewport(&mut shared_context, swapchain_size, scale)
.unwrap();
}
if event.physical_key == PhysicalKey::Code(KeyCode::Minus) {
scale *= 0.75;
render_context
.update_viewport(&mut shared_context, swapchain_size, scale)
.unwrap();
}
testbed.layout().mark_redraw();
}
if event.physical_key == PhysicalKey::Code(KeyCode::F11) {
testbed.layout().print_tree();
}
if event.physical_key == PhysicalKey::Code(KeyCode::Equal) {
scale *= 1.25;
render_context
.update_viewport(&mut shared_context, swapchain_size, scale)
.unwrap();
}
if event.physical_key == PhysicalKey::Code(KeyCode::Minus) {
scale *= 0.75;
render_context
.update_viewport(&mut shared_context, swapchain_size, scale)
.unwrap();
}
testbed
.layout()
.push_event(
&wgui::event::Event::TextInput(wgui::event::TextInputEvent {
text: event.text.map(|text| Rc::from(text.as_str())),
}),
&mut (),
&mut (),
)
.unwrap();
}
}
Event::WindowEvent {

View File

@ -14,8 +14,8 @@ use wgui::{
drawing,
event::{
CallbackDataCommon, Event as WguiEvent, EventAlterables, EventCallback, EventListenerID,
EventListenerKind, InternalStateChangeEvent, MouseButtonIndex, MouseDownEvent,
MouseLeaveEvent, MouseMotionEvent, MouseUpEvent, MouseWheelEvent,
EventListenerKind, InternalStateChangeEvent, MouseButtonEvent, MouseButtonIndex,
MouseLeaveEvent, MouseMotionEvent, MouseWheelEvent,
},
gfx::cmd::WGfxClearMode,
i18n::Translation,
@ -416,13 +416,13 @@ impl<S: 'static> OverlayBackend for GuiPanel<S> {
};
let e = if pressed {
WguiEvent::MouseDown(MouseDownEvent {
WguiEvent::MouseDown(MouseButtonEvent {
pos: hit.uv * self.layout.content_size,
index,
device: hit.pointer,
})
} else {
WguiEvent::MouseUp(MouseUpEvent {
WguiEvent::MouseUp(MouseButtonEvent {
pos: hit.uv * self.layout.content_size,
index,
device: hit.pointer,

View File

@ -8,8 +8,8 @@ use wayvr_ipc::{
};
use wgui::{
event::{
Event as WguiEvent, MouseButtonIndex, MouseDownEvent, MouseLeaveEvent, MouseMotionEvent,
MouseUpEvent, MouseWheelEvent,
Event as WguiEvent, MouseButtonEvent, MouseButtonIndex, MouseLeaveEvent, MouseMotionEvent,
MouseWheelEvent,
},
gfx::cmd::WGfxClearMode,
renderer_vk::context::Context as WguiContext,
@ -247,13 +247,13 @@ impl OverlayBackend for DashFrontend {
};
let e = if pressed {
WguiEvent::MouseDown(MouseDownEvent {
WguiEvent::MouseDown(MouseButtonEvent {
pos: hit.uv * self.inner.layout.content_size,
index,
device: hit.pointer,
})
} else {
WguiEvent::MouseUp(MouseUpEvent {
WguiEvent::MouseUp(MouseButtonEvent {
pos: hit.uv * self.inner.layout.content_size,
index,
device: hit.pointer,

View File

@ -32,7 +32,7 @@ use regex::Regex;
use slotmap::{SlotMap, new_key_type};
use wgui::{
drawing,
event::{InternalStateChangeEvent, MouseButton, MouseButtonIndex},
event::{InternalStateChangeEvent, MouseButtonEvent, MouseButtonIndex},
};
use wlx_common::windowing::{OverlayWindowState, Positioning};
use wlx_common::{
@ -390,7 +390,7 @@ fn handle_press(
app: &mut AppState,
key: &KeyState,
keyboard: &mut KeyboardState,
button: MouseButton,
button: MouseButtonEvent,
) {
match &key.button_state {
KeyButtonData::Key { vk, pressed } => {

View File

@ -6,7 +6,7 @@ use std::{
use anyhow::Context;
use glam::{Affine3A, Vec3, Vec3A};
use slotmap::{HopSlotMap, Key, SecondaryMap};
use slotmap::{Key, SecondaryMap, SlotMap};
use wgui::log::LogErr;
use wlx_common::{
astr_containers::{AStrMap, AStrMapExt},
@ -42,7 +42,7 @@ pub const MAX_OVERLAY_SETS: usize = 6;
pub struct OverlayWindowManager<T> {
wrappers: EditWrapperManager,
overlays: HopSlotMap<OverlayID, OverlayWindowData<T>>,
overlays: SlotMap<OverlayID, OverlayWindowData<T>>,
sets: Vec<OverlayWindowSet>,
global_set: OverlayWindowSet,
/// The set that is currently visible.
@ -65,7 +65,7 @@ where
pub fn new(app: &mut AppState, headless: bool) -> anyhow::Result<Self> {
let mut me = Self {
wrappers: EditWrapperManager::default(),
overlays: HopSlotMap::with_key(),
overlays: SlotMap::<OverlayID, OverlayWindowData<T>>::with_key(),
current_set: Some(0),
restore_set: 0,
sets: vec![OverlayWindowSet::default()],

View File

@ -6,7 +6,7 @@
## [Built-in components](#components)
[Button](#button-component), [Slider](#slider-component), [CheckBox](#checkbox-component)
[Button](#button-component), [Slider](#slider-component), [CheckBox](#checkbox-component), [Tabs](#tabs-component) ([Tab](#tab-component)), [EditBox](#editbox-component)
## [Examples](#examples)
@ -370,6 +370,56 @@ _optional value that will be set as the RadioGroup's value_
### A radio group. Place `<RadioBox>` components inside this.
## Tabs component
### `<Tabs>`
### A tab bar component
#### Parameters
`id`: **string**
_Unique identifier for the tab group_
Only `<Tab>` components should be placed inside `<Tabs>`.
---
## Tab component
### `<Tab>`
### A single tab inside a `<Tabs>` component
#### Parameters
`name`: **string**
_Unique identifier for the tab_
`translation`: **string**
_Translated by key_
`sprite_src` | `sprite_src_ext` | `sprite_src_builtin` | `sprite_src_internal`
_Image path (see [sprite](#sprite-widget)) for src descriptions_
---
## EditBox component
### `<EditBox>`
### A single-line text input field.
#### Parameters
`text`: **string**
_Initial text content_
---
# Examples

View File

@ -349,6 +349,7 @@ fn register_event_mouse_press(state: Rc<RefCell<State>>, listeners: &mut EventLi
common.alterables.trigger_haptics();
common.alterables.play_sound(WguiSoundType::ButtonPress);
common.alterables.mark_redraw();
common.alterables.unfocus();
if state.hovered {
state.down = true;
@ -556,25 +557,19 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul
let base = ComponentBase {
id: root.id,
lhandles: {
let mut widget = ess.layout.state.widgets.get(id_rect).unwrap().state();
let listeners = &mut root.widget.state().event_listeners;
let anim_mult = ess.layout.state.globals.defaults().animation_mult;
vec![
register_event_mouse_enter(
data.clone(),
state.clone(),
&mut widget.event_listeners,
params.tooltip,
anim_mult,
),
register_event_mouse_leave(state.clone(), &mut widget.event_listeners, anim_mult),
register_event_mouse_press(state.clone(), &mut widget.event_listeners),
register_event_mouse_release(data.clone(), state.clone(), &mut widget.event_listeners),
register_event_mouse_enter(data.clone(), state.clone(), listeners, params.tooltip, anim_mult),
register_event_mouse_leave(state.clone(), listeners, anim_mult),
register_event_mouse_press(state.clone(), listeners),
register_event_mouse_release(data.clone(), state.clone(), listeners),
]
},
};
let button = Rc::new(ComponentButton { base, data, state });
ess.layout.register_component_refresh(Component(button.clone()));
ess.layout.register_component_refresh(&Component(button.clone()));
Ok((root, button))
}

View File

@ -248,6 +248,7 @@ fn register_event_mouse_press(state: Rc<RefCell<State>>, listeners: &mut EventLi
common.alterables.trigger_haptics();
common.alterables.mark_redraw();
common.alterables.unfocus();
if state.hovered {
state.down = true;
@ -431,13 +432,13 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul
let base = ComponentBase {
id: root.id,
lhandles: {
let mut widget = ess.layout.state.widgets.get(id_container).unwrap().state();
let listeners = &mut root.widget.state().event_listeners;
let anim_mult = ess.layout.state.globals.defaults().animation_mult;
vec![
register_event_mouse_enter(state.clone(), &mut widget.event_listeners, params.tooltip, anim_mult),
register_event_mouse_leave(state.clone(), &mut widget.event_listeners, anim_mult),
register_event_mouse_press(state.clone(), &mut widget.event_listeners),
register_event_mouse_release(data.clone(), state.clone(), &mut widget.event_listeners),
register_event_mouse_enter(state.clone(), listeners, params.tooltip, anim_mult),
register_event_mouse_leave(state.clone(), listeners, anim_mult),
register_event_mouse_press(state.clone(), listeners),
register_event_mouse_release(data.clone(), state.clone(), listeners),
]
},
};

View File

@ -0,0 +1,421 @@
use std::{
cell::{Ref, RefCell},
rc::{Rc, Weak},
};
use glam::FloatExt;
use taffy::prelude::{auto, length, percent};
use crate::{
animation::{Animation, AnimationEasing},
components::{Component, ComponentBase, ComponentTrait, FocusChangeData, RefreshData},
drawing::{self, Color},
event::{self, CallbackDataCommon, CallbackMetadata, EventListenerCollection, EventListenerKind, StyleSetRequest},
i18n::Translation,
layout::{WidgetID, WidgetPair},
renderer_vk::text::{TextShadow, TextStyle},
widget::{
ConstructEssentials, EventResult,
div::WidgetDiv,
label::{WidgetLabel, WidgetLabelParams},
rectangle::{WidgetRectangle, WidgetRectangleParams},
util::WLength,
},
};
#[derive(Default)]
pub struct Params {
pub style: taffy::Style,
pub initial_text: String,
}
struct State {
text: String,
hovered: bool,
focused: bool,
focused_prev: bool,
first_refresh: bool,
self_ref: Weak<ComponentEditBox>,
}
#[allow(clippy::struct_field_names)]
struct Data {
#[allow(dead_code)]
id_rect_container: WidgetID,
id_rect_bottom: WidgetID,
id_rect_cursor: WidgetID,
id_label: WidgetID,
}
pub struct ComponentEditBox {
base: ComponentBase,
data: Rc<Data>,
state: Rc<RefCell<State>>,
}
fn anim_bottom_rect(
common: &mut CallbackDataCommon,
accent_color: drawing::Color,
id_rect: WidgetID,
anim_mult: f32,
focused: bool,
) {
common.alterables.animate(Animation::new(
id_rect,
(10.0 * anim_mult) as _,
AnimationEasing::OutQuad,
{
Box::new(move |common, data| {
let rect = data.obj.get_as_mut::<WidgetRectangle>().unwrap();
let pos_bidir = if focused { data.pos } else { 1.0 - data.pos };
rect.set_color(
common,
accent_color.lerp(&drawing::Color::new(1.0, 1.0, 1.0, 1.0), pos_bidir),
);
common.alterables.set_style(
data.widget_id,
StyleSetRequest::Size(taffy::Size {
width: percent(0.95.lerp(1.0, pos_bidir)),
height: length(1.0 + pos_bidir),
}),
);
common.alterables.set_style(
data.widget_id,
StyleSetRequest::Margin(taffy::Rect {
bottom: length(pos_bidir),
left: auto(),
right: auto(),
top: auto(),
}),
);
common.alterables.mark_redraw();
})
},
));
}
fn refresh_all(common: &mut CallbackDataCommon, data: &Data, state: &mut State) -> Option<()> {
let defaults = common.defaults();
let editbox_color = defaults.editbox_color;
let anim_mult = defaults.animation_mult;
let accent_color = defaults.accent_color;
drop(defaults);
let (rect_color, border_color) = if state.focused {
(editbox_color.add_rgb(0.15), editbox_color.add_rgb(0.15 + 0.25))
} else if state.hovered {
(editbox_color.add_rgb(0.1), editbox_color.add_rgb(0.1 + 0.15))
} else {
(editbox_color, editbox_color.add_rgb(0.15))
};
// update background color
let mut rect = common.state.widgets.get_as::<WidgetRectangle>(data.id_rect_container)?;
rect.params.border_color = border_color;
rect.set_color(common, rect_color);
if state.focused_prev != state.focused || state.first_refresh {
anim_bottom_rect(common, accent_color, data.id_rect_bottom, anim_mult, state.focused);
state.focused_prev = state.focused;
}
// Cursor
common.alterables.set_style(
data.id_rect_cursor,
StyleSetRequest::Display(if state.focused {
taffy::Display::Flex
} else {
taffy::Display::None
}),
);
state.first_refresh = false;
Some(())
}
impl ComponentTrait for ComponentEditBox {
fn base(&self) -> &ComponentBase {
&self.base
}
fn base_mut(&mut self) -> &mut ComponentBase {
&mut self.base
}
fn refresh(&self, data: &mut RefreshData) {
let mut state = self.state.borrow_mut();
let res = refresh_all(data.common, &self.data, &mut state);
debug_assert!(res.is_some());
}
fn on_focus_change(&self, data: &mut FocusChangeData) {
let mut state = self.state.borrow_mut();
state.focused = data.focused;
data.common.alterables.refresh_component_once(&state.self_ref);
}
}
fn update_text(common: &mut CallbackDataCommon, state: &mut State, data: &Data, text: String) {
let Some(mut label) = common.state.widgets.get_as::<WidgetLabel>(data.id_label) else {
return;
};
label.set_text(common, Translation::from_raw_text(&text));
common.alterables.refresh_component_once(&state.self_ref);
state.text = text;
}
impl ComponentEditBox {
pub fn set_text(&self, common: &mut CallbackDataCommon, text: &str) {
let mut state = self.state.borrow_mut();
update_text(common, &mut state, &self.data, String::from(text));
}
pub fn get_text(&self) -> Ref<'_, String> {
Ref::map(self.state.borrow(), |x| &x.text)
}
}
fn register_event_text_input(
state: Rc<RefCell<State>>,
data: Rc<Data>,
listeners: &mut EventListenerCollection,
) -> event::EventListenerID {
listeners.register(
EventListenerKind::TextInput,
Box::new(move |common, evt, (), ()| {
let mut state = state.borrow_mut();
if !state.focused {
return Ok(EventResult::Pass);
}
let CallbackMetadata::TextInput(input) = &evt.metadata else {
unreachable!();
};
let Some(input_text) = &input.text else {
return Ok(EventResult::Pass); // nothing to do
};
let Some(ch) = input_text.chars().next() else {
return Ok(EventResult::Pass); // ???
};
let mut new_text = std::mem::take(&mut state.text);
if ch == '\x08' {
// Backspace
new_text.pop();
} else {
let printable = !input_text.chars().any(char::is_control);
if printable {
new_text.push_str(input_text);
}
}
update_text(common, &mut state, &data, new_text);
Ok(EventResult::Consumed)
}),
)
}
fn register_event_mouse_enter(
state: Rc<RefCell<State>>,
listeners: &mut EventListenerCollection,
) -> event::EventListenerID {
listeners.register(
EventListenerKind::MouseEnter,
Box::new(move |common, _evt, (), ()| {
let mut state = state.borrow_mut();
state.hovered = true;
common.alterables.trigger_haptics();
common.alterables.refresh_component_once(&state.self_ref);
Ok(EventResult::Pass)
}),
)
}
fn register_event_mouse_press(
state: Rc<RefCell<State>>,
listeners: &mut EventListenerCollection,
) -> event::EventListenerID {
listeners.register(
EventListenerKind::MousePress,
Box::new(move |common, _evt, (), ()| {
let state = state.borrow_mut();
common.alterables.focus(&state.self_ref);
common.alterables.trigger_haptics();
common.alterables.refresh_component_once(&state.self_ref);
Ok(EventResult::Pass)
}),
)
}
fn register_event_mouse_leave(
state: Rc<RefCell<State>>,
listeners: &mut EventListenerCollection,
) -> event::EventListenerID {
listeners.register(
EventListenerKind::MouseLeave,
Box::new(move |common, _evt, (), ()| {
let mut state = state.borrow_mut();
state.hovered = false;
common.alterables.trigger_haptics();
common.alterables.refresh_component_once(&state.self_ref);
Ok(EventResult::Pass)
}),
)
}
pub fn construct(
ess: &mut ConstructEssentials,
mut params: Params,
) -> anyhow::Result<(WidgetPair, Rc<ComponentEditBox>)> {
let globals = ess.layout.state.globals.clone();
let defaults = globals.defaults();
let text_color = defaults.text_color;
drop(defaults);
if params.style.size.width.is_auto() {
params.style.size.width = length(128.0);
}
if params.style.size.height.is_auto() {
params.style.size.height = length(32.0);
}
// override style
params.style.align_items = Some(taffy::AlignItems::Center);
params.style.position = taffy::Position::Relative;
params.style.overflow = taffy::Point {
x: taffy::Overflow::Scroll,
y: taffy::Overflow::Visible,
};
params.style.min_size = params.style.max_size;
let (root, _) = ess.layout.add_child(
ess.parent,
WidgetRectangle::create(WidgetRectangleParams {
border: 2.0,
round: WLength::Units(3.0),
..Default::default()
}),
params.style,
)?;
// for centering
let (rect_bottom_parent, _) = ess.layout.add_child(
root.id,
WidgetDiv::create(),
taffy::Style {
position: taffy::Position::Absolute,
flex_direction: taffy::FlexDirection::Column,
align_content: Some(taffy::AlignContent::Center),
align_items: Some(taffy::AlignItems::Center),
size: taffy::Size {
width: percent(1.0),
height: percent(1.0),
},
..Default::default()
},
)?;
let (rect_bottom, _) = ess.layout.add_child(
rect_bottom_parent.id,
WidgetRectangle::create(Default::default()),
Default::default(),
)?;
let id_container = root.id;
let (label_parent, _) = ess.layout.add_child(
root.id,
WidgetDiv::create(),
taffy::Style {
padding: taffy::Rect::length(8.0),
..Default::default()
},
)?;
let (label, _node_label) = ess.layout.add_child(
label_parent.id,
WidgetLabel::create(
&mut globals.get(),
WidgetLabelParams {
content: Translation::from_raw_text(&params.initial_text),
style: TextStyle {
shadow: Some(TextShadow {
x: 1.0,
y: 1.0,
color: Color::new(0.0, 0.0, 0.0, 1.0),
}),
..Default::default()
},
},
),
taffy::Style {
position: taffy::Position::Relative,
..Default::default()
},
)?;
let (rect_cursor, _) = ess.layout.add_child(
label_parent.id,
WidgetRectangle::create(WidgetRectangleParams {
color: text_color.with_alpha(0.75),
..Default::default()
}),
taffy::Style {
align_self: Some(taffy::AlignSelf::Center),
justify_self: Some(taffy::JustifySelf::End),
min_size: taffy::Size {
width: length(2.0),
height: length(16.0),
},
..Default::default()
},
)?;
let data = Rc::new(Data {
id_rect_container: id_container,
id_label: label.id,
id_rect_bottom: rect_bottom.id,
id_rect_cursor: rect_cursor.id,
});
let state = Rc::new(RefCell::new(State {
self_ref: Weak::new(),
text: params.initial_text,
hovered: false,
focused: false,
focused_prev: false,
first_refresh: true,
}));
let base = ComponentBase {
id: root.id,
lhandles: {
let mut root_state = root.widget.state();
vec![
register_event_mouse_enter(state.clone(), &mut root_state.event_listeners),
register_event_mouse_leave(state.clone(), &mut root_state.event_listeners),
register_event_mouse_press(state.clone(), &mut root_state.event_listeners),
register_event_text_input(state.clone(), data.clone(), &mut root_state.event_listeners),
]
},
};
let editbox = Rc::new(ComponentEditBox { base, data, state });
editbox.state.borrow_mut().self_ref = Rc::downgrade(&editbox);
ess.layout.defer_component_refresh(Component(editbox.clone()));
Ok((root, editbox))
}

View File

@ -9,6 +9,7 @@ use crate::{
pub mod button;
pub mod checkbox;
pub mod editbox;
pub mod radio_group;
pub mod slider;
pub mod tabs;
@ -18,6 +19,11 @@ pub struct RefreshData<'a> {
pub common: &'a mut CallbackDataCommon<'a>,
}
pub struct FocusChangeData<'a> {
pub common: &'a mut CallbackDataCommon<'a>,
pub focused: bool,
}
// common component data
#[derive(Default)]
pub struct ComponentBase {
@ -36,6 +42,7 @@ pub trait ComponentTrait: AnyTrait {
fn base(&self) -> &ComponentBase;
fn base_mut(&mut self) -> &mut ComponentBase;
fn refresh(&self, data: &mut RefreshData);
fn on_focus_change(&self, _data: &mut FocusChangeData) {}
}
#[derive(Clone)]

View File

@ -324,7 +324,6 @@ fn register_event_mouse_enter(
common.alterables.trigger_haptics();
state.borrow_mut().hovered = true;
on_enter_anim(common, data.slider_handle_rect_id, anim_mult);
ComponentTooltip::register_hover_in(common, &tooltip_info, event_data.widget_id, state.clone());
Ok(EventResult::Pass)
@ -388,6 +387,7 @@ fn register_event_mouse_press(
EventListenerKind::MousePress,
Box::new(move |common, event_data, (), ()| {
common.alterables.trigger_haptics();
common.alterables.unfocus();
let mut state = state.borrow_mut();
let CallbackMetadata::MouseButton(btn) = event_data.metadata else {
@ -534,26 +534,20 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul
let base = ComponentBase {
id: root.id,
lhandles: {
let mut widget = ess.layout.state.widgets.get(body_id).unwrap().state();
let listeners = &mut root.widget.state().event_listeners;
let anim_mult = ess.layout.state.globals.defaults().animation_mult;
vec![
register_event_mouse_enter(
data.clone(),
state.clone(),
&mut widget.event_listeners,
params.tooltip,
anim_mult,
),
register_event_mouse_leave(data.clone(), state.clone(), &mut widget.event_listeners, anim_mult),
register_event_mouse_motion(data.clone(), state.clone(), &mut widget.event_listeners),
register_event_mouse_press(data.clone(), state.clone(), &mut widget.event_listeners),
register_event_mouse_release(state.clone(), &mut widget.event_listeners),
register_event_mouse_enter(data.clone(), state.clone(), listeners, params.tooltip, anim_mult),
register_event_mouse_leave(data.clone(), state.clone(), listeners, anim_mult),
register_event_mouse_motion(data.clone(), state.clone(), listeners),
register_event_mouse_press(data.clone(), state.clone(), listeners),
register_event_mouse_release(state.clone(), listeners),
]
},
};
let slider = Rc::new(ComponentSlider { base, data, state });
ess.layout.register_component_refresh(Component(slider.clone()));
ess.layout.register_component_refresh(&Component(slider.clone()));
Ok((root, slider))
}

View File

@ -19,10 +19,6 @@ use crate::{
use super::{layout::Layout, widget::DrawState};
pub struct ImageHandle {
// to be implemented, will contain pixel data (RGB or RGBA) loaded via "ImageBank" or something by the gui
}
#[derive(Debug, Default, Clone, Copy, PartialEq)]
pub struct Boundary {
pub pos: Vec2,
@ -254,14 +250,19 @@ pub fn has_overflow_clip(style: &taffy::Style) -> bool {
style.overflow.x != taffy::Overflow::Visible || style.overflow.y != taffy::Overflow::Visible
}
fn primitive_debug_rect(boundary: &Boundary, transform: &Mat4, color: drawing::Color) -> drawing::RenderPrimitive {
fn primitive_debug_rect(
boundary: &Boundary,
transform: &Mat4,
color: drawing::Color,
border: f32,
) -> drawing::RenderPrimitive {
drawing::RenderPrimitive::Rectangle(
PrimitiveExtent {
boundary: *boundary,
transform: *transform,
},
Rectangle {
border: 1.0,
border,
border_color: color,
color: Color::new(0.0, 0.0, 0.0, 0.0),
..Default::default()
@ -277,12 +278,16 @@ pub fn push_transform_stack(
) {
let raw_dim = Vec2::new(l.size.width, l.size.height);
let visual_dim = raw_dim + scroll_shift;
let content_dim = Vec2::new(l.content_box_width(), l.content_box_height());
let content_rel_pos = Vec2::new(l.content_box_x(), l.content_box_y());
transform_stack.push(stack::Transform {
rel_pos: Vec2::new(l.location.x, l.location.y) - scroll_shift,
transform: widget_state.data.transform,
raw_dim,
visual_dim,
content_dim,
content_rel_pos,
abs_pos: Default::default(),
transform_rel: Default::default(),
});
@ -301,29 +306,33 @@ impl PushScissorStackResult {
}
}
/// Returns true if scissor has been pushed.
/// Returns Some() if scissor has been pushed.
pub fn push_scissor_stack(
transform_stack: &mut TransformStack,
scissor_stack: &mut ScissorStack,
scroll_shift: Vec2,
info: &Option<ScrollbarInfo>,
style: &taffy::Style,
) -> PushScissorStackResult {
) -> Option<PushScissorStackResult> {
let mut boundary_absolute = drawing::Boundary::construct_absolute(transform_stack);
boundary_absolute.pos += scroll_shift;
let do_clip = info.is_some() && has_overflow_clip(style);
if !do_clip {
return None; // Don't care
}
scissor_stack.push(ScissorBoundary(boundary_absolute));
if scissor_stack.is_out_of_bounds() {
return PushScissorStackResult::OutOfBounds;
return Some(PushScissorStackResult::OutOfBounds);
}
if do_clip {
PushScissorStackResult::VisibleAndClip
Some(PushScissorStackResult::VisibleAndClip)
} else {
PushScissorStackResult::VisibleDontClip
Some(PushScissorStackResult::VisibleDontClip)
}
}
@ -368,14 +377,16 @@ fn draw_widget(
&boundary,
&state.transform_stack.get().transform,
Color::new(0.0, 1.0, 1.0, 0.5),
1.0,
));
}
let starting_scissor_set_count = internal.scissor_set_count;
let scissor_result = push_scissor_stack(state.transform_stack, state.scissor_stack, scroll_shift, &info, style);
if scissor_result == PushScissorStackResult::VisibleAndClip {
if let Some(scissor_result) = &scissor_result
&& *scissor_result == PushScissorStackResult::VisibleAndClip
{
if params.debug_draw {
let mut boundary_relative = drawing::Boundary::construct_relative(state.transform_stack);
boundary_relative.pos += scroll_shift;
@ -383,6 +394,7 @@ fn draw_widget(
&boundary_relative,
&state.transform_stack.get().transform,
Color::new(1.0, 0.0, 1.0, 1.0),
3.0,
));
}
state
@ -397,12 +409,17 @@ fn draw_widget(
style,
};
if scissor_result.should_display() {
if scissor_result
.as_ref()
.is_none_or(PushScissorStackResult::should_display)
{
widget_state.draw_all(state, &draw_params);
draw_children(params, state, node_id, internal, false);
}
state.scissor_stack.pop();
if scissor_result.is_some() {
state.scissor_stack.pop();
}
let current_scissor_set_count = internal.scissor_set_count;
@ -412,12 +429,12 @@ fn draw_widget(
.push(drawing::RenderPrimitive::ScissorSet(*state.scissor_stack.get()));
}
state.transform_stack.pop();
if let Some(info) = &info {
widget_state.draw_scrollbars(state, &draw_params, info);
}
state.transform_stack.pop();
if wants_redraw {
state.alterables.mark_redraw();
}

View File

@ -2,6 +2,7 @@ use std::{
any::{Any, TypeId},
cell::{Ref, RefMut},
collections::HashSet,
rc::{Rc, Weak},
};
use glam::Vec2;
@ -9,9 +10,10 @@ use slotmap::{DenseSlotMap, new_key_type};
use crate::{
animation::{self, Animation},
components::{ComponentTrait, ComponentWeak},
globals,
i18n::I18n,
layout::{LayoutState, LayoutTask, WidgetID},
layout::{LayoutDispatchFunc, LayoutState, LayoutTask, WidgetID},
sound::WguiSoundType,
stack::{ScissorStack, Transform, TransformStack},
widget::{EventResult, WidgetData, WidgetObj},
@ -25,7 +27,7 @@ pub enum MouseButtonIndex {
}
#[derive(Debug, Clone, Copy)]
pub struct MouseButton {
pub struct MouseButtonEvent {
pub index: MouseButtonIndex,
pub pos: Vec2,
pub device: usize,
@ -37,12 +39,6 @@ pub struct MousePosition {
pub device: usize,
}
pub struct MouseDownEvent {
pub pos: Vec2,
pub index: MouseButtonIndex,
pub device: usize,
}
pub struct MouseLeaveEvent {
pub device: usize,
}
@ -52,29 +48,29 @@ pub struct MouseMotionEvent {
pub device: usize,
}
pub struct MouseUpEvent {
pub pos: Vec2,
pub index: MouseButtonIndex,
pub device: usize,
}
pub struct MouseWheelEvent {
pub pos: Vec2, /* mouse position */
pub delta: Vec2, /* wheel delta */
pub device: usize,
}
#[derive(Clone)]
pub struct TextInputEvent {
pub text: Option<Rc<str>>,
}
pub struct InternalStateChangeEvent {
pub metadata: usize,
}
pub enum Event {
InternalStateChange(InternalStateChangeEvent),
MouseDown(MouseDownEvent),
MouseDown(MouseButtonEvent),
MouseLeave(MouseLeaveEvent),
MouseMotion(MouseMotionEvent),
MouseUp(MouseUpEvent),
MouseUp(MouseButtonEvent),
MouseWheel(MouseWheelEvent),
TextInput(TextInputEvent),
}
impl Event {
@ -101,12 +97,14 @@ pub enum StyleSetRequest {
Margin(taffy::Rect<taffy::LengthPercentageAuto>),
Width(taffy::Dimension),
Height(taffy::Dimension),
Size(taffy::Size<taffy::Dimension>),
}
// alterables which will be dispatched in the next loop iteration phase
#[derive(Default)]
pub struct EventAlterables {
pub dirty_widgets: Vec<WidgetID>,
pub components_to_refresh_once: Vec<ComponentWeak>,
pub style_set_requests: Vec<(WidgetID, StyleSetRequest)>,
pub animations: Vec<animation::Animation>,
pub widgets_to_tick: HashSet<WidgetID>, // widgets which needs to be ticked in the next `Layout::update()` fn
@ -147,8 +145,20 @@ impl EventAlterables {
self.tasks.push(LayoutTask::PlaySound(sound_type));
}
pub fn dispatch(&mut self, func: Box<dyn FnOnce(&mut CallbackDataCommon) -> anyhow::Result<()>>) {
self.tasks.push(LayoutTask::Dispatch(func))
pub fn dispatch(&mut self, func: LayoutDispatchFunc) {
self.tasks.push(LayoutTask::Dispatch(func));
}
pub fn focus<ComponentType: ComponentTrait>(&mut self, component: &Weak<ComponentType>) {
self.tasks.push(LayoutTask::SetFocus(component.clone()));
}
pub fn unfocus(&mut self) {
self.tasks.push(LayoutTask::Unfocus);
}
pub fn refresh_component_once<ComponentType: ComponentTrait>(&mut self, component: &Weak<ComponentType>) {
self.components_to_refresh_once.push(component.clone());
}
}
@ -187,8 +197,9 @@ pub struct CallbackData<'a> {
pub enum CallbackMetadata {
None,
MouseButton(MouseButton),
MouseButton(MouseButtonEvent),
MousePosition(MousePosition),
TextInput(TextInputEvent),
Custom(usize),
}
@ -197,9 +208,10 @@ impl CallbackMetadata {
pub const fn get_mouse_pos_absolute(&self) -> Option<Vec2> {
match *self {
Self::MouseButton(b) => Some(b.pos),
Self::MousePosition(b) => Some(b.pos),
Self::Custom(_) | Self::None => None,
CallbackMetadata::MouseButton(b) => Some(b.pos),
CallbackMetadata::MousePosition(b) => Some(b.pos),
CallbackMetadata::Custom(_) | CallbackMetadata::None => None,
CallbackMetadata::TextInput(_) => None,
}
}
@ -216,6 +228,7 @@ pub enum EventListenerKind {
MouseEnter,
MouseMotion,
MouseLeave,
TextInput,
InternalStateChange,
}

View File

@ -26,6 +26,7 @@ pub struct Defaults {
pub danger_color: drawing::Color,
pub faded_color: drawing::Color,
pub bg_color: drawing::Color,
pub editbox_color: drawing::Color,
pub translucent_alpha: f32,
pub animation_mult: f32,
pub rounding_mult: f32,
@ -42,6 +43,7 @@ impl Default for Defaults {
danger_color: drawing::Color::new(0.9, 0.0, 0.0, 1.0),
faded_color: drawing::Color::new(0.67, 0.74, 0.80, 1.0),
bg_color: drawing::Color::new(0.0, 0.07, 0.1, 0.75),
editbox_color: drawing::Color::new(0.15, 0.25, 0.35, 0.95),
translucent_alpha: 0.5,
animation_mult: 1.0,
rounding_mult: 1.0,

View File

@ -7,8 +7,10 @@ use std::{
use crate::{
animation::Animations,
components::{Component, RefreshData},
drawing::{self, ANSI_BOLD_CODE, ANSI_RESET_CODE, Boundary, push_scissor_stack, push_transform_stack},
components::{self, Component, ComponentWeak, FocusChangeData, RefreshData},
drawing::{
self, ANSI_BOLD_CODE, ANSI_RESET_CODE, Boundary, PushScissorStackResult, push_scissor_stack, push_transform_stack,
},
event::{self, CallbackDataCommon, EventAlterables},
globals::WguiGlobals,
sound::WguiSoundType,
@ -18,7 +20,7 @@ use crate::{
use anyhow::Context;
use glam::{Vec2, vec2};
use slotmap::{HopSlotMap, SecondaryMap, new_key_type};
use slotmap::{SecondaryMap, SlotMap, new_key_type};
use taffy::{NodeId, TaffyTree, TraversePartialTree};
new_key_type! {
@ -57,7 +59,7 @@ impl WeakWidget {
}
}
pub struct WidgetMap(HopSlotMap<WidgetID, Widget>);
pub struct WidgetMap(SlotMap<WidgetID, Widget>);
pub type WidgetNodeMap = SecondaryMap<WidgetID, taffy::NodeId>;
#[derive(Clone)]
@ -68,7 +70,7 @@ pub struct WidgetPair {
impl WidgetMap {
fn new() -> Self {
Self(HopSlotMap::with_key())
Self(SlotMap::<WidgetID, Widget>::with_key())
}
pub fn get_as<T: 'static>(&self, handle: WidgetID) -> Option<RefMut<'_, T>> {
@ -134,14 +136,17 @@ pub struct LayoutUpdateResult {
pub sounds_to_play: Vec<WguiSoundType>,
}
pub type ModifyLayoutStateFunc = Box<dyn FnOnce(ModifyLayoutStateData) -> anyhow::Result<()>>;
pub type LayoutModifyStateFunc = Box<dyn FnOnce(ModifyLayoutStateData) -> anyhow::Result<()>>;
pub type LayoutDispatchFunc = Box<dyn FnOnce(&mut CallbackDataCommon) -> anyhow::Result<()>>;
pub enum LayoutTask {
RemoveWidget(WidgetID),
SetWidgetStyle(WidgetID, event::StyleSetRequest),
ModifyLayoutState(ModifyLayoutStateFunc),
ModifyLayoutState(LayoutModifyStateFunc),
PlaySound(WguiSoundType),
Dispatch(Box<dyn FnOnce(&mut CallbackDataCommon) -> anyhow::Result<()>>),
Dispatch(LayoutDispatchFunc),
SetFocus(ComponentWeak),
Unfocus,
}
pub type LayoutTasks = Tasks<LayoutTask>;
@ -152,8 +157,9 @@ pub struct Layout {
pub tasks: LayoutTasks,
components_to_refresh_once: HashSet<Component>,
registered_components_to_refresh: HashMap<taffy::NodeId, Component>,
registered_components_to_refresh: HashMap<taffy::NodeId, ComponentWeak>,
sounds_to_play_once: Vec<WguiSoundType>,
focused_component: Option<ComponentWeak>,
pub widgets_to_tick: Vec<WidgetID>,
@ -358,14 +364,14 @@ impl Layout {
}
// call ComponentTrait::refresh() *every time time* the layout is dirty
pub fn register_component_refresh(&mut self, component: Component) {
pub fn register_component_refresh(&mut self, component: &Component) {
let widget_id = component.0.base().get_id();
let Some(node_id) = self.state.nodes.get(widget_id) else {
debug_assert!(false);
return;
};
self.registered_components_to_refresh.insert(*node_id, component);
self.registered_components_to_refresh.insert(*node_id, component.weak());
}
/// Convenience function to avoid repeated `WidgetID` → `WidgetState` lookups.
@ -455,7 +461,10 @@ impl Layout {
style,
);
if scissor_result.should_display() {
if scissor_result
.as_ref()
.is_none_or(PushScissorStackResult::should_display)
{
// check children first
self.push_event_children(node_id, event, event_result, alterables, user_data)?;
@ -472,7 +481,9 @@ impl Layout {
}
}
alterables.scissor_stack.pop();
if scissor_result.is_some() {
alterables.scissor_stack.pop();
}
alterables.transform_stack.pop();
Ok(())
@ -581,6 +592,7 @@ impl Layout {
widgets_to_tick: Vec::new(),
tasks: LayoutTasks::new(),
sounds_to_play_once: Vec::new(),
focused_component: None,
})
}
@ -590,8 +602,10 @@ impl Layout {
return;
}
if let Some(component) = self.registered_components_to_refresh.get(&node_id) {
to_refresh.push(component.clone());
if let Some(component) = self.registered_components_to_refresh.get(&node_id)
&& let Some(c) = component.upgrade()
{
to_refresh.push(Component(c));
}
for child_id in self.state.tree.child_ids(node_id) {
@ -708,15 +722,48 @@ impl Layout {
c.finish()?;
}
LayoutTask::SetWidgetStyle(widget_id, style_request) => {
self.set_style_request(widget_id, style_request);
self.set_style_request(widget_id, &style_request);
}
LayoutTask::SetFocus(weak) => {
if let Some(c) = weak.upgrade() {
self.set_focus(Some(&components::Component(c)))?;
}
}
LayoutTask::Unfocus => self.set_focus(None)?,
}
}
Ok(())
}
fn set_style_request(&mut self, widget_id: WidgetID, style_request: event::StyleSetRequest) {
pub fn set_focus(&mut self, to_focus: Option<&Component>) -> anyhow::Result<()> {
let mut c = self.start_common();
if let Some(focused) = &c.layout.focused_component
&& let Some(focused) = focused.upgrade()
{
// Unfocus
focused.on_focus_change(&mut FocusChangeData {
common: &mut c.common(),
focused: false,
});
c.layout.focused_component = None;
}
if let Some(to_focus) = to_focus {
to_focus.0.on_focus_change(&mut FocusChangeData {
common: &mut c.common(),
focused: true,
});
c.layout.focused_component = Some(to_focus.weak());
}
c.finish()?;
Ok(())
}
fn set_style_request(&mut self, widget_id: WidgetID, style_request: &event::StyleSetRequest) {
let Some(node_id) = self.state.nodes.get(widget_id) else {
return;
};
@ -728,22 +775,26 @@ impl Layout {
match style_request {
event::StyleSetRequest::Display(display) => {
// refresh the component in case if visibility/display mode has changed
if cur_style.display != display
if cur_style.display != *display
&& let Some(component) = self.registered_components_to_refresh.get(node_id)
&& let Some(c) = component.upgrade()
{
self.components_to_refresh_once.insert(component.clone());
self.components_to_refresh_once.insert(Component(c));
}
cur_style.display = display;
cur_style.display = *display;
}
event::StyleSetRequest::Margin(margin) => {
cur_style.margin = margin;
cur_style.margin = *margin;
}
event::StyleSetRequest::Width(val) => {
cur_style.size.width = val;
cur_style.size.width = *val;
}
event::StyleSetRequest::Height(val) => {
cur_style.size.height = val;
cur_style.size.height = *val;
}
event::StyleSetRequest::Size(size) => {
cur_style.size = *size;
}
}
@ -786,8 +837,14 @@ impl Layout {
}
}
for (widget_id, style_request) in alterables.style_set_requests {
self.set_style_request(widget_id, style_request);
for c in alterables.components_to_refresh_once {
if let Some(c) = c.upgrade() {
self.defer_component_refresh(components::Component(c));
}
}
for (widget_id, style_request) in &alterables.style_set_requests {
self.set_style_request(*widget_id, style_request);
}
Ok(())

View File

@ -18,7 +18,8 @@
clippy::needless_pass_by_ref_mut,
clippy::use_self,
clippy::match_same_arms,
clippy::too_many_lines
clippy::too_many_lines,
clippy::struct_excessive_bools
)]
pub mod animation;

View File

@ -0,0 +1,40 @@
use crate::{
components::{Component, editbox},
layout::WidgetID,
parser::{AttribPair, ParserContext, process_component, style::parse_style},
widget::ConstructEssentials,
};
pub fn parse_component_editbox(
ctx: &mut ParserContext,
parent_id: WidgetID,
attribs: &[AttribPair],
tag_name: &str,
) -> anyhow::Result<WidgetID> {
let mut initial_text = String::new();
let style = parse_style(ctx, attribs, tag_name);
for pair in attribs {
let (key, value) = (pair.attrib.as_ref(), pair.value.as_ref());
#[allow(clippy::single_match)]
match key {
"text" => {
initial_text = String::from(value);
}
_ => {}
}
}
let (widget, component) = editbox::construct(
&mut ConstructEssentials {
layout: ctx.layout,
parent: parent_id,
},
editbox::Params { style, initial_text },
)?;
process_component(ctx, Component(component), widget.id, attribs);
Ok(widget.id)
}

View File

@ -1,5 +1,6 @@
mod component_button;
mod component_checkbox;
mod component_editbox;
mod component_radio_group;
mod component_slider;
mod component_tabs;
@ -22,6 +23,7 @@ use crate::{
parser::{
component_button::parse_component_button,
component_checkbox::{CheckboxKind, parse_component_checkbox},
component_editbox::parse_component_editbox,
component_radio_group::parse_component_radio_group,
component_slider::parse_component_slider,
component_tabs::parse_component_tabs,
@ -1053,6 +1055,7 @@ fn parse_child<'a>(
file, ctx, child_node, parent_id, &attribs, tag_name,
)?);
}
"EditBox" => new_widget_id = Some(parse_component_editbox(ctx, parent_id, &attribs, tag_name)?),
"Tabs" => {
new_widget_id = Some(parse_component_tabs(ctx, child_node, parent_id, &attribs, tag_name)?);
}

View File

@ -39,6 +39,11 @@ impl<T: StackItem<T>, const STACK_MAX: usize> GenericStack<T, STACK_MAX> {
pub const fn get(&self) -> &T {
&self.stack[(self.top - 1) as usize]
}
pub const fn parent(&self) -> &T {
debug_assert!(self.top >= 2);
&self.stack[(self.top - 2) as usize]
}
}
impl<T: StackItem<T>, const STACK_MAX: usize> Default for GenericStack<T, STACK_MAX> {
@ -54,9 +59,11 @@ impl<T: StackItem<T>, const STACK_MAX: usize> Default for GenericStack<T, STACK_
#[derive(Debug, Copy, Clone)]
pub struct Transform {
pub rel_pos: Vec2,
pub visual_dim: Vec2, // for convenience
pub raw_dim: Vec2, // for convenience
pub abs_pos: Vec2, // for convenience, will be set after pushing
pub visual_dim: Vec2,
pub raw_dim: Vec2,
pub content_rel_pos: Vec2,
pub content_dim: Vec2,
pub abs_pos: Vec2, // for convenience, will be set after pushing
pub transform: glam::Mat4,
pub transform_rel: glam::Mat4,
}
@ -68,6 +75,8 @@ impl Default for Transform {
rel_pos: Default::default(),
visual_dim: Default::default(),
raw_dim: Default::default(),
content_dim: Default::default(),
content_rel_pos: Default::default(),
transform: Mat4::IDENTITY,
transform_rel: Default::default(),
}

View File

@ -311,6 +311,8 @@ impl WidgetState {
}
pub fn get_scroll_shift_smooth(&self, info: &ScrollbarInfo, l: &taffy::Layout, timestep_alpha: f32) -> (Vec2, bool) {
//return (self.get_scroll_shift_raw(info, l), false);
let currently_animating = self.data.scrolling_cur != self.data.scrolling_cur_prev;
let scrolling = self
@ -368,7 +370,8 @@ impl WidgetState {
return;
}
let transform = state.transform_stack.get();
let transform_widget = state.transform_stack.get();
let transform_parent = state.transform_stack.parent();
let thickness = 6.0;
let margin = 6.0;
@ -385,12 +388,15 @@ impl WidgetState {
PrimitiveExtent {
boundary: drawing::Boundary::from_pos_size(
Vec2::new(
transform.rel_pos.x + transform.raw_dim.x * (1.0 - info.handle_size.x) * self.data.scrolling_cur.x,
transform.rel_pos.y + transform.raw_dim.y - thickness - margin,
// X
transform_widget.content_rel_pos.x
+ transform_parent.raw_dim.x * (1.0 - info.handle_size.x) * self.data.scrolling_cur.x,
// Y
transform_widget.content_rel_pos.y + transform_widget.raw_dim.y - thickness - margin,
),
Vec2::new(transform.raw_dim.x * info.handle_size.x, thickness),
Vec2::new(transform_widget.raw_dim.x * info.handle_size.x, thickness),
),
transform: transform.transform,
transform: transform_parent.transform,
},
rect_params,
));
@ -402,12 +408,15 @@ impl WidgetState {
PrimitiveExtent {
boundary: drawing::Boundary::from_pos_size(
Vec2::new(
transform.rel_pos.x + transform.raw_dim.x - thickness - margin,
transform.rel_pos.y + transform.raw_dim.y * (1.0 - info.handle_size.y) * self.data.scrolling_cur.y,
// X
transform_widget.content_rel_pos.x + transform_widget.raw_dim.x - thickness - margin,
// Y
transform_widget.content_rel_pos.y
+ transform_parent.raw_dim.y * (1.0 - info.handle_size.y) * self.data.scrolling_cur.y,
),
Vec2::new(thickness, transform.raw_dim.y * info.handle_size.y),
Vec2::new(thickness, transform_widget.raw_dim.y * info.handle_size.y),
),
transform: transform.transform,
transform: transform_parent.transform,
},
rect_params,
));
@ -489,16 +498,19 @@ impl WidgetState {
let mut res: Option<InvokeListenersResult> = None;
match &event {
Event::TextInput(e) => {
res = Some(self.invoke_listeners(
&mut invoke_data,
EventListenerKind::TextInput,
CallbackMetadata::TextInput(e.clone()),
)?);
}
Event::MouseDown(e) => {
if hovered && self.data.set_device_pressed(e.device, true) {
res = Some(self.invoke_listeners(
&mut invoke_data,
EventListenerKind::MousePress,
CallbackMetadata::MouseButton(event::MouseButton {
index: e.index,
pos: e.pos,
device: e.device,
}),
CallbackMetadata::MouseButton(*e),
)?);
}
}
@ -507,11 +519,7 @@ impl WidgetState {
res = Some(self.invoke_listeners(
&mut invoke_data,
EventListenerKind::MouseRelease,
CallbackMetadata::MouseButton(event::MouseButton {
index: e.index,
pos: e.pos,
device: e.device,
}),
CallbackMetadata::MouseButton(*e),
)?);
}
}