From 25726140d8a82248ae0195e8db7cc3941ad61005 Mon Sep 17 00:00:00 2001 From: Alexandre Bury Date: Thu, 21 May 2015 23:29:49 -0700 Subject: [PATCH] Add colors and styles to printer --- Cargo.toml | 1 + assets/style.toml | 14 ++ examples/dialog.rs | 3 +- src/color.rs | 329 +++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 10 +- src/printer.rs | 86 ++++++++--- src/view/button.rs | 12 +- src/view/dialog.rs | 30 +++- src/view/stack_view.rs | 45 ++++-- 9 files changed, 492 insertions(+), 38 deletions(-) create mode 100644 assets/style.toml create mode 100644 src/color.rs diff --git a/Cargo.toml b/Cargo.toml index ab4608e..35df7e2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ keywords = ["ncurses", "TUI"] name = "cursive" [dependencies] +toml = "0.1" [dependencies.ncurses] git = "https://github.com/jeaye/ncurses-rs" diff --git a/assets/style.toml b/assets/style.toml new file mode 100644 index 0000000..f223349 --- /dev/null +++ b/assets/style.toml @@ -0,0 +1,14 @@ +background = "#3465a4" +shadow = "#000000" +view = "#d3d7cf" + +primary = "#111111" +secondary = "#EEEEEE" +tertiary = "#444444" + +title_primary = "#ff5555" +title_secondary = "#ffff55" + +highlight = "#FF0000" +highlight_inactive = "#5555FF" + diff --git a/examples/dialog.rs b/examples/dialog.rs index 4d3c1d5..ee9ca2d 100644 --- a/examples/dialog.rs +++ b/examples/dialog.rs @@ -8,7 +8,8 @@ fn main() { // Creates a dialog with a single "Quit" button siv.add_layer(Dialog::new(TextView::new("Hello Dialog!")) - .button("Quit", |s,_| s.quit())); + .title("Cursive") + .button("Quit", |s,_| s.quit())); siv.run(); } diff --git a/src/color.rs b/src/color.rs new file mode 100644 index 0000000..915bc0f --- /dev/null +++ b/src/color.rs @@ -0,0 +1,329 @@ +//! Module to handle colors and themes in the UI. + +use std::io; +use std::io::Read; +use std::fs::File; +use std::path::Path; + +use ncurses; +use toml; + +pub type ThemeColor = i16; + +/// Application background, where no view is present. +pub const BACKGROUND: ThemeColor = 1; +/// Color used by view shadows. Only background matters. +pub const SHADOW: ThemeColor = 2; +/// Main text with default background. +pub const PRIMARY: ThemeColor = 3; +/// Secondary text color, with default background. +pub const SECONDARY: ThemeColor = 4; +/// Tertiary text color, with default background. +pub const TERTIARY: ThemeColor = 5; +/// Title text color with default background. +pub const TITLE_PRIMARY: ThemeColor = 6; +/// Alternative color for a title. +pub const TITLE_SECONDARY: ThemeColor = 7; +/// Alternate text with highlight background. +pub const HIGHLIGHT: ThemeColor = 8; +/// Highlight color for inactive views (not in focus). +pub const HIGHLIGHT_INACTIVE: ThemeColor = 9; + + +fn load_hex(s: &str) -> i16 { + let mut sum = 0; + for c in s.chars() { + sum *= 16; + sum += match c { + n @ '0' ... '9' => n as i16 - '0' as i16, + n @ 'a' ... 'f' => n as i16 - 'a' as i16 + 10, + n @ 'A' ... 'F' => n as i16 - 'A' as i16 + 10, + _ => 0, + }; + } + + sum +} + +/// Defines a color as used by a theme. +/// +/// Can be created from rgb values, or from a preset color. +pub struct Color { + /// Red component. Between 0 and 1000. + pub r: i16, + /// Green component. Between 0 and 1000. + pub g: i16, + /// Blue component. Between 0 and 1000. + pub b: i16, +} + +impl Color { + /// Returns a new color from the given values. + pub fn new(r:i16, g:i16, b:i16) -> Self { + Color { + r:r, + g:g, + b:b, + } + } + + /// Returns a black color: (0,0,0). + pub fn black() -> Self { + Color::new(0,0,0) + } + + /// Returns a white color: (1000,1000,1000). + pub fn white() -> Self { + Color::new(1000,1000,1000) + } + + /// Returns a red color: (1000,0,0). + pub fn red() -> Self { + Color::new(1000,0,0) + } + + /// Returns a green color: (0,1000,0). + pub fn green() -> Self { + Color::new(0,1000,0) + } + + /// Returns a blue color: (0,0,1000). + pub fn blue() -> Self { + Color::new(0,0,1000) + } + + /// Returns a light gray color: (700,700,700). + pub fn gray() -> Self { + Color::new(700,700,700) + } + + /// Returns a dark gray color: (300,300,300). + pub fn dark_gray() -> Self { + Color::new(300,300,300) + } + + /// Returns a yellow color: (1000,1000,0). + pub fn yellow() -> Self { + Color::new(1000,1000,0) + } + + /// Returns a cyan color: (0,1000,1000). + pub fn cyan() -> Self { + Color::new(0,1000,1000) + } + + /// Returns a magenta color: (1000,0,1000). + pub fn magenta() -> Self { + Color::new(1000,0,1000) + } + + /// Applies the current color to the given color id + fn init_color(&self, color_id: ThemeColor) { + ncurses::init_color(color_id, self.r, self.g, self.b); + } + + /// Read a string value into the current color. + fn load_color(&mut self, s: &str) { + + if s.len() == 0 { + panic!("Cannot read color: empty string"); + } + + if s.starts_with("#") { + let s = &s[1..]; + // HTML-style + let l = match s.len() { + 6 => 2, + 3 => 1, + _ => panic!("Cannot parse color: {}", s), + }; + + self.r = (load_hex(&s[0*l..1*l]) as i32 * 1000 / 255) as i16; + self.g = (load_hex(&s[1*l..2*l]) as i32 * 1000 / 255) as i16; + self.b = (load_hex(&s[2*l..3*l]) as i32 * 1000 / 255) as i16; + } else { + // Unknown color. Panic. + panic!("Cannot parse color: {}", s); + } + } +} + +type ColorId = i16; + +const BACKGROUND_COLOR: i16 = 8; +const SHADOW_COLOR: i16 = 9; +const VIEW_COLOR: i16 = 10; +const PRIMARY_COLOR: i16 = 11; +const SECONDARY_COLOR: i16 = 12; +const TERTIARY_COLOR: i16 = 13; +const TITLE_PRIMARY_COLOR: i16 = 14; +const TITLE_SECONDARY__COLOR: i16 = 15; +const HIGHLIGHT_COLOR: i16 = 16; +const HIGHLIGHT_INACTIVE_COLOR: i16 = 17; + +/// Defines colors for various situations. +pub struct Theme { + pub background: Color, + pub shadow: Color, + + pub view_background: Color, + + pub primary: Color, + pub secondary: Color, + pub tertiary: Color, + pub title_primary: Color, + pub title_secondary: Color, + pub highlight: Color, + pub highlight_inactive: Color, +} + +impl Theme { + /// Apply the theme. Effective immediately. + pub fn apply(&self) { + // First, init the colors + self.background.init_color(BACKGROUND_COLOR); + self.shadow.init_color(SHADOW_COLOR); + self.view_background.init_color(VIEW_COLOR); + self.primary.init_color(PRIMARY_COLOR); + self.secondary.init_color(SECONDARY_COLOR); + self.tertiary.init_color(TERTIARY_COLOR); + self.title_primary.init_color(TITLE_PRIMARY_COLOR); + self.title_secondary.init_color(TITLE_SECONDARY__COLOR); + self.highlight.init_color(HIGHLIGHT_COLOR); + self.highlight_inactive.init_color(HIGHLIGHT_INACTIVE_COLOR); + + // Then init the pairs + ncurses::init_pair(BACKGROUND, BACKGROUND_COLOR, BACKGROUND_COLOR); + ncurses::init_pair(SHADOW, SHADOW_COLOR, SHADOW_COLOR); + ncurses::init_pair(PRIMARY, PRIMARY_COLOR, VIEW_COLOR); + ncurses::init_pair(SECONDARY, SECONDARY_COLOR, VIEW_COLOR); + ncurses::init_pair(TERTIARY, TERTIARY_COLOR, VIEW_COLOR); + ncurses::init_pair(TITLE_PRIMARY, TITLE_PRIMARY_COLOR, VIEW_COLOR); + ncurses::init_pair(TITLE_SECONDARY, TITLE_SECONDARY__COLOR, VIEW_COLOR); + ncurses::init_pair(HIGHLIGHT, VIEW_COLOR, HIGHLIGHT_COLOR); + ncurses::init_pair(HIGHLIGHT_INACTIVE, VIEW_COLOR, HIGHLIGHT_INACTIVE_COLOR); + } + + /// Returns the default theme. + pub fn default() -> Theme { + Theme { + background: Color::blue(), + shadow: Color::black(), + view_background: Color::gray(), + primary: Color::black(), + secondary: Color::white(), + tertiary: Color::dark_gray(), + title_primary: Color::red(), + title_secondary: Color::yellow(), + highlight: Color::red(), + highlight_inactive: Color::blue(), + } + } + + /// Load a single value into a color + fn load(color: &mut Color, value: Option<&toml::Value>) { + match value { + Some(&toml::Value::String(ref s)) => color.load_color(s), + _ => (), + } + } + + /// Loads the color content from a TOML configuration + fn load_colors(&mut self, table: toml::Table) { + Theme::load(&mut self.background, table.get("background")); + Theme::load(&mut self.shadow, table.get("shadow")); + Theme::load(&mut self.view_background, table.get("view")); + Theme::load(&mut self.primary, table.get("primary")); + Theme::load(&mut self.secondary, table.get("secondary")); + Theme::load(&mut self.tertiary, table.get("tertiary")); + Theme::load(&mut self.title_primary, table.get("title_primaryy")); + Theme::load(&mut self.title_secondary, table.get("title_secondary")); + Theme::load(&mut self.highlight, table.get("highlight")); + Theme::load(&mut self.highlight_inactive, table.get("highlight_primary")); + } +} + +/// Loads the default theme. +pub fn load_default() { + Theme::default().apply(); +} + +/// Loads a simple default theme using built-in colors. +pub fn load_legacy() { + ncurses::init_pair(BACKGROUND, ncurses::COLOR_WHITE, ncurses::COLOR_BLUE); + ncurses::init_pair(SHADOW, ncurses::COLOR_WHITE, ncurses::COLOR_BLACK); + ncurses::init_pair(PRIMARY, ncurses::COLOR_BLACK, ncurses::COLOR_WHITE); + ncurses::init_pair(SECONDARY, ncurses::COLOR_BLUE, ncurses::COLOR_WHITE); + ncurses::init_pair(TERTIARY, ncurses::COLOR_WHITE, ncurses::COLOR_WHITE); + ncurses::init_pair(TITLE_PRIMARY, ncurses::COLOR_RED, ncurses::COLOR_WHITE); + ncurses::init_pair(TITLE_SECONDARY, ncurses::COLOR_YELLOW, ncurses::COLOR_WHITE); + ncurses::init_pair(HIGHLIGHT, ncurses::COLOR_WHITE, ncurses::COLOR_RED); + ncurses::init_pair(HIGHLIGHT_INACTIVE, ncurses::COLOR_WHITE, ncurses::COLOR_BLUE); +} + +/// Possible error returned when loading a theme. +pub enum Error { + /// An error occured when reading the file. + IoError(io::Error), + /// An error occured when parsing the toml content. + ParseError, +} + +impl From for Error { + fn from(err: io::Error) -> Self { + Error::IoError(err) + } +} + +/// Loads a theme file. +/// +/// The file should be a toml file containing any of the following entries +/// (missing entries will have default value): +/// +/// - `background` +/// - `shadow` +/// - `view` +/// - `primary` +/// - `secondary` +/// - `tertiary` +/// - `title_primary` +/// - `title_secondary` +/// - `highlight` +/// - `highlight_inactive` +/// +/// Here is an example file: +/// +/// ``` +/// background = "#5555FF" +/// shadow = "#000000" +/// view = "#888888" +/// +/// primary = "#111111" +/// secondary = "#EEEEEE" +/// tertiary = "#444444" +/// +/// title_primary = "#ff5555" +/// title_secondary = "#ffff55" +/// +/// highlight = "#FF0000" +/// highlight_inactive = "#5555FF" +/// ``` +pub fn load_theme>(filename: P) -> Result<(),Error> { + let mut file = try!(File::open(filename)); + let mut content = String::new(); + + try!(file.read_to_string(&mut content)); + + let mut parser = toml::Parser::new(&content); + let value = match parser.parse() { + Some(value) => value, + None => return Err(Error::ParseError), + }; + + let mut theme = Theme::default(); + theme.load_colors(value); + + theme.apply(); + + Ok(()) +} diff --git a/src/lib.rs b/src/lib.rs index 0300410..fd2fad5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,11 +21,13 @@ //! } //! ``` extern crate ncurses; +extern crate toml; pub mod event; pub mod view; pub mod printer; pub mod vec; +pub mod color; mod div; mod margins; @@ -66,11 +68,18 @@ pub struct Cursive { impl Cursive { /// Creates a new Cursive root, and initialize ncurses. pub fn new() -> Self { + ncurses::setlocale(ncurses::LcCategory::all, ""); ncurses::initscr(); ncurses::keypad(ncurses::stdscr, true); ncurses::noecho(); ncurses::cbreak(); + ncurses::start_color(); ncurses::curs_set(ncurses::CURSOR_VISIBILITY::CURSOR_INVISIBLE); + color::load_legacy(); + // color::load_default(); + // color::load_theme("assets/style.toml").ok().unwrap(); + + ncurses::wbkgd(ncurses::stdscr, ncurses::COLOR_PAIR(color::BACKGROUND)); let mut res = Cursive { screens: Vec::new(), @@ -172,7 +181,6 @@ impl Cursive { size: self.screen_size(), }; self.screen_mut().draw(&printer, true); - ncurses::wrefresh(ncurses::stdscr); } /// Runs the event loop. diff --git a/src/printer.rs b/src/printer.rs index 5f956e6..eb8580e 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -1,6 +1,8 @@ //! Makes drawing on ncurses windows easier. use ncurses; + +use color; use vec::{Vec2,ToVec2}; /// Wrapper around a subset of a ncurses window. @@ -13,23 +15,70 @@ pub struct Printer { pub size: Vec2, } +/// Wrapper around a parent printer that applies a style to prints. +pub struct StyledPrinter<'a> { + parent: &'a Printer, + style: color::ThemeColor, +} + +impl <'a> StyledPrinter<'a> { + /// Wrapper around the parent's `print` method with `self.style` applied. + pub fn print(&self, pos: S, text: &str) { + ncurses::wattron(self.parent.win, ncurses::COLOR_PAIR(self.style) as i32); + self.parent.print(pos, text); + ncurses::wattroff(self.parent.win, ncurses::COLOR_PAIR(self.style) as i32); + } + + /// Wrapper around the parent's `print_vline` method with `self.style` applied. + pub fn print_vline(&self, start: T, len: u32, c: u64) { + ncurses::wattron(self.parent.win, ncurses::COLOR_PAIR(self.style) as i32); + self.parent.print_vline(start, len, c); + ncurses::wattroff(self.parent.win, ncurses::COLOR_PAIR(self.style) as i32); + } + + /// Wrapper around the parent's `print_hline` method with `self.style` applied. + pub fn print_hline(&self, start: T, len: u32, c: u64) { + ncurses::wattron(self.parent.win, ncurses::COLOR_PAIR(self.style) as i32); + self.parent.print_hline(start, len, c); + ncurses::wattroff(self.parent.win, ncurses::COLOR_PAIR(self.style) as i32); + } +} + impl Printer { + /// Creates a new printer on the given window. + pub fn new(win: ncurses::WINDOW, size: T) -> Self { + Printer { + win: win, + offset: Vec2::zero(), + size: size.to_vec2(), + } + } + /// Prints some text at the given position relative to the window. pub fn print(&self, pos: S, text: &str) { - let p = pos.to_vec2(); - ncurses::mvwprintw(self.win, (p.y + self.offset.y) as i32, (p.x + self.offset.x) as i32, text); + let p = pos.to_vec2() + self.offset; + ncurses::mvwprintw(self.win, p.y as i32, p.x as i32, text); } /// Prints a vertical line using the given character. - pub fn print_vline(&self, c: char, start: T, len: u32) { - let p = start.to_vec2(); - ncurses::mvwvline(self.win, (p.y + self.offset.y) as i32, (p.x + self.offset.x) as i32, c as u64, len as i32); + pub fn print_vline(&self, start: T, len: u32, c: u64) { + let p = start.to_vec2() + self.offset; + ncurses::mvwvline(self.win, p.y as i32, p.x as i32, c, len as i32); } /// Prints a horizontal line using the given character. - pub fn print_hline(&self, c: char, start: T, len: u32) { - let p = start.to_vec2(); - ncurses::mvwhline(self.win, (p.y + self.offset.y) as i32, (p.x + self.offset.x) as i32, c as u64, len as i32); + pub fn print_hline(&self, start: T, len: u32, c: u64) { + let p = start.to_vec2() + self.offset; + ncurses::mvwhline(self.win, p.y as i32, p.x as i32, c, len as i32); + } + + /// Returns a wrapper around this printer, + /// that will apply the given style on prints. + pub fn style<'a>(&'a self, style: color::ThemeColor) -> StyledPrinter<'a> { + StyledPrinter { + parent: self, + style: style, + } } /// Prints a rectangular box. @@ -39,18 +88,21 @@ impl Printer { /// ``` /// printer.print_box((0,0), (6,4), '+', '-', '|'); /// ``` - pub fn print_box(&self, start: T, size: T, corners: char, horizontal: char, vertical: char) { + pub fn print_box(&self, start: T, size: T) { let start_v = start.to_vec2(); let size_v = size.to_vec2() - (1,1); - self.print(start_v, &corners.to_string()); - self.print(start_v + size_v.keep_x(), &corners.to_string()); - self.print(start_v + size_v.keep_y(), &corners.to_string()); - self.print(start_v + size_v, &corners.to_string()); - self.print_hline(horizontal, start_v + (1,0), size_v.x - 1); - self.print_hline(horizontal, start_v + (1,0) + size_v.keep_y(), size_v.x - 1); - self.print_vline(vertical, start_v + (0,1), size_v.y - 1); - self.print_vline(vertical, start_v + (0,1) + size_v.keep_x(), size_v.y - 1); + + + self.print(start_v, "┌"); + self.print(start_v + size_v.keep_x(), "┐"); + self.print(start_v + size_v.keep_y(), "└"); + self.print(start_v + size_v, "┘"); + + self.print_hline(start_v + (1,0), size_v.x - 1, ncurses::ACS_HLINE()); + self.print_vline(start_v + (0,1), size_v.y - 1, ncurses::ACS_VLINE()); + self.print_hline(start_v + (1,0) + size_v.keep_y(), size_v.x - 1, ncurses::ACS_HLINE()); + self.print_vline(start_v + (0,1) + size_v.keep_x(), size_v.y - 1, ncurses::ACS_VLINE()); } /// Returns a printer on a subset of this one's area. diff --git a/src/view/button.rs b/src/view/button.rs index 6f7737d..0fcc279 100644 --- a/src/view/button.rs +++ b/src/view/button.rs @@ -1,5 +1,6 @@ use std::rc::Rc; +use color; use ::Cursive; use vec::Vec2; use view::{View,ViewPath,SizeRequest}; @@ -28,12 +29,13 @@ impl Button { impl View for Button { fn draw(&self, printer: &Printer, focused: bool) { - printer.print((1u32,0u32), &self.label); + let style = if !focused { color::PRIMARY } else { color::HIGHLIGHT }; + let x = printer.size.x - 1; - if focused { - printer.print((0u32,0u32), "<"); - printer.print((printer.size.x-1,0), ">"); - } + let printer = printer.style(style); + printer.print((1u32,0u32), &self.label); + printer.print((0u32,0u32), "<"); + printer.print((x,0), ">"); } fn get_min_size(&self, _: SizeRequest) -> Vec2 { diff --git a/src/view/dialog.rs b/src/view/dialog.rs index f6b8abc..9ce5f54 100644 --- a/src/view/dialog.rs +++ b/src/view/dialog.rs @@ -2,6 +2,7 @@ use std::cmp::max; use ncurses; +use color; use ::{Cursive,Margins}; use event::EventResult; use view::{View,ViewPath,SizeRequest,DimensionRequest}; @@ -23,6 +24,7 @@ enum Focus { /// let dialog = Dialog::new(TextView::new("Hello!")).button("Ok", |s,_| s.quit()); /// ``` pub struct Dialog { + title: String, content: Box, buttons: Vec>, @@ -39,6 +41,7 @@ impl Dialog { Dialog { content: Box::new(view), buttons: Vec::new(), + title: String::new(), focus: Focus::Content, padding: Margins::new(1,1,0,0), borders: Margins::new(1,1,1,1), @@ -56,6 +59,11 @@ impl Dialog { self } + pub fn title(mut self, label: &str) -> Self { + self.title = label.to_string(); + self + } + } impl View for Dialog { @@ -79,7 +87,15 @@ impl View for Dialog { self.content.draw(&printer.sub_printer(self.borders.top_left() + self.padding.top_left(), inner_size), focused && self.focus == Focus::Content); - printer.print_box(Vec2::new(0,0), printer.size, '+', '-', '|'); + printer.print_box(Vec2::new(0,0), printer.size); + + if self.title.len() > 0 { + let x = (printer.size.x - self.title.len() as u32) / 2; + printer.print((x-2,0), "┤ "); + printer.print((x+self.title.len() as u32,0), " ├"); + + printer.style(color::TITLE_PRIMARY).print((x,0), &self.title); + } } @@ -94,11 +110,15 @@ impl View for Dialog { buttons_size.y = max(buttons_size.y, s.y + 1); } - let inner_size = Vec2::new( - max(content_size.x, buttons_size.x), - content_size.y + buttons_size.y); + let mut inner_size = Vec2::new(max(content_size.x, buttons_size.x), + content_size.y + buttons_size.y) + + self.padding.combined() + self.borders.combined(); - inner_size + self.padding.combined() + self.borders.combined() + if self.title.len() > 0 { + inner_size.x = max(inner_size.x, self.title.len() as u32 + 6); + } + + inner_size } fn layout(&mut self, mut size: Vec2) { diff --git a/src/view/stack_view.rs b/src/view/stack_view.rs index 31ef892..412af7c 100644 --- a/src/view/stack_view.rs +++ b/src/view/stack_view.rs @@ -1,5 +1,8 @@ use std::cmp::max; +use ncurses; + +use color; use vec::Vec2; use view::{View,SizeRequest,DimensionRequest}; use event::EventResult; @@ -14,6 +17,7 @@ pub struct StackView { struct Layer { view: Box, size: Vec2, + win: Option, } impl StackView { @@ -29,6 +33,7 @@ impl StackView { self.layers.push(Layer { view: Box::new(view), size: Vec2::new(0,0), + win: None, }); } @@ -41,14 +46,23 @@ impl StackView { impl View for StackView { fn draw(&self, printer: &Printer, focused: bool) { - match self.layers.last() { - None => (), - Some(v) => { - // Center the view - let view_size = Vec2::min(printer.size, v.size); - let offset = (printer.size - view_size) / 2; - v.view.draw(&printer.sub_printer(offset, v.size), focused); - }, + ncurses::wrefresh(printer.win); + for v in self.layers.iter() { + // Center the view + v.view.draw(&Printer::new(v.win.unwrap(), v.size), focused); + + let h = v.size.y; + let w = v.size.x; + let x = (printer.size.x - w) / 2; + let y = (printer.size.y - h) / 2; + + + let printer = printer.style(color::SHADOW); + printer.print_hline((x+1,y+h), w, ' ' as u64); + printer.print_vline((x+w,y+1), h, ' ' as u64); + + // v.view.draw(&printer.sub_printer(offset, v.size), focused); + ncurses::wrefresh(v.win.unwrap()); } } @@ -65,8 +79,21 @@ impl View for StackView { h: DimensionRequest::AtMost(size.y), }; for layer in self.layers.iter_mut() { - layer.size = layer.view.get_min_size(req); + layer.size = Vec2::min(size, layer.view.get_min_size(req)); layer.view.layout(layer.size); + + let h = layer.size.y as i32; + let w = layer.size.x as i32; + let x = (size.x as i32 - w) / 2; + let y = (size.y as i32 - h) / 2; + let win = ncurses::newwin(h, w, y, x); + ncurses::wbkgd(win, ncurses::COLOR_PAIR(color::PRIMARY)); + + match layer.win { + None => (), + Some(w) => { ncurses::delwin(w); }, + } + layer.win = Some(win); } }