@effindomv2/fui-rs 0.1.11 → 0.1.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/Cargo.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "fui-rs"
3
- version = "0.1.11"
3
+ version = "0.1.13"
4
4
  edition = "2021"
5
5
  license = "AGPL-3.0-only OR LicenseRef-EffinDom-Commercial"
6
6
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effindomv2/fui-rs",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "private": false,
5
5
  "license": "AGPL-3.0-only OR LicenseRef-EffinDom-Commercial",
6
6
  "description": "EffinDom v2 Rust frontend framework SDK and host generators",
@@ -167,5 +167,5 @@ export async function generateRustHostEventsFile(
167
167
  lines.push("");
168
168
  });
169
169
  await fs.mkdir(path.dirname(outputPath), { recursive: true });
170
- await fs.writeFile(outputPath, `${lines.join("\n")}\n`, "utf8");
170
+ await fs.writeFile(outputPath, `${lines.join("\n").replace(/\n+$/, "")}\n`, "utf8");
171
171
  }
@@ -140,9 +140,9 @@ function emitWasmCallArgs(args: readonly HostServiceTypeName[]): string {
140
140
  function emitFrameworkNonWasmBody(importName: string, args: readonly HostServiceTypeName[]): string[] {
141
141
  const argNames = args.map((_type, index) => `arg${String(index)}`);
142
142
  if (importName === "fui_now_ms") {
143
- return [" crate::ffi::test::host_now_ms()"];
143
+ return [" crate::ffi::test::host_now_ms()"];
144
144
  }
145
- return [` unsafe { crate::ffi::${importName}(${argNames.join(", ")}) }`];
145
+ return [` unsafe { crate::ffi::${importName}(${argNames.join(", ")}) }`];
146
146
  }
147
147
 
148
148
  function emitDecodeReturn(
@@ -154,7 +154,7 @@ function emitDecodeReturn(
154
154
  return [];
155
155
  }
156
156
  if (!isBufferType(returns)) {
157
- return [" raw_result"];
157
+ return [" raw_result"];
158
158
  }
159
159
  const decodeFn =
160
160
  returns === "string" ? "decode_host_service_string_result" :
@@ -165,7 +165,7 @@ function emitDecodeReturn(
165
165
  returns === "u64_array" ? "decode_host_service_u64_array_result" :
166
166
  "decode_host_service_f64_array_result";
167
167
  return [
168
- ` ${runtimePath}::${decodeFn}(result_ptr, raw_result, "${importName}")`,
168
+ ` ${runtimePath}::${decodeFn}(result_ptr, raw_result, "${importName}")`,
169
169
  ];
170
170
  }
171
171
 
@@ -219,32 +219,32 @@ export async function generateRustHostServicesFile(
219
219
  const wrapperName = snakeCaseIdentifier(method.importName);
220
220
  const returnType = rustReturnType(method.returns);
221
221
  wrappers.push(`pub fn ${wrapperName}(${emitWrapperArgs(method.args)}) -> ${returnType} {`);
222
- wrappers.push(` #[cfg(${hostImportCfg})]`);
223
- wrappers.push(" {");
222
+ wrappers.push(` #[cfg(${hostImportCfg})]`);
223
+ wrappers.push(" {");
224
224
  if (isBufferType(method.returns)) {
225
- wrappers.push(` let result_ptr = ${runtimePath}::host_service_result_buffer_ptr();`);
226
- wrappers.push(` let result_cap = ${runtimePath}::host_service_result_buffer_size();`);
225
+ wrappers.push(` let result_ptr = ${runtimePath}::host_service_result_buffer_ptr();`);
226
+ wrappers.push(` let result_cap = ${runtimePath}::host_service_result_buffer_size();`);
227
227
  const wasmCallArgs = emitWasmCallArgs(method.args);
228
228
  const callArgs = wasmCallArgs.length > 0 ? `${wasmCallArgs}, result_ptr, result_cap` : "result_ptr, result_cap";
229
- wrappers.push(` let raw_result = unsafe { __host_${method.importName}(${callArgs}) };`);
229
+ wrappers.push(` let raw_result = unsafe { __host_${method.importName}(${callArgs}) };`);
230
230
  wrappers.push(...emitDecodeReturn(method.returns, runtimePath, method.importName));
231
231
  } else if (method.returns === "void") {
232
232
  const wasmCallArgs = emitWasmCallArgs(method.args);
233
- wrappers.push(` unsafe { __host_${method.importName}(${wasmCallArgs}) };`);
233
+ wrappers.push(` unsafe { __host_${method.importName}(${wasmCallArgs}) };`);
234
234
  } else {
235
- wrappers.push(` unsafe { __host_${method.importName}(${emitWasmCallArgs(method.args)}) }`);
235
+ wrappers.push(` unsafe { __host_${method.importName}(${emitWasmCallArgs(method.args)}) }`);
236
236
  }
237
- wrappers.push(" }");
238
- wrappers.push(` #[cfg(${fallbackCfg})]`);
239
- wrappers.push(" {");
237
+ wrappers.push(" }");
238
+ wrappers.push(` #[cfg(${fallbackCfg})]`);
239
+ wrappers.push(" {");
240
240
  if (mode === "framework") {
241
241
  wrappers.push(...emitFrameworkNonWasmBody(method.importName, method.args));
242
242
  } else {
243
243
  wrappers.push(
244
- ` panic!("Host service ${method.importName} is only available in wasm/browser builds.");`,
244
+ ` panic!("Host service ${method.importName} is only available in wasm/browser builds.");`,
245
245
  );
246
246
  }
247
- wrappers.push(" }");
247
+ wrappers.push(" }");
248
248
  wrappers.push("}");
249
249
  wrappers.push("");
250
250
  }
@@ -52,3 +52,18 @@ impl HasFlexBoxRoot for AntiSelectionArea {
52
52
  &self.root
53
53
  }
54
54
  }
55
+
56
+ impl ThemeBindable for AntiSelectionArea {
57
+ fn theme_binding_node(&self) -> NodeRef {
58
+ self.root.retained_node_ref()
59
+ }
60
+
61
+ fn weak_theme_target(&self) -> Box<dyn Fn() -> Option<Self>> {
62
+ let weak_root = self.root.downgrade();
63
+ Box::new(move || {
64
+ Some(AntiSelectionArea {
65
+ root: weak_root.upgrade()?,
66
+ })
67
+ })
68
+ }
69
+ }
@@ -4,6 +4,7 @@ use super::internal::button_presenter::{
4
4
  use super::*;
5
5
  use crate::ffi::CursorStyle;
6
6
  use crate::node::{TextCore, WeakFlexBox};
7
+ use crate::theme::subscribe;
7
8
  use crate::{focus_adorner, focus_visibility};
8
9
  use std::rc::Rc;
9
10
 
@@ -250,6 +251,9 @@ impl Button {
250
251
  ButtonEventTarget {
251
252
  weak_root: self.root.downgrade(),
252
253
  presenter: self.presenter.clone(),
254
+ label: self.label.clone(),
255
+ template: self.template.clone(),
256
+ label_value: self.label_value.clone(),
253
257
  click: self.click.clone(),
254
258
  double_click: self.double_click.clone(),
255
259
  triple_click: self.triple_click.clone(),
@@ -345,6 +349,17 @@ impl HasFlexBoxRoot for Button {
345
349
  }
346
350
  }
347
351
 
352
+ impl ThemeBindable for Button {
353
+ fn theme_binding_node(&self) -> NodeRef {
354
+ self.root.retained_node_ref()
355
+ }
356
+
357
+ fn weak_theme_target(&self) -> Box<dyn Fn() -> Option<Self>> {
358
+ let target = self.event_target();
359
+ Box::new(move || target.upgrade())
360
+ }
361
+ }
362
+
348
363
  impl LabeledControlTextStyle for Button {
349
364
  fn set_label_font_family(&self, family: crate::FontFamily) {
350
365
  self.set_explicit_font_family(family);
@@ -434,6 +449,9 @@ fn create_button_presenter(template: Option<Rc<dyn ButtonTemplate>>) -> Rc<dyn B
434
449
  struct ButtonEventTarget {
435
450
  weak_root: WeakFlexBox,
436
451
  presenter: Rc<RefCell<Rc<dyn ButtonPresenter>>>,
452
+ label: Rc<RefCell<TextCore>>,
453
+ template: Rc<RefCell<Option<Rc<dyn ButtonTemplate>>>>,
454
+ label_value: Rc<RefCell<String>>,
437
455
  click: Rc<RefCell<Option<ClickCallback>>>,
438
456
  double_click: Rc<RefCell<Option<ClickCallback>>>,
439
457
  triple_click: Rc<RefCell<Option<ClickCallback>>>,
@@ -448,6 +466,27 @@ struct ButtonEventTarget {
448
466
  }
449
467
 
450
468
  impl ButtonEventTarget {
469
+ fn upgrade(&self) -> Option<Button> {
470
+ Some(Button {
471
+ root: self.weak_root.upgrade()?,
472
+ presenter: self.presenter.clone(),
473
+ label: self.label.clone(),
474
+ template: self.template.clone(),
475
+ label_value: self.label_value.clone(),
476
+ click: self.click.clone(),
477
+ double_click: self.double_click.clone(),
478
+ triple_click: self.triple_click.clone(),
479
+ hovered_state: self.hovered_state.clone(),
480
+ pressed_state: self.pressed_state.clone(),
481
+ focused_state: self.focused_state.clone(),
482
+ keyboard_armed_key: self.keyboard_armed_key.clone(),
483
+ font_family_override: self.font_family_override.clone(),
484
+ font_size_override: self.font_size_override.clone(),
485
+ text_color_override: self.text_color_override.clone(),
486
+ colors_value: self.colors_value.clone(),
487
+ })
488
+ }
489
+
451
490
  fn is_enabled(&self) -> bool {
452
491
  self.weak_root
453
492
  .upgrade()
@@ -2,7 +2,9 @@ use super::internal::checkbox_indicator_presenter::{
2
2
  create_default_checkbox_indicator_presenter, CheckboxIndicatorPresenter,
3
3
  CheckboxIndicatorTemplate, CheckboxIndicatorVisualState,
4
4
  };
5
- use super::internal::pressable_labeled_control::PressableLabeledControlState;
5
+ use super::internal::pressable_labeled_control::{
6
+ PressableLabeledControlState, WeakPressableLabeledControl,
7
+ };
6
8
  use super::*;
7
9
  use std::cell::{Cell, RefCell};
8
10
  use std::rc::Rc;
@@ -318,9 +320,23 @@ impl HasFlexBoxRoot for Checkbox {
318
320
  }
319
321
  }
320
322
 
323
+ impl ThemeBindable for Checkbox {
324
+ fn theme_binding_node(&self) -> NodeRef {
325
+ self.root.retained_node_ref()
326
+ }
327
+
328
+ fn weak_theme_target(&self) -> Box<dyn Fn() -> Option<Self>> {
329
+ let target = CheckboxEventTarget::from_checkbox(self);
330
+ Box::new(move || target.upgrade())
331
+ }
332
+ }
333
+
321
334
  #[derive(Clone)]
322
335
  struct CheckboxEventTarget {
336
+ base: WeakPressableLabeledControl,
323
337
  presenter: Rc<RefCell<Rc<dyn CheckboxIndicatorPresenter>>>,
338
+ template_override: Rc<RefCell<Option<Rc<dyn CheckboxIndicatorTemplate>>>>,
339
+ sizing_value: Rc<Cell<Option<LabeledControlSizing>>>,
324
340
  state: Rc<Cell<CheckState>>,
325
341
  tri_state: Rc<Cell<bool>>,
326
342
  colors_value: Rc<Cell<Option<LabeledControlColors>>>,
@@ -331,7 +347,10 @@ struct CheckboxEventTarget {
331
347
  impl CheckboxEventTarget {
332
348
  fn from_checkbox(checkbox: &Checkbox) -> Self {
333
349
  Self {
350
+ base: checkbox.base.downgrade(),
334
351
  presenter: checkbox.indicator_presenter.clone(),
352
+ template_override: checkbox.template_override.clone(),
353
+ sizing_value: checkbox.sizing_value.clone(),
335
354
  state: checkbox.state.clone(),
336
355
  tri_state: checkbox.tri_state.clone(),
337
356
  colors_value: checkbox.colors_value.clone(),
@@ -340,6 +359,22 @@ impl CheckboxEventTarget {
340
359
  }
341
360
  }
342
361
 
362
+ fn upgrade(&self) -> Option<Checkbox> {
363
+ let base = self.base.upgrade()?;
364
+ Some(Checkbox {
365
+ root: base.root(),
366
+ base,
367
+ indicator_presenter: self.presenter.clone(),
368
+ template_override: self.template_override.clone(),
369
+ sizing_value: self.sizing_value.clone(),
370
+ state: self.state.clone(),
371
+ tri_state: self.tri_state.clone(),
372
+ colors_value: self.colors_value.clone(),
373
+ changed: self.changed.clone(),
374
+ weak_root: self.weak_root.clone(),
375
+ })
376
+ }
377
+
343
378
  fn activate(&self, base_state: PressableLabeledControlState) {
344
379
  let next = match (self.state.get(), self.tri_state.get()) {
345
380
  (CheckState::False, _) => CheckState::True,
@@ -24,7 +24,7 @@ use crate::node::{
24
24
  };
25
25
  use crate::signal::SubscriptionGuard;
26
26
  use crate::theme::{current_theme, subscribe, Theme};
27
- use crate::{app, frame_scheduler};
27
+ use crate::{app, frame_scheduler, ThemeBindable};
28
28
  use std::cell::{Cell, RefCell};
29
29
  use std::rc::{Rc, Weak};
30
30
 
@@ -876,6 +876,23 @@ impl HasFlexBoxRoot for ComboBox {
876
876
  }
877
877
  }
878
878
 
879
+ impl ThemeBindable for ComboBox {
880
+ fn theme_binding_node(&self) -> NodeRef {
881
+ self.root.retained_node_ref()
882
+ }
883
+
884
+ fn weak_theme_target(&self) -> Box<dyn Fn() -> Option<Self>> {
885
+ let weak_root = self.root.downgrade();
886
+ let weak_shared = Rc::downgrade(&self.shared);
887
+ Box::new(move || {
888
+ Some(ComboBox {
889
+ root: weak_root.upgrade()?,
890
+ shared: weak_shared.upgrade()?,
891
+ })
892
+ })
893
+ }
894
+ }
895
+
879
896
  impl Node for ComboBox {
880
897
  fn retained_node_ref(&self) -> NodeRef {
881
898
  self.root.retained_node_ref()
@@ -22,6 +22,7 @@ use crate::node::{row, FlexBox, FlexBoxSurface, HasFlexBoxRoot, Node, NodeRef, W
22
22
  use crate::persisted::{persisted_value_adapter, PersistedInt32Codec};
23
23
  use crate::signal::SubscriptionGuard;
24
24
  use crate::theme::{current_theme, subscribe};
25
+ use crate::ThemeBindable;
25
26
  use std::cell::{Cell, RefCell};
26
27
  use std::rc::{Rc, Weak};
27
28
 
@@ -642,6 +643,23 @@ impl HasFlexBoxRoot for Dropdown {
642
643
  }
643
644
  }
644
645
 
646
+ impl ThemeBindable for Dropdown {
647
+ fn theme_binding_node(&self) -> NodeRef {
648
+ self.root.retained_node_ref()
649
+ }
650
+
651
+ fn weak_theme_target(&self) -> Box<dyn Fn() -> Option<Self>> {
652
+ let weak_root = self.root.downgrade();
653
+ let weak_shared = Rc::downgrade(&self.shared);
654
+ Box::new(move || {
655
+ Some(Dropdown {
656
+ root: weak_root.upgrade()?,
657
+ shared: weak_shared.upgrade()?,
658
+ })
659
+ })
660
+ }
661
+ }
662
+
645
663
  impl DropdownShared {
646
664
  fn is_enabled(&self) -> bool {
647
665
  self.root
@@ -96,6 +96,29 @@ impl PressableLabeledControl {
96
96
  self.root.clone()
97
97
  }
98
98
 
99
+ pub(crate) fn downgrade(&self) -> WeakPressableLabeledControl {
100
+ WeakPressableLabeledControl {
101
+ root: self.root.downgrade(),
102
+ indicator_root: self.indicator_root.clone(),
103
+ label_node: self.label_node.clone(),
104
+ gap_node: self.gap_node.clone(),
105
+ label_host: self.label_host.clone(),
106
+ hovered_state: self.hovered_state.clone(),
107
+ pressed_state: self.pressed_state.clone(),
108
+ focused_state: self.focused_state.clone(),
109
+ key_pressed_state: self.key_pressed_state.clone(),
110
+ pointer_pressed_state: self.pointer_pressed_state.clone(),
111
+ enabled_state: self.enabled_state.clone(),
112
+ label_font_family_override: self.label_font_family_override.clone(),
113
+ label_font_size_override: self.label_font_size_override.clone(),
114
+ label_text_color_override: self.label_text_color_override.clone(),
115
+ colors_value: self.colors_value.clone(),
116
+ activation: self.activation.clone(),
117
+ state_changed: self.state_changed.clone(),
118
+ key_handler: self.key_handler.clone(),
119
+ }
120
+ }
121
+
99
122
  #[cfg(test)]
100
123
  pub(crate) fn test_parts(&self) -> (FlexBox, FlexBox, FlexBox, FlexBox) {
101
124
  (
@@ -302,6 +325,53 @@ impl PressableLabeledControl {
302
325
  }
303
326
  }
304
327
 
328
+ #[derive(Clone)]
329
+ pub(crate) struct WeakPressableLabeledControl {
330
+ root: WeakFlexBox,
331
+ indicator_root: Rc<RefCell<FlexBox>>,
332
+ label_node: TextCore,
333
+ gap_node: FlexBox,
334
+ label_host: FlexBox,
335
+ hovered_state: Rc<Cell<bool>>,
336
+ pressed_state: Rc<Cell<bool>>,
337
+ focused_state: Rc<Cell<bool>>,
338
+ key_pressed_state: Rc<Cell<bool>>,
339
+ pointer_pressed_state: Rc<Cell<bool>>,
340
+ enabled_state: Rc<Cell<bool>>,
341
+ label_font_family_override: Rc<RefCell<Option<crate::FontFamily>>>,
342
+ label_font_size_override: Rc<Cell<f32>>,
343
+ label_text_color_override: Rc<Cell<Option<u32>>>,
344
+ colors_value: Rc<Cell<Option<LabeledControlColors>>>,
345
+ activation: Rc<RefCell<Option<ActivationCallback>>>,
346
+ state_changed: Rc<RefCell<Option<StateCallback>>>,
347
+ key_handler: Rc<RefCell<Option<KeyCallback>>>,
348
+ }
349
+
350
+ impl WeakPressableLabeledControl {
351
+ pub(crate) fn upgrade(&self) -> Option<PressableLabeledControl> {
352
+ Some(PressableLabeledControl {
353
+ root: self.root.upgrade()?,
354
+ indicator_root: self.indicator_root.clone(),
355
+ label_node: self.label_node.clone(),
356
+ gap_node: self.gap_node.clone(),
357
+ label_host: self.label_host.clone(),
358
+ hovered_state: self.hovered_state.clone(),
359
+ pressed_state: self.pressed_state.clone(),
360
+ focused_state: self.focused_state.clone(),
361
+ key_pressed_state: self.key_pressed_state.clone(),
362
+ pointer_pressed_state: self.pointer_pressed_state.clone(),
363
+ enabled_state: self.enabled_state.clone(),
364
+ label_font_family_override: self.label_font_family_override.clone(),
365
+ label_font_size_override: self.label_font_size_override.clone(),
366
+ label_text_color_override: self.label_text_color_override.clone(),
367
+ colors_value: self.colors_value.clone(),
368
+ activation: self.activation.clone(),
369
+ state_changed: self.state_changed.clone(),
370
+ key_handler: self.key_handler.clone(),
371
+ })
372
+ }
373
+ }
374
+
305
375
  fn sync_base_theme_parts(
306
376
  root: &FlexBox,
307
377
  label_node: &TextCore,
@@ -5,7 +5,8 @@ use crate::ffi::{
5
5
  SemanticCheckedState, SemanticRole, Unit,
6
6
  };
7
7
  use crate::node::{
8
- flex_box, row, Child, FlexBox, HasFlexBoxRoot, Node, NodeRef, TextNode, WeakNodeRef,
8
+ flex_box, row, Child, FlexBox, HasFlexBoxRoot, Node, NodeRef, TextNode, ThemeBindable,
9
+ WeakNodeRef,
9
10
  };
10
11
  use crate::theme::{current_theme, subscribe};
11
12
  use std::cell::{Cell, RefCell};
@@ -271,19 +271,6 @@ impl NavLink {
271
271
  self
272
272
  }
273
273
 
274
- pub fn bind_theme(&self, handler: impl Fn(&NavLink, crate::theme::Theme) + 'static) -> &Self {
275
- let target = self.event_target();
276
- let guard = subscribe(move |theme| {
277
- if let Some(link) = target.upgrade() {
278
- handler(&link, theme);
279
- }
280
- });
281
- self.root
282
- .retained_node_ref()
283
- .retain_attachment(Rc::new(guard));
284
- self
285
- }
286
-
287
274
  pub fn on_navigate(&self, handler: impl Fn(NavigateEventArgs) + 'static) -> &Self {
288
275
  *self.navigate.borrow_mut() = Some(Rc::new(handler));
289
276
  self
@@ -315,6 +302,17 @@ impl HasFlexBoxRoot for NavLink {
315
302
  }
316
303
  }
317
304
 
305
+ impl ThemeBindable for NavLink {
306
+ fn theme_binding_node(&self) -> NodeRef {
307
+ self.root.retained_node_ref()
308
+ }
309
+
310
+ fn weak_theme_target(&self) -> Box<dyn Fn() -> Option<Self>> {
311
+ let target = self.event_target();
312
+ Box::new(move || target.upgrade())
313
+ }
314
+ }
315
+
318
316
  impl LabeledControlTextStyle for NavLink {
319
317
  fn set_label_font_family(&self, family: crate::FontFamily) {
320
318
  self.set_explicit_font_family(family);
@@ -31,6 +31,7 @@ pub struct ProgressBar {
31
31
  min: Rc<Cell<f32>>,
32
32
  max: Rc<Cell<f32>>,
33
33
  value: Rc<Cell<f32>>,
34
+ orientation: Rc<Cell<Orientation>>,
34
35
  sizing_value: Rc<Cell<Option<ProgressBarSizing>>>,
35
36
  colors_value: Rc<Cell<Option<ProgressBarColors>>>,
36
37
  weak_root: WeakFlexBox,
@@ -47,7 +48,9 @@ impl ProgressBar {
47
48
  pub fn new() -> Self {
48
49
  let root = flex_box();
49
50
  let fill = flex_box();
50
- root.semantic_value_range(0.0, 0.0, 100.0).child(&fill);
51
+ root.semantic_orientation(Orientation::Horizontal)
52
+ .semantic_value_range(0.0, 0.0, 100.0)
53
+ .child(&fill);
51
54
  let control = Self {
52
55
  weak_root: root.downgrade(),
53
56
  weak_fill: fill.downgrade(),
@@ -56,6 +59,7 @@ impl ProgressBar {
56
59
  min: Rc::new(Cell::new(0.0)),
57
60
  max: Rc::new(Cell::new(100.0)),
58
61
  value: Rc::new(Cell::new(0.0)),
62
+ orientation: Rc::new(Cell::new(Orientation::Horizontal)),
59
63
  sizing_value: Rc::new(Cell::new(None)),
60
64
  colors_value: Rc::new(Cell::new(None)),
61
65
  };
@@ -88,6 +92,43 @@ impl ProgressBar {
88
92
  self
89
93
  }
90
94
 
95
+ pub fn length(&self, value: f32) -> &Self {
96
+ let value = clamp_direct_dimension("length", value);
97
+ let current = self.sizing_value.get().unwrap_or_default();
98
+ let mut sizing = ProgressBarSizing::new().length(value);
99
+ if current.has_thickness() {
100
+ sizing = sizing.thickness(current.thickness_px());
101
+ }
102
+ self.sizing_value.set(Some(sizing));
103
+ self.sync_geometry();
104
+ self
105
+ }
106
+
107
+ pub fn thickness(&self, value: f32) -> &Self {
108
+ let value = clamp_direct_dimension("thickness", value);
109
+ let current = self.sizing_value.get().unwrap_or_default();
110
+ let mut sizing = ProgressBarSizing::new().thickness(value);
111
+ if current.has_length() {
112
+ sizing = sizing.length(current.length_px());
113
+ }
114
+ self.sizing_value.set(Some(sizing));
115
+ self.sync_geometry();
116
+ self.sync_visual_state();
117
+ self
118
+ }
119
+
120
+ pub fn orientation(&self, orientation: Orientation) -> &Self {
121
+ let orientation = if orientation == Orientation::Vertical {
122
+ Orientation::Vertical
123
+ } else {
124
+ Orientation::Horizontal
125
+ };
126
+ self.orientation.set(orientation);
127
+ self.root.semantic_orientation(orientation);
128
+ self.sync_geometry();
129
+ self
130
+ }
131
+
91
132
  pub fn sizing(&self, sizing: ProgressBarSizing) -> &Self {
92
133
  self.sizing_value.set(Some(sizing));
93
134
  self.sync_geometry();
@@ -165,6 +206,7 @@ impl ProgressBar {
165
206
  self.min.get(),
166
207
  self.max.get(),
167
208
  self.value.get(),
209
+ self.orientation.get(),
168
210
  resolve_sizing(self.sizing_value.get()),
169
211
  );
170
212
  }
@@ -179,6 +221,46 @@ impl ProgressBar {
179
221
  }
180
222
  }
181
223
 
224
+ #[derive(Clone)]
225
+ struct ProgressBarThemeTarget {
226
+ root: WeakFlexBox,
227
+ fill: WeakFlexBox,
228
+ min: Rc<Cell<f32>>,
229
+ max: Rc<Cell<f32>>,
230
+ value: Rc<Cell<f32>>,
231
+ orientation: Rc<Cell<Orientation>>,
232
+ sizing_value: Rc<Cell<Option<ProgressBarSizing>>>,
233
+ colors_value: Rc<Cell<Option<ProgressBarColors>>>,
234
+ }
235
+
236
+ impl ProgressBarThemeTarget {
237
+ fn upgrade(&self) -> Option<ProgressBar> {
238
+ Some(ProgressBar {
239
+ root: self.root.upgrade()?,
240
+ fill: self.fill.upgrade()?,
241
+ min: self.min.clone(),
242
+ max: self.max.clone(),
243
+ value: self.value.clone(),
244
+ orientation: self.orientation.clone(),
245
+ sizing_value: self.sizing_value.clone(),
246
+ colors_value: self.colors_value.clone(),
247
+ weak_root: self.root.clone(),
248
+ weak_fill: self.fill.clone(),
249
+ })
250
+ }
251
+ }
252
+
253
+ fn clamp_direct_dimension(method: &str, value: f32) -> f32 {
254
+ if value > 0.0 {
255
+ return value;
256
+ }
257
+ crate::logger::warn(
258
+ "Layout",
259
+ &format!("ProgressBar.{method}() received {value}; clamping to 1.0."),
260
+ );
261
+ 1.0
262
+ }
263
+
182
264
  impl Node for ProgressBar {
183
265
  fn retained_node_ref(&self) -> NodeRef {
184
266
  self.root.retained_node_ref()
@@ -197,12 +279,33 @@ impl HasFlexBoxRoot for ProgressBar {
197
279
  }
198
280
  }
199
281
 
282
+ impl ThemeBindable for ProgressBar {
283
+ fn theme_binding_node(&self) -> NodeRef {
284
+ self.root.retained_node_ref()
285
+ }
286
+
287
+ fn weak_theme_target(&self) -> Box<dyn Fn() -> Option<Self>> {
288
+ let target = ProgressBarThemeTarget {
289
+ root: self.weak_root.clone(),
290
+ fill: self.weak_fill.clone(),
291
+ min: self.min.clone(),
292
+ max: self.max.clone(),
293
+ value: self.value.clone(),
294
+ orientation: self.orientation.clone(),
295
+ sizing_value: self.sizing_value.clone(),
296
+ colors_value: self.colors_value.clone(),
297
+ };
298
+ Box::new(move || target.upgrade())
299
+ }
300
+ }
301
+
200
302
  fn sync_progress_geometry(
201
303
  root: &FlexBox,
202
304
  fill: &FlexBox,
203
305
  min: f32,
204
306
  max: f32,
205
307
  current_value: f32,
308
+ orientation: Orientation,
206
309
  sizing: ResolvedProgressBarSizing,
207
310
  ) {
208
311
  let range = max - min;
@@ -212,15 +315,26 @@ fn sync_progress_geometry(
212
315
  0.0
213
316
  };
214
317
  let fill_length = sizing.length * fraction;
215
- root.width(sizing.length, Unit::Pixel)
216
- .height(sizing.thickness, Unit::Pixel)
217
- .semantic_value_range(current_value, min, max)
318
+ if orientation == Orientation::Vertical {
319
+ root.flex_direction(FlexDirection::Column)
320
+ .justify_content(JustifyContent::End)
321
+ .width(sizing.thickness, Unit::Pixel)
322
+ .height(sizing.length, Unit::Pixel);
323
+ fill.width(sizing.thickness, Unit::Pixel)
324
+ .height(fill_length, Unit::Pixel);
325
+ } else {
326
+ root.flex_direction(FlexDirection::Row)
327
+ .justify_content(JustifyContent::Start)
328
+ .width(sizing.length, Unit::Pixel)
329
+ .height(sizing.thickness, Unit::Pixel);
330
+ fill.width(fill_length, Unit::Pixel)
331
+ .height(sizing.thickness, Unit::Pixel);
332
+ }
333
+ root.semantic_value_range(current_value, min, max)
218
334
  .default_semantic_label(format!(
219
335
  "Progress bar, value {}, range {} to {}",
220
336
  current_value, min, max
221
337
  ));
222
- fill.width(fill_length, Unit::Pixel)
223
- .height(sizing.thickness, Unit::Pixel);
224
338
  }
225
339
 
226
340
  fn sync_progress_visual_state(