From 9e591ef635e33d91d7ec275921d3ace2ed1edd0b Mon Sep 17 00:00:00 2001 From: SEGFAULT Date: Thu, 8 Feb 2018 01:25:00 +0100 Subject: [PATCH] Moving Windows in StackView (#200) * Inital draft of reposition code. * throwing it all in git so I can show @gycos the mess I made * Cleaning up the example and removing that garbage getter function from StackView. More thought needs to be applied to that API. * modified stackview to locally draw background, currently every frame. Adjusted surrounding elements to mirror this change. * fixed dirty flag on stackView to use a cell. Also added dirty events on window resize. * Final code cleanup. * Fixed all highlighted issues in PR #200 --- examples/position.rs | 56 +++++++++++++++++++++++++++ src/cursive.rs | 33 ++++++++++------ src/views/stack_view.rs | 86 ++++++++++++++++++++++++++++++++++------- 3 files changed, 151 insertions(+), 24 deletions(-) create mode 100644 examples/position.rs diff --git a/examples/position.rs b/examples/position.rs new file mode 100644 index 0000000..22d9097 --- /dev/null +++ b/examples/position.rs @@ -0,0 +1,56 @@ +extern crate cursive; + +use cursive::Cursive; +use cursive::view::Position; +use cursive::views::LayerPosition; +use cursive::views::TextView; + +/// Moves top layer by the specified amount +fn move_top(c: &mut Cursive, x_in: isize, y_in: isize) { + // Step 1. Get the current position of the layer. + let s = c.screen_mut(); + let l = LayerPosition::FromFront(0); + + let (x, y) = s.offset().pair(); + + // Step 2. add the specifed amount + // (unsigned math in Rust is a mess.) + let x = if x_in < 0 { + x - (-x_in) as usize + } else { + x + x_in as usize + }; + let y = if y_in < 0 { + y - (-y_in) as usize + } else { + y + y_in as usize + }; + + // convert the new x and y into a position + let p = Position::absolute((x, y)); + + // Step 3. Apply the new position + s.reposition_layer(l, p); +} + +fn main() { + let mut siv = Cursive::new(); + siv.set_fps(60); + + // We can quit by pressing `q` + siv.add_global_callback('q', Cursive::quit); + // Next Gen FPS Controls. + siv.add_global_callback('w', |s| move_top(s, 0, -1)); + siv.add_global_callback('a', |s| move_top(s, -1, 0)); + siv.add_global_callback('s', |s| move_top(s, 0, 1)); + siv.add_global_callback('d', |s| move_top(s, 1, 0)); + + // Add window to fly around. + siv.add_layer(TextView::new( + "Press w,a,s,d to move the window.\n\ + Press q to quit the application.", + )); + + // Run the event loop + siv.run(); +} diff --git a/src/cursive.rs b/src/cursive.rs index 5d5aa6f..b0f647b 100644 --- a/src/cursive.rs +++ b/src/cursive.rs @@ -9,8 +9,8 @@ use std::path::Path; use std::sync::mpsc; use theme; use vec::Vec2; -use view::{self, AnyView, Finder, View}; -use views; +use view::{self, AnyView, Finder, Position, View}; +use views::{self, LayerPosition}; /// Identifies a screen in the cursive root. pub type ScreenId = usize; @@ -416,9 +416,14 @@ impl Cursive { /// Convenient method to remove a layer from the current screen. pub fn pop_layer(&mut self) -> Option> { - let result = self.screen_mut().pop_layer(); - self.clear(); - result + self.screen_mut().pop_layer() + } + + /// Convenient stub forwarding layer repositioning. + pub fn reposition_layer( + &mut self, layer: LayerPosition, position: Position + ) { + self.screen_mut().reposition_layer(layer, position); } // Handles a key event when it was ignored by the current view @@ -460,6 +465,15 @@ impl Cursive { let printer = Printer::new(self.screen_size(), &self.theme, &self.backend); + let selected = self.menubar.receive_events(); + + // Print the stackview background before the menubar + let offset = if self.menubar.autohide { 0 } else { 1 }; + let id = self.active_screen; + let sv_printer = printer.offset((0, offset), !selected); + + self.screens[id].draw_bg(&sv_printer); + // Draw the currently active screen // If the menubar is active, nothing else can be. // Draw the menubar? @@ -472,12 +486,9 @@ impl Cursive { self.menubar.draw(&printer); } - let selected = self.menubar.receive_events(); - - let offset = if self.menubar.autohide { 0 } else { 1 }; - let printer = printer.offset((0, offset), !selected); - let id = self.active_screen; - self.screens[id].draw(&printer); + // finally draw stackview layers + // using variables from above + self.screens[id].draw_fg(&sv_printer); } /// Returns `true` until [`quit(&mut self)`] is called. diff --git a/src/views/stack_view.rs b/src/views/stack_view.rs index 3fc3e1c..9d4155d 100644 --- a/src/views/stack_view.rs +++ b/src/views/stack_view.rs @@ -3,6 +3,7 @@ use With; use direction::Direction; use event::{Event, EventResult}; use std::any::Any; +use std::cell; use std::ops::Deref; use theme::ColorStyle; use vec::Vec2; @@ -15,6 +16,9 @@ pub struct StackView { // Store layers from back to front. layers: Vec, last_size: Vec2, + // Flag indicates if undrawn areas of the background are exposed + // and therefore need redrawing. + bg_dirty: cell::Cell, } enum Placement { @@ -147,6 +151,7 @@ impl StackView { StackView { layers: Vec::new(), last_size: Vec2::zero(), + bg_dirty: cell::Cell::new(true), } } @@ -225,6 +230,7 @@ impl StackView { /// Remove the top-most layer. pub fn pop_layer(&mut self) -> Option> { + self.bg_dirty.set(true); self.layers.pop().map(|child| child.view.unwrap()) } @@ -280,6 +286,65 @@ impl StackView { pub fn move_to_back(&mut self, layer: LayerPosition) { self.move_layer(layer, LayerPosition::FromBack(0)); } + + /// Moves a layer to a new position on the screen. + /// + /// Has no effect on fullscreen layers + /// Has no effect if layer is not found + pub fn reposition_layer( + &mut self, layer: LayerPosition, position: Position + ) { + let i = self.get_index(layer); + let child = match self.layers.get_mut(i) { + Some(i) => i, + None => return, + }; + match child.placement { + Placement::Floating(_) => { + child.placement = Placement::Floating(position); + self.bg_dirty.set(true); + } + Placement::Fullscreen => (), + } + } + + /// Background drawing + /// Drawing functions are split into forground and background to + /// ease inserting layers under the stackview but above it's background + /// you probably just want to call draw() + pub fn draw_bg(&self, printer: &Printer) { + // If the background is dirty draw a new background + if self.bg_dirty.get() { + for y in 0..printer.size.y { + printer.with_color(ColorStyle::background(), |printer| { + printer.print_hline((0, y), printer.size.x, " "); + }); + } + + // set background as clean, so we don't need to do this every frame + self.bg_dirty.set(false); + } + } + + /// Forground drawing + /// Drawing functions are split into forground and background to + /// ease inserting layers under the stackview but above it's background + /// you probably just want to call draw() + pub fn draw_fg(&self, printer: &Printer) { + let last = self.layers.len(); + printer.with_color(ColorStyle::primary(), |printer| { + for (i, (v, offset)) in + StackPositionIterator::new(self.layers.iter(), printer.size) + .enumerate() + { + v.view.draw(&printer.sub_printer( + offset, + v.size, + i + 1 == last, + )); + } + }); + } } struct StackPositionIterator, I: Iterator> { @@ -325,22 +390,17 @@ impl, I: Iterator> Iterator impl View for StackView { fn draw(&self, printer: &Printer) { - let last = self.layers.len(); - printer.with_color(ColorStyle::primary(), |printer| { - for (i, (v, offset)) in - StackPositionIterator::new(self.layers.iter(), printer.size) - .enumerate() - { - v.view.draw(&printer.sub_printer( - offset, - v.size, - i + 1 == last, - )); - } - }); + // This function is included for compat with the view trait, + // it should behave the same as calling them seperately, but does + // not pause to let you insert in between the layers. + self.draw_bg(printer); + self.draw_fg(printer); } fn on_event(&mut self, event: Event) -> EventResult { + if event == Event::WindowResize { + self.bg_dirty.set(true); + } // Use the stack position iterator to get the offset of the top layer. // TODO: save it instead when drawing? match StackPositionIterator::new(