Convert Figma logo to code with AI

linebender logodruid

A data-first Rust-native UI design toolkit.

9,491
566
9,491
299

Top Related Projects

24,093

A cross-platform GUI library for Rust, inspired by Elm

22,057

egui: an easy-to-use immediate mode GUI in Rust that runs on both web and native

17,220

Slint is a declarative GUI toolkit to build native user interfaces for Rust, C++, or JavaScript apps.

1,601

Rust bindings for the FLTK GUI library.

83,441

Build smaller, faster, and more secure desktop and mobile applications with a web frontend.

Quick Overview

Druid is a Rust-based, data-oriented, immediate-mode GUI framework designed for building high-performance, cross-platform applications. It focuses on providing a simple and efficient way to create user interfaces, with a strong emphasis on performance and scalability.

Pros

  • Performance: Druid is designed to be highly performant, with a focus on efficient rendering and event handling.
  • Cross-platform: Druid supports multiple platforms, including Windows, macOS, and Linux, allowing developers to create applications that can run on a variety of systems.
  • Immediate-mode: Druid uses an immediate-mode GUI approach, which simplifies the development process and makes it easier to manage complex UI state.
  • Rust-based: Druid is built using the Rust programming language, which provides strong type safety, memory safety, and concurrency support.

Cons

  • Learning Curve: Druid's immediate-mode approach and Rust-based implementation may have a steeper learning curve for developers who are more familiar with traditional GUI frameworks.
  • Ecosystem: Compared to more established GUI frameworks, Druid has a smaller ecosystem of third-party libraries and tools, which may limit the availability of pre-built components and solutions.
  • Documentation: While the Druid documentation is generally good, it may not be as comprehensive or user-friendly as the documentation for some other GUI frameworks.
  • Maturity: As a relatively new project, Druid may not have the same level of stability and feature completeness as more mature GUI frameworks.

Code Examples

Here are a few examples of how to use Druid:

  1. Creating a simple window:
use druid::widget::{Button, Flex, Label};
use druid::{AppLauncher, LocalizedString, PlatformError, Widget, WidgetExt, WindowDesc};

fn main() -> Result<(), PlatformError> {
    let main_window = WindowDesc::new(build_root_widget());
    AppLauncher::with_window(main_window)
        .launch(())?;
    Ok(())
}

fn build_root_widget() -> impl Widget<()> {
    Flex::column()
        .with_child(Label::new(LocalizedString::new("hello-counter")))
        .with_child(
            Button::new(LocalizedString::new("increment-counter"))
                .on_click(|_ctx, _data, _env| {
                    println!("Button clicked!");
                }),
        )
}
  1. Handling state and events:
use druid::{AppLauncher, Data, Lens, LocalizedString, PlatformError, Widget, WidgetExt, WindowDesc};
use druid::widget::{Button, Flex, Label};

#[derive(Clone, Data, Lens)]
struct CounterState {
    count: i32,
}

fn main() -> Result<(), PlatformError> {
    let main_window = WindowDesc::new(build_root_widget());
    AppLauncher::with_window(main_window)
        .launch(CounterState { count: 0 })?;
    Ok(())
}

fn build_root_widget() -> impl Widget<CounterState> {
    Flex::column()
        .with_child(Label::new(|data: &CounterState, _env: &_| {
            format!("The count is: {}", data.count)
        }))
        .with_child(
            Button::new(LocalizedString::new("increment-counter"))
                .on_click(|_ctx, data, _env| {
                    data.count += 1;
                }),
        )
}
  1. Using custom widgets:
use druid::{AppLauncher, Data, Lens, LocalizedString, PlatformError, Widget, WidgetExt, WindowDesc};
use druid::widget::{Flex, Label, Slider};

#[derive(Clone, Data, Lens)]
struct AppState {
    value: f64,
}

