Focusable crate - manage focus state for your widgets

https://crates.io/crates/focusable

This lets you do things like automatically derive focus for a widget:

#[derive(Debug, Clone, Focus)]
pub(crate) struct TextBox {
    pub(crate) is_focused: bool,
    pub(crate) content: String,
}

Including widgets that won’t be focusable (can_focus() / is_focused return false and focus() / blur() do nothing).

#[derive(Debug, Clone, Focus)]
pub(crate) struct Label {
    pub(crate) text: String,
}

You can use the trait to generically handle focusable widgets, and can derive a FocusContainer trait that navigates between them as appropriate.

trait FocusableWidget: WidgetRef + Focus {
    fn boxed(self) -> Box<dyn FocusableWidget>
    where
        Self: 'static + Sized,
    {
        Box::new(self)
    }
}

#[derive(Focus, FocusContainer)]
pub(crate) struct Form {
    pub(crate) children: Vec<Box<dyn FocusableWidget>>,
}

let mut form = Form::from_iter([
    Label::new("Label 1:").boxed(),
    TextBox::new("Text 1").boxed(),
    Label::new("Label 2:").boxed(),
    TextBox::new("Text 2").boxed(),
    Label::new("Label 3:").boxed(),
    TextBox::new("Text 3").boxed(),
]);

And then in your app code:

form.focus_first();

if let event::Event::Key(key) = event::read()? {
    match key.code {
        KeyCode::Char('q') => running = false,
        KeyCode::Tab => form.focus_next(),
        KeyCode::BackTab => form.focus_previous(),
        _ => {}
    }
}
2 Likes

Neat crate.

I’m still coming to grips with what the idiomatic Rust naming conventions are, if any. Maybe rename the Focus trait to Focusable to match the crate name and English usage?

Put another way, what would you call Focus if you decomposed the trait into its constituent parts? Traits with a single method seem to be named after that method, so perhaps Focus suggests a single method named focus and the larger trait could do with a different name to suggest its broader scope.

pub trait CanFocus {
    fn can_focus(&self) -> bool;
}

pub trait IsFocused {
    fn is_focused(&self) -> bool;
}

pub trait Focus {
    fn focus(&mut self);
}

pub trait Blur {
    fn blur(&mut self);
}

pub trait Focusable: CanFocus + IsFocused + Focus + Blur {}

Also, is there any value in this trait?

pub trait Unfocusable: CanFocus + IsFocused {}

In other words, making it impossible to call focus or blur on something statically known to be unfocusable. Though, I suppose at that point even CanFocus and IsFocused are useless too (or could be const).

I’m probably thinking too much like a C++ programmer (SFINAE, concepts), or a Go programmer (generally, fine-grained interfaces that are composed together at run time, dynamically), and not a Rust programmer…