Reflex - A GUI toolkit for Ruby

May 15, 2026 ยท View on GitHub

Ask DeepWiki License Build Status Gem Version

โš ๏ธ Notice

This repository is a read-only mirror of our monorepo. We do not accept pull requests or direct contributions here.

๐Ÿ”„ Where to Contribute?

All development happens in our xord/all monorepo, which contains all our main libraries. If you'd like to contribute, please submit your changes there.

For more details, check out our Contribution Guidelines.

Thanks for your support! ๐Ÿ™Œ

๐Ÿš€ About

Reflex is a cross-platform GUI toolkit for Ruby. It gives you a Window with a tree of View objects, an event-driven runtime (Application / on_draw, on_update, on_pointer, ...), and bindings to a 2D physics engine, MIDI I/O, and live camera capture โ€” all sitting on top of the Rays drawing engine.

It is the application layer underneath Processing, RubySketch, and Reight. Like the rest of the xord/* family, Reflex is primarily developed for our own use, but it also works as a standalone GUI / creative-coding toolkit.

The gem name is reflexion (not reflex) โ€” gem install reflexion. The Ruby namespace is Reflex.

๐Ÿ“‹ Requirements

  • Ruby 3.0.0 or later
  • A C++ compiler with C++20 support
  • Xot, Rucy, and Rays (declared as runtime dependencies)
  • Platform GUI backend:
    • macOS โ€” AppKit (bundled with the OS)
    • iOS โ€” UIKit (bundled with the OS)
    • Windows โ€” native Win32
    • Linux โ€” libsdl2-dev

The following third-party libraries are cloned from GitHub and statically linked while the native extension is being built:

LibraryRole
Box2D2D physics simulation (View bodies, ContactEvent, gravity, fixtures)
RtMidiMIDI I/O โ€” exposes MidiEvent, NoteEvent, ControlChangeEvent to views

๐Ÿ“ฆ Installation

Add this line to your Gemfile:

gem 'reflexion'

Then install:

$ bundle install

Or install it directly:

$ gem install reflexion

require 'reflex' automatically calls Reflex.init! (and Rays.init!) and registers Reflex.fin! at exit. Set $REFLEX_NOAUTOINIT = true before requiring if you want to manage the lifetime yourself.

๐Ÿ“š What's Included

Application / Window / View

ClassPurpose
Reflex::ApplicationRun loop; created and started by Reflex.start { ... }
Reflex::WindowOS-level window with title, frame, flags (closable / resizable / fullscreen / portrait / landscape)
Reflex::ViewRecursive UI node: position, size, transform, styles, child views, optional clipping / caching
Reflex::ScreenInformation about a display monitor
Reflex::TimerOne-shot or interval timer that delivers TimerEvent

Shapes (drawing + physics body)

A view can carry one or more Shape objects that act both as its drawn appearance and as its physics fixture. Built-ins:

  • Reflex::RectShape
  • Reflex::EllipseShape
  • Reflex::LineShape
  • Reflex::PolygonShape โ€” wraps a Rays::Polygon

Events

Every event class inherits from Reflex::Event. Views receive them via on_<name> hooks (or on(:name) / before(:name) / after(:name) from Xot::Hookable).

Event classView hookWhen it fires
UpdateEventon_updateEvery frame, before drawing
DrawEventon_drawEvery frame, to render with e.painter
FrameEventon_frame_*Frame resize / move
FocusEventon_focusKeyboard focus gained / lost
KeyEventon_keyKey down / up / repeat
PointerEventon_pointerMouse / touch down / move / up
WheelEventon_wheelScroll wheel / trackpad scroll
ScrollEventon_scrollThe view itself scrolled
MidiEvent / NoteEvent / ControlChangeEventon_midi / on_note / on_control_changeIncoming MIDI message
CaptureEventon_captureNew frame from a Rays::Camera
TimerEventon_timerFired by start_timer / start_interval
ContactEventon_contact_*Two physics bodies began / ended overlapping
DeviceEvent / MotionEventvariousDevice-level signals (accelerometer / gyro / connection)

Styling and selectors

Reflex::Style and Reflex::Selector (with HasSelector) provide a lightweight CSS-style mechanism for setting background, padding, layout, etc., on views.

Two ways to use the gem

The gem ships two complementary APIs:

  1. require 'reflex' โ€” the full OO API. Subclass Reflex::Window, override on_draw / on_update / on_pointer, build a view hierarchy, etc.
  2. require 'reflexion/include' โ€” a single-file, Processing-style API that exposes top-level setup, draw, update, key, pointer, motion blocks and auto-starts the application on at_exit.

๐Ÿ’ก Usage

Hello, Reflex (OO style)

require 'reflex'

class HelloWindow < Reflex::Window
  def initialize
    super title: 'Hello Reflex!', frame: [100, 100, 320, 240]
    painter.font = Reflex::Font.new('Menlo', 32)
    painter.background = 0
    painter.fill = 1
  end

  def on_draw(e)
    e.painter.text 'hello world!', 5, 5
  end

  def on_update(e)
    painter.background = rand, rand, rand
    redraw
  end
end

Reflex.start do
  HelloWindow.new.show
end

Block / DSL style

require 'reflex'

Reflex.start do
  Reflex::Window.show title: 'Shapes', frame: [100, 100, 500, 300] do
    def on_draw(e)
      e.painter.push do
        fill   :pink
        stroke 1
        stroke_width 2
        rect    10, 10, 80, 80, 10
        ellipse 120, 10, 80, 80
      end
    end
  end
end

Reflexion (Processing-style single-file)

require 'reflexion/include'

setup do
  window.title = 'Reflexion!'
end

draw do |p|
  p.background 0
  p.fill 1
  p.text 'hello from reflexion', 10, 30
end

pointer do |e|
  puts "pointer at #{e.pos.to_a.inspect}"
end
# Reflexion.start is called automatically at_exit

2D physics with Box2D

require 'reflex'

Reflex.start name: 'Physics' do |app|
  Reflex::Window.show title: app.name, frame: [100, 100, 500, 500] do
    gravity 0, 9.8 * meter

    50.times do
      add Reflex::View.new {
        pos        rand(10..400), rand(10..100)
        size       rand(5..50)
        background [:red, :green, :blue, :yellow, :orange].sample
        dynamic    true
        shape      Reflex::EllipseShape.new(density: 1)
      }
    end

    add Reflex::View.new {     # a static ground
      pos        0, 480
      size       500, 20
      background :darkgray
      static     true
    }

    on :pointer do |e|
      if e.down? || e.drag?
        add Reflex::View.new(pos: e.pos, size: 10, dynamic: true,
                             shape: Reflex::EllipseShape.new(density: 1))
      end
    end
  end
end

See the samples/ directory for more examples covering shapes, layout, models, MIDI, camera capture, etc.

๐Ÿ› ๏ธ Development

$ rake vendor   # clone Box2D and RtMidi into vendor/
$ rake lib      # build the native C++ library (libreflex)
$ rake ext      # build the Ruby C extension
$ rake test     # run the test suite
$ rake doc      # generate RDoc from C++ sources
$ rake          # default: builds the extension

The test suite requires a windowing system, so CI only runs it on macOS. The test_reflex_init.rb test must run in its own process and is listed in TESTS_ALONE.

In the xord/all monorepo you can scope by module, e.g. rake reflex test.

๐Ÿ“œ License

Reflex is licensed under the MIT License. See the LICENSE file for details.

The third-party libraries listed above retain their own licenses (Box2D: MIT, RtMidi: MIT-style).