fn main() -> Result<(), PlatformError> {
    let main_window = WindowDesc::new(

Competitor Comparisons

24,093

A cross-platform GUI library for Rust, inspired by Elm

Pros of iced

  • More mature and stable API, with better documentation
  • Supports multiple backends (e.g., OpenGL, Vulkan, Metal)
  • Easier to get started with for beginners

Cons of iced

  • Less flexible and customizable than Druid
  • Slower performance for complex UIs
  • Limited support for custom widgets

Code Comparison

iced:

use iced::{button, Button, Column, Element, Sandbox, Settings, Text};

struct Counter {
    value: i32,
    increment_button: button::State,
    decrement_button: button::State,
}

impl Sandbox for Counter {
    // ... implementation details
}

Druid:

use druid::widget::{Button, Flex, Label};
use druid::{AppLauncher, Data, Lens, Widget, WidgetExt, WindowDesc};

#[derive(Clone, Data, Lens)]
struct CounterState {
    count: i32,
}

fn build_ui() -> impl Widget<CounterState> {
    // ... implementation details
}

Both iced and Druid are Rust GUI frameworks, but they have different approaches to building user interfaces. iced focuses on simplicity and ease of use, while Druid offers more flexibility and customization options. The code comparison shows that iced uses a more declarative style, while Druid employs a more imperative approach to UI construction.

22,057

egui: an easy-to-use immediate mode GUI in Rust that runs on both web and native

Pros of egui

  • Simpler API and easier to get started with
  • Immediate mode GUI, which can be more performant for certain use cases
  • Supports web assembly (WASM) out of the box

Cons of egui

  • Less feature-rich compared to Druid's widget ecosystem
  • Limited styling options and customization capabilities
  • Lacks some advanced layout features present in Druid

Code Comparison

egui example:

ui.heading("My egui Application");
ui.horizontal(|ui| {
    ui.label("Your name: ");
    ui.text_edit_singleline(&mut name);
});
if ui.button("Click me").clicked() {
    println!("Button clicked");
}

Druid example:

let main_widget = Flex::column()
    .with_child(Label::new("My Druid Application"))
    .with_child(Flex::row()
        .with_child(Label::new("Your name: "))
        .with_child(TextBox::new().lens(AppState::name)))
    .with_child(Button::new("Click me")
        .on_click(|_ctx, _data, _env| println!("Button clicked")));

The code comparison showcases the different approaches: egui uses an immediate mode style, while Druid employs a more declarative, widget-based structure. egui's API is more concise, but Druid offers more flexibility in layout and widget composition.

17,220

Slint is a declarative GUI toolkit to build native user interfaces for Rust, C++, or JavaScript apps.

Pros of Slint

  • Cross-platform support for desktop and embedded systems
  • Declarative UI design language with hot reloading
  • Integration with multiple programming languages (Rust, C++, JavaScript)

Cons of Slint

  • Younger project with potentially less mature ecosystem
  • Limited documentation and community resources compared to Druid
  • Steeper learning curve for developers new to declarative UI design

Code Comparison

Slint:

export component Button {
    text: string;
    clicked => { }
    Rectangle {
        background: blue;
        Text {
            text: root.text;
        }
    }
}

Druid:

struct Button {
    text: String,
}

impl Widget<String> for Button {
    fn event(&mut self, ctx: &mut EventCtx, event: &Event, data: &mut String, _env: &Env) {
        if let Event::MouseDown(_) = event {
            ctx.submit_command(CLICK_BUTTON);
        }
    }
}

The Slint example showcases its declarative syntax, while the Druid example demonstrates its widget-based approach using Rust. Slint's code is more concise and visually oriented, whereas Druid's code offers more fine-grained control over widget behavior.

1,601

Rust bindings for the FLTK GUI library.

Pros of fltk-rs

  • Lightweight and fast, with minimal dependencies
  • Provides a native look and feel across platforms
  • Offers both static and dynamic linking options

Cons of fltk-rs

  • Less modern and feature-rich compared to Druid
  • Smaller community and ecosystem
  • Limited built-in widgets and customization options

Code Comparison

fltk-rs example:

use fltk::{app, button::Button, frame::Frame, prelude::*, window::Window};

fn main() {
    let app = app::App::default();
    let mut wind = Window::new(100, 100, 400, 300, "Hello from rust");
    let mut frame = Frame::new(0, 0, 400, 200, "");
    let mut but = Button::new(160, 210, 80, 40, "Click me!");
    wind.end();
    wind.show();
    but.set_callback(move |_| frame.set_label("Hello World!"));
    app.run().unwrap();
}

Druid example:

use druid::{AppLauncher, PlatformError, Widget, WindowDesc};
use druid::widget::{Button, Flex, Label};

fn main() -> Result<(), PlatformError> {
    let main_window = WindowDesc::new(ui_builder());
    let data = 0_u32;
    AppLauncher::with_window(main_window)
        .launch(data)
}

fn ui_builder() -> impl Widget<u32> {
    Flex::column()
        .with_child(Label::new("Hello"))
        .with_child(Button::new("Click me!"))
}
83,441

Build smaller, faster, and more secure desktop and mobile applications with a web frontend.

Pros of Tauri

  • Cross-platform support for desktop and mobile
  • Smaller app size due to native OS components
  • Flexibility to use any web-based frontend framework

Cons of Tauri

  • Steeper learning curve for developers new to Rust
  • Less mature ecosystem compared to Electron-based alternatives
  • Limited to web technologies for UI development

Code Comparison

Tauri (JavaScript):

import { invoke } from '@tauri-apps/api/tauri'

async function greet(name) {
  return await invoke('greet', { name })
}

Druid (Rust):

use druid::widget::Label;
use druid::{AppLauncher, WindowDesc, Widget};

fn build_ui() -> impl Widget<()> {
    Label::new("Hello, Druid!")
}

Tauri uses a JavaScript API to invoke Rust functions, while Druid directly uses Rust for both backend and UI logic. Tauri's approach allows for more flexibility in frontend technologies, but Druid offers a more integrated Rust experience.

Tauri is better suited for developers comfortable with web technologies and seeking cross-platform support, while Druid is ideal for those who prefer a pure Rust solution and are focused on desktop applications.

Convert Figma logo designs to code with AI

Visual Copilot

Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.

Try Visual Copilot

README

druid banner

A data-first Rust-native UI toolkit.

crates.io docs.rs license chat

Druid is an experimental Rust-native UI toolkit. Its main goal is to offer a polished user experience. There are many factors to this goal, including performance, a rich palette of interactions (hence a widget library to support them), and playing well with the native platform. See the goals section for more details.

We have been doing periodic releases of Druid on crates.io, but the API is still unstable. All changes are documented in the changelog.

For an overview of some key concepts, see the (work in progress) Druid book.

Project status

The Druid project is being discontinued by the core developer team.

New development effort is focused on Xilem, which has a lot of fundamental changes to allow for a wider variety of applications with better performance, but it also heavily inherits from Druid. We see Xilem as the future of Druid.

Druid is reasonably usable for some subset of applications and has a significant testing history, which ensures some stability and correctness. However we don't expect any major new features to be added to Druid. As such we don't recommend using Druid for brand new applications. If you insist, then at least make sure your application doesn't require a feature that Druid doesn't have, e.g. accessibility or 3D support.

Contributions

As the Druid project is being discontinued, we will still accept all contributions, but we'll prefer bug fixes and documentation improvements to new features.

A very good place to ask questions and discuss development work is our Zulip chat instance, in the #druid-help and #druid channels, respectively.

We gladly accept contributions via GitHub pull requests. Please see CONTRIBUTING.md for more details.

Example

Here's a simple counter example app:

use druid::widget::{Button, Flex, Label};
use druid::{AppLauncher, LocalizedString, PlatformError, Widget, WidgetExt, WindowDesc};

fn main() -> Result<(), PlatformError> {
    let main_window = WindowDesc::new(ui_builder());
    let data = 0_u32;
    AppLauncher::with_window(main_window)
        .log_to_console()
        .launch(data)
}

fn ui_builder() -> impl Widget<u32> {
    // The label text will be computed dynamically based on the current locale and count
    let text =
        LocalizedString::new("hello-counter").with_arg("count", |data: &u32, _env| (*data).into());
    let label = Label::new(text).padding(5.0).center();
    let button = Button::new("increment")
        .on_click(|_ctx, data, _env| *data += 1)
        .padding(5.0);

    Flex::column().with_child(label).with_child(button)
}

Check out the the examples folder for a more comprehensive demonstration of Druid's existing functionality and widgets. Check druid_widget_nursery for more widgets.

Screenshots

calc.rs example flex.rs example custom_widget.rs example

Using Druid

An explicit goal of Druid is to be easy to build, so please open an issue if you run into any difficulties. Druid is available on crates.io and should work as a lone dependency (it re-exports all the parts of druid-shell, piet, and kurbo that you'll need):

druid = "0.8.3"

Since Druid is currently in fast-evolving state, you might prefer to drink from the firehose:

druid = { git = "https://github.com/linebender/druid.git" }

Platform notes

Linux

On Linux, Druid requires gtk+3; see GTK installation page. (On ubuntu-based distro, running sudo apt-get install libgtk-3-dev from the terminal will do the job.)

OpenBSD

On OpenBSD, Druid requires gtk+3; install from packages:

pkg_add gtk+3

Alternatively, there is an X11 backend available, although it is currently missing quite a few features. You can try it out with --features=x11.

Goals

Druid's goal is to make it easy to write and deploy high quality desktop applications with a smooth and polished user experience on all common platforms. In order to achieve this we strive for a variety of things:

  • Make it easy to build and package on all supported platforms.
  • Implement abstractions to avoid platform specific quirks.
  • Respect platform conventions and expectations.
  • Handle display resolution and scaling reliably with little effort.
  • Enable easy, yet powerful internationalization.
  • Offer robust accessibility support.
  • Produce small and fast binaries with low memory usage.
  • Have a small dependency tree, a high quality code base and good organization.
  • Focus on powerful, desktop-grade applications.
  • Provide a flexible set of layouts and common widgets.
  • Ease creation of custom components and application logic as needed.

Non-Goals

In order to fulfill those goals, we cannot support every use case. Luckily the Rust community is working on a variety of different libraries with different goals, so here are some of Druid's non-goals and possible alternatives that can offer those capabilities:

  • Use the platform-native widgets or mimic them. (Relm, Slint)
  • Embed easily into custom render pipelines. (Conrod)
  • Adhere to a specific architectural style such as Elm. (Iced, Relm)
  • Support rendering to HTML when targeting the web. (Iced, Moxie)

Druid is just one of many ongoing Rust-native GUI experiments. If it doesn't suit your use case, perhaps one of the others will!

Concepts

druid-shell

The Druid toolkit uses druid-shell for a platform-abstracting application shell. druid-shell is responsible for starting a native platform runloop, listening to events, converting them into a platform-agnostic representation, and calling a user-provided handler with them.

While druid-shell is being developed with the Druid toolkit in mind, it is intended to be general enough that it could be reused by other projects interested in experimenting with Rust GUI. The druid-shell crate includes a couple of non-druid examples.

piet

Druid relies on the Piet library for drawing and text layout. Piet is a 2D graphics abstraction with multiple backends: piet-direct2d, piet-coregraphics, piet-cairo, piet-web, and piet-svg are currently available. In terms of Druid platform support via Piet, macOS uses piet-coregraphics, Linux/OpenBSD/FreeBSD use piet-cairo, Windows uses piet-direct2d, and web uses piet-web.

use druid::kurbo::{BezPath, Point, Rect};
use druid::piet::Color;

// Create an arbitrary bezier path
// (ctx.size() returns the size of the layout rect we're painting in)
let mut path = BezPath::new();
path.move_to(Point::ORIGIN);
path.quad_to(
    (80.0, 90.0),
    (ctx.size().width, ctx.size().height),
);
// Create a color
let stroke_color = Color::rgb8(0x00, 0x80, 0x00);
// Stroke the path with thickness 1.0
ctx.stroke(path, &stroke_color, 1.0);

// Rectangles: the path for practical people
let rect = Rect::from_origin_size((10., 10.), (100., 100.));
// Note the Color:rgba8 which includes an alpha channel (7F in this case)
let fill_color = Color::rgba8(0x00, 0x00, 0x00, 0x7F);
ctx.fill(rect, &fill_color);

widgets

Widgets in Druid (text boxes, buttons, layout components, etc.) are objects which implement the Widget trait. The trait is parametrized by a type (T) for associated data. All trait methods (event, lifecycle, update, paint, and layout) are provided with access to this data, and in the case of event the reference is mutable, so that events can directly update the data.

Whenever the application data changes, the framework traverses the widget hierarchy with an update method.

All the widget trait methods are provided with a corresponding context (EventCtx, LifeCycleCtx, UpdateCtx, LayoutCtx, PaintCtx). The widget can request things and cause actions by calling methods on that context.

In addition, all trait methods are provided with an environment Env, which includes the current theme parameters (colors, dimensions, etc.).

impl<T: Data> Widget<T> for Button<T> {
    fn event(&mut self, ctx: &mut EventCtx, event: &Event, data: &mut T, env: &Env) {
      ...
    }

    fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle, data: &T, env: &Env) {
      ...
    }

    fn update(&mut self, ctx: &mut UpdateCtx, old_data: &T, data: &T, env: &Env) {
      ...
    }

    fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints, data: &T, env: &Env) -> Size {
      ...
    }

    fn paint(&mut self, ctx: &mut PaintCtx, data: &T, env: &Env) {
      ...
    }
}

Druid provides a number of basic utility and layout widgets and it's easy to implement your own. You can also compose widgets into new widgets:

fn build_widget() -> impl Widget<u32> {
    let mut col = Flex::column();
    for i in 0..30 {
        let button = Button::new(format!("Button {}", i).padding(5.0);
        col.add_child(button);
    }
    Scroll::new(col)
}

layout

Druid's layout protocol is strongly inspired by Flutter's box layout model. In Druid, widgets are passed a BoxConstraint that provides them a minimum and maximum size for layout. Widgets are also responsible for computing appropriate constraints for their children if applicable.

data

Druid uses a Data trait to represent value types. These should be cheap to compare and cheap to clone.

In general, you can use derive to generate a Data impl for your types.

#[derive(Clone, Data)]
struct AppState {
    which: bool,
    value: f64,
}

lens

The Lens datatype gives access to a part of a larger data structure. Like Data, this can be derived. Derived lenses are accessed as associated constants with the same name as the field.

#[derive(Clone, Data, Lens)]
struct AppState {
    which: bool,
    value: f64,
}

To use the lens, wrap your widget with LensWrap (note the conversion of CamelCase to snake_case):

LensWrap::new(WidgetThatExpectsf64::new(), AppState::value);

Alternatively, lenses for structs, tuples, and indexable containers can be constructed on-demand with the lens macro:

LensWrap::new(WidgetThatExpectsf64::new(), lens!(AppState, value));

This is particularly useful when working with types defined in another crate.

Authors

The main authors are Raph Levien and Colin Rofls, with much support from an active and friendly community. See the AUTHORS file for more.