#focus #interface #ui #graphics #gui

macro iced_focus_derive

A proc-macro to derive a focus chain for Iced applications

2 releases

0.1.1 Aug 13, 2021
0.1.0 Aug 11, 2021

#67 in #focus


Used in iced_focus

MIT license

19KB
436 lines

iced_focus

This crates adds a procedural macro to derive a focus chain for your application state. This allows the user of your iced application to swap the focus between the input fields of your user interface using Tab and Shift+Tab. The order of the focus chain will be derived of the order of the fields of your state. The crate iced_focus serves as a workaround until iced provides it's own focusing.

How to use

Add iced_focus to your dependencies:

[dependencies]
iced_focus = { version = "0.1.1", features = ["derive"] }
iced = "0.3.0"
iced_native = "0.4.0"

Note: you also need iced_native for the keyboard subscription

Then derive the Focus trait for your State:

use iced::text_input;
use iced_focus::Focus;

#[derive(Focus)]
struct Example {
    text: String,
    #[focus(enable)]
    text_input: text_input::State,
}

As there is no way of knowing whether a field implements a specific trait, you will have to annotate each input field with #focus(enable) to add the field to the focus chain.

To handle the keyboard input of the user add an additional message like Focus(iced_focus::Direction) to your applications message definition:

enum Message {
    TextInput(String),
    // Add this:
    Focus(iced_focus::Direction),
}

Add this subscription to your application ...:

impl Application for Example {
    ...
    fn subscription(&self) -> Subscription<Message> {
        iced_native::subscription::events_with(|event, _status| {
            if let iced_native::Event::Keyboard(iced::keyboard::Event::KeyPressed {
                key_code: iced_native::keyboard::KeyCode::Tab,
                modifiers,
            }) = event
            {
                Some(Message::Focus(if modifiers.shift {
                    iced_focus::Direction::Backwards
                } else {
                    iced_focus::Direction::Forwards
                }))
            } else {
                None
            }
        })
    }
    ...
}

... and handle the focus message:

impl Application for Example {
    ...
    fn update(...) -> iced::Command<Self::Message> {
        match message {
            Message::TextInput(value) => self.text = value,

            // Add this:
            Message::Focus(direction) => {
                let _ = self.focus(direction);
            }
        }

        iced::Command::none()
    }
    ...
}

Done! Happy focusing! 🙂

You can specify whether the field will be added to the focus chain by providing a path to a method. The method must be of kind Fn(&self) -> bool.

struct Example {
    text: String,
    enable: bool,
    #[focus(enable = "self.enable_text_input")]
    text_input: text_input::State,
}

impl Example {
    fn enable_text_input(&self) -> bool {
        self.enable
    }
}

What is supported by this crate?

Currently, only the TextInput widget is supported as it is the only widget that supports focusing. This crate only provides a linear focus chain based on the ordering of the fields. The actual position of the element on the window in unknown to the application state.

You can derive the Focus trait for structs, tuple structs and enums:

#[derive(Focus)]
struct ExampleTuple (
    String,
    #[focus(enable)]
    text_input::State,
);

#[derive(Focus)]
enum ExampleEnum {
    Unit,
    Named {
        text: String,
        #[focus(enable)]
        text_input: text_input::State,
    },
    Unnamed (
        String,
        #[focus(enable)]
        text_input::State,
    )
}

You can have vecs, arrays, options or nested structs:

#[derive(Focus)]
struct ExampleVec {
    #[focus(enable)]
    text_inputs: Vec<text_input::State>,
}

#[derive(Focus)]
struct ExampleArray {
    #[focus(enable)]
    text_inputs: [text_input::State; 10],
}

#[derive(Focus)]
struct ExampleOption {
    #[focus(enable)]
    text_input: Option<text_input::State>,
}

#[derive(Focus)]
struct Example {
    #[focus(enable)]
    vec: ExampleVec,

    #[focus(enable)]
    array: ExampleArray,

    #[focus(enable)]
    option: ExampleOption,
}

Dependencies

~1.5MB
~35K SLoC