Create custom widgets
The Widget
trait is the heart of Druid, and in any serious application you
will eventually need to create and use custom Widget
s.
Painter
and Controller
There are two helper widgets in Druid that let you customize widget behaviour
without needing to implement the full widget trait: Painter
and
Controller
.
Painter
The Painter
widget lets you draw arbitrary custom content, but cannot
respond to events or otherwise contain update logic. Its general use is to
either provide a custom background to some other widget, or to implement
something like an icon or another graphical element that will be contained in
some other widget.
For instance, if we had some color data and we wanted to display it as a swatch
with rounded corners, we could use a Painter
:
fn make_color_swatch() -> Painter<Color> {
Painter::new(|ctx: &mut PaintCtx, data: &Color, env: &Env| {
let bounds = ctx.size().to_rect();
let rounded = bounds.to_rounded_rect(CORNER_RADIUS);
ctx.fill(rounded, data);
ctx.stroke(rounded, &env.get(druid::theme::PRIMARY_DARK), STROKE_WIDTH);
})
}
Painter
uses all the space that is available to it; if you want to give it a
set size, you must pass it explicit constraints, such as by wrapping it in a
SizedBox
:
fn sized_swatch() -> impl Widget<Color> {
SizedBox::new(make_color_swatch()).width(20.0).height(20.0)
}
One other useful thing about Painter
is that it can be used as the background
of a Container
widget. If we wanted to have a label that used our swatch
as a background, we could do:
fn background_label() -> impl Widget<Color> {
Label::dynamic(|color: &Color, _| {
let (r, g, b, _) = color.as_rgba8();
format!("#{r:X}{g:X}{b:X}")
})
.background(make_color_swatch())
}
(This uses the background
method on WidgetExt
to embed our label in a
container.)
Controller
The Controller
trait is sort of the inverse of Painter
; it is a way to
make widgets that handle events, but don't do any layout or drawing. The idea
here is that you can use some Controller
type to customize the behaviour of
some set of children.
The Controller
trait has event
, update
, and lifecycle
methods, just
like Widget
; it does not have paint
or layout
methods. Also unlike
Widget
, all of its methods are optional; you can override only the method
that you need.
There's one other difference to the Controller
methods; it is explicitly
passed a mutable reference to its child in each method, so that it can modify it
or forward events as needed.
As an arbitrary example, here is how you might use a Controller
to make a
textbox fire some action (say doing a search) 300ms after the last keypress:
const ACTION: Selector = Selector::new("hello.textbox-action");
const DELAY: Duration = Duration::from_millis(300);
struct TextBoxActionController {
timer: Option<TimerToken>,
}
impl TextBoxActionController {
pub fn new() -> Self {
TextBoxActionController { timer: None }
}
}
impl Controller<String, TextBox<String>> for TextBoxActionController {
fn event(
&mut self,
child: &mut TextBox<String>,
ctx: &mut EventCtx,
event: &Event,
data: &mut String,
env: &Env,
) {
match event {
Event::KeyDown(k) if k.key == Key::Enter => {
ctx.submit_command(ACTION);
}
Event::KeyUp(k) if k.key == Key::Enter => {
self.timer = Some(ctx.request_timer(DELAY));
child.event(ctx, event, data, env);
}
Event::Timer(token) if Some(*token) == self.timer => {
ctx.submit_command(ACTION);
}
_ => child.event(ctx, event, data, env),
}
}
}