A Rust GUI framework that started as a port of the cycfi/elements C++ framework and has since grown past it: alongside the original element/layout models, and the ability to embed externally-managed native content in an mkgraphic-owned window.
mkgraphic is a lightweight, modular GUI framework for Rust that provides an element-based architecture for building user interfaces. It follows the design principles of the original Elements library while leveraging Rust's safety guarantees and modern ecosystem - and where a use case calls for something the original didn't have (a real code editor, a free-form design surface, native interop), mkgraphic adds it as its own primitive rather than staying a strict port.
- Element-based architecture - Composable UI elements with a hierarchical tree structure
- Pure Rust graphics - Uses tiny-skia for 2D rendering (no C++ dependencies for graphics)
- Cross-platform - Native platform integration for macOS, Windows, and Linux
- Layout system - Flexible layouts with tiles, alignment, margins, and size constraints
- Theming - Built-in support for dark and light themes
- Event handling - Mouse, keyboard, focus, and drag-and-drop support
- Text rendering - Full text shaping with rustybuzz and proper text measurement
- Code editing - Multi-line editor with a line-number gutter, undo/redo, and tree-sitter syntax highlighting
- Visual design surface - Free-form canvas for absolute positioning, drag-to-move/resize, and edge/sibling snap guides
- Native window embedding - Get the platform's real window handle (e.g.
NSWindow*on macOS) to host externally-managed content alongside mkgraphic's own element tree
- Label - Text display with customizable font, color, and alignment
- Button - Clickable button with hover and pressed states
- TextBox - Single-line text input with cursor, selection, and clipboard support
- Slider - Horizontal/vertical value slider with customizable track and thumb
- Dial - Rotary knob control with angular mouse interaction
- Checkbox - Toggle checkbox with label
- RadioButton - Radio button for exclusive selection
- SlideSwitch - iOS-style toggle switch
- Thumbwheel - Scrollable value wheel control
- ProgressBar - Linear and circular progress indicators
- List - Scrollable list with single/multiple selection
- Dropdown - Dropdown menu selection
- TabBar - Tab-based navigation
- ScrollView - Scrollable container with horizontal/vertical scrollbars
- Tooltip - Hover tooltips for elements
- StatusBar - Status bar with segments
- Grid - Grid layout container
- NativeMenuBar - Native OS menu bar integration
- CodeEditor - Multi-line code editor with line numbers, undo/redo, and tree-sitter syntax highlighting (Rust grammar out of the box)
- DesignCanvas - Free-form container for absolute-positioned children with click-to-select, drag-to-move, corner/edge resize handles, and snap guides against sibling edges
src/
├── lib.rs # Library entry point
├── support/ # Core utilities
│ ├── point.rs # Point, Extent, Axis types
│ ├── rect.rs # Rectangle geometry
│ ├── circle.rs # Circle geometry
│ ├── color.rs # RGBA colors
│ ├── canvas.rs # 2D drawing abstraction
│ ├── font.rs # Font handling
│ ├── theme.rs # Theming system
│ └── payload.rs # Drag-and-drop payload data
├── element/ # UI element system
│ ├── mod.rs # Element trait
│ ├── context.rs # Render/event context
│ ├── proxy.rs # Proxy elements (wrap/delegate to a subject)
│ ├── composite.rs # Container elements
│ ├── tile.rs # VTile/HTile layouts
│ ├── align.rs # Alignment elements
│ ├── margin.rs # Margin elements
│ ├── size.rs # Size constraints
│ ├── layer.rs # Layer/Deck stacking
│ ├── label.rs # Text labels
│ ├── button.rs # Button widgets
│ ├── text_box.rs # Text input
│ ├── slider.rs # Slider control
│ ├── dial.rs # Rotary dial/knob
│ ├── checkbox.rs # Checkbox and radio buttons
│ ├── switch.rs # Toggle switches
│ ├── thumbwheel.rs # Thumbwheel control
│ ├── progress.rs # Progress indicators
│ ├── list.rs # List and dropdown
│ ├── menu.rs # Menus and native menu bar
│ ├── tabs.rs # Tab bar
│ ├── tooltip.rs # Tooltips
│ ├── status_bar.rs # Status bar
│ ├── grid.rs # Grid layout
│ ├── floating.rs # Floating elements
│ ├── scroll.rs # Scroll view
│ ├── code_editor.rs # Multi-line code editor + tree-sitter highlighting
│ └── design_canvas.rs # Free-form visual layout surface
├── view/ # View management
│ └── mod.rs # Events and input handling
└── host/ # Platform layer
├── macos.rs # macOS (objc2)
├── windows.rs # Windows (Win32)
└── linux.rs # Linux (X11)
tiny-skia- Pure Rust 2D graphicsfontdb/rustybuzz/ttf-parser- Font handling and text shapingbitflags- Modifier key flagstree-sitter/tree-sitter-rust/streaming-iterator- Incremental parsing and syntax highlighting forCodeEditor(Rust grammar bundled; the parsing setup is generic enough to add other languages' grammars later)
- macOS:
objc2,objc2-foundation,objc2-app-kit - Windows:
windowscrate with Win32 features - Linux:
x11rbfor X11 support
Add to your Cargo.toml:
[dependencies]
mkgraphic = "0.3"use mkgraphic::element::{label, button, vtile, share};
use mkgraphic::element::margin::margin;
// Create a simple UI
let ui = vtile![
label("Hello, World!"),
margin(10.0, button("Click Me").on_click(|| println!("Clicked!"))),
];use mkgraphic::element::{vtile, htile, share};
use mkgraphic::element::text_box::text_box;
use mkgraphic::element::slider::slider;
use mkgraphic::element::dial::dial;
// Create interactive controls
let ui = vtile![
text_box()
.placeholder("Enter text...")
.on_change(|text| println!("Text: {}", text)),
htile![
slider().on_change(|v| println!("Slider: {:.2}", v)),
dial().on_change(|v| println!("Dial: {:.2}", v)),
],
];use mkgraphic::element::{htile, vtile, share};
use mkgraphic::element::align::{halign, valign};
use mkgraphic::element::size::fixed_size;
use mkgraphic::element::label::label;
use mkgraphic::element::button::button;
// Horizontal layout with centered content
let layout = htile![
halign(0.5, label("Centered")),
fixed_size(100.0, 50.0, button("Fixed Size")),
];use mkgraphic::prelude::*;
fn main() {
// Configure menu bar before creating app
set_native_menu_bar(
native_menu_bar()
.app_name("My App")
.add_menu(native_menu("File")
.add_item(native_menu_item("New")
.shortcut_cmd('n')
.on_select(|| println!("New")))
.add_item(native_menu_item("Open...")
.shortcut_cmd('o'))
.add_item(native_separator())
.add_item(native_menu_item("Save")
.shortcut_cmd('s')))
.add_menu(native_menu("View")
.add_item(native_menu_item("Zoom In")
.shortcut_cmd('+'))
.add_item(native_menu_item("Zoom Out")
.shortcut_cmd('-')))
.include_app_menu(true)
.include_edit_menu(true)
.include_window_menu(true)
);
let mut app = App::new();
// ... create windows
app.run();
}use mkgraphic::element::{code_editor, margin, share};
let editor = margin(5.0, code_editor()
.width(420.0)
.height(240.0)
.text("fn main() {\n println!(\"Hello!\");\n}\n")
.on_change(|text| println!("Buffer changed ({} bytes)", text.len())));Ships with Rust highlighting; undo/redo are wired to Cmd/Ctrl-Z and Cmd/Ctrl-Shift-Z (or Ctrl-Y) out of the box.
use mkgraphic::element::{button, design_canvas, label};
use mkgraphic::support::rect::Rect;
let mut canvas = design_canvas(420.0, 240.0)
.on_selection_changed(|index| println!("Selected: {:?}", index))
.on_layout_changed(|| println!("Layout changed"));
canvas.add_child(button("Gain").on_click(|| println!("Gain clicked")), Rect::new(20.0, 20.0, 140.0, 60.0));
canvas.add_child(label("Output"), Rect::new(160.0, 20.0, 260.0, 60.0));Children are positioned absolutely (not flow-laid-out); drag a child to move it, drag a corner/edge handle to resize it, and dragging near a sibling's edge snaps to it with a guide line.
use mkgraphic::prelude::*;
let window = Window::new("Host Window", Extent::new(800.0, 600.0));
// Real platform pointer (NSWindow* on macOS) for attaching content another
// library manages itself, instead of mkgraphic's own element tree.
if let Some(handle) = window.handle() {
// e.g. hand `handle` to a library that wants an NSWindow* to attach
// its own NSView-backed content to.
}Window::handle() no longer always returns None - it returns the real
native window handle so an mkgraphic-created window can host content that
another library renders and manages itself. mkgraphic still owns the
window's lifetime either way.
All UI components implement the Element trait:
pub trait Element: Send + Sync + Any {
fn limits(&self, ctx: &BasicContext) -> ViewLimits;
fn draw(&self, ctx: &Context);
fn hit_test(&self, ctx: &Context, p: Point, leaf: bool, control: bool) -> Option<&dyn Element>;
fn handle_click(&self, ctx: &Context, btn: MouseButton) -> bool;
fn handle_drag(&self, ctx: &Context, btn: MouseButton);
fn handle_key(&self, ctx: &Context, k: KeyInfo) -> bool;
fn handle_text(&self, ctx: &Context, info: TextInfo) -> bool;
fn handle_scroll(&self, ctx: &Context, dir: Point, p: Point) -> bool;
// ... more methods
}- VTile/HTile - Vertical and horizontal stacking
- Align - Horizontal and vertical alignment (0.0 = start, 0.5 = center, 1.0 = end)
- Margin - Spacing around elements
- Size - Fixed, minimum, and maximum size constraints
- Stretch - Control how elements expand to fill available space
- Layer/Deck - Stacked elements with z-ordering
The Context provides access to:
- View information (bounds, cursor position)
- Canvas for drawing
- Element hierarchy
- Enabled state
Elements can receive keyboard focus through the focus system:
wants_focus()- Whether the element can receive focusbegin_focus()/end_focus()- Focus lifecycleclear_focus()- Clears focus from all elements (used when clicking elsewhere)
| Platform | Backend | Status |
|---|---|---|
| macOS | Cocoa/AppKit via objc2 | Working |
| Windows | Win32 API | Basic |
| Linux | X11 via x11rb | Basic |
A minimal starter example:
cargo run --example helloRun the elements gallery to see all available widgets:
cargo run --example elements_gallery# Check compilation
cargo check
# Build
cargo build
# Build with release optimizations
cargo build --release
# Run tests
cargo testA cargo xtask (see xtask/) turns a release build of any example
(or your own app depending on mkgraphic in the same style) into a
distributable package.
# One square source PNG (1024x1024 recommended) -> AppIcon.icns + icon.ico
cargo xtask make-icons --source path/to/icon.png --out-dir path/to/icons
cargo xtask bundle-mac \
--example elements_gallery \
--icon path/to/icons/AppIcon.icns \
--name "Elements Gallery" \
--identity "Developer ID Application: Your Name (TEAMID)"Produces target/bundle/Elements Gallery.app. --identity is a signing
identity from your own Keychain (security find-identity -v -p codesigning
lists what's available) - this tool never hardcodes or assumes anyone's
identity, since every user packaging their own app needs to sign with their
own certificate. Omit --identity to fall back to ad-hoc signing, which runs
locally but won't pass Gatekeeper if you distribute the app to another Mac.
Must run on Windows, with the WiX Toolset v3
(candle/light) on PATH:
cargo xtask bundle-windows `
--example elements_gallery `
--icon path/to/icons/icon.ico `
--name "Elements Gallery" `
--upgrade-code "<a GUID you generate once and keep for this app>"Embeds the icon into the .exe (via a build.rs + winres, gated behind the
MKGRAPHIC_APP_ICON env var so a plain cargo build is unaffected) and
produces target/bundle/Elements Gallery.msi. Generate the upgrade code once
per app (e.g. [guid]::NewGuid() in PowerShell) and keep it constant across
releases - it's what lets the MSI upgrade a previous install instead of
conflicting with it.
MIT
This project began as a Rust translation of the Elements C++ GUI library by Joel de Guzman and Cycfi Research, and its core element/layout model still follows Elements' design. It has since grown beyond a strict port - the code editor, design canvas, and native window embedding described above have no equivalent in the original library and are mkgraphic's own additions.
Also uses tree-sitter and its Rust grammar for CodeEditor's syntax highlighting.