diff --git a/examples/colors.rs b/examples/colors.rs new file mode 100644 index 0000000..8b2e834 --- /dev/null +++ b/examples/colors.rs @@ -0,0 +1,45 @@ +extern crate cursive; + +use cursive::{Cursive, Printer}; +use cursive::theme::{ColorStyle, Color}; +use cursive::view::Boxable; +use cursive::views::Canvas; + +fn main() { + let mut siv = Cursive::new(); + + siv.add_layer(Canvas::new(()).with_draw(draw).fixed_size((20, 10))); + + siv.add_global_callback('q', |s| s.quit()); + + siv.run(); +} + +fn front_color(x: u8, y: u8, x_max: u8, y_max: u8) -> Color { + Color::Rgb(x * (255 / x_max), + y * (255 / y_max), + (x + 2 * y) * (255 / (x_max + 2 * y_max))) +} + +fn back_color(x: u8, y: u8, x_max: u8, y_max: u8) -> Color { + + Color::Rgb(128 + (2 * y_max + x - 2 * y) * (128 / (x_max + 2 * y_max)), + 255 - y * (255 / y_max), + 255 - x * (255 / x_max)) +} + +fn draw(p: &Printer, _: &()) { + let x_max = p.size.x as u8; + let y_max = p.size.y as u8; + + for x in 0..x_max { + for y in 0..y_max { + let style = ColorStyle::Custom { + front: front_color(x, y, x_max, y_max), + back: back_color(x, y, x_max, y_max), + }; + + p.with_color(style, |printer| { printer.print((x, y), "+"); }); + } + } +} diff --git a/examples/theme_manual.rs b/examples/theme_manual.rs index b7b5e57..ea5f9aa 100644 --- a/examples/theme_manual.rs +++ b/examples/theme_manual.rs @@ -1,26 +1,33 @@ extern crate cursive; use cursive::Cursive; -use cursive::views::Dialog; -use cursive::theme::BorderStyle; +use cursive::theme::{ColorStyle, BaseColor, Color, BorderStyle}; +use cursive::views::{EditView, LinearLayout, Dialog, TextView}; fn main() { let mut siv = Cursive::new(); - siv.add_layer(Dialog::text("This is a dynamic theme example!") - .button("Change", |s| { - let mut theme = s.current_theme().clone(); + let layout = LinearLayout::vertical() + .child(TextView::new("This is a dynamic theme example!")) + .child(EditView::new().content("Woo! colors!").style(ColorStyle::Custom { + front: Color::Rgb(200, 150, 150), + back: Color::Dark(BaseColor::Blue), + })); - theme.shadow = !theme.shadow; - theme.borders = match theme.borders { - Some(BorderStyle::Simple) => Some(BorderStyle::Outset), - Some(BorderStyle::Outset) => None, - None => Some(BorderStyle::Simple), - }; + siv.add_layer(Dialog::around(layout) + .button("Change", |s| { + let mut theme = s.current_theme().clone(); - s.set_theme(theme); - }) - .button("Quit", Cursive::quit)); + theme.shadow = !theme.shadow; + theme.borders = match theme.borders { + BorderStyle::Simple => BorderStyle::Outset, + BorderStyle::Outset => BorderStyle::None, + BorderStyle::None => BorderStyle::Simple, + }; + + s.set_theme(theme); + }) + .button("Quit", Cursive::quit)); siv.run(); } diff --git a/src/backend/blt.rs b/src/backend/blt.rs index d8c021c..28dd1df 100644 --- a/src/backend/blt.rs +++ b/src/backend/blt.rs @@ -6,7 +6,7 @@ use self::bear_lib_terminal::terminal::{self, Event as BltEvent, KeyCode}; use backend; use event::{Event, Key}; use std::collections::BTreeMap; -use theme::{BaseColor, Color, ColorStyle, Effect}; +use theme::{BaseColor, Color, ColorPair, Effect}; pub struct Concrete { colours: BTreeMap, @@ -24,15 +24,9 @@ impl backend::Backend for Concrete { terminal::close(); } - 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))); - } - - fn with_color(&self, color: ColorStyle, f: F) { - let (fg, bg) = self.colours[&color.id()]; + fn with_color(&self, color: ColorPair, f: F) { + let fg = colour_to_blt_colour(color.front); + let bg = colour_to_blt_colour(color.back); terminal::with_colors(fg, bg, f); } @@ -60,7 +54,8 @@ impl backend::Backend for Concrete { (width as usize, height as usize) } - fn clear(&self) { + fn clear(&self, color: Color) { + terminal::set_background(colour_to_blt_colour(color)); terminal::clear(None); } @@ -103,8 +98,8 @@ impl backend::Backend for Concrete { } } -fn colour_to_blt_colour(clr: &Color) -> BltColor { - let (r, g, b) = match *clr { +fn colour_to_blt_colour(clr: Color) -> BltColor { + let (r, g, b) = match clr { // Colours taken from // https://en.wikipedia.org/wiki/ANSI_escape_code#Colors Color::Dark(BaseColor::Black) => (0, 0, 0), @@ -140,11 +135,11 @@ fn blt_keycode_to_ev(kc: KeyCode, shift: bool, ctrl: bool) -> Event { KeyCode::F1 | KeyCode::F2 | KeyCode::F3 | KeyCode::F4 | KeyCode::F5 | KeyCode::F6 | KeyCode::F7 | KeyCode::F8 | KeyCode::F9 | KeyCode::F10 | KeyCode::F11 | KeyCode::F12 | - KeyCode::Enter | KeyCode::Escape | KeyCode::Backspace | - KeyCode::Tab | KeyCode::Pause | KeyCode::Insert | KeyCode::Home | - KeyCode::PageUp | KeyCode::Delete | KeyCode::End | - KeyCode::PageDown | KeyCode::Right | KeyCode::Left | - KeyCode::Down | KeyCode::Up => { + KeyCode::NumEnter | KeyCode::Enter | KeyCode::Escape | + KeyCode::Backspace | KeyCode::Tab | KeyCode::Pause | + KeyCode::Insert | KeyCode::Home | KeyCode::PageUp | + KeyCode::Delete | KeyCode::End | KeyCode::PageDown | + KeyCode::Right | KeyCode::Left | KeyCode::Down | KeyCode::Up => { match (shift, ctrl) { (true, true) => Event::CtrlShift(blt_keycode_to_key(kc)), (true, false) => Event::Shift(blt_keycode_to_key(kc)), @@ -168,10 +163,9 @@ fn blt_keycode_to_ev(kc: KeyCode, shift: bool, ctrl: bool) -> Event { KeyCode::Apostrophe | KeyCode::Comma | KeyCode::Period | KeyCode::Slash | KeyCode::Space | KeyCode::NumDivide | KeyCode::NumMultiply | KeyCode::NumMinus | KeyCode::NumPlus | - KeyCode::NumEnter | KeyCode::NumPeriod | KeyCode::Num1 | - KeyCode::Num2 | KeyCode::Num3 | KeyCode::Num4 | KeyCode::Num5 | - KeyCode::Num6 | KeyCode::Num7 | KeyCode::Num8 | KeyCode::Num9 | - KeyCode::Num0 => { + KeyCode::NumPeriod | KeyCode::Num1 | KeyCode::Num2 | + KeyCode::Num3 | KeyCode::Num4 | KeyCode::Num5 | KeyCode::Num6 | + KeyCode::Num7 | KeyCode::Num8 | KeyCode::Num9 | KeyCode::Num0 => { if ctrl { Event::CtrlChar(blt_keycode_to_char(kc, shift)) } else { @@ -246,7 +240,10 @@ fn blt_keycode_to_char(kc: KeyCode, shift: bool) -> char { KeyCode::Num8 => '8', KeyCode::Num9 => '9', KeyCode::Num0 => '0', - _ => unreachable!(), + _ => { + println_stderr!("Found unknown input: {:?}", kc); + unreachable!() + } } } @@ -264,7 +261,7 @@ fn blt_keycode_to_key(kc: KeyCode) -> Key { KeyCode::F10 => Key::F10, KeyCode::F11 => Key::F11, KeyCode::F12 => Key::F12, - KeyCode::Enter => Key::Enter, + KeyCode::NumEnter | KeyCode::Enter => Key::Enter, KeyCode::Escape => Key::Esc, KeyCode::Backspace => Key::Backspace, KeyCode::Tab => Key::Tab, diff --git a/src/backend/curses/mod.rs b/src/backend/curses/mod.rs index fa5a6df..9cfbf43 100644 --- a/src/backend/curses/mod.rs +++ b/src/backend/curses/mod.rs @@ -11,7 +11,7 @@ mod pan; pub use self::pan::*; -fn find_closest(color: &Color) -> u8 { +fn find_closest(color: &Color) -> i16 { match *color { Color::Dark(BaseColor::Black) => 0, Color::Dark(BaseColor::Red) => 1, @@ -33,8 +33,8 @@ fn find_closest(color: &Color) -> u8 { let r = 6 * r as u16 / 256; let g = 6 * g as u16 / 256; let b = 6 * b as u16 / 256; - (16 + 36 * r + 6 * g + b) as u8 + (16 + 36 * r + 6 * g + b) as i16 } - Color::RgbLowRes(r, g, b) => (16 + 36 * r + 6 * g + b) as u8, + Color::RgbLowRes(r, g, b) => (16 + 36 * r + 6 * g + b) as i16, } } diff --git a/src/backend/curses/n.rs b/src/backend/curses/n.rs index 00cff61..fcf3ace 100644 --- a/src/backend/curses/n.rs +++ b/src/backend/curses/n.rs @@ -1,13 +1,65 @@ extern crate ncurses; - use self::super::find_closest; use backend; use event::{Event, Key}; -use theme::{Color, ColorStyle, Effect}; +use std::cell::{RefCell, Cell}; +use std::collections::HashMap; +use theme::{Color, ColorPair, Effect}; use utf8; -pub struct Concrete; +pub struct Concrete { + current_style: Cell, + pairs: RefCell>, +} + +impl Concrete { + /// Save a new color pair. + 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), + find_closest(&pair.back)); + target + } + + /// Checks the pair in the cache, or re-define a color if needed. + fn get_or_create(&self, pair: ColorPair) -> i16 { + + let mut pairs = self.pairs.borrow_mut(); + + // Find if we have this color in stock + if pairs.contains_key(&pair) { + // We got it! + pairs[&pair] + } else { + self.insert_color(&mut *pairs, pair) + } + } + + fn set_colors(&self, pair: ColorPair) { + + let i = self.get_or_create(pair); + + self.current_style.set(pair); + let style = ncurses::COLOR_PAIR(i); + ncurses::attron(style); + } +} impl backend::Backend for Concrete { fn init() -> Self { @@ -22,10 +74,11 @@ impl backend::Backend for Concrete { ncurses::cbreak(); ncurses::start_color(); ncurses::curs_set(ncurses::CURSOR_VISIBILITY::CURSOR_INVISIBLE); - ncurses::wbkgd(ncurses::stdscr(), - ncurses::COLOR_PAIR(ColorStyle::Background.id())); - Concrete + Concrete { + current_style: Cell::new(ColorPair::from_256colors(0, 0)), + pairs: RefCell::new(HashMap::new()), + } } fn screen_size(&self) -> (usize, usize) { @@ -44,25 +97,17 @@ impl backend::Backend for Concrete { } - fn init_color_style(&mut self, style: ColorStyle, foreground: &Color, - background: &Color) { - // TODO: build the color on the spot + fn with_color(&self, colors: ColorPair, f: F) { + let current = self.current_style.get(); + if current != colors { + self.set_colors(colors); + } - ncurses::init_pair(style.id(), - find_closest(foreground) as i16, - find_closest(background) as i16); - } - - fn with_color(&self, color: ColorStyle, f: F) { - let mut current_style: ncurses::attr_t = 0; - let mut current_color: i16 = 0; - ncurses::attr_get(&mut current_style, &mut current_color); - - let style = ncurses::COLOR_PAIR(color.id()); - ncurses::attron(style); f(); - // ncurses::attroff(style); - ncurses::attron(current_style); + + if current != colors { + self.set_colors(current); + } } fn with_effect(&self, effect: Effect, f: F) { @@ -75,7 +120,13 @@ impl backend::Backend for Concrete { ncurses::attroff(style); } - fn clear(&self) { + fn clear(&self, color: Color) { + let id = self.get_or_create(ColorPair { + front: color, + back: color, + }); + ncurses::wbkgd(ncurses::stdscr(), ncurses::COLOR_PAIR(id)); + ncurses::clear(); } diff --git a/src/backend/curses/pan.rs b/src/backend/curses/pan.rs index 0ba96bf..148388a 100644 --- a/src/backend/curses/pan.rs +++ b/src/backend/curses/pan.rs @@ -1,17 +1,69 @@ extern crate pancurses; - - use self::super::find_closest; use backend; use event::{Event, Key}; -use theme::{Color, ColorStyle, Effect}; +use std::cell::{RefCell, Cell}; +use std::collections::HashMap; +use theme::{Color, ColorPair, Effect}; use utf8; pub struct Concrete { + current_style: Cell, + pairs: RefCell>, window: pancurses::Window, } +impl Concrete { + /// Save a new color pair. + fn insert_color(&self, pairs: &mut HashMap, + pair: ColorPair) + -> i32 { + + let n = 1 + pairs.len() as i32; + + // TODO: when COLORS_PAIRS is available... + let target = if 16 > n { + // 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); + pancurses::init_pair(target as i16, + find_closest(&pair.front), + find_closest(&pair.back)); + target + } + + /// Checks the pair in the cache, or re-define a color if needed. + fn get_or_create(&self, pair: ColorPair) -> i32 { + + let mut pairs = self.pairs.borrow_mut(); + + // Find if we have this color in stock + if pairs.contains_key(&pair) { + // We got it! + pairs[&pair] + } else { + self.insert_color(&mut *pairs, pair) + } + } + + fn set_colors(&self, pair: ColorPair) { + + let i = self.get_or_create(pair); + + self.current_style.set(pair); + let style = pancurses::COLOR_PAIR(i as pancurses::chtype); + self.window.attron(style); + } +} + impl backend::Backend for Concrete { fn init() -> Self { let window = pancurses::initscr(); @@ -21,9 +73,12 @@ impl backend::Backend for Concrete { pancurses::cbreak(); pancurses::start_color(); pancurses::curs_set(0); - window.bkgd(pancurses::ColorPair(ColorStyle::Background.id() as u8)); - Concrete { window: window } + Concrete { + current_style: Cell::new(ColorPair::from_256colors(0, 0)), + pairs: RefCell::new(HashMap::new()), + window: window, + } } fn screen_size(&self) -> (usize, usize) { @@ -39,20 +94,18 @@ impl backend::Backend for Concrete { pancurses::endwin(); } - fn init_color_style(&mut self, style: ColorStyle, foreground: &Color, - background: &Color) { - pancurses::init_pair(style.id(), - find_closest(foreground) as i16, - find_closest(background) as i16); - } + fn with_color(&self, colors: ColorPair, f: F) { + let current = self.current_style.get(); - fn with_color(&self, color: ColorStyle, f: F) { - let (_, current_color_pair) = self.window.attrget(); - let color_attribute = pancurses::ColorPair(color.id() as u8); + if current != colors { + self.set_colors(colors); + } - self.window.attron(color_attribute); f(); - self.window.attron(pancurses::ColorPair(current_color_pair as u8)); + + if current != colors { + self.set_colors(current); + } } fn with_effect(&self, effect: Effect, f: F) { @@ -65,7 +118,12 @@ impl backend::Backend for Concrete { self.window.attroff(style); } - fn clear(&self) { + fn clear(&self, color: Color) { + let id = self.get_or_create(ColorPair { + front: color, + back: color, + }); + self.window.bkgd(pancurses::ColorPair(id as u8)); self.window.clear(); } @@ -94,12 +152,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..41ff8a2 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -18,23 +18,23 @@ pub use self::termion::*; pub trait Backend { fn init() -> Self; // TODO: take `self` by value? + // Or implement Drop? fn finish(&mut self); - fn clear(&self); fn refresh(&mut self); 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; - fn set_refresh_rate(&mut self, fps: u32); fn screen_size(&self) -> (usize, usize); + /// Main input method + fn poll_event(&self) -> event::Event; + + /// Main method used for printing + fn print_at(&self, (usize, usize), &str); + fn clear(&self, color: theme::Color); + + fn set_refresh_rate(&mut self, fps: u32); // 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 8776fe3..567e564 100644 --- a/src/backend/termion.rs +++ b/src/backend/termion.rs @@ -13,8 +13,6 @@ use backend; use chan; use event::{Event, Key}; use std::cell::Cell; -use std::collections::BTreeMap; -use std::fmt; use std::io::Write; use std::thread; @@ -22,9 +20,7 @@ use theme; pub struct Concrete { terminal: AlternateScreen>, - current_style: Cell, - colors: BTreeMap, Box)>, - + current_style: Cell, input: chan::Receiver, resize: chan::Receiver, timeout: Option, @@ -35,20 +31,6 @@ trait Effectable { fn off(&self); } -struct ColorRef<'a>(&'a tcolor::Color); - -impl<'a> tcolor::Color for ColorRef<'a> { - #[inline] - fn write_fg(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.0.write_fg(f) - } - - #[inline] - fn write_bg(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.0.write_bg(f) - } -} - impl Effectable for theme::Effect { fn on(&self) { match *self { @@ -65,14 +47,10 @@ impl Effectable for theme::Effect { } } -fn apply_colors(fg: &tcolor::Color, bg: &tcolor::Color) { - print!("{}{}", tcolor::Fg(ColorRef(fg)), tcolor::Bg(ColorRef(bg))); -} - impl Concrete { - fn apply_colorstyle(&self, color_style: theme::ColorStyle) { - let (ref fg, ref bg) = self.colors[&color_style.id()]; - apply_colors(&**fg, &**bg); + fn apply_colors(&self, colors: theme::ColorPair) { + with_color(&colors.front, |c| print!("{}", tcolor::Fg(c))); + with_color(&colors.back, |c| print!("{}", tcolor::Bg(c))); } } @@ -82,7 +60,9 @@ impl backend::Backend for Concrete { let resize = chan_signal::notify(&[chan_signal::Signal::WINCH]); - let terminal = AlternateScreen::from(::std::io::stdout().into_raw_mode().unwrap()); + let terminal = AlternateScreen::from(::std::io::stdout() + .into_raw_mode() + .unwrap()); let (sender, receiver) = chan::async(); thread::spawn(move || for key in ::std::io::stdin().events() { @@ -93,8 +73,7 @@ impl backend::Backend for Concrete { let backend = Concrete { terminal: terminal, - current_style: Cell::new(theme::ColorStyle::Background), - colors: BTreeMap::new(), + current_style: Cell::new(theme::ColorPair::from_256colors(0, 0)), input: receiver, resize: resize, timeout: None, @@ -111,24 +90,20 @@ impl backend::Backend for Concrete { termion::clear::All); } - fn init_color_style(&mut self, style: theme::ColorStyle, - foreground: &theme::Color, background: &theme::Color) { - // Step 1: convert foreground and background into proper termion Color - self.colors.insert(style.id(), - (colour_to_termion_colour(foreground), - colour_to_termion_colour(background))); - } - - fn with_color(&self, color: theme::ColorStyle, f: F) { + fn with_color(&self, color: theme::ColorPair, f: F) { let current_style = self.current_style.get(); - self.apply_colorstyle(color); + if current_style != color { + self.apply_colors(color); + self.current_style.set(color); + } - self.current_style.set(color); f(); - self.current_style.set(current_style); - self.apply_colorstyle(current_style); + if current_style != color { + self.current_style.set(current_style); + self.apply_colors(current_style); + } } fn with_effect(&self, effect: theme::Effect, f: F) { @@ -147,8 +122,11 @@ impl backend::Backend for Concrete { (x as usize, y as usize) } - fn clear(&self) { - self.apply_colorstyle(theme::ColorStyle::Background); + fn clear(&self, color: theme::Color) { + self.apply_colors(theme::ColorPair { + front: color, + back: color, + }); print!("{}", termion::clear::All); } @@ -214,49 +192,43 @@ fn map_key(event: TEvent) -> Event { } -fn colour_to_termion_colour(clr: &theme::Color) -> Box { +fn with_color(clr: &theme::Color, f: F) -> R + where F: FnOnce(&tcolor::Color) -> R +{ + match *clr { - theme::Color::Dark(theme::BaseColor::Black) => Box::new(tcolor::Black), - theme::Color::Dark(theme::BaseColor::Red) => Box::new(tcolor::Red), - theme::Color::Dark(theme::BaseColor::Green) => Box::new(tcolor::Green), - theme::Color::Dark(theme::BaseColor::Yellow) => { - Box::new(tcolor::Yellow) - } - theme::Color::Dark(theme::BaseColor::Blue) => Box::new(tcolor::Blue), - theme::Color::Dark(theme::BaseColor::Magenta) => { - Box::new(tcolor::Magenta) - } - theme::Color::Dark(theme::BaseColor::Cyan) => Box::new(tcolor::Cyan), - theme::Color::Dark(theme::BaseColor::White) => Box::new(tcolor::White), + theme::Color::Dark(theme::BaseColor::Black) => f(&tcolor::Black), + theme::Color::Dark(theme::BaseColor::Red) => f(&tcolor::Red), + theme::Color::Dark(theme::BaseColor::Green) => f(&tcolor::Green), + theme::Color::Dark(theme::BaseColor::Yellow) => f(&tcolor::Yellow), + theme::Color::Dark(theme::BaseColor::Blue) => f(&tcolor::Blue), + theme::Color::Dark(theme::BaseColor::Magenta) => f(&tcolor::Magenta), + theme::Color::Dark(theme::BaseColor::Cyan) => f(&tcolor::Cyan), + theme::Color::Dark(theme::BaseColor::White) => f(&tcolor::White), - theme::Color::Light(theme::BaseColor::Black) => { - Box::new(tcolor::LightBlack) - } - theme::Color::Light(theme::BaseColor::Red) => { - Box::new(tcolor::LightRed) - } - theme::Color::Light(theme::BaseColor::Green) => { - Box::new(tcolor::LightGreen) - } - theme::Color::Light(theme::BaseColor::Yellow) => { - Box::new(tcolor::LightYellow) - } - theme::Color::Light(theme::BaseColor::Blue) => { - Box::new(tcolor::LightBlue) - } - theme::Color::Light(theme::BaseColor::Magenta) => { - Box::new(tcolor::LightMagenta) - } - theme::Color::Light(theme::BaseColor::Cyan) => { - Box::new(tcolor::LightCyan) - } - theme::Color::Light(theme::BaseColor::White) => { - Box::new(tcolor::LightWhite) - } + theme::Color::Light(theme::BaseColor::Black) => { + f(&tcolor::LightBlack) + } + theme::Color::Light(theme::BaseColor::Red) => f(&tcolor::LightRed), + theme::Color::Light(theme::BaseColor::Green) => { + f(&tcolor::LightGreen) + } + theme::Color::Light(theme::BaseColor::Yellow) => { + f(&tcolor::LightYellow) + } + theme::Color::Light(theme::BaseColor::Blue) => f(&tcolor::LightBlue), + theme::Color::Light(theme::BaseColor::Magenta) => { + f(&tcolor::LightMagenta) + } + theme::Color::Light(theme::BaseColor::Cyan) => f(&tcolor::LightCyan), + theme::Color::Light(theme::BaseColor::White) => { + f(&tcolor::LightWhite) + } - theme::Color::Rgb(r, g, b) => Box::new(tcolor::Rgb(r, g, b)), - theme::Color::RgbLowRes(r, g, b) => { - Box::new(tcolor::AnsiValue::rgb(r, g, b)) - } - } + theme::Color::Rgb(r, g, b) => f(&tcolor::Rgb(r, g, b)), + theme::Color::RgbLowRes(r, g, b) => { + f(&tcolor::AnsiValue::rgb(r, g, b)) + } + + } } diff --git a/src/lib.rs b/src/lib.rs index dcf5b78..2f68148 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -170,10 +170,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(); @@ -287,15 +287,15 @@ 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.backend.clear(); + // self.theme.activate(&mut self.backend); + self.clear(); } /// Clears the screen. /// /// Users rarely have to call this directly. pub fn clear(&self) { - self.backend.clear(); + self.backend.clear(self.theme.colors.background); } /// Loads a theme from the given file. @@ -534,9 +534,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(); @@ -544,7 +541,7 @@ impl Cursive { } let printer = Printer::new(self.screen_size(), - self.theme.clone(), + &self.theme, &self.backend); // Draw the currently active screen @@ -625,7 +622,7 @@ impl Cursive { } if event == Event::WindowResize { - self.backend.clear(); + self.clear(); } // Event dispatch order: diff --git a/src/printer.rs b/src/printer.rs index d8e5f55..71f5124 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 { @@ -53,7 +53,7 @@ impl<'a> Printer<'a> { /// /// Users rarely need to call this directly. pub fn clear(&self) { - self.backend.clear(); + self.backend.clear(self.theme.colors.background); } /// Returns `true` if nothing has been printed yet. @@ -123,7 +123,8 @@ impl<'a> Printer<'a> { /// # use cursive::theme; /// # use cursive::backend::{self, Backend}; /// # let b = backend::Concrete::init(); - /// # let printer = Printer::new((6,4), theme::load_default(), &b); + /// # let t = theme::load_default(); + /// # let printer = Printer::new((6,4), &t, &b); /// printer.with_color(theme::ColorStyle::Highlight, |printer| { /// printer.print((0,0), "This text is highlighted!"); /// }); @@ -131,7 +132,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, @@ -156,7 +157,8 @@ impl<'a> Printer<'a> { /// # use cursive::theme; /// # use cursive::backend::{self, Backend}; /// # let b = backend::Concrete::init(); - /// # let printer = Printer::new((6,4), theme::load_default(), &b); + /// # let t = theme::load_default(); + /// # let printer = Printer::new((6,4), &t, &b); /// printer.print_box((0,0), (6,4), false); /// ``` pub fn print_box, S: Into>(&self, start: T, size: S, @@ -195,8 +197,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 +215,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 +262,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 5b4f0e4..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; @@ -129,16 +127,45 @@ pub enum Effect { /// No effect Simple, /// Reverses foreground and background colors - Reverse, + Reverse, // 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. /// /// The current theme will assign each role a foreground and background color. -#[derive(Clone,Copy)] +#[derive(Clone,Copy,Debug,PartialEq,Eq,Hash)] pub enum ColorStyle { /// Application background, where no view is present. Background, @@ -158,22 +185,34 @@ pub enum ColorStyle { Highlight, /// Highlight color for inactive views (not in focus). HighlightInactive, + /// Directly specifies colors, independently of the theme. + Custom { + /// Foreground color + front: Color, + /// Background color + back: Color, + }, } impl ColorStyle { - /// Returns the ncurses pair ID associated with this color pair. - pub fn id(self) -> i16 { - match self { - ColorStyle::Background => 1, - ColorStyle::Shadow => 2, - ColorStyle::Primary => 3, - ColorStyle::Secondary => 4, - ColorStyle::Tertiary => 5, - ColorStyle::TitlePrimary => 6, - ColorStyle::TitleSecondary => 7, - ColorStyle::Highlight => 8, - ColorStyle::HighlightInactive => 9, - } + /// 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 } } } @@ -183,7 +222,7 @@ 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, } @@ -192,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), @@ -223,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 } } } @@ -288,7 +292,7 @@ impl BorderStyle { /// Color configuration for the application. /// /// Assign each color role an actual color. -#[derive(Clone,Debug)] +#[derive(Copy,Clone,Debug)] pub struct Palette { /// Color used for the application background. pub background: Color, @@ -352,7 +356,7 @@ fn load_color(target: &mut Color, value: Option<&toml::Value>) -> bool { } /// One of the 8 base colors. -#[derive(Clone,Copy,Debug)] +#[derive(Clone,Copy,Debug,PartialEq,Eq,Hash)] pub enum BaseColor { /// Black color /// @@ -388,8 +392,24 @@ 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)] +#[derive(Clone,Copy,Debug,PartialEq,Eq,Hash)] pub enum Color { /// One of the 8 base colors. Dark(BaseColor), @@ -405,8 +425,6 @@ pub enum Color { RgbLowRes(u8, u8, u8), } -impl Color {} - /// Possible error returned when loading a theme. #[derive(Debug)] pub enum Error { @@ -429,26 +447,47 @@ 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), - "red" => Color::Dark(BaseColor::Red), - "green" => Color::Dark(BaseColor::Green), - "yellow" => Color::Dark(BaseColor::Yellow), - "blue" => Color::Dark(BaseColor::Blue), - "magenta" => Color::Dark(BaseColor::Magenta), - "cyan" => Color::Dark(BaseColor::Cyan), - "white" => Color::Dark(BaseColor::White), - "light black" => Color::Light(BaseColor::Black), - "light red" => Color::Light(BaseColor::Red), - "light green" => Color::Light(BaseColor::Green), - "light yellow" => Color::Light(BaseColor::Yellow), - "light blue" => Color::Light(BaseColor::Blue), - "light magenta" => Color::Light(BaseColor::Magenta), - "light cyan" => Color::Light(BaseColor::Cyan), - "light white" => Color::Light(BaseColor::White), - value => return Color::parse_special(value), - }) + "black" => Color::Dark(BaseColor::Black), + "red" => Color::Dark(BaseColor::Red), + "green" => Color::Dark(BaseColor::Green), + "yellow" => Color::Dark(BaseColor::Yellow), + "blue" => Color::Dark(BaseColor::Blue), + "magenta" => Color::Dark(BaseColor::Magenta), + "cyan" => Color::Dark(BaseColor::Cyan), + "white" => Color::Dark(BaseColor::White), + "light black" => Color::Light(BaseColor::Black), + "light red" => Color::Light(BaseColor::Red), + "light green" => Color::Light(BaseColor::Green), + "light yellow" => Color::Light(BaseColor::Yellow), + "light blue" => Color::Light(BaseColor::Blue), + "light magenta" => Color::Light(BaseColor::Magenta), + "light cyan" => Color::Light(BaseColor::Cyan), + "light white" => Color::Light(BaseColor::White), + value => return Color::parse_special(value), + }) } fn parse_special(value: &str) -> Option { diff --git a/src/vec.rs b/src/vec.rs index 4a8c316..83aa383 100644 --- a/src/vec.rs +++ b/src/vec.rs @@ -114,6 +114,11 @@ impl From<(u32, u32)> for XY { } } +impl From<(u8, u8)> for XY { + fn from((x, y): (u8, u8)) -> Self { + (x as usize, y as usize).into() + } +} impl, O: Into>> Add for XY { type Output = Self; diff --git a/src/view/scroll.rs b/src/view/scroll.rs index 2e417e9..ff5c872 100644 --- a/src/view/scroll.rs +++ b/src/view/scroll.rs @@ -159,7 +159,8 @@ impl ScrollBase { /// # use cursive::backend::{self, Backend}; /// # let scrollbase = ScrollBase::new(); /// # let b = backend::Concrete::init(); - /// # let printer = Printer::new((5,1), theme::load_default(), &b); + /// # let t = theme::load_default(); + /// # let printer = Printer::new((5,1), &t, &b); /// # let printer = &printer; /// let lines = ["Line 1", "Line number 2"]; /// scrollbase.draw(printer, |printer, i| {