diff --git a/Cargo.toml b/Cargo.toml index a4ca18b..5cca65c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ version = "0.0.2" ncurses = "5.80.0" toml = "0.1" unicode-segmentation = "0.1.2" +unicode-width = "0.1.3" [lib] name = "cursive" diff --git a/src/lib.rs b/src/lib.rs index 7f23c14..8cb64f7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,6 +23,7 @@ extern crate ncurses; extern crate toml; extern crate unicode_segmentation; +extern crate unicode_width; pub mod event; pub mod view; diff --git a/src/menu.rs b/src/menu.rs index 9963a0a..0ef1d8a 100644 --- a/src/menu.rs +++ b/src/menu.rs @@ -13,11 +13,25 @@ pub enum MenuItem { Delimiter, } +impl MenuItem { + pub fn label(&self) -> &str { + match *self { + MenuItem::Delimiter => "", + MenuItem::Leaf(ref label, _) | + MenuItem::Subtree(ref label, _) => label, + } + } +} + impl MenuTree { pub fn new() -> Self { Self::default() } + pub fn len(&self) -> usize { + self.children.len() + } + pub fn clear(&mut self) { self.children.clear(); } diff --git a/src/printer.rs b/src/printer.rs index 18595dd..ee0cf4e 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -139,6 +139,18 @@ impl Printer { self.print_vline(start_v + (0, 1) + size_v.keep_x(), size_v.y - 1, "│"); } + pub fn with_selection(&self, selection: bool, f: F) { + self.with_color(if selection { + if self.focused { + ColorStyle::Highlight + } else { + ColorStyle::HighlightInactive + } + } else { + ColorStyle::Primary + }, f); + } + pub fn print_hdelim(&self, start: T, len: usize) { let start = start.to_vec2(); self.print(start, "├"); diff --git a/src/view/menu_popup.rs b/src/view/menu_popup.rs new file mode 100644 index 0000000..3139914 --- /dev/null +++ b/src/view/menu_popup.rs @@ -0,0 +1,92 @@ +use std::rc::Rc; + +use unicode_width::UnicodeWidthStr; + +use menu::{MenuItem, MenuTree}; +use printer::Printer; +use view::View; +use view::scroll::ScrollBase; +use align::Align; +use vec::Vec2; + +/// fd +pub struct MenuPopup { + menu: Rc, + focus: usize, + scrollbase: ScrollBase, + align: Align, +} + +impl MenuPopup { + pub fn new(menu: Rc) -> Self { + MenuPopup { + menu: menu, + focus: 0, + scrollbase: ScrollBase::new(), + align: Align::top_left(), + } + } + + /// Sets the alignment for this view. + pub fn align(mut self, align: Align) -> Self { + self.align = align; + + self + } +} + +impl View for MenuPopup { + fn draw(&mut self, printer: &Printer) { + let h = self.menu.len(); + let offset = self.align.v.get_offset(h, printer.size.y); + let printer = &printer.sub_printer(Vec2::new(0, offset), + printer.size, + true); + + // Start with a box + printer.print_box(Vec2::new(0, 0), printer.size); + + // We're giving it a reduced size because of borders. + // But we're keeping the full width, + // to integrate horizontal delimiters in the frame. + let size = printer.size - (0, 2); + let printer = printer.sub_printer(Vec2::new(0, 1), size, true); + self.scrollbase.draw(&printer, |printer, i| { + printer.with_selection(i == self.focus, |printer| { + let item = &self.menu.children[i]; + match *item { + MenuItem::Delimiter => { + printer.print_hdelim((0, 0), printer.size.x) + } + MenuItem::Subtree(ref label, _) | + MenuItem::Leaf(ref label, _) => { + printer.print((2, 0), label) + } + } + + }); + }); + } + + fn get_min_size(&self, req: Vec2) -> Vec2 { + // We can't really shrink our items here, so it's not flexible. + let w = self.menu + .children + .iter() + .map(|item| item.label().width()) + .max() + .unwrap_or(1); + let h = self.menu.children.len(); + + + let scrolling = req.y < h; + + let w = if scrolling { + w + 2 + } else { + w + }; + + Vec2::new(w, h) + } +} diff --git a/src/view/mod.rs b/src/view/mod.rs index abb5a0c..728cbed 100644 --- a/src/view/mod.rs +++ b/src/view/mod.rs @@ -19,6 +19,7 @@ mod full_view; mod id_view; mod key_event_view; mod linear_layout; +mod menu_popup; mod shadow_view; mod select_view; mod sized_view; @@ -45,6 +46,7 @@ pub use self::edit_view::EditView; pub use self::full_view::FullView; pub use self::key_event_view::KeyEventView; pub use self::linear_layout::LinearLayout; +pub use self::menu_popup::MenuPopup; pub use self::view_path::ViewPath; pub use self::select_view::SelectView; pub use self::shadow_view::ShadowView; diff --git a/src/view/request.rs b/src/view/request.rs deleted file mode 100644 index c30a74d..0000000 --- a/src/view/request.rs +++ /dev/null @@ -1,51 +0,0 @@ -use vec::ToVec2; - -/// Describe constraints on a view layout in one dimension. -#[derive(PartialEq,Clone,Copy)] -pub enum DimensionRequest { - /// The view must use exactly the attached size. - Fixed(usize), - /// The view is free to choose its size if it stays under the limit. - AtMost(usize), - /// No clear restriction apply. - Unknown, -} - -impl DimensionRequest { - /// Returns a new request, reduced from the original by the given offset. - pub fn reduced(self, offset: usize) -> Self { - match self { - DimensionRequest::Fixed(w) => DimensionRequest::Fixed(w - offset), - DimensionRequest::AtMost(w) => DimensionRequest::AtMost(w - offset), - DimensionRequest::Unknown => DimensionRequest::Unknown, - } - } -} - -/// Describes constraints on a view layout. -#[derive(PartialEq,Clone,Copy)] -pub struct SizeRequest { - /// Restriction on the view width - pub w: DimensionRequest, - /// Restriction on the view height - pub h: DimensionRequest, -} - -impl SizeRequest { - /// Returns a new SizeRequest, reduced from the original by the given offset. - pub fn reduced(self, offset: T) -> Self { - let ov = offset.to_vec2(); - SizeRequest { - w: self.w.reduced(ov.x), - h: self.h.reduced(ov.y), - } - } - - /// Creates a new dummy request, with no restriction. - pub fn dummy() -> Self { - SizeRequest { - w: DimensionRequest::Unknown, - h: DimensionRequest::Unknown, - } - } -} diff --git a/src/view/select_view.rs b/src/view/select_view.rs index 1eec1b8..e657525 100644 --- a/src/view/select_view.rs +++ b/src/view/select_view.rs @@ -1,14 +1,13 @@ use std::cmp::min; use std::rc::Rc; -use theme::ColorStyle; use Cursive; -use align::*; use view::{IdView, View}; +use align::{Align, HAlign, VAlign}; +use view::scroll::ScrollBase; use event::{Event, EventResult, Key}; use vec::Vec2; use printer::Printer; -use super::scroll::ScrollBase; struct Item { label: String, @@ -128,19 +127,12 @@ impl View for SelectView { let h = self.items.len(); let offset = self.align.v.get_offset(h, printer.size.y); - let printer = &printer.sub_printer(Vec2::new(0, offset), printer.size, true); + let printer = &printer.sub_printer(Vec2::new(0, offset), + printer.size, + true); self.scrollbase.draw(printer, |printer, i| { - let style = if i == self.focus { - if printer.focused { - ColorStyle::Highlight - } else { - ColorStyle::HighlightInactive - } - } else { - ColorStyle::Primary - }; - printer.with_color(style, |printer| { + printer.with_selection(i == self.focus, |printer| { let l = self.items[i].label.chars().count(); let x = self.align.h.get_offset(l, printer.size.x); printer.print_hline((0, 0), x, " "); @@ -176,9 +168,13 @@ impl View for SelectView { fn on_event(&mut self, event: Event) -> EventResult { match event { Event::Key(Key::Up) if self.focus > 0 => self.focus -= 1, - Event::Key(Key::Down) if self.focus + 1 < self.items.len() => self.focus += 1, + Event::Key(Key::Down) if self.focus + 1 < self.items.len() => { + self.focus += 1 + } Event::Key(Key::PageUp) => self.focus -= min(self.focus, 10), - Event::Key(Key::PageDown) => self.focus = min(self.focus + 10, self.items.len() - 1), + Event::Key(Key::PageDown) => { + self.focus = min(self.focus + 10, self.items.len() - 1) + } Event::Key(Key::Home) => self.focus = 0, Event::Key(Key::End) => self.focus = self.items.len() - 1, Event::Key(Key::Enter) if self.select_cb.is_some() => { @@ -197,7 +193,9 @@ impl View for SelectView { let iter = self.items.iter().chain(self.items.iter()); if let Some((i, _)) = iter.enumerate() .skip(self.focus + 1) - .find(|&(_, item)| item.label.starts_with(c)) { + .find(|&(_, item)| { + item.label.starts_with(c) + }) { // Apply modulo in case we have a hit // from the chained iterator self.focus = i % self.items.len();