diff --git a/examples/bk/README.md b/examples/README.md similarity index 100% rename from examples/bk/README.md rename to examples/README.md diff --git a/examples/bk/select.rs b/examples/select.rs similarity index 90% rename from examples/bk/select.rs rename to examples/select.rs index 62bf19f9..3782d0eb 100644 --- a/examples/bk/select.rs +++ b/examples/select.rs @@ -3,7 +3,7 @@ use promkit::{error::Result, preset::Select}; fn main() -> Result { let mut p = Select::new(0..100) .title("What number do you like?") - .lines(5) + .window_size(5) .prompt()?; println!("result: {:?}", p.run()?); Ok(()) diff --git a/src/core/listbox.rs b/src/core/listbox.rs index 1833e650..4baab378 100644 --- a/src/core/listbox.rs +++ b/src/core/listbox.rs @@ -2,8 +2,6 @@ use std::{fmt, iter::FromIterator}; mod render; pub use render::Renderer; -mod build; -pub use build::Builder; use crate::core::cursor::Cursor; diff --git a/src/core/listbox/build.rs b/src/core/listbox/build.rs deleted file mode 100644 index 179e70b3..00000000 --- a/src/core/listbox/build.rs +++ /dev/null @@ -1,63 +0,0 @@ -use std::{fmt::Display, iter::FromIterator}; - -use crate::{ - crossterm::style::ContentStyle, - error::Result, - listbox::{Listbox, Renderer}, - render::State, -}; - -#[derive(Clone)] -pub struct Builder { - listbox: Listbox, - style: ContentStyle, - cursor: String, - cursor_style: ContentStyle, - lines: Option, -} - -impl Builder { - pub fn new>(items: I) -> Self { - Self { - listbox: Listbox::from_iter(items), - style: Default::default(), - cursor: Default::default(), - cursor_style: Default::default(), - lines: Default::default(), - } - } - - pub fn cursor>(mut self, cursor: T) -> Self { - self.cursor = cursor.as_ref().to_string(); - self - } - - pub fn style(mut self, style: ContentStyle) -> Self { - self.style = style; - self - } - - pub fn cursor_style(mut self, style: ContentStyle) -> Self { - self.cursor_style = style; - self - } - - pub fn lines(mut self, lines: usize) -> Self { - self.lines = Some(lines); - self - } - - pub fn build(self) -> Result { - Ok(Renderer { - listbox: self.listbox, - cursor: self.cursor, - style: self.style, - cursor_style: self.cursor_style, - lines: self.lines, - }) - } - - pub fn build_state(self) -> Result>> { - Ok(Box::new(State::::new(self.build()?))) - } -} diff --git a/src/core/listbox/render.rs b/src/core/listbox/render.rs index 1f11e350..2a2bb61f 100644 --- a/src/core/listbox/render.rs +++ b/src/core/listbox/render.rs @@ -5,20 +5,45 @@ use crate::{ event::{Event, KeyCode, KeyEvent, KeyEventKind, KeyEventState, KeyModifiers}, style::ContentStyle, }, + error::Result, grapheme::{trim, Graphemes}, listbox::Listbox, pane::Pane, - render::{AsAny, Renderable}, + render::{AsAny, Renderable, State}, }; #[derive(Clone)] pub struct Renderer { pub listbox: Listbox, - pub style: ContentStyle, + /// Style for selected line. + pub active_item_style: ContentStyle, + /// Style for un-selected line. + pub inactive_item_style: ContentStyle, + + /// Symbol for selected line. pub cursor: String, - pub cursor_style: ContentStyle, - pub lines: Option, + + /// Window size. + pub window_size: Option, +} + +impl State { + pub fn try_new( + listbox: Listbox, + active_item_style: ContentStyle, + inactive_item_style: ContentStyle, + cursor: String, + window_size: Option, + ) -> Result>> { + Ok(Box::new(State::::new(Renderer { + listbox, + active_item_style, + inactive_item_style, + cursor, + window_size, + }))) + } } impl Renderable for Renderer { @@ -30,7 +55,10 @@ impl Renderable for Renderer { .enumerate() .map(|(i, item)| { if i == self.listbox.position() { - Graphemes::new_with_style(format!("{}{}", self.cursor, item), self.cursor_style) + Graphemes::new_with_style( + format!("{}{}", self.cursor, item), + self.active_item_style, + ) } else { Graphemes::new_with_style( format!( @@ -38,7 +66,7 @@ impl Renderable for Renderer { " ".repeat(Graphemes::new(self.cursor.clone()).widths()), item ), - self.style, + self.inactive_item_style, ) } }) @@ -46,7 +74,7 @@ impl Renderable for Renderer { let trimed = matrix.iter().map(|row| trim(width as usize, row)).collect(); - Pane::new(trimed, self.listbox.position(), self.lines) + Pane::new(trimed, self.listbox.position(), self.window_size) } /// Default key bindings for item picker. diff --git a/src/preset.rs b/src/preset.rs index 9d1e6387..897aa66d 100644 --- a/src/preset.rs +++ b/src/preset.rs @@ -6,8 +6,8 @@ pub use checkbox::Checkbox; // pub use readline::Readline; // mod password; // pub use password::Password; -// mod select; -// pub use select::Select; +mod select; +pub use select::Select; // mod queryselect; // pub use queryselect::QuerySelect; // mod tree; diff --git a/src/preset/select.rs b/src/preset/select.rs index 8123dd9c..787bd46b 100644 --- a/src/preset/select.rs +++ b/src/preset/select.rs @@ -1,52 +1,83 @@ use std::fmt::Display; use crate::{ + crossterm::style::{Attribute, Attributes, Color, ContentStyle}, error::Result, listbox, - preset::theme::select::Theme, render::{Renderable, State}, + style::Style, text, Prompt, }; +pub struct Theme { + /// Style for title (enabled if you set title). + pub title_style: ContentStyle, + + /// Style for selected item. + pub active_item_style: ContentStyle, + /// Style for un-selected item. + pub inactive_item_style: ContentStyle, + + /// Symbol for selected line. + pub cursor: String, +} + +impl Default for Theme { + fn default() -> Self { + Self { + title_style: Style::new() + .attrs(Attributes::from(Attribute::Bold)) + .build(), + active_item_style: Style::new().fgc(Color::DarkCyan).build(), + inactive_item_style: Style::new().build(), + cursor: String::from("❯ "), + } + } +} + pub struct Select { - title_builder: text::Builder, - listbox_builder: listbox::Builder, + title: String, + listbox: listbox::Listbox, + theme: Theme, + window_size: Option, } impl Select { pub fn new>(items: I) -> Self { Self { - title_builder: Default::default(), - listbox_builder: listbox::Builder::new(items), + title: Default::default(), + listbox: listbox::Listbox::from_iter(items), + theme: Default::default(), + window_size: Default::default(), } - .theme(Theme::default()) } - pub fn theme(mut self, theme: Theme) -> Self { - self.title_builder = self.title_builder.style(theme.title_style); - self.listbox_builder = self - .listbox_builder - .cursor(theme.cursor) - .style(theme.item_style) - .cursor_style(theme.cursor_style); + pub fn title>(mut self, text: T) -> Self { + self.title = text.as_ref().to_string(); self } - pub fn title>(mut self, text: T) -> Self { - self.title_builder = self.title_builder.text(text); + pub fn theme(mut self, theme: Theme) -> Self { + self.theme = theme; self } - pub fn lines(mut self, lines: usize) -> Self { - self.listbox_builder = self.listbox_builder.lines(lines); + pub fn window_size(mut self, window_size: usize) -> Self { + self.window_size = Some(window_size); self } pub fn prompt(self) -> Result> { Prompt::try_new( vec![ - self.title_builder.build_state()?, - self.listbox_builder.build_state()?, + State::::try_new(self.title, self.theme.title_style)?, + State::::try_new( + self.listbox, + self.theme.active_item_style, + self.theme.inactive_item_style, + self.theme.cursor, + self.window_size, + )?, ], |_, _| Ok(true), |renderables: &Vec>| -> Result { diff --git a/src/preset/theme/select.rs b/src/preset/theme/select.rs deleted file mode 100644 index 4d2a57a0..00000000 --- a/src/preset/theme/select.rs +++ /dev/null @@ -1,24 +0,0 @@ -use crate::{ - crossterm::style::{Attribute, Attributes, Color, ContentStyle}, - style::Style, -}; - -pub struct Theme { - pub title_style: ContentStyle, - pub item_style: ContentStyle, - pub cursor: String, - pub cursor_style: ContentStyle, -} - -impl Default for Theme { - fn default() -> Self { - Self { - title_style: Style::new() - .attrs(Attributes::from(Attribute::Bold)) - .build(), - item_style: Style::new().build(), - cursor: String::from("❯ "), - cursor_style: Style::new().fgc(Color::DarkCyan).build(), - } - } -}