Skip to main content

rat_widget_extra/
color_input.rs

1//! Widget for color input.
2//!
3//! Currently, supports
4//! * RGB
5//! * HSV
6//! * hexdigits
7//!
8//! __Keybindings__
9//!
10//! * Switch between color-mode with Up/Down. (or m/M)
11//! * '+' and Alt-'+' increase the value
12//! * '-' and Alt-'-' decrease the value
13//! * 'r', 'h', 'x' switch mode
14//!
15//! __Clipboard__
16//!
17//! Recognizes common formats when pasted from the clipboard.
18//! * #000000 and #00000000
19//! * 0x000000 and 0x00000000
20//! * 000000 and 00000000
21//!
22
23use crate::_private::NonExhaustive;
24use palette::{FromColor, Hsv, Srgb};
25use rat_event::{HandleEvent, MouseOnly, Regular, ct_event, flow};
26use rat_focus::{FocusBuilder, FocusFlag, HasFocus};
27use rat_reloc::RelocatableState;
28use rat_text::clipboard::Clipboard;
29use rat_text::event::{ReadOnly, TextOutcome};
30use rat_text::text_input_mask::{MaskedInput, MaskedInputState};
31use rat_text::{
32    TextError, TextFocusGained, TextFocusLost, TextStyle, TextTab, derive_text_widget_state,
33    upos_type,
34};
35use ratatui_core::buffer::Buffer;
36use ratatui_core::layout::Rect;
37use ratatui_core::style::{Color, Style};
38use ratatui_core::text::Line;
39use ratatui_core::widgets::{StatefulWidget, Widget};
40use ratatui_crossterm::crossterm::event::Event;
41use ratatui_widgets::block::{Block, BlockExt};
42use std::cmp::min;
43use std::ops::Range;
44
45/// Color input widget.
46///
47/// A text input for colors.
48///
49#[derive(Debug, Clone)]
50pub struct ColorInput<'a> {
51    style: Style,
52    block: Option<Block<'a>>,
53
54    disable_modes: bool,
55    mode: Option<Mode>,
56
57    widget: MaskedInput<'a>,
58}
59
60/// Combined styles.
61#[derive(Debug)]
62pub struct ColorInputStyle {
63    /// Base style.
64    pub text: TextStyle,
65    /// Highlighting the field of the input.
66    pub field_style: Option<Style>,
67    /// Disable mode switching.
68    pub disable_modes: Option<bool>,
69    /// Define default mode.
70    pub mode: Option<Mode>,
71    /// ...
72    pub non_exhaustive: NonExhaustive,
73}
74
75impl Default for ColorInputStyle {
76    fn default() -> Self {
77        Self {
78            text: Default::default(),
79            field_style: Default::default(),
80            disable_modes: Default::default(),
81            mode: Default::default(),
82            non_exhaustive: NonExhaustive,
83        }
84    }
85}
86
87/// Color mode.
88#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
89pub enum Mode {
90    #[default]
91    RGB,
92    HEX,
93    HSV,
94}
95
96/// State for the color input.
97#[derive(Debug, Clone)]
98pub struct ColorInputState {
99    /// Area of the widget.
100    /// __read only__ renewed with each render.
101    pub area: Rect,
102    /// Area inside the block.
103    pub inner: Rect,
104    /// Area for the mode.
105    /// __read_only__ renewed with each render.
106    pub mode_area: Rect,
107    /// Area for the mode label.
108    /// __read_only__ renewed with each render.
109    pub label_area: Rect,
110
111    /// value as RGB with 0.0..1.0 ranges.
112    value: (f32, f32, f32),
113
114    /// Disable keys for mode switching.
115    /// __read only__
116    disable_modes: bool,
117    /// __read only__
118    mode: Mode,
119    /// __read only__
120    pub widget: MaskedInputState,
121
122    pub non_exhaustive: NonExhaustive,
123}
124
125impl<'a> Default for ColorInput<'a> {
126    fn default() -> Self {
127        let mut z = Self {
128            style: Default::default(),
129            disable_modes: Default::default(),
130            mode: Default::default(),
131            block: Default::default(),
132            widget: MaskedInput::default(),
133        };
134        z.widget = z.widget.on_focus_lost(TextFocusLost::Position0);
135        z
136    }
137}
138
139// derive_text_widget!(ColorInput<'a>);
140
141impl<'a> ColorInput<'a> {
142    pub fn new() -> Self {
143        Self::default()
144    }
145
146    /// Set the combined style.
147    #[inline]
148    pub fn styles(mut self, mut style: ColorInputStyle) -> Self {
149        self.style = style.text.style;
150        if let Some(block) = style.text.block.take() {
151            self.block = Some(block);
152        }
153        if let Some(border_style) = style.text.border_style {
154            self.block = self.block.map(|v| v.style(border_style));
155        }
156        if let Some(title_style) = style.text.title_style {
157            self.block = self.block.map(|v| v.style(title_style));
158        }
159        self.block = self.block.map(|v| v.style(self.style));
160        self.widget = self.widget.styles(style.text);
161        if let Some(disable_modes) = style.disable_modes {
162            self.disable_modes = disable_modes;
163        }
164        if let Some(mode) = style.mode {
165            self.mode = Some(mode);
166        }
167        if let Some(field_style) = style.field_style {
168            self.widget = self.widget.text_style_idx(1, field_style);
169        }
170        self
171    }
172
173    /// Base text style.
174    #[inline]
175    pub fn style(mut self, style: impl Into<Style>) -> Self {
176        let style = style.into();
177        self.style = style;
178        self.block = self.block.map(|v| v.style(style));
179        self.widget = self.widget.style(style);
180        self
181    }
182
183    /// Style for the fields of the input.
184    #[inline]
185    pub fn field_style(mut self, style: impl Into<Style>) -> Self {
186        self.widget = self.widget.text_style_idx(1, style.into());
187        self
188    }
189
190    /// Disable switching the mode.
191    #[inline]
192    pub fn disable_modes(mut self) -> Self {
193        self.disable_modes = true;
194        self
195    }
196
197    /// Color mode.
198    #[inline]
199    pub fn mode(mut self, mode: Mode) -> Self {
200        self.mode = Some(mode);
201        self
202    }
203
204    /// Style when focused.
205    #[inline]
206    pub fn focus_style(mut self, style: impl Into<Style>) -> Self {
207        self.widget = self.widget.focus_style(style);
208        self
209    }
210
211    /// Style for selection
212    #[inline]
213    pub fn select_style(mut self, style: impl Into<Style>) -> Self {
214        self.widget = self.widget.select_style(style);
215        self
216    }
217
218    /// Style for the invalid indicator.
219    #[inline]
220    pub fn invalid_style(mut self, style: impl Into<Style>) -> Self {
221        self.widget = self.widget.invalid_style(style);
222        self
223    }
224
225    /// Block
226    #[inline]
227    pub fn block(mut self, block: Block<'a>) -> Self {
228        self.block = Some(block);
229        self
230    }
231
232    /// Focus behaviour
233    #[inline]
234    pub fn on_focus_gained(mut self, of: TextFocusGained) -> Self {
235        self.widget = self.widget.on_focus_gained(of);
236        self
237    }
238
239    /// Focus behaviour
240    #[inline]
241    pub fn on_focus_lost(mut self, of: TextFocusLost) -> Self {
242        self.widget = self.widget.on_focus_lost(of);
243        self
244    }
245
246    /// `Tab` behaviour
247    #[inline]
248    pub fn on_tab(mut self, of: TextTab) -> Self {
249        self.widget = self.widget.on_tab(of);
250        self
251    }
252
253    /// Preferred width
254    pub fn width(&self) -> u16 {
255        16
256    }
257
258    /// Preferred height
259    pub fn height(&self) -> u16 {
260        1
261    }
262}
263
264impl<'a> StatefulWidget for &ColorInput<'a> {
265    type State = ColorInputState;
266
267    fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
268        render(self, area, buf, state);
269    }
270}
271
272impl StatefulWidget for ColorInput<'_> {
273    type State = ColorInputState;
274
275    fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
276        render(&self, area, buf, state);
277    }
278}
279
280fn render(widget: &ColorInput<'_>, area: Rect, buf: &mut Buffer, state: &mut ColorInputState) {
281    state.disable_modes = widget.disable_modes;
282    if let Some(mode) = widget.mode {
283        state.mode = mode;
284    }
285
286    let inner = widget.block.inner_if_some(area);
287
288    let mode_area = Rect::new(inner.x, inner.y, 4, inner.height);
289    let mode_label = Rect::new(mode_area.x, mode_area.y + mode_area.height / 2, 4, 1);
290    let widget_area = Rect::new(
291        inner.x + mode_area.width,
292        inner.y,
293        inner.width.saturating_sub(mode_area.width),
294        inner.height,
295    );
296
297    state.area = area;
298    state.inner = inner;
299    state.mode_area = mode_area;
300    state.label_area = mode_label;
301
302    let bg = state.value();
303    let fg_colors = [Color::Black, Color::White];
304    let style = high_contrast_color(bg, &fg_colors);
305
306    widget.block.clone().render(area, buf);
307
308    buf.set_style(mode_area, style);
309    let mode_str = match state.mode {
310        Mode::RGB => "RGB",
311        Mode::HEX => "  #",
312        Mode::HSV => "HSV",
313    };
314    Line::from(mode_str).render(mode_label, buf);
315
316    (&widget.widget).render(widget_area, buf, &mut state.widget);
317}
318
319derive_text_widget_state!( BASE ColorInputState );
320// derive_text_widget_state!( CLIPBOARD ColorInputState );
321derive_text_widget_state!( UNDO ColorInputState );
322derive_text_widget_state!( STYLE ColorInputState );
323derive_text_widget_state!( OFFSET ColorInputState );
324// derive_text_widget_state!( EDIT ColorInputState );
325// derive_text_widget_state!( FOCUS ColorInputState );
326derive_text_widget_state!( SCREENCURSOR ColorInputState );
327// derive_text_widget_state!( RELOCATE ColorInputState );
328
329impl Default for ColorInputState {
330    fn default() -> Self {
331        let mut z = Self {
332            area: Default::default(),
333            inner: Default::default(),
334            mode_area: Default::default(),
335            label_area: Default::default(),
336            value: Default::default(),
337            disable_modes: Default::default(),
338            mode: Default::default(),
339            widget: Default::default(),
340            non_exhaustive: NonExhaustive,
341        };
342        z.set_mode(Mode::RGB);
343        z
344    }
345}
346
347impl HasFocus for ColorInputState {
348    fn build(&self, builder: &mut FocusBuilder) {
349        builder.leaf_with_flags(
350            self.widget.focus(),
351            self.area,
352            self.widget.area_z(),
353            self.widget.navigable(),
354        );
355    }
356
357    fn focus(&self) -> FocusFlag {
358        self.widget.focus()
359    }
360
361    fn area(&self) -> Rect {
362        self.area
363    }
364}
365
366impl ColorInputState {
367    pub fn new() -> Self {
368        Self::default()
369    }
370
371    pub fn named(name: &str) -> Self {
372        let mut z = Self::default();
373        z.widget.focus = z.widget.focus.with_name(name);
374        z
375    }
376}
377
378impl ColorInputState {
379    /// Clipboard used.
380    /// Default is to use the [global_clipboard](rat_text::clipboard::global_clipboard).
381    #[inline]
382    pub fn set_clipboard(&mut self, clip: Option<impl Clipboard + 'static>) {
383        self.widget.set_clipboard(clip);
384    }
385
386    /// Clipboard used.
387    /// Default is to use the [global_clipboard](rat_text::clipboard::global_clipboard).
388    #[inline]
389    pub fn clipboard(&self) -> Option<&dyn Clipboard> {
390        self.widget.clipboard()
391    }
392
393    /// Copy to clipboard
394    #[inline]
395    pub fn copy_to_clip(&mut self) -> bool {
396        let Some(clip) = self.widget.value.clipboard() else {
397            return false;
398        };
399
400        if self.has_selection() {
401            _ = clip.set_string(self.selected_text().as_ref());
402        } else {
403            let r = (self.value.0 * 255f32) as u32;
404            let g = (self.value.1 * 255f32) as u32;
405            let b = (self.value.2 * 255f32) as u32;
406            let value_str = format!("{:06x}", (r << 16) + (g << 8) + b);
407            _ = clip.set_string(&value_str);
408        }
409        true
410    }
411
412    /// Cut to clipboard
413    #[inline]
414    pub fn cut_to_clip(&mut self) -> bool {
415        let Some(clip) = self.widget.value.clipboard() else {
416            return false;
417        };
418
419        if self.has_selection() {
420            match clip.set_string(self.selected_text().as_ref()) {
421                Ok(_) => self.delete_range(self.selection()),
422                Err(_) => false,
423            }
424        } else {
425            let r = (self.value.0 * 255f32) as u32;
426            let g = (self.value.1 * 255f32) as u32;
427            let b = (self.value.2 * 255f32) as u32;
428            let value_str = format!("{:06x}", (r << 16) + (g << 8) + b);
429
430            if clip.set_string(&value_str).is_ok() {
431                self.clear()
432            }
433            true
434        }
435    }
436
437    /// Paste from clipboard.
438    #[inline]
439    pub fn paste_from_clip(&mut self) -> bool {
440        let Some(clip) = self.widget.value.clipboard() else {
441            return false;
442        };
443
444        if let Ok(text) = clip.get_string() {
445            if text.starts_with("#") && text.len() == 7 {
446                // #aabbcc
447                if let Ok(v) = u32::from_str_radix(&text[1..7], 16) {
448                    self.set_value_u32(v);
449                }
450            } else if text.starts_with("#") && text.len() == 9 {
451                // #aabbccdd
452                if let Ok(v) = u32::from_str_radix(&text[1..7], 16) {
453                    self.set_value_u32(v);
454                }
455            } else if text.starts_with("0x") && text.len() == 8 {
456                // 0xaabbcc
457                if let Ok(v) = u32::from_str_radix(&text[2..8], 16) {
458                    self.set_value_u32(v);
459                }
460            } else if text.starts_with("0x") && text.len() == 10 {
461                // 0xaabbccdd
462                if let Ok(v) = u32::from_str_radix(&text[2..8], 16) {
463                    self.set_value_u32(v);
464                }
465            } else if text.len() == 6 {
466                // aabbcc
467                if let Ok(v) = u32::from_str_radix(&text[0..6], 16) {
468                    self.set_value_u32(v);
469                }
470            } else if text.len() == 8 {
471                // aabbccdd
472                if let Ok(v) = u32::from_str_radix(&text[0..6], 16) {
473                    self.set_value_u32(v);
474                }
475            } else {
476                for c in text.chars() {
477                    self.widget.insert_char(c);
478                }
479            }
480            true
481        } else {
482            false
483        }
484    }
485}
486
487impl ColorInputState {
488    /// Value as Color.
489    pub fn value(&self) -> Color {
490        Color::Rgb(
491            (self.value.0 * 255f32) as u8,
492            (self.value.1 * 255f32) as u8,
493            (self.value.2 * 255f32) as u8,
494        )
495    }
496
497    /// Get the value as f32 triple
498    pub fn value_f32(&self) -> (f32, f32, f32) {
499        self.value
500    }
501
502    /// Get the value as u32
503    pub fn value_u32(&self) -> u32 {
504        (((self.value.0 * 255f32) as u32) << 16)
505            + (((self.value.1 * 255f32) as u32) << 8)
506            + ((self.value.2 * 255f32) as u32)
507    }
508
509    fn parse_value(&self) -> (f32, f32, f32) {
510        match self.mode {
511            Mode::RGB => {
512                let r = self.widget.section_value::<f32>(SEC_R).unwrap_or_default();
513                let g = self.widget.section_value::<f32>(SEC_G).unwrap_or_default();
514                let b = self.widget.section_value::<f32>(SEC_B).unwrap_or_default();
515                (r / 255f32, g / 255f32, b / 255f32)
516            }
517            Mode::HEX => {
518                let v = u32::from_str_radix(self.widget.section_text(1), 16).expect("hex");
519                let r = ((v >> 16) & 255) as f32;
520                let g = ((v >> 8) & 255) as f32;
521                let b = (v & 255) as f32;
522                (r / 255f32, g / 255f32, b / 255f32)
523            }
524            Mode::HSV => {
525                let h = self.widget.section_value::<f32>(SEC_H).unwrap_or_default();
526                let s = self.widget.section_value::<f32>(SEC_S).unwrap_or_default();
527                let v = self.widget.section_value::<f32>(SEC_V).unwrap_or_default();
528
529                let h = palette::RgbHue::from_degrees(h);
530                let s = s / 100f32;
531                let v = v / 100f32;
532
533                let hsv = Hsv::from_components((h, s, v));
534                let rgb = Srgb::from_color(hsv);
535
536                rgb.into_components()
537            }
538        }
539    }
540}
541
542impl ColorInputState {
543    /// Reset to empty.
544    #[inline]
545    pub fn clear(&mut self) {
546        self.widget.clear();
547    }
548
549    /// Set the color as f32 triple
550    pub fn set_value_f32(&mut self, color: (f32, f32, f32)) {
551        self.value = color;
552        self.value_to_text();
553    }
554
555    /// Set the color as u32
556    pub fn set_value_u32(&mut self, color: u32) {
557        let r = ((color >> 16) & 255) as f32;
558        let g = ((color >> 8) & 255) as f32;
559        let b = (color & 255) as f32;
560        self.value = (r / 255f32, g / 255f32, b / 255f32);
561        self.value_to_text();
562    }
563
564    /// Set the color as Color.
565    pub fn set_value(&mut self, color: Color) {
566        let (r, g, b) = color2rgb(color);
567        self.value = (r as f32 / 255f32, g as f32 / 255f32, b as f32 / 255f32);
568        self.value_to_text();
569    }
570
571    fn value_to_text(&mut self) {
572        match self.mode {
573            Mode::RGB => {
574                let r = (self.value.0 * 255f32) as u8;
575                let g = (self.value.1 * 255f32) as u8;
576                let b = (self.value.2 * 255f32) as u8;
577                let value_str = format!("{:3} {:3} {:3}", r, g, b);
578                self.widget.set_text(value_str);
579                self.set_mode_styles();
580            }
581            Mode::HEX => {
582                let r = (self.value.0 * 255f32) as u32;
583                let g = (self.value.1 * 255f32) as u32;
584                let b = (self.value.2 * 255f32) as u32;
585                let value_str = format!("{:06x}", (r << 16) + (g << 8) + b);
586                self.widget.set_text(value_str);
587                self.set_mode_styles();
588            }
589            Mode::HSV => {
590                let r = self.value.0;
591                let g = self.value.1;
592                let b = self.value.2;
593                let srgb = Srgb::new(r, g, b);
594                let hsv = Hsv::from_color(srgb);
595                let (h, s, v) = hsv.into_components();
596                let h = h.into_positive_degrees() as u32;
597                let s = (s * 100f32) as u32;
598                let v = (v * 100f32) as u32;
599                let value_str = format!("{:3} {:3} {:3}", h, s, v);
600                self.widget.set_text(value_str);
601                self.set_mode_styles();
602            }
603        }
604    }
605
606    /// Insert a char at the current position.
607    #[inline]
608    pub fn insert_char(&mut self, c: char) -> bool {
609        let r = self.widget.insert_char(c);
610        self.normalize();
611        r
612    }
613
614    /// Remove the selected range. The text will be replaced with the default value
615    /// as defined by the mask.
616    #[inline]
617    pub fn delete_range(&mut self, range: Range<upos_type>) -> bool {
618        let r = self.widget.delete_range(range);
619        self.normalize();
620        r
621    }
622
623    /// Remove the selected range. The text will be replaced with the default value
624    /// as defined by the mask.
625    #[inline]
626    pub fn try_delete_range(&mut self, range: Range<upos_type>) -> Result<bool, TextError> {
627        let r = self.widget.try_delete_range(range);
628        self.normalize();
629        r
630    }
631}
632
633const PAT_RGB: &str = "##0 ##0 ##0";
634const SEC_R: u16 = 1;
635const SEC_G: u16 = 3;
636const SEC_B: u16 = 5;
637
638const PAT_HEX: &str = "HHHHHH";
639#[allow(dead_code)]
640const SEC_X: u16 = 1;
641
642const PAT_HSV: &str = "##0 ##0 ##0";
643const SEC_H: u16 = 1;
644const SEC_S: u16 = 3;
645const SEC_V: u16 = 5;
646
647impl ColorInputState {
648    fn clamp_section(&mut self, section: u16, clamp: u32) -> bool {
649        let r = self
650            .widget
651            .section_value::<u32>(section)
652            .unwrap_or_default();
653        let r_min = min(r, clamp);
654        if r_min != r {
655            self.widget.set_section_value(section, r_min);
656            true
657        } else {
658            false
659        }
660    }
661
662    /// Correct the numeric values for each component.
663    fn normalize(&mut self) -> bool {
664        let r = match self.mode {
665            Mode::RGB => {
666                self.clamp_section(SEC_R, 255)
667                    || self.clamp_section(SEC_G, 255)
668                    || self.clamp_section(SEC_B, 255)
669            }
670            Mode::HEX => {
671                // noop
672                false
673            }
674            Mode::HSV => {
675                self.clamp_section(SEC_H, 360)
676                    || self.clamp_section(SEC_S, 100)
677                    || self.clamp_section(SEC_V, 100)
678            }
679        };
680        self.set_mode_styles();
681        r
682    }
683
684    /// Increment the value at the cursor position.
685    pub fn change_section(&mut self, n: i32) -> bool {
686        self.change_section_pos(self.cursor(), n)
687    }
688
689    pub fn change_section_pos(&mut self, pos: upos_type, n: i32) -> bool {
690        let section = self.widget.section_id(pos);
691        let r = match self.mode {
692            Mode::RGB => match section {
693                SEC_R | SEC_G | SEC_B => {
694                    let r = self
695                        .widget
696                        .section_value::<u32>(section)
697                        .unwrap_or_default();
698                    let r_min = min(r.saturating_add_signed(n), 255);
699                    if r_min != r {
700                        self.widget.set_section_value(section, r_min);
701                        self.set_mode_styles();
702                        true
703                    } else {
704                        false
705                    }
706                }
707                _ => false,
708            },
709            Mode::HEX => {
710                // noop
711                false
712            }
713            Mode::HSV => match section {
714                SEC_H => {
715                    let r = self
716                        .widget
717                        .section_value::<u32>(section)
718                        .unwrap_or_default();
719                    let r_min = {
720                        let mut r_min = (r as i32 + n) % 360;
721                        if r_min < 0 {
722                            r_min += 360;
723                        }
724                        r_min as u32
725                    };
726                    if r_min != r {
727                        self.widget.set_section_value(section, r_min);
728                        self.set_mode_styles();
729                        true
730                    } else {
731                        false
732                    }
733                }
734                SEC_S | SEC_V => {
735                    let r = self
736                        .widget
737                        .section_value::<u32>(section)
738                        .unwrap_or_default();
739                    let r_min = min(r.saturating_add_signed(n), 100);
740                    if r_min != r {
741                        self.widget.set_section_value(section, r_min);
742                        self.set_mode_styles();
743                        true
744                    } else {
745                        false
746                    }
747                }
748                _ => false,
749            },
750        };
751
752        if r {
753            self.value = self.parse_value();
754        }
755        r
756    }
757
758    fn set_mode_styles(&mut self) {
759        match self.mode {
760            Mode::RGB => {
761                // "##0 ##0 ##0"
762                self.widget.clear_styles();
763                self.widget.add_range_style(0..3, 1).expect("fine");
764                self.widget.add_range_style(4..7, 1).expect("fine");
765                self.widget.add_range_style(8..11, 1).expect("fine");
766            }
767            Mode::HEX => {
768                // "hhhhhH"
769                self.widget.clear_styles();
770                self.widget.add_range_style(0..6, 1).expect("fine");
771            }
772            Mode::HSV => {
773                // "##0 ##0 ##0"
774                self.widget.clear_styles();
775                self.widget.add_range_style(0..3, 1).expect("fine");
776                self.widget.add_range_style(4..7, 1).expect("fine");
777                self.widget.add_range_style(8..11, 1).expect("fine");
778            }
779        }
780    }
781
782    /// Color mode.
783    pub fn mode(&self) -> Mode {
784        self.mode
785    }
786
787    /// Set the color mode.
788    pub fn set_mode(&mut self, mode: Mode) -> bool {
789        self.mode = mode;
790        match self.mode {
791            Mode::RGB => {
792                // "##0 ##0 ##0"
793                self.widget.set_mask(PAT_RGB).expect("valid-mask");
794            }
795            Mode::HEX => {
796                // "hhhhhH"
797                self.widget.set_mask(PAT_HEX).expect("valid-mask");
798            }
799            Mode::HSV => {
800                // "##0 ##0 ##0"
801                self.widget.set_mask(PAT_HSV).expect("valid-mask");
802            }
803        }
804
805        self.set_mode_styles();
806        self.value_to_text();
807        self.widget.set_default_cursor();
808        true
809    }
810
811    /// Switch to next mode.
812    pub fn next_mode(&mut self) -> bool {
813        match self.mode {
814            Mode::RGB => self.set_mode(Mode::HEX),
815            Mode::HEX => self.set_mode(Mode::HSV),
816            Mode::HSV => self.set_mode(Mode::RGB),
817        }
818    }
819
820    /// Switch to prev mode.
821    pub fn prev_mode(&mut self) -> bool {
822        match self.mode {
823            Mode::RGB => self.set_mode(Mode::HSV),
824            Mode::HEX => self.set_mode(Mode::RGB),
825            Mode::HSV => self.set_mode(Mode::HEX),
826        }
827    }
828
829    /// Move to the next char.
830    #[inline]
831    pub fn move_right(&mut self, extend_selection: bool) -> bool {
832        self.widget.move_right(extend_selection)
833    }
834
835    /// Move to the previous char.
836    #[inline]
837    pub fn move_left(&mut self, extend_selection: bool) -> bool {
838        self.widget.move_left(extend_selection)
839    }
840
841    /// Start of line
842    #[inline]
843    pub fn move_to_line_start(&mut self, extend_selection: bool) -> bool {
844        self.widget.move_to_line_start(extend_selection)
845    }
846
847    /// End of line
848    #[inline]
849    pub fn move_to_line_end(&mut self, extend_selection: bool) -> bool {
850        self.widget.move_to_line_end(extend_selection)
851    }
852}
853
854impl RelocatableState for ColorInputState {
855    fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
856        self.area.relocate(shift, clip);
857        self.inner.relocate(shift, clip);
858        self.mode_area.relocate(shift, clip);
859        self.label_area.relocate(shift, clip);
860        self.widget.relocate(shift, clip);
861    }
862}
863
864/// Gives the luminance according to BT.709.
865fn luminance_bt_srgb(color: Color) -> f32 {
866    let (r, g, b) = color2rgb(color);
867    0.2126f32 * ((r as f32) / 255f32).powf(2.2f32)
868        + 0.7152f32 * ((g as f32) / 255f32).powf(2.2f32)
869        + 0.0722f32 * ((b as f32) / 255f32).powf(2.2f32)
870}
871
872/// Contrast between two colors.
873fn contrast_bt_srgb(color: Color, color2: Color) -> f32 {
874    let lum1 = luminance_bt_srgb(color);
875    let lum2 = luminance_bt_srgb(color2);
876    (lum1 - lum2).abs()
877    // Don't use this prescribed method.
878    // The abs diff comes out better.
879    // (lum1 + 0.05f32) / (lum2 + 0.05f32)
880}
881
882pub fn high_contrast_color(bg: Color, text: &[Color]) -> Style {
883    let mut color0 = text[0];
884    let mut color1 = text[0];
885    let mut contrast1 = contrast_bt_srgb(color1, bg);
886
887    for text_color in text {
888        let test = contrast_bt_srgb(*text_color, bg);
889        if test > contrast1 {
890            color0 = color1;
891            color1 = *text_color;
892            contrast1 = test;
893        }
894    }
895    // don't use the second brightest.
896    _ = color0;
897
898    Style::new().bg(bg).fg(color1)
899}
900
901/// Gives back the rgb for any ratatui Color.
902/// Has the indexed and the named colors too.
903const fn color2rgb(color: Color) -> (u8, u8, u8) {
904    match color {
905        Color::Black => (0x00, 0x00, 0x00),
906        Color::Red => (0xaa, 0x00, 0x00),
907        Color::Green => (0x00, 0xaa, 0x00),
908        Color::Yellow => (0xaa, 0x55, 0x00),
909        Color::Blue => (0x00, 0x00, 0xaa),
910        Color::Magenta => (0xaa, 0x00, 0xaa),
911        Color::Cyan => (0x00, 0xaa, 0xaa),
912        Color::Gray => (0xaa, 0xaa, 0xaa),
913        Color::DarkGray => (0x55, 0x55, 0x55),
914        Color::LightRed => (0xff, 0x55, 0x55),
915        Color::LightGreen => (0x55, 0xff, 0x55),
916        Color::LightYellow => (0xff, 0xff, 0x55),
917        Color::LightBlue => (0x55, 0x55, 0xff),
918        Color::LightMagenta => (0xff, 0x55, 0xff),
919        Color::LightCyan => (0x55, 0xff, 0xff),
920        Color::White => (0xff, 0xff, 0xff),
921        Color::Rgb(r, g, b) => (r, g, b),
922        Color::Indexed(i) => {
923            const VGA256: [(u8, u8, u8); 256] = [
924                (0x00, 0x00, 0x00),
925                (0x80, 0x00, 0x00),
926                (0x00, 0x80, 0x00),
927                (0x80, 0x80, 0x00),
928                (0x00, 0x00, 0x80),
929                (0x80, 0x00, 0x80),
930                (0x00, 0x80, 0x80),
931                (0xc0, 0xc0, 0xc0),
932                (0x80, 0x80, 0x80),
933                (0xff, 0x00, 0x00),
934                (0x00, 0xff, 0x00),
935                (0xff, 0xff, 0x00),
936                (0x00, 0x00, 0xff),
937                (0xff, 0x00, 0xff),
938                (0x00, 0xff, 0xff),
939                (0xff, 0xff, 0xff),
940                (0x00, 0x00, 0x00),
941                (0x00, 0x00, 0x5f),
942                (0x00, 0x00, 0x87),
943                (0x00, 0x00, 0xaf),
944                (0x00, 0x00, 0xd7),
945                (0x00, 0x00, 0xff),
946                (0x00, 0x5f, 0x00),
947                (0x00, 0x5f, 0x5f),
948                (0x00, 0x5f, 0x87),
949                (0x00, 0x5f, 0xaf),
950                (0x00, 0x5f, 0xd7),
951                (0x00, 0x5f, 0xff),
952                (0x00, 0x87, 0x00),
953                (0x00, 0x87, 0x5f),
954                (0x00, 0x87, 0x87),
955                (0x00, 0x87, 0xaf),
956                (0x00, 0x87, 0xd7),
957                (0x00, 0x87, 0xff),
958                (0x00, 0xaf, 0x00),
959                (0x00, 0xaf, 0x5f),
960                (0x00, 0xaf, 0x87),
961                (0x00, 0xaf, 0xaf),
962                (0x00, 0xaf, 0xd7),
963                (0x00, 0xaf, 0xff),
964                (0x00, 0xd7, 0x00),
965                (0x00, 0xd7, 0x5f),
966                (0x00, 0xd7, 0x87),
967                (0x00, 0xd7, 0xaf),
968                (0x00, 0xd7, 0xd7),
969                (0x00, 0xd7, 0xff),
970                (0x00, 0xff, 0x00),
971                (0x00, 0xff, 0x5f),
972                (0x00, 0xff, 0x87),
973                (0x00, 0xff, 0xaf),
974                (0x00, 0xff, 0xd7),
975                (0x00, 0xff, 0xff),
976                (0x5f, 0x00, 0x00),
977                (0x5f, 0x00, 0x5f),
978                (0x5f, 0x00, 0x87),
979                (0x5f, 0x00, 0xaf),
980                (0x5f, 0x00, 0xd7),
981                (0x5f, 0x00, 0xff),
982                (0x5f, 0x5f, 0x00),
983                (0x5f, 0x5f, 0x5f),
984                (0x5f, 0x5f, 0x87),
985                (0x5f, 0x5f, 0xaf),
986                (0x5f, 0x5f, 0xd7),
987                (0x5f, 0x5f, 0xff),
988                (0x5f, 0x87, 0x00),
989                (0x5f, 0x87, 0x5f),
990                (0x5f, 0x87, 0x87),
991                (0x5f, 0x87, 0xaf),
992                (0x5f, 0x87, 0xd7),
993                (0x5f, 0x87, 0xff),
994                (0x5f, 0xaf, 0x00),
995                (0x5f, 0xaf, 0x5f),
996                (0x5f, 0xaf, 0x87),
997                (0x5f, 0xaf, 0xaf),
998                (0x5f, 0xaf, 0xd7),
999                (0x5f, 0xaf, 0xff),
1000                (0x5f, 0xd7, 0x00),
1001                (0x5f, 0xd7, 0x5f),
1002                (0x5f, 0xd7, 0x87),
1003                (0x5f, 0xd7, 0xaf),
1004                (0x5f, 0xd7, 0xd7),
1005                (0x5f, 0xd7, 0xff),
1006                (0x5f, 0xff, 0x00),
1007                (0x5f, 0xff, 0x5f),
1008                (0x5f, 0xff, 0x87),
1009                (0x5f, 0xff, 0xaf),
1010                (0x5f, 0xff, 0xd7),
1011                (0x5f, 0xff, 0xff),
1012                (0x87, 0x00, 0x00),
1013                (0x87, 0x00, 0x5f),
1014                (0x87, 0x00, 0x87),
1015                (0x87, 0x00, 0xaf),
1016                (0x87, 0x00, 0xd7),
1017                (0x87, 0x00, 0xff),
1018                (0x87, 0x5f, 0x00),
1019                (0x87, 0x5f, 0x5f),
1020                (0x87, 0x5f, 0x87),
1021                (0x87, 0x5f, 0xaf),
1022                (0x87, 0x5f, 0xd7),
1023                (0x87, 0x5f, 0xff),
1024                (0x87, 0x87, 0x00),
1025                (0x87, 0x87, 0x5f),
1026                (0x87, 0x87, 0x87),
1027                (0x87, 0x87, 0xaf),
1028                (0x87, 0x87, 0xd7),
1029                (0x87, 0x87, 0xff),
1030                (0x87, 0xaf, 0x00),
1031                (0x87, 0xaf, 0x5f),
1032                (0x87, 0xaf, 0x87),
1033                (0x87, 0xaf, 0xaf),
1034                (0x87, 0xaf, 0xd7),
1035                (0x87, 0xaf, 0xff),
1036                (0x87, 0xd7, 0x00),
1037                (0x87, 0xd7, 0x5f),
1038                (0x87, 0xd7, 0x87),
1039                (0x87, 0xd7, 0xaf),
1040                (0x87, 0xd7, 0xd7),
1041                (0x87, 0xd7, 0xff),
1042                (0x87, 0xff, 0x00),
1043                (0x87, 0xff, 0x5f),
1044                (0x87, 0xff, 0x87),
1045                (0x87, 0xff, 0xaf),
1046                (0x87, 0xff, 0xd7),
1047                (0x87, 0xff, 0xff),
1048                (0xaf, 0x00, 0x00),
1049                (0xaf, 0x00, 0x5f),
1050                (0xaf, 0x00, 0x87),
1051                (0xaf, 0x00, 0xaf),
1052                (0xaf, 0x00, 0xd7),
1053                (0xaf, 0x00, 0xff),
1054                (0xaf, 0x5f, 0x00),
1055                (0xaf, 0x5f, 0x5f),
1056                (0xaf, 0x5f, 0x87),
1057                (0xaf, 0x5f, 0xaf),
1058                (0xaf, 0x5f, 0xd7),
1059                (0xaf, 0x5f, 0xff),
1060                (0xaf, 0x87, 0x00),
1061                (0xaf, 0x87, 0x5f),
1062                (0xaf, 0x87, 0x87),
1063                (0xaf, 0x87, 0xaf),
1064                (0xaf, 0x87, 0xd7),
1065                (0xaf, 0x87, 0xff),
1066                (0xaf, 0xaf, 0x00),
1067                (0xaf, 0xaf, 0x5f),
1068                (0xaf, 0xaf, 0x87),
1069                (0xaf, 0xaf, 0xaf),
1070                (0xaf, 0xaf, 0xd7),
1071                (0xaf, 0xaf, 0xff),
1072                (0xaf, 0xd7, 0x00),
1073                (0xaf, 0xd7, 0x5f),
1074                (0xaf, 0xd7, 0x87),
1075                (0xaf, 0xd7, 0xaf),
1076                (0xaf, 0xd7, 0xd7),
1077                (0xaf, 0xd7, 0xff),
1078                (0xaf, 0xff, 0x00),
1079                (0xaf, 0xff, 0x5f),
1080                (0xaf, 0xff, 0x87),
1081                (0xaf, 0xff, 0xaf),
1082                (0xaf, 0xff, 0xd7),
1083                (0xaf, 0xff, 0xff),
1084                (0xd7, 0x00, 0x00),
1085                (0xd7, 0x00, 0x5f),
1086                (0xd7, 0x00, 0x87),
1087                (0xd7, 0x00, 0xaf),
1088                (0xd7, 0x00, 0xd7),
1089                (0xd7, 0x00, 0xff),
1090                (0xd7, 0x5f, 0x00),
1091                (0xd7, 0x5f, 0x5f),
1092                (0xd7, 0x5f, 0x87),
1093                (0xd7, 0x5f, 0xaf),
1094                (0xd7, 0x5f, 0xd7),
1095                (0xd7, 0x5f, 0xff),
1096                (0xd7, 0x87, 0x00),
1097                (0xd7, 0x87, 0x5f),
1098                (0xd7, 0x87, 0x87),
1099                (0xd7, 0x87, 0xaf),
1100                (0xd7, 0x87, 0xd7),
1101                (0xd7, 0x87, 0xff),
1102                (0xd7, 0xaf, 0x00),
1103                (0xd7, 0xaf, 0x5f),
1104                (0xd7, 0xaf, 0x87),
1105                (0xd7, 0xaf, 0xaf),
1106                (0xd7, 0xaf, 0xd7),
1107                (0xd7, 0xaf, 0xff),
1108                (0xd7, 0xd7, 0x00),
1109                (0xd7, 0xd7, 0x5f),
1110                (0xd7, 0xd7, 0x87),
1111                (0xd7, 0xd7, 0xaf),
1112                (0xd7, 0xd7, 0xd7),
1113                (0xd7, 0xd7, 0xff),
1114                (0xd7, 0xff, 0x00),
1115                (0xd7, 0xff, 0x5f),
1116                (0xd7, 0xff, 0x87),
1117                (0xd7, 0xff, 0xaf),
1118                (0xd7, 0xff, 0xd7),
1119                (0xd7, 0xff, 0xff),
1120                (0xff, 0x00, 0x00),
1121                (0xff, 0x00, 0x5f),
1122                (0xff, 0x00, 0x87),
1123                (0xff, 0x00, 0xaf),
1124                (0xff, 0x00, 0xd7),
1125                (0xff, 0x00, 0xff),
1126                (0xff, 0x5f, 0x00),
1127                (0xff, 0x5f, 0x5f),
1128                (0xff, 0x5f, 0x87),
1129                (0xff, 0x5f, 0xaf),
1130                (0xff, 0x5f, 0xd7),
1131                (0xff, 0x5f, 0xff),
1132                (0xff, 0x87, 0x00),
1133                (0xff, 0x87, 0x5f),
1134                (0xff, 0x87, 0x87),
1135                (0xff, 0x87, 0xaf),
1136                (0xff, 0x87, 0xd7),
1137                (0xff, 0x87, 0xff),
1138                (0xff, 0xaf, 0x00),
1139                (0xff, 0xaf, 0x5f),
1140                (0xff, 0xaf, 0x87),
1141                (0xff, 0xaf, 0xaf),
1142                (0xff, 0xaf, 0xd7),
1143                (0xff, 0xaf, 0xff),
1144                (0xff, 0xd7, 0x00),
1145                (0xff, 0xd7, 0x5f),
1146                (0xff, 0xd7, 0x87),
1147                (0xff, 0xd7, 0xaf),
1148                (0xff, 0xd7, 0xd7),
1149                (0xff, 0xd7, 0xff),
1150                (0xff, 0xff, 0x00),
1151                (0xff, 0xff, 0x5f),
1152                (0xff, 0xff, 0x87),
1153                (0xff, 0xff, 0xaf),
1154                (0xff, 0xff, 0xd7),
1155                (0xff, 0xff, 0xff),
1156                (0x08, 0x08, 0x08),
1157                (0x12, 0x12, 0x12),
1158                (0x1c, 0x1c, 0x1c),
1159                (0x26, 0x26, 0x26),
1160                (0x30, 0x30, 0x30),
1161                (0x3a, 0x3a, 0x3a),
1162                (0x44, 0x44, 0x44),
1163                (0x4e, 0x4e, 0x4e),
1164                (0x58, 0x58, 0x58),
1165                (0x62, 0x62, 0x62),
1166                (0x6c, 0x6c, 0x6c),
1167                (0x76, 0x76, 0x76),
1168                (0x80, 0x80, 0x80),
1169                (0x8a, 0x8a, 0x8a),
1170                (0x94, 0x94, 0x94),
1171                (0x9e, 0x9e, 0x9e),
1172                (0xa8, 0xa8, 0xa8),
1173                (0xb2, 0xb2, 0xb2),
1174                (0xbc, 0xbc, 0xbc),
1175                (0xc6, 0xc6, 0xc6),
1176                (0xd0, 0xd0, 0xd0),
1177                (0xda, 0xda, 0xda),
1178                (0xe4, 0xe4, 0xe4),
1179                (0xee, 0xee, 0xee),
1180            ];
1181            VGA256[i as usize]
1182        }
1183        Color::Reset => (0, 0, 0),
1184    }
1185}
1186
1187// + #
1188
1189impl HandleEvent<Event, Regular, TextOutcome> for ColorInputState {
1190    fn handle(&mut self, event: &Event, _keymap: Regular) -> TextOutcome {
1191        if self.is_focused() {
1192            flow!(match event {
1193                ct_event!(key press '+') | ct_event!(keycode press Up) =>
1194                    self.change_section(1).into(),
1195                ct_event!(key press '-') | ct_event!(keycode press Down) =>
1196                    self.change_section(-1).into(),
1197                ct_event!(key press ALT-'+') | ct_event!(keycode press ALT-Up) =>
1198                    self.change_section(7).into(),
1199                ct_event!(key press ALT-'-') | ct_event!(keycode press ALT-Down) =>
1200                    self.change_section(-7).into(),
1201                ct_event!(key press CONTROL-'v') => self.paste_from_clip().into(),
1202                ct_event!(key press CONTROL-'c') => self.copy_to_clip().into(),
1203                ct_event!(key press CONTROL-'x') => self.cut_to_clip().into(),
1204                _ => TextOutcome::Continue,
1205            });
1206            if !self.disable_modes {
1207                flow!(match event {
1208                    ct_event!(key press 'r') => self.set_mode(Mode::RGB).into(),
1209                    ct_event!(key press 'h') => self.set_mode(Mode::HSV).into(),
1210                    ct_event!(key press 'x') => self.set_mode(Mode::HEX).into(),
1211                    ct_event!(key press 'm') | ct_event!(keycode press PageUp) =>
1212                        self.next_mode().into(),
1213                    ct_event!(key press SHIFT-'M') | ct_event!(keycode press PageDown) =>
1214                        self.prev_mode().into(),
1215                    _ => TextOutcome::Continue,
1216                });
1217            }
1218        }
1219
1220        flow!(handle_mouse(self, event));
1221
1222        match self.widget.handle(event, Regular) {
1223            TextOutcome::TextChanged => {
1224                self.normalize();
1225                self.value = self.parse_value();
1226                TextOutcome::TextChanged
1227            }
1228            r => r,
1229        }
1230    }
1231}
1232
1233impl HandleEvent<Event, ReadOnly, TextOutcome> for ColorInputState {
1234    fn handle(&mut self, event: &Event, _keymap: ReadOnly) -> TextOutcome {
1235        self.widget.handle(event, ReadOnly)
1236    }
1237}
1238
1239impl HandleEvent<Event, MouseOnly, TextOutcome> for ColorInputState {
1240    fn handle(&mut self, event: &Event, _keymap: MouseOnly) -> TextOutcome {
1241        flow!(handle_mouse(self, event));
1242        self.widget.handle(event, MouseOnly)
1243    }
1244}
1245
1246fn handle_mouse(state: &mut ColorInputState, event: &Event) -> TextOutcome {
1247    if state.is_focused() {
1248        if !state.has_mouse_focus() {
1249            return TextOutcome::Continue;
1250        }
1251
1252        match event {
1253            ct_event!(scroll ALT down for x,y) if state.mode_area.contains((*x, *y).into()) => {
1254                state.next_mode().into()
1255            }
1256            ct_event!(scroll ALT up for x,y) if state.mode_area.contains((*x, *y).into()) => {
1257                state.prev_mode().into()
1258            }
1259            ct_event!(scroll down for x,y) if state.widget.area.contains((*x, *y).into()) => {
1260                let rx = state
1261                    .widget
1262                    .screen_to_col((*x - state.widget.area.x) as i16);
1263                state.change_section_pos(rx, -1).into()
1264            }
1265            ct_event!(scroll up for x,y) if state.widget.area.contains((*x, *y).into()) => {
1266                let rx = state
1267                    .widget
1268                    .screen_to_col((*x - state.widget.area.x) as i16);
1269                state.change_section_pos(rx, 1).into()
1270            }
1271            ct_event!(scroll ALT down for x,y) if state.widget.area.contains((*x, *y).into()) => {
1272                let rx = state
1273                    .widget
1274                    .screen_to_col((*x - state.widget.area.x) as i16);
1275                state.change_section_pos(rx, -7).into()
1276            }
1277            ct_event!(scroll ALT up for x,y) if state.widget.area.contains((*x, *y).into()) => {
1278                let rx = state
1279                    .widget
1280                    .screen_to_col((*x - state.widget.area.x) as i16);
1281                state.change_section_pos(rx, 7).into()
1282            }
1283            _ => TextOutcome::Continue,
1284        }
1285    } else {
1286        TextOutcome::Continue
1287    }
1288}
1289
1290/// Handle all events.
1291/// Text events are only processed if focus is true.
1292/// Mouse events are processed if they are in range.
1293pub fn handle_events(state: &mut ColorInputState, focus: bool, event: &Event) -> TextOutcome {
1294    state.widget.focus.set(focus);
1295    HandleEvent::handle(state, event, Regular)
1296}
1297
1298/// Handle only navigation events.
1299/// Text events are only processed if focus is true.
1300/// Mouse events are processed if they are in range.
1301pub fn handle_readonly_events(
1302    state: &mut ColorInputState,
1303    focus: bool,
1304    event: &Event,
1305) -> TextOutcome {
1306    state.widget.focus.set(focus);
1307    state.handle(event, ReadOnly)
1308}
1309
1310/// Handle only mouse-events.
1311pub fn handle_mouse_events(state: &mut ColorInputState, event: &Event) -> TextOutcome {
1312    HandleEvent::handle(state, event, MouseOnly)
1313}