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<EventResult, CannotFocus>`.
- Change return type of `View::focus_view` from
    `Result<(), ViewNotFound>` to `Result<EventResult, ViewNotFound>`.
- Add `views::FocusTracker` to run callbacks on focus gain/loss.
This commit is contained in:
Alexandre Bury 2021-03-05 12:51:40 -08:00
parent e417a8be0b
commit 7fd86b69ec
28 changed files with 699 additions and 393 deletions

View File

@ -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<EventResult, ViewNotFound> {
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<EventResult, ViewNotFound> {
self.focus(&view::Selector::Name(id))
}
@ -632,7 +638,7 @@ impl Cursive {
pub fn focus(
&mut self,
sel: &view::Selector<'_>,
) -> Result<(), ViewNotFound> {
) -> Result<EventResult, ViewNotFound> {
self.root.focus_view(sel)
}

View File

@ -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,

View File

@ -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`")]

View File

@ -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<EventResult, ViewNotFound> {
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<EventResult, CannotFocus> {
let _ = source;
false
Err(CannotFocus)
}
/// What part of the view is important and should be visible?

View File

@ -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<EventResult, CannotFocus> {
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<EventResult, ViewNotFound> {
self.with_view_mut(|v| v.focus_view(selector))
.unwrap_or(Err(ViewNotFound))
}
@ -123,7 +126,10 @@ impl<T: ViewWrapper> View for T {
self.wrap_layout(size);
}
fn take_focus(&mut self, source: Direction) -> bool {
fn take_focus(
&mut self,
source: Direction,
) -> Result<EventResult, CannotFocus> {
self.wrap_take_focus(source)
}
@ -142,7 +148,7 @@ impl<T: ViewWrapper> View for T {
fn focus_view(
&mut self,
selector: &Selector<'_>,
) -> Result<(), ViewNotFound> {
) -> Result<EventResult, ViewNotFound> {
self.wrap_focus_view(selector)
}

View File

@ -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 <Enter> 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<EventResult, CannotFocus> {
self.enabled.then(EventResult::consumed).ok_or(CannotFocus)
}
fn important_area(&self, view_size: Vec2) -> Rect {

View File

@ -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<T> {
on_event: Box<dyn FnMut(&mut T, Event) -> EventResult>,
required_size: Box<dyn FnMut(&mut T, Vec2) -> Vec2>,
layout: Box<dyn FnMut(&mut T, Vec2)>,
take_focus: Box<dyn FnMut(&mut T, Direction) -> bool>,
take_focus:
Box<dyn FnMut(&mut T, Direction) -> Result<EventResult, CannotFocus>>,
needs_relayout: Box<dyn Fn(&T) -> bool>,
focus_view: Box<dyn FnMut(&mut T, &Selector) -> Result<(), ViewNotFound>>,
focus_view:
Box<dyn FnMut(&mut T, &Selector) -> Result<EventResult, ViewNotFound>>,
call_on_any: CallOnAny<T>,
important_area: Box<dyn Fn(&T, Vec2) -> Rect>,
}
@ -83,7 +85,7 @@ impl<T> Canvas<T> {
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<T> Canvas<T> {
/// Sets the closure for `take_focus(Direction)`.
pub fn set_take_focus<F>(&mut self, f: F)
where
F: 'static + FnMut(&mut T, Direction) -> bool,
F: 'static
+ FnMut(&mut T, Direction) -> Result<EventResult, CannotFocus>,
{
self.take_focus = Box::new(f);
}
@ -183,7 +186,8 @@ impl<T> Canvas<T> {
/// Chainable variant.
pub fn with_take_focus<F>(self, f: F) -> Self
where
F: 'static + FnMut(&mut T, Direction) -> bool,
F: 'static
+ FnMut(&mut T, Direction) -> Result<EventResult, CannotFocus>,
{
self.with(|s| s.set_take_focus(f))
}
@ -245,7 +249,8 @@ impl<T> Canvas<T> {
/// Sets the closure for `focus_view()`.
pub fn set_focus_view<F>(&mut self, f: F)
where
F: 'static + FnMut(&mut T, &Selector<'_>) -> Result<(), ViewNotFound>,
F: 'static
+ FnMut(&mut T, &Selector<'_>) -> Result<EventResult, ViewNotFound>,
{
self.focus_view = Box::new(f);
}
@ -255,7 +260,8 @@ impl<T> Canvas<T> {
/// Chainable variant.
pub fn with_focus_view<F>(self, f: F) -> Self
where
F: 'static + FnMut(&mut T, &Selector<'_>) -> Result<(), ViewNotFound>,
F: 'static
+ FnMut(&mut T, &Selector<'_>) -> Result<EventResult, ViewNotFound>,
{
self.with(|s| s.set_focus_view(f))
}
@ -278,7 +284,10 @@ impl<T: 'static> View for Canvas<T> {
(self.layout)(&mut self.state, size);
}
fn take_focus(&mut self, source: Direction) -> bool {
fn take_focus(
&mut self,
source: Direction,
) -> Result<EventResult, CannotFocus> {
(self.take_focus)(&mut self.state, source)
}
@ -289,7 +298,7 @@ impl<T: 'static> View for Canvas<T> {
fn focus_view(
&mut self,
selector: &Selector<'_>,
) -> Result<(), ViewNotFound> {
) -> Result<EventResult, ViewNotFound> {
(self.focus_view)(&mut self.state, selector)
}

View File

@ -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<EventResult, CannotFocus> {
self.enabled.then(EventResult::consumed).ok_or(CannotFocus)
}
fn draw(&self, printer: &Printer) {

View File

@ -54,6 +54,16 @@ impl<T: View> CircularFocus<T> {
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<T: View> ViewWrapper for CircularFocus<T> {
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,
}

View File

@ -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<EventResult> {
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<EventResult, CannotFocus> {
// 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<EventResult, ViewNotFound> {
self.content.focus_view(selector)
}

View File

@ -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<EventResult, CannotFocus> {
self.enabled.then(EventResult::consumed).ok_or(CannotFocus)
}
fn on_event(&mut self, event: Event) -> EventResult {

View File

@ -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<EventResult, ViewNotFound> {
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<EventResult> {
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<EventResult, CannotFocus> {
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<EventResult, ViewNotFound> {
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)

View File

@ -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<T> {
view: T,
on_focus_lost: Box<dyn FnMut(&mut T) -> EventResult>,
on_focus: Box<dyn FnMut(&mut T) -> EventResult>,
}
impl<T> FocusTracker<T> {
/// 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<F>(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<F>(self, f: F) -> Self
where
F: 'static + FnMut(&mut T) -> EventResult,
{
self.with(|s| s.on_focus_lost = Box::new(f))
}
}
impl<T: View> ViewWrapper for FocusTracker<T> {
wrap_impl!(self.view: T);
fn wrap_take_focus(
&mut self,
source: Direction,
) -> Result<EventResult, CannotFocus> {
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))
}
}

View File

@ -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<EventResult, ViewNotFound> {
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<EventResult> {
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<usize> {
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<EventResult, CannotFocus> {
// 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<EventResult, ViewNotFound> {
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));
}
}

View File

@ -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<EventResult> {
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<usize> {
) -> 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<EventResult, CannotFocus> {
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<EventResult, ViewNotFound> {
// 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)
}

View File

@ -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<EventResult, CannotFocus> {
self.state = State::Selected;
true
Ok(EventResult::consumed())
}
fn required_size(&mut self, _: Vec2) -> Vec2 {

View File

@ -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,

View File

@ -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<T: View + 'static> ViewWrapper for NamedView<T> {
fn wrap_focus_view(
&mut self,
selector: &Selector<'_>,
) -> Result<(), ViewNotFound> {
) -> Result<EventResult, ViewNotFound> {
match selector {
#[allow(deprecated)]
&Selector::Name(name) | &Selector::Id(name)
if name == self.name =>
{
Ok(())
Ok(EventResult::Consumed(None))
}
s => self
.view

View File

@ -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<T: 'static> View for RadioButton<T> {
self.req_size()
}
fn take_focus(&mut self, _: Direction) -> bool {
self.enabled
fn take_focus(
&mut self,
_: Direction,
) -> Result<EventResult, CannotFocus> {
self.enabled.then(EventResult::consumed).ok_or(CannotFocus)
}
fn draw(&self, printer: &Printer) {

View File

@ -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<EventResult, ViewNotFound> {
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);
}
}

View File

@ -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<EventResult, ViewNotFound> {
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<EventResult, CannotFocus> {
// 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),
}
}

View File

@ -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<T: 'static> View for SelectView<T> {
}
}
fn take_focus(&mut self, _: Direction) -> bool {
self.enabled && !self.items.is_empty()
fn take_focus(
&mut self,
source: direction::Direction,
) -> Result<EventResult, CannotFocus> {
(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) {

View File

@ -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<EventResult, CannotFocus> {
Ok(EventResult::Consumed(None))
}
}

View File

@ -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<T: View> View for ChildWrapper<T> {
}
}
fn take_focus(&mut self, source: Direction) -> bool {
fn take_focus(
&mut self,
source: Direction,
) -> Result<EventResult, CannotFocus> {
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<T: View> View for ChildWrapper<T> {
fn focus_view(
&mut self,
selector: &Selector<'_>,
) -> Result<(), ViewNotFound> {
) -> Result<EventResult, ViewNotFound> {
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<EventResult, CannotFocus> {
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<EventResult, ViewNotFound> {
for layer in &mut self.layers {
if layer.view.focus_view(selector).is_ok() {
return Ok(());
return Ok(EventResult::Consumed(None));
}
}

View File

@ -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<EventResult, CannotFocus> {
self.enabled
.then(|| EventResult::Consumed(None))
.ok_or(CannotFocus)
}
fn layout(&mut self, size: Vec2) {

View File

@ -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.

42
examples/src/bin/focus.rs Normal file
View File

@ -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();
}

View File

@ -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<EventResult, CannotFocus> {
Ok(EventResult::Consumed(None))
}
fn on_event(&mut self, event: Event) -> EventResult {