From 5c42a599540aceddd1de59713be07d201bb4da7b Mon Sep 17 00:00:00 2001 From: Alexandre Bury Date: Mon, 12 Jun 2017 11:59:33 -0700 Subject: [PATCH] Refactor colors management Do not register pairs with backend. Let backend cache color pairs if needed. --- examples/theme_manual.rs | 6 +- src/backend/blt.rs | 7 +- src/backend/curses/n.rs | 82 ++++++++++++--------- src/backend/curses/pan.rs | 20 +++--- src/backend/mod.rs | 5 +- src/backend/termion.rs | 3 +- src/lib.rs | 11 ++- src/printer.rs | 16 ++--- src/theme.rs | 145 ++++++++++++++++++++++++++------------ 9 files changed, 182 insertions(+), 113 deletions(-) diff --git a/examples/theme_manual.rs b/examples/theme_manual.rs index 6229994..ea5f9aa 100644 --- a/examples/theme_manual.rs +++ b/examples/theme_manual.rs @@ -20,9 +20,9 @@ fn main() { theme.shadow = !theme.shadow; theme.borders = match theme.borders { - Some(BorderStyle::Simple) => Some(BorderStyle::Outset), - Some(BorderStyle::Outset) => None, - None => Some(BorderStyle::Simple), + BorderStyle::Simple => BorderStyle::Outset, + BorderStyle::Outset => BorderStyle::None, + BorderStyle::None => BorderStyle::Simple, }; s.set_theme(theme); diff --git a/src/backend/blt.rs b/src/backend/blt.rs index d8c021c..341fa9f 100644 --- a/src/backend/blt.rs +++ b/src/backend/blt.rs @@ -26,9 +26,10 @@ impl backend::Backend for Concrete { fn init_color_style(&mut self, style: ColorStyle, foreground: &Color, background: &Color) { - self.colours.insert(style.id(), - (colour_to_blt_colour(foreground), - colour_to_blt_colour(background))); + self.colours + .insert(style.id(), + (colour_to_blt_colour(foreground), + colour_to_blt_colour(background))); } fn with_color(&self, color: ColorStyle, f: F) { diff --git a/src/backend/curses/n.rs b/src/backend/curses/n.rs index a385836..eabf489 100644 --- a/src/backend/curses/n.rs +++ b/src/backend/curses/n.rs @@ -1,15 +1,54 @@ extern crate ncurses; - use self::super::{color_id, find_closest}; use backend; use event::{Event, Key}; -use std::cell::Cell; -use theme::{Color, ColorStyle, Effect}; +use std::cell::{RefCell, Cell}; +use std::collections::HashMap; +use theme::{ColorPair, ColorStyle, Effect}; use utf8; pub struct Concrete { - current_style: Cell, + current_style: Cell, + pairs: RefCell>, +} + +impl Concrete { + fn insert_color(&self, pairs: &mut HashMap, pair: ColorPair) -> i16 { + + let n = 1 + pairs.len() as i16; + let target = if ncurses::COLOR_PAIRS() > n as i32 { + // We still have plenty of space for everyone. + n + } else { + // The world is too small for both of us. + let target = n - 1; + // Remove the mapping to n-1 + pairs.retain(|_, &mut v| v != target); + target + }; + pairs.insert(pair, target); + ncurses::init_pair(target, + find_closest(&pair.front) as i16, + find_closest(&pair.back) as i16); + target + } + fn set_colorstyle(&self, pair: ColorPair) { + self.current_style.set(pair); + + let mut pairs = self.pairs.borrow_mut(); + + // Find if we have this color in stock + let i = if pairs.contains_key(&pair) { + // We got it! + pairs[&pair] + } else { + self.insert_color(&mut *pairs, pair) + }; + + let style = ncurses::COLOR_PAIR(i); + ncurses::attron(style); + } } impl backend::Backend for Concrete { @@ -28,7 +67,10 @@ impl backend::Backend for Concrete { ncurses::wbkgd(ncurses::stdscr(), ncurses::COLOR_PAIR(color_id(ColorStyle::Background))); - Concrete { current_style: Cell::new(ColorStyle::Background) } + Concrete { + current_style: Cell::new(ColorPair::from_256colors(0, 0)), + pairs: RefCell::new(HashMap::new()), + } } fn screen_size(&self) -> (usize, usize) { @@ -47,21 +89,12 @@ impl backend::Backend for Concrete { } - fn init_color_style(&mut self, style: ColorStyle, foreground: &Color, - background: &Color) { - - ncurses::init_pair(color_id(style), - find_closest(foreground) as i16, - find_closest(background) as i16); - } - - fn with_color(&self, color: ColorStyle, f: F) { + fn with_color(&self, colors: ColorPair, f: F) { let current = self.current_style.get(); - self.current_style.set(color); - set_colorstyle(color); + self.set_colorstyle(colors); f(); - set_colorstyle(current); + self.set_colorstyle(current); self.current_style.set(current); } @@ -109,21 +142,6 @@ impl backend::Backend for Concrete { } } -fn set_colorstyle(style: ColorStyle) { - if let ColorStyle::Custom { - ref front, - ref back, - } = style { - - println_stderr!("Redifining..."); - ncurses::init_pair(color_id(style), - find_closest(front) as i16, - find_closest(back) as i16); - } - let style = ncurses::COLOR_PAIR(color_id(style)); - ncurses::attron(style); -} - /// Returns the Key enum corresponding to the given ncurses event. fn parse_ncurses_char(ch: i32) -> Event { match ch { diff --git a/src/backend/curses/pan.rs b/src/backend/curses/pan.rs index 786d544..5a11a5a 100644 --- a/src/backend/curses/pan.rs +++ b/src/backend/curses/pan.rs @@ -21,7 +21,8 @@ impl backend::Backend for Concrete { pancurses::cbreak(); pancurses::start_color(); pancurses::curs_set(0); - window.bkgd(pancurses::ColorPair(color_id(ColorStyle::Background) as u8)); + window.bkgd(pancurses::ColorPair(color_id(ColorStyle::Background) as + u8)); Concrete { window: window } } @@ -52,7 +53,8 @@ impl backend::Backend for Concrete { self.window.attron(color_attribute); f(); - self.window.attron(pancurses::ColorPair(current_color_pair as u8)); + self.window + .attron(pancurses::ColorPair(current_color_pair as u8)); } fn with_effect(&self, effect: Effect, f: F) { @@ -94,12 +96,14 @@ impl backend::Backend for Concrete { pancurses::Input::Character(c) if 32 <= (c as u32) && (c as u32) <= 255 => { Event::Char(utf8::read_char(c as u8, || { - self.window.getch().and_then(|i| match i { - pancurses::Input::Character(c) => { - Some(c as u8) - } - _ => None, - }) + self.window + .getch() + .and_then(|i| match i { + pancurses::Input::Character(c) => { + Some(c as u8) + } + _ => None, + }) }) .unwrap()) } diff --git a/src/backend/mod.rs b/src/backend/mod.rs index f8289e1..3d4fa19 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -25,9 +25,6 @@ pub trait Backend { fn has_colors(&self) -> bool; - fn init_color_style(&mut self, style: theme::ColorStyle, - foreground: &theme::Color, background: &theme::Color); - fn print_at(&self, (usize, usize), &str); fn poll_event(&self) -> event::Event; @@ -35,6 +32,6 @@ pub trait Backend { fn screen_size(&self) -> (usize, usize); // TODO: unify those into a single method? - fn with_color(&self, color: theme::ColorStyle, f: F); + fn with_color(&self, colors: theme::ColorPair, f: F); fn with_effect(&self, effect: theme::Effect, f: F); } diff --git a/src/backend/termion.rs b/src/backend/termion.rs index f909b77..72ee325 100644 --- a/src/backend/termion.rs +++ b/src/backend/termion.rs @@ -23,7 +23,8 @@ use theme; pub struct Concrete { terminal: AlternateScreen>, current_style: Cell, - colors: HashMap, Box)>, + colors: + HashMap, Box)>, input: chan::Receiver, resize: chan::Receiver, diff --git a/src/lib.rs b/src/lib.rs index 705048a..7c1fe2e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -169,10 +169,10 @@ new_default!(Cursive); impl Cursive { /// Creates a new Cursive root, and initialize the back-end. pub fn new() -> Self { - let mut backend = backend::Concrete::init(); + let backend = backend::Concrete::init(); let theme = theme::load_default(); - theme.activate(&mut backend); + // theme.activate(&mut backend); // let theme = theme::load_theme("assets/style.toml").unwrap(); let (tx, rx) = mpsc::channel(); @@ -286,7 +286,7 @@ impl Cursive { /// Sets the current theme. pub fn set_theme(&mut self, theme: theme::Theme) { self.theme = theme; - self.theme.activate(&mut self.backend); + // self.theme.activate(&mut self.backend); self.backend.clear(); } @@ -533,9 +533,6 @@ impl Cursive { } fn draw(&mut self) { - // TODO: don't clone the theme - // Reference it or something - let sizes = self.screen().layer_sizes(); if self.last_sizes != sizes { self.clear(); @@ -543,7 +540,7 @@ impl Cursive { } let printer = Printer::new(self.screen_size(), - self.theme.clone(), + &self.theme, &self.backend); // Draw the currently active screen diff --git a/src/printer.rs b/src/printer.rs index d8e5f55..d7c9942 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -21,7 +21,7 @@ pub struct Printer<'a> { /// Whether the view to draw is currently focused or not. pub focused: bool, /// Currently used theme - pub theme: Theme, + pub theme: &'a Theme, /// `true` if nothing has been drawn yet. new: Rc>, @@ -34,7 +34,7 @@ impl<'a> Printer<'a> { /// /// But nobody needs to know that. #[doc(hidden)] - pub fn new>(size: T, theme: Theme, + pub fn new>(size: T, theme: &'a Theme, backend: &'a backend::Concrete) -> Self { Printer { @@ -131,7 +131,7 @@ impl<'a> Printer<'a> { pub fn with_color(&self, c: ColorStyle, f: F) where F: FnOnce(&Printer) { - self.backend.with_color(c, || f(self)); + self.backend.with_color(c.resolve(self.theme), || f(self)); } /// Same as `with_color`, but apply a ncurses style instead, @@ -195,8 +195,8 @@ impl<'a> Printer<'a> { where F: FnOnce(&Printer) { let color = match self.theme.borders { - None => return, - Some(BorderStyle::Outset) if !invert => ColorStyle::Tertiary, + BorderStyle::None => return, + BorderStyle::Outset if !invert => ColorStyle::Tertiary, _ => ColorStyle::Primary, }; @@ -213,8 +213,8 @@ impl<'a> Printer<'a> { where F: FnOnce(&Printer) { let color = match self.theme.borders { - None => return, - Some(BorderStyle::Outset) if invert => ColorStyle::Tertiary, + BorderStyle::None => return, + BorderStyle::Outset if invert => ColorStyle::Tertiary, _ => ColorStyle::Primary, }; @@ -260,7 +260,7 @@ impl<'a> Printer<'a> { // We can't be larger than what remains size: Vec2::min(self.size - offset, size), focused: self.focused && focused, - theme: self.theme.clone(), + theme: self.theme, backend: self.backend, new: self.new.clone(), } diff --git a/src/theme.rs b/src/theme.rs index 73a73db..f8c8607 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -114,8 +114,6 @@ //! highlight_inactive = "#5555FF" //! ``` - -use backend::{self, Backend}; use std::fs::File; use std::io; use std::io::Read; @@ -133,6 +131,35 @@ pub enum Effect { // TODO: bold, italic, underline } +/// Combines a front and back color. +#[derive(Clone,Copy,Debug,PartialEq,Eq,Hash)] +pub struct ColorPair { + /// Color used for the foreground. + pub front: Color, + /// Color used for the background. + pub back: Color, +} + +impl ColorPair { + /// Return an inverted color pair. + /// + /// With swapped front abd back color. + pub fn invert(&self) -> Self { + ColorPair { + front: self.back, + back: self.front, + } + } + + /// Creates a new color pair from color IDs. + pub fn from_256colors(front: u8, back: u8) -> Self { + Self { + front: Color::from_256colors(front), + back: Color::from_256colors(back), + } + } +} + /// Possible color style for a cell. /// /// Represents a color pair role to use when printing something. @@ -163,17 +190,39 @@ pub enum ColorStyle { /// Foreground color front: Color, /// Background color - back: Color + back: Color, }, } +impl ColorStyle { + /// Return the color pair that this style represents. + /// + /// Returns `(front, back)`. + pub fn resolve(&self, theme: &Theme) -> ColorPair { + let c = &theme.colors; + let (front, back) = match *self { + ColorStyle::Background => (c.view, c.background), + ColorStyle::Shadow => (c.shadow, c.shadow), + ColorStyle::Primary => (c.primary, c.view), + ColorStyle::Secondary => (c.secondary, c.view), + ColorStyle::Tertiary => (c.tertiary, c.view), + ColorStyle::TitlePrimary => (c.title_primary, c.view), + ColorStyle::TitleSecondary => (c.title_secondary, c.view), + ColorStyle::Highlight => (c.view, c.highlight), + ColorStyle::HighlightInactive => (c.view, c.highlight_inactive), + ColorStyle::Custom { front, back } => (front, back), + }; + ColorPair { front, back } + } +} + /// Represents the style a Cursive application will use. #[derive(Clone,Debug)] pub struct Theme { /// Whether views in a StackView should have shadows. pub shadow: bool, /// How view borders should be drawn. - pub borders: Option, + pub borders: BorderStyle, /// What colors should be used through the application? pub colors: Palette, } @@ -182,7 +231,7 @@ impl Default for Theme { fn default() -> Self { Theme { shadow: true, - borders: Some(BorderStyle::Simple), + borders: BorderStyle::Simple, colors: Palette { background: Color::Dark(BaseColor::Blue), shadow: Color::Dark(BaseColor::Black), @@ -213,64 +262,29 @@ impl Theme { self.colors.load(table); } } - - /// Sets a theme as active. - /// - /// **Don't use this directly.** Uses [`Cursive::set_theme`] instead. - /// - /// [`Cursive::set_theme`]: ../struct.Cursive.html#method.set_theme - pub fn activate(&self, backend: &mut backend::Concrete) { - // Initialize each color with the backend - backend.init_color_style(ColorStyle::Background, - &self.colors.view, - &self.colors.background); - backend.init_color_style(ColorStyle::Shadow, - &self.colors.shadow, - &self.colors.shadow); - backend.init_color_style(ColorStyle::Primary, - &self.colors.primary, - &self.colors.view); - backend.init_color_style(ColorStyle::Secondary, - &self.colors.secondary, - &self.colors.view); - backend.init_color_style(ColorStyle::Tertiary, - &self.colors.tertiary, - &self.colors.view); - backend.init_color_style(ColorStyle::TitlePrimary, - &self.colors.title_primary, - &self.colors.view); - backend.init_color_style(ColorStyle::TitleSecondary, - &self.colors.title_secondary, - &self.colors.view); - backend.init_color_style(ColorStyle::Highlight, - &self.colors.view, - &self.colors.highlight); - backend.init_color_style(ColorStyle::HighlightInactive, - &self.colors.view, - &self.colors.highlight_inactive); - backend.clear(); - } } /// Specifies how some borders should be drawn. /// /// Borders are used around Dialogs, select popups, and panels. -#[derive(Clone,Copy,Debug)] +#[derive(Clone,Copy,Debug,PartialEq,Eq,Hash)] pub enum BorderStyle { /// Simple borders. Simple, /// Outset borders with a simple 3d effect. Outset, + /// No borders. + None, } impl BorderStyle { - fn from(s: &str) -> Option { + fn from(s: &str) -> Self { if s == "simple" { - Some(BorderStyle::Simple) + BorderStyle::Simple } else if s == "outset" { - Some(BorderStyle::Outset) + BorderStyle::Outset } else { - None + BorderStyle::None } } } @@ -378,6 +392,22 @@ pub enum BaseColor { White, } +impl From for BaseColor { + fn from(n: u8) -> Self { + match n % 8 { + 0 => BaseColor::Black, + 1 => BaseColor::Red, + 2 => BaseColor::Green, + 3 => BaseColor::Yellow, + 4 => BaseColor::Blue, + 5 => BaseColor::Magenta, + 6 => BaseColor::Cyan, + 7 => BaseColor::White, + _ => unreachable!(), + } + } +} + /// Represents a color used by the theme. #[derive(Clone,Copy,Debug,PartialEq,Eq,Hash)] pub enum Color { @@ -417,6 +447,27 @@ impl From for Error { } impl Color { + /// Creates a color from its ID in the 256 colors list. + /// + /// * Colors 0-7 are base dark colors. + /// * Colors 8-15 are base light colors. + /// * Colors 16-255 are rgb colors with 6 values per channel. + pub fn from_256colors(n: u8) -> Self { + if n < 8 { + Color::Dark(BaseColor::from(n)) + } else if n < 16 { + Color::Light(BaseColor::from(n)) + } else { + let n = n - 16; + + let r = n / 36; + let g = (n % 36) / 6; + let b = n % 6; + + Color::RgbLowRes(r, g, b) + } + } + fn parse(value: &str) -> Option { Some(match value { "black" => Color::Dark(BaseColor::Black),