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,30 +2,44 @@ extern crate cursive;
|
|||||||
|
|
||||||
use cursive::Cursive;
|
use cursive::Cursive;
|
||||||
use cursive::With;
|
use cursive::With;
|
||||||
use cursive::view::{ListView, Checkbox, Dialog, EditView, TextView, LinearLayout};
|
use cursive::view::{Checkbox, Dialog, EditView, LinearLayout, ListView,
|
||||||
|
TextView, SelectView};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut siv = Cursive::new();
|
let mut siv = Cursive::new();
|
||||||
|
|
||||||
siv.add_layer(Dialog::new(ListView::new()
|
siv.add_layer(Dialog::new(ListView::new()
|
||||||
.child("Name", EditView::new().min_length(10))
|
.child("Name", EditView::new().min_length(10))
|
||||||
.child("Email", LinearLayout::horizontal()
|
.child("Email",
|
||||||
.child(EditView::new().min_length(15).disabled().with_id("email1"))
|
LinearLayout::horizontal()
|
||||||
|
.child(EditView::new()
|
||||||
|
.min_length(15)
|
||||||
|
.disabled()
|
||||||
|
.with_id("email1"))
|
||||||
.child(TextView::new("@"))
|
.child(TextView::new("@"))
|
||||||
.child(EditView::new().min_length(10).disabled().with_id("email2")))
|
.child(EditView::new()
|
||||||
.child("Receive spam?", Checkbox::new().on_change(|s, checked| {
|
.min_length(10)
|
||||||
|
.disabled()
|
||||||
|
.with_id("email2")))
|
||||||
|
.child("Receive spam?",
|
||||||
|
Checkbox::new().on_change(|s, checked| {
|
||||||
for name in &["email1", "email2"] {
|
for name in &["email1", "email2"] {
|
||||||
let view: &mut EditView = s.find_id(name).unwrap();
|
let view: &mut EditView = s.find_id(name).unwrap();
|
||||||
view.set_enabled(checked);
|
view.set_enabled(checked);
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
.delimiter()
|
.delimiter()
|
||||||
|
.child("Age",
|
||||||
|
SelectView::new().popup()
|
||||||
|
.item_str("0-18")
|
||||||
|
.item_str("19-30")
|
||||||
|
.item_str("31-40")
|
||||||
|
.item_str("41+"))
|
||||||
.with(|list| {
|
.with(|list| {
|
||||||
for i in 0..50 {
|
for i in 0..50 {
|
||||||
list.add_child(&format!("Item {}", i), EditView::new());
|
list.add_child(&format!("Item {}", i), EditView::new());
|
||||||
}
|
}
|
||||||
})
|
}))
|
||||||
)
|
|
||||||
.title("Please fill out this form")
|
.title("Please fill out this form")
|
||||||
.button("Ok", |s| s.quit()));
|
.button("Ok", |s| s.quit()));
|
||||||
|
|
||||||
|
@ -200,6 +200,12 @@ impl Cursive {
|
|||||||
B::set_refresh_rate(fps)
|
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.
|
/// Returns a mutable reference to the currently active screen.
|
||||||
pub fn screen_mut(&mut self) -> &mut StackView {
|
pub fn screen_mut(&mut self) -> &mut StackView {
|
||||||
let id = self.active_screen;
|
let id = self.active_screen;
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
use std::cmp::min;
|
||||||
|
|
||||||
use unicode_width::UnicodeWidthStr;
|
use unicode_width::UnicodeWidthStr;
|
||||||
|
|
||||||
use Cursive;
|
use Cursive;
|
||||||
|
use With;
|
||||||
use menu::{MenuItem, MenuTree};
|
use menu::{MenuItem, MenuTree};
|
||||||
use Printer;
|
use Printer;
|
||||||
use view::View;
|
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 {
|
fn item_width(item: &MenuItem) -> usize {
|
||||||
match *item {
|
match *item {
|
||||||
MenuItem::Delimiter => 1,
|
MenuItem::Delimiter => 1,
|
||||||
|
@ -7,6 +7,7 @@ use menu::MenuTree;
|
|||||||
use view::MenuPopup;
|
use view::MenuPopup;
|
||||||
use With;
|
use With;
|
||||||
use direction::Direction;
|
use direction::Direction;
|
||||||
|
use view::position::Position;
|
||||||
use view::{IdView, View};
|
use view::{IdView, View};
|
||||||
use align::{Align, HAlign, VAlign};
|
use align::{Align, HAlign, VAlign};
|
||||||
use view::scroll::ScrollBase;
|
use view::scroll::ScrollBase;
|
||||||
@ -45,6 +46,10 @@ pub struct SelectView<T = String> {
|
|||||||
align: Align,
|
align: Align,
|
||||||
// `true` if we show a one-line view, with popup on selection.
|
// `true` if we show a one-line view, with popup on selection.
|
||||||
popup: bool,
|
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> {
|
impl<T: 'static> SelectView<T> {
|
||||||
@ -58,6 +63,8 @@ impl<T: 'static> SelectView<T> {
|
|||||||
select_cb: None,
|
select_cb: None,
|
||||||
align: Align::top_left(),
|
align: Align::top_left(),
|
||||||
popup: false,
|
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> {
|
impl<T: 'static> View for SelectView<T> {
|
||||||
fn draw(&self, printer: &Printer) {
|
fn draw(&self, printer: &Printer) {
|
||||||
|
self.last_offset.set(printer.offset);
|
||||||
|
|
||||||
if self.popup {
|
if self.popup {
|
||||||
let style = if !self.enabled {
|
let style = if !self.enabled {
|
||||||
ColorStyle::Secondary
|
ColorStyle::Secondary
|
||||||
@ -218,15 +227,15 @@ impl<T: 'static> View for SelectView<T> {
|
|||||||
} else {
|
} else {
|
||||||
ColorStyle::Highlight
|
ColorStyle::Highlight
|
||||||
};
|
};
|
||||||
let x = printer.size.x - 1;
|
let x = printer.size.x;
|
||||||
|
|
||||||
|
|
||||||
printer.with_color(style, |printer| {
|
printer.with_color(style, |printer| {
|
||||||
// Prepare the entire background
|
// Prepare the entire background
|
||||||
printer.print_hline((0, 0), x, " ");
|
printer.print_hline((1, 0), x - 1, " ");
|
||||||
// Draw the borders
|
// Draw the borders
|
||||||
printer.print((0, 0), "<");
|
printer.print((0, 0), "<");
|
||||||
printer.print((x, 0), ">");
|
printer.print((x-1, 0), ">");
|
||||||
|
|
||||||
let label = &self.items[self.focus()].label;
|
let label = &self.items[self.focus()].label;
|
||||||
|
|
||||||
@ -287,6 +296,8 @@ impl<T: 'static> View for SelectView<T> {
|
|||||||
if self.popup {
|
if self.popup {
|
||||||
match event {
|
match event {
|
||||||
Event::Key(Key::Enter) => {
|
Event::Key(Key::Enter) => {
|
||||||
|
// Build a shallow menu tree to mimick the items array.
|
||||||
|
// TODO: cache it?
|
||||||
let mut tree = MenuTree::new();
|
let mut tree = MenuTree::new();
|
||||||
for (i, item) in self.items.iter().enumerate() {
|
for (i, item) in self.items.iter().enumerate() {
|
||||||
let focus = self.focus.clone();
|
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 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| {
|
EventResult::with_cb(move |s| {
|
||||||
|
// The callback will want to work with a fresh Rc
|
||||||
let tree = tree.clone();
|
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,
|
_ => EventResult::Ignored,
|
||||||
@ -354,6 +393,8 @@ impl<T: 'static> View for SelectView<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn layout(&mut self, size: Vec2) {
|
fn layout(&mut self, size: Vec2) {
|
||||||
|
self.last_size = size;
|
||||||
|
|
||||||
if !self.popup {
|
if !self.popup {
|
||||||
self.scrollbase.set_heights(size.y, self.items.len());
|
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.
|
/// Only the top-most view is active and can receive input.
|
||||||
pub struct StackView {
|
pub struct StackView {
|
||||||
layers: Vec<Layer>,
|
layers: Vec<Layer>,
|
||||||
|
last_size: Vec2,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Layer {
|
struct Layer {
|
||||||
@ -27,7 +28,10 @@ new_default!(StackView);
|
|||||||
impl StackView {
|
impl StackView {
|
||||||
/// Creates a new empty StackView
|
/// Creates a new empty StackView
|
||||||
pub fn new() -> Self {
|
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.
|
/// Adds new view on top of the stack in the center of the screen.
|
||||||
@ -54,6 +58,17 @@ impl StackView {
|
|||||||
self.layers.pop();
|
self.layers.pop();
|
||||||
::B::clear();
|
::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 {
|
impl View for StackView {
|
||||||
@ -82,6 +97,8 @@ impl View for StackView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn layout(&mut self, size: Vec2) {
|
fn layout(&mut self, size: Vec2) {
|
||||||
|
self.last_size = size;
|
||||||
|
|
||||||
// The call has been made, we can't ask for more space anymore.
|
// The call has been made, we can't ask for more space anymore.
|
||||||
// Let's make do with what we have.
|
// Let's make do with what we have.
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user