Skip to main content

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_enum_field::SelectEnumField;
22use crate::config::*;
23use crate::session::Session;
24use crate::utils::*;
25use crate::*;
26
27#[derive(PartialEq, Eq, Copy, Clone, Debug)]
28pub enum Side {
29    Fg,
30    Bg,
31}
32
33use Side::*;
34
35/// A `ColumnStyle` component is mounted to the window anchored at the screen
36/// position of `elem`.
37///
38/// It needs two input configs, the current configuration object and a default
39/// version without `Option<>`
40#[derive(Properties)]
41pub struct NumberColumnStyleProps {
42    #[cfg_attr(test, prop_or_default)]
43    pub config: Option<NumberColumnStyleConfig>,
44
45    #[cfg_attr(test, prop_or_default)]
46    pub default_config: NumberColumnStyleDefaultConfig,
47
48    #[prop_or_default]
49    pub on_change: Callback<ColumnConfigValueUpdate>,
50
51    #[prop_or_default]
52    pub weak_link: WeakScope<NumberColumnStyle>,
53
54    #[prop_or_default]
55    pub column_name: Option<String>,
56
57    // State
58    pub session: Session,
59}
60
61impl ModalLink<NumberColumnStyle> for NumberColumnStyleProps {
62    fn weak_link(&self) -> &'_ WeakScope<NumberColumnStyle> {
63        &self.weak_link
64    }
65}
66
67impl PartialEq for NumberColumnStyleProps {
68    fn eq(&self, _other: &Self) -> bool {
69        false
70    }
71}
72
73fn set_default_gradient(session: &Session, ctx: &Context<NumberColumnStyle>) {
74    if let Some(column_name) = ctx.props().column_name.clone() {
75        let session = session.clone();
76        ctx.link().send_future(async move {
77            let view = session.get_view().unwrap();
78            let min_max = view.get_min_max(column_name).await.unwrap();
79            let abs_max = min_max
80                .0
81                .parse::<f64>()
82                .unwrap_or_default()
83                .abs()
84                .max(min_max.1.parse::<f64>().unwrap_or_default().abs());
85
86            let gradient = (abs_max * 100.).round() / 100.;
87            NumberColumnStyleMsg::DefaultGradientChanged(gradient)
88        });
89    }
90}
91
92#[derive(Debug)]
93pub enum NumberColumnStyleMsg {
94    PosColorChanged(Side, String),
95    NegColorChanged(Side, String),
96    NumberForeModeChanged(NumberForegroundMode),
97    NumberBackModeChanged(NumberBackgroundMode),
98    GradientChanged(Side, Option<f64>),
99    DefaultGradientChanged(f64),
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        set_default_gradient(&ctx.props().session, 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
135        set_default_gradient(&ctx.props().session, ctx);
136        std::mem::swap(self, &mut new);
137        true
138    }
139
140    fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
141        match msg {
142            NumberColumnStyleMsg::PosColorChanged(side, val) => {
143                if side == Fg {
144                    self.pos_fg_color = val;
145                    self.config.pos_fg_color = Some(self.pos_fg_color.to_owned());
146                } else {
147                    self.pos_bg_color = val;
148                    self.config.pos_bg_color = Some(self.pos_bg_color.to_owned());
149                }
150
151                self.dispatch_config(ctx);
152                true
153            },
154            NumberColumnStyleMsg::NegColorChanged(side, val) => {
155                if side == Fg {
156                    self.neg_fg_color = val;
157                    self.config.neg_fg_color = Some(self.neg_fg_color.to_owned());
158                } else {
159                    self.neg_bg_color = val;
160                    self.config.neg_bg_color = Some(self.neg_bg_color.to_owned());
161                }
162
163                self.dispatch_config(ctx);
164                true
165            },
166            NumberColumnStyleMsg::NumberForeModeChanged(val) => {
167                self.fg_mode = val;
168                self.config.number_fg_mode = val;
169                if self.fg_mode.needs_gradient() {
170                    self.config.fg_gradient = Some(self.fg_gradient.unwrap());
171                } else {
172                    self.config.fg_gradient = None;
173                }
174
175                self.dispatch_config(ctx);
176                true
177            },
178            NumberColumnStyleMsg::NumberBackModeChanged(val) => {
179                self.bg_mode = val;
180                self.config.number_bg_mode = val;
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
187                self.dispatch_config(ctx);
188                true
189            },
190            NumberColumnStyleMsg::GradientChanged(side, gradient) => {
191                match (side, gradient) {
192                    (Fg, Some(x)) => {
193                        self.fg_gradient = Some(x);
194                        self.config.fg_gradient = Some(x);
195                    },
196                    (Fg, None) => {
197                        self.fg_gradient = Some(self.default_config.fg_gradient);
198                        self.config.fg_gradient = None;
199                    },
200                    (Bg, Some(x)) => {
201                        self.bg_gradient = Some(x);
202                        self.config.bg_gradient = Some(x);
203                    },
204                    (Bg, None) => {
205                        self.bg_gradient = Some(self.default_config.bg_gradient);
206                        self.config.bg_gradient = None;
207                    },
208                };
209
210                self.dispatch_config(ctx);
211                false
212            },
213            NumberColumnStyleMsg::DefaultGradientChanged(gradient) => {
214                self.fg_gradient.get_or_insert(gradient);
215                self.bg_gradient.get_or_insert(gradient);
216                self.default_config.fg_gradient = gradient;
217                self.default_config.bg_gradient = gradient;
218                true
219            },
220        }
221    }
222
223    fn view(&self, ctx: &Context<Self>) -> Html {
224        let fg_mode_changed = ctx.link().callback(|x: Option<_>| {
225            NumberColumnStyleMsg::NumberForeModeChanged(x.unwrap_or_default())
226        });
227
228        let bg_mode_changed = ctx.link().callback(|x: Option<_>| {
229            NumberColumnStyleMsg::NumberBackModeChanged(x.unwrap_or_default())
230        });
231
232        let fg_controls = match self.fg_mode {
233            NumberForegroundMode::Disabled => html! {},
234            NumberForegroundMode::Color => html! {
235                <div class="row">
236                    <ColorRangeSelector ..self.color_props("fg-color", Fg, false, ctx) />
237                </div>
238            },
239            NumberForegroundMode::Bar => html! {
240                <>
241                    <div class="row">
242                        <ColorRangeSelector ..self.color_props("bar-color", Fg, false, ctx) />
243                    </div>
244                    <NumberField ..self.max_value_props(Fg, ctx) />
245                </>
246            },
247        };
248
249        let bg_controls = match self.bg_mode {
250            NumberBackgroundMode::Disabled => html! {},
251            NumberBackgroundMode::Color => html! {
252                <div class="row">
253                    <ColorRangeSelector ..self.color_props("bg-color", Bg,false, ctx) />
254                </div>
255            },
256            NumberBackgroundMode::Gradient => html! {
257                <>
258                    <div class="row">
259                        <ColorRangeSelector ..self.color_props("gradient-color", Bg, true, ctx) />
260                    </div>
261                    <NumberField ..self.max_value_props(Bg, ctx) />
262                </>
263            },
264            NumberBackgroundMode::Pulse => html! {
265                <div class="row">
266                    <ColorRangeSelector ..self.color_props("pulse-color", Bg, true, ctx) />
267                </div>
268            },
269        };
270
271        html! {
272            <>
273                <LocalStyle href={css!("column-style")} />
274                <div id="column-style-container" class="number-column-style-container">
275                    <SelectEnumField<NumberForegroundMode>
276                        label="foreground"
277                        on_change={fg_mode_changed}
278                        current_value={self.fg_mode}
279                    />
280                    { fg_controls }
281                    <SelectEnumField<NumberBackgroundMode>
282                        label="background"
283                        on_change={bg_mode_changed}
284                        current_value={self.bg_mode}
285                    />
286                    { bg_controls }
287                </div>
288            </>
289        }
290    }
291}
292
293impl NumberColumnStyle {
294    /// When this config has changed, we must signal the wrapper element.
295    fn dispatch_config(&self, ctx: &Context<Self>) {
296        let mut config = self.config.clone();
297        match &self.config {
298            NumberColumnStyleConfig {
299                pos_fg_color: Some(pos_color),
300                neg_fg_color: Some(neg_color),
301                ..
302            } if *pos_color == self.default_config.pos_fg_color
303                && *neg_color == self.default_config.neg_fg_color =>
304            {
305                config.pos_fg_color = None;
306                config.neg_fg_color = None;
307            },
308            _ => {},
309        };
310
311        match &self.config {
312            NumberColumnStyleConfig {
313                pos_bg_color: Some(pos_color),
314                neg_bg_color: Some(neg_color),
315                ..
316            } if *pos_color == self.default_config.pos_bg_color
317                && *neg_color == self.default_config.neg_bg_color =>
318            {
319                config.pos_bg_color = None;
320                config.neg_bg_color = None;
321            },
322            _ => {},
323        };
324
325        let update = Some(config).filter(|config| config != &NumberColumnStyleConfig::default());
326
327        ctx.props()
328            .on_change
329            .emit(ColumnConfigValueUpdate::DatagridNumberStyle(update));
330    }
331
332    fn color_props(
333        &self,
334        id: &str,
335        side: Side,
336        is_gradient: bool,
337        ctx: &Context<Self>,
338    ) -> ColorRangeProps {
339        let on_pos_color = ctx
340            .link()
341            .callback(move |x| NumberColumnStyleMsg::PosColorChanged(side, x));
342        let on_neg_color = ctx
343            .link()
344            .callback(move |x| NumberColumnStyleMsg::NegColorChanged(side, x));
345
346        let default_config = self.default_config.clone();
347
348        props!(ColorRangeProps {
349            id: id.to_string(),
350            is_gradient,
351            pos_color: if side == Fg {
352                &self.pos_fg_color
353            } else {
354                &self.pos_bg_color
355            }
356            .to_owned(),
357            neg_color: if side == Fg {
358                &self.neg_fg_color
359            } else {
360                &self.neg_bg_color
361            }
362            .to_owned(),
363            on_pos_color,
364            on_neg_color,
365            on_reset: ctx.link().batch_callback(move |_| if side == Fg {
366                vec![
367                    NumberColumnStyleMsg::PosColorChanged(
368                        side,
369                        default_config.pos_fg_color.clone(),
370                    ),
371                    NumberColumnStyleMsg::NegColorChanged(
372                        side,
373                        default_config.neg_fg_color.clone(),
374                    ),
375                ]
376            } else {
377                vec![
378                    NumberColumnStyleMsg::PosColorChanged(
379                        side,
380                        default_config.pos_bg_color.clone(),
381                    ),
382                    NumberColumnStyleMsg::NegColorChanged(
383                        side,
384                        default_config.neg_bg_color.clone(),
385                    ),
386                ]
387            }),
388            is_modified: if side == Fg {
389                self.pos_fg_color != self.default_config.pos_fg_color
390                    || self.neg_fg_color != self.default_config.neg_fg_color
391            } else {
392                self.pos_bg_color != self.default_config.pos_bg_color
393                    || self.neg_bg_color != self.default_config.neg_bg_color
394            },
395        })
396    }
397
398    fn max_value_props(&self, side: Side, ctx: &Context<Self>) -> NumberFieldProps {
399        let on_change = ctx
400            .link()
401            .callback(move |x| NumberColumnStyleMsg::GradientChanged(side, x));
402
403        let value = if side == Fg {
404            self.fg_gradient.unwrap_or_default()
405        } else {
406            self.bg_gradient.unwrap_or_default()
407        };
408
409        props!(NumberFieldProps {
410            default: value,
411            current_value: value,
412            label: "max-value",
413            on_change
414        })
415    }
416
417    fn reset(
418        config: &NumberColumnStyleConfig,
419        default_config: &NumberColumnStyleDefaultConfig,
420    ) -> Self {
421        let mut config = config.clone();
422        let fg_gradient = config.fg_gradient;
423        let bg_gradient = config.bg_gradient;
424
425        let pos_fg_color = config
426            .pos_fg_color
427            .as_ref()
428            .unwrap_or(&default_config.pos_fg_color)
429            .to_owned();
430
431        let neg_fg_color = config
432            .neg_fg_color
433            .as_ref()
434            .unwrap_or(&default_config.neg_fg_color)
435            .to_owned();
436
437        let pos_bg_color = config
438            .pos_bg_color
439            .as_ref()
440            .unwrap_or(&default_config.pos_bg_color)
441            .to_owned();
442
443        let neg_bg_color = config
444            .neg_bg_color
445            .as_ref()
446            .unwrap_or(&default_config.neg_bg_color)
447            .to_owned();
448
449        config.pos_fg_color = Some(pos_fg_color.to_owned());
450        config.neg_fg_color = Some(neg_fg_color.to_owned());
451        let fg_mode = config.number_fg_mode;
452        config.pos_bg_color = Some(pos_bg_color.to_owned());
453        config.neg_bg_color = Some(neg_bg_color.to_owned());
454        let bg_mode = config.number_bg_mode;
455        Self {
456            config,
457            default_config: default_config.clone(),
458            fg_mode,
459            bg_mode,
460            pos_fg_color,
461            neg_fg_color,
462            pos_bg_color,
463            neg_bg_color,
464            fg_gradient,
465            bg_gradient,
466        }
467    }
468}