Introduction to Druid
A simple explanation of what Druid is and why you want to use it (this could also be copied to the crates.io page)
Set up Druid
This tutorial assumes basic familliarity with Rust and a working setup with the basic tooling like Rustup and Cargo. This tutorial will use stable Rust (v1.39.0 at the time of writing) and the latest released version of Druid.
This tutorial will first walk you through setting up the dependencies for developing a Druid application, then it will show you how to set up a basic application, build it and run it.
Setting up Druid dependencies
In addition to including the druid library in your project
macOS
On macOS, druid requires cairo; if you use homebrew, brew install cairo
should be sufficient. Removing this dependency is on the roadmap.
Linux
On Linux, druid requires gtk+3.
On Ubuntu this can be installed with
sudo apt-get install libgtk-3-dev
On Fedora
sudo dnf install gtk3-devel glib2-devel
See gtk-rs dependencies for more installation instructions.
Starting a project
Starting a project is as easy as creating an empty application with
cargo new my-application
and adding the druid dependency to your Cargo.toml
[dependencies]
druid = "0.4.0"
Get started with Druid
This chapter will walk you through setting up a simple druid application from start to finish.
Set up a Druid project
Setting up a project is a simple as creating a new Rust project;
> cargo new druid-example
And then adding druid as a dependency to Cargo.toml
[dependencies]
druid = "0.4.0"
To show a minimal window with a label replace main.rs
with this;
use druid::{AppLauncher, WindowDesc, Widget}; use druid::widget::Label; use druid::shell::Error; fn build_ui() -> impl Widget<()> { Label::new("Hello world") } fn main() -> Result<(), Error> { AppLauncher::with_window(WindowDesc::new(build_ui)).launch(())?; Ok(()) }
In our main function we create an AppLauncher
, pass it a WindowDesc
that wraps build_ui function and launch it. Druid will use our build_ui
function to build and rebuild our main window every time it needs to refresh. build_ui
returns a tree of widgets. For now this tree consists of one simple label widget.
This is a very simple example application and it's missing some important pieces. We will add these in the coming few paragraphs.
Draw more widgets
The first thing we could do to make our example application more interesting is to draw more than one widget. Unfortunately WindowDesc::new
expects a function that returns only one Widget. We also need a way to tell Druid how to lay-out our widgets.
We solve both these problems by passing in a widget-tree with one single widget at the top. Widgets can have children and widgets higher up in the tree know how to lay-out their children. That way we describe a window as a widget-tree with layout containers as the branches and widgets as the leaves. Our build_ui
function is then responsible for building this widget tree.
To see how this works we will divide our window in four. We'll have two rows and two columns with a single label in each of the quadrants. We can lay-out our labels using the Flex
widget.
#![allow(unused_variables)] fn main() { fn build_ui() -> impl Widget<()> { Flex::row() .with_child( Flex::column() .with_child(Label::new("top left"), 1.0) .with_child(Label::new("bottom left"), 1.0), 1.0) .with_child( Flex::column() .with_child(Label::new("top right"), 1.0) .with_child(Label::new("bottom right"), 1.0), 1.0) } }
This looks nice but the labels on the left are drawn right against the window edge, so we needs some padding. Lets say we also want to center the two bottom labels. Unlike many other UI frameworks, widgets in Druid don't have padding or alignment properties themselves. Widgets are kept as simple as possible.
Features like padding or alignment are implemented in separate widgets. To add padding you simply wrap the labels in a Padding
widget. Centering widgets is done using the Align
widget set to centered
.
#![allow(unused_variables)] fn main() { fn build_ui() -> impl Widget<()> { Padding::new( 10.0, Flex::row() .with_child( Flex::column() .with_child(Label::new("top left"), 1.0) .with_child(Align::centered(Label::new("bottom left")), 1.0), 1.0) .with_child( Flex::column() .with_child(Label::new("top right"), 1.0) .with_child(Align::centered(Label::new("bottom right")), 1.0), 1.0)) } }
Do not forget to import the new widgets;
#![allow(unused_variables)] fn main() { use druid::widget::{Label, Flex, Padding, Align}; }
Application state
We can display a window and draw and position widgets in it. Now it's time to find out how we can tie these widgets to the rest of our application. First lets see how we can display information from our application in the user interface. For this we need to define what our application's state looks like.
...
Handle user input
...
Putting it all together
...
Draw the user interface
This chapter will describe how to build up your user interface
Handling user input
This chapter will describe how to wire up your user interface to your Rust code
Handling state
This chapter will describe how to handle state in your application
Create custom widgets
This chapter will describe how to create a custom widget
More information
If you want more information about Druid this document contains links more tutorials, blogposts and youtube videos.
Related projects
These three projects provide the basis that Druid works on
- Piet An abstraction for 2D graphics.
- Kurbo A Rust library for manipulating curves
- Skribo A Rust library for low-level text layout
Projects using Druid
Presentations
Some presentations about Druid, its background and related topics have been recorded
- Declarative UI patterns in Rust by Raph Levien at the Bay Area Rust Meetup December 3 2019
- Data oriented GUI in Rust by Raph Levien at the Bay Area Rust Meetup June 28 2018
Blog posts
People have been blogging about Druid
- Building a widget for Druid a blog post by Paul Miller on how to create custom Widgets that explains lots of Druid on the way