reflexion 0.3.14 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.doc/ext/reflex/key_event.cpp +10 -4
- data/.doc/ext/reflex/view.cpp +1 -2
- data/.github/workflows/release-gem.yml +5 -16
- data/.github/workflows/utils.rb +88 -17
- data/ChangeLog.md +25 -0
- data/README.md +205 -6
- data/VERSION +1 -1
- data/ext/reflex/extconf.rb +1 -6
- data/ext/reflex/key_event.cpp +10 -4
- data/ext/reflex/view.cpp +1 -2
- data/include/reflex/defs.h +317 -197
- data/include/reflex/ruby.h +2 -2
- data/include/reflex.h +2 -2
- data/lib/reflex/extension.rb +8 -2
- data/reflex.gemspec +4 -5
- data/samples/bats.rb +2 -2
- data/samples/ios/hello/hello/main.cpp +3 -3
- data/samples/ios/hello/hello.xcodeproj/project.pbxproj +868 -588
- data/samples/osx/hello/hello/main.cpp +4 -4
- data/samples/osx/hello/hello.xcodeproj/project.pbxproj +902 -594
- data/src/application.cpp +12 -0
- data/src/application.h +2 -0
- data/src/ios/event.h +25 -1
- data/src/ios/event.mm +95 -12
- data/src/ios/view_controller.mm +133 -1
- data/src/midi.cpp +4 -0
- data/src/osx/device.mm +3 -0
- data/src/osx/event.mm +1 -1
- data/src/sdl/application.cpp +40 -16
- data/src/sdl/event.cpp +176 -0
- data/src/sdl/event.h +58 -0
- data/src/sdl/window.cpp +103 -22
- data/src/shape.cpp +1 -7
- data/src/style.cpp +1 -1
- data/src/timer.cpp +3 -0
- data/src/win32/event.cpp +0 -1
- metadata +10 -8
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8781c6a2e533488f1529b59cdb98ed7922909330e254bdbd80b7ce67f40a8252
|
|
4
|
+
data.tar.gz: 449a81f9c5ba57c2daa68af18cc6e42d90319703beab674dee431e5bc2923d4b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c746ba6a4b32c05eca98a2bd3c7ab5e459e44222d80794323757a8516401a91404c76aa4e7dbb08b084126bf08d65bf310ab8f3095f06bd2df29577fa7989604
|
|
7
|
+
data.tar.gz: 928aa64c73861dcb0848dc6c6ea6d770ccb3dff1e9e06ba6bb3cf470cb5104311af6ecfc543d4ca015f8efdc3a12d9bb6662b80014f9fc57a2bbc16c74bf5e43
|
|
@@ -119,11 +119,11 @@ VALUE get_key(VALUE self)
|
|
|
119
119
|
CASE(LBRACKET): SYMBOL1(lbracket);
|
|
120
120
|
CASE(RBRACKET): SYMBOL1(rbracket);
|
|
121
121
|
|
|
122
|
-
#if defined(OSX) || defined(IOS) || defined(WIN32) || defined(LINUX)
|
|
122
|
+
#if defined(OSX) || defined(IOS) || defined(WIN32) || defined(LINUX) || defined(WASM)
|
|
123
123
|
CASE(ENTER): SYMBOL1(enter);
|
|
124
124
|
#else
|
|
125
125
|
CASE(ENTER): SYMBOL1(enter);
|
|
126
|
-
CASE(RETURN):
|
|
126
|
+
CASE(RETURN): SYMBOL2(_return, "return");
|
|
127
127
|
#endif
|
|
128
128
|
CASE(SPACE): SYMBOL1(space);
|
|
129
129
|
CASE(TAB): SYMBOL1(tab);
|
|
@@ -141,25 +141,29 @@ VALUE get_key(VALUE self)
|
|
|
141
141
|
CASE(PAGEUP): SYMBOL1(pageup);
|
|
142
142
|
CASE(PAGEDOWN): SYMBOL1(pagedown);
|
|
143
143
|
|
|
144
|
-
#if !defined(OSX) && !defined(IOS) && !defined(LINUX)
|
|
144
|
+
#if !defined(OSX) && !defined(IOS) && !defined(LINUX) && !defined(WASM)
|
|
145
145
|
CASE(SHIFT):
|
|
146
146
|
#endif
|
|
147
147
|
CASE(LSHIFT):
|
|
148
148
|
CASE(RSHIFT): SYMBOL1(shift);
|
|
149
|
-
#if !defined(OSX) && !defined(IOS) && !defined(LINUX)
|
|
149
|
+
#if !defined(OSX) && !defined(IOS) && !defined(LINUX) && !defined(WASM)
|
|
150
150
|
CASE(CONTROL):
|
|
151
151
|
#endif
|
|
152
152
|
CASE(LCONTROL):
|
|
153
153
|
CASE(RCONTROL): SYMBOL1(control);
|
|
154
154
|
CASE(ALT):
|
|
155
|
+
#if !defined(IOS) && !defined(LINUX) && !defined(WASM)
|
|
155
156
|
CASE(LALT):
|
|
157
|
+
#endif
|
|
156
158
|
CASE(RALT): SYMBOL1(alt);
|
|
157
159
|
CASE(LWIN):
|
|
158
160
|
CASE(RWIN): SYMBOL1(win);
|
|
161
|
+
#if !defined(IOS) && !defined(LINUX) && !defined(WASM)
|
|
159
162
|
CASE(LCOMMAND):
|
|
160
163
|
CASE(RCOMMAND): SYMBOL1(command);
|
|
161
164
|
CASE(LOPTION):
|
|
162
165
|
CASE(ROPTION): SYMBOL1(option);
|
|
166
|
+
#endif
|
|
163
167
|
CASE(FUNCTION): SYMBOL1(function);
|
|
164
168
|
|
|
165
169
|
CASE(F1): SYMBOL1(f1);
|
|
@@ -194,7 +198,9 @@ VALUE get_key(VALUE self)
|
|
|
194
198
|
CASE(NUM_EQUAL): SYMBOL1(equal);
|
|
195
199
|
CASE(NUM_COMMA): SYMBOL1(comma);
|
|
196
200
|
CASE(NUM_DECIMAL): SYMBOL1(decimal);
|
|
201
|
+
#if !defined(IOS) && !defined(LINUX) && !defined(WASM)
|
|
197
202
|
CASE(NUM_CLEAR): SYMBOL1(clear);
|
|
203
|
+
#endif
|
|
198
204
|
CASE(NUM_ENTER): SYMBOL1(enter);
|
|
199
205
|
|
|
200
206
|
CASE(CAPSLOCK): SYMBOL1(capslock);
|
data/.doc/ext/reflex/view.cpp
CHANGED
|
@@ -197,8 +197,7 @@ VALUE each_child(VALUE self)
|
|
|
197
197
|
CHECK;
|
|
198
198
|
|
|
199
199
|
Value ret;
|
|
200
|
-
|
|
201
|
-
for (Reflex::View::child_iterator it = THIS->child_begin(); it != end; ++it)
|
|
200
|
+
for (auto it = THIS->child_begin(), end = THIS->child_end(); it != end; ++it)
|
|
202
201
|
ret = rb_yield(value(it->get()));
|
|
203
202
|
return ret;
|
|
204
203
|
}
|
|
@@ -4,6 +4,9 @@ on:
|
|
|
4
4
|
push:
|
|
5
5
|
tags: ['v[0-9]*']
|
|
6
6
|
|
|
7
|
+
permissions:
|
|
8
|
+
contents: write
|
|
9
|
+
|
|
7
10
|
jobs:
|
|
8
11
|
release:
|
|
9
12
|
runs-on: macos-latest
|
|
@@ -33,23 +36,9 @@ jobs:
|
|
|
33
36
|
echo path=$(ruby -e 'print Dir.glob("*.gem").first') >> $GITHUB_OUTPUT
|
|
34
37
|
|
|
35
38
|
- name: create github release
|
|
36
|
-
id: release
|
|
37
|
-
uses: actions/create-release@v1
|
|
38
39
|
env:
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
tag_name: ${{ github.ref }}
|
|
42
|
-
release_name: ${{ github.ref }}
|
|
43
|
-
|
|
44
|
-
- name: upload to github release
|
|
45
|
-
uses: actions/upload-release-asset@v1
|
|
46
|
-
env:
|
|
47
|
-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
48
|
-
with:
|
|
49
|
-
upload_url: ${{ steps.release.outputs.upload_url }}
|
|
50
|
-
asset_path: ./${{ steps.gem.outputs.path }}
|
|
51
|
-
asset_name: ${{ steps.gem.outputs.path }}
|
|
52
|
-
asset_content_type: application/zip
|
|
40
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
41
|
+
run: ruby -I.github/workflows -rutils -e 'release(*ARGV)' ./${{ steps.gem.outputs.path }}
|
|
53
42
|
|
|
54
43
|
- name: upload to rubygems
|
|
55
44
|
env:
|
data/.github/workflows/utils.rb
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
require 'shellwords'
|
|
2
|
+
|
|
3
|
+
ALL_REPO = 'xord/all'
|
|
4
|
+
ALL_DIR = '../all'
|
|
5
|
+
ALL_FETCH_DEPTH = 100
|
|
6
|
+
|
|
1
7
|
RENAMES = {reflex: 'reflexion'}
|
|
2
8
|
|
|
3
9
|
def sh(cmd)
|
|
@@ -5,7 +11,7 @@ def sh(cmd)
|
|
|
5
11
|
system cmd
|
|
6
12
|
end
|
|
7
13
|
|
|
8
|
-
def setup_dependencies(
|
|
14
|
+
def setup_dependencies(only: nil)
|
|
9
15
|
gemspec_path = `git ls-files`.lines(chomp: true).find {|l| l =~ /\.gemspec$/}
|
|
10
16
|
return unless gemspec_path
|
|
11
17
|
|
|
@@ -13,44 +19,109 @@ def setup_dependencies(build: true, only: nil)
|
|
|
13
19
|
name = File.basename gemspec_path, '.gemspec'
|
|
14
20
|
|
|
15
21
|
exts = File.readlines('Rakefile')
|
|
16
|
-
.map {|l| l[%r|^\s*require\W+(\w+)/extension\W+$|, 1]}
|
|
22
|
+
.map {|l| l[%r|^\s*require\W+([\w\-\_]+)/extension\W+$|, 1]}
|
|
17
23
|
.compact
|
|
18
24
|
.reject {|ext| ext == name}
|
|
19
25
|
exts = exts & [only].flatten.map(&:to_s) if only
|
|
26
|
+
return if exts.empty?
|
|
27
|
+
|
|
28
|
+
unless setup_dependencies_via_monorepo(exts)
|
|
29
|
+
setup_dependencies_via_each_repo_by_version(gemspec, exts)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
exts.each {|ext| sh %( cd ../#{ext} && rake ext )}
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def setup_dependencies_via_monorepo(exts)
|
|
36
|
+
return false unless checkout_monorepo
|
|
37
|
+
exts.each {|ext| sh %( ln -snf all/#{ext} ../#{ext} )}
|
|
38
|
+
true
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def checkout_monorepo()
|
|
42
|
+
uuid = `git log -1 --format=%B`[/^\[\[([0-9a-fA-F-]+)\]\]$/, 1]
|
|
43
|
+
return false unless uuid
|
|
44
|
+
|
|
45
|
+
commit = setup_monorepo uuid
|
|
46
|
+
return false unless commit
|
|
47
|
+
|
|
48
|
+
Dir.chdir(ALL_DIR) {sh %( git checkout -q #{commit} )}
|
|
49
|
+
true
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def setup_monorepo(uuid)
|
|
53
|
+
unless File.directory? ALL_DIR
|
|
54
|
+
url = "https://github.com/#{ALL_REPO}.git"
|
|
55
|
+
sh %( git clone --no-tags --depth #{ALL_FETCH_DEPTH} #{url} #{ALL_DIR} )
|
|
56
|
+
end
|
|
57
|
+
loop do
|
|
58
|
+
commit = find_monorepo_commit uuid
|
|
59
|
+
return commit if commit
|
|
60
|
+
|
|
61
|
+
deepened = Dir.chdir ALL_DIR do
|
|
62
|
+
before = `git rev-list --count HEAD`.to_i
|
|
63
|
+
sh %( git fetch --deepen #{ALL_FETCH_DEPTH} )
|
|
64
|
+
`git rev-list --count HEAD`.to_i > before
|
|
65
|
+
end
|
|
66
|
+
return nil unless deepened
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def find_monorepo_commit(uuid)
|
|
71
|
+
Dir.chdir ALL_DIR do
|
|
72
|
+
out = `git log origin/HEAD -F --grep="[[#{uuid}]]" --format=%H -1`.strip
|
|
73
|
+
out.empty? ? nil : out
|
|
74
|
+
end
|
|
75
|
+
end
|
|
20
76
|
|
|
77
|
+
def setup_dependencies_via_each_repo_by_version(gemspec, exts)
|
|
21
78
|
exts.each do |ext|
|
|
22
79
|
gem = RENAMES[ext.to_sym].then {|s| s || ext}
|
|
23
|
-
ver = gemspec[/add_dependency.*['"]#{gem}['"].*['"]\s
|
|
80
|
+
ver = gemspec[/add_dependency.*['"]#{gem}['"].*['"]\s*~>\s*([\d\.]+)\s*['"]/, 1]
|
|
24
81
|
opts = '-c advice.detachedHead=false --depth 1'
|
|
25
82
|
clone = "git clone #{opts} https://github.com/xord/#{ext}.git ../#{ext}"
|
|
26
83
|
|
|
27
84
|
# 'rake subtree:push' pushes all subrepos, so cloning by new tag
|
|
28
85
|
# often fails before tagging each new tag
|
|
29
86
|
sh %( #{clone} --branch v#{ver} || #{clone} )
|
|
30
|
-
sh %( cd ../#{ext} && rake ext )
|
|
31
87
|
end
|
|
32
88
|
end
|
|
33
89
|
|
|
34
90
|
def tag_versions()
|
|
35
|
-
|
|
36
|
-
|
|
91
|
+
changes = changelogs
|
|
92
|
+
tags = `git tag`.lines chomp: true
|
|
93
|
+
vers = `git log --oneline ./VERSION`
|
|
37
94
|
.lines(chomp: true)
|
|
38
95
|
.map {|line| line.split.first[/^\w+$/]}
|
|
39
|
-
.map {|
|
|
40
|
-
.select {|ver,
|
|
96
|
+
.map {|sha| [`git cat-file -p #{sha}:./VERSION 2>/dev/null`[/[\d\.]+/], sha]}
|
|
97
|
+
.select {|ver, sha| ver && sha}
|
|
41
98
|
.reverse
|
|
42
99
|
.to_h
|
|
43
100
|
|
|
44
|
-
|
|
45
|
-
.split(/^\s*##\s*\[\s*v([\d\.]+)\s*\].*$/)
|
|
46
|
-
.slice(1..-1)
|
|
47
|
-
.each_slice(2)
|
|
48
|
-
.to_h
|
|
49
|
-
.transform_values(&:strip!)
|
|
50
|
-
|
|
51
|
-
vers.to_a.reverse.each do |ver, hash|
|
|
101
|
+
vers.to_a.reverse.each do |ver, sha|
|
|
52
102
|
tag = "v#{ver}"
|
|
53
103
|
break if tags.include?(tag)
|
|
54
|
-
sh %( git tag -a -m \"#{changes[
|
|
104
|
+
sh %( git tag -a -m \"#{changes[tag]&.gsub '"', '\\"'}\" #{tag} #{sha} )
|
|
55
105
|
end
|
|
56
106
|
end
|
|
107
|
+
|
|
108
|
+
def release(*paths)
|
|
109
|
+
tag = ENV['GITHUB_REF']&.sub(%r|^refs/tags/|, '') || raise('GITHUB_REF tag not set')
|
|
110
|
+
notes = (changelogs[tag] || '').shellescape
|
|
111
|
+
paths = paths.flatten.join ' '
|
|
112
|
+
|
|
113
|
+
sh(%( gh release create #{tag} #{paths} --notes #{notes} )) ||
|
|
114
|
+
sh(%( gh release upload #{tag} #{paths} --clobber )) ||
|
|
115
|
+
raise('failed to upload to releases')
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def changelogs()
|
|
119
|
+
File.read('ChangeLog.md')
|
|
120
|
+
.split(/^\s*##\s*\[\s*(v[\d\.]+)\s*\].*$/)
|
|
121
|
+
.slice(1..)
|
|
122
|
+
.each_slice(2)
|
|
123
|
+
.to_h
|
|
124
|
+
.transform_values(&:strip!)
|
|
125
|
+
rescue Errno::ENOENT
|
|
126
|
+
raise 'failed to get changelogs'
|
|
127
|
+
end
|
data/ChangeLog.md
CHANGED
|
@@ -1,6 +1,31 @@
|
|
|
1
1
|
# reflex ChangeLog
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
## [v0.4.0] - 2026-05-17
|
|
5
|
+
|
|
6
|
+
- [BREAKING] Normalize wheel Y delta to top-left origin convention
|
|
7
|
+
|
|
8
|
+
- Add keyboard event handling on iOS
|
|
9
|
+
- Add mouse event handling on iOS
|
|
10
|
+
- Rewrite README.md
|
|
11
|
+
- CI: Migrate release-gem.yml from actions/create-release to gh release create
|
|
12
|
+
- Refresh hello sample Xcode projects
|
|
13
|
+
|
|
14
|
+
- Skip duplicate KEY_CODE aliases on WASM build
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
## [v0.3.15] - 2026-05-10
|
|
18
|
+
|
|
19
|
+
- Support WebAssembly
|
|
20
|
+
- Dispatch SDL touch events as multi-pointer PointerEvents
|
|
21
|
+
- Add SDL scancode mapping and input event handling
|
|
22
|
+
- Enable HiDPI support on SDL backend
|
|
23
|
+
- Handle SDL_QUIT via app->quit() instead of breaking loop
|
|
24
|
+
- Remove deprecated has_rdoc= from gemspecs
|
|
25
|
+
|
|
26
|
+
- Fix quit re-entrancy in Application_call_quit
|
|
27
|
+
|
|
28
|
+
|
|
4
29
|
## [v0.3.14] - 2026-04-17
|
|
5
30
|
|
|
6
31
|
- Fix HID gamepad stick direction release not firing on direction switch
|
data/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Reflex - A
|
|
1
|
+
# Reflex - A GUI toolkit for Ruby
|
|
2
2
|
|
|
3
3
|
[](https://deepwiki.com/xord/reflex)
|
|
4
4
|

|
|
@@ -21,19 +21,38 @@ Thanks for your support! 🙌
|
|
|
21
21
|
|
|
22
22
|
## 🚀 About
|
|
23
23
|
|
|
24
|
-
**Reflex** is a
|
|
24
|
+
**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](https://github.com/xord/rays) drawing engine.
|
|
25
25
|
|
|
26
|
-
It is
|
|
27
|
-
|
|
26
|
+
It is the application layer underneath [Processing](https://github.com/xord/processing), [RubySketch](https://github.com/xord/rubysketch), and [Reight](https://github.com/xord/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.
|
|
27
|
+
|
|
28
|
+
> **The gem name is `reflexion`** (not `reflex`) — `gem install reflexion`. The Ruby namespace is `Reflex`.
|
|
29
|
+
|
|
30
|
+
## 📋 Requirements
|
|
31
|
+
|
|
32
|
+
- Ruby **3.0.0** or later
|
|
33
|
+
- A C++ compiler with C++20 support
|
|
34
|
+
- [Xot](https://rubygems.org/gems/xot), [Rucy](https://rubygems.org/gems/rucy), and [Rays](https://rubygems.org/gems/rays) (declared as runtime dependencies)
|
|
35
|
+
- Platform GUI backend:
|
|
36
|
+
- **macOS** — AppKit (bundled with the OS)
|
|
37
|
+
- **iOS** — UIKit (bundled with the OS)
|
|
38
|
+
- **Windows** — native Win32
|
|
39
|
+
- **Linux** — `libsdl2-dev`
|
|
40
|
+
|
|
41
|
+
The following third-party libraries are cloned from GitHub and statically linked while the native extension is being built:
|
|
42
|
+
|
|
43
|
+
| Library | Role |
|
|
44
|
+
| --------------------------------------------- | ----------------------------------- |
|
|
45
|
+
| [Box2D](https://github.com/erincatto/box2d) | 2D physics simulation (`View` bodies, `ContactEvent`, gravity, fixtures) |
|
|
46
|
+
| [RtMidi](https://github.com/thestk/rtmidi) | MIDI I/O — exposes `MidiEvent`, `NoteEvent`, `ControlChangeEvent` to views |
|
|
28
47
|
|
|
29
48
|
## 📦 Installation
|
|
30
49
|
|
|
31
50
|
Add this line to your Gemfile:
|
|
32
51
|
```ruby
|
|
33
|
-
|
|
52
|
+
gem 'reflexion'
|
|
34
53
|
```
|
|
35
54
|
|
|
36
|
-
Then
|
|
55
|
+
Then install:
|
|
37
56
|
```bash
|
|
38
57
|
$ bundle install
|
|
39
58
|
```
|
|
@@ -43,7 +62,187 @@ Or install it directly:
|
|
|
43
62
|
$ gem install reflexion
|
|
44
63
|
```
|
|
45
64
|
|
|
65
|
+
`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.
|
|
66
|
+
|
|
67
|
+
## 📚 What's Included
|
|
68
|
+
|
|
69
|
+
### Application / Window / View
|
|
70
|
+
|
|
71
|
+
| Class | Purpose |
|
|
72
|
+
| ------------------------ | ----------------------------------------------------------------------- |
|
|
73
|
+
| `Reflex::Application` | Run loop; created and started by `Reflex.start { ... }` |
|
|
74
|
+
| `Reflex::Window` | OS-level window with title, frame, flags (closable / resizable / fullscreen / portrait / landscape) |
|
|
75
|
+
| `Reflex::View` | Recursive UI node: position, size, transform, styles, child views, optional clipping / caching |
|
|
76
|
+
| `Reflex::Screen` | Information about a display monitor |
|
|
77
|
+
| `Reflex::Timer` | One-shot or interval timer that delivers `TimerEvent` |
|
|
78
|
+
|
|
79
|
+
### Shapes (drawing + physics body)
|
|
80
|
+
|
|
81
|
+
A view can carry one or more `Shape` objects that act both as its drawn appearance and as its physics fixture. Built-ins:
|
|
82
|
+
|
|
83
|
+
- `Reflex::RectShape`
|
|
84
|
+
- `Reflex::EllipseShape`
|
|
85
|
+
- `Reflex::LineShape`
|
|
86
|
+
- `Reflex::PolygonShape` — wraps a `Rays::Polygon`
|
|
87
|
+
|
|
88
|
+
### Events
|
|
89
|
+
|
|
90
|
+
Every event class inherits from `Reflex::Event`. Views receive them via `on_<name>` hooks (or `on(:name)` / `before(:name)` / `after(:name)` from `Xot::Hookable`).
|
|
91
|
+
|
|
92
|
+
| Event class | View hook | When it fires |
|
|
93
|
+
| ---------------------------------------------------- | -------------------- | --------------------------------------------------- |
|
|
94
|
+
| `UpdateEvent` | `on_update` | Every frame, before drawing |
|
|
95
|
+
| `DrawEvent` | `on_draw` | Every frame, to render with `e.painter` |
|
|
96
|
+
| `FrameEvent` | `on_frame_*` | Frame resize / move |
|
|
97
|
+
| `FocusEvent` | `on_focus` | Keyboard focus gained / lost |
|
|
98
|
+
| `KeyEvent` | `on_key` | Key down / up / repeat |
|
|
99
|
+
| `PointerEvent` | `on_pointer` | Mouse / touch down / move / up |
|
|
100
|
+
| `WheelEvent` | `on_wheel` | Scroll wheel / trackpad scroll |
|
|
101
|
+
| `ScrollEvent` | `on_scroll` | The view itself scrolled |
|
|
102
|
+
| `MidiEvent` / `NoteEvent` / `ControlChangeEvent` | `on_midi` / `on_note` / `on_control_change` | Incoming MIDI message |
|
|
103
|
+
| `CaptureEvent` | `on_capture` | New frame from a `Rays::Camera` |
|
|
104
|
+
| `TimerEvent` | `on_timer` | Fired by `start_timer` / `start_interval` |
|
|
105
|
+
| `ContactEvent` | `on_contact_*` | Two physics bodies began / ended overlapping |
|
|
106
|
+
| `DeviceEvent` / `MotionEvent` | various | Device-level signals (accelerometer / gyro / connection) |
|
|
107
|
+
|
|
108
|
+
### Styling and selectors
|
|
109
|
+
|
|
110
|
+
`Reflex::Style` and `Reflex::Selector` (with `HasSelector`) provide a lightweight CSS-style mechanism for setting background, padding, layout, etc., on views.
|
|
111
|
+
|
|
112
|
+
### Two ways to use the gem
|
|
113
|
+
|
|
114
|
+
The gem ships **two complementary APIs**:
|
|
115
|
+
|
|
116
|
+
1. **`require 'reflex'`** — the full OO API. Subclass `Reflex::Window`, override `on_draw` / `on_update` / `on_pointer`, build a view hierarchy, etc.
|
|
117
|
+
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`.
|
|
118
|
+
|
|
119
|
+
## 💡 Usage
|
|
120
|
+
|
|
121
|
+
### Hello, Reflex (OO style)
|
|
122
|
+
|
|
123
|
+
```ruby
|
|
124
|
+
require 'reflex'
|
|
125
|
+
|
|
126
|
+
class HelloWindow < Reflex::Window
|
|
127
|
+
def initialize
|
|
128
|
+
super title: 'Hello Reflex!', frame: [100, 100, 320, 240]
|
|
129
|
+
painter.font = Reflex::Font.new('Menlo', 32)
|
|
130
|
+
painter.background = 0
|
|
131
|
+
painter.fill = 1
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def on_draw(e)
|
|
135
|
+
e.painter.text 'hello world!', 5, 5
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def on_update(e)
|
|
139
|
+
painter.background = rand, rand, rand
|
|
140
|
+
redraw
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
Reflex.start do
|
|
145
|
+
HelloWindow.new.show
|
|
146
|
+
end
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Block / DSL style
|
|
150
|
+
|
|
151
|
+
```ruby
|
|
152
|
+
require 'reflex'
|
|
153
|
+
|
|
154
|
+
Reflex.start do
|
|
155
|
+
Reflex::Window.show title: 'Shapes', frame: [100, 100, 500, 300] do
|
|
156
|
+
def on_draw(e)
|
|
157
|
+
e.painter.push do
|
|
158
|
+
fill :pink
|
|
159
|
+
stroke 1
|
|
160
|
+
stroke_width 2
|
|
161
|
+
rect 10, 10, 80, 80, 10
|
|
162
|
+
ellipse 120, 10, 80, 80
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Reflexion (Processing-style single-file)
|
|
170
|
+
|
|
171
|
+
```ruby
|
|
172
|
+
require 'reflexion/include'
|
|
173
|
+
|
|
174
|
+
setup do
|
|
175
|
+
window.title = 'Reflexion!'
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
draw do |p|
|
|
179
|
+
p.background 0
|
|
180
|
+
p.fill 1
|
|
181
|
+
p.text 'hello from reflexion', 10, 30
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
pointer do |e|
|
|
185
|
+
puts "pointer at #{e.pos.to_a.inspect}"
|
|
186
|
+
end
|
|
187
|
+
# Reflexion.start is called automatically at_exit
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### 2D physics with Box2D
|
|
191
|
+
|
|
192
|
+
```ruby
|
|
193
|
+
require 'reflex'
|
|
194
|
+
|
|
195
|
+
Reflex.start name: 'Physics' do |app|
|
|
196
|
+
Reflex::Window.show title: app.name, frame: [100, 100, 500, 500] do
|
|
197
|
+
gravity 0, 9.8 * meter
|
|
198
|
+
|
|
199
|
+
50.times do
|
|
200
|
+
add Reflex::View.new {
|
|
201
|
+
pos rand(10..400), rand(10..100)
|
|
202
|
+
size rand(5..50)
|
|
203
|
+
background [:red, :green, :blue, :yellow, :orange].sample
|
|
204
|
+
dynamic true
|
|
205
|
+
shape Reflex::EllipseShape.new(density: 1)
|
|
206
|
+
}
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
add Reflex::View.new { # a static ground
|
|
210
|
+
pos 0, 480
|
|
211
|
+
size 500, 20
|
|
212
|
+
background :darkgray
|
|
213
|
+
static true
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
on :pointer do |e|
|
|
217
|
+
if e.down? || e.drag?
|
|
218
|
+
add Reflex::View.new(pos: e.pos, size: 10, dynamic: true,
|
|
219
|
+
shape: Reflex::EllipseShape.new(density: 1))
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
See the [`samples/`](./samples) directory for more examples covering shapes, layout, models, MIDI, camera capture, etc.
|
|
227
|
+
|
|
228
|
+
## 🛠️ Development
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
$ rake vendor # clone Box2D and RtMidi into vendor/
|
|
232
|
+
$ rake lib # build the native C++ library (libreflex)
|
|
233
|
+
$ rake ext # build the Ruby C extension
|
|
234
|
+
$ rake test # run the test suite
|
|
235
|
+
$ rake doc # generate RDoc from C++ sources
|
|
236
|
+
$ rake # default: builds the extension
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
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`.
|
|
240
|
+
|
|
241
|
+
In the [`xord/all`](https://github.com/xord/all) monorepo you can scope by module, e.g. `rake reflex test`.
|
|
242
|
+
|
|
46
243
|
## 📜 License
|
|
47
244
|
|
|
48
245
|
**Reflex** is licensed under the MIT License.
|
|
49
246
|
See the [LICENSE](./LICENSE) file for details.
|
|
247
|
+
|
|
248
|
+
The third-party libraries listed above retain their own licenses (Box2D: MIT, RtMidi: MIT-style).
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.4.0
|
data/ext/reflex/extconf.rb
CHANGED
|
@@ -15,12 +15,7 @@ Xot::ExtConf.new Xot, Rucy, Rays, Reflex do
|
|
|
15
15
|
headers << 'ruby.h'
|
|
16
16
|
libs.unshift 'gdi32', 'winmm', 'opengl32', 'glew32', 'xinput' if win32?
|
|
17
17
|
frameworks << 'Cocoa' << 'GameController' if osx?
|
|
18
|
-
$LDFLAGS << ' -Wl,--out-implib=
|
|
19
|
-
|
|
20
|
-
unless osx? || linux?
|
|
21
|
-
lib_dirs << Rays::Extension.ext_dir
|
|
22
|
-
libs << 'rays_ext'
|
|
23
|
-
end
|
|
18
|
+
$LDFLAGS << ' -Wl,--out-implib=libreflex.dll.a' if mingw? || cygwin?
|
|
24
19
|
end
|
|
25
20
|
|
|
26
21
|
create_makefile 'reflex_ext'
|
data/ext/reflex/key_event.cpp
CHANGED
|
@@ -123,11 +123,11 @@ RUCY_DEF0(get_key)
|
|
|
123
123
|
CASE(LBRACKET): SYMBOL1(lbracket);
|
|
124
124
|
CASE(RBRACKET): SYMBOL1(rbracket);
|
|
125
125
|
|
|
126
|
-
#if defined(OSX) || defined(IOS) || defined(WIN32) || defined(LINUX)
|
|
126
|
+
#if defined(OSX) || defined(IOS) || defined(WIN32) || defined(LINUX) || defined(WASM)
|
|
127
127
|
CASE(ENTER): SYMBOL1(enter);
|
|
128
128
|
#else
|
|
129
129
|
CASE(ENTER): SYMBOL1(enter);
|
|
130
|
-
CASE(RETURN):
|
|
130
|
+
CASE(RETURN): SYMBOL2(_return, "return");
|
|
131
131
|
#endif
|
|
132
132
|
CASE(SPACE): SYMBOL1(space);
|
|
133
133
|
CASE(TAB): SYMBOL1(tab);
|
|
@@ -145,25 +145,29 @@ RUCY_DEF0(get_key)
|
|
|
145
145
|
CASE(PAGEUP): SYMBOL1(pageup);
|
|
146
146
|
CASE(PAGEDOWN): SYMBOL1(pagedown);
|
|
147
147
|
|
|
148
|
-
#if !defined(OSX) && !defined(IOS) && !defined(LINUX)
|
|
148
|
+
#if !defined(OSX) && !defined(IOS) && !defined(LINUX) && !defined(WASM)
|
|
149
149
|
CASE(SHIFT):
|
|
150
150
|
#endif
|
|
151
151
|
CASE(LSHIFT):
|
|
152
152
|
CASE(RSHIFT): SYMBOL1(shift);
|
|
153
|
-
#if !defined(OSX) && !defined(IOS) && !defined(LINUX)
|
|
153
|
+
#if !defined(OSX) && !defined(IOS) && !defined(LINUX) && !defined(WASM)
|
|
154
154
|
CASE(CONTROL):
|
|
155
155
|
#endif
|
|
156
156
|
CASE(LCONTROL):
|
|
157
157
|
CASE(RCONTROL): SYMBOL1(control);
|
|
158
158
|
CASE(ALT):
|
|
159
|
+
#if !defined(IOS) && !defined(LINUX) && !defined(WASM)
|
|
159
160
|
CASE(LALT):
|
|
161
|
+
#endif
|
|
160
162
|
CASE(RALT): SYMBOL1(alt);
|
|
161
163
|
CASE(LWIN):
|
|
162
164
|
CASE(RWIN): SYMBOL1(win);
|
|
165
|
+
#if !defined(IOS) && !defined(LINUX) && !defined(WASM)
|
|
163
166
|
CASE(LCOMMAND):
|
|
164
167
|
CASE(RCOMMAND): SYMBOL1(command);
|
|
165
168
|
CASE(LOPTION):
|
|
166
169
|
CASE(ROPTION): SYMBOL1(option);
|
|
170
|
+
#endif
|
|
167
171
|
CASE(FUNCTION): SYMBOL1(function);
|
|
168
172
|
|
|
169
173
|
CASE(F1): SYMBOL1(f1);
|
|
@@ -198,7 +202,9 @@ RUCY_DEF0(get_key)
|
|
|
198
202
|
CASE(NUM_EQUAL): SYMBOL1(equal);
|
|
199
203
|
CASE(NUM_COMMA): SYMBOL1(comma);
|
|
200
204
|
CASE(NUM_DECIMAL): SYMBOL1(decimal);
|
|
205
|
+
#if !defined(IOS) && !defined(LINUX) && !defined(WASM)
|
|
201
206
|
CASE(NUM_CLEAR): SYMBOL1(clear);
|
|
207
|
+
#endif
|
|
202
208
|
CASE(NUM_ENTER): SYMBOL1(enter);
|
|
203
209
|
|
|
204
210
|
CASE(CAPSLOCK): SYMBOL1(capslock);
|
data/ext/reflex/view.cpp
CHANGED
|
@@ -217,8 +217,7 @@ RUCY_DEF0(each_child)
|
|
|
217
217
|
CHECK;
|
|
218
218
|
|
|
219
219
|
Value ret;
|
|
220
|
-
|
|
221
|
-
for (Reflex::View::child_iterator it = THIS->child_begin(); it != end; ++it)
|
|
220
|
+
for (auto it = THIS->child_begin(), end = THIS->child_end(); it != end; ++it)
|
|
222
221
|
ret = rb_yield(value(it->get()));
|
|
223
222
|
return ret;
|
|
224
223
|
}
|