From 7fd86b69ec7519cff41367f16fe3c5cec59d1981 Mon Sep 17 00:00:00 2001 From: Alexandre Bury Date: Fri, 5 Mar 2021 12:51:40 -0800 Subject: [PATCH] Add more control to focus changes. - Add `Event::FocusLost` - View groups: send a "focus lost" event to the focused child when the focus is changing. - Change return type of `View::take_focus` from `bool` to `Result`. - Change return type of `View::focus_view` from `Result<(), ViewNotFound>` to `Result`. - Add `views::FocusTracker` to run callbacks on focus gain/loss. --- cursive-core/src/cursive.rs | 24 +++-- cursive-core/src/event.rs | 8 ++ cursive-core/src/view/mod.rs | 2 +- cursive-core/src/view/view_trait.rs | 33 ++++-- cursive-core/src/view/view_wrapper.rs | 18 ++-- cursive-core/src/views/button.rs | 24 +++-- cursive-core/src/views/canvas.rs | 29 +++-- cursive-core/src/views/checkbox.rs | 22 ++-- cursive-core/src/views/circular_focus.rs | 58 +++++----- cursive-core/src/views/dialog.rs | 120 +++++++++++++-------- cursive-core/src/views/edit_view.rs | 24 +++-- cursive-core/src/views/fixed_layout.rs | 131 +++++++++++++---------- cursive-core/src/views/focus_tracker.rs | 63 +++++++++++ cursive-core/src/views/linear_layout.rs | 107 +++++++++--------- cursive-core/src/views/list_view.rs | 118 ++++++++++++-------- cursive-core/src/views/menubar.rs | 27 ++--- cursive-core/src/views/mod.rs | 2 + cursive-core/src/views/named_view.rs | 6 +- cursive-core/src/views/radio.rs | 21 ++-- cursive-core/src/views/screens_view.rs | 8 +- cursive-core/src/views/scroll_view.rs | 38 ++++--- cursive-core/src/views/select_view.rs | 45 +++++--- cursive-core/src/views/slider_view.rs | 21 ++-- cursive-core/src/views/stack_view.rs | 38 ++++--- cursive-core/src/views/text_area.rs | 24 +++-- examples/src/bin/dialog.rs | 17 +-- examples/src/bin/focus.rs | 42 ++++++++ examples/src/bin/mines/main.rs | 22 ++-- 28 files changed, 699 insertions(+), 393 deletions(-) create mode 100644 cursive-core/src/views/focus_tracker.rs create mode 100644 examples/src/bin/focus.rs diff --git a/cursive-core/src/cursive.rs b/cursive-core/src/cursive.rs index 40e3fab..f9cb44f 100644 --- a/cursive-core/src/cursive.rs +++ b/cursive-core/src/cursive.rs @@ -285,7 +285,10 @@ impl Cursive { /// Selects the menubar. pub fn select_menubar(&mut self) { - self.menubar.take_focus(direction::Direction::none()); + if let Ok(res) = self.menubar.take_focus(direction::Direction::none()) + { + res.process(self); + } } /// Sets the menubar autohide feature. @@ -306,18 +309,18 @@ impl Cursive { /// # use cursive_core::{Cursive, event}; /// # use cursive_core::views::{Dialog}; /// # use cursive_core::traits::*; - /// # use cursive_core::menu::*; + /// # use cursive_core::menu; /// # /// let mut siv = Cursive::new(); /// /// siv.menubar() /// .add_subtree( /// "File", - /// MenuTree::new() + /// menu::Tree::new() /// .leaf("New", |s| s.add_layer(Dialog::info("New file!"))) /// .subtree( /// "Recent", - /// MenuTree::new().with(|tree| { + /// menu::Tree::new().with(|tree| { /// for i in 1..100 { /// tree.add_leaf(format!("Item {}", i), |_| ()) /// } @@ -334,10 +337,10 @@ impl Cursive { /// ) /// .add_subtree( /// "Help", - /// MenuTree::new() + /// menu::Tree::new() /// .subtree( /// "Help", - /// MenuTree::new() + /// menu::Tree::new() /// .leaf("General", |s| { /// s.add_layer(Dialog::info("Help message!")) /// }) @@ -618,13 +621,16 @@ impl Cursive { /// Moves the focus to the view identified by `name`. /// /// Convenient method to call `focus` with a [`view::Selector::Name`]. - pub fn focus_name(&mut self, name: &str) -> Result<(), ViewNotFound> { + pub fn focus_name( + &mut self, + name: &str, + ) -> Result { self.focus(&view::Selector::Name(name)) } /// Same as [`focus_name`](Cursive::focus_name). #[deprecated(note = "`focus_id` is being renamed to `focus_name`")] - pub fn focus_id(&mut self, id: &str) -> Result<(), ViewNotFound> { + pub fn focus_id(&mut self, id: &str) -> Result { self.focus(&view::Selector::Name(id)) } @@ -632,7 +638,7 @@ impl Cursive { pub fn focus( &mut self, sel: &view::Selector<'_>, - ) -> Result<(), ViewNotFound> { + ) -> Result { self.root.focus_view(sel) } diff --git a/cursive-core/src/event.rs b/cursive-core/src/event.rs index 2790504..70c28cd 100644 --- a/cursive-core/src/event.rs +++ b/cursive-core/src/event.rs @@ -253,6 +253,11 @@ impl EventResult { EventResult::Consumed(Some(Callback::from_fn(f))) } + /// Convenient method to create `Consumed(None)` + pub fn consumed() -> Self { + EventResult::Consumed(None) + } + /// Returns `true` if `self` is `EventResult::Consumed`. pub fn is_consumed(&self) -> bool { matches!(*self, EventResult::Consumed(_)) @@ -469,6 +474,9 @@ pub enum Event { /// Event fired when the window is resized. WindowResize, + /// Event fired when the view is about to lose focus. + FocusLost, + /// Event fired regularly when a auto-refresh is set. Refresh, diff --git a/cursive-core/src/view/mod.rs b/cursive-core/src/view/mod.rs index f3beac6..dce8d2c 100644 --- a/cursive-core/src/view/mod.rs +++ b/cursive-core/src/view/mod.rs @@ -115,7 +115,7 @@ pub use self::scroll_base::ScrollBase; pub use self::scrollable::Scrollable; pub use self::size_cache::SizeCache; pub use self::size_constraint::SizeConstraint; -pub use self::view_trait::{View, ViewNotFound}; +pub use self::view_trait::{CannotFocus, View, ViewNotFound}; pub use self::view_wrapper::ViewWrapper; #[deprecated(note = "`Boxable` is being renamed to `Resizable`")] diff --git a/cursive-core/src/view/view_trait.rs b/cursive-core/src/view/view_trait.rs index 73b795a..2cae69a 100644 --- a/cursive-core/src/view/view_trait.rs +++ b/cursive-core/src/view/view_trait.rs @@ -10,12 +10,22 @@ use std::any::Any; #[derive(Debug)] pub struct ViewNotFound; +/// Error indicating a view could not take focus. +#[derive(Debug)] +pub struct CannotFocus; + impl std::fmt::Display for ViewNotFound { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "View could not be found") } } +impl std::fmt::Display for CannotFocus { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "View does not take focus") + } +} + impl std::error::Error for ViewNotFound {} /// Main trait defining a view behaviour. @@ -99,23 +109,32 @@ pub trait View: Any + AnyView { /// Moves the focus to the view identified by the given selector. /// - /// Returns `Ok(())` if the view was found and selected. + /// Returns `Ok(_)` if the view was found and selected. + /// A callback may be included, it should be run on `&mut Cursive`. /// - /// Default implementation simply returns `Err(())`. - fn focus_view(&mut self, _: &Selector<'_>) -> Result<(), ViewNotFound> { + /// Default implementation simply returns `Err(ViewNotFound)`. + fn focus_view( + &mut self, + _: &Selector<'_>, + ) -> Result { Err(ViewNotFound) } - /// This view is offered focus. Will it take it? + /// Attempt to give this view the focus. /// /// `source` indicates where the focus comes from. /// When the source is unclear (for example mouse events), /// `Direction::none()` can be used. /// - /// Default implementation always return `false`. - fn take_focus(&mut self, source: Direction) -> bool { + /// Returns `Ok(_)` if the focus was taken. + /// Returns `Err(_)` if this view does not take focus (default implementation). + fn take_focus( + &mut self, + source: Direction, + ) -> Result { let _ = source; - false + + Err(CannotFocus) } /// What part of the view is important and should be visible? diff --git a/cursive-core/src/view/view_wrapper.rs b/cursive-core/src/view/view_wrapper.rs index 3900a40..9ee8168 100644 --- a/cursive-core/src/view/view_wrapper.rs +++ b/cursive-core/src/view/view_wrapper.rs @@ -2,7 +2,7 @@ use crate::{ direction::Direction, event::{AnyCb, Event, EventResult}, rect::Rect, - view::{Selector, View, ViewNotFound}, + view::{CannotFocus, Selector, View, ViewNotFound}, Printer, Vec2, }; @@ -70,9 +70,12 @@ pub trait ViewWrapper: 'static { } /// Wraps the `take_focus` method. - fn wrap_take_focus(&mut self, source: Direction) -> bool { + fn wrap_take_focus( + &mut self, + source: Direction, + ) -> Result { self.with_view_mut(|v| v.take_focus(source)) - .unwrap_or(false) + .unwrap_or(Err(CannotFocus)) } /// Wraps the `find` method. @@ -88,7 +91,7 @@ pub trait ViewWrapper: 'static { fn wrap_focus_view( &mut self, selector: &Selector<'_>, - ) -> Result<(), ViewNotFound> { + ) -> Result { self.with_view_mut(|v| v.focus_view(selector)) .unwrap_or(Err(ViewNotFound)) } @@ -123,7 +126,10 @@ impl View for T { self.wrap_layout(size); } - fn take_focus(&mut self, source: Direction) -> bool { + fn take_focus( + &mut self, + source: Direction, + ) -> Result { self.wrap_take_focus(source) } @@ -142,7 +148,7 @@ impl View for T { fn focus_view( &mut self, selector: &Selector<'_>, - ) -> Result<(), ViewNotFound> { + ) -> Result { self.wrap_focus_view(selector) } diff --git a/cursive-core/src/views/button.rs b/cursive-core/src/views/button.rs index ec8d8bd..459444f 100644 --- a/cursive-core/src/views/button.rs +++ b/cursive-core/src/views/button.rs @@ -1,11 +1,12 @@ -use crate::align::HAlign; -use crate::direction::Direction; -use crate::event::*; -use crate::rect::Rect; -use crate::theme::ColorStyle; -use crate::view::View; -use crate::Vec2; -use crate::{Cursive, Printer}; +use crate::{ + align::HAlign, + direction::Direction, + event::*, + rect::Rect, + theme::ColorStyle, + view::{CannotFocus, View}, + Cursive, Printer, Vec2, +}; use unicode_width::UnicodeWidthStr; /// Simple text label with a callback when is pressed. @@ -185,8 +186,11 @@ impl View for Button { } } - fn take_focus(&mut self, _: Direction) -> bool { - self.enabled + fn take_focus( + &mut self, + _: Direction, + ) -> Result { + self.enabled.then(EventResult::consumed).ok_or(CannotFocus) } fn important_area(&self, view_size: Vec2) -> Rect { diff --git a/cursive-core/src/views/canvas.rs b/cursive-core/src/views/canvas.rs index 26c8ffa..9d88fa0 100644 --- a/cursive-core/src/views/canvas.rs +++ b/cursive-core/src/views/canvas.rs @@ -2,7 +2,7 @@ use crate::{ direction::Direction, event::{AnyCb, Event, EventResult}, rect::Rect, - view::{Selector, View, ViewNotFound}, + view::{CannotFocus, Selector, View, ViewNotFound}, Printer, Vec2, With, }; @@ -49,9 +49,11 @@ pub struct Canvas { on_event: Box EventResult>, required_size: Box Vec2>, layout: Box, - take_focus: Box bool>, + take_focus: + Box Result>, needs_relayout: Box bool>, - focus_view: Box Result<(), ViewNotFound>>, + focus_view: + Box Result>, call_on_any: CallOnAny, important_area: Box Rect>, } @@ -83,7 +85,7 @@ impl Canvas { on_event: Box::new(|_, _| EventResult::Ignored), required_size: Box::new(|_, _| Vec2::new(1, 1)), layout: Box::new(|_, _| ()), - take_focus: Box::new(|_, _| false), + take_focus: Box::new(|_, _| Err(CannotFocus)), needs_relayout: Box::new(|_| true), focus_view: Box::new(|_, _| Err(ViewNotFound)), call_on_any: Box::new(|_, _, _| ()), @@ -173,7 +175,8 @@ impl Canvas { /// Sets the closure for `take_focus(Direction)`. pub fn set_take_focus(&mut self, f: F) where - F: 'static + FnMut(&mut T, Direction) -> bool, + F: 'static + + FnMut(&mut T, Direction) -> Result, { self.take_focus = Box::new(f); } @@ -183,7 +186,8 @@ impl Canvas { /// Chainable variant. pub fn with_take_focus(self, f: F) -> Self where - F: 'static + FnMut(&mut T, Direction) -> bool, + F: 'static + + FnMut(&mut T, Direction) -> Result, { self.with(|s| s.set_take_focus(f)) } @@ -245,7 +249,8 @@ impl Canvas { /// Sets the closure for `focus_view()`. pub fn set_focus_view(&mut self, f: F) where - F: 'static + FnMut(&mut T, &Selector<'_>) -> Result<(), ViewNotFound>, + F: 'static + + FnMut(&mut T, &Selector<'_>) -> Result, { self.focus_view = Box::new(f); } @@ -255,7 +260,8 @@ impl Canvas { /// Chainable variant. pub fn with_focus_view(self, f: F) -> Self where - F: 'static + FnMut(&mut T, &Selector<'_>) -> Result<(), ViewNotFound>, + F: 'static + + FnMut(&mut T, &Selector<'_>) -> Result, { self.with(|s| s.set_focus_view(f)) } @@ -278,7 +284,10 @@ impl View for Canvas { (self.layout)(&mut self.state, size); } - fn take_focus(&mut self, source: Direction) -> bool { + fn take_focus( + &mut self, + source: Direction, + ) -> Result { (self.take_focus)(&mut self.state, source) } @@ -289,7 +298,7 @@ impl View for Canvas { fn focus_view( &mut self, selector: &Selector<'_>, - ) -> Result<(), ViewNotFound> { + ) -> Result { (self.focus_view)(&mut self.state, selector) } diff --git a/cursive-core/src/views/checkbox.rs b/cursive-core/src/views/checkbox.rs index a0f00f1..88d4f65 100644 --- a/cursive-core/src/views/checkbox.rs +++ b/cursive-core/src/views/checkbox.rs @@ -1,11 +1,10 @@ -use crate::direction::Direction; -use crate::event::{Event, EventResult, Key, MouseButton, MouseEvent}; -use crate::theme::ColorStyle; -use crate::view::View; -use crate::Cursive; -use crate::Printer; -use crate::Vec2; -use crate::With; +use crate::{ + direction::Direction, + event::{Event, EventResult, Key, MouseButton, MouseEvent}, + theme::ColorStyle, + view::{CannotFocus, View}, + Cursive, Printer, Vec2, With, +}; use std::rc::Rc; /// Checkable box. @@ -141,8 +140,11 @@ impl View for Checkbox { Vec2::new(3, 1) } - fn take_focus(&mut self, _: Direction) -> bool { - self.enabled + fn take_focus( + &mut self, + _: Direction, + ) -> Result { + self.enabled.then(EventResult::consumed).ok_or(CannotFocus) } fn draw(&self, printer: &Printer) { diff --git a/cursive-core/src/views/circular_focus.rs b/cursive-core/src/views/circular_focus.rs index 3032fbb..73b22d8 100644 --- a/cursive-core/src/views/circular_focus.rs +++ b/cursive-core/src/views/circular_focus.rs @@ -54,6 +54,16 @@ impl CircularFocus { self.wrap_arrows } + /// Make this view now wrap focus around when the Tab key is pressed. + pub fn set_wrap_tab(&mut self, wrap_tab: bool) { + self.wrap_tab = wrap_tab; + } + + /// Make this view now wrap focus around when arrow keys are pressed. + pub fn set_wrap_arrows(&mut self, wrap_arrows: bool) { + self.wrap_arrows = wrap_arrows; + } + inner_getters!(self.view: T); } @@ -64,61 +74,49 @@ impl ViewWrapper for CircularFocus { match (self.view.on_event(event.clone()), event) { (EventResult::Ignored, Event::Key(Key::Tab)) if self.wrap_tab => { // Focus comes back! - if self.view.take_focus(Direction::front()) { - EventResult::Consumed(None) - } else { - EventResult::Ignored - } + self.view + .take_focus(Direction::front()) + .unwrap_or(EventResult::Ignored) } (EventResult::Ignored, Event::Shift(Key::Tab)) if self.wrap_tab => { // Focus comes back! - if self.view.take_focus(Direction::back()) { - EventResult::Consumed(None) - } else { - EventResult::Ignored - } + self.view + .take_focus(Direction::back()) + .unwrap_or(EventResult::Ignored) } (EventResult::Ignored, Event::Key(Key::Right)) if self.wrap_arrows => { // Focus comes back! - if self.view.take_focus(Direction::left()) { - EventResult::Consumed(None) - } else { - EventResult::Ignored - } + self.view + .take_focus(Direction::left()) + .unwrap_or(EventResult::Ignored) } (EventResult::Ignored, Event::Key(Key::Left)) if self.wrap_arrows => { // Focus comes back! - if self.view.take_focus(Direction::right()) { - EventResult::Consumed(None) - } else { - EventResult::Ignored - } + self.view + .take_focus(Direction::right()) + .unwrap_or(EventResult::Ignored) } (EventResult::Ignored, Event::Key(Key::Up)) if self.wrap_arrows => { // Focus comes back! - if self.view.take_focus(Direction::down()) { - EventResult::Consumed(None) - } else { - EventResult::Ignored - } + self.view + .take_focus(Direction::down()) + .unwrap_or(EventResult::Ignored) } (EventResult::Ignored, Event::Key(Key::Down)) if self.wrap_arrows => { // Focus comes back! - if self.view.take_focus(Direction::up()) { - EventResult::Consumed(None) - } else { - EventResult::Ignored - } + self.view + .take_focus(Direction::up()) + .unwrap_or(EventResult::Ignored) } (other, _) => other, } diff --git a/cursive-core/src/views/dialog.rs b/cursive-core/src/views/dialog.rs index 1d0cea2..b00d8e6 100644 --- a/cursive-core/src/views/dialog.rs +++ b/cursive-core/src/views/dialog.rs @@ -5,7 +5,9 @@ use crate::{ rect::Rect, theme::ColorStyle, utils::markup::StyledString, - view::{IntoBoxedView, Margins, Selector, View, ViewNotFound}, + view::{ + CannotFocus, IntoBoxedView, Margins, Selector, View, ViewNotFound, + }, views::{BoxedView, Button, DummyView, LastSizeView, TextView}, Cursive, Printer, Vec2, With, }; @@ -227,11 +229,17 @@ impl Dialog { } /// Removes any button from `self`. - pub fn clear_buttons(&mut self) { + pub fn clear_buttons(&mut self) -> EventResult { self.buttons.clear(); self.invalidate(); - self.content.take_focus(Direction::none()); - self.focus = DialogFocus::Content; + if self.focus != DialogFocus::Content { + self.focus = DialogFocus::Content; + self.content + .take_focus(Direction::none()) + .unwrap_or(EventResult::Ignored) + } else { + EventResult::Ignored + } } /// Removes a button from this dialog. @@ -239,21 +247,24 @@ impl Dialog { /// # Panics /// /// Panics if `i >= self.buttons_len()`. - pub fn remove_button(&mut self, i: usize) { + pub fn remove_button(&mut self, i: usize) -> EventResult { self.buttons.remove(i); self.invalidate(); // Fix focus? match (self.buttons.len(), self.focus) { - // TODO: take focus? (0, ref mut focus) => { - self.content.take_focus(Direction::none()); *focus = DialogFocus::Content; + return self + .content + .take_focus(Direction::none()) + .unwrap_or(EventResult::Ignored); } (n, DialogFocus::Button(ref mut i)) => { *i = usize::min(*i, n - 1); } _ => (), } + EventResult::Ignored } /// Sets the horizontal alignment for the buttons, if any. @@ -453,6 +464,7 @@ impl Dialog { DialogFocus::Content } DialogFocus::Button(c) => { + // TODO: send Event::LostFocus? DialogFocus::Button(min(c, self.buttons.len() - 1)) } }; @@ -500,9 +512,11 @@ impl Dialog { match event { // Up goes back to the content Event::Key(Key::Up) => { - if self.content.take_focus(Direction::down()) { + if let Ok(res) = + self.content.take_focus(Direction::down()) + { self.focus = DialogFocus::Content; - EventResult::Consumed(None) + res } else { EventResult::Ignored } @@ -511,9 +525,11 @@ impl Dialog { if self.focus == DialogFocus::Button(0) => { // If we're at the first button, jump back to the content. - if self.content.take_focus(Direction::back()) { + if let Ok(res) = + self.content.take_focus(Direction::back()) + { self.focus = DialogFocus::Content; - EventResult::Consumed(None) + res } else { EventResult::Ignored } @@ -651,7 +667,7 @@ impl Dialog { } } - fn check_focus_grab(&mut self, event: &Event) { + fn check_focus_grab(&mut self, event: &Event) -> Option { if let Event::Mouse { offset, position, @@ -659,11 +675,11 @@ impl Dialog { } = *event { if !event.grabs_focus() { - return; + return None; } let position = match position.checked_sub(offset) { - None => return, + None => return None, Some(pos) => pos, }; @@ -678,12 +694,15 @@ impl Dialog { } else if position.fits_in_rect( (self.padding + self.borders).top_left(), self.content.size, - ) && self.content.take_focus(Direction::none()) - { - // Or did we click the content? - self.focus = DialogFocus::Content; + ) { + if let Ok(res) = self.content.take_focus(Direction::none()) { + // Or did we click the content? + self.focus = DialogFocus::Content; + return Some(res); + } } } + None } fn invalidate(&mut self) { @@ -777,52 +796,67 @@ impl View for Dialog { fn on_event(&mut self, event: Event) -> EventResult { // First: some mouse events can instantly change the focus. - self.check_focus_grab(&event); + let res = self + .check_focus_grab(&event) + .unwrap_or(EventResult::Ignored); - match self.focus { + res.and(match self.focus { // If we are on the content, we can only go down. // TODO: Careful if/when we add buttons elsewhere on the dialog! DialogFocus::Content => self.on_event_content(event), // If we are on a button, we have more choice DialogFocus::Button(i) => self.on_event_button(event, i), - } + }) } - fn take_focus(&mut self, source: Direction) -> bool { + fn take_focus( + &mut self, + source: Direction, + ) -> Result { // TODO: This may depend on button position relative to the content? // match source { Direction::Abs(Absolute::None) => { // Only reject focus if no buttons and no focus-taking content. // Also fix focus if we're focusing the wrong thing. - match ( - self.focus, - self.content.take_focus(source), - !self.buttons.is_empty(), - ) { - (DialogFocus::Content, false, true) => { - self.focus = DialogFocus::Button(0); - true + match (self.focus, !self.buttons.is_empty()) { + (DialogFocus::Button(_), true) => { + // Focus stays on the button. + Ok(EventResult::Consumed(None)) } - (DialogFocus::Button(_), true, false) => { - self.focus = DialogFocus::Content; - true + (DialogFocus::Button(_), false) => { + let res = self.content.take_focus(source); + if res.is_ok() { + self.focus = DialogFocus::Content; + } + res + } + (DialogFocus::Content, false) => { + self.content.take_focus(source) + } + (DialogFocus::Content, true) => { + match self.content.take_focus(source) { + Ok(res) => Ok(res), + Err(CannotFocus) => { + self.focus = DialogFocus::Button(0); + Ok(EventResult::Consumed(None)) + } + } } - (_, content, buttons) => content || buttons, } } Direction::Rel(Relative::Front) | Direction::Abs(Absolute::Left) | Direction::Abs(Absolute::Up) => { // Forward focus: content, then buttons - if self.content.take_focus(source) { + if let Ok(res) = self.content.take_focus(source) { self.focus = DialogFocus::Content; - true + Ok(res) } else if self.buttons.is_empty() { - false + Err(CannotFocus) } else { self.focus = DialogFocus::Button(0); - true + Ok(EventResult::Consumed(None)) } } Direction::Rel(Relative::Back) @@ -831,12 +865,12 @@ impl View for Dialog { // Back focus: first buttons, then content if !self.buttons.is_empty() { self.focus = DialogFocus::Button(self.buttons.len() - 1); - true - } else if self.content.take_focus(source) { + Ok(EventResult::Consumed(None)) + } else if let Ok(res) = self.content.take_focus(source) { self.focus = DialogFocus::Content; - true + Ok(res) } else { - false + Err(CannotFocus) } } } @@ -853,7 +887,7 @@ impl View for Dialog { fn focus_view( &mut self, selector: &Selector<'_>, - ) -> Result<(), ViewNotFound> { + ) -> Result { self.content.focus_view(selector) } diff --git a/cursive-core/src/views/edit_view.rs b/cursive-core/src/views/edit_view.rs index f11e5f9..ed712ad 100644 --- a/cursive-core/src/views/edit_view.rs +++ b/cursive-core/src/views/edit_view.rs @@ -1,11 +1,12 @@ -use crate::direction::Direction; -use crate::event::{Callback, Event, EventResult, Key, MouseEvent}; -use crate::rect::Rect; -use crate::theme::{ColorStyle, Effect}; -use crate::utils::lines::simple::{simple_prefix, simple_suffix}; -use crate::view::View; -use crate::Vec2; -use crate::{Cursive, Printer, With}; +use crate::{ + direction::Direction, + event::{Callback, Event, EventResult, Key, MouseEvent}, + rect::Rect, + theme::{ColorStyle, Effect}, + utils::lines::simple::{simple_prefix, simple_suffix}, + view::{CannotFocus, View}, + Cursive, Printer, Vec2, With, +}; use std::cell::RefCell; use std::rc::Rc; use unicode_segmentation::UnicodeSegmentation; @@ -596,8 +597,11 @@ impl View for EditView { self.last_length = size.x; } - fn take_focus(&mut self, _: Direction) -> bool { - self.enabled + fn take_focus( + &mut self, + _: Direction, + ) -> Result { + self.enabled.then(EventResult::consumed).ok_or(CannotFocus) } fn on_event(&mut self, event: Event) -> EventResult { diff --git a/cursive-core/src/views/fixed_layout.rs b/cursive-core/src/views/fixed_layout.rs index 2edf65b..f3250d3 100644 --- a/cursive-core/src/views/fixed_layout.rs +++ b/cursive-core/src/views/fixed_layout.rs @@ -2,7 +2,7 @@ use crate::{ direction::{Absolute, Direction, Relative}, event::{AnyCb, Event, EventResult, Key}, rect::Rect, - view::{IntoBoxedView, Selector, ViewNotFound}, + view::{CannotFocus, IntoBoxedView, Selector, ViewNotFound}, {Printer, Vec2, View, With}, }; @@ -39,6 +39,15 @@ struct Child { position: Rect, } +impl Child { + // Convenient function to look for a focusable child in an iterator. + fn focuser( + source: Direction, + ) -> impl Fn((usize, &mut Self)) -> Option<(usize, EventResult)> { + move |(i, c)| c.view.take_focus(source).ok().map(|res| (i, res)) + } +} + new_default!(FixedLayout); impl FixedLayout { @@ -75,17 +84,22 @@ impl FixedLayout { pub fn set_focus_index( &mut self, index: usize, - ) -> Result<(), ViewNotFound> { - if self - .children + ) -> Result { + self.children .get_mut(index) - .map(|child| child.view.take_focus(Direction::none())) - .unwrap_or(false) - { + .and_then(|child| child.view.take_focus(Direction::none()).ok()) + .map(|res| self.set_focus_unchecked(index).and(res)) + .ok_or(ViewNotFound) + } + + fn set_focus_unchecked(&mut self, index: usize) -> EventResult { + if index != self.focus { + let result = + self.children[self.focus].view.on_event(Event::FocusLost); self.focus = index; - Ok(()) + result } else { - Err(ViewNotFound) + EventResult::Consumed(None) } } @@ -183,13 +197,12 @@ impl FixedLayout { fn move_focus_rel(&mut self, target: Relative) -> EventResult { let source = Direction::Rel(target.swap()); - for (i, c) in - Self::iter_mut(source, &mut self.children).skip(self.focus + 1) - { - if c.view.take_focus(source) { - self.focus = i; - return EventResult::Consumed(None); - } + let focus_res = Self::iter_mut(source, &mut self.children) + .skip(self.focus + 1) + .find_map(Child::focuser(source)); + + if let Some((i, res)) = focus_res { + return self.set_focus_unchecked(i).and(res); } EventResult::Ignored @@ -207,8 +220,8 @@ impl FixedLayout { let current_side = current_position.side(orientation.swap()); let current_edge = current_position.edge(target); - let children = - Self::iter_mut(source, &mut self.children).filter(|(_, c)| { + let focus_res = Self::iter_mut(source, &mut self.children) + .filter(|(_, c)| { // Only select children actually aligned with us Some(rel) == Relative::a_to_b(current_edge, c.position.edge(target)) @@ -216,19 +229,16 @@ impl FixedLayout { c.position.side(orientation.swap()), current_side, ) - }); + }) + .find_map(Child::focuser(source)); - for (i, c) in children { - if c.view.take_focus(source) { - self.focus = i; - return EventResult::Consumed(None); - } + if let Some((i, res)) = focus_res { + return self.set_focus_unchecked(i).and(res); } - EventResult::Ignored } - fn check_focus_grab(&mut self, event: &Event) { + fn check_focus_grab(&mut self, event: &Event) -> Option { if let Event::Mouse { offset, position, @@ -236,22 +246,26 @@ impl FixedLayout { } = *event { if !event.grabs_focus() { - return; + return None; } let position = match position.checked_sub(offset) { - None => return, + None => return None, Some(pos) => pos, }; - for (i, child) in self.children.iter_mut().enumerate() { - if child.position.contains(position) - && child.view.take_focus(Direction::none()) - { - self.focus = i; - } + if let Some((i, res)) = self + .children + .iter_mut() + .enumerate() + .filter(|(_, c)| c.position.contains(position)) + .find_map(Child::focuser(Direction::none())) + { + return Some(self.set_focus_unchecked(i).and(res)); } } + + None } } @@ -274,7 +288,9 @@ impl View for FixedLayout { return EventResult::Ignored; } - self.check_focus_grab(&event); + let res = self + .check_focus_grab(&event) + .unwrap_or(EventResult::Ignored); let child = &mut self.children[self.focus]; @@ -282,7 +298,7 @@ impl View for FixedLayout { .view .on_event(event.relativized(child.position.top_left())); - match result { + res.and(match result { EventResult::Ignored => match event { Event::Shift(Key::Tab) => self.move_focus_rel(Relative::Front), Event::Key(Key::Tab) => self.move_focus_rel(Relative::Back), @@ -293,7 +309,7 @@ impl View for FixedLayout { _ => EventResult::Ignored, }, res => res, - } + }) } fn important_area(&self, size: Vec2) -> Rect { @@ -314,33 +330,33 @@ impl View for FixedLayout { .fold(Vec2::zero(), Vec2::max) } - fn take_focus(&mut self, source: Direction) -> bool { + fn take_focus( + &mut self, + source: Direction, + ) -> Result { match source { Direction::Abs(Absolute::None) => { // We want to guarantee: // * If the current focus _is_ focusable, keep it // * If it isn't, find _any_ focusable view, and focus it // * Otherwise, we can't take focus. - for (i, c) in + let focus_res = Self::circular_mut(self.focus, &mut self.children) - { - if c.view.take_focus(source) { - self.focus = i; - return true; - } + .find_map(Child::focuser(source)); + if let Some((i, res)) = focus_res { + return Ok(self.set_focus_unchecked(i).and(res)); } - false + Err(CannotFocus) } source => { - for (i, c) in Self::iter_mut(source, &mut self.children) { - if c.view.take_focus(source) { - self.focus = i; - return true; - } + let focus_res = Self::iter_mut(source, &mut self.children) + .find_map(Child::focuser(source)); + if let Some((i, res)) = focus_res { + return Ok(self.set_focus_unchecked(i).and(res)); } - false + Err(CannotFocus) } } } @@ -358,12 +374,13 @@ impl View for FixedLayout { fn focus_view( &mut self, selector: &Selector<'_>, - ) -> Result<(), ViewNotFound> { - for (i, child) in self.children.iter_mut().enumerate() { - if child.view.focus_view(selector).is_ok() { - self.focus = i; - return Ok(()); - } + ) -> Result { + let focus_res = + self.children.iter_mut().enumerate().find_map(|(i, c)| { + c.view.focus_view(selector).ok().map(|res| (i, res)) + }); + if let Some((i, res)) = focus_res { + return Ok(self.set_focus_unchecked(i).and(res)); } Err(ViewNotFound) diff --git a/cursive-core/src/views/focus_tracker.rs b/cursive-core/src/views/focus_tracker.rs new file mode 100644 index 0000000..698fbb3 --- /dev/null +++ b/cursive-core/src/views/focus_tracker.rs @@ -0,0 +1,63 @@ +use crate::{ + direction::Direction, + event::{Event, EventResult}, + view::{CannotFocus, View, ViewWrapper}, + With, +}; + +/// Detects focus events for a view. +pub struct FocusTracker { + view: T, + on_focus_lost: Box EventResult>, + on_focus: Box EventResult>, +} + +impl FocusTracker { + /// Wraps a view in a new `FocusTracker`. + pub fn new(view: T) -> Self { + FocusTracker { + view, + on_focus_lost: Box::new(|_| EventResult::Ignored), + on_focus: Box::new(|_| EventResult::Ignored), + } + } + + /// Sets a callback to be run when the focus is gained. + pub fn on_focus(self, f: F) -> Self + where + F: 'static + FnMut(&mut T) -> EventResult, + { + self.with(|s| s.on_focus = Box::new(f)) + } + + /// Sets a callback to be run when the focus is lost. + pub fn on_focus_lost(self, f: F) -> Self + where + F: 'static + FnMut(&mut T) -> EventResult, + { + self.with(|s| s.on_focus_lost = Box::new(f)) + } +} + +impl ViewWrapper for FocusTracker { + wrap_impl!(self.view: T); + + fn wrap_take_focus( + &mut self, + source: Direction, + ) -> Result { + match self.view.take_focus(source) { + Ok(res) => Ok(res.and((self.on_focus)(&mut self.view))), + Err(CannotFocus) => Err(CannotFocus), + } + } + + fn wrap_on_event(&mut self, event: Event) -> EventResult { + let res = if let Event::FocusLost = event { + (self.on_focus_lost)(&mut self.view) + } else { + EventResult::Ignored + }; + res.and(self.view.on_event(event)) + } +} diff --git a/cursive-core/src/views/linear_layout.rs b/cursive-core/src/views/linear_layout.rs index 977b0a4..99af62f 100644 --- a/cursive-core/src/views/linear_layout.rs +++ b/cursive-core/src/views/linear_layout.rs @@ -1,8 +1,11 @@ +/// Event fired when the view is about to lose focus. use crate::{ direction, event::{AnyCb, Event, EventResult, Key}, rect::Rect, - view::{IntoBoxedView, Selector, SizeCache, View, ViewNotFound}, + view::{ + CannotFocus, IntoBoxedView, Selector, SizeCache, View, ViewNotFound, + }, Printer, Vec2, With, XY, }; use log::debug; @@ -230,17 +233,24 @@ impl LinearLayout { pub fn set_focus_index( &mut self, index: usize, - ) -> Result<(), ViewNotFound> { - if self - .children + ) -> Result { + self.children .get_mut(index) - .map(|child| child.view.take_focus(direction::Direction::none())) - .unwrap_or(false) - { + .and_then(|child| { + child.view.take_focus(direction::Direction::none()).ok() + }) + .map(|res| res.and(self.set_focus_unchecked(index))) + .ok_or(ViewNotFound) + } + + fn set_focus_unchecked(&mut self, index: usize) -> EventResult { + if index != self.focus { + let result = + self.children[self.focus].view.on_event(Event::FocusLost); self.focus = index; - Ok(()) + result } else { - Err(ViewNotFound) + EventResult::Consumed(None) } } @@ -373,19 +383,17 @@ impl LinearLayout { // We don't want that one. self.iter_mut(true, rel) .skip(1) - .filter_map(|p| try_focus(p, source)) - .next() + .find_map(|p| try_focus(p, source)) }) - .map_or(EventResult::Ignored, |i| { - self.focus = i; - EventResult::Consumed(None) + .map_or(EventResult::Ignored, |(i, res)| { + res.and(self.set_focus_unchecked(i)) }) } // Move the focus to the selected view if needed. // // Does nothing if the event is not a `MouseEvent`. - fn check_focus_grab(&mut self, event: &Event) { + fn check_focus_grab(&mut self, event: &Event) -> Option { if let Event::Mouse { offset, position, @@ -393,11 +401,11 @@ impl LinearLayout { } = *event { if !event.grabs_focus() { - return; + return None; } let position = match position.checked_sub(offset) { - None => return, + None => return None, Some(pos) => pos, }; @@ -419,27 +427,27 @@ impl LinearLayout { // this will give us the allowed window for a click. let child_size = item.child.last_size.get(self.orientation); - if item.offset + child_size > position { - if item.child.view.take_focus(direction::Direction::none()) - { - self.focus = i; - } - return; + if item.offset + child_size <= position { + continue; } + + return item + .child + .view + .take_focus(direction::Direction::none()) + .ok() + .map(|res| res.and(self.set_focus_unchecked(i))); } } + None } } fn try_focus( (i, child): (usize, &mut Child), source: direction::Direction, -) -> Option { - if child.view.take_focus(source) { - Some(i) - } else { - None - } +) -> Option<(usize, EventResult)> { + child.view.take_focus(source).ok().map(|res| (i, res)) } impl View for LinearLayout { @@ -623,24 +631,24 @@ impl View for LinearLayout { compromise } - fn take_focus(&mut self, source: direction::Direction) -> bool { + fn take_focus( + &mut self, + source: direction::Direction, + ) -> Result { // In what order will we iterate on the children? let rel = source.relative(self.orientation); - // We activate from_focus only if coming from the "sides". - let mut get_next_focus = || { - self.iter_mut( - rel.is_none(), - rel.unwrap_or(direction::Relative::Front), - ) - .filter_map(|p| try_focus(p, source)) - .next() - }; - if let Some(i) = get_next_focus() { - self.focus = i; - true + // We activate from_focus only if coming from the "sides". + let focus_res = self + .iter_mut(rel.is_none(), rel.unwrap_or(direction::Relative::Front)) + .find_map(|p| try_focus(p, source)); + + if let Some((next_focus, res)) = focus_res { + // No "FocusLost" here, since we didn't have focus before. + self.focus = next_focus; + Ok(res) } else { - false + Err(CannotFocus) } } @@ -649,7 +657,9 @@ impl View for LinearLayout { return EventResult::Ignored; } - self.check_focus_grab(&event); + let res = self + .check_focus_grab(&event) + .unwrap_or(EventResult::Ignored); let result = { let mut iterator = ChildIterator::new( @@ -661,7 +671,7 @@ impl View for LinearLayout { let offset = self.orientation.make_vec(item.offset, 0); item.child.view.on_event(event.relativized(offset)) }; - match result { + res.and(match result { EventResult::Ignored => match event { Event::Shift(Key::Tab) if self.focus > 0 => { self.move_focus(direction::Direction::back()) @@ -702,7 +712,7 @@ impl View for LinearLayout { _ => EventResult::Ignored, }, res => res, - } + }) } fn call_on_any<'a>( @@ -718,11 +728,10 @@ impl View for LinearLayout { fn focus_view( &mut self, selector: &Selector<'_>, - ) -> Result<(), ViewNotFound> { + ) -> Result { for (i, child) in self.children.iter_mut().enumerate() { if child.view.focus_view(selector).is_ok() { - self.focus = i; - return Ok(()); + return Ok(self.set_focus_unchecked(i)); } } diff --git a/cursive-core/src/views/list_view.rs b/cursive-core/src/views/list_view.rs index 899f707..31ea516 100644 --- a/cursive-core/src/views/list_view.rs +++ b/cursive-core/src/views/list_view.rs @@ -2,7 +2,7 @@ use crate::{ direction, event::{AnyCb, Callback, Event, EventResult, Key}, rect::Rect, - view::{IntoBoxedView, Selector, View, ViewNotFound}, + view::{CannotFocus, IntoBoxedView, Selector, View, ViewNotFound}, Cursive, Printer, Vec2, With, }; use log::debug; @@ -96,8 +96,10 @@ impl ListView { label: &str, view: V, ) { - let mut view = view.into_boxed_view(); - view.take_focus(direction::Direction::none()); + let view = view.into_boxed_view(); + + // Why were we doing this here? + // view.take_focus(direction::Direction::none()); self.children.push(ListChild::Row(label.to_string(), view)); self.children_heights.push(0); } @@ -106,7 +108,6 @@ impl ListView { pub fn clear(&mut self) { self.children.clear(); self.children_heights.clear(); - self.focus = 0; } /// Adds a view to the end of the list. @@ -190,12 +191,31 @@ impl ListView { } } + fn unfocus_child(&mut self) -> EventResult { + self.children + .get_mut(self.focus) + .and_then(ListChild::view) + .map(|v| v.on_event(Event::FocusLost)) + .unwrap_or(EventResult::Ignored) + } + + // Move focus to the given index, regardless of whether that child accepts focus. + fn set_focus_unchecked(&mut self, index: usize) -> EventResult { + if index != self.focus { + let res = self.unfocus_child(); + self.focus = index; + res + } else { + EventResult::Consumed(None) + } + } + fn move_focus( &mut self, n: usize, source: direction::Direction, ) -> EventResult { - let i = if let Some(i) = source + let (i, res) = if let Some((i, res)) = source .relative(direction::Orientation::Vertical) .and_then(|rel| { // The iterator starts at the focused element. @@ -206,17 +226,17 @@ impl ListView { .take(n) .last() }) { - i + (i, res) } else { return EventResult::Ignored; }; - self.focus = i; + self.set_focus_unchecked(i); - EventResult::Consumed(self.on_select.clone().map(|cb| { + res.and(EventResult::Consumed(self.on_select.clone().map(|cb| { let i = self.focus(); let focused_string = String::from(self.children[i].label()); Callback::from_fn(move |s| cb(s, &focused_string)) - })) + }))) } fn labels_width(&self) -> usize { @@ -228,7 +248,7 @@ impl ListView { .unwrap_or(0) } - fn check_focus_grab(&mut self, event: &Event) { + fn check_focus_grab(&mut self, event: &Event) -> Option { if let Event::Mouse { offset, position, @@ -236,51 +256,56 @@ impl ListView { } = *event { if !event.grabs_focus() { - return; + return None; } let mut position = match position.checked_sub(offset) { - None => return, + None => return None, Some(pos) => pos, }; // eprintln!("Rel pos: {:?}", position); // Now that we have a relative position, checks for buttons? - for (i, (child, height)) in self + for (i, (child, &height)) in self .children .iter_mut() .zip(&self.children_heights) .enumerate() { - if position.y < *height { - if let ListChild::Row(_, ref mut view) = child { - if view.take_focus(direction::Direction::none()) { - self.focus = i; - } - } - break; - } else { - position.y -= height; + if let Some(y) = position.y.checked_sub(height) { + // Not this child. Move on. + position.y = y; + continue; } + + // We found the correct target, try to focus it. + if let ListChild::Row(_, ref mut view) = child { + match view.take_focus(direction::Direction::none()) { + Ok(res) => { + return Some(self.set_focus_unchecked(i).and(res)); + } + Err(CannotFocus) => (), + } + } + // We found the target, but we can't focus it. + break; } } + None } } fn try_focus( (i, child): (usize, &mut ListChild), source: direction::Direction, -) -> Option { +) -> Option<(usize, EventResult)> { match *child { ListChild::Delimiter => None, - ListChild::Row(_, ref mut view) => { - if view.take_focus(source) { - Some(i) - } else { - None - } - } + ListChild::Row(_, ref mut view) => match view.take_focus(source) { + Ok(res) => Some((i, res)), + Err(CannotFocus) => None, + }, } } @@ -367,7 +392,9 @@ impl View for ListView { return EventResult::Ignored; } - self.check_focus_grab(&event); + let res = self + .check_focus_grab(&event) + .unwrap_or(EventResult::Ignored); // Send the event to the focused child. let labels_width = self.labels_width(); @@ -376,12 +403,12 @@ impl View for ListView { let offset = (labels_width + 1, y); let result = view.on_event(event.relativized(offset)); if result.is_consumed() { - return result; + return res.and(result); } } // If the child ignored this event, change the focus. - match event { + res.and(match event { Event::Key(Key::Up) if self.focus > 0 => { self.move_focus(1, direction::Direction::down()) } @@ -405,22 +432,24 @@ impl View for ListView { self.move_focus(1, direction::Direction::back()) } _ => EventResult::Ignored, - } + }) } - fn take_focus(&mut self, source: direction::Direction) -> bool { + fn take_focus( + &mut self, + source: direction::Direction, + ) -> Result { let rel = source.relative(direction::Orientation::Vertical); - let i = if let Some(i) = self + let (i, res) = if let Some((i, res)) = self .iter_mut(rel.is_none(), rel.unwrap_or(direction::Relative::Front)) .find_map(|p| try_focus(p, source)) { - i + (i, res) } else { // No one wants to be in focus - return false; + return Err(CannotFocus); }; - self.focus = i; - true + Ok(self.set_focus_unchecked(i).and(res)) } fn call_on_any<'a>( @@ -436,17 +465,16 @@ impl View for ListView { fn focus_view( &mut self, selector: &Selector<'_>, - ) -> Result<(), ViewNotFound> { + ) -> Result { // Try to focus each view. Skip over delimiters. - if let Some(i) = self + if let Some((i, res)) = self .children .iter_mut() .enumerate() .filter_map(|(i, v)| v.view().map(|v| (i, v))) - .find_map(|(i, v)| v.focus_view(selector).ok().map(|_| i)) + .find_map(|(i, v)| v.focus_view(selector).ok().map(|res| (i, res))) { - self.focus = i; - Ok(()) + Ok(self.set_focus_unchecked(i).and(res)) } else { Err(ViewNotFound) } diff --git a/cursive-core/src/views/menubar.rs b/cursive-core/src/views/menubar.rs index aab851e..4a4e34b 100644 --- a/cursive-core/src/views/menubar.rs +++ b/cursive-core/src/views/menubar.rs @@ -1,13 +1,13 @@ -use crate::direction; -use crate::event::*; -use crate::menu; -use crate::rect::Rect; -use crate::theme::ColorStyle; -use crate::view::{Position, View}; -use crate::views::{MenuPopup, OnEventView}; -use crate::Cursive; -use crate::Printer; -use crate::Vec2; +use crate::{ + direction, + event::*, + menu, + rect::Rect, + theme::ColorStyle, + view::{CannotFocus, Position, View}, + views::{MenuPopup, OnEventView}, + Cursive, Printer, Vec2, +}; use std::rc::Rc; use unicode_width::UnicodeWidthStr; @@ -393,9 +393,12 @@ impl View for Menubar { EventResult::Consumed(None) } - fn take_focus(&mut self, _: direction::Direction) -> bool { + fn take_focus( + &mut self, + _: direction::Direction, + ) -> Result { self.state = State::Selected; - true + Ok(EventResult::consumed()) } fn required_size(&mut self, _: Vec2) -> Vec2 { diff --git a/cursive-core/src/views/mod.rs b/cursive-core/src/views/mod.rs index e257d24..0efa625 100644 --- a/cursive-core/src/views/mod.rs +++ b/cursive-core/src/views/mod.rs @@ -70,6 +70,7 @@ mod dummy; mod edit_view; mod enableable_view; mod fixed_layout; +mod focus_tracker; mod hideable_view; mod last_size_view; mod layer; @@ -108,6 +109,7 @@ pub use self::{ edit_view::EditView, enableable_view::EnableableView, fixed_layout::FixedLayout, + focus_tracker::FocusTracker, hideable_view::HideableView, last_size_view::LastSizeView, layer::Layer, diff --git a/cursive-core/src/views/named_view.rs b/cursive-core/src/views/named_view.rs index f3cca0d..8e3a0eb 100644 --- a/cursive-core/src/views/named_view.rs +++ b/cursive-core/src/views/named_view.rs @@ -1,5 +1,5 @@ use crate::{ - event::AnyCb, + event::{AnyCb, EventResult}, view::{Selector, View, ViewNotFound, ViewWrapper}, }; use owning_ref::{OwningHandle, RcRef}; @@ -111,13 +111,13 @@ impl ViewWrapper for NamedView { fn wrap_focus_view( &mut self, selector: &Selector<'_>, - ) -> Result<(), ViewNotFound> { + ) -> Result { match selector { #[allow(deprecated)] &Selector::Name(name) | &Selector::Id(name) if name == self.name => { - Ok(()) + Ok(EventResult::Consumed(None)) } s => self .view diff --git a/cursive-core/src/views/radio.rs b/cursive-core/src/views/radio.rs index 2dec6cd..0334e0c 100644 --- a/cursive-core/src/views/radio.rs +++ b/cursive-core/src/views/radio.rs @@ -1,10 +1,10 @@ -use crate::direction::Direction; -use crate::event::{Event, EventResult, Key, MouseButton, MouseEvent}; -use crate::theme::ColorStyle; -use crate::view::View; -use crate::Cursive; -use crate::Vec2; -use crate::{Printer, With}; +use crate::{ + direction::Direction, + event::{Event, EventResult, Key, MouseButton, MouseEvent}, + theme::ColorStyle, + view::{CannotFocus, View}, + Cursive, Printer, Vec2, With, +}; use std::cell::RefCell; use std::rc::Rc; @@ -198,8 +198,11 @@ impl View for RadioButton { self.req_size() } - fn take_focus(&mut self, _: Direction) -> bool { - self.enabled + fn take_focus( + &mut self, + _: Direction, + ) -> Result { + self.enabled.then(EventResult::consumed).ok_or(CannotFocus) } fn draw(&self, printer: &Printer) { diff --git a/cursive-core/src/views/screens_view.rs b/cursive-core/src/views/screens_view.rs index ad9a4d8..70cbb3b 100644 --- a/cursive-core/src/views/screens_view.rs +++ b/cursive-core/src/views/screens_view.rs @@ -1,5 +1,5 @@ use crate::{ - event::AnyCb, + event::{AnyCb, EventResult}, view::{Selector, View, ViewNotFound}, views::BoxedView, }; @@ -133,11 +133,11 @@ where fn wrap_focus_view( &mut self, selector: &Selector<'_>, - ) -> Result<(), ViewNotFound> { + ) -> Result { for (i, child) in self.screens.iter_mut().enumerate() { - if child.focus_view(selector).is_ok() { + if let Ok(res) = child.focus_view(selector) { self.active_screen = i; - return Ok(()); + return Ok(res); } } diff --git a/cursive-core/src/views/scroll_view.rs b/cursive-core/src/views/scroll_view.rs index 5a56a8f..9453e07 100644 --- a/cursive-core/src/views/scroll_view.rs +++ b/cursive-core/src/views/scroll_view.rs @@ -1,7 +1,9 @@ use crate::{ direction::Direction, event::{AnyCb, Event, EventResult}, - view::{scroll, ScrollStrategy, Selector, View, ViewNotFound}, + view::{ + scroll, CannotFocus, ScrollStrategy, Selector, View, ViewNotFound, + }, Cursive, Printer, Rect, Vec2, With, }; @@ -364,25 +366,35 @@ where fn focus_view( &mut self, selector: &Selector<'_>, - ) -> Result<(), ViewNotFound> { - self.inner.focus_view(selector).map(|()| { + ) -> Result { + self.inner.focus_view(selector).map(|res| { self.scroll_to_important_area(); + res }) } - fn take_focus(&mut self, source: Direction) -> bool { + fn take_focus( + &mut self, + source: Direction, + ) -> Result { // If the inner view takes focus, re-align the important area. - if self.inner.take_focus(source) { - // Don't do anything if we come from `None` - if source != Direction::none() { - self.scroll_to_important_area(); + match self.inner.take_focus(source) { + Ok(res) => { + // Don't do anything if we come from `None` + if source != Direction::none() { + self.scroll_to_important_area(); - // Note: we can't really return an `EventResult` here :( - self.on_scroll_callback(); + // Note: we can't really return an `EventResult` here :( + self.on_scroll_callback(); + } + Ok(res) } - true - } else { - self.core.is_scrolling().any() + Err(CannotFocus) => self + .core + .is_scrolling() + .any() + .then(|| EventResult::Consumed(None)) + .ok_or(CannotFocus), } } diff --git a/cursive-core/src/views/select_view.rs b/cursive-core/src/views/select_view.rs index 5d5f49e..93d0f7c 100644 --- a/cursive-core/src/views/select_view.rs +++ b/cursive-core/src/views/select_view.rs @@ -1,18 +1,15 @@ -use crate::align::{Align, HAlign, VAlign}; -use crate::direction::Direction; -use crate::event::{ - Callback, Event, EventResult, Key, MouseButton, MouseEvent, +use crate::{ + align::{Align, HAlign, VAlign}, + direction, + event::{Callback, Event, EventResult, Key, MouseButton, MouseEvent}, + menu, + rect::Rect, + theme::ColorStyle, + utils::markup::StyledString, + view::{CannotFocus, Position, View}, + views::MenuPopup, + Cursive, Printer, Vec2, With, }; -use crate::menu; -use crate::rect::Rect; -use crate::theme::ColorStyle; -use crate::utils::markup::StyledString; -use crate::view::{Position, View}; -use crate::views::MenuPopup; -use crate::Cursive; -use crate::Printer; -use crate::Vec2; -use crate::With; use std::borrow::Borrow; use std::cell::Cell; use std::cmp::{min, Ordering}; @@ -962,8 +959,24 @@ impl View for SelectView { } } - fn take_focus(&mut self, _: Direction) -> bool { - self.enabled && !self.items.is_empty() + fn take_focus( + &mut self, + source: direction::Direction, + ) -> Result { + (self.enabled && !self.items.is_empty()) + .then(|| { + match source { + direction::Direction::Abs(direction::Absolute::Up) => { + self.focus.set(0) + } + direction::Direction::Abs(direction::Absolute::Down) => { + self.focus.set(self.items.len().saturating_sub(1)) + } + _ => (), + } + EventResult::Consumed(None) + }) + .ok_or(CannotFocus) } fn layout(&mut self, size: Vec2) { diff --git a/cursive-core/src/views/slider_view.rs b/cursive-core/src/views/slider_view.rs index 886146a..548b6aa 100644 --- a/cursive-core/src/views/slider_view.rs +++ b/cursive-core/src/views/slider_view.rs @@ -1,12 +1,10 @@ -use crate::direction::{Direction, Orientation}; -use crate::event::{ - Callback, Event, EventResult, Key, MouseButton, MouseEvent, +use crate::{ + direction::{Direction, Orientation}, + event::{Callback, Event, EventResult, Key, MouseButton, MouseEvent}, + theme::ColorStyle, + view::{CannotFocus, View}, + Cursive, Printer, Vec2, With, }; -use crate::theme::ColorStyle; -use crate::view::View; -use crate::Vec2; -use crate::With; -use crate::{Cursive, Printer}; use std::rc::Rc; /// A horizontal or vertical slider. @@ -232,7 +230,10 @@ impl View for SliderView { } } - fn take_focus(&mut self, _: Direction) -> bool { - true + fn take_focus( + &mut self, + _: Direction, + ) -> Result { + Ok(EventResult::Consumed(None)) } } diff --git a/cursive-core/src/views/stack_view.rs b/cursive-core/src/views/stack_view.rs index 6dab9c9..45c4759 100644 --- a/cursive-core/src/views/stack_view.rs +++ b/cursive-core/src/views/stack_view.rs @@ -3,8 +3,8 @@ use crate::{ event::{AnyCb, Event, EventResult}, theme::ColorStyle, view::{ - IntoBoxedView, Offset, Position, Selector, View, ViewNotFound, - ViewWrapper, + CannotFocus, IntoBoxedView, Offset, Position, Selector, View, + ViewNotFound, ViewWrapper, }, views::{BoxedView, CircularFocus, Layer, ShadowView}, Printer, Vec2, With, @@ -164,7 +164,10 @@ impl View for ChildWrapper { } } - fn take_focus(&mut self, source: Direction) -> bool { + fn take_focus( + &mut self, + source: Direction, + ) -> Result { match *self { ChildWrapper::Shadow(ref mut v) => v.take_focus(source), ChildWrapper::Backfilled(ref mut v) => v.take_focus(source), @@ -193,7 +196,7 @@ impl View for ChildWrapper { fn focus_view( &mut self, selector: &Selector<'_>, - ) -> Result<(), ViewNotFound> { + ) -> Result { match *self { ChildWrapper::Shadow(ref mut v) => v.focus_view(selector), ChildWrapper::Backfilled(ref mut v) => v.focus_view(selector), @@ -257,7 +260,7 @@ impl StackView { let boxed = BoxedView::boxed(view); self.layers.push(Child { view: ChildWrapper::Backfilled(Layer::new( - CircularFocus::wrap_tab(boxed), + CircularFocus::new(boxed).wrap_tab(), )), size: Vec2::zero(), placement: Placement::Fullscreen, @@ -372,9 +375,11 @@ impl StackView { self.layers.push(Child { // Skip padding for absolute/parent-placed views view: ChildWrapper::Shadow( - ShadowView::new(Layer::new(CircularFocus::wrap_tab(boxed))) - .top_padding(position.y == Offset::Center) - .left_padding(position.x == Offset::Center), + ShadowView::new(Layer::new( + CircularFocus::new(boxed).wrap_tab(), + )) + .top_padding(position.y == Offset::Center) + .left_padding(position.x == Offset::Center), ), size: Vec2::new(0, 0), placement: Placement::Floating(position), @@ -397,7 +402,7 @@ impl StackView { { let boxed = BoxedView::boxed(view); self.layers.push(Child { - view: ChildWrapper::Plain(CircularFocus::wrap_tab(boxed)), + view: ChildWrapper::Plain(CircularFocus::new(boxed).wrap_tab()), size: Vec2::new(0, 0), placement: Placement::Floating(position), virgin: true, @@ -652,7 +657,9 @@ impl View for StackView { // The text view takes focus because it's scrolling, but it only // knows that after a call to `layout()`. if layer.virgin { - layer.view.take_focus(Direction::none()); + // Here we can't really forward the callback. + // So just ignore the result. :( + layer.view.take_focus(Direction::none()).ok(); layer.virgin = false; } } @@ -667,9 +674,12 @@ impl View for StackView { .fold(Vec2::new(1, 1), Vec2::max) } - fn take_focus(&mut self, source: Direction) -> bool { + fn take_focus( + &mut self, + source: Direction, + ) -> Result { match self.layers.last_mut() { - None => false, + None => Err(CannotFocus), Some(v) => v.view.take_focus(source), } } @@ -687,10 +697,10 @@ impl View for StackView { fn focus_view( &mut self, selector: &Selector<'_>, - ) -> Result<(), ViewNotFound> { + ) -> Result { for layer in &mut self.layers { if layer.view.focus_view(selector).is_ok() { - return Ok(()); + return Ok(EventResult::Consumed(None)); } } diff --git a/cursive-core/src/views/text_area.rs b/cursive-core/src/views/text_area.rs index 2be4c25..eb792a2 100644 --- a/cursive-core/src/views/text_area.rs +++ b/cursive-core/src/views/text_area.rs @@ -1,12 +1,13 @@ -use crate::direction::Direction; -use crate::event::{Event, EventResult, Key, MouseButton, MouseEvent}; -use crate::rect::Rect; -use crate::theme::{ColorStyle, Effect}; -use crate::utils::lines::simple::{prefix, simple_prefix, LinesIterator, Row}; #[allow(deprecated)] -use crate::view::{ScrollBase, SizeCache, View}; -use crate::Vec2; -use crate::{Printer, With, XY}; +use crate::{ + direction::Direction, + event::{Event, EventResult, Key, MouseButton, MouseEvent}, + rect::Rect, + theme::{ColorStyle, Effect}, + utils::lines::simple::{prefix, simple_prefix, LinesIterator, Row}, + view::{CannotFocus, ScrollBase, SizeCache, View}, + Vec2, {Printer, With, XY}, +}; use log::debug; use std::cmp::min; use unicode_segmentation::UnicodeSegmentation; @@ -639,8 +640,13 @@ impl View for TextArea { EventResult::Consumed(None) } - fn take_focus(&mut self, _: Direction) -> bool { + fn take_focus( + &mut self, + _: Direction, + ) -> Result { self.enabled + .then(|| EventResult::Consumed(None)) + .ok_or(CannotFocus) } fn layout(&mut self, size: Vec2) { diff --git a/examples/src/bin/dialog.rs b/examples/src/bin/dialog.rs index 0de85e9..c044205 100644 --- a/examples/src/bin/dialog.rs +++ b/examples/src/bin/dialog.rs @@ -1,4 +1,7 @@ -use cursive::views::{CircularFocus, Dialog, TextView}; +use cursive::{ + views::{CircularFocus, Dialog, TextView}, + With as _, +}; fn main() { // Creates the cursive root - required for every application. @@ -7,12 +10,12 @@ fn main() { // Creates a dialog with a single "Quit" button siv.add_layer( // Most views can be configured in a chainable way - CircularFocus::wrap_tab( - Dialog::around(TextView::new("Hello Dialog!")) - .title("Cursive") - .button("Foo", |_s| ()) - .button("Quit", |s| s.quit()), - ), + Dialog::around(TextView::new("Hello Dialog!")) + .title("Cursive") + .button("Foo", |_s| ()) + .button("Quit", |s| s.quit()) + .wrap_with(CircularFocus::new) + .wrap_tab(), ); // Starts the event loop. diff --git a/examples/src/bin/focus.rs b/examples/src/bin/focus.rs new file mode 100644 index 0000000..e73f881 --- /dev/null +++ b/examples/src/bin/focus.rs @@ -0,0 +1,42 @@ +use cursive::traits::*; + +fn main() { + let mut siv = cursive::default(); + + siv.add_layer( + cursive::views::Dialog::new().content( + cursive::views::LinearLayout::vertical() + .child( + cursive::views::TextView::new("Focused").with_name("text"), + ) + .child( + cursive::views::EditView::new() + .wrap_with(cursive::views::FocusTracker::new) + .on_focus(|_| { + cursive::event::EventResult::with_cb(|s| { + s.call_on_name( + "text", + |v: &mut cursive::views::TextView| { + v.set_content("Focused"); + }, + ); + }) + }) + .on_focus_lost(|_| { + cursive::event::EventResult::with_cb(|s| { + s.call_on_name( + "text", + |v: &mut cursive::views::TextView| { + v.set_content("Focus lost"); + }, + ); + }) + }), + ) + .child(cursive::views::Button::new("Quit", |s| s.quit())) + .fixed_width(20), + ), + ); + + siv.run(); +} diff --git a/examples/src/bin/mines/main.rs b/examples/src/bin/mines/main.rs index 5650aca..0335159 100644 --- a/examples/src/bin/mines/main.rs +++ b/examples/src/bin/mines/main.rs @@ -1,12 +1,13 @@ mod game; -use cursive::direction::Direction; -use cursive::event::{Event, EventResult, MouseButton, MouseEvent}; -use cursive::theme::{BaseColor, Color, ColorStyle}; -use cursive::views::{Button, Dialog, LinearLayout, Panel, SelectView}; -use cursive::Cursive; -use cursive::Printer; -use cursive::Vec2; +use cursive::{ + direction::Direction, + event::{Event, EventResult, MouseButton, MouseEvent}, + theme::{BaseColor, Color, ColorStyle}, + view::CannotFocus, + views::{Button, Dialog, LinearLayout, Panel, SelectView}, + Cursive, Printer, Vec2, +}; fn main() { let mut siv = cursive::default(); @@ -211,8 +212,11 @@ impl cursive::view::View for BoardView { } } - fn take_focus(&mut self, _: Direction) -> bool { - true + fn take_focus( + &mut self, + _: Direction, + ) -> Result { + Ok(EventResult::Consumed(None)) } fn on_event(&mut self, event: Event) -> EventResult {