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 Widgets.

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 contraints, 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!("#{:X}{:X}{:X}", r, g, b)
    })
    .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),
        }
    }
}

todo

v controller, painter

  • how to do layout
    • how constraints work
    • child widget, set_layout_rect
    • paint bounds
  • container widgets
  • widgetpod & architecture
  • commands and widgetid
  • focus / active / hot
  • request paint & request layout
  • changing widgets at runtime