diff --git a/Cargo.toml b/Cargo.toml index 35df7e2..49ae98a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,3 +28,7 @@ path = "examples/lorem.rs" [[example]] name = "dialog" path = "examples/dialog.rs" + +[[example]] +name = "logs" +path = "examples/logs.rs" diff --git a/examples/logs.rs b/examples/logs.rs new file mode 100644 index 0000000..e52c9f9 --- /dev/null +++ b/examples/logs.rs @@ -0,0 +1,103 @@ +extern crate cursive; + +use std::sync::mpsc; +use std::thread; + +use cursive::Cursive; +use cursive::printer::Printer; +use cursive::view::{View,FullView}; + +fn main() { + // As usual, create the Cursive root + let mut siv = Cursive::new(); + + // We want to refresh the page even when no input is given. + siv.set_fps(10); + siv.add_global_callback('q' as i32, |s,_| s.quit()); + + // A channel will communicate data from our running task to the UI. + let (tx,rx) = mpsc::channel(); + + // Generate data in a separate thread. + thread::spawn(|| { generate_logs(tx); }); + + // And sets the view to read from the other end of the channel. + siv.add_layer(FullView::new(BufferView::new(200, rx))); + + siv.run(); +} + +// We will only simulate log generation here. +// In real life, this may come from a running task, a separate process, ... +fn generate_logs(tx: mpsc::Sender) { + let mut i = 1; + loop { + let line = format!("Interesting log line {}", i); + i += 1; + match tx.send(line) { + Err(_) => panic!("Uh?..."), + Ok(_) => (), + } + thread::sleep_ms(30); + } +} + +// Let's define a buffer view, that shows the last lines from a stream. +struct BufferView { + // We will emulate a ring buffer + buffer: Vec, + // Current position in the buffer + pos: usize, + // Receiving end of the stream + rx: mpsc::Receiver, +} + +impl BufferView { + // Creates a new view with the given buffer size + fn new(size: usize, rx: mpsc::Receiver) -> Self { + BufferView { + rx: rx, + buffer: (0..size).map(|_| String::new()).collect(), + pos: 0, + } + } + + // Reads available data from the stream into the buffer + fn update(&mut self) { + let mut i = self.pos; + while let Ok(line) = self.rx.try_recv() { + i = (i+1) % self.buffer.len(); + self.buffer[i] = line; + self.pos = i; + } + } + + // Chain together the two parts of the buffer to appear as a circular one. + // The signature is quite ugly, but basically we return an iterator: + // a Chain of two slice iterators. + fn ring<'a>(&'a self) -> std::iter::Chain, std::slice::Iter<'a,String>> { + // The main buffer is "circular" starting at self.pos + // So we chain the two parts as one + self.buffer[self.pos..].iter().chain(self.buffer[..self.pos].iter()) + } +} + +impl View for BufferView { + fn draw(&mut self, printer: &Printer, _: bool) { + // Before drawing, we'll want to update the buffer + self.update(); + + // If the buffer is large enough, we can fill the window + if self.buffer.len() > printer.size.y as usize { + // We'll only print the last entries that fit. + // So discard entries if there are too many. + let discard = self.buffer.len() - printer.size.y as usize; + // And we discard the first entries if needed + for (i, line) in self.ring().skip(discard).enumerate() { + printer.print((0,i as u32), line); + } + } else { + + } + } +} diff --git a/examples/lorem.rs b/examples/lorem.rs index 310c2ed..878f8e8 100644 --- a/examples/lorem.rs +++ b/examples/lorem.rs @@ -1,8 +1,7 @@ extern crate cursive; use cursive::Cursive; -use cursive::view::TextView; -use cursive::view::Dialog; +use cursive::view::{TextView,Dialog}; use std::fs::File; use std::io::Read; diff --git a/src/lib.rs b/src/lib.rs index fd2fad5..aa5c63e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -37,6 +37,7 @@ pub use margins::Margins; use std::any::Any; use std::rc::Rc; use std::collections::HashMap; +use std::sync::mpsc; use vec::Vec2; use view::View; @@ -93,6 +94,17 @@ impl Cursive { res } + /// Regularly redraws everything, even when no input is given. + /// + /// Call with fps=0 to disable (default value). + pub fn set_fps(&self, fps: u32) { + if fps == 0 { + ncurses::timeout(-1); + } else { + ncurses::timeout(1000 / fps as i32); + } + } + /// Returns a mutable reference to the currently active screen. pub fn screen_mut(&mut self) -> &mut StackView { let id = self.active_screen; @@ -122,6 +134,7 @@ impl Cursive { } fn find_any(&mut self, path: &ViewPath) -> Option<&mut Any> { + // Internal find method that returns a Any object. self.screen_mut().find(path) } @@ -190,7 +203,7 @@ impl Cursive { // And the big event loop begins! while self.running { // Do we need to redraw everytime? - // Probably actually. + // Probably, actually. // TODO: Do we actually need to clear everytime? ncurses::clear(); // TODO: Do we need to re-layout everytime? diff --git a/src/view/button.rs b/src/view/button.rs index 0fcc279..5f84722 100644 --- a/src/view/button.rs +++ b/src/view/button.rs @@ -28,7 +28,7 @@ impl Button { impl View for Button { - fn draw(&self, printer: &Printer, focused: bool) { + fn draw(&mut self, printer: &Printer, focused: bool) { let style = if !focused { color::PRIMARY } else { color::HIGHLIGHT }; let x = printer.size.x - 1; diff --git a/src/view/dialog.rs b/src/view/dialog.rs index d2c90c8..a4a4413 100644 --- a/src/view/dialog.rs +++ b/src/view/dialog.rs @@ -74,13 +74,13 @@ impl Dialog { } impl View for Dialog { - fn draw(&self, printer: &Printer, focused: bool) { + fn draw(&mut self, printer: &Printer, focused: bool) { // This will be the height used by the buttons. let mut height = 0; // Current horizontal position of the next button we'll draw. let mut x = 0; - for (i,button) in self.buttons.iter().enumerate().rev() { + for (i,button) in self.buttons.iter_mut().enumerate().rev() { let size = button.size; let offset = printer.size - self.borders.bot_right() - self.padding.bot_right() - size - Vec2::new(x, 0); // Add some special effect to the focused button diff --git a/src/view/full_view.rs b/src/view/full_view.rs new file mode 100644 index 0000000..45c9db5 --- /dev/null +++ b/src/view/full_view.rs @@ -0,0 +1,33 @@ +use view::{View,ViewWrapper,SizeRequest,DimensionRequest}; +use vec::Vec2; + +pub struct FullView { + pub view: T, +} + +impl FullView { + pub fn new(view: T) -> Self { + FullView { + view: view, + } + } +} + +impl ViewWrapper for FullView { + wrap_impl!(&self.view); + + fn wrap_get_min_size(&self, req: SizeRequest) -> Vec2 { + let w = match req.w { + DimensionRequest::Fixed(w) => w, + DimensionRequest::AtMost(w) => w, + DimensionRequest::Unknown => self.view.get_min_size(req).x, + }; + let h = match req.h { + DimensionRequest::Fixed(h) => h, + DimensionRequest::AtMost(h) => h, + DimensionRequest::Unknown => self.view.get_min_size(req).y, + }; + + Vec2::new(w,h) + } +} diff --git a/src/view/mod.rs b/src/view/mod.rs index af3f6f4..36abd6e 100644 --- a/src/view/mod.rs +++ b/src/view/mod.rs @@ -9,6 +9,7 @@ mod view_path; mod dialog; mod button; mod sized_view; +mod full_view; use std::any::Any; @@ -21,6 +22,7 @@ pub use self::dialog::Dialog; pub use self::button::Button; pub use self::sized_view::SizedView; pub use self::view_wrapper::ViewWrapper; +pub use self::full_view::FullView; use event::EventResult; use vec::{Vec2,ToVec2}; @@ -89,7 +91,7 @@ pub trait View { fn layout(&mut self, Vec2) { } /// Draws the view with the given printer (includes bounds) and focus. - fn draw(&self, printer: &Printer, focused: bool); + fn draw(&mut self, printer: &Printer, focused: bool); /// Finds the view pointed to by the given path. /// Returns None if the path doesn't lead to a view. diff --git a/src/view/stack_view.rs b/src/view/stack_view.rs index a0f5d70..84cccba 100644 --- a/src/view/stack_view.rs +++ b/src/view/stack_view.rs @@ -45,9 +45,9 @@ impl StackView { impl View for StackView { - fn draw(&self, printer: &Printer, focused: bool) { + fn draw(&mut self, printer: &Printer, focused: bool) { ncurses::wrefresh(printer.win); - for v in self.layers.iter() { + for v in self.layers.iter_mut() { // Center the view v.view.draw(&Printer::new(v.win.unwrap(), v.size), focused); diff --git a/src/view/text_view.rs b/src/view/text_view.rs index d2b719d..5681ae5 100644 --- a/src/view/text_view.rs +++ b/src/view/text_view.rs @@ -112,7 +112,7 @@ impl <'a> Iterator for LinesIterator<'a> { } impl View for TextView { - fn draw(&self, printer: &Printer, _: bool) { + fn draw(&mut self, printer: &Printer, _: bool) { // We don't have a focused view let lines = self.content.split("\n") diff --git a/src/view/view_wrapper.rs b/src/view/view_wrapper.rs index 82ec6f2..3d2a048 100644 --- a/src/view/view_wrapper.rs +++ b/src/view/view_wrapper.rs @@ -11,8 +11,8 @@ pub trait ViewWrapper { fn get_view_mut(&mut self) -> &mut View; /// Wraps the draw method. - fn wrap_draw(&self, printer: &Printer, focused: bool) { - self.get_view().draw(printer, focused); + fn wrap_draw(&mut self, printer: &Printer, focused: bool) { + self.get_view_mut().draw(printer, focused); } /// Wraps the get_min_size method. @@ -37,7 +37,7 @@ pub trait ViewWrapper { } impl View for T { - fn draw(&self, printer: &Printer, focused: bool) { + fn draw(&mut self, printer: &Printer, focused: bool) { self.wrap_draw(printer, focused); }