diff options
author | Manos Pitsidianakis <el13635@mail.ntua.gr> | 2020-02-04 15:52:12 +0200 |
---|---|---|
committer | Manos Pitsidianakis <el13635@mail.ntua.gr> | 2020-02-04 17:29:55 +0200 |
commit | 8b6ea8de9a89b3ad42276eb7835992f7b875128b (patch) | |
tree | 864a7136b4c23d76761ee3e7d034307f8c302838 /src/terminal | |
parent | 6fcc792b83f715644c041f728049de65f7da2e38 (diff) |
Remove ui crate
Merge ui crate with root crate.
In preparation for uploading `meli` as a separate crate on crates.io.
Workspace crates will need to be published as well and having a separate
`ui` crate and binary perhaps doesn't make sense anymore.
Diffstat (limited to 'src/terminal')
-rw-r--r-- | src/terminal/cells.rs | 2067 | ||||
-rw-r--r-- | src/terminal/embed.rs | 391 | ||||
-rw-r--r-- | src/terminal/embed/grid.rs | 1126 | ||||
-rw-r--r-- | src/terminal/keys.rs | 427 | ||||
-rw-r--r-- | src/terminal/position.rs | 162 | ||||
-rw-r--r-- | src/terminal/text_editing.rs | 175 |
6 files changed, 4348 insertions, 0 deletions
diff --git a/src/terminal/cells.rs b/src/terminal/cells.rs new file mode 100644 index 00000000..df76faf6 --- /dev/null +++ b/src/terminal/cells.rs @@ -0,0 +1,2067 @@ +/* + * meli + * + * Copyright 2017-2018 Manos Pitsidianakis + * + * This file is part of meli. + * + * meli is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * meli is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with meli. If not, see <http://www.gnu.org/licenses/>. + */ + +/*! + Define a (x, y) point in the terminal display as a holder of a character, foreground/background + colors and attributes. +*/ + +use super::position::*; +use crate::state::Context; +use text_processing::wcwidth; + +use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; +use std::convert::From; +use std::fmt; +use std::ops::{Deref, DerefMut, Index, IndexMut}; +use termion::color::{AnsiValue, Rgb as TermionRgb}; + +/// In a scroll region up and down cursor movements shift the region vertically. The new lines are +/// empty. +/// +/// See `CellBuffer::scroll_up` and `CellBuffer::scroll_down` for an explanation of how `xterm` +/// scrolling works. +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub struct ScrollRegion { + pub top: usize, + pub bottom: usize, + pub left: usize, + pub right: usize, +} + +/// An array of `Cell`s that represents a terminal display. +/// +/// A `CellBuffer` is a two-dimensional array of `Cell`s, each pair of indices correspond to a +/// single point on the underlying terminal. +/// +/// The first index, `Cellbuffer[y]`, corresponds to a row, and thus the y-axis. The second +/// index, `Cellbuffer[y][x]`, corresponds to a column within a row and thus the x-axis. +#[derive(Clone, PartialEq, Eq)] +pub struct CellBuffer { + cols: usize, + rows: usize, + buf: Vec<Cell>, + /// ASCII-only flag. + pub ascii_drawing: bool, + /// If printing to this buffer and we run out of space, expand it. + growable: bool, +} + +impl fmt::Debug for CellBuffer { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "CellBuffer {{ cols: {}, rows: {}, buf: {} cells", + self.cols, + self.rows, + self.buf.len() + ) + } +} + +impl CellBuffer { + pub fn area(&self) -> Area { + ( + (0, 0), + (self.cols.saturating_sub(1), self.rows.saturating_sub(1)), + ) + } + pub fn set_cols(&mut self, new_cols: usize) { + self.cols = new_cols; + } + + /// Constructs a new `CellBuffer` with the given number of columns and rows, using the given + /// `cell` as a blank. + pub fn new(cols: usize, rows: usize, cell: Cell) -> CellBuffer { + CellBuffer { + cols, + rows, + buf: vec![cell; cols * rows], + growable: false, + ascii_drawing: false, + } + } + + pub fn new_with_context(cols: usize, rows: usize, cell: Cell, context: &Context) -> CellBuffer { + CellBuffer { + cols, + rows, + buf: vec![cell; cols * rows], + growable: false, + ascii_drawing: context.settings.terminal.ascii_drawing, + } + } + + pub fn set_ascii_drawing(&mut self, new_val: bool) { + self.ascii_drawing = new_val; + } + + pub fn set_growable(&mut self, new_val: bool) { + self.growable = new_val; + } + + /// Resizes `CellBuffer` to the given number of rows and columns, using the given `Cell` as + /// a blank. + pub fn resize(&mut self, newcols: usize, newrows: usize, blank: Cell) { + let newlen = newcols * newrows; + if self.buf.len() == newlen { + self.cols = newcols; + self.rows = newrows; + return; + } + + if newlen >= 200_000 { + return; + } + + let mut newbuf: Vec<Cell> = Vec::with_capacity(newlen); + for y in 0..newrows { + for x in 0..newcols { + let cell = self.get(x, y).unwrap_or(&blank); + newbuf.push(*cell); + } + } + self.buf = newbuf; + self.cols = newcols; + self.rows = newrows; + } + + pub fn is_empty(&self) -> bool { + self.buf.is_empty() + } + + pub fn empty(&mut self) { + self.buf.clear(); + self.cols = 0; + self.rows = 0; + } + + /// Clears `self`, using the given `Cell` as a blank. + pub fn clear(&mut self, blank: Cell) { + for cell in self.cellvec_mut().iter_mut() { + *cell = blank; + } + } + + pub fn pos_to_index(&self, x: usize, y: usize) -> Option<usize> { + let (cols, rows) = self.size(); + if x < cols && y < rows { + Some((cols * y) + x) + } else { + None + } + } + + /// Returns a reference to the `Cell` at the given coordinates, or `None` if the index is out of + /// bounds. + /// + /// # Examples + /// + /// ```no_run + /// + /// let mut term = Terminal::new().unwrap(); + /// + /// let a_cell = term.get(5, 5); + /// ``` + pub fn get(&self, x: usize, y: usize) -> Option<&Cell> { + match self.pos_to_index(x, y) { + Some(i) => self.cellvec().get(i), + None => None, + } + } + + /// Returns a mutable reference to the `Cell` at the given coordinates, or `None` if the index + /// is out of bounds. + /// + /// # Examples + /// + /// ```no_run + /// + /// let mut term = Terminal::new().unwrap(); + /// + /// let a_mut_cell = term.get_mut(5, 5); + /// ``` + pub fn get_mut(&mut self, x: usize, y: usize) -> Option<&mut Cell> { + match self.pos_to_index(x, y) { + Some(i) => self.cellvec_mut().get_mut(i), + None => None, + } + } + + pub fn size(&self) -> (usize, usize) { + (self.cols, self.rows) + } + + pub fn cellvec(&self) -> &Vec<Cell> { + &self.buf + } + + pub fn cellvec_mut(&mut self) -> &mut Vec<Cell> { + &mut self.buf + } + + pub fn cols(&self) -> usize { + self.size().0 + } + + pub fn rows(&self) -> usize { + self.size().1 + } + + #[inline(always)] + /// Performs the normal scroll up motion: + /// + /// First clear offset number of lines: + /// + /// For offset = 1, top = 1: + /// + /// ```text + /// | 111111111111 | | | + /// | 222222222222 | | 222222222222 | + /// | 333333333333 | | 333333333333 | + /// | 444444444444 | --> | 444444444444 | + /// | 555555555555 | | 555555555555 | + /// | 666666666666 | | 666666666666 | + /// ``` + /// + /// In each step, swap the current line with the next by offset: + /// + /// ```text + /// | | | 222222222222 | + /// | 222222222222 | | | + /// | 333333333333 | | 333333333333 | + /// | 444444444444 | --> | 444444444444 | + /// | 555555555555 | | 555555555555 | + /// | 666666666666 | | 666666666666 | + /// ``` + /// + /// Result: + /// ```text + /// Before After + /// | 111111111111 | | 222222222222 | + /// | 222222222222 | | 333333333333 | + /// | 333333333333 | | 444444444444 | + /// | 444444444444 | | 555555555555 | + /// | 555555555555 | | 666666666666 | + /// | 666666666666 | | | + /// ``` + /// + pub fn scroll_up(&mut self, scroll_region: &ScrollRegion, top: usize, offset: usize) { + //debug!( + // "scroll_up scroll_region {:?}, top: {} offset {}", + // scroll_region, top, offset + //); + let l = scroll_region.left; + let r = if scroll_region.right == 0 { + self.size().0 + } else { + scroll_region.right + }; + for y in top..=(top + offset - 1) { + for x in l..r { + self[(x, y)] = Cell::default(); + } + } + for y in top..=(scroll_region.bottom - offset) { + for x in l..r { + let temp = self[(x, y)]; + self[(x, y)] = self[(x, y + offset)]; + self[(x, y + offset)] = temp; + } + } + } + + #[inline(always)] + /// Performs the normal scroll down motion: + /// + /// First clear offset number of lines: + /// + /// For offset = 1, top = 1: + /// + /// ```text + /// | 111111111111 | | 111111111111 | + /// | 222222222222 | | 222222222222 | + /// | 333333333333 | | 333333333333 | + /// | 444444444444 | --> | 444444444444 | + /// | 555555555555 | | 555555555555 | + /// | 666666666666 | | | + /// ``` + /// + /// In each step, swap the current line with the prev by offset: + /// + /// ```text + /// | 111111111111 | | 111111111111 | + /// | 222222222222 | | 222222222222 | + /// | 333333333333 | | 333333333333 | + /// | 444444444444 | --> | 444444444444 | + /// | 555555555555 | | | + /// | | | 555555555555 | + /// ``` + /// + /// Result: + /// ```text + /// Before After + /// | 111111111111 | | | + /// | 222222222222 | | 111111111111 | + /// | 333333333333 | | 222222222222 | + /// | 444444444444 | | 333333333333 | + /// | 555555555555 | | 444444444444 | + /// | 666666666666 | | 555555555555 | + /// ``` + /// + pub fn scroll_down(&mut self, scroll_region: &ScrollRegion, top: usize, offset: usize) { + //debug!( + // "scroll_down scroll_region {:?}, top: {} offset {}", + // scroll_region, top, offset + //); + for y in (scroll_region.bottom - offset + 1)..=scroll_region.bottom { + for x in 0..self.size().0 { + self[(x, y)] = Cell::default(); + } + } + + for y in ((top + offset)..=scroll_region.bottom).rev() { + for x in 0..self.size().0 { + let temp = self[(x, y)]; + self[(x, y)] = self[(x, y - offset)]; + self[(x, y - offset)] = temp; + } + } + } + + /// See `BoundsIterator` documentation. + pub fn bounds_iter(&self, area: Area) -> BoundsIterator { + BoundsIterator { + rows: std::cmp::min(self.rows.saturating_sub(1), get_y(upper_left!(area))) + ..(std::cmp::min(self.rows, get_y(bottom_right!(area)) + 1)), + cols: ( + std::cmp::min(self.cols.saturating_sub(1), get_x(upper_left!(area))), + std::cmp::min(self.cols, get_x(bottom_right!(area)) + 1), + ), + } + } + + /// See `RowIterator` documentation. + pub fn row_iter(&self, bounds: std::ops::Range<usize>, row: usize) -> RowIterator { + if row < self.rows { + RowIterator { + row, + col: std::cmp::min(self.cols.saturating_sub(1), bounds.start) + ..(std::cmp::min(self.cols, bounds.end)), + } + } else { + RowIterator { row, col: 0..0 } + } + } +} + +impl Deref for CellBuffer { + type Target = [Cell]; + + fn deref(&self) -> &[Cell] { + &self.buf + } +} + +impl DerefMut for CellBuffer { + fn deref_mut(&mut self) -> &mut [Cell] { + &mut self.buf + } +} + +impl Index<Pos> for CellBuffer { + type Output = Cell; + + fn index(&self, index: Pos) -> &Cell { + let (x, y) = index; + self.get(x, y).expect("index out of bounds") + } +} + +impl IndexMut<Pos> for CellBuffer { + fn index_mut(&mut self, index: Pos) -> &mut Cell { + let (x, y) = index; + self.get_mut(x, y).expect("index out of bounds") + } +} + +impl Default for CellBuffer { + /// Constructs a new `CellBuffer` with a size of `(0, 0)`, using the default `Cell` as a blank. + fn default() -> CellBuffer { + CellBuffer::new(0, 0, Cell::default()) + } +} + +impl fmt::Display for CellBuffer { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + '_y: for y in 0..self.rows { + for x in 0..self.cols { + let c: &char = &self[(x, y)].ch(); + write!(f, "{}", *c).unwrap(); + if *c == '\n' { + continue '_y; + } + } + } + Ok(()) + } +} + +/// A single point on a terminal display. +/// +/// A `Cell` contains a character and style. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct Cell { + ch: char, + + /// Set a `Cell` as empty when a previous cell spans multiple columns and it would + /// "overflow" to this cell. + empty: bool, + fg: Color, + bg: Color, + attrs: Attr, + keep_fg: bool, + keep_bg: bool, +} + +impl Cell { + /// Creates a new `Cell` with the given `char`, `Color`s and `Attr`. + /// + /// # Examples + /// + /// ```no_run + /// let cell = Cell::new('x', Color::Default, Color::Green, Attr::Default); + /// assert_eq!(cell.ch(), 'x'); + /// assert_eq!(cell.fg(), Color::Default); + /// assert_eq!(cell.bg(), Color::Green); + /// assert_eq!(cell.attrs(), Attr::Default); + /// ``` + pub fn new(ch: char, fg: Color, bg: Color, attrs: Attr) -> Cell { + Cell { + ch, + fg, + bg, + attrs, + empty: false, + keep_fg: false, + keep_bg: false, + } + } + + /// Creates a new `Cell` with the given `char` and default style. + /// + /// # Examples + /// + /// ```no_run + /// let mut cell = Cell::with_char('x'); + /// assert_eq!(cell.ch(), 'x'); + /// assert_eq!(cell.fg(), Color::Default); + /// assert_eq!(cell.bg(), Color::Default); + /// assert_eq!(cell.attrs(), Attr::Default); + /// ``` + pub fn with_char(ch: char) -> Cell { + Cell::new(ch, Color::Default, Color::Default, Attr::Default) + } + + /// Creates a new `Cell` with the given style and a blank `char`. + /// + /// # Examples + /// + /// ```no_run + /// let mut cell = Cell::with_style(Color::Default, Color::Red, Attr::Bold); + /// assert_eq!(cell.fg(), Color::Default); + /// assert_eq!(cell.bg(), Color::Red); + /// assert_eq!(cell.attrs(), Attr::Bold); + /// assert_eq!(cell.ch(), ' '); + /// ``` + pub fn with_style(fg: Color, bg: Color, attr: Attr) -> Cell { + Cell::new(' ', fg, bg, attr) + } + + /// Returns the `Cell`'s character. + /// + /// # Examples + /// + /// ```no_run + /// let mut cell = Cell::with_char('x'); + /// assert_eq!(cell.ch(), 'x'); + /// ``` + pub fn ch(&self) -> char { + self.ch + } + + /// Sets the `Cell`'s character to the given `char` + /// + /// # Examples + /// + /// ```no_run + /// let mut cell = Cell::with_char('x'); + /// assert_eq!(cell.ch(), 'x'); + /// + /// cell.set_ch('y'); + /// assert_eq!(cell.ch(), 'y'); + /// ``` + pub fn set_ch(&mut self, newch: char) -> &mut Cell { + self.ch = newch; + self.keep_fg = false; + self.keep_bg = false; + self + } + + /// Returns the `Cell`'s foreground `Color`. + /// + /// # Examples + /// + /// ```no_run + /// let mut cell = Cell::with_style(Color::Blue, Color::Default, Attr::Default); + /// assert_eq!(cell.fg(), Color::Blue); + /// ``` + pub fn fg(&self) -> Color { + self.fg + } + + /// Sets the `Cell`'s foreground `Color` to the given `Color`. + /// + /// # Examples + /// + /// ```no_run + /// let mut cell = Cell::default(); + /// assert_eq!(cell.fg(), Color::Default); + /// + /// cell.set_fg(Color::White); + /// assert_eq!(cell.fg(), Color::White); + /// ``` + pub fn set_fg(&mut self, newfg: Color) -> &mut Cell { + if !self.keep_fg { + self.fg = newfg; + } + self + } + + /// Returns the `Cell`'s background `Color`. + /// + /// # Examples + /// + /// ```no_run + /// let mut cell = Cell::with_style(Color::Default, Color::Green, Attr::Default); + /// assert_eq!(cell.bg(), Color::Green); + /// ``` + pub fn bg(&self) -> Color { + self.bg + } + + /// Sets the `Cell`'s background `Color` to the given `Color`. + /// + /// # Examples + /// + /// ```no_run + /// let mut cell = Cell::default(); + /// assert_eq!(cell.bg(), Color::Default); + /// + /// cell.set_bg(Color::Black); + /// assert_eq!(cell.bg(), Color::Black); + /// ``` + pub fn set_bg(&mut self, newbg: Color) -> &mut Cell { + if !self.keep_bg { + self.bg = newbg; + } + self + } + + pub fn attrs(&self) -> Attr { + self.attrs + } + + pub fn set_attrs(&mut self, newattrs: Attr) -> &mut Cell { + self.attrs = newattrs; + self + } + + /// Set a `Cell` as empty when a previous cell spans multiple columns and it would + /// "overflow" to this cell. + pub fn empty(&self) -> bool { + self.empty + } + + pub fn set_empty(&mut self, new_val: bool) -> &mut Cell { + self.empty = new_val; + self + } + + /// Sets `keep_fg` field. If true, the foreground color will not be altered if attempted so + /// until the character content of the cell is changed. + pub fn set_keep_fg(&mut self, new_val: bool) -> &mut Cell { + self.keep_fg = new_val; + self + } + + /// Sets `keep_bg` field. If true, the background color will not be altered if attempted so + /// until the character content of the cell is changed. + pub fn set_keep_bg(&mut self, new_val: bool) -> &mut Cell { + self.keep_bg = new_val; + self + } +} + +impl Default for Cell { + /// Constructs a new `Cell` with a blank `char` and default `Color`s. + /// + /// # Examples + /// + /// ```no_run + /// let mut cell = Cell::default(); + /// assert_eq!(cell.ch(), ' '); + /// assert_eq!(cell.fg(), Color::Default); + /// assert_eq!(cell.bg(), Color::Default); + /// ``` + fn default() -> Cell { + Cell::new(' ', Color::Default, Color::Default, Attr::Default) + } +} + +/// The color of a `Cell`. +/// +/// `Color::Default` represents the default color of the underlying terminal. +/// +/// The eight basic colors may be used directly and correspond to 0x00..0x07 in the 8-bit (256) +/// color range; in addition, the eight basic colors coupled with `Attr::Bold` correspond to +/// 0x08..0x0f in the 8-bit color range. +/// +/// `Color::Byte(..)` may be used to specify a color in the 8-bit range. +/// +/// # Examples +/// +/// ```no_run +/// // The default color. +/// let default = Color::Default; +/// +/// // A basic color. +/// let red = Color::Red; +/// +/// // An 8-bit color. +/// let fancy = Color::Byte(0x01); +/// +/// // Basic colors are also 8-bit colors (but not vice-versa). +/// assert_eq!(red.as_byte(), fancy.as_byte()) +/// ``` +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum Color { + Black, + Red, + Green, + Yellow, + Blue, + Magenta, + Cyan, + White, + Byte(u8), + Rgb(u8, u8, u8), + /// Terminal default. + Default, +} + +impl Color { + /// Returns the `u8` representation of the `Color`. + pub fn as_byte(self) -> u8 { + match self { + Color::Black => 0x00, + Color::Red => 0x01, + Color::Green => 0x02, + Color::Yellow => 0x03, + Color::Blue => 0x04, + Color::Magenta => 0x05, + Color::Cyan => 0x06, + Color::White => 0x07, + Color::Byte(b) => b, + Color::Rgb(_, _, _) => unreachable!(), + Color::Default => 0x00, + } + } + + pub fn from_byte(val: u8) -> Self { + match val { + 0x00 => Color::Black, + 0x01 => Color::Red, + 0x02 => Color::Green, + 0x03 => Color::Yellow, + 0x04 => Color::Blue, + 0x05 => Color::Magenta, + 0x06 => Color::Cyan, + 0x07 => Color::White, + _ => Color::Default, + } + } + + pub fn write_fg(self, stdout: &mut crate::StateStdout) -> std::io::Result<()> { + use std::io::Write; + match self { + Color::Default => write!(stdout, "{}", termion::color::Fg(termion::color::Reset)), + Color::Rgb(r, g, b) => write!(stdout, "{}", termion::color::Fg(TermionRgb(r, g, b))), + _ => write!(stdout, "{}", termion::color::Fg(self.as_termion())), + } + } + + pub fn write_bg(self, stdout: &mut crate::StateStdout) -> std::io::Result<()> { + use std::io::Write; + match self { + Color::Default => write!(stdout, "{}", termion::color::Bg(termion::color::Reset)), + Color::Rgb(r, g, b) => write!(stdout, "{}", termion::color::Bg(TermionRgb(r, g, b))), + _ => write!(stdout, "{}", termion::color::Bg(self.as_termion())), + } + } + + pub fn as_termion(self) -> AnsiValue { + match self { + b @ Color::Black + | b @ Color::Red + | b @ Color::Green + | b @ Color::Yellow + | b @ Color::Blue + | b @ Color::Magenta + | b @ Color::Cyan + | b @ Color::White + | b @ Color::Default => AnsiValue(b.as_byte()), + Color::Byte(b) => AnsiValue(b), + Color::Rgb(_, _, _) => AnsiValue(0), + } + } + + pub fn from_string_de<'de, D>(s: String) -> std::result::Result<Self, D::Error> + where + D: Deserializer<'de>, + { + let byte = match s.as_str() { + "Aqua" => 14, + "Aquamarine1" => 122, + "Aquamarine2" => 86, + "Aquamarine3" => 79, + "Black" => 0, + "Blue" => 12, + "Blue1" => 21, + "Blue2" => 19, + "Blue3" => 20, + "BlueViolet" => 57, + "CadetBlue" => 72, + "CadetBlue1" => 73, + "Chartreuse1" => 118, + "Chartreuse2" => 112, + "Chartreuse3" => 82, + "Chartreuse4" => 70, + "Chartreuse5" => 76, + "Chartreuse6" => 64, + "CornflowerBlue" => 69, + "Cornsilk1" => 230, + "Cyan1" => 51, + "Cyan2" => 50, + "Cyan3" => 43, + "DarkBlue" => 18, + "DarkCyan" => 36, + "DarkGoldenrod" => 136, + "DarkGreen" => 22, + "DarkKhaki" => 143, + "DarkMagenta" => 90, + "DarkMagenta1" => 91, + "DarkOliveGreen1" => 192, + "DarkOliveGreen2" => 155, + "DarkOliveGreen3" => 191, + "DarkOliveGreen4" => 107, + "DarkOliveGreen5" => 113, + "DarkOliveGreen6" => 149, + "DarkOrange" => 208, + "DarkOrange2" => 130, + "DarkOrange3" => 166, + "DarkRed" => 52, + "DarkRed2" => 88, + "DarkSeaGreen" => 108, + "DarkSeaGreen1" => 158, + "DarkSeaGreen2" => 193, + "DarkSeaGreen3" => 151, + "DarkSeaGreen4" => 157, + "DarkSeaGreen5" => 115, + "DarkSeaGreen6" => 150, + "DarkSeaGreen7" => 65, + "DarkSeaGreen8" => 71, + "DarkSlateGray1" => 123, + "DarkSlateGray2" => 87, + "DarkSlateGray3" => 116, + "DarkTurquoise" => 44, + "DarkViolet" => 128, + "DarkViolet1" => 92, + "DeepPink1" => 199, + "DeepPink2" => 197, + "DeepPink3" => 198, + "DeepPink4" => 125, + "DeepPink6" => 162, + "DeepPink7" => 89, + "DeepPink8" => 53, + "DeepPink9" => 161, + "DeepSkyBlue1" => 39, + "DeepSkyBlue2" => 38, + "DeepSkyBlue3" => 31, + "DeepSkyBlue4" => 32, + "DeepSkyBlue5" => 23, + "DeepSkyBlue6" => 24, + "DeepSkyBlue7" => 25, + "DodgerBlue1" => 33, + "DodgerBlue2" => 27, + "DodgerBlue3" => 26, + "Fuchsia" => 13, + "Gold1" => 220, + "Gold2" => 142, + "Gold3" => 178, + "Green" => 2, + "Green1" => 46, + "Green2" => 34, + "Green3" => 40, + "Green4" => 28, + "GreenYellow" => 154, + "Grey" => 8, + "Grey0" => 16, + "Grey100" => 231, + "Grey11" => 234, + "Grey15" => 235, + "Grey19" => 236, + "Grey23" => 237, + "Grey27" => 238, + "Grey3" => 232, + "Grey30" => 239, + "Grey35" => 240, + "Grey37" => 59, + "Grey39" => 241, + "Grey42" => 242, + "Grey46" => 243, + "Grey50" => 244, + "Grey53" => 102, + "Grey54" => 245, + "Grey58" => 246, + "Grey62" => 247, + "Grey63" => 139, + "Grey66" => 248, + "Grey69" => 145, + "Grey7" => 233, + "Grey70" => 249, + "Grey74" => 250, + "Grey78" => 251, + "Grey82" => 252, + "Grey84" => 188, + "Grey85" => 253, + "Grey89" => 254, + "Grey93" => 255, + "Honeydew2" => 194, + "HotPink" => 205, + "HotPink1" => 206, + "HotPink2" => 169, + "HotPink3" => 132, + "HotPink4" => 168, + "IndianRed" => 131, + "IndianRed1" => 167, + "IndianRed2" => 204, + "IndianRed3" => 203, + "Khaki1" => 228, + "Khaki3" => 185, + "LightCoral" => 210, + "LightCyan2" => 195, + "LightCyan3" => 152, + "LightGoldenrod1" => 227, + "LightGoldenrod2" => 222, + "LightGoldenrod3" => 179, + "LightGoldenrod4" => 221, + "LightGoldenrod5" => 186, + "LightGreen" => 119, + "LightGreen1" => 120, + "LightPink1" => 217, + "LightPink2" => 174, + "LightPink3" => 95, + "LightSalmon1" => 216, + "LightSalmon2" => 137, + "LightSalmon3" => 173, + "LightSeaGreen" => 37, + "LightSkyBlue1" => 153, + "LightSkyBlue2" => 109, + "LightSkyBlue3" => 110, + "LightSlateBlue" => 105, + "LightSlateGrey" => 103, + "LightSteelBlue" => 147, + "LightSteelBlue1" => 189, + "LightSteelBlue3" => 146, + "LightYellow3" => 187, + "Lime" => 10, + "Magenta1" => 201, + "Magenta2" => 165, + "Magenta3" => 200, + "Magenta4" => 127, + "Magenta5" => 163, + "Magenta6" => 164, + "Maroon" => 1, + "MediumOrchid" => 134, + "MediumOrchid1" => 171, + "MediumOrchid2" => 207, + "MediumOrchid3" => 133, + "MediumPurple" => 104, + "MediumPurple1" => 141, + "MediumPurple2" => 135, + "MediumPurple3" => 140, + "MediumPurple4" => 97, + "MediumPurple5" => 98, + "MediumPurple6" => 60, + "MediumSpringGreen" => 49, + "MediumTurquoise" => 80, + "MediumVioletRed" => 126, + "MistyRose1" => 224, + "MistyRose3" => 181, + "NavajoWhite1" => 223, + "NavajoWhite3" => 144, + "Navy" => 4, + "NavyBlue" => 17, + "Olive" => 3, + "Orange1" => 214, + "Orange2" => 172, + "Orange3" => 58, + "Orange4" => 94, + "OrangeRed1" => 202, + "Orchid" => 170, + "Orchid1" => 213, + "Orchid2" => 212, + "PaleGreen1" => 121, + "PaleGreen2" => 156, + "PaleGreen3" => 114, + "PaleGreen4" => 77, + "PaleTurquoise1" => 159, + "PaleTurquoise4" => 66, + "PaleVioletRed1" => 211, + "Pink1" => 218, + "Pink3" => 175, + "Plum1" => 219, + "Plum2" => 183, + "Plum3" => 176, + "Plum4" => 96, + "Purple" => 129, + "Purple1" => 5, + "Purple2" => 93, + "Purple3" => 56, + "Purple4" => 54, + "Purple5" => 55, + "Red" => 9, + "Red1" => 196, + "Red2" => 124, + "Red3" => 160, + "RosyBrown" => 138, + "RoyalBlue1" => 63, + "Salmon1" => 209, + "SandyBrown" => 215, + "SeaGreen1" => 84, + "SeaGreen2" => 85, + "SeaGreen3" => 83, + "SeaGreen4" => 78, + "Silver" => 7, + "SkyBlue1" => 117, + "SkyBlue2" => 111, + "SkyBlue3" => 74, + "SlateBlue1" => 99, + "SlateBlue2" => 61, + "SlateBlue3" => 62, + "SpringGreen1" => 48, + "SpringGreen2" => 42, + "SpringGreen3" => 47, + "SpringGreen4" => 35, + "SpringGreen5" => 41, + "SpringGreen6" => 29, + "SteelBlue" => 67, + "SteelBlue1" => 75, + "SteelBlue2" => 81, + "SteelBlue3" => 68, + "Tan" => 180, + "Teal" => 6, + "Thistle1" => 225, + "Thistle3" => 182, + "Turquoise2" => 45, + "Turquoise4" => 30, + "Violet" => 177, + "Wheat1" => 229, + "Wheat4" => 101, + "White" => 15, + "Yellow" => 11, + "Yellow1" => 226, + "Yellow2" => 190, + "Yellow3" => 184, + "Yellow4" => 100, + "Yellow5" => 106, + "Yellow6" => 148, + "Default" => return Ok(Color::Default), + s if s.starts_with("#") + && s.len() == 7 + && s[1..].as_bytes().iter().all(|&b| { + (b >= b'0' && b <= b'9') || (b >= b'a' && b <= b'f') || (b >= b'A' && b <= b'F') + }) => + { + return Ok(Color::Rgb( + u8::from_str_radix(&s[1..3], 16) + .map_err(|_| de::Error::custom("invalid `color` value"))?, + u8::from_str_radix(&s[3..5], 16) + .map_err(|_| de::Error::custom("invalid `color` value"))?, + u8::from_str_radix(&s[5..7], 16) + .map_err(|_| de::Error::custom("invalid `color` value"))?, + )) + } + s if s.starts_with("#") + && s.len() == 4 + && s[1..].as_bytes().iter().all(|&b| { + (b >= b'0' && b <= b'9') || (b >= b'a' && b <= b'f') || (b >= b'A' && b <= b'F') + }) => + { + return Ok(Color::Rgb( + 17 * u8::from_str_radix(&s[1..2], 16) + .map_err(|_| de::Error::custom("invalid `color` value"))?, + 17 * u8::from_str_radix(&s[2..3], 16) + .map_err(|_| de::Error::c |