diff --git a/src/event.rs b/src/event.rs index 10bd2dc..467372f 100644 --- a/src/event.rs +++ b/src/event.rs @@ -17,3 +17,13 @@ pub enum EventResult { /// The event was consumed. An optionnal callback to run is attached. Consumed(Option>, ViewPath), } + +impl EventResult { + pub fn callback(cb: Rc) -> Self { + EventResult::Consumed(Some(cb), ViewPath::new()) + } + + pub fn consume() -> Self { + EventResult::Consumed(None, ViewPath::new()) + } +} diff --git a/src/margins.rs b/src/margins.rs index 4dd524e..0321143 100644 --- a/src/margins.rs +++ b/src/margins.rs @@ -34,4 +34,12 @@ impl Margins { pub fn combined(&self) -> Vec2 { Vec2::new(self.horizontal(), self.vertical()) } + + pub fn top_left(&self) -> Vec2 { + Vec2::new(self.left, self.top) + } + + pub fn bot_right(&self) -> Vec2 { + Vec2::new(self.right, self.bottom) + } } diff --git a/src/vec.rs b/src/vec.rs index 80f4fba..9779f89 100644 --- a/src/vec.rs +++ b/src/vec.rs @@ -1,7 +1,7 @@ -//! 2D points. +//! Points on the 2D character grid. use std::ops::{Add, Sub, Mul, Div}; -use std::cmp::min; +use std::cmp::{min,max}; /// Simple 2D size, in characters. #[derive(Clone,Copy)] @@ -21,12 +21,27 @@ impl Vec2 { } } + /// Returns a new Vec2 that is a maximum per coordinate. + pub fn max(a: Self, b: Self) -> Self { + Vec2::new(max(a.x, b.x), max(a.y, b.y)) + } + /// Returns a new Vec2 that is no larger than any input in both dimensions. - pub fn min(a: Vec2, b: Vec2) -> Vec2 { - Vec2 { - x: min(a.x, b.x), - y: min(a.y, b.y), - } + pub fn min(a: Self, b: Self) -> Self { + Vec2::new(min(a.x, b.x), min(a.y, b.y)) + } + + pub fn keep_x(&self) -> Self { + Vec2::new(self.x, 0) + } + + pub fn keep_y(&self) -> Self { + Vec2::new(0, self.y) + } + + /// Alias for Vec::new(0,0) + pub fn zero() -> Self { + Vec2::new(0,0) } } diff --git a/src/view/button.rs b/src/view/button.rs new file mode 100644 index 0000000..d56b22c --- /dev/null +++ b/src/view/button.rs @@ -0,0 +1,44 @@ +use std::rc::Rc; + +use ncurses; + +use ::Cursive; +use vec::Vec2; +use view::{View,ViewPath,SizeRequest}; +use event::{Callback,EventResult}; +use printer::Printer; + +/// Simple text label with a callback when ENTER is pressed. +pub struct Button { + label: String, + callback: Rc, +} + +impl Button { + pub fn new(label: &str, cb: F) -> Self + where F: Fn(&mut Cursive, &ViewPath) + 'static + { + Button { + label: label.to_string(), + callback: Rc::new(Box::new(cb)), + } + } +} + +impl View for Button { + + fn draw(&self, printer: &Printer) { + printer.print(Vec2::zero(), &self.label); + } + + fn get_min_size(&self, req: SizeRequest) -> Vec2 { + Vec2::new(self.label.len() as u32, 1) + } + + fn on_key_event(&mut self, ch: i32) -> EventResult { + match ch { + ncurses::KEY_ENTER => EventResult::callback(self.callback.clone()), + _ => EventResult::Ignored, + } + } +} diff --git a/src/view/dialog.rs b/src/view/dialog.rs index bcb38cd..32e0314 100644 --- a/src/view/dialog.rs +++ b/src/view/dialog.rs @@ -1,5 +1,11 @@ -use ::Cursive; +use std::cmp::max; + +use ncurses; + +use ::{Cursive,Margins}; +use event::EventResult; use view::{View,ViewPath,SizeRequest,DimensionRequest}; +use view::{Button,SizedView}; use vec::Vec2; use printer::Printer; @@ -11,7 +17,12 @@ enum Focus { pub struct Dialog { content: Box, - buttons: Vec>, + + buttons: Vec>, + + padding: Margins, + borders: Margins, + focus: Focus, } @@ -21,14 +32,19 @@ impl Dialog { content: Box::new(view), buttons: Vec::new(), focus: Focus::Nothing, + padding: Margins::new(1,1,0,0), + borders: Margins::new(1,1,1,1), } } - pub fn button<'a, F>(self, label: &'a str, cb: F) -> Self - where F: Fn(&mut Cursive, &ViewPath) + pub fn button<'a, F>(mut self, label: &'a str, cb: F) -> Self + where F: Fn(&mut Cursive, &ViewPath) + 'static { + self.buttons.push(SizedView::new(Button::new(label, cb))); + self } + } fn offset_request(request: DimensionRequest, offset: i32) -> DimensionRequest { @@ -41,16 +57,86 @@ fn offset_request(request: DimensionRequest, offset: i32) -> DimensionRequest { impl View for Dialog { fn draw(&self, printer: &Printer) { - + + let mut height = 0; + let mut x = 0; + for button in self.buttons.iter().rev() { + // button.draw(&printer.sub_printer(), + let size = button.size; + let offset = printer.size - self.borders.bot_right() - self.padding.bot_right() - size - Vec2::new(x, 0); + button.draw(&printer.sub_printer(offset, size)); + x += size.x + 1; + height = max(height, size.y+1); + } + + let inner_size = printer.size + - Vec2::new(0, height) + - self.borders.combined() + - self.padding.combined(); + + self.content.draw(&printer.sub_printer(self.borders.top_left() + self.padding.top_left(), inner_size)); + + printer.print(Vec2::new(0,0), "+"); + printer.print(Vec2::new(printer.size.x-1, 0), "+"); + printer.print(Vec2::new(0, printer.size.y-1), "+"); + printer.print(Vec2::new(printer.size.x-1, printer.size.y-1), "+"); } - fn get_min_size(&self, size: SizeRequest) -> Vec2 { - let content_req = SizeRequest { - w: offset_request(size.w, -2), - h: offset_request(size.h, -2), - }; + fn get_min_size(&self, req: SizeRequest) -> Vec2 { + let content_req = req.reduced(self.padding.combined() + self.borders.combined()); let content_size = self.content.get_min_size(content_req); - content_size + (2u32,2u32) + let mut buttons_size = Vec2::new(0,0); + for button in self.buttons.iter() { + let s = button.view.get_min_size(req); + buttons_size.x += s.x + 1; + 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); + + inner_size + self.padding.combined() + self.borders.combined() + } + + fn layout(&mut self, mut size: Vec2) { + // First layout the buttons + size = size - (self.borders.combined() + self.padding.combined()); + let req = SizeRequest { + w: DimensionRequest::AtMost(size.x), + h: DimensionRequest::AtMost(size.y), + }; + + let mut buttons_height = 0; + for button in self.buttons.iter_mut().rev() { + let size = button.get_min_size(req); + buttons_height = max(buttons_height, size.y+1); + button.layout(size); + } + + self.content.layout(size - Vec2::new(0, buttons_height)); + } + + fn on_key_event(&mut self, ch: i32) -> EventResult { + match self.focus { + Focus::Content => match self.content.on_key_event(ch) { + EventResult::Ignored if !self.buttons.is_empty() => match ch { + ncurses::KEY_DOWN => { + self.focus = Focus::Button(0); + EventResult::Consumed(None, ViewPath::new()) + }, + _ => EventResult::Ignored, + }, + res => res, + }, + Focus::Button(i) => match self.buttons[i].on_key_event(ch) { + EventResult::Ignored => match ch { + _ => EventResult::Ignored, + }, + res => res, + }, + Focus::Nothing => EventResult::Ignored, + } } } diff --git a/src/view/mod.rs b/src/view/mod.rs index 80ddfee..e7581af 100644 --- a/src/view/mod.rs +++ b/src/view/mod.rs @@ -6,6 +6,9 @@ mod text_view; mod key_event_view; mod view_path; mod dialog; +mod button; +mod sized_view; +mod view_wrapper; use std::any::Any; @@ -15,9 +18,12 @@ pub use self::box_view::BoxView; pub use self::stack_view::StackView; pub use self::text_view::TextView; pub use self::dialog::Dialog; +pub use self::button::Button; +pub use self::sized_view::SizedView; +pub use self::view_wrapper::ViewWrapper; use event::EventResult; -use vec::Vec2; +use vec::{Vec2,ToVec2}; use printer::Printer; /// Describe constraints on a view layout in one dimension. @@ -31,6 +37,16 @@ pub enum DimensionRequest { Unknown, } +impl DimensionRequest { + pub fn reduced(self, offset: u32) -> 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 { @@ -40,6 +56,23 @@ pub struct SizeRequest { pub h: DimensionRequest, } +impl SizeRequest { + 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), + } + } + + pub fn dummy() -> Self { + SizeRequest { + w: DimensionRequest::Unknown, + h: DimensionRequest::Unknown, + } + } +} + /// Main trait defining a view behaviour. pub trait View { /// Called when a key was pressed. Default implementation just ignores it. diff --git a/src/view/sized_view.rs b/src/view/sized_view.rs new file mode 100644 index 0000000..a62a978 --- /dev/null +++ b/src/view/sized_view.rs @@ -0,0 +1,34 @@ +use view::View; +use vec::Vec2; +use view::ViewWrapper; + +/// Wrapper around a view that remembers its size. +pub struct SizedView { + pub view: T, + pub size: Vec2, +} + +impl SizedView { + /// Wraps the given view. + pub fn new(view: T) -> Self { + SizedView { + view: view, + size: Vec2::zero(), + } + } +} + +impl ViewWrapper for SizedView { + fn get_view(&self) -> &View { + &self.view + } + + fn get_view_mut(&mut self) -> &mut View { + &mut self.view + } + + fn wrap_layout(&mut self, size: Vec2) { + self.view.layout(size); + self.size = size; + } +} diff --git a/src/view/view_wrapper.rs b/src/view/view_wrapper.rs new file mode 100644 index 0000000..127c13b --- /dev/null +++ b/src/view/view_wrapper.rs @@ -0,0 +1,43 @@ +use vec::Vec2; +use view::{View,SizeRequest}; +use printer::Printer; +use event::EventResult; + +pub trait ViewWrapper { + fn get_view(&self) -> &View; + fn get_view_mut(&mut self) -> &mut View; + + fn wrap_draw(&self, printer: &Printer) { + self.get_view().draw(printer); + } + + fn wrap_get_min_size(&self, req: SizeRequest) -> Vec2 { + self.get_view().get_min_size(req) + } + + fn wrap_on_key_event(&mut self, ch: i32) -> EventResult { + self.get_view_mut().on_key_event(ch) + } + + fn wrap_layout(&mut self, size: Vec2) { + self.get_view_mut().layout(size); + } +} + +impl View for T { + fn draw(&self, printer: &Printer) { + self.wrap_draw(printer); + } + + fn get_min_size(&self, req: SizeRequest) -> Vec2 { + self.wrap_get_min_size(req) + } + + fn on_key_event(&mut self, ch: i32) -> EventResult { + self.wrap_on_key_event(ch) + } + + fn layout(&mut self, size: Vec2) { + self.wrap_layout(size); + } +}