perspective_viewer/components/
number_column_style.rs1use 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#[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 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
102pub 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 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}