A free, functional, IMGUI-oriented GUI framework for rapid development of desktop applications written in Rust, supported by the Mozilla WebRender rendering engine.

Features

  • Cross-platform GUI toolkit (Windows, Linux, Mac)
  • IMGUI / MVVM-like programming model
  • DOM-based stateless components - Widget drawing done using pure functions
  • CSS-like styling engine, support for many common CSS properties
  • Flexbox-based layout description
  • Built-in standard controls for common user interface elements
  • Custom widgets via function composition, easy dynamic UI composition
  • SVG rendering engine, 2D drawing helpers (lines, circles, rects, etc.)
  • OpenGL integration
  • Async I/O helpers
  • Optional integrated logging and error reporting helpers
  • Single deployment binary, minimal binary size (roughly 5MB all-incl.),
    CPU (0 - 4%) and RAM usage (~ 50MB total)
  • Fast redraw time (0.5 - 4ms), efficient state caching included
Calculator built using the Azul GUI framework Spreadsheet built with Azul GUI framework running on Windows SVG widget displaying a tiger on Linux

Hello world


extern crate azul;

use azul::{prelude::*, widgets::{label::Label, button::Button}};

struct MyDataModel {
    counter: usize,
}

impl Layout for MyDataModel {
    fn layout(&self, _info: WindowInfo<Self>) -> Dom<Self> {
        let label = Label::new(format!("{}", self.counter)).dom();
        let button = Button::with_label("Update counter").dom()
            .with_callback(On::MouseUp, Callback(update_counter));

        Dom::new(NodeType::Div)
            .with_child(label)
            .with_child(button)
    }
}

fn update_counter(app_state: &mut AppState<MyDataModel>, _event: WindowEvent<MyDataModel>) -> UpdateScreen {
    app_state.data.modify(|state| state.counter += 1);
    UpdateScreen::Redraw
}

fn main() {
    let app = App::new(MyDataModel { counter: 0 }, AppConfig::default());
    app.run(Window::new(WindowCreateOptions::default(), Css::native()).unwrap()).unwrap();
}

A functional GUI framework for Rust applications

Easily compose custom widgets together by appending their DOM trees together. No macros, meta-compiler or external scripting language required. You can store your own widgets in external crates and re-use them throughout your projects.


struct DataModel {
    emails: Vec<EmailInfo>,
}

struct EmailInfo {
    from: String,
    subject: String,
    profile_pic: ImageId,
    message: TextId,
}

impl Layout for DataModel {
    fn layout(&self, _info: WindowInfo<Self>) -> Dom<Self> {
            Dom::new(NodeType::Div).with_id("email-container")
                .with_child(self.emails.iter().map(layout_email_component).collect())
    }
}

fn layout_email_component(email: &EmailInfo) -> Dom<DataModel> {
    Dom::new(NodeType::Div)
        .with_child(Dom::new(NodeType::Div)
        .with_class("email-header")
            .with_child(Dom::new(NodeType::Image(email.profile_pic)))
            .with_child(Dom::new(NodeType::Label(email.from.clone()))))
        .with_child(Dom::new(NodeType::Div)
        .with_class("email-content")
            .with_child(Dom::new(NodeType::Label(email.subject.clone())))
            .with_child(Dom::new(NodeType::Text(email.message))))
}

Stylable with CSS

Choose whether you want a platform-native look or a custom, flashy style!

{{ TODO: Screenshots go here }}

Simple Task-based asynchronous IO

Azul provides simple helpers for asynchronous I/O, which are thread-based. Each task is a single thread, polled for completion by azul.


impl Layout for DataModel {
    fn layout(&self, _info: WindowInfo<Self>) -> Dom<Self> {
        let button = Button::with_label("Connect to database...").dom()
            .with_callback(On::MouseUp, Callback(start_connection));

        let status = Label::new(match &self.connection_status {
            Connected    => format!("You are connected!"),
            Err(e)       => format!("There was an error: {}", e),
            InProgress   => format!("Loading..."),
        }).dom();

        Dom::new(NodeType::Div)
            .with_child(status)
            .with_child(button)
    }
}

fn start_connection(app_state: &mut AppState<DataModel>, _event: WindowEvent<DataModel>) -> UpdateScreen {
    app_state.data.modify(|state| state.connection_status = ConnectionStatus::InProgress);
    app_state.add_task(connect_to_db_async, &[]);
    UpdateScreen::Redraw
}

fn connect_to_db_async(app_data: Arc<Mutex<DataModel>>, _: Arc<()>) {
    thread::sleep(Duration::from_secs(2)); // simulate slow load
    app_data.modify(|state| state.connection_status = ConnectionStatus::Connected);
}

Simple two-way data binding

Contrary to other IMGUI-like toolkits, azul provides automatic two way data binding - only minimal code changes to go from a static label to a dynamic input form.


struct DataModel {
    text_input_state: TextInputState,
}

impl Layout for DataModel {
    fn layout(&self, info: WindowInfo<Self>) -> Dom<Self> {
        // Create a new text input field
        TextInput::new()
        // ... bind it to self.text_input - will automatically update
        .bind(info.window, &self.text_input_state, &self)
        // ... and render it in the UI
        .dom(&self.text_input_state)
        .with_callback(On::KeyUp, Callback(print_text_field))
    }
}

fn print_text_field(app_state: &mut AppState<DataModel>, _event: WindowEvent<DataModel>) -> UpdateScreen {
    println!("You've typed: {}", app_state.data.lock().unwrap().text_input_state.text);
    UpdateScreen::DontRedraw
}

Interested? Check out our user guide!