mirror of
https://github.com/FliegendeWurst/cursive.git
synced 2024-11-24 01:46:31 +00:00
Merge branch 'master' into refview
This commit is contained in:
commit
cdb3df7fc8
32
Cargo.toml
32
Cargo.toml
@ -10,32 +10,35 @@ license = "MIT"
|
|||||||
name = "cursive"
|
name = "cursive"
|
||||||
readme = "Readme.md"
|
readme = "Readme.md"
|
||||||
repository = "https://github.com/gyscos/Cursive"
|
repository = "https://github.com/gyscos/Cursive"
|
||||||
version = "0.4.0"
|
version = "0.4.1"
|
||||||
|
|
||||||
[badges.travis-ci]
|
[badges.travis-ci]
|
||||||
repository = "gyscos/Cursive"
|
repository = "gyscos/Cursive"
|
||||||
|
|
||||||
[build-dependencies.skeptic]
|
[build-dependencies.skeptic]
|
||||||
optional = true
|
optional = true
|
||||||
version = "0.6"
|
version = "0.7"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
chan = "0.1.18"
|
|
||||||
num = "0.1"
|
num = "0.1"
|
||||||
odds = "0.2"
|
odds = "0.2"
|
||||||
owning_ref = "0.2.4"
|
owning_ref = "0.2.4"
|
||||||
toml = "0.2"
|
toml = "0.3"
|
||||||
unicode-segmentation = "1.0"
|
unicode-segmentation = "1.0"
|
||||||
unicode-width = "0.1"
|
unicode-width = "0.1"
|
||||||
|
|
||||||
|
[dependencies.chan]
|
||||||
|
optional = true
|
||||||
|
version = "0.1.18"
|
||||||
|
|
||||||
|
[dependencies.chan-signal]
|
||||||
|
optional = true
|
||||||
|
version = "0.2"
|
||||||
|
|
||||||
[dependencies.bear-lib-terminal]
|
[dependencies.bear-lib-terminal]
|
||||||
optional = true
|
optional = true
|
||||||
version = "1.3.1"
|
version = "1.3.1"
|
||||||
|
|
||||||
[dependencies.chan-signal]
|
|
||||||
optional = true
|
|
||||||
version = "0.1"
|
|
||||||
|
|
||||||
[dependencies.ncurses]
|
[dependencies.ncurses]
|
||||||
features = ["wide"]
|
features = ["wide"]
|
||||||
optional = true
|
optional = true
|
||||||
@ -44,20 +47,23 @@ version = "5.85.0"
|
|||||||
[dependencies.pancurses]
|
[dependencies.pancurses]
|
||||||
features = ["wide"]
|
features = ["wide"]
|
||||||
optional = true
|
optional = true
|
||||||
version = "0.7"
|
version = "0.8"
|
||||||
|
|
||||||
[dependencies.termion]
|
[dependencies.termion]
|
||||||
optional = true
|
optional = true
|
||||||
version = "1.1.1"
|
version = "1.3.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
rand = "0.3"
|
rand = "0.3"
|
||||||
skeptic = "0.6"
|
skeptic = "0.7"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["ncurses"]
|
default = ["ncurses-backend"]
|
||||||
|
|
||||||
|
ncurses-backend = ["ncurses"]
|
||||||
|
termion-backend = ["termion", "chan", "chan-signal"]
|
||||||
pancurses-backend = ["pancurses"]
|
pancurses-backend = ["pancurses"]
|
||||||
termion-backend = ["termion", "chan-signal"]
|
blt-backend = ["bear-lib-terminal"]
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "cursive"
|
name = "cursive"
|
||||||
|
15
Readme.md
15
Readme.md
@ -3,6 +3,7 @@
|
|||||||
[![crates.io](https://meritbadge.herokuapp.com/cursive)](https://crates.io/crates/cursive)
|
[![crates.io](https://meritbadge.herokuapp.com/cursive)](https://crates.io/crates/cursive)
|
||||||
[![Build Status](https://travis-ci.org/gyscos/Cursive.svg?branch=master)](https://travis-ci.org/gyscos/Cursive)
|
[![Build Status](https://travis-ci.org/gyscos/Cursive.svg?branch=master)](https://travis-ci.org/gyscos/Cursive)
|
||||||
[![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)
|
[![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)
|
||||||
|
[![Gitter chat](https://badges.gitter.im/gyscos/cursive.png)](https://gitter.im/cursive-rs/cursive)
|
||||||
|
|
||||||
Cursive is a TUI (Text User Interface) library for rust. It is currently based on jeaye's [ncurses-rs](https://github.com/jeaye/ncurses-rs), but [other backends are available](https://github.com/gyscos/Cursive/wiki/Backends).
|
Cursive is a TUI (Text User Interface) library for rust. It is currently based on jeaye's [ncurses-rs](https://github.com/jeaye/ncurses-rs), but [other backends are available](https://github.com/gyscos/Cursive/wiki/Backends).
|
||||||
|
|
||||||
@ -50,12 +51,14 @@ fn main() {
|
|||||||
|
|
||||||
Check out the other [examples](https://github.com/gyscos/Cursive/tree/master/examples) to get these results, and more:
|
Check out the other [examples](https://github.com/gyscos/Cursive/tree/master/examples) to get these results, and more:
|
||||||
|
|
||||||
<img src="doc/examples/edit.png" alt="`edit` example", width="49%" />
|
<div>
|
||||||
<img src="doc/examples/lorem.png" alt="`lorem` example", width="49%" />
|
<img src="doc/examples/edit.png" alt="edit.rs example", width="49%" />
|
||||||
<img src="doc/examples/menubar.png" alt="`menubar` example", width="49%" />
|
<img src="doc/examples/lorem.png" alt="lorem.rs example", width="49%" />
|
||||||
<img src="doc/examples/select.png" alt="`select` example", width="49%" />
|
<img src="doc/examples/menubar.png" alt="menubar.rs example", width="49%" />
|
||||||
<img src="doc/examples/list_view.png" alt="`list_view` example", width="49%" />
|
<img src="doc/examples/select.png" alt="select.rs example", width="49%" />
|
||||||
<img src="doc/examples/theme.png" alt="`theme` example", width="49%" />
|
<img src="doc/examples/list_view.png" alt="list_view.rs example", width="49%" />
|
||||||
|
<img src="doc/examples/theme.png" alt="theme.rs example", width="49%" />
|
||||||
|
</div>
|
||||||
|
|
||||||
_(Colors may depend on your terminal configuration.)_
|
_(Colors may depend on your terminal configuration.)_
|
||||||
|
|
||||||
|
@ -22,8 +22,8 @@ fn main() {
|
|||||||
let (tx, rx) = mpsc::channel();
|
let (tx, rx) = mpsc::channel();
|
||||||
|
|
||||||
// Generate data in a separate thread.
|
// Generate data in a separate thread.
|
||||||
thread::spawn(|| {
|
thread::spawn(move || {
|
||||||
generate_logs(tx);
|
generate_logs(&tx);
|
||||||
});
|
});
|
||||||
|
|
||||||
// And sets the view to read from the other end of the channel.
|
// And sets the view to read from the other end of the channel.
|
||||||
@ -34,7 +34,7 @@ fn main() {
|
|||||||
|
|
||||||
// We will only simulate log generation here.
|
// We will only simulate log generation here.
|
||||||
// In real life, this may come from a running task, a separate process, ...
|
// In real life, this may come from a running task, a separate process, ...
|
||||||
fn generate_logs(tx: mpsc::Sender<String>) {
|
fn generate_logs(tx: &mpsc::Sender<String>) {
|
||||||
let mut i = 1;
|
let mut i = 1;
|
||||||
loop {
|
loop {
|
||||||
let line = format!("Interesting log line {}", i);
|
let line = format!("Interesting log line {}", i);
|
||||||
|
@ -28,7 +28,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Function to simulate a long process.
|
// Function to simulate a long process.
|
||||||
fn fake_load(n_max: usize, counter: Counter) {
|
fn fake_load(n_max: usize, counter: &Counter) {
|
||||||
for _ in 0..n_max {
|
for _ in 0..n_max {
|
||||||
thread::sleep(Duration::from_millis(5));
|
thread::sleep(Duration::from_millis(5));
|
||||||
// The `counter.tick()` method increases the progress value
|
// The `counter.tick()` method increases the progress value
|
||||||
@ -50,7 +50,7 @@ fn phase_1(s: &mut Cursive) {
|
|||||||
.range(0, n_max)
|
.range(0, n_max)
|
||||||
.with_task(move |counter| {
|
.with_task(move |counter| {
|
||||||
// This closure will be called in a separate thread.
|
// This closure will be called in a separate thread.
|
||||||
fake_load(n_max, counter);
|
fake_load(n_max, &counter);
|
||||||
|
|
||||||
// When we're done, send a callback through the channel
|
// When we're done, send a callback through the channel
|
||||||
cb.send(Box::new(coffee_break)).unwrap();
|
cb.send(Box::new(coffee_break)).unwrap();
|
||||||
|
@ -94,7 +94,7 @@ impl backend::Backend for Concrete {
|
|||||||
if 32 <= ch && ch <= 255 && ch != 127 {
|
if 32 <= ch && ch <= 255 && ch != 127 {
|
||||||
Event::Char(utf8::read_char(ch as u8,
|
Event::Char(utf8::read_char(ch as u8,
|
||||||
|| Some(ncurses::getch() as u8))
|
|| Some(ncurses::getch() as u8))
|
||||||
.unwrap())
|
.unwrap())
|
||||||
} else {
|
} else {
|
||||||
parse_ncurses_char(ch)
|
parse_ncurses_char(ch)
|
||||||
}
|
}
|
||||||
@ -226,6 +226,11 @@ fn parse_ncurses_char(ch: i32) -> Event {
|
|||||||
// Values 8-10 (H,I,J) are used by other commands,
|
// Values 8-10 (H,I,J) are used by other commands,
|
||||||
// so we probably won't receive them. Meh~
|
// so we probably won't receive them. Meh~
|
||||||
c @ 1...25 => Event::CtrlChar((b'a' + (c - 1) as u8) as char),
|
c @ 1...25 => Event::CtrlChar((b'a' + (c - 1) as u8) as char),
|
||||||
_ => Event::Unknown(ch),
|
other => {
|
||||||
|
// Split the i32 into 4 bytes
|
||||||
|
Event::Unknown((0..4)
|
||||||
|
.map(|i| ((other >> (8 * i)) & 0xFF) as u8)
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,13 +5,11 @@ extern crate pancurses;
|
|||||||
use self::super::find_closest;
|
use self::super::find_closest;
|
||||||
use backend;
|
use backend;
|
||||||
use event::{Event, Key};
|
use event::{Event, Key};
|
||||||
use std::cell::Cell;
|
|
||||||
use theme::{Color, ColorStyle, Effect};
|
use theme::{Color, ColorStyle, Effect};
|
||||||
use utf8;
|
use utf8;
|
||||||
|
|
||||||
pub struct Concrete {
|
pub struct Concrete {
|
||||||
window: pancurses::Window,
|
window: pancurses::Window,
|
||||||
current_style: Cell<ColorStyle>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl backend::Backend for Concrete {
|
impl backend::Backend for Concrete {
|
||||||
@ -23,13 +21,9 @@ impl backend::Backend for Concrete {
|
|||||||
pancurses::cbreak();
|
pancurses::cbreak();
|
||||||
pancurses::start_color();
|
pancurses::start_color();
|
||||||
pancurses::curs_set(0);
|
pancurses::curs_set(0);
|
||||||
window.bkgd(pancurses::COLOR_PAIR(ColorStyle::Background.id() as
|
window.bkgd(pancurses::ColorPair(ColorStyle::Background.id() as u8));
|
||||||
pancurses::chtype));
|
|
||||||
|
|
||||||
Concrete {
|
Concrete { window: window }
|
||||||
window: window,
|
|
||||||
current_style: Cell::new(ColorStyle::Background),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn screen_size(&self) -> (usize, usize) {
|
fn screen_size(&self) -> (usize, usize) {
|
||||||
@ -53,29 +47,18 @@ impl backend::Backend for Concrete {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn with_color<F: FnOnce()>(&self, color: ColorStyle, f: F) {
|
fn with_color<F: FnOnce()>(&self, color: ColorStyle, f: F) {
|
||||||
// TODO: pancurses doesn't have an `attr_get` equivalent
|
let (_, current_color_pair) = self.window.attrget();
|
||||||
// let mut current_style: pancurses::attr_t = 0;
|
let color_attribute = pancurses::ColorPair(color.id() as u8);
|
||||||
// let mut current_color: i16 = 0;
|
|
||||||
// pancurses::attr_get(&mut current_style, &mut current_color);
|
|
||||||
let current_style = self.current_style.get();
|
|
||||||
|
|
||||||
let style = pancurses::COLOR_PAIR(color.id() as pancurses::chtype);
|
self.window.attron(color_attribute);
|
||||||
self.window.attron(style);
|
|
||||||
|
|
||||||
self.current_style.set(color);
|
|
||||||
f();
|
f();
|
||||||
self.current_style.set(current_style);
|
self.window.attron(pancurses::ColorPair(current_color_pair as u8));
|
||||||
|
|
||||||
// self.window.attroff(style);
|
|
||||||
self.window.attron(pancurses::COLOR_PAIR(current_style.id() as
|
|
||||||
pancurses::chtype));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn with_effect<F: FnOnce()>(&self, effect: Effect, f: F) {
|
fn with_effect<F: FnOnce()>(&self, effect: Effect, f: F) {
|
||||||
let style = match effect {
|
let style = match effect {
|
||||||
// A_REVERSE, taken from ncurses
|
Effect::Reverse => pancurses::Attribute::Reverse,
|
||||||
Effect::Reverse => 1 << (10 + 8u32),
|
Effect::Simple => pancurses::Attribute::Normal,
|
||||||
Effect::Simple => pancurses::A_NORMAL,
|
|
||||||
};
|
};
|
||||||
self.window.attron(style);
|
self.window.attron(style);
|
||||||
f();
|
f();
|
||||||
@ -111,19 +94,31 @@ impl backend::Backend for Concrete {
|
|||||||
pancurses::Input::Character(c) if 32 <= (c as u32) &&
|
pancurses::Input::Character(c) if 32 <= (c as u32) &&
|
||||||
(c as u32) <= 255 => {
|
(c as u32) <= 255 => {
|
||||||
Event::Char(utf8::read_char(c as u8, || {
|
Event::Char(utf8::read_char(c as u8, || {
|
||||||
self.window.getch().and_then(|i| match i {
|
self.window.getch().and_then(|i| match i {
|
||||||
pancurses::Input::Character(c) => {
|
pancurses::Input::Character(c) => {
|
||||||
Some(c as u8)
|
Some(c as u8)
|
||||||
}
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.unwrap())
|
.unwrap())
|
||||||
|
}
|
||||||
|
pancurses::Input::Character(c) => {
|
||||||
|
let mut bytes = [0u8; 4];
|
||||||
|
Event::Unknown(c.encode_utf8(&mut bytes)
|
||||||
|
.as_bytes()
|
||||||
|
.to_vec())
|
||||||
}
|
}
|
||||||
pancurses::Input::Character(c) => Event::Unknown(c as i32),
|
|
||||||
// TODO: Some key combos are not recognized by pancurses,
|
// TODO: Some key combos are not recognized by pancurses,
|
||||||
// but are sent as Unknown. We could still parse them here.
|
// but are sent as Unknown. We could still parse them here.
|
||||||
pancurses::Input::Unknown(i) => Event::Unknown(i),
|
pancurses::Input::Unknown(other) => {
|
||||||
|
Event::Unknown((0..4)
|
||||||
|
.map(|i| {
|
||||||
|
((other >> (8 * i)) & 0xFF) as
|
||||||
|
u8
|
||||||
|
})
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
// TODO: I honestly have no fucking idea what KeyCodeYes is
|
// TODO: I honestly have no fucking idea what KeyCodeYes is
|
||||||
pancurses::Input::KeyCodeYes => Event::Refresh,
|
pancurses::Input::KeyCodeYes => Event::Refresh,
|
||||||
pancurses::Input::KeyBreak => Event::Key(Key::PauseBreak),
|
pancurses::Input::KeyBreak => Event::Key(Key::PauseBreak),
|
||||||
|
@ -3,6 +3,7 @@ extern crate termion;
|
|||||||
extern crate chan_signal;
|
extern crate chan_signal;
|
||||||
|
|
||||||
use self::termion::color as tcolor;
|
use self::termion::color as tcolor;
|
||||||
|
use self::termion::event::Event as TEvent;
|
||||||
use self::termion::event::Key as TKey;
|
use self::termion::event::Key as TKey;
|
||||||
use self::termion::input::TermRead;
|
use self::termion::input::TermRead;
|
||||||
use self::termion::raw::IntoRawMode;
|
use self::termion::raw::IntoRawMode;
|
||||||
@ -15,7 +16,6 @@ use std::collections::BTreeMap;
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time;
|
|
||||||
|
|
||||||
use theme;
|
use theme;
|
||||||
|
|
||||||
@ -84,11 +84,11 @@ impl backend::Backend for Concrete {
|
|||||||
let terminal = ::std::io::stdout().into_raw_mode().unwrap();
|
let terminal = ::std::io::stdout().into_raw_mode().unwrap();
|
||||||
let (sender, receiver) = chan::async();
|
let (sender, receiver) = chan::async();
|
||||||
|
|
||||||
thread::spawn(move || for key in ::std::io::stdin().keys() {
|
thread::spawn(move || for key in ::std::io::stdin().events() {
|
||||||
if let Ok(key) = key {
|
if let Ok(key) = key {
|
||||||
sender.send(map_key(key))
|
sender.send(map_key(key))
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let backend = Concrete {
|
let backend = Concrete {
|
||||||
terminal: terminal,
|
terminal: terminal,
|
||||||
@ -185,29 +185,30 @@ impl backend::Backend for Concrete {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn map_key(key: TKey) -> Event {
|
fn map_key(event: TEvent) -> Event {
|
||||||
match key {
|
match event {
|
||||||
TKey::Esc => Event::Key(Key::Esc),
|
TEvent::Unsupported(bytes) => Event::Unknown(bytes),
|
||||||
TKey::Backspace => Event::Key(Key::Backspace),
|
TEvent::Key(TKey::Esc) => Event::Key(Key::Esc),
|
||||||
TKey::Left => Event::Key(Key::Left),
|
TEvent::Key(TKey::Backspace) => Event::Key(Key::Backspace),
|
||||||
TKey::Right => Event::Key(Key::Right),
|
TEvent::Key(TKey::Left) => Event::Key(Key::Left),
|
||||||
TKey::Up => Event::Key(Key::Up),
|
TEvent::Key(TKey::Right) => Event::Key(Key::Right),
|
||||||
TKey::Down => Event::Key(Key::Down),
|
TEvent::Key(TKey::Up) => Event::Key(Key::Up),
|
||||||
TKey::Home => Event::Key(Key::Home),
|
TEvent::Key(TKey::Down) => Event::Key(Key::Down),
|
||||||
TKey::End => Event::Key(Key::End),
|
TEvent::Key(TKey::Home) => Event::Key(Key::Home),
|
||||||
TKey::PageUp => Event::Key(Key::PageUp),
|
TEvent::Key(TKey::End) => Event::Key(Key::End),
|
||||||
TKey::PageDown => Event::Key(Key::PageDown),
|
TEvent::Key(TKey::PageUp) => Event::Key(Key::PageUp),
|
||||||
TKey::Delete => Event::Key(Key::Del),
|
TEvent::Key(TKey::PageDown) => Event::Key(Key::PageDown),
|
||||||
TKey::Insert => Event::Key(Key::Ins),
|
TEvent::Key(TKey::Delete) => Event::Key(Key::Del),
|
||||||
TKey::F(i) if i < 12 => Event::Key(Key::from_f(i)),
|
TEvent::Key(TKey::Insert) => Event::Key(Key::Ins),
|
||||||
TKey::F(j) => Event::Unknown(-(j as i32)),
|
TEvent::Key(TKey::F(i)) if i < 12 => Event::Key(Key::from_f(i)),
|
||||||
TKey::Char('\n') => Event::Key(Key::Enter),
|
TEvent::Key(TKey::F(j)) => Event::Unknown(vec![j]),
|
||||||
TKey::Char('\t') => Event::Key(Key::Tab),
|
TEvent::Key(TKey::Char('\n')) => Event::Key(Key::Enter),
|
||||||
TKey::Char(c) => Event::Char(c),
|
TEvent::Key(TKey::Char('\t')) => Event::Key(Key::Tab),
|
||||||
TKey::Ctrl('c') => Event::Exit,
|
TEvent::Key(TKey::Char(c)) => Event::Char(c),
|
||||||
TKey::Ctrl(c) => Event::CtrlChar(c),
|
TEvent::Key(TKey::Ctrl('c')) => Event::Exit,
|
||||||
TKey::Alt(c) => Event::AltChar(c),
|
TEvent::Key(TKey::Ctrl(c)) => Event::CtrlChar(c),
|
||||||
_ => Event::Unknown(-1),
|
TEvent::Key(TKey::Alt(c)) => Event::AltChar(c),
|
||||||
|
_ => Event::Unknown(vec![]),
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -186,7 +186,7 @@ impl Key {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Represents an event as seen by the application.
|
/// Represents an event as seen by the application.
|
||||||
#[derive(PartialEq,Eq,Clone,Copy,Hash,Debug)]
|
#[derive(PartialEq,Eq,Clone,Hash,Debug)]
|
||||||
pub enum Event {
|
pub enum Event {
|
||||||
/// Event fired when the window is resized.
|
/// Event fired when the window is resized.
|
||||||
WindowResize,
|
WindowResize,
|
||||||
@ -217,7 +217,7 @@ pub enum Event {
|
|||||||
CtrlAlt(Key),
|
CtrlAlt(Key),
|
||||||
|
|
||||||
/// An unknown event was received.
|
/// An unknown event was received.
|
||||||
Unknown(i32),
|
Unknown(Vec<u8>),
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
/// The application is about to exit.
|
/// The application is about to exit.
|
||||||
|
17
src/lib.rs
17
src/lib.rs
@ -66,6 +66,7 @@ extern crate odds;
|
|||||||
extern crate num;
|
extern crate num;
|
||||||
extern crate owning_ref;
|
extern crate owning_ref;
|
||||||
|
|
||||||
|
#[cfg(feature = "termion")]
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate chan;
|
extern crate chan;
|
||||||
|
|
||||||
@ -399,6 +400,8 @@ impl Cursive {
|
|||||||
self.screen_mut().find(sel, callback)
|
self.screen_mut().find(sel, callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Tries to find the view identified by the given id.
|
||||||
|
///
|
||||||
/// Convenient method to use `find` with a `view::Selector::Id`.
|
/// Convenient method to use `find` with a `view::Selector::Id`.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
@ -439,6 +442,18 @@ impl Cursive {
|
|||||||
self.find_id(id, views::RefCellView::<V>::get_mut)
|
self.find_id(id, views::RefCellView::<V>::get_mut)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Moves the focus to the view identified by `id`.
|
||||||
|
///
|
||||||
|
/// Convenient method to call `focus` with a `view::Selector::Id`.
|
||||||
|
pub fn focus_id(&mut self, id: &str) -> Result<(), ()> {
|
||||||
|
self.focus(&view::Selector::Id(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Moves the focus to the view identified by `sel`.
|
||||||
|
pub fn focus(&mut self, sel: &view::Selector) -> Result<(), ()> {
|
||||||
|
self.screen_mut().focus_view(sel)
|
||||||
|
}
|
||||||
|
|
||||||
/// Adds a global callback.
|
/// Adds a global callback.
|
||||||
///
|
///
|
||||||
/// Will be triggered on the given key press when no view catches it.
|
/// Will be triggered on the given key press when no view catches it.
|
||||||
@ -616,7 +631,7 @@ impl Cursive {
|
|||||||
if self.menubar.receive_events() {
|
if self.menubar.receive_events() {
|
||||||
self.menubar.on_event(event).process(self);
|
self.menubar.on_event(event).process(self);
|
||||||
} else {
|
} else {
|
||||||
match self.screen_mut().on_event(event) {
|
match self.screen_mut().on_event(event.clone()) {
|
||||||
// If the event was ignored,
|
// If the event was ignored,
|
||||||
// it is our turn to play with it.
|
// it is our turn to play with it.
|
||||||
EventResult::Ignored => self.on_event(event),
|
EventResult::Ignored => self.on_event(event),
|
||||||
|
15
src/theme.rs
15
src/theme.rs
@ -210,7 +210,7 @@ impl Default for Theme {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Theme {
|
impl Theme {
|
||||||
fn load(&mut self, table: &toml::Table) {
|
fn load(&mut self, table: &toml::value::Table) {
|
||||||
if let Some(&toml::Value::Boolean(shadow)) = table.get("shadow") {
|
if let Some(&toml::Value::Boolean(shadow)) = table.get("shadow") {
|
||||||
self.shadow = shadow;
|
self.shadow = shadow;
|
||||||
}
|
}
|
||||||
@ -314,7 +314,7 @@ pub struct Palette {
|
|||||||
|
|
||||||
impl Palette {
|
impl Palette {
|
||||||
/// Fills `self` with the colors from the given `table`.
|
/// Fills `self` with the colors from the given `table`.
|
||||||
fn load(&mut self, table: &toml::Table) {
|
fn load(&mut self, table: &toml::value::Table) {
|
||||||
load_color(&mut self.background, table.get("background"));
|
load_color(&mut self.background, table.get("background"));
|
||||||
load_color(&mut self.shadow, table.get("shadow"));
|
load_color(&mut self.shadow, table.get("shadow"));
|
||||||
load_color(&mut self.view, table.get("view"));
|
load_color(&mut self.view, table.get("view"));
|
||||||
@ -413,7 +413,7 @@ pub enum Error {
|
|||||||
/// An error occured when reading the file.
|
/// An error occured when reading the file.
|
||||||
Io(io::Error),
|
Io(io::Error),
|
||||||
/// An error occured when parsing the toml content.
|
/// An error occured when parsing the toml content.
|
||||||
Parse,
|
Parse(toml::de::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<io::Error> for Error {
|
impl From<io::Error> for Error {
|
||||||
@ -422,6 +422,12 @@ impl From<io::Error> for Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<toml::de::Error> for Error {
|
||||||
|
fn from(err: toml::de::Error) -> Self {
|
||||||
|
Error::Parse(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Color {
|
impl Color {
|
||||||
fn parse(value: &str) -> Option<Self> {
|
fn parse(value: &str) -> Option<Self> {
|
||||||
Some(match value {
|
Some(match value {
|
||||||
@ -490,8 +496,7 @@ pub fn load_theme_file<P: AsRef<Path>>(filename: P) -> Result<Theme, Error> {
|
|||||||
|
|
||||||
/// Loads a theme string and sets it as active.
|
/// Loads a theme string and sets it as active.
|
||||||
pub fn load_theme(content: &str) -> Result<Theme, Error> {
|
pub fn load_theme(content: &str) -> Result<Theme, Error> {
|
||||||
let mut parser = toml::Parser::new(content);
|
let table = toml::de::from_str(content)?;
|
||||||
let table = try!(parser.parse().ok_or(Error::Parse));
|
|
||||||
|
|
||||||
let mut theme = Theme::default();
|
let mut theme = Theme::default();
|
||||||
theme.load(&table);
|
theme.load(&table);
|
||||||
|
@ -49,25 +49,29 @@ pub fn prefix<'a, I>(iter: I, available_width: usize, delimiter: &str)
|
|||||||
let delimiter_width = delimiter.width();
|
let delimiter_width = delimiter.width();
|
||||||
let delimiter_len = delimiter.len();
|
let delimiter_len = delimiter.len();
|
||||||
|
|
||||||
|
// `current_width` is the width of everything
|
||||||
|
// before the next token, including any space.
|
||||||
let mut current_width = 0;
|
let mut current_width = 0;
|
||||||
let sum = iter.take_while(|token| {
|
let sum: usize = iter.take_while(|token| {
|
||||||
let width = token.width();
|
let width = token.width();
|
||||||
if current_width + width > available_width {
|
if current_width + width > available_width {
|
||||||
false
|
false
|
||||||
} else {
|
} else {
|
||||||
if current_width != 0 {
|
// Include the delimiter after this token.
|
||||||
current_width += delimiter_width;
|
|
||||||
}
|
|
||||||
current_width += width;
|
current_width += width;
|
||||||
|
current_width += delimiter_width;
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.map(|token| token.len() + delimiter_len)
|
.map(|token| token.len() + delimiter_len)
|
||||||
.fold(0, |a, b| a + b);
|
.sum();
|
||||||
|
|
||||||
// We counted delimiter once too many times,
|
// We counted delimiter once too many times,
|
||||||
// but only if the iterator was non empty.
|
// but only if the iterator was non empty.
|
||||||
let length = if sum == 0 { sum } else { sum - delimiter_len };
|
let length = sum.saturating_sub(delimiter_len);
|
||||||
|
|
||||||
|
// `current_width` includes a delimiter after the last token
|
||||||
|
debug_assert!(current_width <= available_width + delimiter_width);
|
||||||
|
|
||||||
Prefix {
|
Prefix {
|
||||||
length: length,
|
length: length,
|
||||||
@ -95,3 +99,15 @@ pub fn suffix<'a, I>(iter: I, width: usize, delimiter: &str) -> Prefix
|
|||||||
pub fn simple_suffix(text: &str, width: usize) -> Prefix {
|
pub fn simple_suffix(text: &str, width: usize) -> Prefix {
|
||||||
suffix(text.graphemes(true), width, "")
|
suffix(text.graphemes(true), width, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use utils;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_prefix() {
|
||||||
|
assert_eq!(utils::prefix(" abra ".split(' '), 5, " ").length, 5);
|
||||||
|
assert_eq!(utils::prefix("abra a".split(' '), 5, " ").length, 4);
|
||||||
|
assert_eq!(utils::prefix("a a br".split(' '), 5, " ").length, 3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -114,7 +114,7 @@ pub trait View {
|
|||||||
/// Draws the view with the given printer (includes bounds) and focus.
|
/// Draws the view with the given printer (includes bounds) and focus.
|
||||||
fn draw(&self, printer: &Printer);
|
fn draw(&self, printer: &Printer);
|
||||||
|
|
||||||
/// Finds the view pointed to by the given path.
|
/// Finds the view identified by the given selector.
|
||||||
///
|
///
|
||||||
/// See [`Finder::find`] for a nicer interface, implemented for all views.
|
/// See [`Finder::find`] for a nicer interface, implemented for all views.
|
||||||
///
|
///
|
||||||
@ -127,6 +127,13 @@ pub trait View {
|
|||||||
// TODO: FnMut -> FnOnce once it works
|
// TODO: FnMut -> FnOnce once it works
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Moves the focus to the view identified by the given selector.
|
||||||
|
///
|
||||||
|
/// Returns `Ok(())` if the view was found and selected.
|
||||||
|
fn focus_view(&mut self, &Selector) -> Result<(), ()> {
|
||||||
|
Err(())
|
||||||
|
}
|
||||||
|
|
||||||
/// This view is offered focus. Will it take it?
|
/// This view is offered focus. Will it take it?
|
||||||
///
|
///
|
||||||
/// `source` indicates where the focus comes from.
|
/// `source` indicates where the focus comes from.
|
||||||
|
@ -119,22 +119,28 @@ impl ScrollBase {
|
|||||||
|
|
||||||
/// Scroll to the bottom of the view.
|
/// Scroll to the bottom of the view.
|
||||||
pub fn scroll_bottom(&mut self) {
|
pub fn scroll_bottom(&mut self) {
|
||||||
self.start_line = self.content_height - self.view_height;
|
if self.scrollable() {
|
||||||
|
self.start_line = self.content_height - self.view_height;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Scroll down by the given number of line.
|
/// Scroll down by the given number of line.
|
||||||
///
|
///
|
||||||
/// Never further than the bottom of the view.
|
/// Never further than the bottom of the view.
|
||||||
pub fn scroll_down(&mut self, n: usize) {
|
pub fn scroll_down(&mut self, n: usize) {
|
||||||
self.start_line = min(self.start_line + n,
|
if self.scrollable() {
|
||||||
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.
|
/// Scroll up by the given number of lines.
|
||||||
///
|
///
|
||||||
/// Never above the top of the view.
|
/// Never above the top of the view.
|
||||||
pub fn scroll_up(&mut self, n: usize) {
|
pub fn scroll_up(&mut self, n: usize) {
|
||||||
self.start_line -= min(self.start_line, n);
|
if self.scrollable() {
|
||||||
|
self.start_line -= min(self.start_line, n);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draws the scroll bar and the content using the given drawer.
|
/// Draws the scroll bar and the content using the given drawer.
|
||||||
|
@ -57,6 +57,11 @@ pub trait ViewWrapper {
|
|||||||
self.with_view_mut(|v| v.find_any(selector, callback));
|
self.with_view_mut(|v| v.find_any(selector, callback));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Wraps the `focus_view` method.
|
||||||
|
fn wrap_focus_view(&mut self, selector: &Selector) -> Result<(), ()> {
|
||||||
|
self.with_view_mut(|v| v.focus_view(selector)).unwrap_or(Err(()))
|
||||||
|
}
|
||||||
|
|
||||||
/// Wraps the `needs_relayout` method.
|
/// Wraps the `needs_relayout` method.
|
||||||
fn wrap_needs_relayout(&self) -> bool {
|
fn wrap_needs_relayout(&self) -> bool {
|
||||||
self.with_view(|v| v.needs_relayout()).unwrap_or(true)
|
self.with_view(|v| v.needs_relayout()).unwrap_or(true)
|
||||||
@ -92,6 +97,10 @@ impl<T: ViewWrapper> View for T {
|
|||||||
fn needs_relayout(&self) -> bool {
|
fn needs_relayout(&self) -> bool {
|
||||||
self.wrap_needs_relayout()
|
self.wrap_needs_relayout()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn focus_view(&mut self, selector: &Selector) -> Result<(), ()> {
|
||||||
|
self.wrap_focus_view(selector)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convenient macro to implement the [`ViewWrapper`] trait.
|
/// Convenient macro to implement the [`ViewWrapper`] trait.
|
||||||
|
@ -316,7 +316,7 @@ impl View for Dialog {
|
|||||||
match self.focus {
|
match self.focus {
|
||||||
// If we are on the content, we can only go down.
|
// If we are on the content, we can only go down.
|
||||||
Focus::Content => {
|
Focus::Content => {
|
||||||
match self.content.on_event(event) {
|
match self.content.on_event(event.clone()) {
|
||||||
EventResult::Ignored if !self.buttons.is_empty() => {
|
EventResult::Ignored if !self.buttons.is_empty() => {
|
||||||
match event {
|
match event {
|
||||||
Event::Key(Key::Down) |
|
Event::Key(Key::Down) |
|
||||||
@ -334,7 +334,7 @@ impl View for Dialog {
|
|||||||
}
|
}
|
||||||
// If we are on a button, we have more choice
|
// If we are on a button, we have more choice
|
||||||
Focus::Button(i) => {
|
Focus::Button(i) => {
|
||||||
match self.buttons[i].on_event(event) {
|
match self.buttons[i].on_event(event.clone()) {
|
||||||
EventResult::Ignored => {
|
EventResult::Ignored => {
|
||||||
match event {
|
match event {
|
||||||
// Up goes back to the content
|
// Up goes back to the content
|
||||||
@ -403,4 +403,8 @@ impl View for Dialog {
|
|||||||
callback: Box<FnMut(&mut Any) + 'a>) {
|
callback: Box<FnMut(&mut Any) + 'a>) {
|
||||||
self.content.find_any(selector, callback);
|
self.content.find_any(selector, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn focus_view(&mut self, selector: &Selector) -> Result<(), ()> {
|
||||||
|
self.content.focus_view(selector)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
use {Cursive, Printer, With};
|
use {Cursive, Printer, With};
|
||||||
use direction::Direction;
|
use direction::Direction;
|
||||||
use event::{Callback, Event, EventResult, Key};
|
use event::{Callback, Event, EventResult, Key};
|
||||||
|
use std::cell::RefCell;
|
||||||
|
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use theme::{ColorStyle, Effect};
|
use theme::{ColorStyle, Effect};
|
||||||
@ -73,7 +74,7 @@ pub struct EditView {
|
|||||||
|
|
||||||
/// Callback when the content is modified.
|
/// Callback when the content is modified.
|
||||||
///
|
///
|
||||||
/// Will be called with the current content and the cursor position
|
/// Will be called with the current content and the cursor position.
|
||||||
on_edit: Option<Rc<Fn(&mut Cursive, &str, usize)>>,
|
on_edit: Option<Rc<Fn(&mut Cursive, &str, usize)>>,
|
||||||
|
|
||||||
/// Callback when <Enter> is pressed.
|
/// Callback when <Enter> is pressed.
|
||||||
@ -135,25 +136,118 @@ impl EditView {
|
|||||||
self.enabled = true;
|
self.enabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets a mutable callback to be called whenever the content is modified.
|
||||||
|
///
|
||||||
|
/// `callback` will be called with the view
|
||||||
|
/// content and the current cursor position.
|
||||||
|
///
|
||||||
|
/// *Warning*: this callback cannot be called recursively. If you somehow
|
||||||
|
/// trigger this callback again in the given closure, it will be ignored.
|
||||||
|
///
|
||||||
|
/// If you don't need a mutable closure but want the possibility of
|
||||||
|
/// recursive calls, see [`set_on_edit`](#method.set_on_edit).
|
||||||
|
pub fn set_on_edit_mut<F>(&mut self, callback: F)
|
||||||
|
where F: FnMut(&mut Cursive, &str, usize) + 'static
|
||||||
|
{
|
||||||
|
let callback = RefCell::new(callback);
|
||||||
|
// Here's the weird trick: if we're already borrowed,
|
||||||
|
// just ignored the callback.
|
||||||
|
self.set_on_edit(move |s, text, cursor| {
|
||||||
|
if let Ok(mut f) = callback.try_borrow_mut() {
|
||||||
|
// Beeeaaah that's ugly.
|
||||||
|
// Why do we need to manually dereference here?
|
||||||
|
(&mut *f)(s, text, cursor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/// Sets a callback to be called whenever the content is modified.
|
/// Sets a callback to be called whenever the content is modified.
|
||||||
///
|
///
|
||||||
/// `callback` will be called with the view
|
/// `callback` will be called with the view
|
||||||
/// content and the current cursor position.
|
/// content and the current cursor position.
|
||||||
pub fn on_edit<F>(mut self, callback: F) -> Self
|
///
|
||||||
|
/// This callback can safely trigger itself recursively if needed
|
||||||
|
/// (for instance if you call `on_event` on this view from the callback).
|
||||||
|
///
|
||||||
|
/// If you need a mutable closure and don't care about the recursive
|
||||||
|
/// aspect, see [`set_on_edit_mut`](#method.set_on_edit_mut).
|
||||||
|
pub fn set_on_edit<F>(&mut self, callback: F)
|
||||||
where F: Fn(&mut Cursive, &str, usize) + 'static
|
where F: Fn(&mut Cursive, &str, usize) + 'static
|
||||||
{
|
{
|
||||||
self.on_edit = Some(Rc::new(callback));
|
self.on_edit = Some(Rc::new(callback));
|
||||||
self
|
}
|
||||||
|
|
||||||
|
/// Sets a mutable callback to be called whenever the content is modified.
|
||||||
|
///
|
||||||
|
/// Chainable variant. See [`set_on_edit_mut`](#method.set_on_edit_mut).
|
||||||
|
pub fn on_edit_mut<F>(self, callback: F) -> Self
|
||||||
|
where F: FnMut(&mut Cursive, &str, usize) + 'static
|
||||||
|
{
|
||||||
|
self.with(|v| v.set_on_edit_mut(callback))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets a callback to be called whenever the content is modified.
|
||||||
|
///
|
||||||
|
/// Chainable variant. See [`set_on_edit`](#method.set_on_edit).
|
||||||
|
pub fn on_edit<F>(self, callback: F) -> Self
|
||||||
|
where F: Fn(&mut Cursive, &str, usize) + 'static
|
||||||
|
{
|
||||||
|
self.with(|v| v.set_on_edit(callback))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets a mutable callback to be called when `<Enter>` is pressed.
|
||||||
|
///
|
||||||
|
/// `callback` will be given the content of the view.
|
||||||
|
///
|
||||||
|
/// *Warning*: this callback cannot be called recursively. If you somehow
|
||||||
|
/// trigger this callback again in the given closure, it will be ignored.
|
||||||
|
///
|
||||||
|
/// If you don't need a mutable closure but want the possibility of
|
||||||
|
/// recursive calls, see [`set_on_submit`](#method.set_on_submit).
|
||||||
|
pub fn set_on_submit_mut<F>(&mut self, callback: F)
|
||||||
|
where F: FnMut(&mut Cursive, &str) + 'static
|
||||||
|
{
|
||||||
|
// TODO: don't duplicate all those methods.
|
||||||
|
// Instead, have some generic function immutify()
|
||||||
|
// or something that wraps a FnMut closure.
|
||||||
|
let callback = RefCell::new(callback);
|
||||||
|
self.set_on_submit(move |s, text| if let Ok(mut f) =
|
||||||
|
callback.try_borrow_mut() {
|
||||||
|
(&mut *f)(s, text);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets a callback to be called when `<Enter>` is pressed.
|
/// Sets a callback to be called when `<Enter>` is pressed.
|
||||||
///
|
///
|
||||||
/// `callback` will be given the content of the view.
|
/// `callback` will be given the content of the view.
|
||||||
pub fn on_submit<F>(mut self, callback: F) -> Self
|
///
|
||||||
|
/// This callback can safely trigger itself recursively if needed
|
||||||
|
/// (for instance if you call `on_event` on this view from the callback).
|
||||||
|
///
|
||||||
|
/// If you need a mutable closure and don't care about the recursive
|
||||||
|
/// aspect, see [`set_on_submit_mut`](#method.set_on_submit_mut).
|
||||||
|
pub fn set_on_submit<F>(&mut self, callback: F)
|
||||||
where F: Fn(&mut Cursive, &str) + 'static
|
where F: Fn(&mut Cursive, &str) + 'static
|
||||||
{
|
{
|
||||||
self.on_submit = Some(Rc::new(callback));
|
self.on_submit = Some(Rc::new(callback));
|
||||||
self
|
}
|
||||||
|
|
||||||
|
/// Sets a mutable callback to be called when `<Enter>` is pressed.
|
||||||
|
///
|
||||||
|
/// Chainable variant.
|
||||||
|
pub fn on_submit_mut<F>(self, callback: F) -> Self
|
||||||
|
where F: FnMut(&mut Cursive, &str) + 'static
|
||||||
|
{
|
||||||
|
self.with(|v| v.set_on_submit_mut(callback))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets a callback to be called when `<Enter>` is pressed.
|
||||||
|
///
|
||||||
|
/// Chainable variant.
|
||||||
|
pub fn on_submit<F>(self, callback: F) -> Self
|
||||||
|
where F: Fn(&mut Cursive, &str) + 'static
|
||||||
|
{
|
||||||
|
self.with(|v| v.set_on_submit(callback))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Enable or disable this view.
|
/// Enable or disable this view.
|
||||||
@ -267,7 +361,7 @@ fn make_small_stars(length: usize) -> &'static str {
|
|||||||
|
|
||||||
impl View for EditView {
|
impl View for EditView {
|
||||||
fn draw(&self, printer: &Printer) {
|
fn draw(&self, printer: &Printer) {
|
||||||
assert!(printer.size.x == self.last_length,
|
assert_eq!(printer.size.x, self.last_length,
|
||||||
"Was promised {}, received {}",
|
"Was promised {}, received {}",
|
||||||
self.last_length,
|
self.last_length,
|
||||||
printer.size.x);
|
printer.size.x);
|
||||||
@ -343,6 +437,7 @@ impl View for EditView {
|
|||||||
|
|
||||||
fn layout(&mut self, size: Vec2) {
|
fn layout(&mut self, size: Vec2) {
|
||||||
self.last_length = size.x;
|
self.last_length = size.x;
|
||||||
|
// println_stderr!("Promised: {}", size.x);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn take_focus(&mut self, _: Direction) -> bool {
|
fn take_focus(&mut self, _: Direction) -> bool {
|
||||||
@ -401,7 +496,7 @@ impl View for EditView {
|
|||||||
|
|
||||||
let cb = self.on_edit.clone().map(|cb| {
|
let cb = self.on_edit.clone().map(|cb| {
|
||||||
|
|
||||||
// Get a new Rc on it
|
// Get a new Rc on the content
|
||||||
let content = self.content.clone();
|
let content = self.content.clone();
|
||||||
let cursor = self.cursor;
|
let cursor = self.cursor;
|
||||||
|
|
||||||
|
@ -28,4 +28,11 @@ impl<T: View + Any> ViewWrapper for IdView<T> {
|
|||||||
s => self.view.find_any(s, callback),
|
s => self.view.find_any(s, callback),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn wrap_focus_view(&mut self, selector: &Selector) -> Result<(), ()> {
|
||||||
|
match selector {
|
||||||
|
&Selector::Id(id) if id == self.id => Ok(()),
|
||||||
|
s => self.view.focus_view(s),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,7 @@ impl<T: View> ViewWrapper for KeyEventView<T> {
|
|||||||
wrap_impl!(self.content: T);
|
wrap_impl!(self.content: T);
|
||||||
|
|
||||||
fn wrap_on_event(&mut self, event: Event) -> EventResult {
|
fn wrap_on_event(&mut self, event: Event) -> EventResult {
|
||||||
match self.content.on_event(event) {
|
match self.content.on_event(event.clone()) {
|
||||||
EventResult::Ignored => {
|
EventResult::Ignored => {
|
||||||
match self.callbacks.get(&event) {
|
match self.callbacks.get(&event) {
|
||||||
None => EventResult::Ignored,
|
None => EventResult::Ignored,
|
||||||
|
@ -26,6 +26,7 @@ struct Child {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Child {
|
impl Child {
|
||||||
|
// Compute and caches the required size.
|
||||||
fn required_size(&mut self, req: Vec2) -> Vec2 {
|
fn required_size(&mut self, req: Vec2) -> Vec2 {
|
||||||
self.size = self.view.required_size(req);
|
self.size = self.view.required_size(req);
|
||||||
self.size
|
self.size
|
||||||
@ -36,6 +37,17 @@ impl Child {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn cap<'a, I: Iterator<Item = &'a mut usize>>(iter: I, max: usize) {
|
||||||
|
let mut available = max;
|
||||||
|
for item in iter {
|
||||||
|
if *item > available {
|
||||||
|
*item = available;
|
||||||
|
}
|
||||||
|
|
||||||
|
available -= *item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl LinearLayout {
|
impl LinearLayout {
|
||||||
/// Creates a new layout with the given orientation.
|
/// Creates a new layout with the given orientation.
|
||||||
pub fn new(orientation: direction::Orientation) -> Self {
|
pub fn new(orientation: direction::Orientation) -> Self {
|
||||||
@ -197,6 +209,7 @@ impl View for LinearLayout {
|
|||||||
let o = self.orientation;
|
let o = self.orientation;
|
||||||
|
|
||||||
for child in &mut self.children {
|
for child in &mut self.children {
|
||||||
|
// Every item has the same size orthogonal to the layout
|
||||||
child.size.set_axis_from(o.swap(), &size);
|
child.size.set_axis_from(o.swap(), &size);
|
||||||
child.view.layout(size.with_axis_from(o, &child.size));
|
child.view.layout(size.with_axis_from(o, &child.size));
|
||||||
}
|
}
|
||||||
@ -225,8 +238,8 @@ impl View for LinearLayout {
|
|||||||
return ideal;
|
return ideal;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ok, so maybe it didn't.
|
// Ok, so maybe it didn't. Budget cuts, everyone.
|
||||||
// Budget cuts, everyone.
|
// Let's pretend we have almost no space in this direction.
|
||||||
let budget_req = req.with_axis(self.orientation, 1);
|
let budget_req = req.with_axis(self.orientation, 1);
|
||||||
// println_stderr!("Budget req: {:?}", budget_req);
|
// println_stderr!("Budget req: {:?}", budget_req);
|
||||||
|
|
||||||
@ -240,8 +253,15 @@ impl View for LinearLayout {
|
|||||||
// println_stderr!("Desperate: {:?}", desperate);
|
// println_stderr!("Desperate: {:?}", desperate);
|
||||||
|
|
||||||
// This is the lowest we'll ever go. It better fit at least.
|
// This is the lowest we'll ever go. It better fit at least.
|
||||||
|
let orientation = self.orientation;
|
||||||
if !desperate.fits_in(req) {
|
if !desperate.fits_in(req) {
|
||||||
// Just give up...
|
// Just give up...
|
||||||
|
// TODO: hard-cut
|
||||||
|
cap(self.children
|
||||||
|
.iter_mut()
|
||||||
|
.map(|c| c.size.get_mut(orientation)),
|
||||||
|
*req.get(self.orientation));
|
||||||
|
|
||||||
// TODO: print some error message or something
|
// TODO: print some error message or something
|
||||||
// println_stderr!("Seriously? {:?} > {:?}???", desperate, req);
|
// println_stderr!("Seriously? {:?} > {:?}???", desperate, req);
|
||||||
// self.cache = Some(SizeCache::build(desperate, req));
|
// self.cache = Some(SizeCache::build(desperate, req));
|
||||||
@ -333,7 +353,7 @@ impl View for LinearLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn on_event(&mut self, event: Event) -> EventResult {
|
fn on_event(&mut self, event: Event) -> EventResult {
|
||||||
match self.children[self.focus].view.on_event(event) {
|
match self.children[self.focus].view.on_event(event.clone()) {
|
||||||
EventResult::Ignored => {
|
EventResult::Ignored => {
|
||||||
match event {
|
match event {
|
||||||
Event::Shift(Key::Tab) if self.focus > 0 => {
|
Event::Shift(Key::Tab) if self.focus > 0 => {
|
||||||
@ -381,4 +401,15 @@ impl View for LinearLayout {
|
|||||||
child.view.find_any(selector, Box::new(|any| callback(any)));
|
child.view.find_any(selector, Box::new(|any| callback(any)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn focus_view(&mut self, selector: &Selector) -> Result<(), ()> {
|
||||||
|
for (i, child) in self.children.iter_mut().enumerate() {
|
||||||
|
if child.view.focus_view(selector).is_ok() {
|
||||||
|
self.focus = i;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,8 @@ use event::{Callback, Event, EventResult, Key};
|
|||||||
|
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use unicode_width::UnicodeWidthStr;
|
||||||
use vec::Vec2;
|
use vec::Vec2;
|
||||||
use view::ScrollBase;
|
use view::ScrollBase;
|
||||||
use view::Selector;
|
use view::Selector;
|
||||||
@ -116,7 +118,10 @@ impl ListView {
|
|||||||
direction::Relative::Front => {
|
direction::Relative::Front => {
|
||||||
let start = if from_focus { self.focus } else { 0 };
|
let start = if from_focus { self.focus } else { 0 };
|
||||||
|
|
||||||
Box::new(self.children.iter_mut().enumerate().skip(start))
|
Box::new(self.children
|
||||||
|
.iter_mut()
|
||||||
|
.enumerate()
|
||||||
|
.skip(start))
|
||||||
}
|
}
|
||||||
direction::Relative::Back => {
|
direction::Relative::Back => {
|
||||||
let end = if from_focus {
|
let end = if from_focus {
|
||||||
@ -181,10 +186,11 @@ impl View for ListView {
|
|||||||
let offset = self.children
|
let offset = self.children
|
||||||
.iter()
|
.iter()
|
||||||
.map(Child::label)
|
.map(Child::label)
|
||||||
.map(str::len)
|
.map(UnicodeWidthStr::width)
|
||||||
.max()
|
.max()
|
||||||
.unwrap_or(0) + 1;
|
.unwrap_or(0) + 1;
|
||||||
|
|
||||||
|
// println_stderr!("Offset: {}", offset);
|
||||||
self.scrollbase.draw(printer, |printer, i| match self.children[i] {
|
self.scrollbase.draw(printer, |printer, i| match self.children[i] {
|
||||||
Child::Row(ref label, ref view) => {
|
Child::Row(ref label, ref view) => {
|
||||||
printer.print((0, 0), label);
|
printer.print((0, 0), label);
|
||||||
@ -195,12 +201,14 @@ impl View for ListView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn required_size(&mut self, req: Vec2) -> Vec2 {
|
fn required_size(&mut self, req: Vec2) -> Vec2 {
|
||||||
let label_size = self.children
|
// We'll show 2 columns: the labels, and the views.
|
||||||
|
let label_width = self.children
|
||||||
.iter()
|
.iter()
|
||||||
.map(Child::label)
|
.map(Child::label)
|
||||||
.map(str::len)
|
.map(UnicodeWidthStr::width)
|
||||||
.max()
|
.max()
|
||||||
.unwrap_or(0);
|
.unwrap_or(0);
|
||||||
|
|
||||||
let view_size = self.children
|
let view_size = self.children
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.filter_map(Child::view)
|
.filter_map(Child::view)
|
||||||
@ -209,26 +217,34 @@ impl View for ListView {
|
|||||||
.unwrap_or(0);
|
.unwrap_or(0);
|
||||||
|
|
||||||
if self.children.len() > req.y {
|
if self.children.len() > req.y {
|
||||||
Vec2::new(label_size + 1 + view_size + 2, req.y)
|
Vec2::new(label_width + 1 + view_size + 2, req.y)
|
||||||
} else {
|
} else {
|
||||||
Vec2::new(label_size + 1 + view_size, self.children.len())
|
Vec2::new(label_width + 1 + view_size, self.children.len())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn layout(&mut self, size: Vec2) {
|
fn layout(&mut self, size: Vec2) {
|
||||||
self.scrollbase.set_heights(size.y, self.children.len());
|
self.scrollbase.set_heights(size.y, self.children.len());
|
||||||
|
|
||||||
let label_size = self.children
|
// We'll show 2 columns: the labels, and the views.
|
||||||
|
let label_width = self.children
|
||||||
.iter()
|
.iter()
|
||||||
.map(Child::label)
|
.map(Child::label)
|
||||||
.map(str::len)
|
.map(UnicodeWidthStr::width)
|
||||||
.max()
|
.max()
|
||||||
.unwrap_or(0);
|
.unwrap_or(0);
|
||||||
let mut available = size.x - label_size - 1;
|
|
||||||
|
|
||||||
if self.children.len() > size.y {
|
let spacing = 1;
|
||||||
available -= 2;
|
let scrollbar_width = if self.children.len() > size.y { 2 } else { 0 };
|
||||||
}
|
|
||||||
|
let available = if label_width + spacing + scrollbar_width > size.x {
|
||||||
|
// We have no space for the kids! :(
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
size.x - label_width - spacing - scrollbar_width
|
||||||
|
};
|
||||||
|
|
||||||
|
// println_stderr!("Available: {}", available);
|
||||||
|
|
||||||
for child in self.children.iter_mut().filter_map(Child::view) {
|
for child in self.children.iter_mut().filter_map(Child::view) {
|
||||||
child.layout(Vec2::new(available, 1));
|
child.layout(Vec2::new(available, 1));
|
||||||
@ -241,7 +257,7 @@ impl View for ListView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Child::Row(_, ref mut view) = self.children[self.focus] {
|
if let Child::Row(_, ref mut view) = self.children[self.focus] {
|
||||||
let result = view.on_event(event);
|
let result = view.on_event(event.clone());
|
||||||
if result.is_consumed() {
|
if result.is_consumed() {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@ -282,10 +298,11 @@ impl View for ListView {
|
|||||||
|
|
||||||
fn take_focus(&mut self, source: direction::Direction) -> bool {
|
fn take_focus(&mut self, source: direction::Direction) -> bool {
|
||||||
let rel = source.relative(direction::Orientation::Vertical);
|
let rel = source.relative(direction::Orientation::Vertical);
|
||||||
let i = if let Some(i) = self.iter_mut(rel.is_none(),
|
let i = if let Some(i) =
|
||||||
rel.unwrap_or(direction::Relative::Front))
|
self.iter_mut(rel.is_none(),
|
||||||
.filter_map(|p| try_focus(p, source))
|
rel.unwrap_or(direction::Relative::Front))
|
||||||
.next() {
|
.filter_map(|p| try_focus(p, source))
|
||||||
|
.next() {
|
||||||
i
|
i
|
||||||
} else {
|
} else {
|
||||||
// No one wants to be in focus
|
// No one wants to be in focus
|
||||||
@ -304,4 +321,18 @@ impl View for ListView {
|
|||||||
view.find_any(selector, Box::new(|any| callback(any)));
|
view.find_any(selector, Box::new(|any| callback(any)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn focus_view(&mut self, selector: &Selector) -> Result<(), ()> {
|
||||||
|
if let Some(i) = self.children
|
||||||
|
.iter_mut()
|
||||||
|
.enumerate()
|
||||||
|
.filter_map(|(i, v)| v.view().map(|v| (i, v)))
|
||||||
|
.filter_map(|(i, v)| v.focus_view(selector).ok().map(|_| i))
|
||||||
|
.next() {
|
||||||
|
self.focus = i;
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,12 +20,27 @@ impl<V: View> ViewWrapper for Panel<V> {
|
|||||||
|
|
||||||
fn wrap_required_size(&mut self, req: Vec2) -> Vec2 {
|
fn wrap_required_size(&mut self, req: Vec2) -> Vec2 {
|
||||||
// TODO: make borders conditional?
|
// TODO: make borders conditional?
|
||||||
self.view.required_size(req - (2, 2)) + (2, 2)
|
let req = if Vec2::new(2, 2).fits_in(req) {
|
||||||
|
req - (2, 2)
|
||||||
|
} else {
|
||||||
|
Vec2::zero()
|
||||||
|
};
|
||||||
|
|
||||||
|
self.view.required_size(req) + (2, 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn wrap_draw(&self, printer: &Printer) {
|
fn wrap_draw(&self, printer: &Printer) {
|
||||||
printer.print_box((0, 0), printer.size, true);
|
printer.print_box((0, 0), printer.size, true);
|
||||||
self.view
|
self.view.draw(&printer.sub_printer((1, 1),
|
||||||
.draw(&printer.sub_printer((1, 1), printer.size - (2, 2), true));
|
printer.size - (2, 2),
|
||||||
|
true));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wrap_layout(&mut self, size: Vec2) {
|
||||||
|
self.view.layout(if Vec2::new(2, 2).fits_in(size) {
|
||||||
|
size - (2, 2)
|
||||||
|
} else {
|
||||||
|
size
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -340,6 +340,9 @@ impl<T: 'static> View for SelectView<T> {
|
|||||||
ColorStyle::Highlight
|
ColorStyle::Highlight
|
||||||
};
|
};
|
||||||
let x = printer.size.x;
|
let x = printer.size.x;
|
||||||
|
if x == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
printer.with_color(style, |printer| {
|
printer.with_color(style, |printer| {
|
||||||
|
@ -186,4 +186,14 @@ impl View for StackView {
|
|||||||
layer.view.find_any(selector, Box::new(|any| callback(any)));
|
layer.view.find_any(selector, Box::new(|any| callback(any)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn focus_view(&mut self, selector: &Selector) -> Result<(), ()> {
|
||||||
|
for layer in &mut self.layers {
|
||||||
|
if layer.view.focus_view(selector).is_ok() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,6 +45,14 @@ impl<T> XY<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a mutable reference to the value on the given axis.
|
||||||
|
pub fn get_mut(&mut self, o: Orientation) -> &mut T {
|
||||||
|
match o {
|
||||||
|
Orientation::Horizontal => &mut self.x,
|
||||||
|
Orientation::Vertical => &mut self.y,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns a new `XY` of tuples made by zipping `self` and `other`.
|
/// Returns a new `XY` of tuples made by zipping `self` and `other`.
|
||||||
pub fn zip<U>(self, other: XY<U>) -> XY<(T, U)> {
|
pub fn zip<U>(self, other: XY<U>) -> XY<(T, U)> {
|
||||||
XY::new((self.x, other.x), (self.y, other.y))
|
XY::new((self.x, other.x), (self.y, other.y))
|
||||||
|
Loading…
Reference in New Issue
Block a user