perspective_viewer/components/
number_column_style.rs

1// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
2// ┃ ██████ ██████ ██████       █      █      █      █      █ █▄  ▀███ █       ┃
3// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█  ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄  ▀█ █ ▀▀▀▀▀ ┃
4// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄   █ ▄▄▄▄▄ ┃
5// ┃ █      ██████ █  ▀█▄       █ ██████      █      ███▌▐███ ███████▄ █       ┃
6// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
7// ┃ Copyright (c) 2017, the Perspective Authors.                              ┃
8// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
9// ┃ This file is part of the Perspective library, distributed under the terms ┃
10// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
13use yew::prelude::*;
14use yew::*;
15
16use super::form::color_range_selector::*;
17use super::form::number_field::NumberFieldProps;
18use super::modal::*;
19use super::style::LocalStyle;
20use crate::components::form::number_field::NumberField;
21use crate::components::form::select_field::SelectEnumField;
22use crate::config::*;
23use crate::session::Session;
24use crate::utils::WeakScope;
25use crate::*;
26
27#[derive(PartialEq, Eq, Copy, Clone, Debug)]
28pub enum Side {
29    Fg,
30    Bg,
31}
32
33use Side::*;
34
35#[derive(Debug)]
36pub enum NumberColumnStyleMsg {
37    PosColorChanged(Side, String),
38    NegColorChanged(Side, String),
39    NumberForeModeChanged(NumberForegroundMode),
40    NumberBackModeChanged(NumberBackgroundMode),
41    GradientChanged(Side, Option<f64>),
42    DefaultGradientChanged(f64),
43}
44
45/// A `ColumnStyle` component is mounted to the window anchored at the screen
46/// position of `elem`.
47///
48/// It needs two input configs, the current configuration object and a default
49/// version without `Option<>`
50#[derive(Properties)]
51pub struct NumberColumnStyleProps {
52    #[cfg_attr(test, prop_or_default)]
53    pub config: Option<NumberColumnStyleConfig>,
54
55    #[cfg_attr(test, prop_or_default)]
56    pub default_config: NumberColumnStyleDefaultConfig,
57
58    #[prop_or_default]
59    pub on_change: Callback<ColumnConfigValueUpdate>,
60
61    #[prop_or_default]
62    pub weak_link: WeakScope<NumberColumnStyle>,
63
64    #[prop_or_default]
65    pub session: Option<Session>,
66
67    #[prop_or_default]
68    pub column_name: Option<String>,
69}
70
71impl ModalLink<NumberColumnStyle> for NumberColumnStyleProps {
72    fn weak_link(&self) -> &'_ WeakScope<NumberColumnStyle> {
73        &self.weak_link
74    }
75}
76
77impl PartialEq for NumberColumnStyleProps {
78    fn eq(&self, _other: &Self) -> bool {
79        false
80    }
81}
82
83impl NumberColumnStyleProps {
84    fn set_default_gradient(&self, ctx: &Context<NumberColumnStyle>) {
85        if let Some(session) = self.session.clone()
86            && let Some(column_name) = self.column_name.clone()
87        {
88            ctx.link().send_future(async move {
89                let view = session.get_view().unwrap();
90                let min_max = view.get_min_max(column_name).await.unwrap();
91                let abs_max = max!(
92                    min_max.0.parse::<f64>().unwrap_or_default().abs(),
93                    min_max.1.parse::<f64>().unwrap_or_default().abs()
94                );
95                let gradient = (abs_max * 100.).round() / 100.;
96                NumberColumnStyleMsg::DefaultGradientChanged(gradient)
97            });
98        }
99    }
100}
101
102/// A column style form control for `number` columns.
103pub struct NumberColumnStyle {
104    config: NumberColumnStyleConfig,
105    default_config: NumberColumnStyleDefaultConfig,
106    fg_mode: NumberForegroundMode,
107    bg_mode: NumberBackgroundMode,
108    pos_fg_color: String,
109    neg_fg_color: String,
110    pos_bg_color: String,
111    neg_bg_color: String,
112    fg_gradient: Option<f64>,
113    bg_gradient: Option<f64>,
114}
115
116impl Component for NumberColumnStyle {
117    type Message = NumberColumnStyleMsg;
118    type Properties = NumberColumnStyleProps;
119
120    fn create(ctx: &Context<Self>) -> Self {
121        ctx.set_modal_link();
122        ctx.props().set_default_gradient(ctx);
123        Self::reset(
124            &ctx.props().config.clone().unwrap_or_default(),
125            &ctx.props().default_config.clone(),
126        )
127    }
128
129    fn changed(&mut self, ctx: &Context<Self>, _old: &Self::Properties) -> bool {
130        let mut new = Self::reset(
131            &ctx.props().config.clone().unwrap_or_default(),
132            &ctx.props().default_config.clone(),
133        );
134        ctx.props().set_default_gradient(ctx);
135        std::mem::swap(self, &mut new);
136        true
137    }
138
139    fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
140        match msg {
141            // NumberColumnStyleMsg::Reset(config, default_config) => {
142            //     let mut new = Self::reset(&config, &default_config);
143            //     std::mem::swap(self, &mut new);
144            //     true
145            // },
146            // NumberColumnStyleMsg::ForeEnabledChanged(val) => {
147            //     if val {
148            //         let color_mode = match self.fg_mode {
149            //             NumberForegroundMode::Disabled => NumberForegroundMode::default(),
150            //             x => x,
151            //         };
152
153            //         self.config.number_fg_mode = color_mode;
154            //         self.config.pos_fg_color = Some(self.pos_fg_color.to_owned());
155            //         self.config.neg_fg_color = Some(self.neg_fg_color.to_owned());
156            //         if self.fg_mode.needs_gradient() {
157            //             self.config.fg_gradient = Some(self.fg_gradient.unwrap());
158            //         } else {
159            //             self.config.fg_gradient = None;
160            //         }
161            //     } else {
162            //         self.config.number_fg_mode = NumberForegroundMode::Disabled;
163            //         self.config.pos_fg_color = None;
164            //         self.config.neg_fg_color = None;
165            //         self.config.fg_gradient = None;
166            //     }
167
168            //     self.dispatch_config(ctx);
169            //     true
170            // },
171            // NumberColumnStyleMsg::BackEnabledChanged(val) => {
172            //     if val {
173            //         let color_mode = match self.bg_mode {
174            //             NumberBackgroundMode::Disabled => NumberBackgroundMode::Color,
175            //             x => x,
176            //         };
177
178            //         self.config.number_bg_mode = color_mode;
179            //         self.config.pos_bg_color = Some(self.pos_bg_color.to_owned());
180            //         self.config.neg_bg_color = Some(self.neg_bg_color.to_owned());
181            //         if self.bg_mode.needs_gradient() {
182            //             self.config.bg_gradient = Some(self.bg_gradient.unwrap());
183            //         } else {
184            //             self.config.bg_gradient = None;
185            //         }
186            //     } else {
187            //         self.config.number_bg_mode = NumberBackgroundMode::Disabled;
188            //         self.config.pos_bg_color = None;
189            //         self.config.neg_bg_color = None;
190            //         self.config.bg_gradient = None;
191            //     }
192
193            //     self.dispatch_config(ctx);
194            //     true
195            // },
196            NumberColumnStyleMsg::PosColorChanged(side, val) => {
197                if side == Fg {
198                    self.pos_fg_color = val;
199                    self.config.pos_fg_color = Some(self.pos_fg_color.to_owned());
200                } else {
201                    self.pos_bg_color = val;
202                    self.config.pos_bg_color = Some(self.pos_bg_color.to_owned());
203                }
204
205                self.dispatch_config(ctx);
206                true
207            },
208            NumberColumnStyleMsg::NegColorChanged(side, val) => {
209                if side == Fg {
210                    self.neg_fg_color = val;
211                    self.config.neg_fg_color = Some(self.neg_fg_color.to_owned());
212                } else {
213                    self.neg_bg_color = val;
214                    self.config.neg_bg_color = Some(self.neg_bg_color.to_owned());
215                }
216
217                self.dispatch_config(ctx);
218                true
219            },
220            NumberColumnStyleMsg::NumberForeModeChanged(val) => {
221                self.fg_mode = val;
222                self.config.number_fg_mode = val;
223                if self.fg_mode.needs_gradient() {
224                    self.config.fg_gradient = Some(self.fg_gradient.unwrap());
225                } else {
226                    self.config.fg_gradient = None;
227                }
228
229                self.dispatch_config(ctx);
230                true
231            },
232            NumberColumnStyleMsg::NumberBackModeChanged(val) => {
233                self.bg_mode = val;
234                self.config.number_bg_mode = val;
235                if self.bg_mode.needs_gradient() {
236                    self.config.bg_gradient = Some(self.bg_gradient.unwrap());
237                } else {
238                    self.config.bg_gradient = None;
239                }
240
241                self.dispatch_config(ctx);
242                true
243            },
244            NumberColumnStyleMsg::GradientChanged(side, gradient) => {
245                match (side, gradient) {
246                    (Fg, Some(x)) => {
247                        self.fg_gradient = Some(x);
248                        self.config.fg_gradient = Some(x);
249                    },
250                    (Fg, None) => {
251                        self.fg_gradient = Some(self.default_config.fg_gradient);
252                        self.config.fg_gradient = None;
253                    },
254                    (Bg, Some(x)) => {
255                        self.bg_gradient = Some(x);
256                        self.config.bg_gradient = Some(x);
257                    },
258                    (Bg, None) => {
259                        self.bg_gradient = Some(self.default_config.bg_gradient);
260                        self.config.bg_gradient = None;
261                    },
262                };
263
264                self.dispatch_config(ctx);
265                false
266            },
267            NumberColumnStyleMsg::DefaultGradientChanged(gradient) => {
268                self.fg_gradient.get_or_insert(gradient);
269                self.bg_gradient.get_or_insert(gradient);
270                self.default_config.fg_gradient = gradient;
271                self.default_config.bg_gradient = gradient;
272                true
273            },
274        }
275    }
276
277    fn view(&self, ctx: &Context<Self>) -> Html {
278        let fg_mode_changed = ctx.link().callback(|x: Option<_>| {
279            NumberColumnStyleMsg::NumberForeModeChanged(x.unwrap_or_default())
280        });
281
282        let bg_mode_changed = ctx.link().callback(|x: Option<_>| {
283            NumberColumnStyleMsg::NumberBackModeChanged(x.unwrap_or_default())
284        });
285
286        let fg_controls = match self.fg_mode {
287            NumberForegroundMode::Disabled => html! {},
288            NumberForegroundMode::Color => html! {
289                <div class="row">
290                    <ColorRangeSelector ..self.color_props("fg-color", Fg, false, ctx) />
291                </div>
292            },
293            NumberForegroundMode::Bar => html! {
294                <>
295                    <div class="row">
296                        <ColorRangeSelector ..self.color_props("bar-color", Fg, false, ctx) />
297                    </div>
298                    <NumberField ..self.max_value_props(Fg, ctx) />
299                </>
300            },
301        };
302
303        let bg_controls = match self.bg_mode {
304            NumberBackgroundMode::Disabled => html! {},
305            NumberBackgroundMode::Color => html! {
306                <div class="row">
307                    <ColorRangeSelector ..self.color_props("bg-color", Bg,false, ctx) />
308                </div>
309            },
310            NumberBackgroundMode::Gradient => html! {
311                <>
312                    <div class="row">
313                        <ColorRangeSelector ..self.color_props("gradient-color", Bg, true, ctx) />
314                    </div>
315                    <NumberField ..self.max_value_props(Bg, ctx) />
316                </>
317            },
318            NumberBackgroundMode::Pulse => html! {
319                <div class="row">
320                    <ColorRangeSelector ..self.color_props("pulse-color", Bg, true, ctx) />
321                </div>
322            },
323        };
324
325        html! {
326            <>
327                <LocalStyle href={css!("column-style")} />
328                <div id="column-style-container" class="number-column-style-container">
329                    <SelectEnumField<NumberForegroundMode>
330                        label="foreground"
331                        on_change={fg_mode_changed}
332                        current_value={self.fg_mode}
333                    />
334                    { fg_controls }
335                    <SelectEnumField<NumberBackgroundMode>
336                        label="background"
337                        on_change={bg_mode_changed}
338                        current_value={self.bg_mode}
339                    />
340                    { bg_controls }
341                </div>
342            </>
343        }
344    }
345}
346
347impl NumberColumnStyle {
348    /// When this config has changed, we must signal the wrapper element.
349    fn dispatch_config(&self, ctx: &Context<Self>) {
350        let mut config = self.config.clone();
351        match &self.config {
352            NumberColumnStyleConfig {
353                pos_fg_color: Some(pos_color),
354                neg_fg_color: Some(neg_color),
355                ..
356            } if *pos_color == self.default_config.pos_fg_color
357                && *neg_color == self.default_config.neg_fg_color =>
358            {
359                config.pos_fg_color = None;
360                config.neg_fg_color = None;
361            },
362            _ => {},
363        };
364
365        match &self.config {
366            NumberColumnStyleConfig {
367                pos_bg_color: Some(pos_color),
368                neg_bg_color: Some(neg_color),
369                ..
370            } if *pos_color == self.default_config.pos_bg_color
371                && *neg_color == self.default_config.neg_bg_color =>
372            {
373                config.pos_bg_color = None;
374                config.neg_bg_color = None;
375            },
376            _ => {},
377        };
378
379        let update = Some(config).filter(|config| config != &NumberColumnStyleConfig::default());
380
381        ctx.props()
382            .on_change
383            .emit(ColumnConfigValueUpdate::DatagridNumberStyle(update));
384    }
385
386    fn color_props(
387        &self,
388        id: &str,
389        side: Side,
390        is_gradient: bool,
391        ctx: &Context<Self>,
392    ) -> ColorRangeProps {
393        let on_pos_color = ctx
394            .link()
395            .callback(move |x| NumberColumnStyleMsg::PosColorChanged(side, x));
396        let on_neg_color = ctx
397            .link()
398            .callback(move |x| NumberColumnStyleMsg::NegColorChanged(side, x));
399
400        let default_config = self.default_config.clone();
401
402        props!(ColorRangeProps {
403            id: id.to_string(),
404            is_gradient,
405            pos_color: if side == Fg {
406                &self.pos_fg_color
407            } else {
408                &self.pos_bg_color
409            }
410            .to_owned(),
411            neg_color: if side == Fg {
412                &self.neg_fg_color
413            } else {
414                &self.neg_bg_color
415            }
416            .to_owned(),
417            on_pos_color,
418            on_neg_color,
419            on_reset: ctx.link().batch_callback(move |_| if side == Fg {
420                vec![
421                    NumberColumnStyleMsg::PosColorChanged(
422                        side,
423                        default_config.pos_fg_color.clone(),
424                    ),
425                    NumberColumnStyleMsg::NegColorChanged(
426                        side,
427                        default_config.neg_fg_color.clone(),
428                    ),
429                ]
430            } else {
431                vec![
432                    NumberColumnStyleMsg::PosColorChanged(
433                        side,
434                        default_config.pos_bg_color.clone(),
435                    ),
436                    NumberColumnStyleMsg::NegColorChanged(
437                        side,
438                        default_config.neg_bg_color.clone(),
439                    ),
440                ]
441            }),
442            is_modified: if side == Fg {
443                self.pos_fg_color != self.default_config.pos_fg_color
444                    || self.neg_fg_color != self.default_config.neg_fg_color
445            } else {
446                self.pos_bg_color != self.default_config.pos_bg_color
447                    || self.neg_bg_color != self.default_config.neg_bg_color
448            },
449        })
450    }
451
452    fn max_value_props(&self, side: Side, ctx: &Context<Self>) -> NumberFieldProps {
453        let on_change = ctx
454            .link()
455            .callback(move |x| NumberColumnStyleMsg::GradientChanged(side, x));
456
457        let value = if side == Fg {
458            self.fg_gradient.unwrap_or_default()
459        } else {
460            self.bg_gradient.unwrap_or_default()
461        };
462
463        props!(NumberFieldProps {
464            default: value,
465            current_value: value,
466            label: "max-value",
467            on_change
468        })
469    }
470
471    fn reset(
472        config: &NumberColumnStyleConfig,
473        default_config: &NumberColumnStyleDefaultConfig,
474    ) -> Self {
475        let mut config = config.clone();
476        let fg_gradient = config.fg_gradient;
477        let bg_gradient = config.bg_gradient;
478
479        let pos_fg_color = config
480            .pos_fg_color
481            .as_ref()
482            .unwrap_or(&default_config.pos_fg_color)
483            .to_owned();
484
485        let neg_fg_color = config
486            .neg_fg_color
487            .as_ref()
488            .unwrap_or(&default_config.neg_fg_color)
489            .to_owned();
490
491        let pos_bg_color = config
492            .pos_bg_color
493            .as_ref()
494            .unwrap_or(&default_config.pos_bg_color)
495            .to_owned();
496
497        let neg_bg_color = config
498            .neg_bg_color
499            .as_ref()
500            .unwrap_or(&default_config.neg_bg_color)
501            .to_owned();
502
503        config.pos_fg_color = Some(pos_fg_color.to_owned());
504        config.neg_fg_color = Some(neg_fg_color.to_owned());
505        let fg_mode = config.number_fg_mode;
506        config.pos_bg_color = Some(pos_bg_color.to_owned());
507        config.neg_bg_color = Some(neg_bg_color.to_owned());
508        let bg_mode = config.number_bg_mode;
509        Self {
510            config,
511            default_config: default_config.clone(),
512            fg_mode,
513            bg_mode,
514            pos_fg_color,
515            neg_fg_color,
516            pos_bg_color,
517            neg_bg_color,
518            fg_gradient,
519            bg_gradient,
520        }
521    }
522}