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…

Thanks for taking a look :slight_smile:

Focus is idiomatic. See 0344-conventions-galore - The Rust RFC Book

The wiki guidelines have long suggested naming traits as follows:

Prefer (transitive) verbs, nouns, and then adjectives; avoid grammatical suffixes (like able)

Trait names like Copy, Clone and Show follow this convention. The convention avoids grammatical verbosity and gives Rust code a distinctive flavor (similar to its short keywords).

This RFC proposes to amend the convention to further say: if there is a single method that is the dominant functionality of the trait, consider using the same name for the trait itself. This is already the case for Clone and ToCStr, for example.

According to these rules, Encodable should probably be Encode.

It’s worth thinking not from the design of the trait, but from the perspective of how the trait should be used. I’m not sure I can really see how the suggested approach lends itself to real world utility. In contrast, the repo has a FocusContainer trait, and a derive macro which given a list of items that implement Focus can transition between the right items. There isn’t (AFAIK) a good way to query what traits does a type implement aside from requiring them to be implemented, so instead of writing if foo implements Blur { foo.blur() this just becomes foo.blur() where foo.blur() is a noop.

One possible alternative to explore on this might be something like:

trait HasFocusFlag {
    fn focus_flag() -> Option<FocusFlag>;
    fn can_focus() -> bool { focus_flag().is_some() }
}

struct FocusFlag {
    bool is_focused,
}

impl FocusFlag {
    fn focus() { is_focused = true; }
    fn blur() { is_focused = false; }
}

Here the ability to focus is governed by whether or not the type returns Some(FocusFlag) or None. It’s difficult to see a way to make unfocusable representable easily (without the user having to write more code for every case), and I don’t know that I like the naming as much.

I don’t think there’s any value in splitting the focus and blur methods - the only real alternative is focus(bool) which I don’t like as much as the more explicit names. I’m 90% certain there’s no value in splitting is_focused() away from those methods. The 10% uncertainty comes from seeing rust traits like Read and Write that are split. I think the distinction here is that anything dealing with focusing things usually cares about whether they are focused.

I’m unfamiliar with SFINAE (and don’t have enough modern C++ experience to understand it without going deeper), and static rather than dynamic composition of interfaces seems to require different strategies in Rust than Go for this sort of thing. Sure, there’s probably some dynamic reflection approaches that could work here, but those move away from the simple and obvious into a world of complexity and doubt. (It’s not that I can’t write that code, its just that when thinking about utility the simple approaches tend to be be better).

Btw, for non focusable items that implement focus, I want the following derive syntax, and for the macro to error otherwise if there’s no is_focused field.

#[derive(Focus)]
#[focus(ignore) // this is an attribute that the Focus derive macro sees
struct Foo;

Also for defining a non-standard field, similar to how Default works for enums:

#[derive(Focus)]
struct Foo {
    #[focus]
    custom_focused_field: bool,
}

Ah, thanks. I had not found that reference yet. That is something that could go in Naming - Rust API Guidelines.

I think what you have is clearer than using the Option<FocusFlag>.

Something like a focus(bool) is nice if that bool will often be passed in as a parameter. I’ve used APIs that were effectively setting a bool, split across two methods, where I had to write:

if desired_value
  foo.set_boolean_thing()
else
  foo.clear_boolean_thing()

…and that gets annoying if it comes up often.

You can also toggle easily: foo.set_thing(!foo.thing())

Anyway, this is bike shedding! :slight_smile:

In C++ you can often express conditional logic at compile time in convenient ways, so the types that can never have focus may not even need to have methods to set and retrieve the attribute. Advantage being you never have to worry about the semantics of calling focus() on an un-focusable thing if it won’t even compile. That kind of “unrepresentable state” feels very Rust-like to me, if it is achievable…but that probably goes down the path of your FocusFlag idea and, in Rust, maybe some proc macro convenience macros to make it palateable.

In C++, it is possible to do the semantic equivalent of the following (assuming widget has a statically known type, not the equivalent of a dyn Focus):

if constexpr (widget.can_focus()) {
  widget.focus();
}

…and if the widget has no “focus()” method, but the can_focus() was like a Rust “const” function that returned false, the code would still compile.

SFINAE is a deeper subject, but in short the if contexpr is a simpler more modern-C+±ish way of expressing what has been possible with much more complex C++ template metaprogramming since the beginning.

Honestly, I rather like the comparative simplicity of Rust’s generic programming stuff. Unlike C++, I can generally read and understand it!

1 Like

It’s something I considered. The problem with the word focus is that it’s a verb and a noun, so idiomatically focus(true) is wrong, set_focus(true) is probably the right compromise there, but is_focused_mut() is another approach that could also work. There’s also the consideration of using enums rather than bools, but I can’t think of an obvious name other than say:

enum FocusState {
    Focused,
    Blurred,
}

Which might be ok (and perhaps a better convention than is_focused. There’s a bunch of possible options there on naming, which I deferred bikeshedding over for now, focusing (pun intended) on the core idea (derive macro and trait).

Just looked up the Win32 API for losing focus; the event was named WM_KILL_FOCUS. Names of things involved lots of killing back then. Any of these options are improvements! :wink:

1 Like

Something interesting over in Bevy-land: Keyboard Focus, Global Shortcuts and LWIM · bevyengine/bevy · Discussion #15374 · GitHub

This covers the interaction between events, bubbling, focus, etc. pretty well. In a world where you’re not doing ECS, a bevy Focus resource might instead map to a mpsc channel which allows you to send a Focus event. I think you’d have to also have to make some sort of widget ID tracking system, and update that with render locations on each frame (to handle clicks etc.). Using IDs instead of storing widgets themselves would also allow you to track track the widget hierarchy outside of the widgets themselves.

Random thoughts recorded for posterity.