-
Notifications
You must be signed in to change notification settings - Fork 247
Replies: 1 comment · 5 replies
-
Hi, and thanks for the question! I think the issue is that (In general, most views don't implement scrolling by themselves, but leave it to wrappers like Also a minor note: self.add_button
.draw(&printer.windowed(self.add_button_position).focused(
printer.focused
&& self.focus.is_some()
&& self.focus.unwrap() == FocusIndex::AddButton,
));
self.add_button
.draw(&printer.windowed(self.add_button_position).focused(self.focus == Some(FocusIndex::AddButton))); |
Beta Was this translation helpful? Give feedback.
All reactions
-
Oh interesting. So does adding a SelectView to the Cursive instance implicitly wrap it in a scroll view under the covers? Testing out wrapping it into a scroll view using Do I need to maybe do something with either Thanks for the hint on the printer call. Edit: |
Beta Was this translation helpful? Give feedback.
All reactions
-
Do you have some code I could try to run to investigate the issue? Otherwise, a few random answers or details:
This is equivalent to
Adding a
These are what the
Ah indeed looks like I should add a method for a non-mut reference. In the meantime you may use As for the unwrap issue: the problem is that And lastly, the main way to mis-use a |
Beta Was this translation helpful? Give feedback.
All reactions
-
Ah one more thing: it shouldn't be critical here, but if you wrap a view and it returns a consumed event, it's best to forward any callback it returns: Some(FocusIndex::SelectView) => match self.selector.on_event(event) {
EventResult::Ignored => {
// Remove focus and let the parent deal
self.move_focus(None);
EventResult::Ignored
}
// Here it discards any callback the selector would return
EventResult::Consumed(_) => EventResult::consumed(),
// Instead, you should forward the event:
event @ EventResult::Consumed(_) => event,
}, |
Beta Was this translation helpful? Give feedback.
All reactions
-
Once again, thanks for all the small tips. I've been working on teaching myself rust in part using your library for building this app, so the hints are much appreciated. I'm pretty sure I'm giving the scroll view the right size, I compute a position in layout, it's stored in the struct and that same position object is used for the print call path. For code to run to investigate, I'll include the full view source file below, and here's a link to my gitlab with the actual project itself if you need to run/see it in context (https://gitlab.com/tpmoney/rom_mgr). You'll want the custom_layout branch. use core::fmt::Debug;
use std::{
cmp::max,
path::{Path, PathBuf},
};
use cursive::{
direction::{
self, Absolute, Direction,
Relative::{self, Back, Front},
},
event::{Event, EventResult, Key},
traits::{Nameable, Scrollable},
view::{CannotFocus, ViewWrapper},
views::{Button, NamedView, SelectView, TextView},
Cursive, Rect, Vec2, View, With, XY,
};
use cursive::{reexports::log::info, views::ScrollView};
use uuid::Uuid;
use crate::enclose;
use super::{file_picker::FilterOptions, FilePicker, PaddedDialog};
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
enum FocusIndex {
SelectView,
AddButton,
DeleteButton,
}
impl FocusIndex {
fn get_next(&self) -> Option<FocusIndex> {
match self {
FocusIndex::SelectView => Some(FocusIndex::AddButton),
FocusIndex::AddButton => Some(FocusIndex::DeleteButton),
FocusIndex::DeleteButton => None,
}
}
fn get_previous(&self) -> Option<FocusIndex> {
match self {
FocusIndex::SelectView => None,
FocusIndex::AddButton => Some(FocusIndex::SelectView),
FocusIndex::DeleteButton => Some(FocusIndex::AddButton),
}
}
fn first() -> FocusIndex {
FocusIndex::SelectView
}
fn last() -> FocusIndex {
FocusIndex::DeleteButton
}
}
type PathSelectorView = NamedView<ScrollView<SelectView<PathBuf>>>;
pub struct PathCollector {
label: TextView,
selector: PathSelectorView,
add_button: Button,
delete_button: Button,
label_position: Rect,
divider_positon: Rect,
add_button_position: Rect,
delete_button_position: Rect,
selector_position: Rect,
focus: Option<FocusIndex>,
}
impl View for PathCollector {
fn draw(&self, printer: &cursive::Printer) {
self.label.draw(&printer.windowed(self.label_position));
printer.print_vline(self.divider_positon.top_left(), printer.size.y, "\u{2502}");
self.add_button.draw(
&printer
.windowed(self.add_button_position)
.focused(self.focus.is_some() && self.focus.unwrap() == FocusIndex::AddButton),
);
self.delete_button.draw(
&printer
.windowed(self.delete_button_position)
.focused(self.focus.is_some() && self.focus.unwrap() == FocusIndex::DeleteButton),
);
self.selector.draw(
&printer
.windowed(self.selector_position)
.focused(self.focus.is_some() && self.focus.unwrap() == FocusIndex::SelectView),
);
}
fn layout(&mut self, size: XY<usize>) {
let size = size.saturating_sub((1, 1));
let label_size = self.label.required_size(size);
let add_button_size = self.add_button.required_size(size);
let delete_button_size = self.delete_button.required_size(size);
let buttons_width = max(add_button_size.x, delete_button_size.x);
self.label_position = Rect::from_size((0usize, 0), label_size);
self.divider_positon = Rect::from_size((size.x - buttons_width, 0), (1, size.y));
self.add_button_position =
Rect::from_size((self.divider_positon.left() + 1, 0), add_button_size);
self.delete_button_position = Rect::from_size(
(self.divider_positon.left() + 1, size.y),
delete_button_size,
);
self.selector_position = Rect::from_corners(
(0, self.label_position.bottom() + 1),
(size.x - (size.x - self.divider_positon.left()) - 1, size.y),
);
self.label.layout(self.label_position.size());
self.add_button.layout(self.add_button_position.size());
self.delete_button
.layout(self.delete_button_position.size());
self.selector.layout(self.selector_position.size())
}
fn required_size(&mut self, constraint: cursive::Vec2) -> cursive::Vec2 {
self.layout(constraint);
self.delete_button_position.bottom_right()
}
fn take_focus(&mut self, source: Direction) -> Result<EventResult, CannotFocus> {
info!("Take focus from {:?}", source);
let rel = convert_direction(source);
let next_focus = self.next_focus(rel);
self.move_focus(next_focus);
Ok(EventResult::consumed())
}
fn on_event(&mut self, event: Event) -> EventResult {
match event {
Event::Key(Key::Tab) => {
let next_focus = self.next_focus(Front);
self.move_focus(next_focus);
match next_focus {
Some(_) => EventResult::consumed(),
None => EventResult::Ignored,
}
}
Event::Shift(Key::Tab) => {
let next_focus = self.next_focus(Back);
self.move_focus(next_focus);
match next_focus {
Some(_) => EventResult::consumed(),
None => EventResult::Ignored,
}
}
Event::Key(Key::Left) => match self.focus {
Some(FocusIndex::SelectView) => self.selector.on_event(event),
Some(_) => {
if !self.selector.get_mut().get_inner().is_empty() {
self.move_focus(Some(FocusIndex::SelectView));
}
EventResult::consumed()
}
None => EventResult::Ignored,
},
Event::Key(Key::Right) => match self.focus {
Some(FocusIndex::SelectView) => match self.selector.on_event(event) {
EventResult::Consumed(_) => EventResult::consumed(),
EventResult::Ignored => {
self.move_focus(Some(FocusIndex::AddButton));
EventResult::consumed()
}
},
_ => EventResult::Ignored,
},
Event::Key(Key::Up) => match self.focus {
Some(FocusIndex::AddButton) => {
// Remove focus and let the parent deal
self.move_focus(None);
EventResult::Ignored
}
Some(FocusIndex::DeleteButton) => {
self.move_focus(Some(FocusIndex::AddButton));
EventResult::consumed()
}
Some(FocusIndex::SelectView) => match self.selector.on_event(event) {
EventResult::Ignored => {
// Remove focus and let the parent deal
self.move_focus(None);
EventResult::Ignored
}
event @ EventResult::Consumed(_) => event,
},
None => EventResult::Ignored,
},
Event::Key(Key::Down) => match self.focus {
Some(FocusIndex::AddButton) => {
// Remove focus and let the parent deal
self.move_focus(Some(FocusIndex::DeleteButton));
EventResult::consumed()
}
Some(FocusIndex::DeleteButton) => {
self.move_focus(None);
EventResult::Ignored
}
Some(FocusIndex::SelectView) => match self.selector.on_event(event) {
EventResult::Ignored => {
// Remove focus and let the parent deal
self.move_focus(None);
EventResult::Ignored
}
event @ EventResult::Consumed(_) => event,
},
None => EventResult::Ignored,
},
_ => match self.get_focused_view() {
Some(view) => view.on_event(event),
None => EventResult::Ignored,
},
}
}
fn call_on_any<'a>(
&mut self,
selector: &cursive::view::Selector<'_>,
cb: cursive::event::AnyCb<'a>,
) {
self.selector.call_on_any(selector, cb);
self.add_button.call_on_any(selector, cb);
self.delete_button.call_on_any(selector, cb);
}
fn important_area(&self, _: Vec2) -> Rect {
self.selector.important_area(self.selector_position.size())
+ self.selector_position.top_left()
}
}
impl PathCollector {
pub fn new(filters: FilterOptions) -> Self {
let unique_id = Uuid::new_v4().to_string();
let mut selector = SelectView::<PathBuf>::new()
.scrollable()
.with_name(unique_id.clone());
selector
.get_mut()
.get_inner_mut()
.add_item("Test 1", PathBuf::default());
selector
.get_mut()
.get_inner_mut()
.add_item("Test 2", PathBuf::default());
selector
.get_mut()
.get_inner_mut()
.add_item("Test 3", PathBuf::default());
selector
.get_mut()
.get_inner_mut()
.add_item("Test 4", PathBuf::default());
selector
.get_mut()
.get_inner_mut()
.add_item("Test 5", PathBuf::default());
selector
.get_mut()
.get_inner_mut()
.add_item("Test 6", PathBuf::default());
selector
.get_mut()
.get_inner_mut()
.add_item("Test 7", PathBuf::default());
selector
.get_mut()
.get_inner_mut()
.add_item("Test 8", PathBuf::default());
selector
.get_mut()
.get_inner_mut()
.add_item("Test 9", PathBuf::default());
PathCollector {
label: TextView::new(String::default()),
selector,
add_button: Button::new(
"Add",
enclose! {(unique_id, filters) move |term| {
add_path_to_collector(term, &unique_id, &filters);
}},
),
delete_button: Button::new(
"Delete",
enclose! {(unique_id) move |term| {
delete_path_from_collector(term, &unique_id)
}},
),
label_position: Rect::from_point(XY::default()),
divider_positon: Rect::from_point(XY::default()),
add_button_position: Rect::from_point(XY::default()),
delete_button_position: Rect::from_point(XY::default()),
selector_position: Rect::from_point(XY::default()),
focus: None,
}
}
pub fn label(self, label: &str) -> Self {
self.with(|s| {
s.label.set_content(label);
})
}
pub fn content<I, T>(self, paths: I) -> Self
where
I: Iterator<Item = PathBuf>,
{
let mut sorted = paths.collect::<Vec<PathBuf>>();
sorted.sort_by_key(|path| path.file_name().unwrap().to_str().unwrap().to_owned());
let items: Vec<(String, PathBuf)> = sorted
.into_iter()
.map(|entry| {
let name = entry.file_name().unwrap().to_str().unwrap().to_owned();
(name, entry)
})
.collect();
self.with(|s| s.selector.get_mut().get_inner_mut().add_all(items))
}
/// Return the paths in this collector
pub fn get_owned_values(&mut self) -> Vec<PathBuf> {
self.selector
.get_mut()
.get_inner_mut()
.iter()
.map(|entry| entry.1.to_owned())
.collect()
}
pub fn add_path(&mut self, path: &Path) {
let name = path.to_str().unwrap().to_owned();
self.selector
.get_mut()
.get_inner_mut()
.add_item(name, path.to_path_buf())
}
fn next_focus(&mut self, source: direction::Relative) -> Option<FocusIndex> {
let mut next = match source {
Front => match self.focus {
Some(index) => index.get_next(),
None => Some(FocusIndex::first()),
},
Back => match self.focus {
Some(index) => index.get_previous(),
None => Some(FocusIndex::last()),
},
};
// Don't focus select view if it's empty
if let Some(FocusIndex::SelectView) = next {
if self.selector.get_mut().get_inner().is_empty() {
next = match source {
Front => next.unwrap().get_next(),
Back => next.unwrap().get_previous(),
}
}
}
next
}
fn get_focused_view(&mut self) -> Option<&mut dyn View> {
self.focus_to_view(self.focus)
}
fn focus_to_view(&mut self, index: Option<FocusIndex>) -> Option<&mut dyn View> {
match index {
Some(FocusIndex::SelectView) => Some(&mut self.selector),
Some(FocusIndex::AddButton) => Some(&mut self.add_button),
Some(FocusIndex::DeleteButton) => Some(&mut self.delete_button),
_ => None,
}
}
fn move_focus(&mut self, new_focus: Option<FocusIndex>) {
if let Some(view) = self.focus_to_view(self.focus) {
view.on_event(Event::FocusLost);
}
if let Some(view) = self.focus_to_view(new_focus) {
// We know all of our fields will take focus
_ = view.take_focus(Direction::none());
}
self.focus = new_focus;
}
}
// Convert any direction into a relative direction for focus purposes
fn convert_direction(source: Direction) -> Relative {
match source {
Direction::Abs(abs) => match abs {
Absolute::Up | Absolute::Left => Front,
Absolute::Down | Absolute::Right => Back,
Absolute::None => Front,
},
Direction::Rel(rel) => rel,
}
}
fn add_path_to_collector(term: &mut Cursive, unique_id: &str, filters: &FilterOptions) {
let unique_id = unique_id.to_string();
let fp = FilePicker::new(enclose! {(unique_id, filters) move |term, path| {
match filters.matches(path) {
Ok(()) => {
term.find_name::<SelectView<PathBuf>>(&unique_id).unwrap().add_item(path.to_str().unwrap().to_owned(), path.to_path_buf());
},
Err(error) => term.add_layer(PaddedDialog::error(error))
}
}})
.filters(filters.clone());
match fp.load(&dirs::home_dir().unwrap()) {
Ok(picker) => term.add_layer(picker),
Err(error) => term.add_layer(PaddedDialog::error(error)),
}
}
fn delete_path_from_collector(term: &mut Cursive, unique_id: &str) {
let mut selector = term.find_name::<SelectView<PathBuf>>(unique_id).unwrap();
match selector.selected_id() {
Some(id) => {
selector.remove_item(id);
}
None => (),
}
} |
Beta Was this translation helpful? Give feedback.
All reactions
-
Aah I think I found the issue: you're calling
The issue here is that the So the solution is to not call I sent a merge request with that, and some other minor changes. |
Beta Was this translation helpful? Give feedback.
-
Hi all. I'm trying to build a custom view that allows a user to select multiple paths from their file system and displays them in a select view. The buttons are properly adding and removing items from the select view, and when there are multiple items in the select view, pressing up and down to move through them works as expected. What isn't working is that the scroll view position isn't updating.
. In the screen shot, "Test 1", and "Test 2" are items in the select list. Not shown because it's outside the viewable area is a "Test 3" which is currently the Selected item in the select view, nor the "Test 4" - "Test 9" entries below that. I'm passing any events I don't handle to the focused child view of my view so I don't think I'm missing some event that I should be sending on, but I'm not sure then why it's not updating as I scroll down with the arrow keys.
Here's the relevant code for the view:
Beta Was this translation helpful? Give feedback.
All reactions