wgui: WguiWindow, open/close

This commit is contained in:
Aleksander
2025-10-06 19:09:34 +02:00
parent fc4781dcaf
commit ce8cd3bce7
8 changed files with 192 additions and 53 deletions

View File

@@ -18,8 +18,8 @@ use wgui::{
i18n::Translation,
layout::{Layout, LayoutParams, RcLayout, Widget},
parser::{Fetchable, ParseDocumentExtra, ParseDocumentParams, ParserState},
taffy::{self, prelude::length},
widget::{div::WidgetDiv, label::WidgetLabel, rectangle::WidgetRectangle},
widget::{label::WidgetLabel, rectangle::WidgetRectangle},
windowing::{WguiWindow, WguiWindowParams},
};
pub enum TestbedTask {
@@ -30,12 +30,15 @@ struct Data {
tasks: VecDeque<TestbedTask>,
#[allow(dead_code)]
state: ParserState,
popup_window: WguiWindow,
}
#[derive(Clone)]
pub struct TestbedGeneric {
pub layout: RcLayout,
globals: WguiGlobals,
data: Rc<RefCell<Data>>,
}
@@ -106,7 +109,7 @@ impl TestbedGeneric {
let (layout, state) = wgui::parser::new_layout_from_assets(
listeners,
&ParseDocumentParams {
globals,
globals: globals.clone(),
path: XML_PATH,
extra,
},
@@ -148,9 +151,11 @@ impl TestbedGeneric {
let testbed = Self {
layout: layout.as_rc(),
globals: globals.clone(),
data: Rc::new(RefCell::new(Data {
state,
tasks: Default::default(),
popup_window: WguiWindow::default(),
})),
};
@@ -187,51 +192,14 @@ impl TestbedGeneric {
&mut self,
params: &mut TestbedUpdateParams,
layout: &mut Layout,
_data: &mut Data,
data: &mut Data,
) -> anyhow::Result<()> {
const XML_PATH: AssetPath = AssetPath::WguiInternal("wgui/window_frame.xml");
let globals = WguiGlobals::new(
Box::new(assets::Asset {}),
wgui::globals::Defaults::default(),
)?;
let (widget, _) = layout.add_topmost_child(
WidgetDiv::create(),
taffy::Style {
position: taffy::Position::Absolute,
margin: taffy::Rect {
left: length(64.0),
right: length(0.0),
top: length(64.0),
bottom: length(0.0),
},
..Default::default()
},
)?;
let state = wgui::parser::parse_from_assets(
&ParseDocumentParams {
globals,
path: XML_PATH,
extra: Default::default(),
},
data.popup_window.open(WguiWindowParams {
globals: self.globals.clone(),
position: Vec2::new(128.0, 128.0),
layout,
params.listeners,
widget.id,
)?;
let button = state
.fetch_component_as::<ComponentButton>("button")
.unwrap();
button.on_click(Box::new(move |_common, _e| {
log::info!("click");
Ok(())
}));
// temporary, preserve listeners state
Box::leak(Box::new(state));
listeners: params.listeners,
})?;
Ok(())
}

View File

@@ -11,7 +11,9 @@
<rectangle width="100%" height="100%" round="4" align_items="center" justify_content="space_between"
gradient="vertical" color="#224466" color2="#113355">
<label margin_left="8" text="Window title" weight="bold" />
<sprite src_internal="wgui/close.svg" width="24" height="24" />
<Button id="but_close" border="0" round="0">
<sprite src_internal="wgui/close.svg" width="24" height="24" />
</Button>
</rectangle>
<!-- content itself -->

View File

@@ -180,6 +180,8 @@ _Translated by key_
`round`: **float** (default: 4) | **percent** (0-100%)
`border`: **float** (default: 2)
`color`: #FFAABB | #FFAABBCC
`border_color`: #FFAABB | #FFAABBCC

View File

@@ -24,6 +24,7 @@ use taffy::{AlignItems, JustifyContent};
pub struct Params {
pub text: Option<Translation>, // if unset, label will not be populated
pub color: Option<drawing::Color>,
pub border: f32,
pub border_color: Option<drawing::Color>,
pub hover_border_color: Option<drawing::Color>,
pub hover_color: Option<drawing::Color>,
@@ -39,6 +40,7 @@ impl Default for Params {
color: None,
hover_color: None,
border_color: None,
border: 2.0,
hover_border_color: None,
round: WLength::Units(4.0),
style: Default::default(),
@@ -116,7 +118,6 @@ fn anim_hover(
rect.params.color = bgcolor;
rect.params.color2 = get_color2(&bgcolor);
rect.params.border_color = data.initial_border_color.lerp(&data.initial_hover_border_color, mult);
rect.params.border = 2.0;
}
fn anim_hover_create(data: Rc<Data>, state: Rc<RefCell<State>>, widget_id: WidgetID, fade_in: bool) -> Animation {
@@ -313,7 +314,7 @@ pub fn construct<U1, U2>(
gradient: drawing::GradientMode::Vertical,
round: params.round,
border_color,
border: 2.0,
border: params.border,
}),
style,
)?;

View File

@@ -1,5 +1,6 @@
use std::{
cell::{RefCell, RefMut},
collections::VecDeque,
io::Write,
rc::{Rc, Weak},
};
@@ -108,9 +109,28 @@ pub struct LayoutState {
pub tree: taffy::tree::TaffyTree<WidgetID>,
}
pub enum LayoutTask {
RemoveWidget(WidgetID),
}
#[derive(Clone)]
pub struct LayoutTasks(pub Rc<RefCell<VecDeque<LayoutTask>>>);
impl LayoutTasks {
fn new() -> Self {
Self(Rc::new(RefCell::new(VecDeque::new())))
}
pub fn push(&self, task: LayoutTask) {
self.0.borrow_mut().push_back(task);
}
}
pub struct Layout {
pub state: LayoutState,
pub tasks: LayoutTasks,
pub components_to_init: Vec<Component>,
pub widgets_to_tick: Vec<WidgetID>,
@@ -221,6 +241,14 @@ impl Layout {
}
}
fn remove_widget_single(&mut self, widget_id: WidgetID, node_id: Option<taffy::NodeId>) {
self.state.widgets.remove_single(widget_id);
self.state.nodes.remove(widget_id);
if let Some(node_id) = node_id {
let _ = self.state.tree.remove(node_id);
}
}
// removes all children of a specific widget
pub fn remove_children(&mut self, widget_id: WidgetID) {
let mut ids = Vec::new();
@@ -231,12 +259,18 @@ impl Layout {
}
for (widget_id, node_id) in ids {
self.state.widgets.remove_single(widget_id);
self.state.nodes.remove(widget_id);
self.state.tree.remove(node_id).unwrap();
self.remove_widget_single(widget_id, Some(node_id));
}
}
// remove widget and its children, recursively
pub fn remove_widget(&mut self, widget_id: WidgetID) {
self.remove_children(widget_id);
let node_id = self.state.nodes.get(widget_id);
self.remove_widget_single(widget_id, node_id.copied());
self.mark_redraw();
}
pub const fn mark_redraw(&mut self) {
self.needs_redraw = true;
}
@@ -468,6 +502,7 @@ impl Layout {
animations: Animations::default(),
components_to_init: Vec::new(),
widgets_to_tick: Vec::new(),
tasks: LayoutTasks::new(),
})
}
@@ -541,7 +576,21 @@ impl Layout {
Ok(())
}
fn process_tasks(&mut self) {
let tasks = self.tasks.clone();
let mut tasks = tasks.0.borrow_mut();
while let Some(task) = tasks.pop_front() {
match task {
LayoutTask::RemoveWidget(widget_id) => {
self.remove_widget(widget_id);
}
}
}
}
pub fn process_alterables(&mut self, alterables: EventAlterables) -> anyhow::Result<()> {
self.process_tasks();
for node in alterables.dirty_nodes {
self.state.tree.mark_dirty(node)?;
}

View File

@@ -35,6 +35,7 @@ pub mod parser;
pub mod renderer_vk;
pub mod stack;
pub mod widget;
pub mod windowing;
// re-exported libs
pub use cosmic_text;

View File

@@ -4,7 +4,7 @@ use crate::{
i18n::Translation,
layout::WidgetID,
parser::{
AttribPair, ParserContext, ParserFile, parse_children, process_component,
AttribPair, ParserContext, ParserFile, parse_check_f32, parse_children, process_component,
style::{parse_color_opt, parse_round, parse_style, parse_text_style},
},
widget::util::WLength,
@@ -18,10 +18,12 @@ pub fn parse_component_button<'a, U1, U2>(
attribs: &[AttribPair],
) -> anyhow::Result<WidgetID> {
let mut color: Option<Color> = None;
let mut border = 2.0;
let mut border_color: Option<Color> = None;
let mut hover_color: Option<Color> = None;
let mut hover_border_color: Option<Color> = None;
let mut round = WLength::Units(4.0);
let mut translation: Option<Translation> = None;
let text_style = parse_text_style(attribs);
@@ -42,6 +44,9 @@ pub fn parse_component_button<'a, U1, U2>(
"color" => {
parse_color_opt(value, &mut color);
}
"border" => {
parse_check_f32(value, &mut border);
}
"border_color" => {
parse_color_opt(value, &mut border_color);
}
@@ -64,6 +69,7 @@ pub fn parse_component_button<'a, U1, U2>(
parent_id,
button::Params {
color,
border,
border_color,
hover_border_color,
hover_color,

110
wgui/src/windowing.rs Normal file
View File

@@ -0,0 +1,110 @@
use std::{cell::RefCell, rc::Rc};
use glam::Vec2;
use taffy::prelude::length;
use crate::{
assets::AssetPath,
components::button::ComponentButton,
event::EventListenerCollection,
globals::WguiGlobals,
layout::{Layout, LayoutTask, LayoutTasks, WidgetPair},
parser::{self, Fetchable, ParserState},
widget::div::WidgetDiv,
};
struct OpenedWindow {
layout_tasks: LayoutTasks,
widget: WidgetPair,
#[allow(dead_code)]
state: ParserState,
}
impl Drop for OpenedWindow {
fn drop(&mut self) {
self.layout_tasks.push(LayoutTask::RemoveWidget(self.widget.id));
}
}
struct State {
opened_window: Option<OpenedWindow>,
}
#[derive(Clone)]
pub struct WguiWindow(Rc<RefCell<State>>);
pub struct WguiWindowParams<'a> {
pub position: Vec2,
pub globals: WguiGlobals,
pub layout: &'a mut Layout,
pub listeners: &'a mut EventListenerCollection<(), ()>,
}
impl Default for WguiWindow {
fn default() -> Self {
Self(Rc::new(RefCell::new(State { opened_window: None })))
}
}
impl WguiWindow {
pub fn close(&self) {
self.0.borrow_mut().opened_window = None;
}
pub fn open(&mut self, params: WguiWindowParams) -> anyhow::Result<()> {
// close previous one if it's already open
self.close();
const XML_PATH: AssetPath = AssetPath::WguiInternal("wgui/window_frame.xml");
let (widget, _) = params.layout.add_topmost_child(
WidgetDiv::create(),
taffy::Style {
position: taffy::Position::Absolute,
margin: taffy::Rect {
left: length(params.position.x),
right: length(0.0),
top: length(params.position.y),
bottom: length(0.0),
},
..Default::default()
},
)?;
let state = parser::parse_from_assets(
&parser::ParseDocumentParams {
globals: params.globals,
path: XML_PATH,
extra: Default::default(),
},
params.layout,
params.listeners,
widget.id,
)?;
let but_close = state.fetch_component_as::<ComponentButton>("but_close").unwrap();
but_close.on_click({
let this = self.clone();
Box::new(move |_common, _e| {
this.close();
Ok(())
})
});
let button = state.fetch_component_as::<ComponentButton>("button").unwrap();
button.on_click(Box::new(move |_common, _e| {
log::info!("click");
Ok(())
}));
self.0.borrow_mut().opened_window = Some(OpenedWindow {
widget,
state,
layout_tasks: params.layout.tasks.clone(),
});
Ok(())
}
}