pub trait Widget:
Send
+ Debug
+ 'static {
Show 23 methods
// Required method
fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_>);
// Provided methods
fn summarize(&self, f: &mut Formatter<'_>) -> Result { ... }
fn full_control_redraw(&self) -> bool { ... }
fn layout(
&mut self,
available_space: Size<ConstraintLimit>,
context: &mut LayoutContext<'_, '_, '_, '_>,
) -> Size<UPx> { ... }
fn mounted(&mut self, context: &mut EventContext<'_>) { ... }
fn unmounted(&mut self, context: &mut EventContext<'_>) { ... }
fn hit_test(
&mut self,
location: Point<Px>,
context: &mut EventContext<'_>,
) -> bool { ... }
fn hover(
&mut self,
location: Point<Px>,
context: &mut EventContext<'_>,
) -> Option<CursorIcon> { ... }
fn unhover(&mut self, context: &mut EventContext<'_>) { ... }
fn accept_focus(&mut self, context: &mut EventContext<'_>) -> bool { ... }
fn focus(&mut self, context: &mut EventContext<'_>) { ... }
fn advance_focus(
&mut self,
direction: VisualOrder,
context: &mut EventContext<'_>,
) -> EventHandling { ... }
fn allow_blur(&mut self, context: &mut EventContext<'_>) -> bool { ... }
fn blur(&mut self, context: &mut EventContext<'_>) { ... }
fn activate(&mut self, context: &mut EventContext<'_>) { ... }
fn deactivate(&mut self, context: &mut EventContext<'_>) { ... }
fn mouse_down(
&mut self,
location: Point<Px>,
device_id: DeviceId,
button: MouseButton,
context: &mut EventContext<'_>,
) -> EventHandling { ... }
fn mouse_drag(
&mut self,
location: Point<Px>,
device_id: DeviceId,
button: MouseButton,
context: &mut EventContext<'_>,
) { ... }
fn mouse_up(
&mut self,
location: Option<Point<Px>>,
device_id: DeviceId,
button: MouseButton,
context: &mut EventContext<'_>,
) { ... }
fn keyboard_input(
&mut self,
device_id: DeviceId,
input: KeyEvent,
is_synthetic: bool,
context: &mut EventContext<'_>,
) -> EventHandling { ... }
fn ime(&mut self, ime: Ime, context: &mut EventContext<'_>) -> EventHandling { ... }
fn mouse_wheel(
&mut self,
device_id: DeviceId,
delta: MouseScrollDelta,
phase: TouchPhase,
context: &mut EventContext<'_>,
) -> EventHandling { ... }
fn root_behavior(
&mut self,
context: &mut EventContext<'_>,
) -> Option<(RootBehavior, WidgetInstance)> { ... }
}
Expand description
A type that makes up a graphical user interface.
This type can go by many names in other UI frameworks: View, Component, Control.
§Widgets are hierarchical
Cushy’s widgets are organized in a hierarchical structure: widgets can contain other widgets. A window in Cushy contains a single root widget, which may contain one or more additional widgets.
§How Widgets are created
Cushy offers several approaches to creating widgets. The primary trait that
is used to instantiate a widget is MakeWidget
. This trait is
automatically implemented for all types that implement Widget
.
MakeWidget::make_widget
is responsible for returning a
WidgetInstance
. This is a wrapper for a type that implements Widget
that can be used without knowing the original type of the Widget
.
While all MakeWidget
is automatically implemented for all Widget
types, it can also be implemented by types that do not implement Widget
.
This is a useful strategy when designing reusable widgets that are able to
be completely represented by composing existing widgets. The
ProgressBar
type uses this strategy, as it
uses either a Spinner
or a
Slider
to show its progress.
One last convenience trait is provided to help create widgets that contain
exactly one child: WrapperWidget
. WrapperWidget
exposes most of the
same functions, but provides purpose-built functions for tweaking child’s
layout and rendering behavior to minimize the amount of redundant code
between these types of widgets.
§Identifying Widgets
Once a widget has been instantiated as a WidgetInstance
, it will be
assigned a unique WidgetId
. Sometimes, it may be helpful to pre-create a
WidgetId
before the widget has been created. For these situations,
WidgetTag
allows creating a tag that can be passed to
MakeWidgetWithTag::make_with_tag
to set the returned
WidgetInstance
’s id.
§How to “talk” to another widget
Once a widget has been wrapped inside of a WidgetInstance
, it is no
longer possible to invoke Widget
/s functions directly. Instead, a
context must be created for that widget. In each of the Widget
functions, a context is provided that represents the current widget. Each
context type has a for_other()
function that accepts any widget type: a
WidgetId
, a WidgetInstance
, a MountedWidget
, or a WidgetRef
.
The returned context will represent the associate widget, allowing access to
the exposed APIs through the context.
While WidgetInstance::lock
can be used to gain access to the underlying
Widget
type, this behavior should only be reserved for limited
situations. It should be preferred to pass data between widgets using
Dynamic
s or style components if possible. This ensures that your code
can work with as many other widgets as possible, instead of restricting
features to a specific set of types.
§How layout and rendering works
When a window is rendered, the root widget has its
layout()
function called with both constraints specifying
ConstraintLimit::SizeToFit
with the window’s inner size. The root widget
measures its content to try to fit within the specified constraints, and
returns its calculated size. If a widget has children, it can invoke
LayoutContext::layout()
on a context for each of its children to
determine their required sizes.
Next, the window sets the root’s layout. When a widget contains another
widget, it must call LayoutContext::set_child_layout
for the child to be
able to be rendered. This tells Cushy the location to draw the widget. While
it is possible to provide any rectangle, Cushy clips all widgets and their
children so that they cannot draw outside of their assigned bounds.
Once the layout has been determined, the window will invoke the root
widget’s redraw()
function. If a widget contains one or
more children, it needs to invoke GraphicsContext::redraw()
on a context
for each of its children during its own render function. This allows full
control over the order of drawing calls, allowing widgets to draw behind,
in-between, or in front of their children.
The last responsibility the window has each frame is size adjustment. The
window will potentially adjust its size automatically based on the root
widget’s root_behavior()
.
§Controlling Invalidation and Redrawing
Cushy only redraws window contents when requested by the operating system or
a tracked Dynamic
is updated. Similarly, Cushy caches the known layout
sizes and locations for widgets unless they are invalidated. Invalidation
is done automatically when the window size changes or a tracked Dynamic
is updated.
These systems require Cushy to track which Dynamic
values a widget
depends on for redrawing and invalidation. During a widget’s redraw and
layout functions, it needs to ensure that all depended upon Dynamic
s are
tracked using one of the various
*_tracking_redraw()
/*_tracking_invalidate()
functions. For example,
Source::get_tracking_redraw()
and
Source::get_tracking_invalidate()
.
§Hover State: Hit Testing
Before any cursor-related events are sent to a widget, the cursor’s position
is tested with Widget::hit_test
. When a widget returns true for a
position, it is eligible to receive events such as mouse buttons.
When a widget returns false, it will not receive any cursor related events
with one exception: hover events. Hover events will fire for widgets whose
children are currently being hovered, regardless of whether
Widget::hit_test
returned true.
The provided Widget::hit_test
implementation returns false.
As the cursor moves across the window, the window will look at the render
information to see what widgets are positioned under the cursor and the
order in which they were drawn. Beginning at the topmost widget,
Widget::hit_test
is called on each widget.
The currently hovered widget state is tracked for events that target widgets beneath the current cursor.
§Mouse Button Events
When a window receives an event for a mouse button being pressed, it calls
the hovered widget’s mouse_down()
function. If the
function returns HANDLED
/ControlFlow::Break
, the widget becomes the
tracking widget for that mouse button.
If the widget returns IGNORED
/ControlFlow::Continue
, the window will
call the parent’s mouse_down()
function. This repeats until the root
widget is reached or a widget returns HANDLED
.
Once a tracking widget is found, any cursor-related movements will cause
Widget::mouse_drag()
to be called. Upon the mouse button being released,
the tracking widget’s mouse_up()
function will be
called.
§User Input Focus
A window can have a widget be focused for user input. For example, a text
Input
only responds to keyboard input once user
input focus has been directed at the widget. This state is generally
represented by drawing the theme’s highlight color around the border of the
widget. GraphicsContext::draw_focus_ring
can be used to draw the
standard focus ring for rectangular-shaped widgets.
The most direct way to give a widget focus is to call
WidgetContext::focus
. However, not all widgets can accept focus. If a
widget returns true from its accept_focus()
function, focus will be given to it and its focus()
function will be invoked.
If a widget returns false from its accept_focus()
function, the window
will perform these steps:
- If the widget has any children, sort its children visually and attempt to focus each one until a widget accepts focus. If any of these children have children, those children should also be checked.
- The widget asks its parent to find the next focus after itself. The parent finds the current widget in that list and attempts to focus each widget after the current widget in the visual order.
- This repeats until the root widget is reached, at which point focus is attempted using this algorithm until either a focused widget is found or the original widget is reached again. If no widget can be found in a full cycle of the widget tree, focus will be cleared.
When a window first opens, it call focus()
on the
root widget’s context.
§Losing Focus
A Widget can deny the ability for focus to be taken away from it by
returning false
from Widget::allow_blur()
. In general, widgets should
not do this. However, some user interfaces are designed to always keep focus
on a single widget, and this feature enables that functionality.
When a widget currently has focused and loses it, its blur()
function will be invoked.
§Styling
Cushy allows widgets to receive styling information through the widget
hierarchy using Styles
. Cushy calculates the effectives styles for each
widget by inheriting all inheritable styles from its parent.
The Style
widget allows assigining Styles
to all of its children
widget. It works by calling WidgetContext::attach_styles
, and Cushy
takes care of the rest.
Styling in Cushy aims to be simple, easy-to-understand, and extensible.
§Color Themes
Cushy aims to make it easy for developers to customize the appearance of its
applications. The way color themes work in Cushy begins with the
ColorScheme
. A color scheme is a set of
ColorSource
that are used to generate a
variety of shades of colors for various roles color plays in a user
interface. In a way, coloring Cushy apps is a bit like paint-by-number,
where the number is the name of the color role.
A ColorScheme
can be used to create a ThemePair
, which is theme
definition that a theme for light and dark mode.
In the repository, the theme
example is a good way to explore how
the color system works in Cushy.
Required Methods§
sourcefn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_>)
fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_>)
Redraw the contents of this widget.
Provided Methods§
sourcefn summarize(&self, f: &mut Formatter<'_>) -> Result
fn summarize(&self, f: &mut Formatter<'_>) -> Result
Writes a summary of this widget into fmt
.
The default implementation calls Debug::fmt
. This function allows
widget authors to print only publicly relevant information that will
appear when debug formatting a WidgetInstance
.
sourcefn full_control_redraw(&self) -> bool
fn full_control_redraw(&self) -> bool
Returns true if this widget handles all built-in style components that apply.
These components are:
Opacity
WidgetBackground
- [
FontFamily
] - [
TextSize
] - [
LineHeight
] - [
FontStyle
] - [
FontWeight
]
sourcefn layout(
&mut self,
available_space: Size<ConstraintLimit>,
context: &mut LayoutContext<'_, '_, '_, '_>,
) -> Size<UPx>
fn layout( &mut self, available_space: Size<ConstraintLimit>, context: &mut LayoutContext<'_, '_, '_, '_>, ) -> Size<UPx>
Layout this widget and returns the ideal size based on its contents and
the available_space
.
sourcefn mounted(&mut self, context: &mut EventContext<'_>)
fn mounted(&mut self, context: &mut EventContext<'_>)
The widget has been mounted into a parent widget.
Widgets that contain MountedWidget
references should call
MountedWidget::remount_if_needed
in this function.
sourcefn unmounted(&mut self, context: &mut EventContext<'_>)
fn unmounted(&mut self, context: &mut EventContext<'_>)
The widget has been removed from its parent widget.
sourcefn hit_test(
&mut self,
location: Point<Px>,
context: &mut EventContext<'_>,
) -> bool
fn hit_test( &mut self, location: Point<Px>, context: &mut EventContext<'_>, ) -> bool
Returns true if this widget should respond to mouse input at location
.
This function is critical for how event propagation works for these functions:
See Hover State: Hit Testing for an explanation of how these events work together.
sourcefn hover(
&mut self,
location: Point<Px>,
context: &mut EventContext<'_>,
) -> Option<CursorIcon>
fn hover( &mut self, location: Point<Px>, context: &mut EventContext<'_>, ) -> Option<CursorIcon>
The widget is currently has a cursor hovering it at location
.
This function will not be invoked if Self::hit_test
returns false.
See Hover State: Hit Testing for more
information on how hover state is handled in Cushy.
sourcefn unhover(&mut self, context: &mut EventContext<'_>)
fn unhover(&mut self, context: &mut EventContext<'_>)
The widget is no longer being hovered.
This function will only be invoked after Self::hover
.
sourcefn accept_focus(&mut self, context: &mut EventContext<'_>) -> bool
fn accept_focus(&mut self, context: &mut EventContext<'_>) -> bool
This widget has been targeted to be focused. If this function returns true, the widget will be focused. If false, Cushy will continue searching for another focus target.
sourcefn focus(&mut self, context: &mut EventContext<'_>)
fn focus(&mut self, context: &mut EventContext<'_>)
The widget has received focus for user input.
sourcefn advance_focus(
&mut self,
direction: VisualOrder,
context: &mut EventContext<'_>,
) -> EventHandling
fn advance_focus( &mut self, direction: VisualOrder, context: &mut EventContext<'_>, ) -> EventHandling
The widget should switch to the next focusable area within this widget,
honoring direction
in a consistent manner. Returning HANDLED
will
cause the search for the next focus widget stop.
sourcefn allow_blur(&mut self, context: &mut EventContext<'_>) -> bool
fn allow_blur(&mut self, context: &mut EventContext<'_>) -> bool
The widget is about to lose focus. Returning true allows the focus to switch away from this widget.
sourcefn blur(&mut self, context: &mut EventContext<'_>)
fn blur(&mut self, context: &mut EventContext<'_>)
The widget is no longer focused for user input.
sourcefn activate(&mut self, context: &mut EventContext<'_>)
fn activate(&mut self, context: &mut EventContext<'_>)
The widget has become the active widget.
sourcefn deactivate(&mut self, context: &mut EventContext<'_>)
fn deactivate(&mut self, context: &mut EventContext<'_>)
The widget is no longer active.
sourcefn mouse_down(
&mut self,
location: Point<Px>,
device_id: DeviceId,
button: MouseButton,
context: &mut EventContext<'_>,
) -> EventHandling
fn mouse_down( &mut self, location: Point<Px>, device_id: DeviceId, button: MouseButton, context: &mut EventContext<'_>, ) -> EventHandling
A mouse button event has occurred at location
. Returns whether the
event has been handled or not.
If an event is handled, the widget will receive callbacks for
mouse_drag
and mouse_up
. See
Mouse Button Events for more information on
how mouse events work in Cushy.
This function will only be invoked if it or a child is the currently hovered widget. See Hover State: Hit Testing for more information on how hover state is handled in Cushy.
sourcefn mouse_drag(
&mut self,
location: Point<Px>,
device_id: DeviceId,
button: MouseButton,
context: &mut EventContext<'_>,
)
fn mouse_drag( &mut self, location: Point<Px>, device_id: DeviceId, button: MouseButton, context: &mut EventContext<'_>, )
A mouse button is being held down as the cursor is moved across the widget.
This function will only be invoked if Self::mouse_down
returns
HANDLED
. See Mouse Button Events for
more information on how mouse events work in Cushy.
sourcefn mouse_up(
&mut self,
location: Option<Point<Px>>,
device_id: DeviceId,
button: MouseButton,
context: &mut EventContext<'_>,
)
fn mouse_up( &mut self, location: Option<Point<Px>>, device_id: DeviceId, button: MouseButton, context: &mut EventContext<'_>, )
A mouse button is no longer being pressed.
This function will only be invoked if Self::mouse_down
returns
HANDLED
. See Mouse Button Events for
more information on how mouse events work in Cushy.
sourcefn keyboard_input(
&mut self,
device_id: DeviceId,
input: KeyEvent,
is_synthetic: bool,
context: &mut EventContext<'_>,
) -> EventHandling
fn keyboard_input( &mut self, device_id: DeviceId, input: KeyEvent, is_synthetic: bool, context: &mut EventContext<'_>, ) -> EventHandling
A keyboard event has been sent to this widget. Returns whether the event has been handled or not.
sourcefn ime(&mut self, ime: Ime, context: &mut EventContext<'_>) -> EventHandling
fn ime(&mut self, ime: Ime, context: &mut EventContext<'_>) -> EventHandling
An input manager event has been sent to this widget. Returns whether the event has been handled or not.
sourcefn mouse_wheel(
&mut self,
device_id: DeviceId,
delta: MouseScrollDelta,
phase: TouchPhase,
context: &mut EventContext<'_>,
) -> EventHandling
fn mouse_wheel( &mut self, device_id: DeviceId, delta: MouseScrollDelta, phase: TouchPhase, context: &mut EventContext<'_>, ) -> EventHandling
A mouse wheel event has been sent to this widget. Returns whether the event has been handled or not.
This function will only be invoked if it or a child is the currently hovered widget. See Hover State: Hit Testing for more information on how hover state is handled in Cushy.
sourcefn root_behavior(
&mut self,
context: &mut EventContext<'_>,
) -> Option<(RootBehavior, WidgetInstance)>
fn root_behavior( &mut self, context: &mut EventContext<'_>, ) -> Option<(RootBehavior, WidgetInstance)>
Returns a reference to a single child widget if this widget is a widget that primarily wraps a single other widget to customize its behavior.