ferroui_base/styling/
style_instance.rs1use super::activators::{IStyleActivator, IStyleActivatorSink};
2use super::{ISetterInstance, IStyleInstance, SetterInstance, SetterInstanceKind, StyleBase};
3use crate::animation::IAnimation;
4use crate::data::BindingPriority;
5use crate::property_store::{FrameType, ValueFrame, ValueFrameBase};
6use crate::reactive::{IDisposable, IObservable, IObserver, LightweightSubject};
7use crate::{Ref, StyledElement, WeakRef};
8use std::any::Any;
9use std::cell::{Cell, RefCell};
10use std::rc::{Rc, Weak};
11
12#[derive(Clone, Debug, PartialEq, Eq)]
15pub struct DuplicateSetterError {
16 pub property: String,
18 pub style: String,
20}
21
22impl std::fmt::Display for DuplicateSetterError {
23 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24 write!(f, "Duplicate setter encountered for property '{}' in '{}'.", self.property, self.style)
25 }
26}
27
28impl std::error::Error for DuplicateSetterError {}
29
30pub struct StyleInstance {
38 base: ValueFrameBase,
39 this: Weak<StyleInstance>,
40 activator: Option<Rc<dyn IStyleActivator>>,
41 is_active: Cell<bool>,
42 setters: RefCell<Vec<Rc<dyn ISetterInstance>>>,
43 animations: RefCell<Option<Vec<Rc<dyn IAnimation>>>>,
44 animation_apply_disposables: RefCell<Option<Vec<Rc<dyn IDisposable>>>>,
45 animation_trigger: RefCell<Option<LightweightSubject<bool>>>,
46 source: WeakRef<StyleBase>,
47}
48
49impl StyleInstance {
50 pub(crate) fn new(
51 style: WeakRef<StyleBase>,
52 activator: Option<Rc<dyn IStyleActivator>>,
53 type_: FrameType,
54 ) -> Rc<Self> {
55 let priority = Self::get_priority(activator.is_some());
56 Rc::new_cyclic(|this| Self {
57 base: ValueFrameBase::new(priority, type_),
58 this: this.clone(),
59 activator,
60 is_active: Cell::new(false),
61 setters: RefCell::new(Vec::new()),
62 animations: RefCell::new(None),
63 animation_apply_disposables: RefCell::new(None),
64 animation_trigger: RefCell::new(None),
65 source: style,
66 })
67 }
68
69 pub fn priority(&self) -> BindingPriority {
71 self.base.priority()
72 }
73
74 pub(crate) fn is_source(&self, style: &Ref<StyleBase>) -> bool {
76 self.source.points_to(style)
77 }
78
79 pub(crate) fn source_display(&self) -> String {
80 match self.source.upgrade() {
81 Some(source) => source.to_display_string(),
82 None => "Style".to_string(),
83 }
84 }
85
86 pub(crate) fn try_add(&self, instance: SetterInstance) -> Result<(), DuplicateSetterError> {
90 match instance.kind {
91 SetterInstanceKind::Entry(value_entry) => {
92 if self.base.contains(value_entry.property()) {
93 return Err(DuplicateSetterError {
94 property: value_entry.property().to_string(),
95 style: self.source_display(),
96 });
97 }
98 self.base.add(value_entry);
99 }
100 SetterInstanceKind::Other(instance) => self.setters.borrow_mut().push(instance),
101 }
102 Ok(())
103 }
104
105 #[allow(dead_code)]
108 pub(crate) fn add(&self, instance: SetterInstance) {
109 if let Err(error) = self.try_add(instance) {
110 panic!("{error}");
111 }
112 }
113
114 pub(crate) fn add_animations(&self, animations: &[Rc<dyn IAnimation>]) {
115 self.animations.borrow_mut().get_or_insert_with(Vec::new).extend(animations.iter().cloned());
116 }
117
118 pub(crate) fn apply_animations(&self, control: &StyledElement) {
121 let animations = self.animations.borrow().clone();
122 if let Some(animations) = animations {
123 let trigger = self.animation_trigger.borrow_mut().get_or_insert_with(LightweightSubject::new).clone();
124 let match_: Rc<dyn IObservable<bool>> = Rc::new(trigger.clone());
125 for animation in &animations {
126 let disposable = animation.apply(control, None, match_.clone(), None, false);
127 self.animation_apply_disposables.borrow_mut().get_or_insert_with(Vec::new).push(disposable);
128 }
129
130 if self.activator.is_none() {
131 trigger.on_next(true);
132 }
133 }
134 }
135
136 fn trigger_animations(&self, value: bool) {
137 let trigger = self.animation_trigger.borrow().clone();
138 if let Some(trigger) = trigger {
139 trigger.on_next(value);
140 }
141 }
142
143 pub(crate) fn make_shared(&self) {
144 self.base.make_shared();
145 }
146
147 fn get_priority(has_activator: bool) -> BindingPriority {
148 if has_activator {
149 BindingPriority::StyleTrigger
150 } else {
151 BindingPriority::Style
152 }
153 }
154}
155
156impl IStyleInstance for StyleInstance {
157 fn source(&self) -> Option<Ref<StyleBase>> {
158 self.source.upgrade()
159 }
160
161 #[inline]
162 fn has_activator(&self) -> bool {
163 self.activator.is_some()
164 }
165
166 fn is_active(&self) -> bool {
167 self.is_active.get()
168 }
169}
170
171impl IStyleActivatorSink for StyleInstance {
172 fn on_next(&self, value: bool) {
173 if let Some(owner) = self.base.owner() {
174 if let Some(this) = self.this.upgrade() {
175 let frame: Rc<dyn ValueFrame> = this;
176 owner.values().on_frame_activation_changed(&owner, &frame);
177 }
178 }
179 self.trigger_animations(value);
180 }
181}
182
183impl ValueFrame for StyleInstance {
184 fn base(&self) -> &ValueFrameBase {
185 &self.base
186 }
187
188 fn get_is_active(&self) -> (bool, bool) {
189 let previous = self.is_active.get();
190
191 let is_active = match &self.activator {
192 Some(activator) => {
193 if !activator.is_subscribed() {
194 let sink: Weak<dyn IStyleActivatorSink> = self.this.clone();
195 activator.subscribe(sink);
196 self.trigger_animations(activator.get_is_active());
197 }
198 activator.get_is_active()
199 }
200 None => true,
201 };
202
203 self.is_active.set(is_active);
204 (is_active, is_active != previous)
205 }
206
207 fn dispose(&self) {
208 self.base.unsubscribe_all();
209 if let Some(activator) = &self.activator {
210 activator.dispose();
211 }
212 let disposables = self.animation_apply_disposables.borrow().clone();
213 if let Some(disposables) = disposables {
214 for item in disposables {
215 item.dispose();
216 }
217 }
218 }
219
220 fn as_any(&self) -> &dyn Any {
221 self
222 }
223}