Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Dialog the capability to be focusable #616

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 22 additions & 13 deletions cursive-core/src/printer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,8 @@ impl<'a, 'b> Printer<'a, 'b> {
/// If `invert` is `true`, and the theme uses `Outset` borders, then the
/// box will use an "inset" style instead.
///
/// If `highlight` is `true`, then the "title" primary color will be used instead.
///
/// # Examples
///
/// ```rust
Expand All @@ -407,13 +409,14 @@ impl<'a, 'b> Printer<'a, 'b> {
/// # let b = backend::Dummy::init();
/// # let t = theme::load_default();
/// # let printer = Printer::new((6,4), &t, &*b);
/// printer.print_box((0, 0), (6, 4), false);
/// printer.print_box((0, 0), (6, 4), false, false);
/// ```
pub fn print_box<T: Into<Vec2>, S: Into<Vec2>>(
&self,
start: T,
size: S,
invert: bool,
highlight: bool,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think at this point it may be better to use some parameter-struct (to have named parameters) rather than additional parameters:

use BoxStyle as BS;

printer.print_box(start, size, BS { invert: false, highlight: false });

) {
let start = start.into();
let size = size.into();
Expand All @@ -423,14 +426,14 @@ impl<'a, 'b> Printer<'a, 'b> {
}
let size = size - (1, 1);

self.with_high_border(invert, |s| {
self.with_high_border(invert, highlight, |s| {
s.print(start, "┌");
s.print(start + size.keep_y(), "└");
s.print_hline(start + (1, 0), size.x - 1, "─");
s.print_vline(start + (0, 1), size.y - 1, "│");
});

self.with_low_border(invert, |s| {
self.with_low_border(invert, highlight, |s| {
s.print(start + size.keep_x(), "┐");
s.print(start + size, "┘");
s.print_hline(start + (1, 0) + size.keep_y(), size.x - 1, "─");
Expand All @@ -444,14 +447,17 @@ impl<'a, 'b> Printer<'a, 'b> {
/// * If the theme's borders is "outset" and `invert` is `false`,
/// use `ColorStyle::Tertiary`.
/// * Otherwise, use `ColorStyle::Primary`.
pub fn with_high_border<F>(&self, invert: bool, f: F)
pub fn with_high_border<F>(&self, invert: bool, highlight: bool, f: F)
where
F: FnOnce(&Printer),
{
let color = match self.theme.borders {
BorderStyle::None => return,
BorderStyle::Outset if !invert => ColorStyle::tertiary(),
_ => ColorStyle::primary(),
let color = match (self.theme.borders, highlight) {
(BorderStyle::None, true) => ColorStyle::primary(),
(BorderStyle::None, false) => return,
(BorderStyle::Outset, true) if !invert => ColorStyle::primary(),
(BorderStyle::Outset, false) if !invert => ColorStyle::tertiary(),
(_, false) => ColorStyle::primary(),
(_, true) => ColorStyle::title_primary(),
};

self.with_color(color, f);
Expand All @@ -463,14 +469,17 @@ impl<'a, 'b> Printer<'a, 'b> {
/// * If the theme's borders is "outset" and `invert` is `true`,
/// use `ColorStyle::tertiary()`.
/// * Otherwise, use `ColorStyle::primary()`.
pub fn with_low_border<F>(&self, invert: bool, f: F)
pub fn with_low_border<F>(&self, invert: bool, highlight: bool, f: F)
where
F: FnOnce(&Printer),
{
let color = match self.theme.borders {
BorderStyle::None => return,
BorderStyle::Outset if invert => ColorStyle::tertiary(),
_ => ColorStyle::primary(),
let color = match (self.theme.borders, highlight) {
(BorderStyle::None, true) => ColorStyle::primary(),
(BorderStyle::None, false) => return,
(BorderStyle::Outset, true) if invert => ColorStyle::primary(),
(BorderStyle::Outset, false) if invert => ColorStyle::tertiary(),
(_, false) => ColorStyle::primary(),
(_, true) => ColorStyle::title_primary(),
};

self.with_color(color, f);
Expand Down
141 changes: 132 additions & 9 deletions cursive-core/src/views/dialog.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use crate::{
align::*,
direction::{Absolute, Direction, Relative},
event::{AnyCb, Event, EventResult, Key},
event::{AnyCb, Callback, Event, EventResult, Key},
rect::Rect,
theme::ColorStyle,
theme::{BorderStyle, ColorStyle},
utils::markup::StyledString,
view::{
CannotFocus, IntoBoxedView, Margins, Selector, View, ViewNotFound,
Expand All @@ -18,10 +18,12 @@ use unicode_width::UnicodeWidthStr;
/// Identifies currently focused element in [`Dialog`].
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum DialogFocus {
/// Content element focused
/// Content element focused.
Content,
/// One of buttons focused
/// One of the buttons is focused.
Button(usize),
/// The dialog itself focused.
Dialog,
}

struct ChildButton {
Expand Down Expand Up @@ -78,6 +80,12 @@ pub struct Dialog {

// `true` when we needs to relayout
invalidated: bool,

// Dialog can take focus itself.
dialog_focusable: bool,

// Dialog callback when focused and Enter is pressed.
dialog_focus_callback: Option<Callback>,
}

new_default!(Dialog);
Expand All @@ -102,6 +110,8 @@ impl Dialog {
borders: Margins::lrtb(1, 1, 1, 1),
align: Align::top_right(),
invalidated: true,
dialog_focusable: false,
dialog_focus_callback: None,
}
}

Expand Down Expand Up @@ -442,6 +452,43 @@ impl Dialog {
self.buttons.iter_mut().map(|b| &mut b.button.view)
}

/// Set the dialog itself focusable.
pub fn focusable(self, dialog_focusable: bool) -> Self {
self.with(|s| s.set_focusable(dialog_focusable))
}

/// Set the dialog itself focusable.
pub fn set_focusable(&mut self, dialog_focusable: bool) {
self.dialog_focusable = dialog_focusable
}

/// Sets the function to be called when the dialog is focused and Enter is pressed.
///
/// Replaces the previous callback.
pub fn focus_callback<F>(self, cb: F) -> Self
where
F: Fn(&mut Cursive) + 'static,
{
self.with(|s| s.set_focus_callback(cb))
}

/// Sets the function to be called when the dialog is focused and Enter is pressed.
///
/// Replaces the previous callback.
pub fn set_focus_callback<F>(&mut self, cb: F)
where
F: Fn(&mut Cursive) + 'static,
{
self.dialog_focus_callback = Some(Callback::from_fn(cb));
}

/// Sets the function to be called when the dialog is focused and Enter is pressed.
///
/// Replaces the previous callback.
pub fn remove_focus_callback(&mut self) {
self.dialog_focus_callback = None;
}

/// Returns currently focused element
pub fn focus(&self) -> DialogFocus {
self.focus
Expand All @@ -467,6 +514,7 @@ impl Dialog {
// TODO: send Event::LostFocus?
DialogFocus::Button(min(c, self.buttons.len() - 1))
}
DialogFocus::Dialog => DialogFocus::Dialog,
};
}

Expand Down Expand Up @@ -577,6 +625,59 @@ impl Dialog {
}
}

// An event is received while the dialog itself is focused
fn on_event_dialog(&mut self, event: Event) -> EventResult {
match event {
// Enter calls callback or goes into the content
Event::Key(Key::Enter) => {
if let Some(callback) = self.dialog_focus_callback.clone() {
EventResult::Consumed(Some(callback))
} else if let Ok(res) =
self.content.take_focus(Direction::down())
{
self.focus = DialogFocus::Content;
res
} else if !self.buttons.is_empty() {
self.focus = DialogFocus::Button(0);
EventResult::Consumed(None)
} else {
EventResult::Ignored
}
}
// Tab goes to the buttons
Event::Key(Key::Tab) => {
if !self.buttons.is_empty() {
self.focus = DialogFocus::Button(0);
EventResult::Consumed(None)
} else if let Ok(res) =
self.content.take_focus(Direction::down())
{
self.focus = DialogFocus::Content;
res
} else {
EventResult::Ignored
}
}
// Tab goes to the buttons
Event::Shift(Key::Tab) => {
if !self.buttons.is_empty() {
self.focus = DialogFocus::Button(
self.buttons.len().saturating_sub(1),
);
EventResult::Consumed(None)
} else if let Ok(res) =
self.content.take_focus(Direction::up())
{
self.focus = DialogFocus::Content;
res
} else {
EventResult::Ignored
}
}
_ => EventResult::Ignored,
}
}

fn draw_buttons(&self, printer: &Printer) -> Option<usize> {
let mut buttons_height = 0;
// Current horizontal position of the next button we'll draw.
Expand Down Expand Up @@ -656,10 +757,14 @@ impl Dialog {
+ self
.title_position
.get_offset(len, printer.size.x - spacing_both_ends);
printer.with_high_border(false, |printer| {
printer.print((x - 2, 0), "┤ ");
printer.print((x + len, 0), " ├");
});
printer.with_high_border(
false,
self.box_highlight(printer),
|printer| {
printer.print((x - 2, 0), "┤ ");
printer.print((x + len, 0), " ├");
},
);

printer.with_color(ColorStyle::title_primary(), |p| {
p.print((x, 0), &self.title)
Expand Down Expand Up @@ -708,6 +813,12 @@ impl Dialog {
fn invalidate(&mut self) {
self.invalidated = true;
}

fn box_highlight(&self, printer: &Printer) -> bool {
printer.focused
&& (self.focus == DialogFocus::Dialog
|| printer.theme.borders == BorderStyle::None)
}
}

impl View for Dialog {
Expand All @@ -721,7 +832,12 @@ impl View for Dialog {
self.draw_content(printer, buttons_height);

// Print the borders
printer.print_box(Vec2::new(0, 0), printer.size, false);
printer.print_box(
Vec2::new(0, 0),
printer.size,
false,
self.box_highlight(printer),
);

self.draw_title(printer);
}
Expand Down Expand Up @@ -806,13 +922,19 @@ impl View for 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),
DialogFocus::Dialog => self.on_event_dialog(event),
})
}

fn take_focus(
&mut self,
source: Direction,
) -> Result<EventResult, CannotFocus> {
if self.dialog_focusable {
self.focus = DialogFocus::Dialog;
return Ok(EventResult::Consumed(None));
}

// TODO: This may depend on button position relative to the content?
//
match source {
Expand Down Expand Up @@ -843,6 +965,7 @@ impl View for Dialog {
}
}
}
(DialogFocus::Dialog, _) => unreachable!(),
}
}
Direction::Rel(Relative::Front)
Expand Down
4 changes: 2 additions & 2 deletions cursive-core/src/views/panel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ impl<V> Panel<V> {
+ self
.title_position
.get_offset(len, printer.size.x - 2 * TITLE_SPACING);
printer.with_high_border(false, |printer| {
printer.with_high_border(false, false, |printer| {
printer.print((x - 2, 0), "┤ ");
printer.print((x + len, 0), " ├");
});
Expand Down Expand Up @@ -111,7 +111,7 @@ impl<V: View> ViewWrapper for Panel<V> {
}

fn wrap_draw(&self, printer: &Printer) {
printer.print_box((0, 0), printer.size, true);
printer.print_box((0, 0), printer.size, true, false);
self.draw_title(printer);

let printer = printer.offset((1, 1)).shrinked((1, 1));
Expand Down
Loading