diff --git a/dash-frontend/src/frontend.rs b/dash-frontend/src/frontend.rs index 18f1d579..26a1970c 100644 --- a/dash-frontend/src/frontend.rs +++ b/dash-frontend/src/frontend.rs @@ -485,7 +485,6 @@ impl Frontend { fn action_show_audio_settings(&mut self) -> anyhow::Result<()> { self.window_audio_settings.open(&mut WguiWindowParams { - globals: &self.globals, position: Vec2::new(64.0, 64.0), layout: &mut self.layout, extra: WguiWindowParamsExtra { diff --git a/dash-frontend/src/tab/monado.rs b/dash-frontend/src/tab/monado.rs index 0f714306..abc65c7f 100644 --- a/dash-frontend/src/tab/monado.rs +++ b/dash-frontend/src/tab/monado.rs @@ -267,7 +267,6 @@ impl SubtabGeneralSettings { let tasks = tasks.clone(); Box::new(move |_common, e| { tasks.push(Task::SetBrightness(e.value / 100.0)); - Ok(()) }) }); } diff --git a/dash-frontend/src/tab/settings/macros.rs b/dash-frontend/src/tab/settings/macros.rs index 8c761412..235c053e 100644 --- a/dash-frontend/src/tab/settings/macros.rs +++ b/dash-frontend/src/tab/settings/macros.rs @@ -118,7 +118,6 @@ pub fn options_slider_f32( let tasks = mp.tasks.clone(); move |_common, e| { tasks.push(Task::UpdateFloat(setting, e.value)); - Ok(()) } })); @@ -168,7 +167,6 @@ pub fn options_slider_i32( let tasks = mp.tasks.clone(); move |_common, e| { tasks.push(Task::UpdateInt(setting, e.value as i32)); - Ok(()) } })); Ok(()) diff --git a/dash-frontend/src/views/audio_settings.rs b/dash-frontend/src/views/audio_settings.rs index 61061d33..8284ea50 100644 --- a/dash-frontend/src/views/audio_settings.rs +++ b/dash-frontend/src/views/audio_settings.rs @@ -823,8 +823,10 @@ impl View { slider.on_value_changed({ let control = params.control.clone(); Box::new(move |_common, event| { - control.on_volume_change(event.value * VOLUME_MULT)?; - Ok(()) + if let Err(e) = control.on_volume_change(event.value * VOLUME_MULT) { + log::error!("{:?}", e); + debug_assert!(false); + }; }) }); diff --git a/uidev/assets/gui/testbed.xml b/uidev/assets/gui/testbed.xml index b008f7fe..823d94c1 100644 --- a/uidev/assets/gui/testbed.xml +++ b/uidev/assets/gui/testbed.xml @@ -92,4 +92,4 @@ - \ No newline at end of file + diff --git a/uidev/assets/gui/various_widgets.xml b/uidev/assets/gui/various_widgets.xml index 14ae8016..163e7391 100644 --- a/uidev/assets/gui/various_widgets.xml +++ b/uidev/assets/gui/various_widgets.xml @@ -59,7 +59,11 @@ -
+
+ +
- \ No newline at end of file + diff --git a/uidev/src/testbed/testbed_generic.rs b/uidev/src/testbed/testbed_generic.rs index 6f8425c6..802863f4 100644 --- a/uidev/src/testbed/testbed_generic.rs +++ b/uidev/src/testbed/testbed_generic.rs @@ -229,7 +229,6 @@ impl TestbedGeneric { data: &mut Data, ) -> anyhow::Result<()> { data.popup_window.open(&mut WguiWindowParams { - globals: &self.globals, position: Vec2::new(128.0, 128.0), layout: &mut self.layout, extra: WguiWindowParamsExtra { diff --git a/wayvr/src/overlays/edit/mod.rs b/wayvr/src/overlays/edit/mod.rs index 04f4fd94..66b19a40 100644 --- a/wayvr/src/overlays/edit/mod.rs +++ b/wayvr/src/overlays/edit/mod.rs @@ -674,7 +674,6 @@ fn set_up_slider( OverlaySelector::Id(*overlay_id.borrow()), Box::new(move |app, owc| callback(app, owc, e_value)), ))); - Ok(()) })); Ok(()) diff --git a/wgui/assets/wgui/color_selector.xml b/wgui/assets/wgui/color_selector.xml new file mode 100644 index 00000000..d2b1d9d4 --- /dev/null +++ b/wgui/assets/wgui/color_selector.xml @@ -0,0 +1,25 @@ + + + + + + +
+
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+
diff --git a/wgui/src/components/bar_graph.rs b/wgui/src/components/bar_graph.rs index 083369a4..37026997 100644 --- a/wgui/src/components/bar_graph.rs +++ b/wgui/src/components/bar_graph.rs @@ -70,7 +70,11 @@ impl ComponentTrait for ComponentBarGraph { fn refresh(&self, data: &mut RefreshData) { let state = self.state.borrow(); - self.update_limits_text(&state, data.common); + + // FIXME: refactor this after merging feat-skybox-catalog branch + let mut lc = data.layout.start_common(); + self.update_limits_text(&state, &mut lc.common()); + let _ = lc.finish(); } } diff --git a/wgui/src/components/button.rs b/wgui/src/components/button.rs index cb955c7f..e498d736 100644 --- a/wgui/src/components/button.rs +++ b/wgui/src/components/button.rs @@ -123,15 +123,21 @@ impl ComponentTrait for ComponentButton { fn refresh(&self, data: &mut RefreshData) { let mut state = self.state.borrow_mut(); + // FIXME: refactor this after merging feat-skybox-catalog branch + let mut lc = data.layout.start_common(); + if state.active_tooltip.is_some() { - if let Some(node_id) = data.common.state.nodes.get(self.base.get_id()) { - if !widget::is_node_visible(&data.common.state.tree, *node_id) { + let common = lc.common(); + if let Some(node_id) = common.state.nodes.get(self.base.get_id()) { + if !widget::is_node_visible(&common.state.tree, *node_id) { state.active_tooltip = None; // destroy the tooltip, this button is now hidden } } else { debug_assert!(false); } } + + let _ = lc.finish(); } } @@ -139,6 +145,10 @@ fn get_color2(color: &drawing::Color, gradient_intensity: f32) -> drawing::Color color.lerp(&Color::new(0.0, 0.0, 0.0, color.a), gradient_intensity) } +fn get_hover_color(color: &drawing::Color) -> drawing::Color { + Color::new(color.r + 0.25, color.g + 0.25, color.g + 0.25, color.a + 0.15) +} + impl ComponentButton { pub fn get_label(&self) -> WidgetID { self.data.id_label @@ -165,7 +175,7 @@ impl ComponentButton { let mut state = self.state.borrow_mut(); state.colors.color = color; - + state.colors.hover_color = get_hover_color(&color); rect.params.color = color; rect.params.color2 = get_color2(&color, gradient_intensity); } @@ -435,9 +445,7 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul .border_color .unwrap_or_else(|| Color::new(color.r, color.g, color.b, color.a + 0.25)); - let hover_color = params - .hover_color - .unwrap_or_else(|| Color::new(color.r + 0.25, color.g + 0.25, color.g + 0.25, color.a + 0.15)); + let hover_color = params.hover_color.unwrap_or_else(|| get_hover_color(&color)); let hover_border_color = params .hover_border_color diff --git a/wgui/src/components/color_selector.rs b/wgui/src/components/color_selector.rs new file mode 100644 index 00000000..bbda2afe --- /dev/null +++ b/wgui/src/components/color_selector.rs @@ -0,0 +1,299 @@ +use crate::{ + assets::AssetPath, + components::{ + Component, ComponentBase, ComponentTrait, RefreshData, + button::{self, ComponentButton}, + slider::{ComponentSlider, SliderValueChangedCallback}, + }, + drawing::{self}, + event::CallbackDataCommon, + globals::WguiGlobals, + i18n::Translation, + layout::{Layout, WidgetID, WidgetPair}, + parser::{self, Fetchable, ParseDocumentParams, ParserState}, + widget::{ConstructEssentials, rectangle::WidgetRectangle, util::WLength}, + windowing::window::{WguiWindow, WguiWindowParams, WguiWindowParamsExtra}, +}; +use glam::Vec2; +use std::{ + cell::RefCell, + rc::{Rc, Weak}, +}; +use taffy::prelude::length; + +pub struct Params { + pub color: drawing::Color, + pub style: taffy::Style, +} + +impl Default for Params { + fn default() -> Self { + Self { + color: drawing::Color::new(1.0, 1.0, 1.0, 1.0), + style: Default::default(), + } + } +} + +struct WantsOpen { + position: Vec2, +} + +#[allow(dead_code)] +struct PopupState { + slider_r: Rc, + slider_g: Rc, + slider_b: Rc, + id_rect_color: WidgetID, +} + +struct State { + color: drawing::Color, + self_ref: Weak, + wants_open: Option, + on_changed: Option, + popup_state: Option, +} + +struct Data { + button: Rc, +} + +pub struct ColorSelectorChangedEvent { + pub color: drawing::Color, +} + +pub type ColorSelectorChangedCallback = Box; + +pub struct ComponentColorSelector { + base: ComponentBase, + data: Rc, + state: Rc>, + window: WguiWindow, +} + +impl ComponentTrait for ComponentColorSelector { + 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(); + + if let Some(wants_open) = state.wants_open.take() { + if let Err(e) = self.open(data.layout, &mut state, wants_open.position) { + log::error!("{:?}", e); + debug_assert!(false); + } + } + + // FIXME: refactor this after merging feat-skybox-catalog branch + let mut lc = data.layout.start_common(); + let mut common = lc.common(); + + self.data.button.set_text( + &mut common, + Translation::from_raw_text_string(format!("{}", state.color.to_hex_rgb())), + ); + + self.data.button.set_color(&mut common, state.color); + + let _ = lc.finish(); + } +} + +enum ColorIndex { + Red, + Green, + Blue, +} + +fn set_color_internal(state: &mut State, common: &mut CallbackDataCommon, color: drawing::Color) { + if state.color == color { + return; + } + + if let Some(on_changed) = &state.on_changed { + on_changed(common, ColorSelectorChangedEvent { color }) + } + + state.color = color; + common.alterables.refresh_component_once(&state.self_ref); + common.alterables.mark_redraw(); +} + +impl ComponentColorSelector { + pub fn on_changed(&self, func: ColorSelectorChangedCallback) { + self.state.borrow_mut().on_changed = Some(func); + } + + pub fn set_color(&self, common: &mut CallbackDataCommon, color: drawing::Color) { + let mut state = self.state.borrow_mut(); + set_color_internal(&mut state, common, color); + } + + pub fn get_color(&self) -> drawing::Color { + self.state.borrow().color + } + + fn open(&self, layout: &mut Layout, state: &mut State, position: Vec2) -> anyhow::Result<()> { + self.window.open(&mut WguiWindowParams { + position, + layout, + extra: WguiWindowParamsExtra { + close_if_clicked_outside: true, + // TODO: translation text in wgui too? + title: Some(Translation::from_raw_text("Select color")), + ..Default::default() + }, + })?; + + let id_content = self.window.get_content().id; + + let parser_state = parser::parse_from_assets( + &mut ParseDocumentParams { + globals: layout.state.globals.clone(), + path: AssetPath::WguiInternal("wgui/color_selector.xml"), + extra: Default::default(), + }, + layout, + id_content, + )?; + + let slider_r = parser_state.fetch_component_as::("slider_r")?; + let slider_g = parser_state.fetch_component_as::("slider_g")?; + let slider_b = parser_state.fetch_component_as::("slider_b")?; + + { + let mut lc = layout.start_common(); + let common = &mut lc.common(); + + slider_r.set_value(common, state.color.r * 255.0); + slider_g.set_value(common, state.color.g * 255.0); + slider_b.set_value(common, state.color.b * 255.0); + } + + slider_r.on_value_changed(self.gen_slider_callback(ColorIndex::Red)); + slider_g.on_value_changed(self.gen_slider_callback(ColorIndex::Green)); + slider_b.on_value_changed(self.gen_slider_callback(ColorIndex::Blue)); + + let id_rect_color = parser_state.get_widget_id("rect_color")?; + + state.popup_state = Some(PopupState { + slider_r, + slider_g, + slider_b, + id_rect_color, + }); + Ok(()) + } + + fn gen_slider_callback(&self, idx: ColorIndex) -> SliderValueChangedCallback { + let state = Rc::downgrade(&self.state); + Box::new(move |common, evt| { + let Some(state) = state.upgrade() else { + return; + }; + + let mut state = state.borrow_mut(); + let Some(popup_state) = &state.popup_state else { + return; + }; + + let norm = evt.value / 255.0; + + let mut new_color = state.color; + match idx { + ColorIndex::Red => new_color.r = norm, + ColorIndex::Green => new_color.g = norm, + ColorIndex::Blue => new_color.b = norm, + } + + if let Some(mut rect) = common + .state + .widgets + .get_as::(popup_state.id_rect_color) + { + rect.set_color(common, new_color); + } + set_color_internal(&mut state, common, new_color); + }) + } +} + +const DEFAULT_WIDTH: f32 = 96.0; +const DEFAULT_HEIGHT: f32 = 32.0; + +pub fn construct( + ess: &mut ConstructEssentials, + params: Params, +) -> anyhow::Result<(WidgetPair, Rc)> { + let mut style = params.style; + + if style.size.width.is_auto() { + style.size.width = length(DEFAULT_WIDTH); + } + + if style.size.height.is_auto() { + style.size.height = length(DEFAULT_HEIGHT); + } + + style.min_size = style.size; + style.max_size = style.size; + + let (widget_button, button) = button::construct( + ess, + button::Params { + color: Some(params.color), + round: WLength::Percent(1.0), + border: 2.0, + border_color: Some(drawing::Color::new(0.0, 0.0, 0.0, 1.0)), + style, + ..Default::default() + }, + )?; + + let data = Rc::new(Data { button: button.clone() }); + + let state = Rc::new(RefCell::new(State { + color: params.color, + self_ref: Default::default(), + wants_open: None, + popup_state: None, + on_changed: None, + })); + + let base = ComponentBase { + id: widget_button.id, + lhandles: Default::default(), + }; + + let color_selector = Rc::new(ComponentColorSelector { + base, + data, + state: state.clone(), + window: WguiWindow::default(), + }); + + let self_ref = Rc::downgrade(&color_selector); + state.borrow_mut().self_ref = self_ref.clone(); + + button.on_click(Rc::new({ + let color_selector = color_selector.clone(); + move |common, evt| { + let mut state = color_selector.state.borrow_mut(); + state.wants_open = Some(WantsOpen { + position: evt.mouse_pos_absolute.unwrap_or_default(), + }); + common.alterables.refresh_component_once(&self_ref); + Ok(()) + } + })); + + ess.layout.defer_component_refresh(Component(color_selector.clone())); + Ok((widget_button, color_selector)) +} diff --git a/wgui/src/components/editbox.rs b/wgui/src/components/editbox.rs index 7dff8cb5..0b9032bf 100644 --- a/wgui/src/components/editbox.rs +++ b/wgui/src/components/editbox.rs @@ -147,9 +147,15 @@ impl ComponentTrait for ComponentEditBox { } fn refresh(&self, data: &mut RefreshData) { + // FIXME: refactor this after merging feat-skybox-catalog branch + let mut lc = data.layout.start_common(); + let mut common = lc.common(); + let mut state = self.state.borrow_mut(); - let res = refresh_all(data.common, &self.data, &mut state); + let res = refresh_all(&mut common, &self.data, &mut state); debug_assert!(res.is_some()); + + let _ = lc.finish(); } fn on_focus_change(&self, data: &mut FocusChangeData) { diff --git a/wgui/src/components/mod.rs b/wgui/src/components/mod.rs index b5916f18..d91e7d71 100644 --- a/wgui/src/components/mod.rs +++ b/wgui/src/components/mod.rs @@ -1,6 +1,7 @@ use std::hash::Hash; use std::{hash::Hasher, rc::Rc}; +use crate::layout::Layout; use crate::{ any::AnyTrait, event::{CallbackDataCommon, EventListenerID}, @@ -10,6 +11,7 @@ use crate::{ pub mod bar_graph; pub mod button; pub mod checkbox; +pub mod color_selector; pub mod editbox; pub mod radio_group; pub mod slider; @@ -17,7 +19,7 @@ pub mod tabs; pub mod tooltip; pub struct RefreshData<'a> { - pub common: &'a mut CallbackDataCommon<'a>, + pub layout: &'a mut Layout, } pub struct FocusChangeData<'a> { diff --git a/wgui/src/components/slider.rs b/wgui/src/components/slider.rs index af94d3ec..36ae9421 100644 --- a/wgui/src/components/slider.rs +++ b/wgui/src/components/slider.rs @@ -101,8 +101,7 @@ pub struct SliderValueChangedEvent { pub value: f32, } -pub type SliderValueChangedCallback = - Box anyhow::Result<()>>; +pub type SliderValueChangedCallback = Box; pub struct ComponentSlider { base: ComponentBase, @@ -111,10 +110,14 @@ pub struct ComponentSlider { } impl ComponentTrait for ComponentSlider { - fn refresh(&self, init_data: &mut RefreshData) { + fn refresh(&self, data: &mut RefreshData) { + // FIXME: refactor this after merging feat-skybox-catalog branch + let mut lc = data.layout.start_common(); + let mut common = lc.common(); let mut state = self.state.borrow_mut(); let value = state.values.value; - state.set_value(init_data.common, &self.data, value); + state.set_value(&mut common, &self.data, value); + let _ = lc.finish(); } fn base(&self) -> &ComponentBase { @@ -250,15 +253,13 @@ impl State { Self::update_text(common, &mut label, self.values.value); } - if changed - && let Some(on_value_changed) = &self.on_value_changed - && let Err(e) = on_value_changed( + if changed && let Some(on_value_changed) = &self.on_value_changed { + on_value_changed( common, SliderValueChangedEvent { value: self.values.value, }, - ) { - log::error!("{e:?}"); // FIXME: proper error handling + ) } } } diff --git a/wgui/src/drawing.rs b/wgui/src/drawing.rs index 2f1aca81..210c0a62 100644 --- a/wgui/src/drawing.rs +++ b/wgui/src/drawing.rs @@ -102,7 +102,7 @@ impl Boundary { } } -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq)] pub struct Color { pub r: f32, pub g: f32, @@ -179,6 +179,14 @@ impl Color { format!("#{r:02X}{g:02X}{b:02X}{a:02X}") } + #[must_use] + pub fn to_hex_rgb(&self) -> String { + let r = (self.r.clamp(0.0, 1.0) * 255.0).round() as u8; + let g = (self.g.clamp(0.0, 1.0) * 255.0).round() as u8; + let b = (self.b.clamp(0.0, 1.0) * 255.0).round() as u8; + format!("#{r:02X}{g:02X}{b:02X}") + } + #[must_use] pub const fn as_arr(&self) -> [f32; 4] { [self.r, self.b, self.g, self.a] diff --git a/wgui/src/layout.rs b/wgui/src/layout.rs index 2569f8b7..b3a15aed 100644 --- a/wgui/src/layout.rs +++ b/wgui/src/layout.rs @@ -339,15 +339,14 @@ impl Layout { } fn process_pending_components(&mut self, alterables: &mut EventAlterables) { - for comp in &self.components_to_refresh_once { + for comp in std::mem::take(&mut self.components_to_refresh_once) { let mut common = CallbackDataCommon { state: &self.state, alterables, }; - comp.0.refresh(&mut RefreshData { common: &mut common }); + comp.0.refresh(&mut RefreshData { layout: self }); } - self.components_to_refresh_once.clear(); } fn process_pending_widget_ticks(&mut self, alterables: &mut EventAlterables) { diff --git a/wgui/src/parser/component_color_selector.rs b/wgui/src/parser/component_color_selector.rs new file mode 100644 index 00000000..63fa2110 --- /dev/null +++ b/wgui/src/parser/component_color_selector.rs @@ -0,0 +1,25 @@ +use crate::{ + components::{Component, color_selector}, + drawing, + layout::WidgetID, + parser::{AttribPair, ParserContext, process_component, style::parse_style}, +}; + +pub fn parse_component_color_selector( + ctx: &mut ParserContext, + parent_id: WidgetID, + attribs: &[AttribPair], + tag_name: &str, +) -> anyhow::Result { + let style = parse_style(ctx, attribs, tag_name); + let color = drawing::Color::new(1.0, 1.0, 1.0, 1.0); + + let (widget, component) = color_selector::construct( + &mut ctx.get_construct_essentials(parent_id), + color_selector::Params { color, style }, + )?; + + process_component(ctx, Component(component), widget.id, attribs); + + Ok(widget.id) +} diff --git a/wgui/src/parser/mod.rs b/wgui/src/parser/mod.rs index a715b4df..e8bac585 100644 --- a/wgui/src/parser/mod.rs +++ b/wgui/src/parser/mod.rs @@ -1,6 +1,7 @@ mod component_bar_graph; mod component_button; mod component_checkbox; +mod component_color_selector; mod component_editbox; mod component_radio_group; mod component_slider; @@ -25,6 +26,7 @@ use crate::{ component_bar_graph::parse_component_bar_graph, component_button::parse_component_button, component_checkbox::{CheckboxKind, parse_component_checkbox}, + component_color_selector::parse_component_color_selector, component_editbox::parse_component_editbox, component_radio_group::parse_component_radio_group, component_slider::parse_component_slider, @@ -1063,6 +1065,7 @@ fn parse_child<'a>( "Slider" => { new_widget_id = Some(parse_component_slider(ctx, parent_id, &attribs, tag_name)?); } + "ColorSelector" => new_widget_id = Some(parse_component_color_selector(ctx, parent_id, &attribs, tag_name)?), "CheckBox" => { new_widget_id = Some(parse_component_checkbox( ctx, diff --git a/wgui/src/windowing/context_menu.rs b/wgui/src/windowing/context_menu.rs index 6852604e..e9139015 100644 --- a/wgui/src/windowing/context_menu.rs +++ b/wgui/src/windowing/context_menu.rs @@ -94,7 +94,6 @@ impl ContextMenu { let globals = layout.state.globals.clone(); self.window.open(&mut WguiWindowParams { - globals: &globals, layout, position: params.position, extra: WguiWindowParamsExtra { diff --git a/wgui/src/windowing/window.rs b/wgui/src/windowing/window.rs index 2ab6fc95..82779273 100644 --- a/wgui/src/windowing/window.rs +++ b/wgui/src/windowing/window.rs @@ -81,7 +81,6 @@ impl Default for WguiWindowParamsExtra { pub struct WguiWindowParams<'a> { pub position: Vec2, - pub globals: &'a WguiGlobals, pub layout: &'a mut Layout, pub extra: WguiWindowParamsExtra, } @@ -100,7 +99,7 @@ impl WguiWindow { self.0.borrow_mut().opened_window = None; } - pub fn open(&mut self, params: &mut WguiWindowParams) -> anyhow::Result<()> { + pub fn open(&self, params: &mut WguiWindowParams) -> anyhow::Result<()> { // close previous one if it's already open self.close(); @@ -216,12 +215,14 @@ impl WguiWindow { }, )?; + let globals = params.layout.state.globals.clone(); + let content_id = if params.extra.with_decorations { let xml_path: AssetPath = AssetPath::WguiInternal("wgui/window_frame.xml"); let state = parser::parse_from_assets( &parser::ParseDocumentParams { - globals: params.globals.clone(), + globals: globals.clone(), path: xml_path, extra: Default::default(), }, @@ -240,7 +241,7 @@ impl WguiWindow { if let Some(title) = ¶ms.extra.title { let mut text_title = state.fetch_widget_as::(¶ms.layout.state, "text_window_title")?; - text_title.set_text_simple(&mut params.globals.get(), title.clone()); + text_title.set_text_simple(&mut globals.get(), title.clone()); } let content = state.fetch_widget(¶ms.layout.state, "content")?;