mirror of
https://github.com/FliegendeWurst/cursive.git
synced 2024-11-23 17:35:00 +00:00
Update for new ncurses version
This commit is contained in:
parent
0e37a7f2e4
commit
07c3c99e54
@ -10,7 +10,7 @@ version = "0.1.0"
|
||||
|
||||
[dependencies]
|
||||
ncurses = "5.80.0"
|
||||
toml = "0.1.25"
|
||||
toml = "0.1"
|
||||
|
||||
[[example]]
|
||||
name = "hello_world"
|
||||
|
@ -2,6 +2,7 @@ extern crate cursive;
|
||||
|
||||
use std::sync::mpsc;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use cursive::Cursive;
|
||||
use cursive::printer::Printer;
|
||||
@ -42,7 +43,7 @@ fn generate_logs(tx: mpsc::Sender<String>) {
|
||||
Err(_) => return,
|
||||
Ok(_) => (),
|
||||
}
|
||||
thread::sleep_ms(30);
|
||||
thread::sleep(Duration::from_millis(30));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9,10 +9,7 @@ pub struct Align {
|
||||
impl Align {
|
||||
/// Creates a new Align object from the given horizontal and vertical alignments.
|
||||
pub fn new(h: HAlign, v: VAlign) -> Self {
|
||||
Align {
|
||||
h: h,
|
||||
v: v,
|
||||
}
|
||||
Align { h: h, v: v }
|
||||
}
|
||||
|
||||
/// Creates a top-left alignment.
|
||||
@ -61,7 +58,7 @@ impl HAlign {
|
||||
pub fn get_offset(&self, content: usize, container: usize) -> usize {
|
||||
match *self {
|
||||
HAlign::Left => 0,
|
||||
HAlign::Center => (container - content)/2,
|
||||
HAlign::Center => (container - content) / 2,
|
||||
HAlign::Right => (container - content),
|
||||
}
|
||||
}
|
||||
@ -73,7 +70,7 @@ impl VAlign {
|
||||
pub fn get_offset(&self, content: usize, container: usize) -> usize {
|
||||
match *self {
|
||||
VAlign::Top => 0,
|
||||
VAlign::Center => (container - content)/2,
|
||||
VAlign::Center => (container - content) / 2,
|
||||
VAlign::Bottom => (container - content),
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,9 @@ pub fn div_up_usize(p: usize, q: usize) -> usize {
|
||||
|
||||
/// Integer division that rounds up.
|
||||
pub fn div_up(p: u32, q: u32) -> u32 {
|
||||
if p % q == 0 { p/q }
|
||||
else { 1 + p/q }
|
||||
if p % q == 0 {
|
||||
p / q
|
||||
} else {
|
||||
1 + p / q
|
||||
}
|
||||
}
|
||||
|
10
src/event.rs
10
src/event.rs
@ -5,7 +5,7 @@ use std::rc::Rc;
|
||||
|
||||
use ncurses;
|
||||
|
||||
use ::Cursive;
|
||||
use Cursive;
|
||||
|
||||
/// Callback is a function that can be triggered by an event.
|
||||
/// It has a mutable access to the cursive root.
|
||||
@ -136,10 +136,10 @@ impl Key {
|
||||
f @ 293 ... 303 => Key::CtrlF((f - 293 + 5) as u8),
|
||||
f @ 305 ... 315 => Key::CtrlShiftF((f - 305 + 5) as u8),
|
||||
// Shift and Ctrl F{1-4} need escape sequences...
|
||||
|
||||
//
|
||||
// TODO: shift and ctrl Fn keys
|
||||
// Avoids 8-10 (H,I,J), they are used by other commands.
|
||||
c @ 1 ... 7 | c @ 11 ... 25 => Key::CtrlChar(('a' as u8 + (c-1) as u8) as char),
|
||||
c @ 1 ... 7 | c @ 11 ... 25 => Key::CtrlChar(('a' as u8 + (c - 1) as u8) as char),
|
||||
_ => Key::Unknown(ch),
|
||||
}
|
||||
}
|
||||
@ -154,7 +154,9 @@ impl fmt::Display for Key {
|
||||
Key::ShiftF(n) => write!(f, "Shift-F{}", n),
|
||||
Key::CtrlF(n) => write!(f, "Ctrl-F{}", n),
|
||||
Key::CtrlShiftF(n) => write!(f, "Ctrl-Shift-F{}", n),
|
||||
key => write!(f, "{}", match key {
|
||||
key => write!(f,
|
||||
"{}",
|
||||
match key {
|
||||
Key::NumpadCenter => "Numpad center",
|
||||
Key::Left => "Left",
|
||||
Key::Right => "Right",
|
||||
|
14
src/lib.rs
14
src/lib.rs
@ -42,9 +42,9 @@ use std::path::Path;
|
||||
use vec::Vec2;
|
||||
use printer::Printer;
|
||||
use view::View;
|
||||
use view::{StackView,Selector};
|
||||
use view::{StackView, Selector};
|
||||
|
||||
use event::{Event,ToEvent,Key,EventResult,Callback};
|
||||
use event::{Event, ToEvent, Key, EventResult, Callback};
|
||||
|
||||
/// Identifies a screen in the cursive ROOT.
|
||||
pub type ScreenId = usize;
|
||||
@ -83,7 +83,8 @@ impl Cursive {
|
||||
let theme = theme::load_default();
|
||||
// let theme = theme::load_theme("assets/style.toml").unwrap();
|
||||
|
||||
ncurses::wbkgd(ncurses::stdscr, ncurses::COLOR_PAIR(theme::ColorPair::Background.ncurses_id()));
|
||||
ncurses::wbkgd(ncurses::stdscr,
|
||||
ncurses::COLOR_PAIR(theme::ColorPair::Background.ncurses_id()));
|
||||
|
||||
let mut res = Cursive {
|
||||
screens: Vec::new(),
|
||||
@ -148,7 +149,9 @@ impl Cursive {
|
||||
/// Sets the active screen. Panics if no such screen exist.
|
||||
pub fn set_screen(&mut self, screen_id: ScreenId) {
|
||||
if screen_id >= self.screens.len() {
|
||||
panic!("Tried to set an invalid screen ID: {}, but only {} screens present.", screen_id, self.screens.len());
|
||||
panic!("Tried to set an invalid screen ID: {}, but only {} screens present.",
|
||||
screen_id,
|
||||
self.screens.len());
|
||||
}
|
||||
self.active_screen = screen_id;
|
||||
}
|
||||
@ -174,7 +177,7 @@ impl Cursive {
|
||||
}
|
||||
|
||||
/// Adds a global callback, triggered on the given key press when no view catches it.
|
||||
pub fn add_global_callback<F,E: ToEvent>(&mut self, event: E, cb: F)
|
||||
pub fn add_global_callback<F, E: ToEvent>(&mut self, event: E, cb: F)
|
||||
where F: Fn(&mut Cursive) + 'static
|
||||
{
|
||||
self.global_callbacks.insert(event.to_event(), Rc::new(Box::new(cb)));
|
||||
@ -275,4 +278,3 @@ impl Drop for Cursive {
|
||||
ncurses::endwin();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -30,7 +30,7 @@ impl Orientation {
|
||||
|
||||
/// Returns a mutable reference to the component of the given vector
|
||||
/// corresponding to this orientation.
|
||||
pub fn get_ref<'a,'b>(&'a self, v: &'b mut Vec2) -> &'b mut usize {
|
||||
pub fn get_ref<'a, 'b>(&'a self, v: &'b mut Vec2) -> &'b mut usize {
|
||||
match *self {
|
||||
Orientation::Horizontal => &mut v.x,
|
||||
Orientation::Vertical => &mut v.y,
|
||||
@ -42,10 +42,10 @@ impl Orientation {
|
||||
///
|
||||
/// For an horizontal view, returns (Sum(x), Max(y)).
|
||||
/// For a vertical view, returns (Max(x),Sum(y)).
|
||||
pub fn stack<'a,T: Iterator<Item=&'a Vec2>>(&self, iter: T) -> Vec2 {
|
||||
pub fn stack<'a, T: Iterator<Item = &'a Vec2>>(&self, iter: T) -> Vec2 {
|
||||
match *self {
|
||||
Orientation::Horizontal => iter.fold(Vec2::zero(), |a,b| a.stack_horizontal(&b)),
|
||||
Orientation::Vertical => iter.fold(Vec2::zero(), |a,b| a.stack_vertical(&b)),
|
||||
Orientation::Horizontal => iter.fold(Vec2::zero(), |a, b| a.stack_horizontal(&b)),
|
||||
Orientation::Vertical => iter.fold(Vec2::zero(), |a, b| a.stack_vertical(&b)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,9 +3,10 @@
|
||||
use std::cmp::min;
|
||||
|
||||
use ncurses;
|
||||
use ncurses::chtype;
|
||||
|
||||
use theme::{ColorPair,Theme};
|
||||
use vec::{Vec2,ToVec2};
|
||||
use theme::{ColorPair, Theme};
|
||||
use vec::{Vec2, ToVec2};
|
||||
|
||||
/// Convenient interface to draw on a subset of the screen.
|
||||
pub struct Printer {
|
||||
@ -33,12 +34,14 @@ impl Printer {
|
||||
/// Prints some text at the given position relative to the window.
|
||||
pub fn print<S: ToVec2>(&self, pos: S, text: &str) {
|
||||
let p = pos.to_vec2();
|
||||
if p.y >= self.size.y || p.x >= self.size.x { return; }
|
||||
if p.y >= self.size.y || p.x >= self.size.x {
|
||||
return;
|
||||
}
|
||||
// Do we have enough room for the entire line?
|
||||
let room = self.size.x - p.x;
|
||||
// We want the number of CHARACTERS, not bytes.
|
||||
let text = match text.char_indices().nth(room) {
|
||||
Some((i,_)) => &text[..i],
|
||||
Some((i, _)) => &text[..i],
|
||||
_ => text,
|
||||
};
|
||||
|
||||
@ -51,9 +54,11 @@ impl Printer {
|
||||
}
|
||||
|
||||
/// Prints a vertical line using the given character.
|
||||
pub fn print_vline<T: ToVec2>(&self, start: T, len: usize, c: u64) {
|
||||
pub fn print_vline<T: ToVec2>(&self, start: T, len: usize, c: chtype) {
|
||||
let p = start.to_vec2();
|
||||
if p.y > self.size.y || p.x > self.size.x { return; }
|
||||
if p.y > self.size.y || p.x > self.size.x {
|
||||
return;
|
||||
}
|
||||
let len = min(len, self.size.y - p.y);
|
||||
|
||||
let p = p + self.offset;
|
||||
@ -61,9 +66,11 @@ impl Printer {
|
||||
}
|
||||
|
||||
/// Prints a horizontal line using the given character.
|
||||
pub fn print_hline<T: ToVec2>(&self, start: T, len: usize, c: u64) {
|
||||
pub fn print_hline<T: ToVec2>(&self, start: T, len: usize, c: chtype) {
|
||||
let p = start.to_vec2();
|
||||
if p.y > self.size.y || p.x > self.size.x { return; }
|
||||
if p.y > self.size.y || p.x > self.size.x {
|
||||
return;
|
||||
}
|
||||
let len = min(len, self.size.x - p.x);
|
||||
|
||||
let p = p + self.offset;
|
||||
@ -114,17 +121,21 @@ impl Printer {
|
||||
/// ```
|
||||
pub fn print_box<T: ToVec2>(&self, start: T, size: T) {
|
||||
let start_v = start.to_vec2();
|
||||
let size_v = size.to_vec2() - (1,1);
|
||||
let size_v = size.to_vec2() - (1, 1);
|
||||
|
||||
self.print(start_v, "┌");
|
||||
self.print(start_v + size_v.keep_x(), "┐");
|
||||
self.print(start_v + size_v.keep_y(), "└");
|
||||
self.print(start_v + size_v, "┘");
|
||||
|
||||
self.print_hline(start_v + (1,0), size_v.x - 1, ncurses::ACS_HLINE());
|
||||
self.print_vline(start_v + (0,1), size_v.y - 1, ncurses::ACS_VLINE());
|
||||
self.print_hline(start_v + (1,0) + size_v.keep_y(), size_v.x - 1, ncurses::ACS_HLINE());
|
||||
self.print_vline(start_v + (0,1) + size_v.keep_x(), size_v.y - 1, ncurses::ACS_VLINE());
|
||||
self.print_hline(start_v + (1, 0), size_v.x - 1, ncurses::ACS_HLINE());
|
||||
self.print_vline(start_v + (0, 1), size_v.y - 1, ncurses::ACS_VLINE());
|
||||
self.print_hline(start_v + (1, 0) + size_v.keep_y(),
|
||||
size_v.x - 1,
|
||||
ncurses::ACS_HLINE());
|
||||
self.print_vline(start_v + (0, 1) + size_v.keep_x(),
|
||||
size_v.y - 1,
|
||||
ncurses::ACS_VLINE());
|
||||
}
|
||||
|
||||
/// Returns a printer on a subset of this one's area.
|
||||
|
56
src/theme.rs
56
src/theme.rs
@ -100,15 +100,29 @@ impl Theme {
|
||||
}
|
||||
|
||||
fn apply(&self) {
|
||||
Theme::apply_color(ColorPair::Background, &self.colors.background, &self.colors.background);
|
||||
Theme::apply_color(ColorPair::Background,
|
||||
&self.colors.background,
|
||||
&self.colors.background);
|
||||
Theme::apply_color(ColorPair::Shadow, &self.colors.shadow, &self.colors.shadow);
|
||||
Theme::apply_color(ColorPair::Primary, &self.colors.primary, &self.colors.view);
|
||||
Theme::apply_color(ColorPair::Secondary, &self.colors.secondary, &self.colors.view);
|
||||
Theme::apply_color(ColorPair::Tertiary, &self.colors.tertiary, &self.colors.view);
|
||||
Theme::apply_color(ColorPair::TitlePrimary, &self.colors.title_primary, &self.colors.view);
|
||||
Theme::apply_color(ColorPair::TitleSecondary, &self.colors.title_secondary, &self.colors.view);
|
||||
Theme::apply_color(ColorPair::Highlight, &self.colors.view, &self.colors.highlight);
|
||||
Theme::apply_color(ColorPair::HighlightInactive, &self.colors.view, &self.colors.highlight_inactive);
|
||||
Theme::apply_color(ColorPair::Secondary,
|
||||
&self.colors.secondary,
|
||||
&self.colors.view);
|
||||
Theme::apply_color(ColorPair::Tertiary,
|
||||
&self.colors.tertiary,
|
||||
&self.colors.view);
|
||||
Theme::apply_color(ColorPair::TitlePrimary,
|
||||
&self.colors.title_primary,
|
||||
&self.colors.view);
|
||||
Theme::apply_color(ColorPair::TitleSecondary,
|
||||
&self.colors.title_secondary,
|
||||
&self.colors.view);
|
||||
Theme::apply_color(ColorPair::Highlight,
|
||||
&self.colors.view,
|
||||
&self.colors.highlight);
|
||||
Theme::apply_color(ColorPair::HighlightInactive,
|
||||
&self.colors.view,
|
||||
&self.colors.highlight_inactive);
|
||||
}
|
||||
|
||||
fn apply_color(pair: ColorPair, front: &Color, back: &Color) {
|
||||
@ -192,12 +206,12 @@ pub struct Color {
|
||||
|
||||
impl Color {
|
||||
/// Return the rgb values used by the color.
|
||||
pub fn rgb(&self) -> (i16,i16,i16) {
|
||||
let (mut r, mut g, mut b) = (0,0,0);
|
||||
pub fn rgb(&self) -> (i16, i16, i16) {
|
||||
let (mut r, mut g, mut b) = (0, 0, 0);
|
||||
|
||||
ncurses::color_content(self.id, &mut r, &mut g, &mut b);
|
||||
|
||||
(r,g,b)
|
||||
(r, g, b)
|
||||
}
|
||||
}
|
||||
|
||||
@ -244,10 +258,14 @@ impl Color {
|
||||
|
||||
fn load(&mut self, table: &toml::Table, key: &str, new_id: &mut i16) {
|
||||
match table.get(key) {
|
||||
Some(&toml::Value::String(ref value)) => { self.load_value(value, new_id); },
|
||||
Some(&toml::Value::String(ref value)) => {
|
||||
self.load_value(value, new_id);
|
||||
}
|
||||
Some(&toml::Value::Array(ref array)) => for color in array.iter() {
|
||||
match color {
|
||||
&toml::Value::String(ref color) => if self.load_value(color, new_id) { return; },
|
||||
&toml::Value::String(ref color) => if self.load_value(color, new_id) {
|
||||
return;
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
},
|
||||
@ -278,15 +296,15 @@ impl Color {
|
||||
}
|
||||
|
||||
let s = &value[1..];
|
||||
let (l,max) = match s.len() {
|
||||
6 => (2,255),
|
||||
3 => (1,15),
|
||||
let (l, max) = match s.len() {
|
||||
6 => (2, 255),
|
||||
3 => (1, 15),
|
||||
_ => panic!("Cannot parse color: {}", s),
|
||||
};
|
||||
|
||||
let r = (load_hex(&s[0*l..1*l]) as i32 * 1000 / max) as i16;
|
||||
let g = (load_hex(&s[1*l..2*l]) as i32 * 1000 / max) as i16;
|
||||
let b = (load_hex(&s[2*l..3*l]) as i32 * 1000 / max) as i16;
|
||||
let r = (load_hex(&s[0 * l..1 * l]) as i32 * 1000 / max) as i16;
|
||||
let g = (load_hex(&s[1 * l..2 * l]) as i32 * 1000 / max) as i16;
|
||||
let b = (load_hex(&s[2 * l..3 * l]) as i32 * 1000 / max) as i16;
|
||||
|
||||
ncurses::init_color(*new_id, r, g, b);
|
||||
|
||||
@ -359,7 +377,7 @@ impl Color {
|
||||
/// highlight_inactive = "#5555FF"
|
||||
/// ```
|
||||
|
||||
pub fn load_theme<P: AsRef<Path>>(filename: P) -> Result<Theme,Error> {
|
||||
pub fn load_theme<P: AsRef<Path>>(filename: P) -> Result<Theme, Error> {
|
||||
let content = {
|
||||
let mut content = String::new();
|
||||
let mut file = try!(File::open(filename));
|
||||
|
@ -3,18 +3,18 @@ use std::char::from_u32;
|
||||
/// Reads a char from a first byte, and a function to fetch next bytes as required.
|
||||
///
|
||||
/// Returns an error if the stream is invalid utf-8.
|
||||
pub fn read_char<F>(first: u8, next: F) -> Result<char,String>
|
||||
pub fn read_char<F>(first: u8, next: F) -> Result<char, String>
|
||||
where F: Fn() -> u8
|
||||
{
|
||||
if first < 0x80 {
|
||||
return Ok(first as char)
|
||||
return Ok(first as char);
|
||||
}
|
||||
|
||||
// Number of leading 1s determines the number of bytes we'll have to read
|
||||
let n_bytes = match (!first).leading_zeros() {
|
||||
n @ 2 ... 6 => n as usize,
|
||||
1 => return Err("First byte is continuation byte.".to_string()),
|
||||
7 ... 8 => return Err("WTF is this byte??".to_string()),
|
||||
7...8 => return Err("WTF is this byte??".to_string()),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
|
32
src/vec.rs
32
src/vec.rs
@ -1,7 +1,7 @@
|
||||
//! Points on the 2D character grid.
|
||||
|
||||
use std::ops::{Add, Sub, Mul, Div};
|
||||
use std::cmp::{min,max,Ordering};
|
||||
use std::cmp::{min, max, Ordering};
|
||||
|
||||
/// Simple 2D size, in characters.
|
||||
#[derive(Clone,Copy,PartialEq,Debug)]
|
||||
@ -14,20 +14,22 @@ pub struct Vec2 {
|
||||
|
||||
impl PartialOrd for Vec2 {
|
||||
fn partial_cmp(&self, other: &Vec2) -> Option<Ordering> {
|
||||
if self == other { Some(Ordering::Equal) }
|
||||
else if self.x < other.x && self.y < other.y { Some(Ordering::Less) }
|
||||
else if self.x > other.x && self.y > other.y { Some(Ordering::Greater) }
|
||||
else { None }
|
||||
if self == other {
|
||||
Some(Ordering::Equal)
|
||||
} else if self.x < other.x && self.y < other.y {
|
||||
Some(Ordering::Less)
|
||||
} else if self.x > other.x && self.y > other.y {
|
||||
Some(Ordering::Greater)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Vec2 {
|
||||
/// Creates a new Vec2 from coordinates.
|
||||
pub fn new(x: usize, y: usize) -> Self {
|
||||
Vec2 {
|
||||
x: x,
|
||||
y: y,
|
||||
}
|
||||
Vec2 { x: x, y: y }
|
||||
}
|
||||
|
||||
/// Returns a new Vec2 that is a maximum per coordinate.
|
||||
@ -52,7 +54,7 @@ impl Vec2 {
|
||||
|
||||
/// Alias for Vec::new(0,0).
|
||||
pub fn zero() -> Self {
|
||||
Vec2::new(0,0)
|
||||
Vec2::new(0, 0)
|
||||
}
|
||||
|
||||
/// Returns (max(self.x,other.x), self.y+other.y)
|
||||
@ -199,7 +201,9 @@ pub trait ToVec4 {
|
||||
}
|
||||
|
||||
impl ToVec4 for Vec4 {
|
||||
fn to_vec4(self) -> Vec4 { self }
|
||||
fn to_vec4(self) -> Vec4 {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl ToVec4 for (usize,usize,usize,usize) {
|
||||
@ -210,7 +214,10 @@ impl ToVec4 for (usize,usize,usize,usize) {
|
||||
|
||||
impl ToVec4 for (i32,i32,i32,i32) {
|
||||
fn to_vec4(self) -> Vec4 {
|
||||
Vec4::new(self.0 as usize, self.1 as usize, self.2 as usize, self.3 as usize)
|
||||
Vec4::new(self.0 as usize,
|
||||
self.1 as usize,
|
||||
self.2 as usize,
|
||||
self.3 as usize)
|
||||
}
|
||||
}
|
||||
|
||||
@ -270,4 +277,3 @@ impl Mul<usize> for Vec4 {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
use vec::{Vec2,ToVec2};
|
||||
use super::{View,ViewWrapper,SizeRequest,DimensionRequest};
|
||||
use vec::{Vec2, ToVec2};
|
||||
use super::{View, ViewWrapper, SizeRequest, DimensionRequest};
|
||||
|
||||
/// BoxView is a wrapper around an other view, with a given minimum size.
|
||||
pub struct BoxView<T: View> {
|
||||
@ -30,13 +30,21 @@ impl <T: View> ViewWrapper for BoxView<T> {
|
||||
wrap_impl!(&self.view);
|
||||
|
||||
fn wrap_get_min_size(&self, mut req: SizeRequest) -> Vec2 {
|
||||
if self.size.x > 0 { req.w = DimensionRequest::AtMost(self.size.x); }
|
||||
if self.size.y > 0 { req.h = DimensionRequest::AtMost(self.size.y); }
|
||||
if self.size.x > 0 {
|
||||
req.w = DimensionRequest::AtMost(self.size.x);
|
||||
}
|
||||
if self.size.y > 0 {
|
||||
req.h = DimensionRequest::AtMost(self.size.y);
|
||||
}
|
||||
|
||||
let mut size = self.view.get_min_size(req);
|
||||
|
||||
if self.size.x > 0 { size.x = self.size.x; }
|
||||
if self.size.y > 0 { size.y = self.size.y; }
|
||||
if self.size.x > 0 {
|
||||
size.x = self.size.x;
|
||||
}
|
||||
if self.size.y > 0 {
|
||||
size.y = self.size.y;
|
||||
}
|
||||
|
||||
size
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use theme::ColorPair;
|
||||
use ::Cursive;
|
||||
use Cursive;
|
||||
use vec::Vec2;
|
||||
use view::{View,SizeRequest};
|
||||
use view::{View, SizeRequest};
|
||||
use event::*;
|
||||
use printer::Printer;
|
||||
|
||||
@ -29,13 +29,17 @@ impl Button {
|
||||
impl View for Button {
|
||||
|
||||
fn draw(&mut self, printer: &Printer) {
|
||||
let style = if !printer.focused { ColorPair::Primary } else { ColorPair::Highlight };
|
||||
let style = if !printer.focused {
|
||||
ColorPair::Primary
|
||||
} else {
|
||||
ColorPair::Highlight
|
||||
};
|
||||
let x = printer.size.x - 1;
|
||||
|
||||
printer.with_color(style, |printer| {
|
||||
printer.print((1,0), &self.label);
|
||||
printer.print((0,0), "<");
|
||||
printer.print((x,0), ">");
|
||||
printer.print((1, 0), &self.label);
|
||||
printer.print((0, 0), "<");
|
||||
printer.print((x, 0), ">");
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1,13 +1,13 @@
|
||||
use std::cmp::max;
|
||||
use std::any::Any;
|
||||
|
||||
use ::{Cursive};
|
||||
use Cursive;
|
||||
use align::*;
|
||||
use event::*;
|
||||
use theme::ColorPair;
|
||||
use view::{View,SizeRequest,DimensionRequest,Selector};
|
||||
use view::{Button,SizedView};
|
||||
use vec::{Vec2,Vec4,ToVec4};
|
||||
use view::{View, SizeRequest, DimensionRequest, Selector};
|
||||
use view::{Button, SizedView};
|
||||
use vec::{Vec2, Vec4, ToVec4};
|
||||
use printer::Printer;
|
||||
|
||||
#[derive(PartialEq)]
|
||||
@ -46,8 +46,8 @@ impl Dialog {
|
||||
buttons: Vec::new(),
|
||||
title: String::new(),
|
||||
focus: Focus::Content,
|
||||
padding: Vec4::new(1,1,0,0),
|
||||
borders: Vec4::new(1,1,1,1),
|
||||
padding: Vec4::new(1, 1, 0, 0),
|
||||
borders: Vec4::new(1, 1, 1, 1),
|
||||
align: Align::top_right(),
|
||||
}
|
||||
}
|
||||
@ -111,59 +111,55 @@ impl View for Dialog {
|
||||
let width = if self.buttons.is_empty() {
|
||||
0
|
||||
} else {
|
||||
self.buttons.iter()
|
||||
self.buttons
|
||||
.iter()
|
||||
.map(|button| button.size.x)
|
||||
.fold(0, |a,b| a+b) +
|
||||
self.buttons.len() - 1
|
||||
.fold(0, |a, b| a + b) + self.buttons.len() - 1
|
||||
};
|
||||
let overhead = self.padding + self.borders;
|
||||
let mut offset = overhead.left +
|
||||
self.align.h.get_offset(width, printer.size.x -
|
||||
overhead.horizontal());
|
||||
self.align.h.get_offset(width, printer.size.x - overhead.horizontal());
|
||||
let y = printer.size.y - self.padding.bottom - self.borders.bottom - 1;
|
||||
|
||||
for (i,button) in self.buttons.iter_mut().enumerate() {
|
||||
for (i, button) in self.buttons.iter_mut().enumerate() {
|
||||
let size = button.size;
|
||||
// Add some special effect to the focused button
|
||||
button.draw(&printer.sub_printer(Vec2::new(offset, y), size, self.focus == Focus::Button(i)));
|
||||
button.draw(&printer.sub_printer(Vec2::new(offset, y),
|
||||
size,
|
||||
self.focus == Focus::Button(i)));
|
||||
// Keep 1 blank between two buttons
|
||||
offset += size.x + 1;
|
||||
// Also keep 1 blank above the buttons
|
||||
height = max(height, size.y+1);
|
||||
height = max(height, size.y + 1);
|
||||
}
|
||||
|
||||
// What do we have left?
|
||||
let inner_size = printer.size
|
||||
- Vec2::new(0, height)
|
||||
- self.borders.combined()
|
||||
- self.padding.combined();
|
||||
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(),
|
||||
self.content.draw(&printer.sub_printer(self.borders.top_left() + self.padding.top_left(),
|
||||
inner_size,
|
||||
self.focus == Focus::Content));
|
||||
|
||||
printer.print_box(Vec2::new(0,0), printer.size);
|
||||
printer.print_box(Vec2::new(0, 0), printer.size);
|
||||
|
||||
if self.title.len() > 0 {
|
||||
let len = self.title.chars().count();
|
||||
let x = (printer.size.x - len) / 2;
|
||||
printer.print((x-2,0), "┤ ");
|
||||
printer.print((x+len,0), " ├");
|
||||
printer.print((x - 2, 0), "┤ ");
|
||||
printer.print((x + len, 0), " ├");
|
||||
|
||||
printer.with_color(ColorPair::TitlePrimary,
|
||||
|p| p.print((x,0), &self.title));
|
||||
printer.with_color(ColorPair::TitlePrimary, |p| p.print((x, 0), &self.title));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fn get_min_size(&self, req: SizeRequest) -> Vec2 {
|
||||
// Padding and borders are not available for kids.
|
||||
let content_req = req.reduced(self.padding.combined() +
|
||||
self.borders.combined());
|
||||
let content_req = req.reduced(self.padding.combined() + self.borders.combined());
|
||||
let content_size = self.content.get_min_size(content_req);
|
||||
|
||||
let mut buttons_size = Vec2::new(0,0);
|
||||
let mut buttons_size = Vec2::new(0, 0);
|
||||
if !self.buttons.is_empty() {
|
||||
buttons_size.x += self.buttons.len() - 1;
|
||||
}
|
||||
@ -176,8 +172,9 @@ impl View for Dialog {
|
||||
// On the Y axis, we add buttons and content.
|
||||
// On the X axis, we take the max.
|
||||
let mut inner_size = Vec2::new(max(content_size.x, buttons_size.x),
|
||||
content_size.y + buttons_size.y)
|
||||
+ self.padding.combined() + self.borders.combined();
|
||||
content_size.y + buttons_size.y) +
|
||||
self.padding.combined() +
|
||||
self.borders.combined();
|
||||
|
||||
if self.title.len() > 0 {
|
||||
// If we have a title, we have to fit it too!
|
||||
@ -199,7 +196,7 @@ impl View for Dialog {
|
||||
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);
|
||||
buttons_height = max(buttons_height, size.y + 1);
|
||||
button.layout(size);
|
||||
}
|
||||
|
||||
@ -216,7 +213,7 @@ impl View for Dialog {
|
||||
// Default to leftmost button when going down.
|
||||
self.focus = Focus::Button(0);
|
||||
EventResult::Consumed(None)
|
||||
},
|
||||
}
|
||||
Event::KeyEvent(Key::Tab) | Event::KeyEvent(Key::ShiftTab) => {
|
||||
self.focus = Focus::Button(0);
|
||||
EventResult::Consumed(None)
|
||||
@ -236,7 +233,7 @@ impl View for Dialog {
|
||||
} else {
|
||||
EventResult::Ignored
|
||||
}
|
||||
},
|
||||
}
|
||||
Event::KeyEvent(Key::Tab) | Event::KeyEvent(Key::ShiftTab) => {
|
||||
if self.content.take_focus() {
|
||||
self.focus = Focus::Content;
|
||||
@ -244,16 +241,16 @@ impl View for Dialog {
|
||||
} else {
|
||||
EventResult::Ignored
|
||||
}
|
||||
},
|
||||
}
|
||||
// Left and Right move to other buttons
|
||||
Event::KeyEvent(Key::Right) if i+1 < self.buttons.len() => {
|
||||
self.focus = Focus::Button(i+1);
|
||||
Event::KeyEvent(Key::Right) if i + 1 < self.buttons.len() => {
|
||||
self.focus = Focus::Button(i + 1);
|
||||
EventResult::Consumed(None)
|
||||
},
|
||||
}
|
||||
Event::KeyEvent(Key::Left) if i > 0 => {
|
||||
self.focus = Focus::Button(i-1);
|
||||
self.focus = Focus::Button(i - 1);
|
||||
EventResult::Consumed(None)
|
||||
},
|
||||
}
|
||||
_ => EventResult::Ignored,
|
||||
},
|
||||
res => res,
|
||||
|
@ -1,10 +1,10 @@
|
||||
use ncurses;
|
||||
use ncurses::{self, chtype};
|
||||
|
||||
use std::cmp::min;
|
||||
|
||||
use theme::ColorPair;
|
||||
use vec::Vec2;
|
||||
use view::{View,IdView,SizeRequest};
|
||||
use view::{View, IdView, SizeRequest};
|
||||
use event::*;
|
||||
use printer::Printer;
|
||||
|
||||
@ -20,9 +20,8 @@ pub struct EditView {
|
||||
/// When the content is too long for the display, offset it
|
||||
offset: usize,
|
||||
/// Last display length, to know the possible offset range
|
||||
last_length: usize,
|
||||
// scrollable: bool,
|
||||
// TODO: add a max text length?
|
||||
last_length: usize, /* scrollable: bool,
|
||||
* TODO: add a max text length? */
|
||||
}
|
||||
|
||||
impl EditView {
|
||||
@ -33,8 +32,7 @@ impl EditView {
|
||||
cursor: 0,
|
||||
offset: 0,
|
||||
min_length: 1,
|
||||
last_length: 0,
|
||||
// scrollable: false,
|
||||
last_length: 0, /* scrollable: false, */
|
||||
}
|
||||
}
|
||||
|
||||
@ -71,7 +69,7 @@ impl EditView {
|
||||
|
||||
fn remove_char(s: &mut String, cursor: usize) {
|
||||
let i = match s.char_indices().nth(cursor) {
|
||||
Some((i,_)) => i,
|
||||
Some((i, _)) => i,
|
||||
None => return,
|
||||
};
|
||||
s.remove(i);
|
||||
@ -84,15 +82,15 @@ impl View for EditView {
|
||||
printer.with_color(ColorPair::Secondary, |printer| {
|
||||
printer.with_style(ncurses::A_REVERSE(), |printer| {
|
||||
if len < self.last_length {
|
||||
printer.print((0,0), &self.content);
|
||||
printer.print_hline((len,0), printer.size.x-len, '_' as u64);
|
||||
printer.print((0, 0), &self.content);
|
||||
printer.print_hline((len, 0), printer.size.x - len, '_' as chtype);
|
||||
} else {
|
||||
let visible_end = min(self.content.len(), self.offset + self.last_length);
|
||||
|
||||
let content = &self.content[self.offset..visible_end];
|
||||
printer.print((0,0), content);
|
||||
printer.print((0, 0), content);
|
||||
if visible_end - self.offset < printer.size.x {
|
||||
printer.print((printer.size.x-1,0), "_");
|
||||
printer.print((printer.size.x - 1, 0), "_");
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -103,9 +101,14 @@ impl View for EditView {
|
||||
'_'
|
||||
} else {
|
||||
// Get the char from the string... Is it so hard?
|
||||
self.content.chars().nth(self.cursor).expect(&format!("Found no char at cursor {} in {}", self.cursor, self.content))
|
||||
self.content
|
||||
.chars()
|
||||
.nth(self.cursor)
|
||||
.expect(&format!("Found no char at cursor {} in {}",
|
||||
self.cursor,
|
||||
self.content))
|
||||
};
|
||||
printer.print_hline((self.cursor-self.offset, 0), 1, c as u64);
|
||||
printer.print_hline((self.cursor - self.offset, 0), 1, c as chtype);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -130,18 +133,23 @@ impl View for EditView {
|
||||
|
||||
match self.content.char_indices().nth(self.cursor) {
|
||||
None => self.content.push(ch),
|
||||
Some((i,_)) => self.content.insert(i, ch),
|
||||
Some((i, _)) => self.content.insert(i, ch),
|
||||
}
|
||||
// TODO: handle wide (CJK) chars
|
||||
self.cursor += 1;
|
||||
},
|
||||
}
|
||||
Event::KeyEvent(key) => match key {
|
||||
Key::Home => self.cursor = 0,
|
||||
Key::End => self.cursor = self.content.chars().count(),
|
||||
Key::Left if self.cursor > 0 => self.cursor -= 1,
|
||||
Key::Right if self.cursor < self.content.chars().count() => self.cursor += 1,
|
||||
Key::Backspace if self.cursor > 0 => { self.cursor -= 1; remove_char(&mut self.content, self.cursor); },
|
||||
Key::Del if self.cursor < self.content.chars().count() => { remove_char(&mut self.content, self.cursor); },
|
||||
Key::Backspace if self.cursor > 0 => {
|
||||
self.cursor -= 1;
|
||||
remove_char(&mut self.content, self.cursor);
|
||||
}
|
||||
Key::Del if self.cursor < self.content.chars().count() => {
|
||||
remove_char(&mut self.content, self.cursor);
|
||||
}
|
||||
_ => return EventResult::Ignored,
|
||||
},
|
||||
}
|
||||
@ -154,7 +162,7 @@ impl View for EditView {
|
||||
} else if self.cursor < self.offset {
|
||||
self.offset = self.cursor;
|
||||
}
|
||||
if self.offset + self.last_length > self.content.len() + 1{
|
||||
if self.offset + self.last_length > self.content.len() + 1 {
|
||||
|
||||
self.offset = if self.content.len() > self.last_length {
|
||||
self.content.len() - self.last_length + 1
|
||||
|
@ -1,4 +1,4 @@
|
||||
use view::{View,ViewWrapper,SizeRequest,DimensionRequest};
|
||||
use view::{View, ViewWrapper, SizeRequest, DimensionRequest};
|
||||
use vec::Vec2;
|
||||
|
||||
/// Simple wrapper view that asks for all the space it can get.
|
||||
@ -9,9 +9,7 @@ pub struct FullView<T: View> {
|
||||
impl <T: View> FullView<T> {
|
||||
/// Wraps the given view into a new FullView.
|
||||
pub fn new(view: T) -> Self {
|
||||
FullView {
|
||||
view: view,
|
||||
}
|
||||
FullView { view: view }
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,6 +28,6 @@ impl <T: View> ViewWrapper for FullView<T> {
|
||||
DimensionRequest::Unknown => self.view.get_min_size(req).y,
|
||||
};
|
||||
|
||||
Vec2::new(w,h)
|
||||
Vec2::new(w, h)
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
use std::any::Any;
|
||||
|
||||
use view::{View,ViewWrapper,Selector};
|
||||
use view::{View, ViewWrapper, Selector};
|
||||
|
||||
/// Wrapper view that allows to select its content with a fixed string id.
|
||||
pub struct IdView<T: View> {
|
||||
|
@ -1,9 +1,9 @@
|
||||
use std::collections::HashMap;
|
||||
use std::rc::Rc;
|
||||
|
||||
use ::Cursive;
|
||||
use event::{Event,EventResult,ToEvent,Callback};
|
||||
use super::{View,ViewWrapper};
|
||||
use Cursive;
|
||||
use event::{Event, EventResult, ToEvent, Callback};
|
||||
use super::{View, ViewWrapper};
|
||||
|
||||
/// A simple wrapper view that catches some ignored event from its child.
|
||||
///
|
||||
@ -23,7 +23,7 @@ impl KeyEventView {
|
||||
}
|
||||
|
||||
/// Registers a callback when the given key is ignored by the child.
|
||||
pub fn register<F,E: ToEvent>(mut self, event: E, cb: F) -> Self
|
||||
pub fn register<F, E: ToEvent>(mut self, event: E, cb: F) -> Self
|
||||
where F: Fn(&mut Cursive) + 'static
|
||||
{
|
||||
self.callbacks.insert(event.to_event(), Rc::new(Box::new(cb)));
|
||||
|
@ -1,8 +1,8 @@
|
||||
use view::{View,SizeRequest,DimensionRequest};
|
||||
use view::{View, SizeRequest, DimensionRequest};
|
||||
use vec::Vec2;
|
||||
use printer::Printer;
|
||||
use orientation::Orientation;
|
||||
use event::{Event,EventResult,Key};
|
||||
use event::{Event, EventResult, Key};
|
||||
|
||||
/// Arranges its children linearly according to its orientation.
|
||||
pub struct LinearLayout {
|
||||
@ -63,7 +63,7 @@ impl LinearLayout {
|
||||
fn find_max(list: &Vec<usize>) -> usize {
|
||||
let mut max_value = 0;
|
||||
let mut max = 0;
|
||||
for (i,&x) in list.iter().enumerate() {
|
||||
for (i, &x) in list.iter().enumerate() {
|
||||
if x > max_value {
|
||||
max_value = x;
|
||||
max = i;
|
||||
@ -78,9 +78,11 @@ fn find_max(list: &Vec<usize>) -> usize {
|
||||
fn share(total: usize, weights: Vec<usize>) -> Vec<usize> {
|
||||
// It first give a base value to everyone, which is their truncated share.
|
||||
// Then, it gives the rest to the most deserving.
|
||||
if weights.len() == 0 { return Vec::new(); }
|
||||
if weights.len() == 0 {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
let sum_weight = weights.iter().fold(0,|a,b| a+b);
|
||||
let sum_weight = weights.iter().fold(0, |a, b| a + b);
|
||||
if sum_weight == 0 {
|
||||
return (0..weights.len()).map(|_| 0).collect();
|
||||
}
|
||||
@ -93,7 +95,7 @@ fn share(total: usize, weights: Vec<usize>) -> Vec<usize> {
|
||||
let b = total * weight / sum_weight;
|
||||
extra -= b;
|
||||
base.push(b);
|
||||
rest.push(total * weight - b*sum_weight);
|
||||
rest.push(total * weight - b * sum_weight);
|
||||
}
|
||||
|
||||
// TODO: better to sort (base,rest) as one array and pick the extra first.
|
||||
@ -110,7 +112,7 @@ impl View for LinearLayout {
|
||||
fn draw(&mut self, printer: &Printer) {
|
||||
// Use pre-computed sizes
|
||||
let mut offset = Vec2::zero();
|
||||
for (i,child) in self.children.iter_mut().enumerate() {
|
||||
for (i, child) in self.children.iter_mut().enumerate() {
|
||||
child.view.draw(&printer.sub_printer(offset, child.size, i == self.focus));
|
||||
|
||||
*self.orientation.get_ref(&mut offset) += self.orientation.get(&child.size);
|
||||
@ -119,18 +121,21 @@ impl View for LinearLayout {
|
||||
|
||||
fn layout(&mut self, size: Vec2) {
|
||||
// Compute the very minimal required size
|
||||
let req = SizeRequest{
|
||||
let req = SizeRequest {
|
||||
w: DimensionRequest::AtMost(size.x),
|
||||
h: DimensionRequest::AtMost(size.y),
|
||||
};
|
||||
let min_sizes: Vec<Vec2> = self.children.iter().map(|child| child.view.get_min_size(req)).collect();
|
||||
let min_sizes: Vec<Vec2> = self.children
|
||||
.iter()
|
||||
.map(|child| child.view.get_min_size(req))
|
||||
.collect();
|
||||
let min_size = self.orientation.stack(min_sizes.iter());
|
||||
|
||||
// Emulate 'non-strict inequality' on integers
|
||||
// (default comparison on Vec2 is strict)
|
||||
if !(min_size < size+(1,1)) {
|
||||
if !(min_size < size + (1, 1)) {
|
||||
// Error! Not enough space! Emergency procedures!
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
// Now share this extra space among everyone
|
||||
@ -138,11 +143,14 @@ impl View for LinearLayout {
|
||||
let extras = {
|
||||
let extra = size - min_size;
|
||||
let space = self.orientation.get(&extra);
|
||||
share(space, self.children.iter().map(|child| child.weight).collect())
|
||||
share(space,
|
||||
self.children.iter().map(|child| child.weight).collect())
|
||||
};
|
||||
|
||||
|
||||
for (child,(child_size,extra)) in self.children.iter_mut().zip(min_sizes.iter().zip(extras.iter())) {
|
||||
for (child, (child_size, extra)) in self.children
|
||||
.iter_mut()
|
||||
.zip(min_sizes.iter().zip(extras.iter())) {
|
||||
let mut child_size = *child_size;
|
||||
*self.orientation.get_ref(&mut child_size) += *extra;
|
||||
*self.orientation.swap().get_ref(&mut child_size) = self.orientation.swap().get(&size);
|
||||
@ -153,7 +161,10 @@ impl View for LinearLayout {
|
||||
|
||||
fn get_min_size(&self, req: SizeRequest) -> Vec2 {
|
||||
// First, make a naive scenario: everything will work fine.
|
||||
let sizes: Vec<Vec2> = self.children.iter().map(|view| view.view.get_min_size(req)).collect();
|
||||
let sizes: Vec<Vec2> = self.children
|
||||
.iter()
|
||||
.map(|view| view.view.get_min_size(req))
|
||||
.collect();
|
||||
self.orientation.stack(sizes.iter())
|
||||
|
||||
|
||||
@ -173,27 +184,31 @@ impl View for LinearLayout {
|
||||
Event::KeyEvent(Key::Tab) if self.focus > 0 => {
|
||||
self.focus -= 1;
|
||||
EventResult::Consumed(None)
|
||||
},
|
||||
Event::KeyEvent(Key::ShiftTab) if self.focus+1 < self.children.len() => {
|
||||
}
|
||||
Event::KeyEvent(Key::ShiftTab) if self.focus + 1 < self.children.len() => {
|
||||
self.focus += 1;
|
||||
EventResult::Consumed(None)
|
||||
}
|
||||
Event::KeyEvent(Key::Left) if self.orientation == Orientation::Horizontal && self.focus > 0 => {
|
||||
Event::KeyEvent(Key::Left) if self.orientation == Orientation::Horizontal &&
|
||||
self.focus > 0 => {
|
||||
self.focus -= 1;
|
||||
EventResult::Consumed(None)
|
||||
},
|
||||
Event::KeyEvent(Key::Up) if self.orientation == Orientation::Vertical && self.focus > 0 => {
|
||||
}
|
||||
Event::KeyEvent(Key::Up) if self.orientation == Orientation::Vertical &&
|
||||
self.focus > 0 => {
|
||||
self.focus -= 1;
|
||||
EventResult::Consumed(None)
|
||||
},
|
||||
Event::KeyEvent(Key::Right) if self.orientation == Orientation::Horizontal && self.focus+1 < self.children.len() => {
|
||||
}
|
||||
Event::KeyEvent(Key::Right) if self.orientation == Orientation::Horizontal &&
|
||||
self.focus + 1 < self.children.len() => {
|
||||
self.focus += 1;
|
||||
EventResult::Consumed(None)
|
||||
},
|
||||
Event::KeyEvent(Key::Down) if self.orientation == Orientation::Vertical && self.focus+1 < self.children.len() => {
|
||||
}
|
||||
Event::KeyEvent(Key::Down) if self.orientation == Orientation::Vertical &&
|
||||
self.focus + 1 < self.children.len() => {
|
||||
self.focus += 1;
|
||||
EventResult::Consumed(None)
|
||||
},
|
||||
}
|
||||
_ => EventResult::Ignored,
|
||||
},
|
||||
res => res,
|
||||
|
@ -1,6 +1,6 @@
|
||||
//! Defines various views to use when creating the layout.
|
||||
|
||||
#[macro_use] mod view_wrapper;
|
||||
#[macro_use]mod view_wrapper;
|
||||
|
||||
mod box_view;
|
||||
mod button;
|
||||
@ -22,11 +22,11 @@ mod view_path;
|
||||
|
||||
use std::any::Any;
|
||||
|
||||
use event::{Event,EventResult};
|
||||
use event::{Event, EventResult};
|
||||
use vec::Vec2;
|
||||
use printer::Printer;
|
||||
|
||||
pub use self::request::{DimensionRequest,SizeRequest};
|
||||
pub use self::request::{DimensionRequest, SizeRequest};
|
||||
pub use self::scroll::ScrollBase;
|
||||
|
||||
pub use self::id_view::IdView;
|
||||
@ -49,24 +49,33 @@ pub use self::view_wrapper::ViewWrapper;
|
||||
/// Main trait defining a view behaviour.
|
||||
pub trait View {
|
||||
/// Called when a key was pressed. Default implementation just ignores it.
|
||||
fn on_event(&mut self, Event) -> EventResult { EventResult::Ignored }
|
||||
fn on_event(&mut self, Event) -> EventResult {
|
||||
EventResult::Ignored
|
||||
}
|
||||
|
||||
/// Returns the minimum size the view requires under the given restrictions.
|
||||
fn get_min_size(&self, SizeRequest) -> Vec2 { Vec2::new(1,1) }
|
||||
fn get_min_size(&self, SizeRequest) -> Vec2 {
|
||||
Vec2::new(1, 1)
|
||||
}
|
||||
|
||||
/// Called once the size for this view has been decided, so it can
|
||||
/// propagate the information to its children.
|
||||
fn layout(&mut self, Vec2) { }
|
||||
fn layout(&mut self, Vec2) {
|
||||
}
|
||||
|
||||
/// Draws the view with the given printer (includes bounds) and focus.
|
||||
fn draw(&mut self, printer: &Printer);
|
||||
|
||||
/// Finds the view pointed to by the given path.
|
||||
/// Returns None if the path doesn't lead to a view.
|
||||
fn find(&mut self, &Selector) -> Option<&mut Any> { None }
|
||||
fn find(&mut self, &Selector) -> Option<&mut Any> {
|
||||
None
|
||||
}
|
||||
|
||||
/// This view is offered focus. Will it take it?
|
||||
fn take_focus(&mut self) -> bool { false }
|
||||
fn take_focus(&mut self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Selects a single view (if any) in the tree.
|
||||
@ -76,4 +85,3 @@ pub enum Selector<'a> {
|
||||
/// Selects a view from its path
|
||||
Path(&'a ViewPath),
|
||||
}
|
||||
|
||||
|
@ -49,5 +49,3 @@ impl SizeRequest {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
use std::cmp::{min,max};
|
||||
use std::cmp::{min, max};
|
||||
use ncurses::chtype;
|
||||
|
||||
use theme::ColorPair;
|
||||
use vec::Vec2;
|
||||
@ -68,7 +69,7 @@ impl ScrollBase {
|
||||
|
||||
/// Scroll down by the given number of line, never going further than the bottom of the view.
|
||||
pub fn scroll_down(&mut self, n: usize) {
|
||||
self.start_line = min(self.start_line+n, self.content_height - self.view_height);
|
||||
self.start_line = min(self.start_line + n, self.content_height - self.view_height);
|
||||
}
|
||||
|
||||
/// Scroll up by the given number of lines, never going above the top of the view.
|
||||
@ -98,15 +99,20 @@ impl ScrollBase {
|
||||
/// });
|
||||
/// ```
|
||||
pub fn draw<F>(&self, printer: &Printer, line_drawer: F)
|
||||
where F: Fn(&Printer,usize)
|
||||
where F: Fn(&Printer, usize)
|
||||
{
|
||||
// Print the content in a sub_printer
|
||||
let max_y = min(self.view_height, self.content_height - self.start_line);
|
||||
let w = if self.scrollable() { printer.size.x - 2 } else { printer.size.x };
|
||||
let w = if self.scrollable() {
|
||||
printer.size.x - 2
|
||||
} else {
|
||||
printer.size.x
|
||||
};
|
||||
for y in 0..max_y {
|
||||
// Y is the actual coordinate of the line.
|
||||
// The item ID is then Y + self.start_line
|
||||
line_drawer(&printer.sub_printer(Vec2::new(0,y),Vec2::new(w,1),true), y+self.start_line);
|
||||
line_drawer(&printer.sub_printer(Vec2::new(0, y), Vec2::new(w, 1), true),
|
||||
y + self.start_line);
|
||||
}
|
||||
|
||||
|
||||
@ -115,18 +121,22 @@ impl ScrollBase {
|
||||
// We directly compute the size of the scrollbar (this allow use to avoid using floats).
|
||||
// (ratio) * max_height
|
||||
// Where ratio is ({start or end} / content.height)
|
||||
let height = max(1,self.view_height * self.view_height / self.content_height);
|
||||
let height = max(1, self.view_height * self.view_height / self.content_height);
|
||||
// Number of different possible positions
|
||||
let steps = self.view_height - height + 1;
|
||||
|
||||
// Now
|
||||
let start = steps * self.start_line / (1 + self.content_height - self.view_height);
|
||||
|
||||
let color = if printer.focused { ColorPair::Highlight } else { ColorPair::HighlightInactive };
|
||||
let color = if printer.focused {
|
||||
ColorPair::Highlight
|
||||
} else {
|
||||
ColorPair::HighlightInactive
|
||||
};
|
||||
|
||||
printer.print_vline((printer.size.x-1,0), printer.size.y, '|' as u64);
|
||||
printer.print_vline((printer.size.x - 1, 0), printer.size.y, '|' as chtype);
|
||||
printer.with_color(color, |printer| {
|
||||
printer.print_vline((printer.size.x-1, start), height, ' ' as u64);
|
||||
printer.print_vline((printer.size.x - 1, start), height, ' ' as chtype);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,12 @@
|
||||
use std::cmp::min;
|
||||
use std::rc::Rc;
|
||||
use ncurses::chtype;
|
||||
|
||||
use theme::ColorPair;
|
||||
use ::Cursive;
|
||||
use Cursive;
|
||||
use align::*;
|
||||
use view::{View,IdView,SizeRequest,DimensionRequest};
|
||||
use event::{Event,EventResult,Key};
|
||||
use view::{View, IdView, SizeRequest, DimensionRequest};
|
||||
use event::{Event, EventResult, Key};
|
||||
use vec::Vec2;
|
||||
use printer::Printer;
|
||||
use super::scroll::ScrollBase;
|
||||
@ -27,11 +28,11 @@ impl <T> Item<T> {
|
||||
/// View to select an item among a list.
|
||||
///
|
||||
/// It contains a list of values of type T, with associated labels.
|
||||
pub struct SelectView<T=String> {
|
||||
pub struct SelectView<T = String> {
|
||||
items: Vec<Item<T>>,
|
||||
focus: usize,
|
||||
scrollbase: ScrollBase,
|
||||
select_cb: Option<Rc<Box<Fn(&mut Cursive,&T)>>>,
|
||||
select_cb: Option<Rc<Box<Fn(&mut Cursive, &T)>>>,
|
||||
align: Align,
|
||||
}
|
||||
|
||||
@ -91,7 +92,7 @@ impl <T: 'static> SelectView<T> {
|
||||
|
||||
/// Adds a item to the list, with given label and value.
|
||||
pub fn add_item(&mut self, label: &str, value: T) {
|
||||
self.items.push(Item::new(label,value));
|
||||
self.items.push(Item::new(label, value));
|
||||
}
|
||||
|
||||
/// Chainable variant of add_item
|
||||
@ -125,21 +126,24 @@ impl <T: 'static> View for SelectView<T> {
|
||||
|
||||
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| {
|
||||
self.scrollbase.draw(printer, |printer, i| {
|
||||
let style = if i == self.focus {
|
||||
if printer.focused { ColorPair::Highlight }
|
||||
else { ColorPair::HighlightInactive }
|
||||
if printer.focused {
|
||||
ColorPair::Highlight
|
||||
} else {
|
||||
ColorPair::HighlightInactive
|
||||
}
|
||||
} else {
|
||||
ColorPair::Primary
|
||||
};
|
||||
printer.with_color(style, |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, ' ' as u64);
|
||||
printer.print((x,0), &self.items[i].label);
|
||||
printer.print_hline((x+l,0), printer.size.x-l-x, ' ' as u64);
|
||||
printer.print_hline((0, 0), x, ' ' as chtype);
|
||||
printer.print((x, 0), &self.items[i].label);
|
||||
printer.print_hline((x + l, 0), printer.size.x - l - x, ' ' as chtype);
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -157,39 +161,43 @@ impl <T: 'static> View for SelectView<T> {
|
||||
};
|
||||
|
||||
// Add 2 spaces for the scrollbar if we need
|
||||
let w = if scrolling { w + 2 } else { w };
|
||||
let w = if scrolling {
|
||||
w + 2
|
||||
} else {
|
||||
w
|
||||
};
|
||||
|
||||
Vec2::new(w,h)
|
||||
Vec2::new(w, h)
|
||||
}
|
||||
|
||||
fn on_event(&mut self, event: Event) -> EventResult {
|
||||
match event {
|
||||
Event::KeyEvent(Key::Up) if self.focus > 0 => self.focus -= 1,
|
||||
Event::KeyEvent(Key::Down) if self.focus + 1 < self.items.len() => self.focus += 1,
|
||||
Event::KeyEvent(Key::PageUp) => self.focus -= min(self.focus,10),
|
||||
Event::KeyEvent(Key::PageDown) => self.focus = min(self.focus+10,self.items.len()-1),
|
||||
Event::KeyEvent(Key::PageUp) => self.focus -= min(self.focus, 10),
|
||||
Event::KeyEvent(Key::PageDown) =>
|
||||
self.focus = min(self.focus + 10, self.items.len() - 1),
|
||||
Event::KeyEvent(Key::Home) => self.focus = 0,
|
||||
Event::KeyEvent(Key::End) => self.focus = self.items.len()-1,
|
||||
Event::KeyEvent(Key::End) => self.focus = self.items.len() - 1,
|
||||
Event::KeyEvent(Key::Enter) if self.select_cb.is_some() => {
|
||||
if let Some(ref cb) = self.select_cb {
|
||||
let cb = cb.clone();
|
||||
let v = self.selection();
|
||||
return EventResult::Consumed(Some(Rc::new(Box::new(move |s| cb(s, &*v)))));
|
||||
}
|
||||
},
|
||||
}
|
||||
Event::CharEvent(c) => {
|
||||
// Starting from the current focus, find the first item that match the char.
|
||||
// Cycle back to the beginning of the list when we reach the end.
|
||||
// This is achieved by chaining twice the iterator
|
||||
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))
|
||||
{
|
||||
if let Some((i, _)) = iter.enumerate()
|
||||
.skip(self.focus + 1)
|
||||
.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();
|
||||
}
|
||||
},
|
||||
}
|
||||
_ => return EventResult::Ignored,
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
use view::{View,ViewWrapper,SizeRequest};
|
||||
use ncurses::chtype;
|
||||
use view::{View, ViewWrapper, SizeRequest};
|
||||
use printer::Printer;
|
||||
use vec::Vec2;
|
||||
use theme::ColorPair;
|
||||
@ -13,9 +14,7 @@ pub struct ShadowView<T: View> {
|
||||
impl <T: View> ShadowView<T> {
|
||||
/// Wraps the given view.
|
||||
pub fn new(view: T) -> Self {
|
||||
ShadowView {
|
||||
view: view,
|
||||
}
|
||||
ShadowView { view: view }
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,32 +23,30 @@ impl <T: View> ViewWrapper for ShadowView<T> {
|
||||
wrap_impl!(&self.view);
|
||||
|
||||
fn wrap_get_min_size(&self, req: SizeRequest) -> Vec2 {
|
||||
self.view.get_min_size(req.reduced((2,2))) + (2,2)
|
||||
self.view.get_min_size(req.reduced((2, 2))) + (2, 2)
|
||||
}
|
||||
|
||||
fn wrap_layout(&mut self, size: Vec2) {
|
||||
self.view.layout(size - (2,2));
|
||||
self.view.layout(size - (2, 2));
|
||||
}
|
||||
|
||||
fn wrap_draw(&mut self, printer: &Printer) {
|
||||
|
||||
printer.with_color(ColorPair::Primary, |printer| {
|
||||
// Draw the view background
|
||||
for y in 1..printer.size.y-1 {
|
||||
printer.print_hline((1,y), printer.size.x-2, ' ' as u64);
|
||||
for y in 1..printer.size.y - 1 {
|
||||
printer.print_hline((1, y), printer.size.x - 2, ' ' as chtype);
|
||||
}
|
||||
});
|
||||
|
||||
self.view.draw(&printer.sub_printer(Vec2::new(1,1),
|
||||
printer.size - (2,2),
|
||||
true));
|
||||
self.view.draw(&printer.sub_printer(Vec2::new(1, 1), printer.size - (2, 2), true));
|
||||
|
||||
let h = printer.size.y-1;
|
||||
let w = printer.size.x-1;
|
||||
let h = printer.size.y - 1;
|
||||
let w = printer.size.x - 1;
|
||||
|
||||
printer.with_color(ColorPair::Shadow, |printer| {
|
||||
printer.print_hline((2,h), w-1, ' ' as u64);
|
||||
printer.print_vline((w,2), h-1, ' ' as u64);
|
||||
printer.print_hline((2, h), w - 1, ' ' as chtype);
|
||||
printer.print_vline((w, 2), h - 1, ' ' as chtype);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ use vec::Vec2;
|
||||
use view::ViewWrapper;
|
||||
|
||||
/// Wrapper around a view that remembers its size.
|
||||
pub struct SizedView <T: View> {
|
||||
pub struct SizedView<T: View> {
|
||||
/// Wrapped view.
|
||||
pub view: T,
|
||||
/// Cached size from the last layout() call.
|
||||
|
@ -1,9 +1,8 @@
|
||||
use std::cmp::max;
|
||||
use std::any::Any;
|
||||
|
||||
use vec::Vec2;
|
||||
use view::{View,SizeRequest,DimensionRequest,Selector,ShadowView};
|
||||
use event::{Event,EventResult};
|
||||
use view::{View, SizeRequest, DimensionRequest, Selector, ShadowView};
|
||||
use event::{Event, EventResult};
|
||||
use printer::Printer;
|
||||
|
||||
/// Simple stack of views.
|
||||
@ -22,16 +21,14 @@ struct Layer {
|
||||
impl StackView {
|
||||
/// Creates a new empty StackView
|
||||
pub fn new() -> Self {
|
||||
StackView {
|
||||
layers: Vec::new(),
|
||||
}
|
||||
StackView { layers: Vec::new() }
|
||||
}
|
||||
|
||||
/// Add new view on top of the stack.
|
||||
pub fn add_layer<T: 'static + View>(&mut self, view: T) {
|
||||
self.layers.push(Layer {
|
||||
view: Box::new(ShadowView::new(view)),
|
||||
size: Vec2::new(0,0),
|
||||
size: Vec2::new(0, 0),
|
||||
virgin: true,
|
||||
});
|
||||
}
|
||||
@ -45,12 +42,12 @@ impl StackView {
|
||||
impl View for StackView {
|
||||
fn draw(&mut self, printer: &Printer) {
|
||||
let last = self.layers.len();
|
||||
for (i,v) in self.layers.iter_mut().enumerate() {
|
||||
for (i, v) in self.layers.iter_mut().enumerate() {
|
||||
// Center the view
|
||||
let size = v.size;
|
||||
let offset = (printer.size - size) / 2;
|
||||
// TODO: only draw focus for the top view
|
||||
v.view.draw(&printer.sub_printer(offset, size, i+1 == last));
|
||||
v.view.draw(&printer.sub_printer(offset, size, i + 1 == last));
|
||||
}
|
||||
}
|
||||
|
||||
@ -78,7 +75,7 @@ impl View for StackView {
|
||||
|
||||
fn get_min_size(&self, size: SizeRequest) -> Vec2 {
|
||||
// The min size is the max of all children's
|
||||
let mut s = Vec2::new(1,1);
|
||||
let mut s = Vec2::new(1, 1);
|
||||
|
||||
for layer in self.layers.iter() {
|
||||
let vs = layer.view.get_min_size(size);
|
||||
@ -91,7 +88,7 @@ impl View for StackView {
|
||||
fn take_focus(&mut self) -> bool {
|
||||
match self.layers.last_mut() {
|
||||
None => false,
|
||||
Some(mut v) => v.view.take_focus()
|
||||
Some(mut v) => v.view.take_focus(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
use std::cmp::max;
|
||||
|
||||
use vec::Vec2;
|
||||
use view::{View,DimensionRequest,SizeRequest};
|
||||
use view::{View, DimensionRequest, SizeRequest};
|
||||
use div::*;
|
||||
use printer::Printer;
|
||||
use align::*;
|
||||
@ -98,7 +98,8 @@ impl TextView {
|
||||
/// Returns the number of lines required to display the content
|
||||
/// with the given width.
|
||||
fn get_num_lines(&self, max_width: usize) -> usize {
|
||||
self.content.split("\n")
|
||||
self.content
|
||||
.split("\n")
|
||||
.map(|line| get_line_span(line, max_width))
|
||||
.fold(0, |sum, x| sum + x)
|
||||
}
|
||||
@ -161,7 +162,7 @@ impl <'a> Iterator for LinesIterator<'a> {
|
||||
if content[..next].chars().count() <= self.width {
|
||||
// We found a newline before the allowed limit.
|
||||
// Break early.
|
||||
self.start += next+1;
|
||||
self.start += next + 1;
|
||||
return Some(Row {
|
||||
start: start,
|
||||
end: next + start,
|
||||
@ -173,22 +174,22 @@ impl <'a> Iterator for LinesIterator<'a> {
|
||||
if content_len <= self.width {
|
||||
// I thought it would be longer! -- that's what she said :(
|
||||
self.start += content.len();
|
||||
return Some(Row{
|
||||
return Some(Row {
|
||||
start: start,
|
||||
end: start + content.len(),
|
||||
});
|
||||
}
|
||||
|
||||
let i = if content_len == self.width+1 {
|
||||
let i = if content_len == self.width + 1 {
|
||||
// We can't look at the index if we're looking at the end of the string
|
||||
content.len()
|
||||
} else {
|
||||
content.char_indices().nth(self.width+1).unwrap().0
|
||||
content.char_indices().nth(self.width + 1).unwrap().0
|
||||
};
|
||||
let substr = &content[..i];
|
||||
if let Some(i) = substr.rfind(" ") {
|
||||
// If we have to break, try to find a whitespace for that.
|
||||
self.start += i+1;
|
||||
self.start += i + 1;
|
||||
return Some(Row {
|
||||
start: start,
|
||||
end: i + start,
|
||||
@ -210,14 +211,14 @@ impl View for TextView {
|
||||
|
||||
let h = self.rows.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 row = &self.rows[i];
|
||||
let text = &self.content[row.start..row.end];
|
||||
let l = text.chars().count();
|
||||
let x = self.align.h.get_offset(l, printer.size.x);
|
||||
printer.print((x,0), text);
|
||||
printer.print((x, 0), text);
|
||||
});
|
||||
}
|
||||
|
||||
@ -229,8 +230,10 @@ impl View for TextView {
|
||||
match event {
|
||||
Event::KeyEvent(Key::Home) => self.scrollbase.scroll_top(),
|
||||
Event::KeyEvent(Key::End) => self.scrollbase.scroll_bottom(),
|
||||
Event::KeyEvent(Key::Up) if self.scrollbase.can_scroll_up() => self.scrollbase.scroll_up(1),
|
||||
Event::KeyEvent(Key::Down) if self.scrollbase.can_scroll_down() => self.scrollbase.scroll_down(1),
|
||||
Event::KeyEvent(Key::Up) if self.scrollbase.can_scroll_up() =>
|
||||
self.scrollbase.scroll_up(1),
|
||||
Event::KeyEvent(Key::Down) if self.scrollbase.can_scroll_down() =>
|
||||
self.scrollbase.scroll_down(1),
|
||||
Event::KeyEvent(Key::PageDown) => self.scrollbase.scroll_down(10),
|
||||
Event::KeyEvent(Key::PageUp) => self.scrollbase.scroll_up(10),
|
||||
_ => return EventResult::Ignored,
|
||||
@ -240,19 +243,19 @@ impl View for TextView {
|
||||
}
|
||||
|
||||
fn get_min_size(&self, size: SizeRequest) -> Vec2 {
|
||||
match (size.w,size.h) {
|
||||
match (size.w, size.h) {
|
||||
// If we have no directive, ask for a single big line.
|
||||
// TODO: what if the text has newlines??
|
||||
(DimensionRequest::Unknown, DimensionRequest::Unknown) => self.get_ideal_size(),
|
||||
(DimensionRequest::Fixed(w),_) => {
|
||||
(DimensionRequest::Fixed(w), _) => {
|
||||
let h = self.get_num_lines(w);
|
||||
Vec2::new(w, h)
|
||||
},
|
||||
(_,DimensionRequest::Fixed(h)) => {
|
||||
}
|
||||
(_, DimensionRequest::Fixed(h)) => {
|
||||
let w = self.get_num_cols(h);
|
||||
Vec2::new(w, h)
|
||||
},
|
||||
(DimensionRequest::AtMost(w),_) => {
|
||||
}
|
||||
(DimensionRequest::AtMost(w), _) => {
|
||||
// Don't _force_ the max width, but take it if we have to.
|
||||
let ideal = self.get_ideal_size();
|
||||
|
||||
@ -262,7 +265,7 @@ impl View for TextView {
|
||||
let h = self.get_num_lines(w);
|
||||
Vec2::new(w, h)
|
||||
}
|
||||
},
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
@ -8,9 +8,7 @@ pub struct ViewPath {
|
||||
impl ViewPath {
|
||||
/// Creates a new empty path.
|
||||
pub fn new() -> Self {
|
||||
ViewPath {
|
||||
path: Vec::new(),
|
||||
}
|
||||
ViewPath { path: Vec::new() }
|
||||
}
|
||||
|
||||
/// Creates a path from the given item.
|
||||
@ -27,8 +25,6 @@ pub trait ToPath {
|
||||
|
||||
impl <'a> ToPath for &'a [usize] {
|
||||
fn to_path(self) -> ViewPath {
|
||||
ViewPath {
|
||||
path: self.to_owned(),
|
||||
}
|
||||
ViewPath { path: self.to_owned() }
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
use std::any::Any;
|
||||
|
||||
use vec::Vec2;
|
||||
use view::{View,SizeRequest,Selector};
|
||||
use view::{View, SizeRequest, Selector};
|
||||
use printer::Printer;
|
||||
use event::{Event,EventResult};
|
||||
use event::{Event, EventResult};
|
||||
|
||||
/// Generic wrapper around a view.
|
||||
///
|
||||
@ -127,4 +127,3 @@ macro_rules! wrap_impl {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user