pub trait Widget: WidgetChildren + Layout {
    fn pre_configure(&mut self, mgr: &mut ConfigMgr<'_>, id: WidgetId);

    fn configure(&mut self, mgr: &mut ConfigMgr<'_>) { ... }
    fn navigable(&self) -> bool { ... }
    fn translation(&self) -> Offset { ... }
    fn nav_next(
        &mut self,
        mgr: &mut ConfigMgr<'_>,
        reverse: bool,
        from: Option<usize>
    ) -> Option<usize> { ... } fn handle_event(&mut self, mgr: &mut EventMgr<'_>, event: Event) -> Response { ... } fn steal_event(
        &mut self,
        mgr: &mut EventMgr<'_>,
        id: &WidgetId,
        event: &Event
    ) -> Response { ... } fn handle_unused(
        &mut self,
        mgr: &mut EventMgr<'_>,
        index: usize,
        event: Event
    ) -> Response { ... } fn handle_message(&mut self, mgr: &mut EventMgr<'_>, index: usize) { ... } fn handle_scroll(&mut self, mgr: &mut EventMgr<'_>, scroll: Scroll) { ... } }
Expand description

The Widget trait

Widgets implement a family of traits, of which this trait is the final member:

  • WidgetCore — base functionality
  • WidgetChildren — enumerates children
  • Layout — handles sizing and positioning for self and children
  • Widget — configuration, some aspects of layout, event handling

Implementing Widget

To implement a widget, use the macros::widget macro. This is the only supported method of implementing Widget.

The macros::widget macro only works within macros::impl_scope. Other trait implementations can be detected within this scope:

  • WidgetCore is always generated
  • WidgetChildren is generated if no direct implementation is present
  • Layout is generated if the layout attribute property is set, and no direct implementation is found. In other cases where a direct implementation of the trait is found, (default) method implementations may be injected where not already present.
  • Widget is generated if no direct implementation is present, otherwise some (default) method implementations are injected where these methods are not directly implemented.

Some simple examples follow. See also examples apps and kas_widgets code.

use kas::prelude::*;
use kas::event;
use kas::theme::TextClass;
use std::fmt::Debug;

impl_scope! {
    /// A text label
    #[derive(Clone, Debug)]
    #[widget]
    pub struct AccelLabel {
        core: widget_core!(),
        class: TextClass,
        label: Text<AccelString>,
    }

    impl Self {
        /// Construct from `label`
        pub fn new(label: impl Into<AccelString>) -> Self {
            AccelLabel {
                core: Default::default(),
                class: TextClass::AccelLabel(true),
                label: Text::new(label.into()),
            }
        }

        /// Set text class (inline)
        pub fn with_class(mut self, class: TextClass) -> Self {
            self.class = class;
            self
        }

        /// Get the accelerator keys
        pub fn keys(&self) -> &[event::VirtualKeyCode] {
            self.label.text().keys()
        }
    }

    impl Layout for Self {
        fn size_rules(&mut self, size_mgr: SizeMgr, mut axis: AxisInfo) -> SizeRules {
            axis.set_default_align_hv(Align::Default, Align::Center);
            size_mgr.text_rules(&mut self.label, self.class, axis)
        }

        fn set_rect(&mut self, mgr: &mut ConfigMgr, rect: Rect) {
            self.core.rect = rect;
            mgr.text_set_size(&mut self.label, self.class, rect.size, None);
        }

        fn draw(&mut self, mut draw: DrawMgr) {
            draw.text_effects(self.rect(), &self.label, self.class);
        }
    }
}

impl_scope! {
    /// A push-button with a text label
    #[derive(Debug)]
    #[widget {
        layout = button: self.label;
        navigable = true;
        hover_highlight = true;
    }]
    pub struct TextButton<M: Clone + Debug + 'static> {
        core: widget_core!(),
        #[widget]
        label: AccelLabel,
        message: M,
    }

    impl Self {
        /// Construct a button with given `label`
        pub fn new(label: impl Into<AccelString>, message: M) -> Self {
            TextButton {
                core: Default::default(),
                label: AccelLabel::new(label).with_class(TextClass::Button),
                message,
            }
        }
    }
    impl Widget for Self {
        fn configure(&mut self, mgr: &mut ConfigMgr) {
            mgr.add_accel_keys(self.id_ref(), self.label.keys());
        }

        fn handle_event(&mut self, mgr: &mut EventMgr, event: Event) -> Response {
            event.on_activate(mgr, self.id(), |mgr| {
                mgr.push_msg(self.message.clone());
                Response::Used
            })
        }
    }
}

Required Methods

Pre-configuration

This method is called before children are configured to assign a WidgetId. Usually it does nothing else, but a custom implementation may be used to affect child configuration, e.g. via EventState::new_accel_layer.

Default impl: assign id to self

Provided Methods

Configure widget

Widgets are configured on window creation or dynamically via the parent calling ConfigMgr::configure. Parent widgets are responsible for ensuring that children are configured before calling Layout::size_rules or Layout::set_rect. Configuration may be repeated and may be used as a mechanism to change a child’s WidgetId, but this may be expensive.

This method may be used to configure event handling and to load resources, including resources affecting Layout::size_rules.

The window’s scale factor (and thus any sizes available through ConfigMgr::size_mgr) may not be correct initially (some platforms construct all windows using scale factor 1) and/or may change in the future. Changes to the scale factor result in recalculation of Layout::size_rules but not repeated configuration.

Is this widget navigable via Tab key?

Defaults to false.

Get translation of children relative to this widget

Usually this is zero; only widgets with scrollable or offset content and child widgets need to implement this. Such widgets must also implement Widget::handle_scroll.

Affects event handling via Layout::find_id and affects the positioning of pop-up menus. Layout::draw must be implemented directly using DrawMgr::with_clip_region to offset contents.

Navigation in spatial order

Controls Tab navigation order of children. This method should:

  • Return None if there is no next child
  • Determine the next child after from (if provided) or the whole range, optionally in reverse order
  • Ensure that the selected widget is addressable through WidgetChildren::get_child

Both from and the return value use the widget index, as used by WidgetChildren::get_child.

Default implementation:

  • Generated from #[widget]’s layout property, if used
  • Otherwise, iterate through children in order of definition

Handle an event sent to this widget

An Event is some form of user input, timer or notification.

This is the primary event handler for a widget. Secondary handlers are:

Default implementation: do nothing; return Response::Unused.

Calling handle_event

It is not recommended to call handle_event directly except on self. Doing so would miss related event handling code such as cursor-hover effects and calling other event-handling methods on parents. Instead, one should call EventMgr::send with the target’s id.

Potentially steal an event before it reaches a child

This is called on each widget while sending an event, including when the target is self. If this returns Response::Used, the event is not sent further.

Default implementation: return Response::Unused.

Handle an event sent to child index but left unhandled

Default implementation: call Self::handle_event with event.

Handler for messages from children/descendants

This method is called when a child leaves a message on the stack. Some parent or ancestor widget should read this message.

The default implementation does nothing.

Handler for scrolling

When a child calls EventMgr::set_scroll with a value other than Scroll::None, this method is called. (This method is not called after Self::handle_event or other handlers called on self.)

Note that Scroll::Rect values are in the child’s coordinate space, and must be translated to the widget’s own coordinate space by this method (this is not done by the default implementation since any widget with non-zero translation very likely wants to implement this method anyway).

If the child is in an independent coordinate space, then this method should call mgr.set_scroll(Scroll::None) to avoid any reactions to child’s scroll requests.

The default implementation does nothing.

Implementations on Foreign Types

Implementors