asobi 0.1.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 +7 -0
- data/DESIGN.md +388 -0
- data/LICENSE +21 -0
- data/README.ja.md +154 -0
- data/README.md +157 -0
- data/docs/first-sketch.svg +1 -0
- data/exe/asobi +106 -0
- data/lib/asobi/art.rb +498 -0
- data/lib/asobi/color.rb +236 -0
- data/lib/asobi/core_ext.rb +28 -0
- data/lib/asobi/geometry.rb +126 -0
- data/lib/asobi/live.rb +256 -0
- data/lib/asobi/many.rb +235 -0
- data/lib/asobi/messages.rb +155 -0
- data/lib/asobi/motion.rb +471 -0
- data/lib/asobi/nodes.rb +230 -0
- data/lib/asobi/pure.rb +21 -0
- data/lib/asobi/random.rb +51 -0
- data/lib/asobi/reel.rb +113 -0
- data/lib/asobi/shapes.rb +314 -0
- data/lib/asobi/sketch.rb +207 -0
- data/lib/asobi/svg.rb +352 -0
- data/lib/asobi/top_level.rb +14 -0
- data/lib/asobi/version.rb +5 -0
- data/lib/asobi/web/live.html +34 -0
- data/lib/asobi/web/player.js +295 -0
- data/lib/asobi.rb +12 -0
- metadata +73 -0
data/README.md
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# asobi
|
|
2
|
+
|
|
3
|
+
[日本語版](README.ja.md)
|
|
4
|
+
|
|
5
|
+
asobi (Japanese for “play”) is a Ruby library for making SVG pictures and looping animations. A picture is a Ruby value. Here, eight copies of a petal form a flower, and the whole flower turns once every eight seconds.
|
|
6
|
+
|
|
7
|
+
```ruby
|
|
8
|
+
require "asobi"
|
|
9
|
+
|
|
10
|
+
flower = petal.repeat(8).in_fan.colored(:crimson, :navy)
|
|
11
|
+
show flower.spin(every: 8)
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
<img src="docs/first-sketch.svg" width="240" alt="Eight petals, alternating crimson and navy, arranged around a common centre.">
|
|
15
|
+
|
|
16
|
+
## Run your first sketch
|
|
17
|
+
|
|
18
|
+
You'll need Ruby **3.2 or newer** and a browser. The library uses only Ruby's standard library. Run this command from the repository root.
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
ruby exe/asobi
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
asobi creates `play.rb` with the flower sketch above and opens a live preview in your browser. If `play.rb` already exists, it opens that sketch. Change `repeat(8)` to `repeat(12)` and save to add four petals. Change `every: 8` to `every: 2` to make the flower turn once every two seconds. Press Ctrl-C in the terminal to stop the preview.
|
|
25
|
+
|
|
26
|
+
To open another sketch, pass its path, for example `ruby exe/asobi examples/flower.rb`. Add `--no-open` to open the URL printed in the terminal yourself. The server looks for a free port starting at 4567. Use `--port 5000` to start the search at 5000.
|
|
27
|
+
|
|
28
|
+
### Install the command for use in other folders
|
|
29
|
+
|
|
30
|
+
Build and install the gem from the repository root.
|
|
31
|
+
|
|
32
|
+
```sh
|
|
33
|
+
gem build asobi.gemspec --output asobi.gem
|
|
34
|
+
gem install --local ./asobi.gem
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
You can now run `asobi` in a sketch folder to start with `play.rb`, or open an existing sketch with `asobi sketch.rb`. A file that contains `require "asobi"` and calls `show` also runs with `ruby sketch.rb`.
|
|
38
|
+
|
|
39
|
+
## Build pictures in Ruby
|
|
40
|
+
|
|
41
|
+
To try each example in this section, keep `require "asobi"` at the top of `play.rb` and replace the lines beneath it.
|
|
42
|
+
|
|
43
|
+
### Arrange and combine shapes
|
|
44
|
+
|
|
45
|
+
`repeat` makes copies in the same spot. `in_fan` turns them around a shared centre. With `a + b`, the second picture sits on top, so a small circle added last becomes the centre of the flower.
|
|
46
|
+
|
|
47
|
+
```ruby
|
|
48
|
+
petals = petal(40, 16).repeat(8).in_fan.colored(:crimson, :navy)
|
|
49
|
+
flower = petals + circle(5).colored(:gold)
|
|
50
|
+
show flower.beside(flower.scale(0.6), gap: 12)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The smaller flower is a new picture. The original keeps its size. Lengths are relative to the other shapes in the sketch, and the preview fits the drawing to the screen. Angles are measured in degrees clockwise, so `rotate(90)` makes a quarter turn.
|
|
54
|
+
|
|
55
|
+
### Turn data into a picture
|
|
56
|
+
|
|
57
|
+
With `to_art`, a number becomes a disc, and an array becomes a row of pictures in element order. Give it a shape with `as:` to use each number as the size of that shape. Here the Fibonacci sequence becomes a row of growing stars.
|
|
58
|
+
|
|
59
|
+
```ruby
|
|
60
|
+
fib = [1, 1, 2, 3, 5, 8, 13, 21]
|
|
61
|
+
show fib.to_art(as: star).colored(:gold, :orange), on: :black
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Give your own class a visual form
|
|
65
|
+
|
|
66
|
+
Define `to_art` to return the picture for an instance. Include `Asobi::Art` when you also want to call methods such as `spin` on the instance itself.
|
|
67
|
+
|
|
68
|
+
```ruby
|
|
69
|
+
class Flower
|
|
70
|
+
include Asobi::Art
|
|
71
|
+
|
|
72
|
+
def initialize(count, color)
|
|
73
|
+
@count = count
|
|
74
|
+
@color = color
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def to_art
|
|
78
|
+
petal.repeat(@count).in_fan.colored(@color)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
show Flower.new(7, :coral).spin(every: 4)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Animate a group
|
|
86
|
+
|
|
87
|
+
The dots below grow to twice their size and shrink back over two seconds. Placing `one_by_one` after `pulse` offsets the start of each dot's motion, sending a pulse around the ring.
|
|
88
|
+
|
|
89
|
+
```ruby
|
|
90
|
+
dots = circle(5).repeat(24).in_ring.colored(:gold, :orange)
|
|
91
|
+
show dots.pulse(every: 2, to: 2).one_by_one, on: :black
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
| To do this | Try |
|
|
95
|
+
| --- | --- |
|
|
96
|
+
| Start with a shape | `circle`, `rect`, `star`, `text`, `path`, `wave`, `heart`, `petal` |
|
|
97
|
+
| Arrange copies | `in_row` for a line, `in_grid` for tiles, `in_ring` for a circle, `in_spiral` for a spiral |
|
|
98
|
+
| Add colour and texture | `colored(from: :gold, to: :crimson)` for a gradient, `glowing` for a halo, `shadowed` for a shadow, `clipped_by` for a cut-out |
|
|
99
|
+
| Add motion | `spin` to rotate, `sway` to rock, `orbit` to circle a point, `trail` for fading copies, `animate` for your own motion |
|
|
100
|
+
| Set up the preview | In `show`, use `on:` for the background colour, `paper:` for page dimensions, or `grain: true` for film grain |
|
|
101
|
+
|
|
102
|
+
## Preview, export and record
|
|
103
|
+
|
|
104
|
+
When a saved sketch has an error, the live preview keeps the last picture that worked and displays the error underneath. Press **C** in the browser to toggle the source code. In browsers that support recording, **R** starts recording and **R** again downloads the clip as MP4 or WebM.
|
|
105
|
+
|
|
106
|
+
Use `save` to write a still picture as SVG or a playable animation as a self-contained HTML file. You can open the HTML file directly in a browser. The example writes both files to the current directory.
|
|
107
|
+
|
|
108
|
+
```ruby
|
|
109
|
+
flower = petal.repeat(8).in_fan.colored(:crimson, :navy)
|
|
110
|
+
motion = flower.spin(every: 4)
|
|
111
|
+
|
|
112
|
+
flower.save("flower.svg")
|
|
113
|
+
motion.save("flower.html")
|
|
114
|
+
show motion
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
To save a particular frame, use `motion.at(1).save("frame.svg")` for the picture one second into the loop.
|
|
118
|
+
|
|
119
|
+
## Use asobi from an existing Ruby program
|
|
120
|
+
|
|
121
|
+
Load `asobi/pure` to keep shape names under `Asobi` and leave Ruby's built-in classes untouched. Save the following as `export.rb` in the repository root and run `ruby -Ilib export.rb`. It writes three navy stars to `stars.svg` in the same directory.
|
|
122
|
+
|
|
123
|
+
```ruby
|
|
124
|
+
require "asobi/pure"
|
|
125
|
+
|
|
126
|
+
stars = Asobi.art([3, 1, 4], as: Asobi.star)
|
|
127
|
+
stars.colored(:navy).save("stars.svg")
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Examples and reference
|
|
131
|
+
|
|
132
|
+
Each file is a complete sketch that you can run with `ruby exe/asobi` followed by its path. [Browse all examples](examples/README.md)
|
|
133
|
+
|
|
134
|
+
- Turn a number sequence into a row of stars. [Number sequence](examples/sequence.rb)
|
|
135
|
+
- Layer glow, screen blending and trails. [Neon flower](examples/neon_flower.rb)
|
|
136
|
+
- Give a custom class an orbit and compose several instances into one picture. [Planets](examples/planets.rb)
|
|
137
|
+
|
|
138
|
+
The documentation site includes a step-by-step guide, a gallery, a reference with method defaults and examples, and a browser playground. To build it locally, run the following from the repository root.
|
|
139
|
+
|
|
140
|
+
```sh
|
|
141
|
+
bundle install
|
|
142
|
+
bundle exec rake site
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Open `_site/index.html` in a browser for English or `_site/ja/index.html` for Japanese. Editing and running code in the playground requires an internet connection to load its Ruby WebAssembly runtime from a CDN.
|
|
146
|
+
|
|
147
|
+
## Development
|
|
148
|
+
|
|
149
|
+
After `bundle install`, run `bundle exec rake test`. The suite checks the library and CLI, builds the site with test data, and runs the sketch examples from the gallery and both READMEs. For the design of pictures, motion and the `to_art` protocol, see the [design notes (Japanese)](DESIGN.md).
|
|
150
|
+
|
|
151
|
+
## Bugs and feature ideas
|
|
152
|
+
|
|
153
|
+
Issues are welcome in English or Japanese. For a bug, a small sketch that shows the problem helps us reproduce it. For an idea, tell us what you'd like to draw or animate. [Open an issue](https://github.com/Hayato1031/asobi/issues/new/choose)
|
|
154
|
+
|
|
155
|
+
## License
|
|
156
|
+
|
|
157
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-23.2 -23.2 46.4 46.4" width="512" height="512" stroke-linejoin="round" stroke-linecap="round"><rect x="-23.2" y="-23.2" width="46.4" height="46.4" fill="white"/><g><g fill="crimson"><path d="M 0 0 C -7.385 -3 -5.908 -15 0 -20 C 5.908 -15 7.385 -3 0 0 Z"/></g><g transform="matrix(0.707 0.707 -0.707 0.707 0 0)"><g fill="navy"><path d="M 0 0 C -7.385 -3 -5.908 -15 0 -20 C 5.908 -15 7.385 -3 0 0 Z"/></g></g><g transform="matrix(0 1 -1 0 0 0)"><g fill="crimson"><path d="M 0 0 C -7.385 -3 -5.908 -15 0 -20 C 5.908 -15 7.385 -3 0 0 Z"/></g></g><g transform="matrix(-0.707 0.707 -0.707 -0.707 0 0)"><g fill="navy"><path d="M 0 0 C -7.385 -3 -5.908 -15 0 -20 C 5.908 -15 7.385 -3 0 0 Z"/></g></g><g transform="matrix(-1 0 0 -1 0 0)"><g fill="crimson"><path d="M 0 0 C -7.385 -3 -5.908 -15 0 -20 C 5.908 -15 7.385 -3 0 0 Z"/></g></g><g transform="matrix(-0.707 -0.707 0.707 -0.707 0 0)"><g fill="navy"><path d="M 0 0 C -7.385 -3 -5.908 -15 0 -20 C 5.908 -15 7.385 -3 0 0 Z"/></g></g><g transform="matrix(0 -1 1 0 0 0)"><g fill="crimson"><path d="M 0 0 C -7.385 -3 -5.908 -15 0 -20 C 5.908 -15 7.385 -3 0 0 Z"/></g></g><g transform="matrix(0.707 -0.707 0.707 0.707 0 0)"><g fill="navy"><path d="M 0 0 C -7.385 -3 -5.908 -15 0 -20 C 5.908 -15 7.385 -3 0 0 Z"/></g></g></g></svg>
|
data/exe/asobi
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# asobi play.rb がなければ見本を作り、ライブ表示する
|
|
5
|
+
# asobi FILE.rb そのスケッチをライブ表示する
|
|
6
|
+
# asobi version
|
|
7
|
+
#
|
|
8
|
+
# Test seam: ASOBI_CREATE_ONLY=1 stops right after play.rb has been created
|
|
9
|
+
# (or found), before the live view starts.
|
|
10
|
+
|
|
11
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __dir__) # a checkout; an installed gem is already on the path
|
|
12
|
+
require "optparse"
|
|
13
|
+
require "asobi"
|
|
14
|
+
require "asobi/live"
|
|
15
|
+
|
|
16
|
+
module Asobi
|
|
17
|
+
Messages.add(:cli_missing,
|
|
18
|
+
en: "asobi can't find %{file}. Check the name, like: asobi flower.rb (or run asobi alone to start with play.rb)",
|
|
19
|
+
ja: "%{file} が見つかりません。名前を確かめてください。例:asobi flower.rb(asobi だけで実行すると play.rb から始められます)")
|
|
20
|
+
Say.add(:cli_created,
|
|
21
|
+
en: "asobi: made %{file} for you. Change anything in it and save.",
|
|
22
|
+
ja: "asobi: %{file} を作りました。好きなところを書き換えて保存してください。")
|
|
23
|
+
Messages.add(:cli_bad_option,
|
|
24
|
+
en: "asobi doesn't know %{got}. See the options with, like: asobi --help",
|
|
25
|
+
ja: "%{got} は知りません。使えるオプションは次で見られます。例:asobi --help")
|
|
26
|
+
Say.add(:cli_help,
|
|
27
|
+
en: <<~TEXT,
|
|
28
|
+
asobi: play with pictures in Ruby
|
|
29
|
+
|
|
30
|
+
asobi make play.rb if it is missing, and show it live
|
|
31
|
+
asobi FILE.rb show that sketch live (save the file and the picture follows)
|
|
32
|
+
asobi version
|
|
33
|
+
asobi help
|
|
34
|
+
|
|
35
|
+
options:
|
|
36
|
+
--port N start looking for a free port at N (default 4567)
|
|
37
|
+
--no-open don't open the browser
|
|
38
|
+
TEXT
|
|
39
|
+
ja: <<~TEXT)
|
|
40
|
+
asobi: Ruby で絵と遊ぶ
|
|
41
|
+
|
|
42
|
+
asobi play.rb がなければ見本を作って、ライブ表示します
|
|
43
|
+
asobi FILE.rb そのスケッチをライブ表示します(保存すると絵が変わります)
|
|
44
|
+
asobi version
|
|
45
|
+
asobi help
|
|
46
|
+
|
|
47
|
+
オプション:
|
|
48
|
+
--port N ポート N から空きを探す(初期値 4567)
|
|
49
|
+
--no-open ブラウザを開かない
|
|
50
|
+
TEXT
|
|
51
|
+
Say.add(:cli_starter_comment,
|
|
52
|
+
en: "Change anything and save. The picture follows.",
|
|
53
|
+
ja: "どこでも書き換えて保存してください。絵がついてきます。")
|
|
54
|
+
|
|
55
|
+
module CLI
|
|
56
|
+
module_function
|
|
57
|
+
|
|
58
|
+
def starter
|
|
59
|
+
<<~RUBY
|
|
60
|
+
require "asobi"
|
|
61
|
+
|
|
62
|
+
# #{Say.text(:cli_starter_comment)}
|
|
63
|
+
flower = petal.repeat(8).in_fan.colored(:crimson, :navy)
|
|
64
|
+
|
|
65
|
+
show flower.spin(every: 8)
|
|
66
|
+
RUBY
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def main(argv)
|
|
70
|
+
options = { port: Live::DEFAULT_PORT, open: true }
|
|
71
|
+
parser = OptionParser.new do |o|
|
|
72
|
+
o.on("--port N", Integer) { options[:port] = _1 }
|
|
73
|
+
o.on("--no-open") { options[:open] = false }
|
|
74
|
+
o.on("-h", "--help") { options[:help] = true }
|
|
75
|
+
o.on("-v", "--version") { options[:version] = true }
|
|
76
|
+
end
|
|
77
|
+
begin
|
|
78
|
+
args = parser.parse(argv)
|
|
79
|
+
rescue OptionParser::ParseError => e
|
|
80
|
+
warn Messages.text(:cli_bad_option, got: e.args.first || e.message)
|
|
81
|
+
return 1
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
return puts(Say.text(:cli_help)) || 0 if options[:help] || args.first == "help"
|
|
85
|
+
return puts(Asobi::VERSION) || 0 if options[:version] || args.first == "version"
|
|
86
|
+
|
|
87
|
+
file = args.first || "play.rb"
|
|
88
|
+
if args.empty? && !File.exist?(file)
|
|
89
|
+
File.write(file, starter, encoding: "UTF-8")
|
|
90
|
+
puts Say.text(:cli_created, file:)
|
|
91
|
+
elsif !File.file?(file)
|
|
92
|
+
warn Messages.text(:cli_missing, file:)
|
|
93
|
+
return 1
|
|
94
|
+
end
|
|
95
|
+
return 0 if ENV["ASOBI_CREATE_ONLY"] == "1"
|
|
96
|
+
|
|
97
|
+
Live.run(file, port: options[:port], open: options[:open])
|
|
98
|
+
0
|
|
99
|
+
rescue IOError, SystemCallError => e
|
|
100
|
+
warn e.message
|
|
101
|
+
1
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
exit Asobi::CLI.main(ARGV)
|