Ruby bindings for native windowing, GPU 2D rendering, and WebView — powered by Rust. Wraps tao, wry, Vello, muda, and more.
- Native windows with full event handling (keyboard, mouse, IME, resize, DPI)
- Native menu bar (via muda)
- System tray icon with menu (via tray-icon)
- Embedded WebView (WKWebView on macOS, via wry)
- GPU-accelerated 2D graphics — rects, circles, arcs, paths, text, images (via Vello + wgpu)
- Clipboard read/write (via arboard)
- Global hotkeys (via global-hotkey)
- System theme detection — dark/light mode (via dark-light)
- Multi-monitor support (monitor enumeration, video modes, exclusive fullscreen)
- IME preedit support (macOS)
- macOS (primary platform; tao also targets Linux and Windows but those are untested)
- Ruby >= 3.0
- Rust toolchain (
cargo) — required to compile the native extension
Add to your Gemfile:
gem "ranma"Then run:
bundle installOr install directly:
gem install ranmarequire "ranma"
Ranma::App.start do
window = Ranma::AppWindow.new(
title: "Hello Ranma!",
inner_size: Ranma::LogicalSize.new(800, 600)
)
window.on_event do |event|
case event[:type]
when :close_requested
Ranma::App.exit
end
end
window.visible = true
endRanma::App.start { ... } # Start the event loop; block runs before the loop begins
Ranma::App.exit # Request application exit
Ranma::App.request_redraw # Request a redraw of all windowswindow = Ranma::AppWindow.new(
title: "My App",
inner_size: Ranma::LogicalSize.new(800, 600)
)
window.on_event { |event| ... } # Per-window event handler
window.visible = true
window.title = "New Title"
window.resizable = true
window.decorations = true
window.inner_size # => Ranma::LogicalSize
window.outer_position # => Ranma::LogicalPosition
window.scale_factor # => Float
window.set_fullscreen_borderless # Borderless fullscreen
window.set_fullscreen_exclusive(video_mode) # Exclusive fullscreen
window.set_ime_position([x, y]) # or Ranma::LogicalPosition.new(x, y)
window.setup_ime_preedit # Enable IME preedit events (macOS)| Event type | Additional fields |
|---|---|
:close_requested |
— |
:resized |
:width, :height |
:moved |
:x, :y |
:focused |
:focused (bool) |
:keyboard_input |
:state, :key_code, :modifiers |
:received_ime_text |
:text |
:ime_preedit |
:text, :cursor_start, :cursor_end |
:cursor_moved |
:x, :y |
:mouse_input |
:state, :button |
:mouse_wheel |
:delta_x, :delta_y |
:scale_factor_changed |
:scale_factor |
:theme_changed |
:theme (:dark or :light) |
:redraw_requested |
— |
menu = Ranma::Menu.new
submenu = Ranma::Submenu.new("File", enabled: true)
item = Ranma::MenuItem.new("Open", enabled: true, accelerator: "CmdOrCtrl+O")
check = Ranma::CheckMenuItem.new("Show Toolbar", enabled: true, checked: true)
sep = Ranma::PredefinedMenuItem.separator
quit = Ranma::PredefinedMenuItem.quit
submenu.append(item)
submenu.append(check)
submenu.append(sep)
submenu.append(quit)
menu.append(submenu)
menu.init_for_nsapp # macOS: set as the application menu
Ranma::App.on_menu_event { |event| puts event[:id] }tray = Ranma::TrayIcon.new(icon_path: "icon.png", menu: menu)
Ranma::App.on_tray_event { |event| puts event.inspect }webview = Ranma::WebView.new(window, url: "https://example.com")
# or
webview = Ranma::WebView.new(window, html: "<h1>Hello</h1>")
webview.load_url("https://example.com")
webview.load_html("<h1>Hello</h1>")
webview.evaluate_script("document.title")
webview.reload
webview.zoom(1.5)
webview.set_bounds(0, 0, 800, 600) # positional: (x, y, width, height)
webview.set_visible(true)
webview.on_ipc_message { |msg| puts msg }
webview.on_navigation { |url| puts url }surface = Ranma::GpuSurface.new(window)
painter = Ranma::Painter.new(surface)
window.on_event do |event|
if event[:type] == :redraw_requested
painter.clear_all("#ffffff")
style = Ranma::PainterStyle.new(
fill_color: "#3366cc",
stroke_color: "#000000",
stroke_width: 2.0,
font_size: 24.0,
border_radius: 8.0
)
painter.style(style)
painter.fill_rect(10, 10, 200, 100)
painter.stroke_rect(10, 10, 200, 100)
painter.fill_circle(150, 150, 50)
painter.fill_text("Hello!", 50, 50, nil) # 4th arg: max_width (Float or nil)
painter.save
painter.translate(100, 100)
painter.scale(2.0, 2.0)
painter.fill_rect(0, 0, 50, 50)
painter.restore
painter.flush
end
end| Method | Description |
|---|---|
clear_all(color_hex = nil) |
Fill entire surface with a hex color (e.g. "#ffffff"); omit to clear to transparent |
style(painter_style) |
Set the active Ranma::PainterStyle for subsequent draw calls |
fill_rect(x, y, w, h) |
Filled rectangle (respects border_radius from active style) |
stroke_rect(x, y, w, h) |
Stroked rectangle |
fill_circle(cx, cy, r) |
Filled circle |
fill_arc(cx, cy, r, start_angle, end_angle) |
Filled arc |
stroke_arc(cx, cy, r, start_angle, end_angle) |
Stroked arc |
fill_triangle(x1,y1, x2,y2, x3,y3) |
Filled triangle |
fill_text(text, x, y, max_width) |
Text rendering; max_width is a Float or nil |
measure_text(text) |
Returns Ranma::PainterFontMetrics |
draw_image(path, x, y, w, h) |
Draw image from file |
measure_image(path) |
Returns [width, height] |
begin_path / path_move_to / path_line_to / fill_path |
Path drawing |
save / restore |
Save/restore transform+clip state |
translate(dx, dy) / scale(sx, sy) |
Transform |
clip(x, y, w, h) |
Clip rectangle |
flush |
Submit frame to GPU |
Ranma::Clipboard.set_text("Hello, world!")
text = Ranma::Clipboard.get_text
Ranma::Clipboard.clearhotkey = Ranma::HotKey.new(modifiers: [:ctrl, :shift], key: :a)
hotkey.register { puts "Ctrl+Shift+A pressed!" }
hotkey.unregistertheme = Ranma::Theme.detect # => :dark | :light | :unspecifiedRanma::LogicalSize.new(width, height)
Ranma::PhysicalSize.new(width, height)
Ranma::LogicalPosition.new(x, y)
Ranma::PhysicalPosition.new(x, y)The examples/ directory contains runnable demonstrations:
| File | Description |
|---|---|
basic_window.rb |
Minimal window using the high-level API |
event_handling.rb |
Per-window event handling |
low_level.rb |
Low-level Ranma.run API |
menu_example.rb |
Native menu bar with menu events |
full_featured.rb |
Full demo: menus, tray, IME, monitors |
clipboard_example.rb |
Clipboard read/write operations |
hotkey_example.rb |
Global hotkey registration |
webview_example.rb |
Embedded WebView (wry) |
painter_example.rb |
GPU-accelerated 2D drawing (Vello + Painter) |
Run any example with:
bundle exec ruby examples/basic_window.rbgit clone https://github.com/i2y/ranma
cd ranma
bundle install
bundle exec rake compileMIT — see LICENSE