wgui: WguiWindow, open/close
This commit is contained in:
@@ -18,8 +18,8 @@ use wgui::{
|
|||||||
i18n::Translation,
|
i18n::Translation,
|
||||||
layout::{Layout, LayoutParams, RcLayout, Widget},
|
layout::{Layout, LayoutParams, RcLayout, Widget},
|
||||||
parser::{Fetchable, ParseDocumentExtra, ParseDocumentParams, ParserState},
|
parser::{Fetchable, ParseDocumentExtra, ParseDocumentParams, ParserState},
|
||||||
taffy::{self, prelude::length},
|
widget::{label::WidgetLabel, rectangle::WidgetRectangle},
|
||||||
widget::{div::WidgetDiv, label::WidgetLabel, rectangle::WidgetRectangle},
|
windowing::{WguiWindow, WguiWindowParams},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub enum TestbedTask {
|
pub enum TestbedTask {
|
||||||
@@ -30,12 +30,15 @@ struct Data {
|
|||||||
tasks: VecDeque<TestbedTask>,
|
tasks: VecDeque<TestbedTask>,
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
state: ParserState,
|
state: ParserState,
|
||||||
|
|
||||||
|
popup_window: WguiWindow,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct TestbedGeneric {
|
pub struct TestbedGeneric {
|
||||||
pub layout: RcLayout,
|
pub layout: RcLayout,
|
||||||
|
|
||||||
|
globals: WguiGlobals,
|
||||||
data: Rc<RefCell<Data>>,
|
data: Rc<RefCell<Data>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,7 +109,7 @@ impl TestbedGeneric {
|
|||||||
let (layout, state) = wgui::parser::new_layout_from_assets(
|
let (layout, state) = wgui::parser::new_layout_from_assets(
|
||||||
listeners,
|
listeners,
|
||||||
&ParseDocumentParams {
|
&ParseDocumentParams {
|
||||||
globals,
|
globals: globals.clone(),
|
||||||
path: XML_PATH,
|
path: XML_PATH,
|
||||||
extra,
|
extra,
|
||||||
},
|
},
|
||||||
@@ -148,9 +151,11 @@ impl TestbedGeneric {
|
|||||||
|
|
||||||
let testbed = Self {
|
let testbed = Self {
|
||||||
layout: layout.as_rc(),
|
layout: layout.as_rc(),
|
||||||
|
globals: globals.clone(),
|
||||||
data: Rc::new(RefCell::new(Data {
|
data: Rc::new(RefCell::new(Data {
|
||||||
state,
|
state,
|
||||||
tasks: Default::default(),
|
tasks: Default::default(),
|
||||||
|
popup_window: WguiWindow::default(),
|
||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -187,51 +192,14 @@ impl TestbedGeneric {
|
|||||||
&mut self,
|
&mut self,
|
||||||
params: &mut TestbedUpdateParams,
|
params: &mut TestbedUpdateParams,
|
||||||
layout: &mut Layout,
|
layout: &mut Layout,
|
||||||
_data: &mut Data,
|
data: &mut Data,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
const XML_PATH: AssetPath = AssetPath::WguiInternal("wgui/window_frame.xml");
|
data.popup_window.open(WguiWindowParams {
|
||||||
|
globals: self.globals.clone(),
|
||||||
let globals = WguiGlobals::new(
|
position: Vec2::new(128.0, 128.0),
|
||||||
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(),
|
|
||||||
},
|
|
||||||
layout,
|
layout,
|
||||||
params.listeners,
|
listeners: 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));
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,9 @@
|
|||||||
<rectangle width="100%" height="100%" round="4" align_items="center" justify_content="space_between"
|
<rectangle width="100%" height="100%" round="4" align_items="center" justify_content="space_between"
|
||||||
gradient="vertical" color="#224466" color2="#113355">
|
gradient="vertical" color="#224466" color2="#113355">
|
||||||
<label margin_left="8" text="Window title" weight="bold" />
|
<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>
|
</rectangle>
|
||||||
|
|
||||||
<!-- content itself -->
|
<!-- content itself -->
|
||||||
|
|||||||
@@ -180,6 +180,8 @@ _Translated by key_
|
|||||||
|
|
||||||
`round`: **float** (default: 4) | **percent** (0-100%)
|
`round`: **float** (default: 4) | **percent** (0-100%)
|
||||||
|
|
||||||
|
`border`: **float** (default: 2)
|
||||||
|
|
||||||
`color`: #FFAABB | #FFAABBCC
|
`color`: #FFAABB | #FFAABBCC
|
||||||
|
|
||||||
`border_color`: #FFAABB | #FFAABBCC
|
`border_color`: #FFAABB | #FFAABBCC
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ use taffy::{AlignItems, JustifyContent};
|
|||||||
pub struct Params {
|
pub struct Params {
|
||||||
pub text: Option<Translation>, // if unset, label will not be populated
|
pub text: Option<Translation>, // if unset, label will not be populated
|
||||||
pub color: Option<drawing::Color>,
|
pub color: Option<drawing::Color>,
|
||||||
|
pub border: f32,
|
||||||
pub border_color: Option<drawing::Color>,
|
pub border_color: Option<drawing::Color>,
|
||||||
pub hover_border_color: Option<drawing::Color>,
|
pub hover_border_color: Option<drawing::Color>,
|
||||||
pub hover_color: Option<drawing::Color>,
|
pub hover_color: Option<drawing::Color>,
|
||||||
@@ -39,6 +40,7 @@ impl Default for Params {
|
|||||||
color: None,
|
color: None,
|
||||||
hover_color: None,
|
hover_color: None,
|
||||||
border_color: None,
|
border_color: None,
|
||||||
|
border: 2.0,
|
||||||
hover_border_color: None,
|
hover_border_color: None,
|
||||||
round: WLength::Units(4.0),
|
round: WLength::Units(4.0),
|
||||||
style: Default::default(),
|
style: Default::default(),
|
||||||
@@ -116,7 +118,6 @@ fn anim_hover(
|
|||||||
rect.params.color = bgcolor;
|
rect.params.color = bgcolor;
|
||||||
rect.params.color2 = get_color2(&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_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 {
|
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,
|
gradient: drawing::GradientMode::Vertical,
|
||||||
round: params.round,
|
round: params.round,
|
||||||
border_color,
|
border_color,
|
||||||
border: 2.0,
|
border: params.border,
|
||||||
}),
|
}),
|
||||||
style,
|
style,
|
||||||
)?;
|
)?;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use std::{
|
use std::{
|
||||||
cell::{RefCell, RefMut},
|
cell::{RefCell, RefMut},
|
||||||
|
collections::VecDeque,
|
||||||
io::Write,
|
io::Write,
|
||||||
rc::{Rc, Weak},
|
rc::{Rc, Weak},
|
||||||
};
|
};
|
||||||
@@ -108,9 +109,28 @@ pub struct LayoutState {
|
|||||||
pub tree: taffy::tree::TaffyTree<WidgetID>,
|
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 struct Layout {
|
||||||
pub state: LayoutState,
|
pub state: LayoutState,
|
||||||
|
|
||||||
|
pub tasks: LayoutTasks,
|
||||||
|
|
||||||
pub components_to_init: Vec<Component>,
|
pub components_to_init: Vec<Component>,
|
||||||
pub widgets_to_tick: Vec<WidgetID>,
|
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
|
// removes all children of a specific widget
|
||||||
pub fn remove_children(&mut self, widget_id: WidgetID) {
|
pub fn remove_children(&mut self, widget_id: WidgetID) {
|
||||||
let mut ids = Vec::new();
|
let mut ids = Vec::new();
|
||||||
@@ -231,12 +259,18 @@ impl Layout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (widget_id, node_id) in ids {
|
for (widget_id, node_id) in ids {
|
||||||
self.state.widgets.remove_single(widget_id);
|
self.remove_widget_single(widget_id, Some(node_id));
|
||||||
self.state.nodes.remove(widget_id);
|
|
||||||
self.state.tree.remove(node_id).unwrap();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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) {
|
pub const fn mark_redraw(&mut self) {
|
||||||
self.needs_redraw = true;
|
self.needs_redraw = true;
|
||||||
}
|
}
|
||||||
@@ -468,6 +502,7 @@ impl Layout {
|
|||||||
animations: Animations::default(),
|
animations: Animations::default(),
|
||||||
components_to_init: Vec::new(),
|
components_to_init: Vec::new(),
|
||||||
widgets_to_tick: Vec::new(),
|
widgets_to_tick: Vec::new(),
|
||||||
|
tasks: LayoutTasks::new(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -541,7 +576,21 @@ impl Layout {
|
|||||||
Ok(())
|
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<()> {
|
pub fn process_alterables(&mut self, alterables: EventAlterables) -> anyhow::Result<()> {
|
||||||
|
self.process_tasks();
|
||||||
|
|
||||||
for node in alterables.dirty_nodes {
|
for node in alterables.dirty_nodes {
|
||||||
self.state.tree.mark_dirty(node)?;
|
self.state.tree.mark_dirty(node)?;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ pub mod parser;
|
|||||||
pub mod renderer_vk;
|
pub mod renderer_vk;
|
||||||
pub mod stack;
|
pub mod stack;
|
||||||
pub mod widget;
|
pub mod widget;
|
||||||
|
pub mod windowing;
|
||||||
|
|
||||||
// re-exported libs
|
// re-exported libs
|
||||||
pub use cosmic_text;
|
pub use cosmic_text;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use crate::{
|
|||||||
i18n::Translation,
|
i18n::Translation,
|
||||||
layout::WidgetID,
|
layout::WidgetID,
|
||||||
parser::{
|
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},
|
style::{parse_color_opt, parse_round, parse_style, parse_text_style},
|
||||||
},
|
},
|
||||||
widget::util::WLength,
|
widget::util::WLength,
|
||||||
@@ -18,10 +18,12 @@ pub fn parse_component_button<'a, U1, U2>(
|
|||||||
attribs: &[AttribPair],
|
attribs: &[AttribPair],
|
||||||
) -> anyhow::Result<WidgetID> {
|
) -> anyhow::Result<WidgetID> {
|
||||||
let mut color: Option<Color> = None;
|
let mut color: Option<Color> = None;
|
||||||
|
let mut border = 2.0;
|
||||||
let mut border_color: Option<Color> = None;
|
let mut border_color: Option<Color> = None;
|
||||||
let mut hover_color: Option<Color> = None;
|
let mut hover_color: Option<Color> = None;
|
||||||
let mut hover_border_color: Option<Color> = None;
|
let mut hover_border_color: Option<Color> = None;
|
||||||
let mut round = WLength::Units(4.0);
|
let mut round = WLength::Units(4.0);
|
||||||
|
|
||||||
let mut translation: Option<Translation> = None;
|
let mut translation: Option<Translation> = None;
|
||||||
|
|
||||||
let text_style = parse_text_style(attribs);
|
let text_style = parse_text_style(attribs);
|
||||||
@@ -42,6 +44,9 @@ pub fn parse_component_button<'a, U1, U2>(
|
|||||||
"color" => {
|
"color" => {
|
||||||
parse_color_opt(value, &mut color);
|
parse_color_opt(value, &mut color);
|
||||||
}
|
}
|
||||||
|
"border" => {
|
||||||
|
parse_check_f32(value, &mut border);
|
||||||
|
}
|
||||||
"border_color" => {
|
"border_color" => {
|
||||||
parse_color_opt(value, &mut border_color);
|
parse_color_opt(value, &mut border_color);
|
||||||
}
|
}
|
||||||
@@ -64,6 +69,7 @@ pub fn parse_component_button<'a, U1, U2>(
|
|||||||
parent_id,
|
parent_id,
|
||||||
button::Params {
|
button::Params {
|
||||||
color,
|
color,
|
||||||
|
border,
|
||||||
border_color,
|
border_color,
|
||||||
hover_border_color,
|
hover_border_color,
|
||||||
hover_color,
|
hover_color,
|
||||||
|
|||||||
110
wgui/src/windowing.rs
Normal file
110
wgui/src/windowing.rs
Normal 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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user