wgui: basic i18n support, refactoring: use LayoutState, translation framework (LLM-based generator)

This commit is contained in:
Aleksander
2025-08-02 23:31:23 +02:00
parent 4e46c45bcf
commit eaa81450b5
45 changed files with 916 additions and 223 deletions

View File

@@ -1,8 +1,8 @@
use glam::{FloatExt, Vec2};
use crate::{
event::{CallbackDataCommon, EventAlterables, EventRefs},
layout::WidgetID,
event::{CallbackDataCommon, EventAlterables},
layout::{LayoutState, WidgetID},
widget::{WidgetData, WidgetObj},
};
@@ -89,13 +89,13 @@ impl Animation {
}
}
fn call(&self, refs: &EventRefs, alterables: &mut EventAlterables, pos: f32) {
let Some(widget) = refs.widgets.get(self.target_widget).cloned() else {
fn call(&self, state: &LayoutState, alterables: &mut EventAlterables, pos: f32) {
let Some(widget) = state.widgets.get(self.target_widget).cloned() else {
return; // failed
};
let widget_node = *refs.nodes.get(self.target_widget).unwrap();
let layout = refs.tree.layout(widget_node).unwrap(); // should always succeed
let widget_node = *state.nodes.get(self.target_widget).unwrap();
let layout = state.tree.layout(widget_node).unwrap(); // should always succeed
let mut widget = widget.lock();
@@ -109,7 +109,7 @@ impl Animation {
pos,
};
let common = &mut CallbackDataCommon { refs, alterables };
let common = &mut CallbackDataCommon { state, alterables };
(self.callback)(common, data);
}
@@ -121,7 +121,7 @@ pub struct Animations {
}
impl Animations {
pub fn tick(&mut self, refs: &EventRefs, alterables: &mut EventAlterables) {
pub fn tick(&mut self, state: &LayoutState, alterables: &mut EventAlterables) {
for anim in &mut self.running_animations {
let x = 1.0 - (anim.ticks_remaining as f32 / anim.ticks_duration as f32);
let pos = if anim.ticks_remaining > 0 {
@@ -133,7 +133,7 @@ impl Animations {
anim.pos_prev = anim.pos;
anim.pos = pos;
anim.call(refs, alterables, 1.0);
anim.call(state, alterables, 1.0);
if anim.last_tick {
alterables.needs_redraw = true;
@@ -147,10 +147,10 @@ impl Animations {
.retain(|anim| anim.ticks_remaining > 0);
}
pub fn process(&mut self, refs: &EventRefs, alterables: &mut EventAlterables, alpha: f32) {
pub fn process(&mut self, state: &LayoutState, alterables: &mut EventAlterables, alpha: f32) {
for anim in &mut self.running_animations {
let pos = anim.pos_prev.lerp(anim.pos, alpha);
anim.call(refs, alterables, pos);
anim.call(state, alterables, pos);
}
}

View File

@@ -6,6 +6,7 @@ use crate::{
components::{Component, InitData},
drawing::{self, Color},
event::{CallbackDataCommon, EventListenerCollection, EventListenerKind, ListenerHandleVec},
i18n::Translation,
layout::{Layout, WidgetID},
renderer_vk::text::{FontWeight, TextStyle},
widget::{
@@ -15,8 +16,8 @@ use crate::{
},
};
pub struct Params<'a> {
pub text: &'a str,
pub struct Params {
pub text: Translation,
pub color: drawing::Color,
pub border_color: drawing::Color,
pub round: WLength,
@@ -24,10 +25,10 @@ pub struct Params<'a> {
pub text_style: TextStyle,
}
impl Default for Params<'_> {
impl Default for Params {
fn default() -> Self {
Self {
text: "Text",
text: Translation::from_raw_text(""),
color: drawing::Color::new(1.0, 1.0, 1.0, 1.0),
border_color: drawing::Color::new(0.0, 0.0, 0.0, 1.0),
round: WLength::Units(4.0),
@@ -55,12 +56,14 @@ impl Component for Button {
}
impl Button {
pub fn set_text<C>(&self, common: &mut CallbackDataCommon, text: &str) {
pub fn set_text<C>(&self, common: &mut CallbackDataCommon, text: Translation) {
let globals = common.state.globals.clone();
common
.refs
.state
.widgets
.call(self.data.text_id, |label: &mut TextLabel| {
label.set_text(text);
label.set_text(&mut globals.i18n(), text);
});
common.alterables.mark_redraw();
common.alterables.mark_dirty(self.data.text_node);
@@ -118,6 +121,8 @@ pub fn construct<U1, U2>(
style.justify_content = Some(JustifyContent::Center);
style.padding = length(1.0);
let globals = layout.state.globals.clone();
let (rect_id, _) = layout.add_child(
parent,
Rectangle::create(RectangleParams {
@@ -137,18 +142,21 @@ pub fn construct<U1, U2>(
let (text_id, text_node) = layout.add_child(
rect_id,
TextLabel::create(TextParams {
content: String::from(params.text),
style: TextStyle {
weight: Some(FontWeight::Bold),
color: Some(if light_text {
Color::new(1.0, 1.0, 1.0, 1.0)
} else {
Color::new(0.0, 0.0, 0.0, 1.0)
}),
..params.text_style
TextLabel::create(
&mut globals.i18n(),
TextParams {
content: params.text,
style: TextStyle {
weight: Some(FontWeight::Bold),
color: Some(if light_text {
Color::new(1.0, 1.0, 1.0, 1.0)
} else {
Color::new(0.0, 0.0, 0.0, 1.0)
}),
..params.text_style
},
},
})?,
)?,
taffy::Style {
..Default::default()
},

View File

@@ -1,18 +1,11 @@
use taffy::TaffyTree;
use crate::{
any::AnyTrait,
event::EventAlterables,
layout::{WidgetID, WidgetMap},
};
use crate::{any::AnyTrait, event::EventAlterables, layout::LayoutState};
pub mod button;
pub mod slider;
pub struct InitData<'a> {
pub state: &'a LayoutState,
pub alterables: &'a mut EventAlterables,
pub widgets: &'a WidgetMap,
pub tree: &'a TaffyTree<WidgetID>,
}
pub trait Component: AnyTrait {

View File

@@ -1,10 +1,7 @@
use std::{cell::RefCell, rc::Rc};
use glam::{Mat4, Vec2, Vec3};
use taffy::{
TaffyTree,
prelude::{length, percent},
};
use taffy::prelude::{length, percent};
use crate::{
animation::{Animation, AnimationEasing},
@@ -14,7 +11,8 @@ use crate::{
self, CallbackDataCommon, EventAlterables, EventListenerCollection, EventListenerKind,
ListenerHandleVec,
},
layout::{Layout, WidgetID, WidgetMap},
i18n::{I18n, Translation},
layout::{Layout, LayoutState, WidgetID},
renderer_vk::{
text::{FontWeight, HorizontalAlign, TextStyle},
util,
@@ -76,13 +74,7 @@ impl Component for Slider {
fn init(&self, init_data: &mut InitData) {
let mut state = self.state.borrow_mut();
let value = state.values.value;
state.set_value(
&self.data,
init_data.alterables,
init_data.widgets,
init_data.tree,
value,
);
state.set_value(init_data.state, &self.data, init_data.alterables, value);
}
}
@@ -128,44 +120,42 @@ impl SliderState {
let norm = map_mouse_x_to_normalized(
mouse_pos.x - HANDLE_WIDTH / 2.0,
get_width(data.slider_body_node, common.refs.tree) - HANDLE_WIDTH,
get_width(data.slider_body_node, &common.state.tree) - HANDLE_WIDTH,
);
let target_value = self.values.get_from_normalized(norm);
let val = target_value;
self.set_value(
data,
common.alterables,
common.refs.widgets,
common.refs.tree,
val,
);
self.set_value(common.state, data, common.alterables, val);
}
fn update_text(&self, text: &mut TextLabel, value: f32) {
fn update_text(&self, i18n: &mut I18n, text: &mut TextLabel, value: f32) {
// round displayed value, should be sufficient for now
text.set_text(&format!("{}", value.round()));
text.set_text(
i18n,
Translation::from_raw_text(&format!("{}", value.round())),
);
}
fn set_value(
&mut self,
state: &LayoutState,
data: &Data,
alterables: &mut EventAlterables,
widgets: &WidgetMap,
tree: &TaffyTree<WidgetID>,
value: f32,
) {
//common.call_on_widget(data.slider_handle_id, |_div: &mut Div| {});
self.values.value = value;
let mut style = tree.style(data.slider_handle_node).unwrap().clone();
conf_handle_style(&self.values, data.slider_body_node, &mut style, tree);
let mut style = state.tree.style(data.slider_handle_node).unwrap().clone();
conf_handle_style(&self.values, data.slider_body_node, &mut style, &state.tree);
alterables.mark_dirty(data.slider_handle_node);
alterables.mark_redraw();
alterables.set_style(data.slider_handle_node, style);
widgets.call(data.slider_text_id, |label: &mut TextLabel| {
self.update_text(label, value);
});
state
.widgets
.call(data.slider_text_id, |label: &mut TextLabel| {
self.update_text(&mut state.globals.i18n(), label, value);
});
}
}
@@ -398,16 +388,22 @@ pub fn construct<U1, U2>(
values: params.values,
};
let globals = layout.state.globals.clone();
let mut i18n = globals.i18n();
let (slider_text_id, _) = layout.add_child(
slider_handle_id,
TextLabel::create(TextParams {
content: String::new(),
style: TextStyle {
weight: Some(FontWeight::Bold),
align: Some(HorizontalAlign::Center),
..Default::default()
TextLabel::create(
&mut i18n,
TextParams {
content: Translation::default(),
style: TextStyle {
weight: Some(FontWeight::Bold),
align: Some(HorizontalAlign::Center),
..Default::default()
},
},
})?,
)?,
Default::default(),
)?;

View File

@@ -111,7 +111,7 @@ fn draw_widget(
widget: &BoxWidget,
parent_transform: &glam::Mat4,
) {
let Ok(l) = layout.tree.layout(node_id) else {
let Ok(l) = layout.state.tree.layout(node_id) else {
debug_assert!(false);
return;
};
@@ -154,18 +154,18 @@ fn draw_children(
parent_node_id: taffy::NodeId,
model: &glam::Mat4,
) {
for node_id in layout.tree.child_ids(parent_node_id) {
let Some(widget_id) = layout.tree.get_node_context(node_id).cloned() else {
for node_id in layout.state.tree.child_ids(parent_node_id) {
let Some(widget_id) = layout.state.tree.get_node_context(node_id).cloned() else {
debug_assert!(false);
continue;
};
let Ok(style) = layout.tree.style(node_id) else {
let Ok(style) = layout.state.tree.style(node_id) else {
debug_assert!(false);
continue;
};
let Some(widget) = layout.widget_map.get(widget_id) else {
let Some(widget) = layout.state.widgets.get(widget_id) else {
debug_assert!(false);
continue;
};
@@ -181,11 +181,11 @@ pub fn draw(layout: &Layout) -> anyhow::Result<Vec<RenderPrimitive>> {
let mut transform_stack = TransformStack::new();
let model = glam::Mat4::IDENTITY;
let Some(root_widget) = layout.widget_map.get(layout.root_widget) else {
let Some(root_widget) = layout.state.widgets.get(layout.root_widget) else {
panic!();
};
let Ok(style) = layout.tree.style(layout.root_node) else {
let Ok(style) = layout.state.tree.style(layout.root_node) else {
panic!();
};

View File

@@ -1,11 +1,15 @@
use std::{cell::RefCell, rc::Rc};
use std::{
cell::{RefCell, RefMut},
rc::Rc,
};
use glam::Vec2;
use slotmap::SecondaryMap;
use crate::{
animation::{self, Animation},
layout::{WidgetID, WidgetMap, WidgetNodeMap},
i18n::I18n,
layout::{LayoutState, WidgetID},
transform_stack::{Transform, TransformStack},
widget::{WidgetData, WidgetObj},
};
@@ -87,12 +91,6 @@ impl Event {
}
}
pub struct EventRefs<'a> {
pub widgets: &'a WidgetMap,
pub nodes: &'a WidgetNodeMap,
pub tree: &'a taffy::tree::TaffyTree<WidgetID>,
}
#[derive(Default)]
pub struct EventAlterables {
pub dirty_nodes: Vec<taffy::NodeId>,
@@ -126,10 +124,16 @@ impl EventAlterables {
}
pub struct CallbackDataCommon<'a> {
pub refs: &'a EventRefs<'a>,
pub state: &'a LayoutState,
pub alterables: &'a mut EventAlterables,
}
impl<'a> CallbackDataCommon<'a> {
pub fn i18n(&self) -> RefMut<I18n> {
self.state.globals.i18n()
}
}
pub struct CallbackData<'a> {
pub obj: &'a mut dyn WidgetObj,
pub widget_data: &'a mut WidgetData,

34
wgui/src/globals.rs Normal file
View File

@@ -0,0 +1,34 @@
use std::{
cell::{RefCell, RefMut},
rc::Rc,
};
use crate::{assets::AssetProvider, i18n::I18n};
pub struct Globals {
pub assets: Box<dyn AssetProvider>,
pub i18n: I18n,
}
#[derive(Clone)]
pub struct WguiGlobals(Rc<RefCell<Globals>>);
impl WguiGlobals {
pub fn new(mut assets: Box<dyn AssetProvider>) -> anyhow::Result<Self> {
let i18n = I18n::new(&mut assets)?;
Ok(Self(Rc::new(RefCell::new(Globals { assets, i18n }))))
}
pub fn get(&self) -> RefMut<Globals> {
self.0.borrow_mut()
}
pub fn i18n(&self) -> RefMut<I18n> {
RefMut::map(self.0.borrow_mut(), |x| &mut x.i18n)
}
pub fn assets(&self) -> RefMut<Box<dyn AssetProvider>> {
RefMut::map(self.0.borrow_mut(), |x| &mut x.assets)
}
}

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

@@ -0,0 +1,110 @@
use std::rc::Rc;
use crate::assets::AssetProvider;
// a string which optionally has translation key in it
// it will hopefully support dynamic language changing soon
// for now it's just a simple string container
#[derive(Default)]
pub struct Translation {
text: Rc<str>,
translated: bool, // if true, `text` is a translation key
}
impl PartialEq for Translation {
fn eq(&self, other: &Self) -> bool {
*self.text == *other.text && self.translated == other.translated
}
}
impl Translation {
pub fn generate(&self, i18n: &mut I18n) -> Rc<str> {
if self.translated {
i18n.translate(&self.text)
} else {
self.text.clone()
}
}
pub fn from_raw_text(text: &str) -> Self {
Self {
text: Rc::from(text),
translated: false,
}
}
pub fn from_translation_key(translated: &str) -> Self {
Self {
text: Rc::from(translated),
translated: true,
}
}
}
pub struct I18n {
json_root_translated: serde_json::Value, // any language
// TODO
json_root_fallback: serde_json::Value, // english
}
fn find_translation<'a>(translation: &str, mut val: &'a serde_json::Value) -> Option<&'a str> {
for part in translation.split('.') {
if let Some(sub) = val.get(part) {
val = sub;
}
}
val.as_str()
}
fn guess_lang() -> String {
if let Ok(lang) = std::env::var("LANG") {
if let Some((first, _)) = lang.split_once('_') {
String::from(first)
} else {
lang
}
} else {
log::warn!("LANG is not set, defaulting to \"en\".");
String::from("en")
}
}
impl I18n {
pub fn new(provider: &mut Box<dyn AssetProvider>) -> anyhow::Result<Self> {
let mut lang = guess_lang();
log::info!("Guessed system language: {lang}");
match lang.as_str() {
"en" | "pl" | "it" | "ja" | "es" => {}
_ => {
log::warn!(
"Unsupported language \"{}\", defaulting to \"en\".",
lang.as_str()
);
lang = String::from("en");
}
}
let data_english = provider.load_from_path(&format!("lang/{lang}.json"))?;
let data_translated = provider.load_from_path(&format!("lang/{lang}.json"))?;
let json_root_fallback = serde_json::from_str(str::from_utf8(&data_english)?)?;
let json_root_translated = serde_json::from_str(str::from_utf8(&data_translated)?)?;
Ok(Self {
json_root_fallback,
json_root_translated,
})
}
pub fn translate(&mut self, translation_key: &str) -> Rc<str> {
if let Some(translated) = find_translation(translation_key, &self.json_root_translated) {
Rc::from(translated)
} else {
Rc::from(translation_key) // show translation key as a fallback
}
}
}

View File

@@ -2,9 +2,9 @@ use std::{collections::VecDeque, rc::Rc, sync::Arc};
use crate::{
animation::Animations,
assets::AssetProvider,
components::{Component, InitData},
event::{self, EventAlterables, EventListenerCollection, EventRefs},
event::{self, EventAlterables, EventListenerCollection},
globals::WguiGlobals,
transform_stack::Transform,
widget::{self, EventParams, WidgetObj, WidgetState, div::Div},
};
@@ -60,15 +60,18 @@ impl WidgetMap {
}
}
pub struct LayoutState {
pub globals: WguiGlobals,
pub widgets: WidgetMap,
pub nodes: WidgetNodeMap,
pub tree: taffy::tree::TaffyTree<WidgetID>,
}
pub struct Layout {
pub tree: TaffyTree<WidgetID>,
pub state: LayoutState,
pub assets: Box<dyn AssetProvider>,
pub components_to_init: VecDeque<Rc<dyn Component>>,
pub widget_map: WidgetMap,
pub widget_node_map: WidgetNodeMap,
pub root_widget: WidgetID,
pub root_node: taffy::NodeId,
@@ -83,21 +86,21 @@ pub struct Layout {
fn add_child_internal(
tree: &mut taffy::TaffyTree<WidgetID>,
widget_map: &mut WidgetMap,
widget_node_map: &mut WidgetNodeMap,
widgets: &mut WidgetMap,
nodes: &mut WidgetNodeMap,
parent_node: Option<taffy::NodeId>,
widget: WidgetState,
style: taffy::Style,
) -> anyhow::Result<(WidgetID, taffy::NodeId)> {
#[allow(clippy::arc_with_non_send_sync)]
let child_id = widget_map.insert(Arc::new(Mutex::new(widget)));
let child_id = widgets.insert(Arc::new(Mutex::new(widget)));
let child_node = tree.new_leaf_with_context(style, child_id)?;
if let Some(parent_node) = parent_node {
tree.add_child(parent_node, child_node)?;
}
widget_node_map.insert(child_id, child_node);
nodes.insert(child_id, child_node);
Ok((child_id, child_node))
}
@@ -109,14 +112,14 @@ impl Layout {
widget: WidgetState,
style: taffy::Style,
) -> anyhow::Result<(WidgetID, taffy::NodeId)> {
let parent_node = *self.widget_node_map.get(parent_widget_id).unwrap();
let parent_node = *self.state.nodes.get(parent_widget_id).unwrap();
self.needs_redraw = true;
add_child_internal(
&mut self.tree,
&mut self.widget_map,
&mut self.widget_node_map,
&mut self.state.tree,
&mut self.state.widgets,
&mut self.state.nodes,
Some(parent_node),
widget,
style,
@@ -128,9 +131,8 @@ impl Layout {
while let Some(c) = self.components_to_init.pop_front() {
c.init(&mut InitData {
widgets: &self.widget_map,
state: &self.state,
alterables: &mut alterables,
tree: &self.tree,
});
}
@@ -151,7 +153,7 @@ impl Layout {
alterables: &mut EventAlterables,
user_data: &mut (&mut U1, &mut U2),
) -> anyhow::Result<()> {
for child_id in self.tree.child_ids(parent_node_id) {
for child_id in self.state.tree.child_ids(parent_node_id) {
self.push_event_widget(listeners, child_id, event, alterables, user_data)?;
}
@@ -166,14 +168,14 @@ impl Layout {
alterables: &mut EventAlterables,
user_data: &mut (&mut U1, &mut U2),
) -> anyhow::Result<()> {
let l = self.tree.layout(node_id)?;
let Some(widget_id) = self.tree.get_node_context(node_id).cloned() else {
let l = self.state.tree.layout(node_id)?;
let Some(widget_id) = self.state.tree.get_node_context(node_id).cloned() else {
anyhow::bail!("invalid widget ID");
};
let style = self.tree.style(node_id)?;
let style = self.state.tree.style(node_id)?;
let Some(widget) = self.widget_map.get(widget_id) else {
let Some(widget) = self.state.widgets.get(widget_id) else {
debug_assert!(false);
anyhow::bail!("invalid widget");
};
@@ -191,11 +193,7 @@ impl Layout {
let mut iter_children = true;
let mut params = EventParams {
refs: &EventRefs {
tree: &self.tree,
widgets: &self.widget_map,
nodes: &self.widget_node_map,
},
state: &self.state,
layout: l,
alterables,
node_id,
@@ -278,15 +276,18 @@ impl Layout {
Ok(())
}
pub fn new(assets: Box<dyn AssetProvider>) -> anyhow::Result<Self> {
let mut tree = TaffyTree::new();
let mut widget_node_map = WidgetNodeMap::default();
let mut widget_map = WidgetMap::new();
pub fn new(globals: WguiGlobals) -> anyhow::Result<Self> {
let mut state = LayoutState {
tree: TaffyTree::new(),
widgets: WidgetMap::new(),
nodes: WidgetNodeMap::default(),
globals,
};
let (root_widget, root_node) = add_child_internal(
&mut tree,
&mut widget_map,
&mut widget_node_map,
&mut state.tree,
&mut state.widgets,
&mut state.nodes,
None, // no parent
Div::create()?,
taffy::Style {
@@ -296,17 +297,14 @@ impl Layout {
)?;
Ok(Self {
tree,
state,
prev_size: Vec2::default(),
content_size: Vec2::default(),
root_node,
root_widget,
widget_node_map,
widget_map,
needs_redraw: true,
haptics_triggered: false,
animations: Animations::default(),
assets,
components_to_init: VecDeque::new(),
})
}
@@ -314,23 +312,17 @@ impl Layout {
pub fn update(&mut self, size: Vec2, timestep_alpha: f32) -> anyhow::Result<()> {
let mut alterables = EventAlterables::default();
let refs = EventRefs {
tree: &self.tree,
widgets: &self.widget_map,
nodes: &self.widget_node_map,
};
self
.animations
.process(&refs, &mut alterables, timestep_alpha);
.process(&self.state, &mut alterables, timestep_alpha);
self.process_alterables(alterables)?;
if self.tree.dirty(self.root_node)? || self.prev_size != size {
if self.state.tree.dirty(self.root_node)? || self.prev_size != size {
self.needs_redraw = true;
log::debug!("re-computing layout, size {}x{}", size.x, size.y);
self.prev_size = size;
self.tree.compute_layout_with_measure(
self.state.tree.compute_layout_with_measure(
self.root_node,
taffy::Size {
width: taffy::AvailableSpace::Definite(size.x),
@@ -348,7 +340,7 @@ impl Layout {
match node_context {
None => taffy::Size::ZERO,
Some(h) => {
if let Some(w) = self.widget_map.get(*h) {
if let Some(w) = self.state.widgets.get(*h) {
w.lock().obj.measure(known_dimensions, available_space)
} else {
taffy::Size::ZERO
@@ -357,7 +349,7 @@ impl Layout {
}
},
)?;
let root_size = self.tree.layout(self.root_node).unwrap().size;
let root_size = self.state.tree.layout(self.root_node).unwrap().size;
log::debug!(
"content size {:.0}x{:.0} → {:.0}x{:.0}",
self.content_size.x,
@@ -373,13 +365,7 @@ impl Layout {
pub fn tick(&mut self) -> anyhow::Result<()> {
let mut alterables = EventAlterables::default();
let refs = EventRefs {
tree: &self.tree,
widgets: &self.widget_map,
nodes: &self.widget_node_map,
};
self.animations.tick(&refs, &mut alterables);
self.animations.tick(&self.state, &mut alterables);
self.process_alterables(alterables)?;
self.process_pending_components()?;
@@ -388,7 +374,7 @@ impl Layout {
fn process_alterables(&mut self, alterables: EventAlterables) -> anyhow::Result<()> {
for node in alterables.dirty_nodes {
self.tree.mark_dirty(node)?;
self.state.tree.mark_dirty(node)?;
}
if alterables.needs_redraw {
@@ -407,7 +393,7 @@ impl Layout {
}
for request in alterables.style_set_requests {
if let Err(e) = self.tree.set_style(request.0, request.1) {
if let Err(e) = self.state.tree.set_style(request.0, request.1) {
log::error!(
"failed to set style for taffy widget ID {:?}: {:?}",
request.0,

View File

@@ -5,6 +5,8 @@ pub mod components;
pub mod drawing;
pub mod event;
pub mod gfx;
pub mod globals;
pub mod i18n;
pub mod layout;
pub mod parser;
pub mod renderer_vk;

View File

@@ -1,6 +1,7 @@
use crate::{
components::button,
drawing::Color,
i18n::Translation,
layout::WidgetID,
parser::{
ParserContext, ParserFile, iter_attribs,
@@ -18,8 +19,7 @@ pub fn parse_component_button<'a, U1, U2>(
let mut color = Color::new(1.0, 1.0, 1.0, 1.0);
let mut border_color: Option<Color> = None;
let mut round = WLength::Units(4.0);
let mut text = String::default();
let mut translation = Translation::default();
let attribs: Vec<_> = iter_attribs(file, ctx, &node, false).collect();
let text_style = parse_text_style(&attribs);
@@ -28,7 +28,10 @@ pub fn parse_component_button<'a, U1, U2>(
for (key, value) in attribs {
match key.as_ref() {
"text" => {
text = String::from(value.as_ref());
translation = Translation::from_raw_text(&value);
}
"translation" => {
translation = Translation::from_translation_key(&value);
}
"round" => {
parse_round(&value, &mut round);
@@ -59,7 +62,7 @@ pub fn parse_component_button<'a, U1, U2>(
button::Params {
color,
border_color: border_color.unwrap(),
text: &text,
text: translation,
style,
text_style,
round,

View File

@@ -11,6 +11,7 @@ use crate::{
components::Component,
drawing::{self},
event::EventListenerCollection,
globals::WguiGlobals,
layout::{Layout, WidgetID},
parser::{
component_button::parse_component_button, component_slider::parse_component_slider,
@@ -631,11 +632,11 @@ pub fn parse_from_assets<U1, U2>(
}
pub fn new_layout_from_assets<U1, U2>(
assets: Box<dyn AssetProvider>,
globals: WguiGlobals,
listeners: &mut EventListenerCollection<U1, U2>,
path: &str,
) -> anyhow::Result<(Layout, ParserState)> {
let mut layout = Layout::new(assets)?;
let mut layout = Layout::new(globals)?;
let widget = layout.root_widget;
let state = parse_from_assets(&mut layout, listeners, widget, path)?;
Ok((layout, state))
@@ -650,7 +651,7 @@ fn get_doc_from_path<U1, U2>(
ctx: &mut ParserContext<U1, U2>,
path: &Path,
) -> anyhow::Result<(ParserFile, roxmltree::NodeId)> {
let xml = assets_path_to_xml(&mut ctx.layout.assets, path)?;
let xml = assets_path_to_xml(&mut ctx.layout.state.globals.assets(), path)?;
let document = Rc::new(XmlDocument::new(xml, |xml| {
let opt = roxmltree::ParsingOptions {
allow_dtd: true,

View File

@@ -1,4 +1,5 @@
use crate::{
i18n::Translation,
layout::WidgetID,
parser::{
ParserContext, ParserFile, iter_attribs, parse_children, parse_universal,
@@ -22,15 +23,20 @@ pub fn parse_widget_label<'a, U1, U2>(
for (key, value) in attribs {
match &*key {
"text" => {
params.content = String::from(value.as_ref());
params.content = Translation::from_raw_text(&value);
}
"translation" => params.content = Translation::from_translation_key(&value),
_ => {}
}
}
let (new_id, _) = ctx
.layout
.add_child(parent_id, TextLabel::create(params)?, style)?;
let globals = ctx.layout.state.globals.clone();
let mut i18n = globals.i18n();
let (new_id, _) =
ctx
.layout
.add_child(parent_id, TextLabel::create(&mut i18n, params)?, style)?;
parse_universal(file, ctx, node, new_id)?;
parse_children(file, ctx, node, new_id)?;

View File

@@ -23,13 +23,14 @@ pub fn parse_widget_sprite<'a, U1, U2>(
for (key, value) in attribs {
match key.as_ref() {
"src" => {
glyph = match CustomGlyphContent::from_assets(&mut ctx.layout.assets, &value) {
Ok(glyph) => Some(glyph),
Err(e) => {
log::warn!("failed to load {}: {}", value, e);
None
glyph =
match CustomGlyphContent::from_assets(&mut ctx.layout.state.globals.assets(), &value) {
Ok(glyph) => Some(glyph),
Err(e) => {
log::warn!("failed to load {value}: {e}");
None
}
}
}
}
"src_ext" => {
if std::fs::exists(value.as_ref()).unwrap_or(false) {

View File

@@ -7,9 +7,9 @@ use crate::{
drawing,
event::{
self, CallbackData, CallbackDataCommon, CallbackMetadata, Event, EventAlterables,
EventListenerKind, EventListenerVec, EventRefs, MouseWheelEvent,
EventListenerKind, EventListenerVec, MouseWheelEvent,
},
layout::{Layout, WidgetID},
layout::{Layout, LayoutState, WidgetID},
transform_stack::TransformStack,
};
@@ -124,7 +124,7 @@ pub trait WidgetObj: AnyTrait {
pub struct EventParams<'a> {
pub node_id: taffy::NodeId,
pub style: &'a taffy::Style,
pub refs: &'a EventRefs<'a>,
pub state: &'a LayoutState,
pub alterables: &'a mut EventAlterables,
pub layout: &'a taffy::Layout,
}
@@ -197,7 +197,7 @@ macro_rules! call_event {
};
let mut common = CallbackDataCommon {
refs: $params.refs,
state: $params.state,
alterables: $params.alterables,
};

View File

@@ -5,6 +5,7 @@ use taffy::AvailableSpace;
use crate::{
drawing::{self, Boundary},
i18n::{I18n, Translation},
renderer_vk::text::{FONT_SYSTEM, TextStyle},
};
@@ -12,7 +13,7 @@ use super::{WidgetObj, WidgetState};
#[derive(Default)]
pub struct TextParams {
pub content: String,
pub content: Translation,
pub style: TextStyle,
}
@@ -23,7 +24,7 @@ pub struct TextLabel {
}
impl TextLabel {
pub fn create(params: TextParams) -> anyhow::Result<WidgetState> {
pub fn create(i18n: &mut I18n, params: TextParams) -> anyhow::Result<WidgetState> {
let metrics = Metrics::from(&params.style);
let attrs = Attrs::from(&params.style);
let wrap = Wrap::from(&params.style);
@@ -35,7 +36,7 @@ impl TextLabel {
buffer.set_wrap(wrap);
buffer.set_rich_text(
[(params.content.as_str(), attrs)],
[(params.content.generate(i18n).as_ref(), attrs)],
&Attrs::new(),
Shaping::Advanced,
params.style.align.map(|a| a.into()),
@@ -49,28 +50,24 @@ impl TextLabel {
}))
}
pub fn set_text(&mut self, text: &str) {
if self.params.content.as_str() == text {
pub fn set_text(&mut self, i18n: &mut I18n, translation: Translation) {
if self.params.content == translation {
return;
}
self.params.content = String::from(text);
self.params.content = translation;
let attrs = Attrs::from(&self.params.style);
let mut font_system = FONT_SYSTEM.lock();
let mut buffer = self.buffer.borrow_mut();
buffer.set_rich_text(
&mut font_system,
[(self.params.content.as_str(), attrs)],
[(self.params.content.generate(i18n).as_ref(), attrs)],
&Attrs::new(),
Shaping::Advanced,
self.params.style.align.map(|a| a.into()),
);
}
pub fn get_text(&self) -> &str {
&self.params.content
}
}
impl WidgetObj for TextLabel {