1use crate::metadata::{PseudoClassesAttribute, TemplatePartAttribute};
2use crate::primitives::{RangeBase, TemplateAppliedEventArgs, TemplatedControlImpl};
3use crate::{Border, ControlImpl};
4use ferroui_base::data::BindingMode;
5use ferroui_base::input::InputElementImpl;
6use ferroui_base::interactivity::InteractiveImpl;
7use ferroui_base::layout::{LayoutableImpl, LayoutableImplExt, Orientation};
8use ferroui_base::reactive::IDisposable;
9use ferroui_base::{
10 ferro_class, ferro_impl_classes, ferro_property, instantiate, DirectProperty, FerroObject, FerroObjectExtensions,
11 FerroObjectImpl, FerroObjectImplExt, FerroProperty, FerroPropertyChangedEventArgs, Ref, Size, StaticType,
12 StyledElementImpl, StyledProperty, StyledPropertyMetadata, Visual, VisualImpl,
13};
14use std::cell::{Cell, RefCell};
15use std::rc::Rc;
16
17const PC_VERTICAL: &str = ":vertical";
18const PC_HORIZONTAL: &str = ":horizontal";
19const PC_INDETERMINATE: &str = ":indeterminate";
20
21#[repr(C)]
26pub struct ProgressBarTemplateSettings {
27 base: FerroObject,
28 container2_width: Cell<f64>,
29 container_width: Cell<f64>,
30 container_animation_start_position: Cell<f64>,
31 container_animation_end_position: Cell<f64>,
32 container2_animation_start_position: Cell<f64>,
33 container2_animation_end_position: Cell<f64>,
34 indeterminate_starting_offset: Cell<f64>,
35 indeterminate_ending_offset: Cell<f64>,
36}
37
38ferro_class!(ProgressBarTemplateSettings: FerroObject);
39ferroui_base::ferro_class_info!(ProgressBarTemplateSettings {
40 new: ProgressBarTemplateSettings::new,
41});
42ferro_impl_classes!(ProgressBarTemplateSettings: FerroObjectImpl);
43
44ferroui_base::ferro_properties! { impl ProgressBarTemplateSettings {
45 ferro_property!(
46 pub fn container_animation_start_position_property() -> DirectProperty<ProgressBarTemplateSettings, f64> {
48 FerroProperty::register_direct::<ProgressBarTemplateSettings, _>(
49 "ContainerAnimationStartPosition",
50 |p| p.container_animation_start_position(),
51 Some(|p, o| p.set_container_animation_start_position(o)),
52 0.0,
53 )
54 }
55 );
56
57 ferro_property!(
58 pub fn container_animation_end_position_property() -> DirectProperty<ProgressBarTemplateSettings, f64> {
60 FerroProperty::register_direct::<ProgressBarTemplateSettings, _>(
61 "ContainerAnimationEndPosition",
62 |p| p.container_animation_end_position(),
63 Some(|p, o| p.set_container_animation_end_position(o)),
64 0.0,
65 )
66 }
67 );
68
69 ferro_property!(
70 pub fn container2_animation_start_position_property() -> DirectProperty<ProgressBarTemplateSettings, f64> {
72 FerroProperty::register_direct::<ProgressBarTemplateSettings, _>(
73 "Container2AnimationStartPosition",
74 |p| p.container2_animation_start_position(),
75 Some(|p, o| p.set_container2_animation_start_position(o)),
76 0.0,
77 )
78 }
79 );
80
81 ferro_property!(
82 pub fn container2_animation_end_position_property() -> DirectProperty<ProgressBarTemplateSettings, f64> {
84 FerroProperty::register_direct::<ProgressBarTemplateSettings, _>(
85 "Container2AnimationEndPosition",
86 |p| p.container2_animation_end_position(),
87 Some(|p, o| p.set_container2_animation_end_position(o)),
88 0.0,
89 )
90 }
91 );
92
93 ferro_property!(
94 pub fn container2_width_property() -> DirectProperty<ProgressBarTemplateSettings, f64> {
96 FerroProperty::register_direct::<ProgressBarTemplateSettings, _>(
97 "Container2Width",
98 |p| p.container2_width(),
99 Some(|p, o| p.set_container2_width(o)),
100 0.0,
101 )
102 }
103 );
104
105 ferro_property!(
106 pub fn container_width_property() -> DirectProperty<ProgressBarTemplateSettings, f64> {
108 FerroProperty::register_direct::<ProgressBarTemplateSettings, _>(
109 "ContainerWidth",
110 |p| p.container_width(),
111 Some(|p, o| p.set_container_width(o)),
112 0.0,
113 )
114 }
115 );
116
117 ferro_property!(
118 pub fn indeterminate_starting_offset_property() -> DirectProperty<ProgressBarTemplateSettings, f64> {
120 FerroProperty::register_direct::<ProgressBarTemplateSettings, _>(
121 "IndeterminateStartingOffset",
122 |p| p.indeterminate_starting_offset(),
123 Some(|p, o| p.set_indeterminate_starting_offset(o)),
124 0.0,
125 )
126 }
127 );
128
129 ferro_property!(
130 pub fn indeterminate_ending_offset_property() -> DirectProperty<ProgressBarTemplateSettings, f64> {
132 FerroProperty::register_direct::<ProgressBarTemplateSettings, _>(
133 "IndeterminateEndingOffset",
134 |p| p.indeterminate_ending_offset(),
135 Some(|p, o| p.set_indeterminate_ending_offset(o)),
136 0.0,
137 )
138 }
139 );
140} }
141
142impl ProgressBarTemplateSettings {
143 pub fn new() -> Ref<Self> {
144 instantiate(Self {
145 base: FerroObject::construct(),
146 container2_width: Cell::new(0.0),
147 container_width: Cell::new(0.0),
148 container_animation_start_position: Cell::new(0.0),
149 container_animation_end_position: Cell::new(0.0),
150 container2_animation_start_position: Cell::new(0.0),
151 container2_animation_end_position: Cell::new(0.0),
152 indeterminate_starting_offset: Cell::new(0.0),
153 indeterminate_ending_offset: Cell::new(0.0),
154 })
155 }
156
157 pub fn container_width(&self) -> f64 {
160 self.container_width.get()
161 }
162
163 pub fn set_container_width(&self, value: f64) {
164 self.set_and_raise_cell(Self::container_width_property(), &self.container_width, value);
165 }
166
167 pub fn container2_width(&self) -> f64 {
170 self.container2_width.get()
171 }
172
173 pub fn set_container2_width(&self, value: f64) {
174 self.set_and_raise_cell(Self::container2_width_property(), &self.container2_width, value);
175 }
176
177 pub fn container_animation_start_position(&self) -> f64 {
180 self.container_animation_start_position.get()
181 }
182
183 pub fn set_container_animation_start_position(&self, value: f64) {
184 self.set_and_raise_cell(
185 Self::container_animation_start_position_property(),
186 &self.container_animation_start_position,
187 value,
188 );
189 }
190
191 pub fn container_animation_end_position(&self) -> f64 {
194 self.container_animation_end_position.get()
195 }
196
197 pub fn set_container_animation_end_position(&self, value: f64) {
198 self.set_and_raise_cell(
199 Self::container_animation_end_position_property(),
200 &self.container_animation_end_position,
201 value,
202 );
203 }
204
205 pub fn container2_animation_start_position(&self) -> f64 {
208 self.container2_animation_start_position.get()
209 }
210
211 pub fn set_container2_animation_start_position(&self, value: f64) {
212 self.set_and_raise_cell(
213 Self::container2_animation_start_position_property(),
214 &self.container2_animation_start_position,
215 value,
216 );
217 }
218
219 pub fn container2_animation_end_position(&self) -> f64 {
222 self.container2_animation_end_position.get()
223 }
224
225 pub fn set_container2_animation_end_position(&self, value: f64) {
226 self.set_and_raise_cell(
227 Self::container2_animation_end_position_property(),
228 &self.container2_animation_end_position,
229 value,
230 );
231 }
232
233 pub fn indeterminate_starting_offset(&self) -> f64 {
236 self.indeterminate_starting_offset.get()
237 }
238
239 pub fn set_indeterminate_starting_offset(&self, value: f64) {
240 self.set_and_raise_cell(
241 Self::indeterminate_starting_offset_property(),
242 &self.indeterminate_starting_offset,
243 value,
244 );
245 }
246
247 pub fn indeterminate_ending_offset(&self) -> f64 {
250 self.indeterminate_ending_offset.get()
251 }
252
253 pub fn set_indeterminate_ending_offset(&self, value: f64) {
254 self.set_and_raise_cell(
255 Self::indeterminate_ending_offset_property(),
256 &self.indeterminate_ending_offset,
257 value,
258 );
259 }
260}
261
262#[repr(C)]
264pub struct ProgressBar {
265 base: RangeBase,
266 percentage: Cell<f64>,
267 indicator: RefCell<Option<Ref<Border>>>,
268 track_size_changed_listener: RefCell<Option<Rc<dyn IDisposable>>>,
269 template_settings: Ref<ProgressBarTemplateSettings>,
270}
271
272ferro_class!(ProgressBar: RangeBase);
273ferroui_base::ferro_class_info!(ProgressBar {
277 new: ProgressBar::new,
278 markup: {
279 properties: [
280 TemplateSettings: Ref<ProgressBarTemplateSettings> { get: ProgressBar::template_settings },
281 ],
282 attributes: [
283 TemplatePart("PART_Indicator", type(Ref<Border>), IsRequired = true),
284 PseudoClasses(":vertical", ":horizontal", ":indeterminate"),
285 ],
286 },
287});
288ferro_impl_classes!(ProgressBar: StyledElementImpl, VisualImpl, InteractiveImpl, InputElementImpl);
289
290impl ControlImpl for ProgressBar {
291 fn on_create_automation_peer(this: &Self) -> Ref<crate::automation::peers::AutomationPeer> {
292 crate::automation::peers::ProgressBarAutomationPeer::new(this).upcast()
293 }
294}
295
296impl FerroObjectImpl for ProgressBar {
297 fn constructed(this: &Self) {
298 Self::parent_constructed(this);
299 this.update_pseudo_classes(Some(this.is_indeterminate()), Some(this.orientation()));
300 }
301
302 fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
303 Self::parent_on_property_changed(this, change);
304
305 let property = change.property();
306
307 if property == RangeBase::value_property().as_property()
308 || property == RangeBase::minimum_property().as_property()
309 || property == RangeBase::maximum_property().as_property()
310 || property == Self::is_indeterminate_property().as_property()
311 || property == Self::orientation_property().as_property()
312 {
313 this.update_indicator();
314 }
315
316 if property == Self::is_indeterminate_property().as_property() {
317 this.update_pseudo_classes(Some(change.get_new_value::<bool>()), None);
318 } else if property == Self::orientation_property().as_property() {
319 this.update_pseudo_classes(None, Some(change.get_new_value::<Orientation>()));
320 }
321 }
322}
323
324impl LayoutableImpl for ProgressBar {
325 fn arrange_override(this: &Self, final_size: Size) -> Size {
326 let result = Self::parent_arrange_override(this, final_size);
327 this.update_indicator();
328 result
329 }
330}
331
332impl TemplatedControlImpl for ProgressBar {
333 fn on_apply_template(this: &Self, e: &TemplateAppliedEventArgs) {
334 let previous = this.track_size_changed_listener.borrow_mut().take();
336 if let Some(previous) = previous {
337 previous.dispose();
338 }
339
340 let indicator = e.name_scope().get_as::<Border>("PART_Indicator");
341 *this.indicator.borrow_mut() = Some(indicator.clone());
342
343 let listener = indicator.parent().map(|parent| {
346 let weak = this.to_ref().downgrade();
347 parent.get_property_changed_observable(Visual::bounds_property().as_property()).subscribe(move |_| {
348 if let Some(this) = weak.upgrade() {
349 this.update_indicator();
350 }
351 })
352 });
353 *this.track_size_changed_listener.borrow_mut() = listener;
354
355 this.update_indicator();
356 }
357}
358
359impl ProgressBar {
360 pub const TEMPLATE_PARTS: &'static [TemplatePartAttribute] =
362 &[TemplatePartAttribute::required("PART_Indicator", <Border as StaticType>::TYPE)];
363
364 pub const PSEUDO_CLASSES: PseudoClassesAttribute =
366 PseudoClassesAttribute::new(&[PC_VERTICAL, PC_HORIZONTAL, PC_INDETERMINATE]);
367}
368
369ferroui_base::ferro_properties! { impl ProgressBar {
370 ferro_property!(
371 pub fn is_indeterminate_property() -> StyledProperty<bool> {
373 FerroProperty::register::<ProgressBar, _>("IsIndeterminate", false)
374 }
375 );
376
377 ferro_property!(
378 pub fn show_progress_text_property() -> StyledProperty<bool> {
380 FerroProperty::register::<ProgressBar, _>("ShowProgressText", false)
381 }
382 );
383
384 ferro_property!(
385 pub fn progress_text_format_property() -> StyledProperty<String> {
387 FerroProperty::register::<ProgressBar, _>("ProgressTextFormat", "{1:0}%".to_string())
388 }
389 );
390
391 ferro_property!(
392 pub fn orientation_property() -> StyledProperty<Orientation> {
394 FerroProperty::register::<ProgressBar, _>("Orientation", Orientation::Horizontal)
395 }
396 );
397
398 ferro_property!(
399 pub fn percentage_property() -> DirectProperty<ProgressBar, f64> {
401 FerroProperty::register_direct::<ProgressBar, _>("Percentage", |o| o.percentage(), None, 0.0)
402 }
403 );
404} }
405
406impl ProgressBar {
407 fn static_constructor() {
408 RangeBase::value_property().override_metadata::<ProgressBar>(
409 StyledPropertyMetadata::new(None).with_default_binding_mode(BindingMode::OneWay),
410 );
411 }
412
413 pub fn construct() -> Self {
415 Self {
416 base: RangeBase::construct(),
417 percentage: Cell::new(0.0),
418 indicator: RefCell::new(None),
419 track_size_changed_listener: RefCell::new(None),
420 template_settings: ProgressBarTemplateSettings::new(),
421 }
422 }
423
424 pub fn new() -> Ref<Self> {
425 instantiate(Self::construct())
426 }
427
428 pub fn percentage(&self) -> f64 {
433 self.percentage.get()
434 }
435
436 fn set_percentage(&self, value: f64) {
437 self.set_and_raise_cell(Self::percentage_property(), &self.percentage, value);
438 }
439
440 pub fn template_settings(&self) -> Ref<ProgressBarTemplateSettings> {
442 self.template_settings.clone()
443 }
444
445 pub fn is_indeterminate(&self) -> bool {
448 self.get_value(Self::is_indeterminate_property())
449 }
450
451 pub fn set_is_indeterminate(&self, value: bool) {
452 self.set_value(Self::is_indeterminate_property(), value)
453 }
454
455 pub fn show_progress_text(&self) -> bool {
457 self.get_value(Self::show_progress_text_property())
458 }
459
460 pub fn set_show_progress_text(&self, value: bool) {
461 self.set_value(Self::show_progress_text_property(), value)
462 }
463
464 pub fn progress_text_format(&self) -> String {
467 self.get_value(Self::progress_text_format_property())
468 }
469
470 pub fn set_progress_text_format(&self, value: impl Into<String>) {
471 self.set_value(Self::progress_text_format_property(), value.into())
472 }
473
474 pub fn orientation(&self) -> Orientation {
476 self.get_value(Self::orientation_property())
477 }
478
479 pub fn set_orientation(&self, value: Orientation) {
480 self.set_value(Self::orientation_property(), value)
481 }
482
483 fn update_indicator(&self) {
484 let Some(indicator) = self.indicator.borrow().clone() else {
485 return;
486 };
487
488 let bar_size = indicator.visual_parent().map_or_else(|| self.bounds().size(), |parent| parent.bounds().size());
490
491 if self.is_indeterminate() {
492 let dim = if self.orientation() == Orientation::Horizontal { bar_size.width } else { bar_size.height };
495 let bar_indicator_width = dim * 0.4; let bar_indicator_width2 = dim * 0.6; let settings = &self.template_settings;
499
500 settings.set_container_width(bar_indicator_width);
501 settings.set_container2_width(bar_indicator_width2);
502
503 settings.set_container_animation_start_position(bar_indicator_width * -1.8); settings.set_container_animation_end_position(bar_indicator_width * 3.0); settings.set_container2_animation_start_position(bar_indicator_width2 * -1.5); settings.set_container2_animation_end_position(bar_indicator_width2 * 1.66); settings.set_indeterminate_starting_offset(-dim);
510 settings.set_indeterminate_ending_offset(dim);
511 } else {
512 let (minimum, maximum) = (self.minimum(), self.maximum());
513 let percent =
516 if (maximum - minimum).abs() < 5e-324 { 1.0 } else { (self.value() - minimum) / (maximum - minimum) };
517
518 let margin = indicator.margin();
522 if self.orientation() == Orientation::Horizontal {
523 let width = (bar_size.width - margin.left - margin.right) * percent;
524 indicator.set_width(if width > 0.0 { width } else { 0.0 });
525 indicator.set_height(f64::NAN);
526 } else {
527 indicator.set_width(f64::NAN);
528 let height = (bar_size.height - margin.top - margin.bottom) * percent;
529 indicator.set_height(if height > 0.0 { height } else { 0.0 });
530 }
531
532 self.set_percentage(percent * 100.0);
533 }
534 }
535
536 fn update_pseudo_classes(&self, is_indeterminate: Option<bool>, o: Option<Orientation>) {
537 if let Some(is_indeterminate) = is_indeterminate {
538 self.pseudo_classes().set(PC_INDETERMINATE, is_indeterminate);
539 }
540
541 let Some(o) = o else {
542 return;
543 };
544 self.pseudo_classes().set(PC_VERTICAL, o == Orientation::Vertical);
545 self.pseudo_classes().set(PC_HORIZONTAL, o == Orientation::Horizontal);
546 }
547}