mirror of
https://github.com/FliegendeWurst/cursive.git
synced 2024-11-23 17:35:00 +00:00
Add proper select popup placement
Added a popup select to the `list_view` example
This commit is contained in:
parent
8b6022a398
commit
64d176ffa5
@ -2,32 +2,46 @@ extern crate cursive;
|
||||
|
||||
use cursive::Cursive;
|
||||
use cursive::With;
|
||||
use cursive::view::{ListView, Checkbox, Dialog, EditView, TextView, LinearLayout};
|
||||
use cursive::view::{Checkbox, Dialog, EditView, LinearLayout, ListView,
|
||||
TextView, SelectView};
|
||||
|
||||
fn main() {
|
||||
let mut siv = Cursive::new();
|
||||
|
||||
siv.add_layer(Dialog::new(ListView::new()
|
||||
.child("Name", EditView::new().min_length(10))
|
||||
.child("Email", LinearLayout::horizontal()
|
||||
.child(EditView::new().min_length(15).disabled().with_id("email1"))
|
||||
.child(TextView::new("@"))
|
||||
.child(EditView::new().min_length(10).disabled().with_id("email2")))
|
||||
.child("Receive spam?", Checkbox::new().on_change(|s, checked| {
|
||||
for name in &["email1", "email2"] {
|
||||
let view: &mut EditView = s.find_id(name).unwrap();
|
||||
view.set_enabled(checked);
|
||||
}
|
||||
}))
|
||||
.delimiter()
|
||||
.with(|list| {
|
||||
for i in 0..50 {
|
||||
list.add_child(&format!("Item {}", i), EditView::new());
|
||||
}
|
||||
})
|
||||
)
|
||||
.title("Please fill out this form")
|
||||
.button("Ok", |s| s.quit()));
|
||||
.child("Name", EditView::new().min_length(10))
|
||||
.child("Email",
|
||||
LinearLayout::horizontal()
|
||||
.child(EditView::new()
|
||||
.min_length(15)
|
||||
.disabled()
|
||||
.with_id("email1"))
|
||||
.child(TextView::new("@"))
|
||||
.child(EditView::new()
|
||||
.min_length(10)
|
||||
.disabled()
|
||||
.with_id("email2")))
|
||||
.child("Receive spam?",
|
||||
Checkbox::new().on_change(|s, checked| {
|
||||
for name in &["email1", "email2"] {
|
||||
let view: &mut EditView = s.find_id(name).unwrap();
|
||||
view.set_enabled(checked);
|
||||
}
|
||||
}))
|
||||
.delimiter()
|
||||
.child("Age",
|
||||
SelectView::new().popup()
|
||||
.item_str("0-18")
|
||||
.item_str("19-30")
|
||||
.item_str("31-40")
|
||||
.item_str("41+"))
|
||||
.with(|list| {
|
||||
for i in 0..50 {
|
||||
list.add_child(&format!("Item {}", i), EditView::new());
|
||||
}
|
||||
}))
|
||||
.title("Please fill out this form")
|
||||
.button("Ok", |s| s.quit()));
|
||||
|
||||
siv.run();
|
||||
}
|
||||
|
@ -200,6 +200,12 @@ impl Cursive {
|
||||
B::set_refresh_rate(fps)
|
||||
}
|
||||
|
||||
/// Returns a reference to the currently active screen.
|
||||
pub fn screen(&self) -> &StackView {
|
||||
let id = self.active_screen;
|
||||
&self.screens[id]
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the currently active screen.
|
||||
pub fn screen_mut(&mut self) -> &mut StackView {
|
||||
let id = self.active_screen;
|
||||
|
@ -1,8 +1,10 @@
|
||||
use std::rc::Rc;
|
||||
use std::cmp::min;
|
||||
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
use Cursive;
|
||||
use With;
|
||||
use menu::{MenuItem, MenuTree};
|
||||
use Printer;
|
||||
use view::View;
|
||||
@ -36,6 +38,18 @@ impl MenuPopup {
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the currently focused element.
|
||||
pub fn set_focus(&mut self, focus: usize) {
|
||||
self.focus = min(focus, self.menu.len());
|
||||
}
|
||||
|
||||
/// Sets the currently focused element.
|
||||
///
|
||||
/// Chainable variant.
|
||||
pub fn focus(self, focus: usize) -> Self {
|
||||
self.with(|s| s.set_focus(focus))
|
||||
}
|
||||
|
||||
fn item_width(item: &MenuItem) -> usize {
|
||||
match *item {
|
||||
MenuItem::Delimiter => 1,
|
||||
|
@ -7,6 +7,7 @@ use menu::MenuTree;
|
||||
use view::MenuPopup;
|
||||
use With;
|
||||
use direction::Direction;
|
||||
use view::position::Position;
|
||||
use view::{IdView, View};
|
||||
use align::{Align, HAlign, VAlign};
|
||||
use view::scroll::ScrollBase;
|
||||
@ -45,6 +46,10 @@ pub struct SelectView<T = String> {
|
||||
align: Align,
|
||||
// `true` if we show a one-line view, with popup on selection.
|
||||
popup: bool,
|
||||
// We need the last offset to place the popup window
|
||||
// We "cache" it during the draw, so we need interior mutability.
|
||||
last_offset: Cell<Vec2>,
|
||||
last_size: Vec2,
|
||||
}
|
||||
|
||||
impl<T: 'static> SelectView<T> {
|
||||
@ -58,6 +63,8 @@ impl<T: 'static> SelectView<T> {
|
||||
select_cb: None,
|
||||
align: Align::top_left(),
|
||||
popup: false,
|
||||
last_offset: Cell::new(Vec2::zero()),
|
||||
last_size: Vec2::zero(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -210,6 +217,8 @@ impl SelectView<String> {
|
||||
|
||||
impl<T: 'static> View for SelectView<T> {
|
||||
fn draw(&self, printer: &Printer) {
|
||||
self.last_offset.set(printer.offset);
|
||||
|
||||
if self.popup {
|
||||
let style = if !self.enabled {
|
||||
ColorStyle::Secondary
|
||||
@ -218,15 +227,15 @@ impl<T: 'static> View for SelectView<T> {
|
||||
} else {
|
||||
ColorStyle::Highlight
|
||||
};
|
||||
let x = printer.size.x - 1;
|
||||
let x = printer.size.x;
|
||||
|
||||
|
||||
printer.with_color(style, |printer| {
|
||||
// Prepare the entire background
|
||||
printer.print_hline((0, 0), x, " ");
|
||||
printer.print_hline((1, 0), x - 1, " ");
|
||||
// Draw the borders
|
||||
printer.print((0, 0), "<");
|
||||
printer.print((x, 0), ">");
|
||||
printer.print((x-1, 0), ">");
|
||||
|
||||
let label = &self.items[self.focus()].label;
|
||||
|
||||
@ -287,6 +296,8 @@ impl<T: 'static> View for SelectView<T> {
|
||||
if self.popup {
|
||||
match event {
|
||||
Event::Key(Key::Enter) => {
|
||||
// Build a shallow menu tree to mimick the items array.
|
||||
// TODO: cache it?
|
||||
let mut tree = MenuTree::new();
|
||||
for (i, item) in self.items.iter().enumerate() {
|
||||
let focus = self.focus.clone();
|
||||
@ -299,10 +310,38 @@ impl<T: 'static> View for SelectView<T> {
|
||||
}
|
||||
});
|
||||
}
|
||||
// Let's keep the tree around,
|
||||
// the callback will want to use it.
|
||||
let tree = Rc::new(tree);
|
||||
|
||||
let focus = self.focus();
|
||||
// This is the offset for the label text.
|
||||
// We'll want to show the popup so that the text matches.
|
||||
// It'll be soo cool.
|
||||
let text_offset =
|
||||
(self.last_size.x - self.items[focus].label.len()) / 2;
|
||||
// The total offset for the window is:
|
||||
// * the last absolute offset at which we drew this view
|
||||
// * shifted to the top of the focus (so the line matches)
|
||||
// * shifted to the right of the text offset
|
||||
// * shifted top-left of the border+padding of the popup
|
||||
let offset = self.last_offset.get() - (0, focus) +
|
||||
(text_offset, 0) -
|
||||
(2, 1);
|
||||
// And now, we can return the callback.
|
||||
EventResult::with_cb(move |s| {
|
||||
// The callback will want to work with a fresh Rc
|
||||
let tree = tree.clone();
|
||||
s.add_layer(MenuPopup::new(tree));
|
||||
// We'll relativise the absolute position,
|
||||
// So that we are locked to the parent view.
|
||||
// A nice effect is that window resizes will keep both
|
||||
// layers together.
|
||||
let current_offset = s.screen().offset();
|
||||
let offset = offset - current_offset;
|
||||
// And finally, put the view in view!
|
||||
s.screen_mut()
|
||||
.add_layer_at(Position::parent(offset),
|
||||
MenuPopup::new(tree).focus(focus));
|
||||
})
|
||||
}
|
||||
_ => EventResult::Ignored,
|
||||
@ -354,6 +393,8 @@ impl<T: 'static> View for SelectView<T> {
|
||||
}
|
||||
|
||||
fn layout(&mut self, size: Vec2) {
|
||||
self.last_size = size;
|
||||
|
||||
if !self.popup {
|
||||
self.scrollbase.set_heights(size.y, self.items.len());
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ use theme::ColorStyle;
|
||||
/// Only the top-most view is active and can receive input.
|
||||
pub struct StackView {
|
||||
layers: Vec<Layer>,
|
||||
last_size: Vec2,
|
||||
}
|
||||
|
||||
struct Layer {
|
||||
@ -27,7 +28,10 @@ new_default!(StackView);
|
||||
impl StackView {
|
||||
/// Creates a new empty StackView
|
||||
pub fn new() -> Self {
|
||||
StackView { layers: Vec::new() }
|
||||
StackView {
|
||||
layers: Vec::new(),
|
||||
last_size: Vec2::zero(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds new view on top of the stack in the center of the screen.
|
||||
@ -54,6 +58,17 @@ impl StackView {
|
||||
self.layers.pop();
|
||||
::B::clear();
|
||||
}
|
||||
|
||||
/// Computes the offset of the current top view.
|
||||
pub fn offset(&self) -> Vec2 {
|
||||
let mut previous = Vec2::zero();
|
||||
for layer in &self.layers {
|
||||
let offset = layer.position
|
||||
.compute_offset(layer.size, self.last_size, previous);
|
||||
previous = offset;
|
||||
}
|
||||
previous
|
||||
}
|
||||
}
|
||||
|
||||
impl View for StackView {
|
||||
@ -82,6 +97,8 @@ impl View for StackView {
|
||||
}
|
||||
|
||||
fn layout(&mut self, size: Vec2) {
|
||||
self.last_size = size;
|
||||
|
||||
// The call has been made, we can't ask for more space anymore.
|
||||
// Let's make do with what we have.
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user