Refactor colors management

Do not register pairs with backend. Let backend cache color pairs if
needed.
This commit is contained in:
Alexandre Bury 2017-06-12 11:59:33 -07:00
parent 9e1a83f7cc
commit 5c42a59954
9 changed files with 182 additions and 113 deletions

View File

@ -20,9 +20,9 @@ fn main() {
theme.shadow = !theme.shadow; theme.shadow = !theme.shadow;
theme.borders = match theme.borders { theme.borders = match theme.borders {
Some(BorderStyle::Simple) => Some(BorderStyle::Outset), BorderStyle::Simple => BorderStyle::Outset,
Some(BorderStyle::Outset) => None, BorderStyle::Outset => BorderStyle::None,
None => Some(BorderStyle::Simple), BorderStyle::None => BorderStyle::Simple,
}; };
s.set_theme(theme); s.set_theme(theme);

View File

@ -26,9 +26,10 @@ impl backend::Backend for Concrete {
fn init_color_style(&mut self, style: ColorStyle, foreground: &Color, fn init_color_style(&mut self, style: ColorStyle, foreground: &Color,
background: &Color) { background: &Color) {
self.colours.insert(style.id(), self.colours
(colour_to_blt_colour(foreground), .insert(style.id(),
colour_to_blt_colour(background))); (colour_to_blt_colour(foreground),
colour_to_blt_colour(background)));
} }
fn with_color<F: FnOnce()>(&self, color: ColorStyle, f: F) { fn with_color<F: FnOnce()>(&self, color: ColorStyle, f: F) {

View File

@ -1,15 +1,54 @@
extern crate ncurses; extern crate ncurses;
use self::super::{color_id, find_closest}; use self::super::{color_id, find_closest};
use backend; use backend;
use event::{Event, Key}; use event::{Event, Key};
use std::cell::Cell; use std::cell::{RefCell, Cell};
use theme::{Color, ColorStyle, Effect}; use std::collections::HashMap;
use theme::{ColorPair, ColorStyle, Effect};
use utf8; use utf8;
pub struct Concrete { pub struct Concrete {
current_style: Cell<ColorStyle>, current_style: Cell<ColorPair>,
pairs: RefCell<HashMap<ColorPair, i16>>,
}
impl Concrete {
fn insert_color(&self, pairs: &mut HashMap<ColorPair, i16>, 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 { impl backend::Backend for Concrete {
@ -28,7 +67,10 @@ impl backend::Backend for Concrete {
ncurses::wbkgd(ncurses::stdscr(), ncurses::wbkgd(ncurses::stdscr(),
ncurses::COLOR_PAIR(color_id(ColorStyle::Background))); 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) { 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, fn with_color<F: FnOnce()>(&self, colors: ColorPair, f: F) {
background: &Color) {
ncurses::init_pair(color_id(style),
find_closest(foreground) as i16,
find_closest(background) as i16);
}
fn with_color<F: FnOnce()>(&self, color: ColorStyle, f: F) {
let current = self.current_style.get(); let current = self.current_style.get();
self.current_style.set(color); self.set_colorstyle(colors);
set_colorstyle(color);
f(); f();
set_colorstyle(current); self.set_colorstyle(current);
self.current_style.set(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. /// Returns the Key enum corresponding to the given ncurses event.
fn parse_ncurses_char(ch: i32) -> Event { fn parse_ncurses_char(ch: i32) -> Event {
match ch { match ch {

View File

@ -21,7 +21,8 @@ impl backend::Backend for Concrete {
pancurses::cbreak(); pancurses::cbreak();
pancurses::start_color(); pancurses::start_color();
pancurses::curs_set(0); 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 } Concrete { window: window }
} }
@ -52,7 +53,8 @@ impl backend::Backend for Concrete {
self.window.attron(color_attribute); self.window.attron(color_attribute);
f(); f();
self.window.attron(pancurses::ColorPair(current_color_pair as u8)); self.window
.attron(pancurses::ColorPair(current_color_pair as u8));
} }
fn with_effect<F: FnOnce()>(&self, effect: Effect, f: F) { fn with_effect<F: FnOnce()>(&self, effect: Effect, f: F) {
@ -94,12 +96,14 @@ impl backend::Backend for Concrete {
pancurses::Input::Character(c) if 32 <= (c as u32) && pancurses::Input::Character(c) if 32 <= (c as u32) &&
(c as u32) <= 255 => { (c as u32) <= 255 => {
Event::Char(utf8::read_char(c as u8, || { Event::Char(utf8::read_char(c as u8, || {
self.window.getch().and_then(|i| match i { self.window
pancurses::Input::Character(c) => { .getch()
Some(c as u8) .and_then(|i| match i {
} pancurses::Input::Character(c) => {
_ => None, Some(c as u8)
}) }
_ => None,
})
}) })
.unwrap()) .unwrap())
} }

View File

@ -25,9 +25,6 @@ pub trait Backend {
fn has_colors(&self) -> bool; 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 print_at(&self, (usize, usize), &str);
fn poll_event(&self) -> event::Event; fn poll_event(&self) -> event::Event;
@ -35,6 +32,6 @@ pub trait Backend {
fn screen_size(&self) -> (usize, usize); fn screen_size(&self) -> (usize, usize);
// TODO: unify those into a single method? // TODO: unify those into a single method?
fn with_color<F: FnOnce()>(&self, color: theme::ColorStyle, f: F); fn with_color<F: FnOnce()>(&self, colors: theme::ColorPair, f: F);
fn with_effect<F: FnOnce()>(&self, effect: theme::Effect, f: F); fn with_effect<F: FnOnce()>(&self, effect: theme::Effect, f: F);
} }

View File

@ -23,7 +23,8 @@ use theme;
pub struct Concrete { pub struct Concrete {
terminal: AlternateScreen<termion::raw::RawTerminal<::std::io::Stdout>>, terminal: AlternateScreen<termion::raw::RawTerminal<::std::io::Stdout>>,
current_style: Cell<theme::ColorStyle>, current_style: Cell<theme::ColorStyle>,
colors: HashMap<theme::ColorStyle, (Box<tcolor::Color>, Box<tcolor::Color>)>, colors:
HashMap<theme::ColorStyle, (Box<tcolor::Color>, Box<tcolor::Color>)>,
input: chan::Receiver<Event>, input: chan::Receiver<Event>,
resize: chan::Receiver<chan_signal::Signal>, resize: chan::Receiver<chan_signal::Signal>,

View File

@ -169,10 +169,10 @@ new_default!(Cursive);
impl Cursive { impl Cursive {
/// Creates a new Cursive root, and initialize the back-end. /// Creates a new Cursive root, and initialize the back-end.
pub fn new() -> Self { pub fn new() -> Self {
let mut backend = backend::Concrete::init(); let backend = backend::Concrete::init();
let theme = theme::load_default(); let theme = theme::load_default();
theme.activate(&mut backend); // theme.activate(&mut backend);
// let theme = theme::load_theme("assets/style.toml").unwrap(); // let theme = theme::load_theme("assets/style.toml").unwrap();
let (tx, rx) = mpsc::channel(); let (tx, rx) = mpsc::channel();
@ -286,7 +286,7 @@ impl Cursive {
/// Sets the current theme. /// Sets the current theme.
pub fn set_theme(&mut self, theme: theme::Theme) { pub fn set_theme(&mut self, theme: theme::Theme) {
self.theme = theme; self.theme = theme;
self.theme.activate(&mut self.backend); // self.theme.activate(&mut self.backend);
self.backend.clear(); self.backend.clear();
} }
@ -533,9 +533,6 @@ impl Cursive {
} }
fn draw(&mut self) { fn draw(&mut self) {
// TODO: don't clone the theme
// Reference it or something
let sizes = self.screen().layer_sizes(); let sizes = self.screen().layer_sizes();
if self.last_sizes != sizes { if self.last_sizes != sizes {
self.clear(); self.clear();
@ -543,7 +540,7 @@ impl Cursive {
} }
let printer = Printer::new(self.screen_size(), let printer = Printer::new(self.screen_size(),
self.theme.clone(), &self.theme,
&self.backend); &self.backend);
// Draw the currently active screen // Draw the currently active screen

View File

@ -21,7 +21,7 @@ pub struct Printer<'a> {
/// Whether the view to draw is currently focused or not. /// Whether the view to draw is currently focused or not.
pub focused: bool, pub focused: bool,
/// Currently used theme /// Currently used theme
pub theme: Theme, pub theme: &'a Theme,
/// `true` if nothing has been drawn yet. /// `true` if nothing has been drawn yet.
new: Rc<Cell<bool>>, new: Rc<Cell<bool>>,
@ -34,7 +34,7 @@ impl<'a> Printer<'a> {
/// ///
/// But nobody needs to know that. /// But nobody needs to know that.
#[doc(hidden)] #[doc(hidden)]
pub fn new<T: Into<Vec2>>(size: T, theme: Theme, pub fn new<T: Into<Vec2>>(size: T, theme: &'a Theme,
backend: &'a backend::Concrete) backend: &'a backend::Concrete)
-> Self { -> Self {
Printer { Printer {
@ -131,7 +131,7 @@ impl<'a> Printer<'a> {
pub fn with_color<F>(&self, c: ColorStyle, f: F) pub fn with_color<F>(&self, c: ColorStyle, f: F)
where F: FnOnce(&Printer) 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, /// Same as `with_color`, but apply a ncurses style instead,
@ -195,8 +195,8 @@ impl<'a> Printer<'a> {
where F: FnOnce(&Printer) where F: FnOnce(&Printer)
{ {
let color = match self.theme.borders { let color = match self.theme.borders {
None => return, BorderStyle::None => return,
Some(BorderStyle::Outset) if !invert => ColorStyle::Tertiary, BorderStyle::Outset if !invert => ColorStyle::Tertiary,
_ => ColorStyle::Primary, _ => ColorStyle::Primary,
}; };
@ -213,8 +213,8 @@ impl<'a> Printer<'a> {
where F: FnOnce(&Printer) where F: FnOnce(&Printer)
{ {
let color = match self.theme.borders { let color = match self.theme.borders {
None => return, BorderStyle::None => return,
Some(BorderStyle::Outset) if invert => ColorStyle::Tertiary, BorderStyle::Outset if invert => ColorStyle::Tertiary,
_ => ColorStyle::Primary, _ => ColorStyle::Primary,
}; };
@ -260,7 +260,7 @@ impl<'a> Printer<'a> {
// We can't be larger than what remains // We can't be larger than what remains
size: Vec2::min(self.size - offset, size), size: Vec2::min(self.size - offset, size),
focused: self.focused && focused, focused: self.focused && focused,
theme: self.theme.clone(), theme: self.theme,
backend: self.backend, backend: self.backend,
new: self.new.clone(), new: self.new.clone(),
} }

View File

@ -114,8 +114,6 @@
//! highlight_inactive = "#5555FF" //! highlight_inactive = "#5555FF"
//! ``` //! ```
use backend::{self, Backend};
use std::fs::File; use std::fs::File;
use std::io; use std::io;
use std::io::Read; use std::io::Read;
@ -133,6 +131,35 @@ pub enum Effect {
// TODO: bold, italic, underline // 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. /// Possible color style for a cell.
/// ///
/// Represents a color pair role to use when printing something. /// Represents a color pair role to use when printing something.
@ -163,17 +190,39 @@ pub enum ColorStyle {
/// Foreground color /// Foreground color
front: Color, front: Color,
/// Background 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. /// Represents the style a Cursive application will use.
#[derive(Clone,Debug)] #[derive(Clone,Debug)]
pub struct Theme { pub struct Theme {
/// Whether views in a StackView should have shadows. /// Whether views in a StackView should have shadows.
pub shadow: bool, pub shadow: bool,
/// How view borders should be drawn. /// How view borders should be drawn.
pub borders: Option<BorderStyle>, pub borders: BorderStyle,
/// What colors should be used through the application? /// What colors should be used through the application?
pub colors: Palette, pub colors: Palette,
} }
@ -182,7 +231,7 @@ impl Default for Theme {
fn default() -> Self { fn default() -> Self {
Theme { Theme {
shadow: true, shadow: true,
borders: Some(BorderStyle::Simple), borders: BorderStyle::Simple,
colors: Palette { colors: Palette {
background: Color::Dark(BaseColor::Blue), background: Color::Dark(BaseColor::Blue),
shadow: Color::Dark(BaseColor::Black), shadow: Color::Dark(BaseColor::Black),
@ -213,64 +262,29 @@ impl Theme {
self.colors.load(table); 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. /// Specifies how some borders should be drawn.
/// ///
/// Borders are used around Dialogs, select popups, and panels. /// Borders are used around Dialogs, select popups, and panels.
#[derive(Clone,Copy,Debug)] #[derive(Clone,Copy,Debug,PartialEq,Eq,Hash)]
pub enum BorderStyle { pub enum BorderStyle {
/// Simple borders. /// Simple borders.
Simple, Simple,
/// Outset borders with a simple 3d effect. /// Outset borders with a simple 3d effect.
Outset, Outset,
/// No borders.
None,
} }
impl BorderStyle { impl BorderStyle {
fn from(s: &str) -> Option<Self> { fn from(s: &str) -> Self {
if s == "simple" { if s == "simple" {
Some(BorderStyle::Simple) BorderStyle::Simple
} else if s == "outset" { } else if s == "outset" {
Some(BorderStyle::Outset) BorderStyle::Outset
} else { } else {
None BorderStyle::None
} }
} }
} }
@ -378,6 +392,22 @@ pub enum BaseColor {
White, White,
} }
impl From<u8> 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. /// Represents a color used by the theme.
#[derive(Clone,Copy,Debug,PartialEq,Eq,Hash)] #[derive(Clone,Copy,Debug,PartialEq,Eq,Hash)]
pub enum Color { pub enum Color {
@ -417,6 +447,27 @@ impl From<toml::de::Error> for Error {
} }
impl Color { 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<Self> { fn parse(value: &str) -> Option<Self> {
Some(match value { Some(match value {
"black" => Color::Dark(BaseColor::Black), "black" => Color::Dark(BaseColor::Black),