1use crate::media::ref_adapter::RefAdapter;
2use crate::media::{AcrylicBackgroundSource, Color, IExperimentalAcrylicMaterial, ImmutableExperimentalAcrylicMaterial};
3use crate::reactive::{Disposable, IDisposable};
4use crate::utilities::HandlerList;
5use crate::{
6 ferro_class, ferro_property, instantiate, FerroObject, FerroObjectImpl, FerroProperty,
7 ObjectType, Ref, StyledProperty, Upcast,
8};
9use std::cell::Cell;
10use std::rc::Rc;
11
12#[repr(C)]
14pub struct ExperimentalAcrylicMaterial {
15 base: FerroObject,
16 effective_tint_color: Cell<Color>,
17 effective_luminosity_color: Cell<Color>,
18 invalidated: HandlerList<dyn Fn()>,
19}
20
21ferro_class!(ExperimentalAcrylicMaterial: FerroObject);
22crate::ferro_class_info!(ExperimentalAcrylicMaterial { new: ExperimentalAcrylicMaterial::new, interfaces: [std::rc::Rc<dyn crate::media::IExperimentalAcrylicMaterial>] });
23
24crate::ferro_impl_classes!(ExperimentalAcrylicMaterial: FerroObjectImpl);
25
26crate::ferro_properties! { impl ExperimentalAcrylicMaterial {
27 ferro_property!(
28 pub fn tint_color_property() -> StyledProperty<Color> {
30 FerroProperty::register::<ExperimentalAcrylicMaterial, _>("TintColor", Color::default())
31 }
32 );
33
34 ferro_property!(
35 pub fn background_source_property() -> StyledProperty<AcrylicBackgroundSource> {
37 FerroProperty::register::<ExperimentalAcrylicMaterial, _>("BackgroundSource", AcrylicBackgroundSource::None)
38 }
39 );
40
41 ferro_property!(
42 pub fn tint_opacity_property() -> StyledProperty<f64> {
44 FerroProperty::register::<ExperimentalAcrylicMaterial, _>("TintOpacity", 0.8)
45 }
46 );
47
48 ferro_property!(
49 pub fn material_opacity_property() -> StyledProperty<f64> {
51 FerroProperty::register::<ExperimentalAcrylicMaterial, _>("MaterialOpacity", 0.5)
52 }
53 );
54
55 ferro_property!(
56 pub fn platform_transparency_compensation_level_property() -> StyledProperty<f64> {
58 FerroProperty::register::<ExperimentalAcrylicMaterial, _>("PlatformTransparencyCompensationLevel", 0.0)
59 }
60 );
61
62 ferro_property!(
63 pub fn fallback_color_property() -> StyledProperty<Color> {
65 FerroProperty::register::<ExperimentalAcrylicMaterial, _>("FallbackColor", Color::default())
66 }
67 );
68} }
69
70impl ExperimentalAcrylicMaterial {
71 fn static_constructor() {
72 Self::affects_render::<ExperimentalAcrylicMaterial>(&[
73 Self::tint_color_property().as_property(),
74 Self::background_source_property().as_property(),
75 Self::tint_opacity_property().as_property(),
76 Self::material_opacity_property().as_property(),
77 Self::platform_transparency_compensation_level_property().as_property(),
78 ]);
79
80 for property in [
81 Self::tint_color_property().as_property(),
82 Self::tint_opacity_property().as_property(),
83 Self::material_opacity_property().as_property(),
84 Self::platform_transparency_compensation_level_property().as_property(),
85 ] {
86 property.changed().add_class_handler::<ExperimentalAcrylicMaterial>(|b, _| {
87 b.effective_tint_color.set(Self::get_effective_tint_color(b.tint_color(), b.tint_opacity()));
88 b.effective_luminosity_color.set(b.get_effective_luminosity_color());
89 });
90 }
91 }
92
93 pub fn construct() -> Self {
95 Self {
96 base: FerroObject::construct(),
97 effective_tint_color: Cell::new(Color::default()),
98 effective_luminosity_color: Cell::new(Color::default()),
99 invalidated: HandlerList::new(),
100 }
101 }
102
103 pub fn new() -> Ref<Self> {
104 instantiate(Self::construct())
105 }
106
107 pub fn invalidated(&self, handler: impl Fn() + 'static) -> Rc<dyn IDisposable> {
109 let token = self.invalidated.add(Rc::new(handler));
110 let weak = self.to_ref().downgrade();
111 Disposable::create(move || {
112 if let Some(this) = weak.upgrade() {
113 this.invalidated.remove(token);
114 }
115 })
116 }
117
118 pub fn background_source(&self) -> AcrylicBackgroundSource {
120 self.get_value(Self::background_source_property())
121 }
122
123 pub fn set_background_source(&self, value: AcrylicBackgroundSource) {
124 self.set_value(Self::background_source_property(), value)
125 }
126
127 pub fn tint_color(&self) -> Color {
129 self.get_value(Self::tint_color_property())
130 }
131
132 pub fn set_tint_color(&self, value: Color) {
133 self.set_value(Self::tint_color_property(), value)
134 }
135
136 pub fn tint_opacity(&self) -> f64 {
138 self.get_value(Self::tint_opacity_property())
139 }
140
141 pub fn set_tint_opacity(&self, value: f64) {
142 self.set_value(Self::tint_opacity_property(), value)
143 }
144
145 pub fn fallback_color(&self) -> Color {
147 self.get_value(Self::fallback_color_property())
148 }
149
150 pub fn set_fallback_color(&self, value: Color) {
151 self.set_value(Self::fallback_color_property(), value)
152 }
153
154 pub fn material_opacity(&self) -> f64 {
156 self.get_value(Self::material_opacity_property())
157 }
158
159 pub fn set_material_opacity(&self, value: f64) {
160 self.set_value(Self::material_opacity_property(), value)
161 }
162
163 pub fn platform_transparency_compensation_level(&self) -> f64 {
165 self.get_value(Self::platform_transparency_compensation_level_property())
166 }
167
168 pub fn set_platform_transparency_compensation_level(&self, value: f64) {
169 self.set_value(Self::platform_transparency_compensation_level_property(), value)
170 }
171
172 pub(crate) fn effective_luminosity_color(&self) -> Color {
174 self.effective_luminosity_color.get()
175 }
176
177 pub(crate) fn effective_tint_color(&self) -> Color {
179 self.effective_tint_color.get()
180 }
181
182 fn get_effective_tint_color(tint_color: Color, tint_opacity: f64) -> Color {
183 let tint_opacity_modifier = Self::get_tint_opacity_modifier(tint_color);
185
186 Color::new(
187 (255.0 * ((255.0 / tint_color.a as f64) * tint_opacity) * tint_opacity_modifier) as u8,
188 tint_color.r,
189 tint_color.g,
190 tint_color.b,
191 )
192 }
193
194 fn get_tint_opacity_modifier(tint_color: Color) -> f64 {
195 const MID_POINT: f64 = 0.5;
207
208 const WHITE_MAX_OPACITY: f64 = 0.2; const MID_POINT_MAX_OPACITY: f64 = 0.45; const BLACK_MAX_OPACITY: f64 = 0.45; let hsv = tint_color.to_hsv();
213
214 let mut opacity_modifier = MID_POINT_MAX_OPACITY;
215
216 if hsv.v != MID_POINT {
217 let mut lowest_max_opacity = MID_POINT_MAX_OPACITY;
219 let mut max_deviation = MID_POINT;
220
221 if hsv.v > MID_POINT {
222 lowest_max_opacity = WHITE_MAX_OPACITY; max_deviation = 1.0 - max_deviation;
224 } else if hsv.v < MID_POINT {
225 lowest_max_opacity = BLACK_MAX_OPACITY; }
227
228 let mut max_opacity_suppression = MID_POINT_MAX_OPACITY - lowest_max_opacity;
229
230 let deviation = (hsv.v - MID_POINT).abs();
232 let normalized_deviation = deviation / max_deviation;
233
234 if hsv.s > 0.0 {
237 max_opacity_suppression *= f64::max(1.0 - (hsv.s * 2.0), 0.0);
240 }
241
242 let opacity_suppression = max_opacity_suppression * normalized_deviation;
243
244 opacity_modifier = MID_POINT_MAX_OPACITY - opacity_suppression;
245 }
246
247 opacity_modifier
248 }
249
250 fn get_effective_luminosity_color(&self) -> Color {
251 let luminosity_opacity = Some(self.material_opacity());
252
253 self.get_luminosity_color(luminosity_opacity)
254 }
255
256 fn trim(value: f64) -> u8 {
257 let value = f64::min((value * 256.0).floor(), 255.0);
258
259 if value < 0.0 {
260 return 0;
261 } else if value > 255.0 {
262 return 255;
263 }
264
265 value as u8
266 }
267
268 fn rgb_max(color: Color) -> f32 {
269 if color.r > color.g {
270 if color.r > color.b {
271 color.r as f32
272 } else {
273 color.b as f32
274 }
275 } else if color.g > color.b {
276 color.g as f32
277 } else {
278 color.b as f32
279 }
280 }
281
282 fn rgb_min(color: Color) -> f32 {
283 if color.r < color.g {
284 if color.r < color.b {
285 color.r as f32
286 } else {
287 color.b as f32
288 }
289 } else if color.g < color.b {
290 color.g as f32
291 } else {
292 color.b as f32
293 }
294 }
295
296 fn get_luminosity_color(&self, luminosity_opacity: Option<f64>) -> Color {
299 let tint_color = self.tint_color();
300
301 let max = Self::rgb_max(tint_color) / 255.0f32;
303 let min = Self::rgb_min(tint_color) / 255.0f32;
304
305 let mut lightness = ((max + min) as f64) / 2.0;
306
307 lightness = 1.0 - ((1.0 - lightness) * luminosity_opacity.unwrap_or(1.0));
308
309 lightness = 0.13 + (lightness * 0.74);
310
311 let luminosity_color = Color::new(255, Self::trim(lightness), Self::trim(lightness), Self::trim(lightness));
312
313 let compensation_level = self.platform_transparency_compensation_level();
314 let compensation_multiplier = 1.0 - compensation_level;
315 Color::new(
316 (255.0
317 * f64::max(
318 f64::min(compensation_level + (luminosity_opacity.unwrap_or(1.0) * compensation_multiplier), 1.0),
319 0.0,
320 )) as u8,
321 luminosity_color.r,
322 luminosity_color.g,
323 luminosity_color.b,
324 )
325 }
326
327 pub fn affects_render<T: ObjectType + Upcast<ExperimentalAcrylicMaterial>>(properties: &[&'static FerroProperty]) {
331 for property in properties {
332 property.changed().subscribe(|e| {
333 if let Some(sender) = e.sender().downcast_ref::<T>() {
334 let material: &ExperimentalAcrylicMaterial = sender.upcast();
335 material.raise_invalidated();
336 }
337 });
338 }
339 }
340
341 pub fn raise_invalidated(&self) {
343 if self.invalidated.is_empty() {
344 return;
345 }
346 for (_, handler) in self.invalidated.snapshot().iter() {
347 handler();
348 }
349 }
350
351 pub fn to_immutable(&self) -> Rc<dyn IExperimentalAcrylicMaterial> {
353 Rc::new(ImmutableExperimentalAcrylicMaterial::new(&RefAdapter(self.to_ref())))
354 }
355}
356
357#[cfg(test)]
358mod tests {
359 use super::*;
361 use crate::media::{Colors, MaterialExtensions};
362
363 #[test]
364 fn changing_properties_raises_invalidated_and_updates_effective_colors() {
365 let target = ExperimentalAcrylicMaterial::new();
366 let count = Rc::new(Cell::new(0));
367 let c = count.clone();
368 target.invalidated(move || c.set(c.get() + 1));
369
370 target.set_tint_color(Colors::WHITE);
371 assert_eq!(1, count.get());
372 target.set_tint_opacity(1.0);
373 target.set_material_opacity(0.65);
374 target.set_background_source(AcrylicBackgroundSource::Digger);
375 assert_eq!(4, count.get());
376 target.set_fallback_color(Colors::RED);
378 assert_eq!(4, count.get());
379
380 let material: Rc<dyn IExperimentalAcrylicMaterial> = target.clone().into();
381 assert_eq!(Color::new(51, 255, 255, 255), material.tint_color());
383 assert_eq!(Color::new(165, 222, 222, 222), material.material_color());
386 assert_eq!(1.0, material.tint_opacity());
387 assert_eq!(Colors::RED, material.fallback_color());
388 assert_eq!(AcrylicBackgroundSource::Digger, material.background_source());
389
390 let immutable = MaterialExtensions::to_immutable(&material);
391 assert!(immutable.as_mutable_material().is_none());
392 assert_eq!(Color::new(51, 255, 255, 255), immutable.tint_color());
393 let again = MaterialExtensions::to_immutable(&immutable);
394 assert!(*again == *immutable);
395
396 let a = immutable.as_any().downcast_ref::<ImmutableExperimentalAcrylicMaterial>().unwrap();
397 target.set_platform_transparency_compensation_level(1.0);
398 let b = target.to_immutable();
399 let b = b.as_any().downcast_ref::<ImmutableExperimentalAcrylicMaterial>().unwrap();
400 assert_eq!(255, b.material_color().a);
401 assert!(a != b);
402 assert!(a == &a.clone());
403 }
404}