Skip to content

i2y/ranma

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ranma

Ruby bindings for native windowing, GPU 2D rendering, and WebView — powered by Rust. Wraps tao, wry, Vello, muda, and more.

Gem Version License: MIT Platform: macOS

Features

  • 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)

Requirements

  • 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

Installation

Add to your Gemfile:

gem "ranma"

Then run:

bundle install

Or install directly:

gem install ranma

Quick Start

require "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
end

API

App Lifecycle

Ranma::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 windows

Window (Ranma::AppWindow)

window = 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)

Events (Hash with :type key)

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

Menus

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] }

System Tray

tray = Ranma::TrayIcon.new(icon_path: "icon.png", menu: menu)

Ranma::App.on_tray_event { |event| puts event.inspect }

WebView

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 }

GPU 2D Graphics

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

Available drawing methods

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

Clipboard

Ranma::Clipboard.set_text("Hello, world!")
text = Ranma::Clipboard.get_text
Ranma::Clipboard.clear

HotKey

hotkey = Ranma::HotKey.new(modifiers: [:ctrl, :shift], key: :a)
hotkey.register { puts "Ctrl+Shift+A pressed!" }
hotkey.unregister

Theme

theme = Ranma::Theme.detect  # => :dark | :light | :unspecified

DPI Types

Ranma::LogicalSize.new(width, height)
Ranma::PhysicalSize.new(width, height)
Ranma::LogicalPosition.new(x, y)
Ranma::PhysicalPosition.new(x, y)

Examples

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.rb

Building from Source

git clone https://github.com/i2y/ranma
cd ranma
bundle install
bundle exec rake compile

License

MIT — see LICENSE

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published