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 align::*;
|
||||||
use direction::Direction;
|
use direction::{Absolute, Direction, Relative};
|
||||||
use event::{AnyCb, Event, EventResult, Key};
|
use event::{AnyCb, Event, EventResult, Key};
|
||||||
use rect::Rect;
|
use rect::Rect;
|
||||||
use std::cell::Cell;
|
use std::cell::Cell;
|
||||||
@ -300,9 +300,7 @@ impl Dialog {
|
|||||||
EventResult::Ignored => {
|
EventResult::Ignored => {
|
||||||
if !self.buttons.is_empty() {
|
if !self.buttons.is_empty() {
|
||||||
match event {
|
match event {
|
||||||
Event::Key(Key::Down)
|
Event::Key(Key::Down) | Event::Key(Key::Tab) => {
|
||||||
| Event::Key(Key::Tab)
|
|
||||||
| Event::Shift(Key::Tab) => {
|
|
||||||
// Default to leftmost button when going down.
|
// Default to leftmost button when going down.
|
||||||
self.focus = DialogFocus::Button(0);
|
self.focus = DialogFocus::Button(0);
|
||||||
EventResult::Consumed(None)
|
EventResult::Consumed(None)
|
||||||
@ -310,25 +308,7 @@ impl Dialog {
|
|||||||
_ => EventResult::Ignored,
|
_ => EventResult::Ignored,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
match event {
|
EventResult::Ignored
|
||||||
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,
|
res => res,
|
||||||
@ -357,7 +337,10 @@ impl Dialog {
|
|||||||
EventResult::Ignored
|
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()) {
|
if self.content.take_focus(Direction::back()) {
|
||||||
self.focus = DialogFocus::Content;
|
self.focus = DialogFocus::Content;
|
||||||
EventResult::Consumed(None)
|
EventResult::Consumed(None)
|
||||||
@ -365,13 +348,30 @@ impl Dialog {
|
|||||||
EventResult::Ignored
|
EventResult::Ignored
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Event::Key(Key::Tab) => {
|
Event::Shift(Key::Tab) => {
|
||||||
if self.content.take_focus(Direction::front()) {
|
// Otherwise, jump to the previous button.
|
||||||
self.focus = DialogFocus::Content;
|
if let DialogFocus::Button(ref mut i) = self.focus {
|
||||||
EventResult::Consumed(None)
|
// This should always be the case.
|
||||||
} else {
|
*i -= 1;
|
||||||
EventResult::Ignored
|
|
||||||
}
|
}
|
||||||
|
EventResult::Consumed(None)
|
||||||
|
}
|
||||||
|
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
|
// Left and Right move to other buttons
|
||||||
Event::Key(Key::Right)
|
Event::Key(Key::Right)
|
||||||
@ -406,10 +406,11 @@ impl Dialog {
|
|||||||
if printer.size.x < overhead.horizontal() {
|
if printer.size.x < overhead.horizontal() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let mut offset = overhead.left + self
|
let mut offset = overhead.left
|
||||||
.align
|
+ self
|
||||||
.h
|
.align
|
||||||
.get_offset(width, printer.size.x - overhead.horizontal());
|
.h
|
||||||
|
.get_offset(width, printer.size.x - overhead.horizontal());
|
||||||
|
|
||||||
let overhead_bottom = self.padding.bottom + self.borders.bottom + 1;
|
let overhead_bottom = self.padding.bottom + self.borders.bottom + 1;
|
||||||
|
|
||||||
@ -464,9 +465,10 @@ impl Dialog {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let spacing = 3; //minimum distance to borders
|
let spacing = 3; //minimum distance to borders
|
||||||
let x = spacing + self
|
let x = spacing
|
||||||
.title_position
|
+ self
|
||||||
.get_offset(len, printer.size.x - 2 * spacing);
|
.title_position
|
||||||
|
.get_offset(len, printer.size.x - 2 * spacing);
|
||||||
printer.with_high_border(false, |printer| {
|
printer.with_high_border(false, |printer| {
|
||||||
printer.print((x - 2, 0), "┤ ");
|
printer.print((x - 2, 0), "┤ ");
|
||||||
printer.print((x + len, 0), " ├");
|
printer.print((x + len, 0), " ├");
|
||||||
@ -616,16 +618,38 @@ impl View for Dialog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn take_focus(&mut self, source: Direction) -> bool {
|
fn take_focus(&mut self, source: Direction) -> bool {
|
||||||
// Dialogs aren't meant to be used in layouts, so...
|
// TODO: This may depend on button position relative to the content?
|
||||||
// Let's be super lazy and not even care about the focus source.
|
//
|
||||||
if self.content.take_focus(source) {
|
match source {
|
||||||
self.focus = DialogFocus::Content;
|
Direction::Abs(Absolute::None)
|
||||||
true
|
| Direction::Rel(Relative::Front)
|
||||||
} else if !self.buttons.is_empty() {
|
| Direction::Abs(Absolute::Left)
|
||||||
self.focus = DialogFocus::Button(0);
|
| Direction::Abs(Absolute::Up) => {
|
||||||
true
|
// Forward focus: content, then buttons
|
||||||
} else {
|
if self.content.take_focus(source) {
|
||||||
false
|
self.focus = DialogFocus::Content;
|
||||||
|
true
|
||||||
|
} else if !self.buttons.is_empty() {
|
||||||
|
self.focus = DialogFocus::Button(0);
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,6 +39,7 @@ mod box_view;
|
|||||||
mod button;
|
mod button;
|
||||||
mod canvas;
|
mod canvas;
|
||||||
mod checkbox;
|
mod checkbox;
|
||||||
|
mod circular_focus;
|
||||||
mod dialog;
|
mod dialog;
|
||||||
mod dummy;
|
mod dummy;
|
||||||
mod edit_view;
|
mod edit_view;
|
||||||
@ -70,6 +71,7 @@ pub use self::box_view::BoxView;
|
|||||||
pub use self::button::Button;
|
pub use self::button::Button;
|
||||||
pub use self::canvas::Canvas;
|
pub use self::canvas::Canvas;
|
||||||
pub use self::checkbox::Checkbox;
|
pub use self::checkbox::Checkbox;
|
||||||
|
pub use self::circular_focus::CircularFocus;
|
||||||
pub use self::dialog::{Dialog, DialogFocus};
|
pub use self::dialog::{Dialog, DialogFocus};
|
||||||
pub use self::dummy::DummyView;
|
pub use self::dummy::DummyView;
|
||||||
pub use self::edit_view::EditView;
|
pub use self::edit_view::EditView;
|
||||||
|
Loading…
Reference in New Issue
Block a user