mirror of
https://github.com/FliegendeWurst/cursive.git
synced 2024-11-23 17:35:00 +00:00
Add CircularFocus view
Can be used to have focus wrap around when pressing Tab or Arrow keys.
This commit is contained in:
parent
4ac418393a
commit
3e1eefd2db
124
src/views/circular_focus.rs
Normal file
124
src/views/circular_focus.rs
Normal file
@ -0,0 +1,124 @@
|
||||
use direction::Direction;
|
||||
use event::{Event, EventResult, Key};
|
||||
use view::{View, ViewWrapper};
|
||||
|
||||
/// Adds circular focus to a wrapped view.
|
||||
///
|
||||
/// Wrap a view in `CircularFocus` to enable wrap-around focus
|
||||
/// (when the focus exits this view, it will come back the other side).
|
||||
///
|
||||
/// It can be configured to wrap Tab (and Shift+Tab) keys, and/or Arrow keys.
|
||||
pub struct CircularFocus<T: View> {
|
||||
view: T,
|
||||
wrap_tab: bool,
|
||||
wrap_arrows: bool,
|
||||
}
|
||||
|
||||
impl<T: View> CircularFocus<T> {
|
||||
/// Creates a new `CircularFocus` around the given view.
|
||||
///
|
||||
/// If `wrap_tab` is true, Tab keys will cause focus to wrap around.
|
||||
/// If `wrap_arrows` is true, Arrow keys will cause focus to wrap around.
|
||||
pub fn new(view: T, wrap_tab: bool, wrap_arrows: bool) -> Self {
|
||||
CircularFocus {
|
||||
view,
|
||||
wrap_tab,
|
||||
wrap_arrows,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new `CircularFocus` view which will wrap around Tab-based
|
||||
/// focus changes.
|
||||
///
|
||||
/// Whenever `Tab` would leave focus from this view, the focus will be
|
||||
/// brought back to the beginning of the view.
|
||||
pub fn wrap_tab(view: T) -> Self {
|
||||
CircularFocus::new(view, true, false)
|
||||
}
|
||||
|
||||
/// Creates a new `CircularFocus` view which will wrap around Tab-based
|
||||
/// focus changes.
|
||||
///
|
||||
/// Whenever an arrow key
|
||||
pub fn wrap_arrows(view: T) -> Self {
|
||||
CircularFocus::new(view, false, true)
|
||||
}
|
||||
|
||||
/// Returns `true` if Tab key cause focus to wrap around.
|
||||
pub fn wraps_tab(&self) -> bool {
|
||||
self.wrap_tab
|
||||
}
|
||||
|
||||
/// Returns `true` if Arrow keys cause focus to wrap around.
|
||||
pub fn wraps_arrows(&self) -> bool {
|
||||
self.wrap_arrows
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: View> ViewWrapper for CircularFocus<T> {
|
||||
wrap_impl!(self.view: T);
|
||||
|
||||
fn wrap_on_event(&mut self, event: Event) -> EventResult {
|
||||
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
|
||||
}
|
||||
}
|
||||
(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
|
||||
}
|
||||
}
|
||||
(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
|
||||
}
|
||||
}
|
||||
(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
|
||||
}
|
||||
}
|
||||
(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
|
||||
}
|
||||
}
|
||||
(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
|
||||
}
|
||||
}
|
||||
(other, _) => other,
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
use align::*;
|
||||
use direction::Direction;
|
||||
use direction::{Absolute, Direction, Relative};
|
||||
use event::{AnyCb, Event, EventResult, Key};
|
||||
use rect::Rect;
|
||||
use std::cell::Cell;
|
||||
@ -300,37 +300,17 @@ impl Dialog {
|
||||
EventResult::Ignored => {
|
||||
if !self.buttons.is_empty() {
|
||||
match event {
|
||||
Event::Key(Key::Down)
|
||||
| Event::Key(Key::Tab)
|
||||
| Event::Shift(Key::Tab) => {
|
||||
Event::Key(Key::Down) | Event::Key(Key::Tab) => {
|
||||
// Default to leftmost button when going down.
|
||||
self.focus = DialogFocus::Button(0);
|
||||
EventResult::Consumed(None)
|
||||
}
|
||||
_ => EventResult::Ignored,
|
||||
}
|
||||
} else {
|
||||
match event {
|
||||
Event::Shift(Key::Tab) => {
|
||||
if self.content.take_focus(Direction::back()) {
|
||||
self.focus = DialogFocus::Content;
|
||||
EventResult::Consumed(None)
|
||||
} else {
|
||||
EventResult::Ignored
|
||||
}
|
||||
}
|
||||
Event::Key(Key::Tab) => {
|
||||
if self.content.take_focus(Direction::front()) {
|
||||
self.focus = DialogFocus::Content;
|
||||
EventResult::Consumed(None)
|
||||
} else {
|
||||
EventResult::Ignored
|
||||
}
|
||||
}
|
||||
_ => EventResult::Ignored,
|
||||
}
|
||||
}
|
||||
}
|
||||
res => res,
|
||||
}
|
||||
}
|
||||
@ -357,7 +337,10 @@ impl Dialog {
|
||||
EventResult::Ignored
|
||||
}
|
||||
}
|
||||
Event::Shift(Key::Tab) => {
|
||||
Event::Shift(Key::Tab)
|
||||
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()) {
|
||||
self.focus = DialogFocus::Content;
|
||||
EventResult::Consumed(None)
|
||||
@ -365,13 +348,30 @@ impl Dialog {
|
||||
EventResult::Ignored
|
||||
}
|
||||
}
|
||||
Event::Key(Key::Tab) => {
|
||||
if self.content.take_focus(Direction::front()) {
|
||||
self.focus = DialogFocus::Content;
|
||||
Event::Shift(Key::Tab) => {
|
||||
// Otherwise, jump to the previous button.
|
||||
if let DialogFocus::Button(ref mut i) = self.focus {
|
||||
// This should always be the case.
|
||||
*i -= 1;
|
||||
}
|
||||
EventResult::Consumed(None)
|
||||
} else {
|
||||
}
|
||||
Event::Key(Key::Tab)
|
||||
if self.focus
|
||||
== DialogFocus::Button(
|
||||
self.buttons.len().saturating_sub(1),
|
||||
) =>
|
||||
{
|
||||
// End of the line
|
||||
EventResult::Ignored
|
||||
}
|
||||
Event::Key(Key::Tab) => {
|
||||
// Otherwise, jump to the next button.
|
||||
if let DialogFocus::Button(ref mut i) = self.focus {
|
||||
// This should always be the case.
|
||||
*i += 1;
|
||||
}
|
||||
EventResult::Consumed(None)
|
||||
}
|
||||
// Left and Right move to other buttons
|
||||
Event::Key(Key::Right)
|
||||
@ -406,7 +406,8 @@ impl Dialog {
|
||||
if printer.size.x < overhead.horizontal() {
|
||||
return None;
|
||||
}
|
||||
let mut offset = overhead.left + self
|
||||
let mut offset = overhead.left
|
||||
+ self
|
||||
.align
|
||||
.h
|
||||
.get_offset(width, printer.size.x - overhead.horizontal());
|
||||
@ -464,7 +465,8 @@ impl Dialog {
|
||||
return;
|
||||
}
|
||||
let spacing = 3; //minimum distance to borders
|
||||
let x = spacing + self
|
||||
let x = spacing
|
||||
+ self
|
||||
.title_position
|
||||
.get_offset(len, printer.size.x - 2 * spacing);
|
||||
printer.with_high_border(false, |printer| {
|
||||
@ -616,8 +618,14 @@ impl View for Dialog {
|
||||
}
|
||||
|
||||
fn take_focus(&mut self, source: Direction) -> bool {
|
||||
// Dialogs aren't meant to be used in layouts, so...
|
||||
// Let's be super lazy and not even care about the focus source.
|
||||
// TODO: This may depend on button position relative to the content?
|
||||
//
|
||||
match source {
|
||||
Direction::Abs(Absolute::None)
|
||||
| Direction::Rel(Relative::Front)
|
||||
| Direction::Abs(Absolute::Left)
|
||||
| Direction::Abs(Absolute::Up) => {
|
||||
// Forward focus: content, then buttons
|
||||
if self.content.take_focus(source) {
|
||||
self.focus = DialogFocus::Content;
|
||||
true
|
||||
@ -628,6 +636,22 @@ impl View for Dialog {
|
||||
false
|
||||
}
|
||||
}
|
||||
Direction::Rel(Relative::Back)
|
||||
| Direction::Abs(Absolute::Right)
|
||||
| Direction::Abs(Absolute::Down) => {
|
||||
// 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) {
|
||||
self.focus = DialogFocus::Content;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn call_on_any<'a>(&mut self, selector: &Selector, callback: AnyCb<'a>) {
|
||||
self.content.call_on_any(selector, callback);
|
||||
|
@ -39,6 +39,7 @@ mod box_view;
|
||||
mod button;
|
||||
mod canvas;
|
||||
mod checkbox;
|
||||
mod circular_focus;
|
||||
mod dialog;
|
||||
mod dummy;
|
||||
mod edit_view;
|
||||
@ -70,6 +71,7 @@ pub use self::box_view::BoxView;
|
||||
pub use self::button::Button;
|
||||
pub use self::canvas::Canvas;
|
||||
pub use self::checkbox::Checkbox;
|
||||
pub use self::circular_focus::CircularFocus;
|
||||
pub use self::dialog::{Dialog, DialogFocus};
|
||||
pub use self::dummy::DummyView;
|
||||
pub use self::edit_view::EditView;
|
||||
|
Loading…
Reference in New Issue
Block a user