summaryrefslogtreecommitdiffhomepage
path: root/docs/docs.html
diff options
context:
space:
mode:
Diffstat (limited to 'docs/docs.html')
-rw-r--r--docs/docs.html2645
1 files changed, 2582 insertions, 63 deletions
diff --git a/docs/docs.html b/docs/docs.html
index 0b76535..8fafcb1 100644
--- a/docs/docs.html
+++ b/docs/docs.html
@@ -1,7 +1,7 @@
<html>
<head>
<title>DragonRuby Game Toolkit Documentation</title>
- <link href="docs.css?ver=1602116477" rel="stylesheet" type="text/css" media="all">
+ <link href="docs.css?ver=1605252394" rel="stylesheet" type="text/css" media="all">
</head>
<body>
<div id='toc'>
@@ -31,9 +31,9 @@
<li><a href='#--cheatsheet--using--args.state--to-store-your-game-state'>CHEATSHEET: Using <code>args.state</code> To Store Your Game State</a></li>
<li><a href='#--cheatsheet--troubleshoot-performance'>CHEATSHEET: Troubleshoot Performance</a></li>
<li><a href='#--docs---gtk--runtime-'>DOCS: <code>GTK::Runtime</code></a></li>
+<li><a href='#--docs---gtk--runtime#write_file-'>DOCS: <code>GTK::Runtime#write_file</code></a></li>
<li><a href='#--docs---gtk--runtime#reset-'>DOCS: <code>GTK::Runtime#reset</code></a></li>
<li><a href='#--docs---gtk--runtime#calcstringbox-'>DOCS: <code>GTK::Runtime#calcstringbox</code></a></li>
-<li><a href='#--docs---gtk--runtime#write_file-'>DOCS: <code>GTK::Runtime#write_file</code></a></li>
<li><a href='#--docs---array-'>DOCS: <code>Array</code></a></li>
<li><a href='#--docs---array#map-'>DOCS: <code>Array#map</code></a></li>
<li><a href='#--docs---array#each-'>DOCS: <code>Array#each</code></a></li>
@@ -46,6 +46,7 @@
<li><a href='#--docs---gtk--outputs-'>DOCS: <code>GTK::Outputs</code></a></li>
<li><a href='#--docs---gtk--outputs#solids-'>DOCS: <code>GTK::Outputs#solids</code></a></li>
<li><a href='#--docs---gtk--outputs#borders-'>DOCS: <code>GTK::Outputs#borders</code></a></li>
+<li><a href='#--docs---gtk--outputs#screenshots-'>DOCS: <code>GTK::Outputs#screenshots</code></a></li>
<li><a href='#--docs---gtk--mouse-'>DOCS: <code>GTK::Mouse</code></a></li>
<li><a href='#--docs---gtk--mousepoint-'>DOCS: <code>GTK::MousePoint</code></a></li>
<li><a href='#--docs---gtk--openentity-'>DOCS: <code>GTK::OpenEntity</code></a></li>
@@ -69,17 +70,19 @@
<li><a href='#--learn-ruby-optional---intermediate-ruby-primer---functions.txt'>Learn Ruby Optional - Intermediate Ruby Primer - functions.txt</a></li>
<li><a href='#--learn-ruby-optional---intermediate-ruby-primer---arrays.txt'>Learn Ruby Optional - Intermediate Ruby Primer - arrays.txt</a></li>
<li><a href='#--learn-ruby-optional---intermediate-ruby-primer---main.rb'>Learn Ruby Optional - Intermediate Ruby Primer - main.rb</a></li>
-<li><a href='#--learn-ruby-optional---intermediate-ruby-primer---repl.rb'>Learn Ruby Optional - Intermediate Ruby Primer - repl.rb</a></li>
<li><a href='#--rendering-basics---labels---main.rb'>Rendering Basics - Labels - main.rb</a></li>
<li><a href='#--rendering-basics---lines---main.rb'>Rendering Basics - Lines - main.rb</a></li>
<li><a href='#--rendering-basics---solids-borders---main.rb'>Rendering Basics - Solids Borders - main.rb</a></li>
<li><a href='#--rendering-basics---sprites---main.rb'>Rendering Basics - Sprites - main.rb</a></li>
<li><a href='#--rendering-basics---sounds---main.rb'>Rendering Basics - Sounds - main.rb</a></li>
+<li><a href='#--rendering-basics---audio-mixer---main.rb'>Rendering Basics - Audio Mixer - main.rb</a></li>
+<li><a href='#--rendering-basics---sound-synthesis---main.rb'>Rendering Basics - Sound Synthesis - main.rb</a></li>
<li><a href='#--input-basics---keyboard---main.rb'>Input Basics - Keyboard - main.rb</a></li>
<li><a href='#--input-basics---mouse---main.rb'>Input Basics - Mouse - main.rb</a></li>
<li><a href='#--input-basics---mouse-point-to-rect---main.rb'>Input Basics - Mouse Point To Rect - main.rb</a></li>
<li><a href='#--input-basics---mouse-rect-to-rect---main.rb'>Input Basics - Mouse Rect To Rect - main.rb</a></li>
<li><a href='#--input-basics---controller---main.rb'>Input Basics - Controller - main.rb</a></li>
+<li><a href='#--input-basics---touch---main.rb'>Input Basics - Touch - main.rb</a></li>
<li><a href='#--rendering-sprites---animation-using-separate-pngs---main.rb'>Rendering Sprites - Animation Using Separate Pngs - main.rb</a></li>
<li><a href='#--rendering-sprites---animation-using-sprite-sheet---main.rb'>Rendering Sprites - Animation Using Sprite Sheet - main.rb</a></li>
<li><a href='#--rendering-sprites---animation-states---main.rb'>Rendering Sprites - Animation States - main.rb</a></li>
@@ -100,6 +103,7 @@
<li><a href='#--advanced-rendering---render-target-viewports---main.rb'>Advanced Rendering - Render Target Viewports - main.rb</a></li>
<li><a href='#--advanced-rendering---render-primitive-hierarchies---main.rb'>Advanced Rendering - Render Primitive Hierarchies - main.rb</a></li>
<li><a href='#--advanced-rendering---render-primitives-as-hash---main.rb'>Advanced Rendering - Render Primitives As Hash - main.rb</a></li>
+<li><a href='#--advanced-rendering---pixel-arrays---main.rb'>Advanced Rendering - Pixel Arrays - main.rb</a></li>
<li><a href='#--tweening-lerping-easing-functions---easing-functions---main.rb'>Tweening Lerping Easing Functions - Easing Functions - main.rb</a></li>
<li><a href='#--tweening-lerping-easing-functions---cubic-bezier---main.rb'>Tweening Lerping Easing Functions - Cubic Bezier - main.rb</a></li>
<li><a href='#--tweening-lerping-easing-functions---easing-using-spline---main.rb'>Tweening Lerping Easing Functions - Easing Using Spline - main.rb</a></li>
@@ -123,6 +127,8 @@
<li><a href='#--advanced-debugging---unit-tests---state_serialization_experimental_tests.rb'>Advanced Debugging - Unit Tests - state_serialization_experimental_tests.rb</a></li>
<li><a href='#--http---retrieve-images---main.rb'>Http - Retrieve Images - main.rb</a></li>
<li><a href='#--12-c-extensions---basics---main.rb'>12 C Extensions - Basics - main.rb</a></li>
+<li><a href='#--12-c-extensions---intermediate---main.rb'>12 C Extensions - Intermediate - main.rb</a></li>
+<li><a href='#--12-c-extensions---native-pixel-arrays---main.rb'>12 C Extensions - Native Pixel Arrays - main.rb</a></li>
<li><a href='#--3d---3d-cube---main.rb'>3d - 3d Cube - main.rb</a></li>
<li><a href='#--arcade---dueling-starships---main.rb'>Arcade - Dueling Starships - main.rb</a></li>
<li><a href='#--arcade/flappy-dragon/credits.txt'>arcade/flappy dragon/credits.txt</a></li>
@@ -140,7 +146,6 @@
<li><a href='#--platformer---clepto-frog---map.rb'>Platformer - Clepto Frog - map.rb</a></li>
<li><a href='#--platformer---gorillas-basic---credits.txt'>Platformer - Gorillas Basic - credits.txt</a></li>
<li><a href='#--platformer---gorillas-basic---main.rb'>Platformer - Gorillas Basic - main.rb</a></li>
-<li><a href='#--platformer---gorillas-basic---repl.rb'>Platformer - Gorillas Basic - repl.rb</a></li>
<li><a href='#--platformer---gorillas-basic---tests.rb'>Platformer - Gorillas Basic - tests.rb</a></li>
<li><a href='#--platformer---gorillas-basic---tests---building_generation_tests.rb'>Platformer - Gorillas Basic - Tests - building_generation_tests.rb</a></li>
<li><a href='#--platformer---the-little-probe---main.rb'>Platformer - The Little Probe - main.rb</a></li>
@@ -150,7 +155,6 @@
<li><a href='#--rpg-narrative---choose-your-own-adventure---main.rb'>Rpg Narrative - Choose Your Own Adventure - main.rb</a></li>
<li><a href='#--rpg-narrative---return-of-serenity---lowrez_simulator.rb'>Rpg Narrative - Return Of Serenity - lowrez_simulator.rb</a></li>
<li><a href='#--rpg-narrative---return-of-serenity---main.rb'>Rpg Narrative - Return Of Serenity - main.rb</a></li>
-<li><a href='#--rpg-narrative---return-of-serenity---repl.rb'>Rpg Narrative - Return Of Serenity - repl.rb</a></li>
<li><a href='#--rpg-narrative---return-of-serenity---require.rb'>Rpg Narrative - Return Of Serenity - require.rb</a></li>
<li><a href='#--rpg-narrative---return-of-serenity---storyline.rb'>Rpg Narrative - Return Of Serenity - storyline.rb</a></li>
<li><a href='#--rpg-narrative---return-of-serenity---storyline_anka.rb'>Rpg Narrative - Return Of Serenity - storyline_anka.rb</a></li>
@@ -187,12 +191,17 @@
<li><a href='#--geometry.rb'>geometry.rb</a></li>
<li><a href='#--grid.rb'>grid.rb</a></li>
<li><a href='#--inputs.rb'>inputs.rb</a></li>
+<li><a href='#--ios_wizard.rb'>ios_wizard.rb</a></li>
+<li><a href='#--itch_wizard.rb'>itch_wizard.rb</a></li>
+<li><a href='#--layout.rb'>layout.rb</a></li>
<li><a href='#--log.rb'>log.rb</a></li>
<li><a href='#--numeric.rb'>numeric.rb</a></li>
+<li><a href='#--runtime/draw.rb'>runtime/draw.rb</a></li>
<li><a href='#--runtime/framerate_diagnostics.rb'>runtime/framerate_diagnostics.rb</a></li>
<li><a href='#--string.rb'>string.rb</a></li>
<li><a href='#--tests.rb'>tests.rb</a></li>
<li><a href='#--trace.rb'>trace.rb</a></li>
+<li><a href='#--wizards.rb'>wizards.rb</a></li>
</ul> </div>
<div id='content'>
<h1 id='--dragonruby-game-toolkit-live-docs'>DragonRuby Game Toolkit Live Docs</h1>
@@ -1144,6 +1153,16 @@ end
<p>
The GTK::Runtime class is the core of DragonRuby. It is globally accessible via <code>$gtk</code>.
</p>
+<h1 id='--docs---gtk--runtime#write_file-'>DOCS: <code>GTK::Runtime#write_file</code></h1>
+<p>
+This function takes in two parameters. The first paramter is the file path and assumes the the game directory is the root. The second parameter is the string that will be written. The method overwrites whatever is currently in the file. Use <code>GTK::Runtime#append_file</code> to append to the file as opposed to overwriting.
+</p>
+<pre>def tick args
+ if args.inputs.mouse.click
+ args.gtk.write_file "last-mouse-click.txt", "Mouse was clicked at #{args.state.tick_count}."
+ end
+end
+</pre>
<h1 id='--docs---gtk--runtime#reset-'>DOCS: <code>GTK::Runtime#reset</code></h1>
<p>
This function will reset Kernel.tick_count to 0 and will remove all data from args.state.
@@ -1157,16 +1176,6 @@ This function returns the width and height of a string.
args.state.string_size_font_size ||= args.gtk.calcstringbox "Hello World"
end
</pre>
-<h1 id='--docs---gtk--runtime#write_file-'>DOCS: <code>GTK::Runtime#write_file</code></h1>
-<p>
-This function takes in two parameters. The first paramter is the file path and assumes the the game directory is the root. The second parameter is the string that will be written. The method overwrites whatever is currently in the file. Use <code>GTK::Runtime#append_file</code> to append to the file as opposed to overwriting.
-</p>
-<pre>def tick args
- if args.inputs.mouse.click
- args.gtk.write_file "last-mouse-click.txt", "Mouse was clicked at #{args.state.tick_count}."
- end
-end
-</pre>
<h1 id='--docs---array-'>DOCS: <code>Array</code></h1>
<p>
The Array class has been extend to provide methods that will help in common game development tasks. Array is one of the most powerful classes in Ruby and a very fundamental component of Game Toolkit.
@@ -1485,6 +1494,25 @@ You have to use <code>args.outputs.borders</code>:
args.outputs.borders << [100, 100, 160, 90]
end
</pre>
+<h1 id='--docs---gtk--outputs#screenshots-'>DOCS: <code>GTK::Outputs#screenshots</code></h1>
+<p>
+Add a hash to this collection to take a screenshot and save as png file. The keys of the hash can be provided in any order.
+</p>
+<pre>def tick args
+ args.outputs.screenshots << {
+ x: 0, y: 0, w: 100, h: 100, # Which portion of the screen should be captured
+ path: 'screenshot.png', # Output path of PNG file (inside game directory)
+ r: 255, g: 255, b: 255, a: 0 # Optional chroma key
+ }
+end
+</pre>
+<h2>Chroma key (Making a color transparent)</h2>
+<p>
+By specifying the r, g, b and a keys of the hash you change the transparency of a color in the resulting PNG file. This can be useful if you want to create files with transparent background like spritesheets. The transparency of the color specified by <code>r</code>, <code>g</code>, <code>b</code> will be set to the transparency specified by <code>a</code>.
+</p>
+<p>
+The example above sets the color white (255, 255, 255) as transparent.
+</p>
<h1 id='--docs---gtk--mouse-'>DOCS: <code>GTK::Mouse</code></h1>
<p>
The mouse is accessible via <code>args.inputs.mouse</code>:
@@ -2823,10 +2851,6 @@ def tick args
end
</pre>
-<h1 id='--learn-ruby-optional---intermediate-ruby-primer---repl.rb'>Learn Ruby Optional - Intermediate Ruby Primer - repl.rb</h1>
-<pre># ./samples/00_learn_ruby_optional/00_intermediate_ruby_primer/app/repl.rb
-
-</pre>
<h1 id='--rendering-basics---labels---main.rb'>Rendering Basics - Labels - main.rb</h1>
<pre># ./samples/01_rendering_basics/01_labels/app/main.rb
=begin
@@ -3298,6 +3322,530 @@ def tick_instructions args, text, y = 715
end
</pre>
+<h1 id='--rendering-basics---audio-mixer---main.rb'>Rendering Basics - Audio Mixer - main.rb</h1>
+<pre># ./samples/01_rendering_basics/06_audio_mixer/app/main.rb
+$gtk.reset
+
+$boxsize = 30
+
+def render_sources args
+ mouse_in_panel = (args.state.selected != 0) && args.inputs.mouse.position.inside_rect?([900, 450, 340, 250])
+ mouse_new_down = (args.state.mouse_held == 1)
+
+ if (mouse_new_down && !mouse_in_panel)
+ args.state.selected = 0 # will reset below if we hit something.
+ end
+
+ args.audio.keys.each { |k|
+ s = args.audio[k]
+
+ if (mouse_new_down) && !mouse_in_panel && args.inputs.mouse.position.inside_rect?([s[:screenx], s[:screeny], $boxsize, $boxsize])
+ args.state.selected = k
+ args.state.dragging_source = true
+ end
+
+ isselected = (k == args.state.selected)
+
+ if isselected && args.state.dragging_source
+ # you can hang anything on the audio hashes you want, so we store the
+ # actual screen position so it doesn't scale weirdly vs your mouse.
+ s[:screenx] = args.inputs.mouse.x - ($boxsize / 2)
+ s[:screeny] = args.inputs.mouse.y - ($boxsize / 2)
+
+ s[:screeny] = 50 if s[:screeny] < 50
+ s[:screeny] = (719 - $boxsize) if s[:screeny] > (719 - $boxsize)
+ s[:screenx] = 0 if s[:screenx] < 0
+ s[:screenx] = (1279 - $boxsize) if s[:screenx] > (1279 - $boxsize)
+
+ s[:x] = ((s[:screenx] / 1279.0) * 2.0) - 1.0 # scale to -1.0 - 1.0 range
+ s[:y] = ((s[:screeny] / 719.0) * 2.0) - 1.0 # scale to -1.0 - 1.0 range
+ end
+
+ color = isselected ? [ 0, 255, 0, 255 ] : [ 0, 0, 255, 255 ]
+ args.outputs.primitives << [s[:screenx], s[:screeny], $boxsize, $boxsize, *color].solid
+ }
+end
+
+def render_panel args
+ s = args.audio[args.state.selected]
+ return if s.nil?
+ mouse_down = (args.state.mouse_held > 0)
+
+ args.outputs.primitives << [900, 450, 340, 250, 127, 127, 200, 255].solid
+ args.outputs.primitives << [1075, 690, "Source ##{args.state.selected}", 3, 1, 255, 255, 255].label
+ args.outputs.primitives << [910, 660, 1230, 660, 255, 255, 255].line
+ args.outputs.primitives << [910, 650, "screen: (#{s[:screenx].to_i}, #{s[:screeny].to_i})", 0, 0, 255, 255, 255].label
+ args.outputs.primitives << [910, 625, "position: (#{s[:x].round(5).to_s[0..6]}, #{s[:y].round(5).to_s[0..6]})", 0, 0, 255, 255, 255].label
+
+ slider = [1022, 586, 200, 7]
+ if mouse_down && args.inputs.mouse.position.inside_rect?(slider)
+ s[:pitch] = ((args.inputs.mouse.x - slider[0]).to_f / (slider[2]-1.0)) * 2.0
+ end
+ slidercolor = (s[:pitch] / 2.0) * 255
+ args.outputs.primitives << [*slider, slidercolor, slidercolor, slidercolor, 255].solid
+ args.outputs.primitives << [910, 600, "pitch: #{s[:pitch].round(3).to_s[0..2]}", 0, 0, 255, 255, 255].label
+
+ slider = [1022, 561, 200, 7]
+ if mouse_down && args.inputs.mouse.position.inside_rect?(slider)
+ s[:gain] = (args.inputs.mouse.x - slider[0]).to_f / (slider[2]-1.0)
+ end
+ slidercolor = s[:gain] * 255
+ args.outputs.primitives << [*slider, slidercolor, slidercolor, slidercolor, 255].solid
+ args.outputs.primitives << [910, 575, "gain: #{s[:gain].round(3).to_s[0..2]}", 0, 0, 255, 255, 255].label
+
+ checkbox = [1022, 533, 10, 12]
+ if (args.state.mouse_held == 1) && args.inputs.mouse.position.inside_rect?(checkbox)
+ s[:looping] = !s[:looping]
+ end
+ checkboxcolor = s[:looping] ? 255 : 0
+ args.outputs.primitives << [*checkbox, checkboxcolor, checkboxcolor, checkboxcolor, 255].solid
+ args.outputs.primitives << [910, 550, "looping:", 0, 0, 255, 255, 255].label
+
+ checkbox = [1022, 508, 10, 12]
+ if (args.state.mouse_held == 1) && args.inputs.mouse.position.inside_rect?(checkbox)
+ s[:paused] = !s[:paused]
+ end
+ checkboxcolor = s[:paused] ? 255 : 0
+ args.outputs.primitives << [*checkbox, checkboxcolor, checkboxcolor, checkboxcolor, 255].solid
+ args.outputs.primitives << [910, 525, "paused:", 0, 0, 255, 255, 255].label
+
+ button = [910, 460, 320, 20]
+ if (args.state.mouse_held == 1) && args.inputs.mouse.position.inside_rect?(button)
+ args.audio.delete(args.state.selected)
+ args.state.selected = 0
+ end
+ args.outputs.primitives << [*button, 255, 0, 0, 255].solid
+ args.outputs.primitives << [button[0] + (button[2] / 2), button[1]+20, "DELETE SOURCE", 0, 1, 255, 255, 0].label
+end
+
+def spawn_new_sound args, num
+ input = nil
+ input = "sounds/#{num}.#{(num == 6) ? 'ogg' : 'wav'}"
+
+ # Spawn randomly in an area that won't be covered by UI.
+ screenx = (rand * 600.0) + 200.0
+ screeny = (rand * 400.0) + 100.0
+
+ args.state.next_sound_index += 1
+
+ # you can hang anything on the audio hashes you want, so we store the
+ # actual screen position in here for convenience.
+ args.audio[args.state.next_sound_index] = {
+ input: input,
+ screenx: screenx,
+ screeny: screeny,
+ x: ((screenx / 1279.0) * 2.0) - 1.0, # scale to -1.0 - 1.0 range
+ y: ((screeny / 719.0) * 2.0) - 1.0, # scale to -1.0 - 1.0 range
+ z: 0.0,
+ gain: 1.0,
+ pitch: 1.0,
+ looping: true,
+ paused: false
+ }
+
+ args.state.selected = args.state.next_sound_index
+end
+
+def render_launcher args
+ total = 6
+ x = (1280 - (total * $boxsize * 3)) / 2
+ y = 10
+ args.outputs.primitives << [0, 0, 1280, ((y*2) + $boxsize), 127, 127, 127, 255].solid
+ for i in 1..total
+ args.outputs.primitives << [x, y, $boxsize, $boxsize, 255, 255, 255, 255].solid
+ args.outputs.primitives << [x+8, y+28, i.to_s, 3, 0, 0, 0, 255, 255].label
+ if args.inputs.mouse.click && args.inputs.mouse.click.point.inside_rect?([x, y, $boxsize, $boxsize])
+ spawn_new_sound args, i
+ end
+ x = x + ($boxsize * 3)
+ end
+end
+
+def render_ui args
+ render_launcher args
+ render_panel args
+end
+
+def tick args
+ args.state.mouse_held ||= 0
+ args.state.dragging_source ||= false
+ args.state.selected ||= 0
+ args.state.next_sound_index ||= 0
+
+ if args.inputs.mouse.up
+ args.state.mouse_held = 0
+ args.state.dragging_source = false
+ elsif args.inputs.mouse.down || (args.state.mouse_held > 0)
+ args.state.mouse_held += 1
+ else
+ end
+
+ args.outputs.background_color = [ 0, 0, 0, 255 ]
+ render_sources args
+ render_ui args
+end
+
+</pre>
+<h1 id='--rendering-basics---sound-synthesis---main.rb'>Rendering Basics - Sound Synthesis - main.rb</h1>
+<pre># ./samples/01_rendering_basics/07_sound_synthesis/app/main.rb
+def tick args
+ defaults args
+ render args
+ input args
+ process_audio_queue args
+end
+
+def defaults args
+ args.state.sine_waves ||= {}
+ args.state.audio_queue ||= []
+ args.state.buttons ||= [
+ (frequency_buttons args),
+ (note_buttons args),
+ (bell_buttons args)
+ ].flatten
+end
+
+def frequency_buttons args
+ [
+ (button args,
+ row: 4.0, col: 0, text: "300hz",
+ frequency: 300,
+ method_to_call: :play_sine_wave),
+ (button args,
+ row: 5.0, col: 0, text: "400hz",
+ frequency: 400,
+ method_to_call: :play_sine_wave),
+ (button args,
+ row: 6.0, col: 0, text: "500hz",
+ frequency: 500,
+ method_to_call: :play_sine_wave),
+ ]
+end
+
+def play_sine_wave args, sender
+ queue_sine_wave args,
+ frequency: sender[:frequency],
+ duration: 1.seconds,
+ fade_out: true
+end
+
+
+def note_buttons args
+ [
+ (button args,
+ row: 1.5, col: 3, text: "C4",
+ note: :c, octave: 4, method_to_call: :play_note),
+ (button args,
+ row: 2.5, col: 3, text: "D4",
+ note: :d, octave: 4, method_to_call: :play_note),
+ (button args,
+ row: 3.5, col: 3, text: "E4",
+ note: :e, octave: 4, method_to_call: :play_note),
+ (button args,
+ row: 4.5, col: 3, text: "F4",
+ note: :f, octave: 4, method_to_call: :play_note),
+ (button args,
+ row: 5.5, col: 3, text: "G4",
+ note: :g, octave: 4, method_to_call: :play_note),
+ (button args,
+ row: 6.5, col: 3, text: "A5",
+ note: :a, octave: 5, method_to_call: :play_note),
+ (button args,
+ row: 7.5, col: 3, text: "B5",
+ note: :b, octave: 5, method_to_call: :play_note),
+ (button args,
+ row: 8.5, col: 3, text: "C5",
+ note: :c, octave: 5, method_to_call: :play_note),
+ ]
+end
+
+def play_note args, sender
+ queue_sine_wave args,
+ frequency: (frequency_for note: sender[:note],
+ octave: sender[:octave]),
+ duration: 1.seconds,
+ fade_out: true
+end
+
+def bell_buttons args
+ [
+ (button args,
+ row: 1.5, col: 6, text: "Bell C4",
+ note: :c, octave: 4, method_to_call: :play_bell),
+ (button args,
+ row: 2.5, col: 6, text: "Bell D4",
+ note: :d, octave: 4, method_to_call: :play_bell),
+ (button args,
+ row: 3.5, col: 6, text: "Bell E4",
+ note: :e, octave: 4, method_to_call: :play_bell),
+ (button args,
+ row: 4.5, col: 6, text: "Bell F4",
+ note: :f, octave: 4, method_to_call: :play_bell),
+ (button args,
+ row: 5.5, col: 6, text: "Bell G4",
+ note: :g, octave: 4, method_to_call: :play_bell),
+ (button args,
+ row: 6.5, col: 6, text: "Bell A5",
+ note: :a, octave: 5, method_to_call: :play_bell),
+ (button args,
+ row: 7.5, col: 6, text: "Bell B5",
+ note: :b, octave: 5, method_to_call: :play_bell),
+ (button args,
+ row: 8.5, col: 6, text: "Bell C5",
+ note: :c, octave: 5, method_to_call: :play_bell),
+ ]
+end
+
+def play_bell args, sender
+ queue_bell args,
+ frequency: (frequency_for note: sender[:note],
+ octave: sender[:octave]),
+ duration: 2.seconds,
+ fade_out: true
+end
+
+def render args
+ args.outputs.borders << args.state.buttons.map { |b| b[:border] }
+ args.outputs.labels << args.state.buttons.map { |b| b[:label] }
+ args.outputs.labels << args.layout
+ .rect(row: 0,
+ col: 11.5)
+ .yield_self { |r| r.merge y: r.y + r.h }
+ .merge(text: "This is a Pro only feature. Click here to watch the YouTube video if you are on the Standard License.",
+ alignment_enum: 1)
+end
+
+def input args
+ args.state.buttons.each do |b|
+ if args.inputs.mouse.click.inside_rect? b[:rect]
+ parameter_string = (b.slice :frequency, :note, :octave).map { |k, v| "#{k}: #{v}" }.join ", "
+ args.gtk.notify! "#{b[:method_to_call]} #{parameter_string}"
+ send b[:method_to_call], args, b
+ end
+ end
+
+ if args.inputs.mouse.click.inside_rect? (args.layout.rect(row: 0).yield_self { |r| r.merge y: r.y + r.h.half, h: r.h.half })
+ args.gtk.openurl 'https://www.youtube.com/watch?v=zEzovM5jT-k&ab_channel=AmirRajan'
+ end
+end
+
+def process_audio_queue args
+ to_queue = args.state.audio_queue.find_all { |v| v[:queue_at] <= args.tick_count }
+ args.state.audio_queue -= to_queue
+
+ to_queue.each do |a|
+ args.audio[a[:id]] = a
+ end
+
+ args.audio.each do |k, v|
+ if v[:decay_rate]
+ v[:gain] -= v[:decay_rate]
+ end
+ end
+
+ sounds_to_stop = args.audio.find_all do |k, v|
+ v[:stop_at] && args.state.tick_count >= v[:stop_at]
+ end
+
+ sounds_to_stop.each do |(k, v)|
+ args.audio.delete k
+ end
+end
+
+def graph_sine_wave args, sine_wave, frequency
+ if args.state.tick_count != args.state.graphed_at
+ args.outputs.static_lines.clear
+ args.outputs.static_sprites.clear
+ end
+
+ r, g, b = frequency.to_i % 80, frequency.to_i % 128, frequency.to_i % 255
+ center_row = args.layout.rect(row: 5, col: 9)
+ x_scale = 20
+ y_scale = 100
+ max_points = 20
+
+ points = sine_wave
+ if sine_wave.length > max_points
+ resolution = sine_wave.length.idiv max_points
+ points = sine_wave.find_all
+ .with_index { |y, i| i % resolution == 0 }
+ end
+
+ args.outputs.static_lines << points.map_with_index do |y, x|
+ next_y = points[x + 1]
+
+ if next_y
+ {
+ x: center_row.x + (x * x_scale),
+ y: center_row.y + center_row.h.half + y_scale * y,
+ x2: center_row.x + ((x + 1) * x_scale),
+ y2: center_row.y + center_row.h.half + y_scale * next_y,
+ r: r,
+ g: g,
+ b: b
+ }
+ end
+ end
+
+ args.outputs.static_sprites << points.map_with_index do |y, x|
+ {
+ x: (center_row.x + (x * x_scale)) - 1,
+ y: (center_row.y + center_row.h.half + y_scale * y) - 1,
+ w: 2,
+ h: 2,
+ path: 'sprites/square-black.png'
+ }
+ end
+
+ args.state.graphed_at = args.state.tick_count
+end
+
+def defaults_period_sine_wave_for
+ { frequency: 440, sample_rate: 48000 }
+end
+
+def sine_wave_for opts = { }
+ opts = defaults_period_sine_wave_for.merge opts
+ frequency = opts[:frequency]
+ sample_rate = opts[:sample_rate]
+ period_size = (sample_rate.fdiv frequency).ceil
+ period_size.map_with_index do |i|
+ Math::sin((2.0 * Math::PI) / (sample_rate.to_f / frequency.to_f) * i)
+ end.to_a
+end
+
+def generate_audio_data sine_wave, sample_rate
+ sample_size = (sample_rate.fdiv (1000.fdiv 60)).ceil
+ copy_count = (sample_size.fdiv sine_wave.length).ceil
+ sine_wave * copy_count
+end
+
+def defaults_queue_sine_wave
+ { frequency: 440, duration: 60, gain: 1.0, fade_out: false, queue_in: 0 }
+end
+
+def queue_sine_wave args, opts = { }
+ opts = defaults_queue_sine_wave.merge opts
+ decay_rate = 0
+ decay_rate = 1.fdiv(opts[:duration]) * opts[:gain] if opts[:fade_out]
+ frequency = opts[:frequency]
+ sample_rate = 48000
+
+ audio_state = {
+ id: (new_id! args),
+ frequency: frequency,
+ sample_rate: 48000,
+ stop_at: args.tick_count + opts[:queue_in] + opts[:duration],
+ gain: opts[:gain].to_f,
+ queue_at: args.state.tick_count + opts[:queue_in],
+ decay_rate: decay_rate,
+ pitch: 1.0,
+ looping: true,
+ paused: false
+ }
+
+ sine_wave = sine_wave_for frequency: frequency, sample_rate: sample_rate
+ args.state.sine_waves[frequency] ||= sine_wave_for frequency: frequency, sample_rate: sample_rate
+
+ proc = lambda do
+ generate_audio_data args.state.sine_waves[frequency], sample_rate
+ end
+
+ audio_state[:input] = [1, sample_rate, proc]
+ graph_sine_wave args, sine_wave, frequency
+ args.state.audio_queue << audio_state
+end
+
+def defaults_queue_bell
+ { frequency: 440, duration: 1.seconds, queue_in: 0 }
+end
+
+def queue_bell args, opts = {}
+ (bell_to_sine_waves (defaults_queue_bell.merge opts)).each { |b| queue_sine_wave args, b }
+end
+
+def bell_harmonics
+ [
+ { frequency_ratio: 0.5, duration_ratio: 1.00 },
+ { frequency_ratio: 1.0, duration_ratio: 0.80 },
+ { frequency_ratio: 2.0, duration_ratio: 0.60 },
+ { frequency_ratio: 3.0, duration_ratio: 0.40 },
+ { frequency_ratio: 4.2, duration_ratio: 0.25 },
+ { frequency_ratio: 5.4, duration_ratio: 0.20 },
+ { frequency_ratio: 6.8, duration_ratio: 0.15 }
+ ]
+end
+
+def bell_to_sine_waves opts
+ bell_harmonics.map do |b|
+ {
+ frequency: opts[:frequency] * b[:frequency_ratio],
+ duration: opts[:duration] * b[:duration_ratio],
+ queue_in: opts[:queue_in],
+ gain: (1.fdiv bell_harmonics.length),
+ fade_out: true
+ }
+ end
+end
+
+def defaults_frequency_for
+ { note: :a, octave: 5, sharp: false, flat: false }
+end
+
+def frequency_for opts = {}
+ opts = defaults_frequency_for.merge opts
+ octave_offset_multiplier = opts[:octave] - 5
+ note = note_frequencies_octave_5[opts[:note]]
+ if octave_offset_multiplier < 0
+ note = note * 1 / (octave_offset_multiplier.abs + 1)
+ elsif octave_offset_multiplier > 0
+ note = note * (octave_offset_multiplier.abs + 1) / 1
+ end
+ note
+end
+
+def note_frequencies_octave_5
+ {
+ a: 440.0,
+ a_sharp: 466.16, b_flat: 466.16,
+ b: 493.88,
+ c: 523.25,
+ c_sharp: 554.37, d_flat: 587.33,
+ d: 587.33,
+ d_sharp: 622.25, e_flat: 659.25,
+ e: 659.25,
+ f: 698.25,
+ f_sharp: 739.99, g_flat: 739.99,
+ g: 783.99,
+ g_sharp: 830.61, a_flat: 830.61
+ }
+end
+
+def new_id! args
+ args.state.audio_id ||= 0
+ args.state.audio_id += 1
+end
+
+def button args, opts
+ button_def = opts.merge rect: (args.layout.rect (opts.merge w: 2, h: 1))
+
+ button_def[:border] = button_def[:rect].merge r: 0, g: 0, b: 0
+
+ font_size_enum = args.layout.font_relative_size_enum 0
+ label_offset_x = 4
+ label_offset_y = button_def[:rect].h.half + button_def[:rect].h.idiv(4)
+
+ button_def[:label] = button_def[:rect].merge text: opts[:text],
+ size_enum: font_size_enum,
+ x: button_def[:rect].x + label_offset_x,
+ y: button_def[:rect].y + label_offset_y
+
+ button_def
+end
+
+$gtk.reset
+
+</pre>
<h1 id='--input-basics---keyboard---main.rb'>Input Basics - Keyboard - main.rb</h1>
<pre># ./samples/02_input_basics/01_keyboard/app/main.rb
=begin
@@ -3888,6 +4436,52 @@ def tick_instructions args, text, y = 715
end
</pre>
+<h1 id='--input-basics---touch---main.rb'>Input Basics - Touch - main.rb</h1>
+<pre># ./samples/02_input_basics/06_touch/app/main.rb
+def tick args
+ args.outputs.background_color = [ 0, 0, 0 ]
+ args.outputs.primitives << [640, 700, "Touch your screen.", 5, 1, 255, 255, 255].label
+
+ # If you don't want to get fancy, you can just look for finger_one
+ # (and _two, if you like), which are assigned in the order new touches hit
+ # the screen. If not nil, they are touching right now, and are just
+ # references to specific items in the args.input.touch hash.
+ # If finger_one lifts off, it will become nil, but finger_two, if it was
+ # touching, remains until it also lifts off. When all fingers lift off, the
+ # the next new touch will be finger_one again, but until then, new touches
+ # don't fill in earlier slots.
+ if !args.inputs.finger_one.nil?
+ args.outputs.primitives << [640, 650, "Finger #1 is touching at (#{args.inputs.finger_one.x}, #{args.inputs.finger_one.y}).", 5, 1, 255, 255, 255].label
+ end
+ if !args.inputs.finger_two.nil?
+ args.outputs.primitives << [640, 600, "Finger #2 is touching at (#{args.inputs.finger_two.x}, #{args.inputs.finger_two.y}).", 5, 1, 255, 255, 255].label
+ end
+
+ # Here's the more flexible interface: this will report as many simultaneous
+ # touches as the system can handle, but it's a little more effort to track
+ # them. Each item in the args.input.touch hash has a unique key (an
+ # incrementing integer) that exists until the finger lifts off. You can
+ # tell which order the touches happened globally by the key value, or
+ # by the touch[id].touch_order field, which resets to zero each time all
+ # touches have lifted.
+
+ args.state.colors ||= [
+ 0xFF0000, 0x00FF00, 0x1010FF, 0xFFFF00, 0xFF00FF, 0x00FFFF, 0xFFFFFF
+ ]
+
+ size = 100
+ args.inputs.touch.each { |k,v|
+ color = args.state.colors[v.touch_order % 7]
+ r = (color & 0xFF0000) >> 16
+ g = (color & 0x00FF00) >> 8
+ b = (color & 0x0000FF)
+ args.outputs.primitives << [v.x - (size / 2), v.y + (size / 2), size, size, r, g, b, 255].solid
+ args.outputs.primitives << [v.x, v.y + size, k.to_s, 0, 1, 0, 0, 0].label
+ }
+end
+
+
+</pre>
<h1 id='--rendering-sprites---animation-using-separate-pngs---main.rb'>Rendering Sprites - Animation Using Separate Pngs - main.rb</h1>
<pre># ./samples/03_rendering_sprites/01_animation_using_separate_pngs/app/main.rb
=begin
@@ -8395,6 +8989,51 @@ def tick args
end
</pre>
+<h1 id='--advanced-rendering---pixel-arrays---main.rb'>Advanced Rendering - Pixel Arrays - main.rb</h1>
+<pre># ./samples/07_advanced_rendering/06_pixel_arrays/app/main.rb
+$gtk.reset
+
+def tick args
+ args.state.posinc ||= 1
+ args.state.pos ||= 0
+ args.state.rotation ||= 0
+
+ dimension = 10 # keep it small and let the GPU scale it when rendering the sprite.
+
+ # Set up our "scanner" pixel array and fill it with black pixels.
+ args.pixel_array(:scanner).width = dimension
+ args.pixel_array(:scanner).height = dimension
+ args.pixel_array(:scanner).pixels.fill(0xFF000000, 0, dimension * dimension) # black, full alpha
+
+ # Draw a green line that bounces up and down the sprite.
+ args.pixel_array(:scanner).pixels.fill(0xFF00FF00, dimension * args.state.pos, dimension) # green, full alpha
+
+ # Adjust position for next frame.
+ args.state.pos += args.state.posinc
+ if args.state.posinc > 0 && args.state.pos >= dimension
+ args.state.posinc = -1
+ args.state.pos = dimension - 1
+ elsif args.state.posinc < 0 && args.state.pos < 0
+ args.state.posinc = 1
+ args.state.pos = 1
+ end
+
+ # New/changed pixel arrays get uploaded to the GPU before we render
+ # anything. At that point, they can be scaled, rotated, and otherwise
+ # used like any other sprite.
+ w = 100
+ h = 100
+ x = (1280 - w) / 2
+ y = (720 - h) / 2
+ args.outputs.background_color = [64, 0, 128]
+ args.outputs.primitives << [x, y, w, h, :scanner, args.state.rotation].sprite
+ args.state.rotation += 1
+
+ args.outputs.primitives << args.gtk.current_framerate_primitives
+end
+
+
+</pre>
<h1 id='--tweening-lerping-easing-functions---easing-functions---main.rb'>Tweening Lerping Easing Functions - Easing Functions - main.rb</h1>
<pre># ./samples/08_tweening_lerping_easing_functions/01_easing_functions/app/main.rb
def tick args
@@ -9609,12 +10248,12 @@ def test_serialize args, assert
GTK::Entity.__reset_id__!
args.state.player_one = "test"
result = args.gtk.serialize_state args.state
- assert.equal! result, "{:entity_id=>3, :tick_count=>-1, :player_one=>\"test\"}"
+ assert.equal! result, "{:entity_id=>3, :entity_keys_by_ref=>{}, :tick_count=>-1, :player_one=>\"test\"}"
GTK::Entity.__reset_id__!
args.gtk.write_file 'state.txt', ''
result = args.gtk.serialize_state 'state.txt', args.state
- assert.equal! result, "{:entity_id=>3, :tick_count=>-1, :player_one=>\"test\"}"
+ assert.equal! result, "{:entity_id=>3, :entity_keys_by_ref=>{}, :tick_count=>-1, :player_one=>\"test\"}"
end
def test_deserialize args, assert
@@ -9645,8 +10284,9 @@ def test_strict_entity_serialization args, assert
args.state.player_two = args.state.new_entity_strict(:player_strict, name: "Ken")
serialized_state = args.gtk.serialize_state args.state
- assert.equal! serialized_state, '{:entity_id=>1, :tick_count=>-1, :player_one=>{:entity_id=>1, :entity_name=>:player, :entity_type=>:player, :created_at=>-1, :global_created_at=>-1, :name=>"Ryu"}, :player_two=>{:entity_id=>3, :entity_name=>:player_strict, :created_at=>-1, :global_created_at_elapsed=>-1, :entity_strict=>true, :name=>"Ken"}}'
+ assert.equal! serialized_state, '{:entity_id=>1, :entity_keys_by_ref=>{}, :tick_count=>-1, :player_one=>{:entity_id=>1, :entity_name=>:player, :entity_keys_by_ref=>{}, :entity_type=>:player, :created_at=>-1, :global_created_at=>-1, :name=>"Ryu"}, :player_two=>{:entity_id=>3, :entity_name=>:player_strict, :entity_type=>:player_strict, :created_at=>-1, :global_created_at_elapsed=>-1, :entity_strict=>true, :entity_keys_by_ref=>{:entity_type=>:entity_name, :global_created_at_elapsed=>:created_at}, :name=>"Ken"}}'
+ GTK::Entity.__reset_id__!
deserialize_state = args.gtk.deserialize_state serialized_state
assert.equal! args.state.player_one.name, deserialize_state.player_one.name
@@ -9662,8 +10302,9 @@ def test_strict_entity_serialization_with_nil args, assert
args.state.player_two = args.state.new_entity_strict(:player_strict, name: "Ken", blood_type: nil)
serialized_state = args.gtk.serialize_state args.state
- assert.equal! serialized_state, '{:entity_id=>3, :tick_count=>-1, :player_one=>{:entity_id=>1, :entity_name=>:player, :entity_type=>:player, :created_at=>-1, :global_created_at=>-1, :name=>"Ryu"}, :player_two=>{:entity_id=>3, :entity_name=>:player_strict, :created_at=>-1, :global_created_at_elapsed=>-1, :entity_strict=>true, :name=>"Ken", :blood_type=>nil}}'
+ assert.equal! serialized_state, '{:entity_id=>9, :entity_keys_by_ref=>{}, :tick_count=>-1, :player_one=>{:entity_id=>1, :entity_name=>:player, :entity_keys_by_ref=>{}, :entity_type=>:player, :created_at=>-1, :global_created_at=>-1, :name=>"Ryu"}, :player_two=>{:entity_id=>2, :entity_name=>:player_strict, :entity_type=>:player_strict, :created_at=>-1, :global_created_at_elapsed=>-1, :entity_strict=>true, :entity_keys_by_ref=>{:entity_type=>:entity_name, :global_created_at_elapsed=>:created_at}, :name=>"Ken", :blood_type=>nil}}'
+ GTK::Entity.__reset_id__!
deserialized_state = args.gtk.deserialize_state serialized_state
assert.equal! args.state.player_one.name, deserialized_state.player_one.name
@@ -9684,12 +10325,41 @@ def test_multiple_strict_entities args, assert
args.state.enemy = args.state.new_entity_strict(:enemy, name: "Bison", other_property: 'extra mean')
serialized_state = args.gtk.serialize_state args.state
+
+ GTK::Entity.__reset_id__!
deserialized_state = args.gtk.deserialize_state serialized_state
assert.equal! deserialized_state.player.name, "Ryu"
assert.equal! deserialized_state.enemy.other_property, "extra mean"
end
+def test_by_reference_state args, assert
+ GTK::Entity.__reset_id__!
+ args.state.a = { name: "Jane Doe" }
+ args.state.b = args.state.a
+ assert.equal! args.state.a.object_id, args.state.b.object_id
+ serialized_state = args.gtk.serialize_state args.state
+
+ GTK::Entity.__reset_id__!
+ deserialized_state = args.gtk.deserialize_state serialized_state
+ assert.equal! deserialized_state.a.object_id, deserialized_state.b.object_id
+end
+
+def test_by_reference_state_strict_entities args, assert
+ GTK::Entity.__reset_id__!
+ args.state.a = { name: "Jane Doe" }
+ args.state.strict_entity = args.state.new_entity_strict(:couple) do |e|
+ e.one = args.state.new_entity_strict(:person, name: "Jane")
+ e.two = e.one
+ end
+ assert.equal! args.state.strict_entity.one, args.state.strict_entity.two
+ serialized_state = args.gtk.serialize_state args.state
+
+ GTK::Entity.__reset_id__!
+ deserialized_state = args.gtk.deserialize_state serialized_state
+ assert.equal! deserialized_state.strict_entity.one, deserialized_state.strict_entity.two
+end
+
$tests.start
</pre>
@@ -9875,6 +10545,55 @@ end
</pre>
+<h1 id='--12-c-extensions---intermediate---main.rb'>12 C Extensions - Intermediate - main.rb</h1>
+<pre># ./samples/12_c_extensions/02_intermediate/app/main.rb
+$gtk.ffi_misc.gtk_dlopen("ext")
+include FFI::RE
+
+def split_words(input)
+ words = []
+ last = IntPointer.new
+ re = re_compile("\\w+")
+ first = re_matchp(re, input, last)
+ while first != -1
+ words << input.slice(first, last.value)
+ input = input.slice(last.value + first, input.length)
+ first = re_matchp(re, input, last)
+ end
+ words
+end
+
+def tick args
+ args.outputs.labels << [640, 500, split_words("hello, dragonriders!").join(' '), 5, 1]
+end
+
+</pre>
+<h1 id='--12-c-extensions---native-pixel-arrays---main.rb'>12 C Extensions - Native Pixel Arrays - main.rb</h1>
+<pre># ./samples/12_c_extensions/03_native_pixel_arrays/app/main.rb
+$gtk.ffi_misc.gtk_dlopen("ext")
+include FFI::CExt
+
+def tick args
+ args.state.rotation ||= 0
+
+ update_scanner_texture # this calls into a C extension!
+
+ # New/changed pixel arrays get uploaded to the GPU before we render
+ # anything. At that point, they can be scaled, rotated, and otherwise
+ # used like any other sprite.
+ w = 100
+ h = 100
+ x = (1280 - w) / 2
+ y = (720 - h) / 2
+ args.outputs.background_color = [64, 0, 128]
+ args.outputs.primitives << [x, y, w, h, :scanner, args.state.rotation].sprite
+ args.state.rotation += 1
+
+ args.outputs.primitives << args.gtk.current_framerate_primitives
+end
+
+
+</pre>
<h1 id='--3d---3d-cube---main.rb'>3d - 3d Cube - main.rb</h1>
<pre># ./samples/99_genre_3d/3d_cube/app/main.rb
STARTX = 0.0
@@ -15520,27 +16239,6 @@ def tick args
end
</pre>
-<h1 id='--platformer---gorillas-basic---repl.rb'>Platformer - Gorillas Basic - repl.rb</h1>
-<pre># ./samples/99_genre_platformer/gorillas_basic/app/repl.rb
-begin
- if $gtk.args.state.current_turn == :player_1_angle
- $gtk.args.state.player_1_angle = "#{60 + 10.randomize(:ratio).to_i}"
- $you_so_basic_gorillas.input_execute_turn
- $gtk.args.state.player_1_velocity = "#{30 + 20.randomize(:ratio).to_i}"
- $you_so_basic_gorillas.input_execute_turn
- elsif $gtk.args.state.current_turn == :player_2_angle
- $gtk.args.state.player_2_angle = "#{60 + 10.randomize(:ratio).to_i}"
- $you_so_basic_gorillas.input_execute_turn
- $gtk.args.state.player_2_velocity = "#{30 + 20.randomize(:ratio).to_i}"
- $you_so_basic_gorillas.input_execute_turn
- else
- $you_so_basic_gorillas.input_execute_turn
- end
-rescue Exception => e
- puts e
-end
-
-</pre>
<h1 id='--platformer---gorillas-basic---tests.rb'>Platformer - Gorillas Basic - tests.rb</h1>
<pre># ./samples/99_genre_platformer/gorillas_basic/app/tests.rb
$gtk.reset 100
@@ -18930,11 +19628,6 @@ def player_xs args, x, y
end
</pre>
-<h1 id='--rpg-narrative---return-of-serenity---repl.rb'>Rpg Narrative - Return Of Serenity - repl.rb</h1>
-<pre># ./samples/99_genre_rpg_narrative/return_of_serenity/app/repl.rb
-puts $gtk.args.state.current_scene
-
-</pre>
<h1 id='--rpg-narrative---return-of-serenity---require.rb'>Rpg Narrative - Return Of Serenity - require.rb</h1>
<pre># ./samples/99_genre_rpg_narrative/return_of_serenity/app/require.rb
require 'app/lowrez_simulator.rb'
@@ -21693,6 +22386,11 @@ module GTK
# @return [Outputs]
attr_accessor :outputs
+ # Contains the means to interact with the audio mixer.
+ #
+ # @return [Hash]
+ attr_accessor :audio
+
# Contains display size information to assist in positioning things on the screen.
#
# @return [Grid]
@@ -21723,9 +22421,12 @@ module GTK
attr_accessor :wizards
+ attr_accessor :layout
+
def initialize runtime, recording
@inputs = Inputs.new
@outputs = Outputs.new args: self
+ @audio = {}
@passes = []
@state = OpenEntity.new
@state.tick_count = -1
@@ -21733,9 +22434,11 @@ module GTK
@recording = recording
@grid = Grid.new runtime
@render_targets = {}
+ @pixel_arrays = {}
@all_tests = []
@geometry = GTK::Geometry
@wizards = Wizards.new
+ @layout = GTK::Layout.new @grid.w, @grid.h
end
@@ -21764,6 +22467,26 @@ module GTK
[grid, inputs, state, outputs, runtime, passes]
end
+ def clear_pixel_arrays
+ pixel_arrays_clear
+ end
+
+ def pixel_arrays_clear
+ @pixel_arrays = {}
+ end
+
+ def pixel_arrays
+ @pixel_arrays
+ end
+
+ def pixel_array name
+ name = name.to_s
+ if !@pixel_arrays[name]
+ @pixel_arrays[name] = PixelArray.new
+ end
+ @pixel_arrays[name]
+ end
+
def clear_render_targets
render_targets_clear
end
@@ -21970,7 +22693,7 @@ end
@assertion_performed = true
if actual != expected
actual_string = "#{actual}#{actual.nil? ? " (nil) " : " " }".strip
- message = "actual:\n#{actual_string} did not equal\nexpected:\n#{expected}.\n#{message}"
+ message = "actual:\n#{actual_string}\n\ndid not equal\n\nexpected:\n#{expected}.\n#{message}"
raise message
end
nil
@@ -22039,6 +22762,10 @@ module AttrGTK
args.passes
end
+ def pixel_arrays
+ args.pixel_arrays
+ end
+
def geometry
args.geometry
end
@@ -22116,6 +22843,8 @@ end
module GTK
class Console
+ include ConsoleDeprecated
+
attr_accessor :show_reason, :log, :logo, :background_color,
:text_color, :animation_duration,
:max_log_lines, :max_history, :log,
@@ -22186,7 +22915,7 @@ module GTK
@disabled = false
end
- def addsprite obj
+ def add_sprite obj
@log_invocation_count += 1
obj[:id] ||= "id_#{obj[:path]}_#{Time.now.to_i}".to_sym
@@ -22206,14 +22935,14 @@ module GTK
def add_primitive obj
if obj.is_a? Hash
- addsprite obj
+ add_sprite obj
else
- addtext obj
+ add_text obj
end
nil
end
- def addtext obj
+ def add_text obj
@last_log_lines_count ||= 1
@log_invocation_count += 1
@@ -22337,12 +23066,12 @@ S
@toasted_at = Kernel.global_tick_count
log_once_info :perma_toast_tip, "Use console.perma_toast to show the toast for longer."
dwim_duration = 5.seconds
- addtext "* toast :#{id}"
+ add_text "* toast :#{id}"
puts "* TOAST: :#{id}"
messages.each do |message|
lines = message.to_s.wrapped_lines(self.console_text_width)
dwim_duration += lines.length.seconds
- addtext "** #{message}"
+ add_text "** #{message}"
puts "** #{message}"
end
show :toast
@@ -22560,6 +23289,10 @@ S
@command_history_index -= 1
self.current_input_str = @command_history[@command_history_index].dup
end
+ elsif args.inputs.keyboard.key_down.left
+ prompt.move_cursor_left
+ elsif args.inputs.keyboard.key_down.right
+ prompt.move_cursor_right
elsif inputs_scroll_up_full? args
scroll_up_full
elsif inputs_scroll_down_full? args
@@ -23119,23 +23852,53 @@ module GTK
@cursor_color = Color.new [187, 21, 6]
@console_text_width = console_text_width
+ @cursor_position = 0
+ update_cursor_position_px
+
@last_autocomplete_prefix = nil
@next_candidate_index = 0
end
+ def update_cursor_position_px
+ @cursor_position_px = ($gtk.calcstringbox (@prompt + @current_input_str[0...@cursor_position]), @font_style.size_enum, @font_style.font).x
+ end
+
+ def current_input_str=(str)
+ @current_input_str = str
+ @cursor_position = str.length
+ end
+
def <<(str)
- @current_input_str << str
+ @current_input_str = @current_input_str[0...@cursor_position] + str + @current_input_str[@cursor_position..-1]
+ @cursor_position += str.length
+ update_cursor_position_px
@current_input_changed_at = Kernel.global_tick_count
reset_autocomplete
end
def backspace
- @current_input_str.chop!
+ return if current_input_str.length.zero? || @cursor_position.zero?
+
+ @current_input_str = @current_input_str[0...(@cursor_position - 1)] + @current_input_str[@cursor_position..-1]
+ @cursor_position -= 1
+ update_cursor_position_px
reset_autocomplete
end
+ def move_cursor_left
+ @cursor_position -= 1 if @cursor_position > 0
+ update_cursor_position_px
+ end
+
+ def move_cursor_right
+ @cursor_position += 1 if @cursor_position < current_input_str.length
+ update_cursor_position_px
+ end
+
def clear
@current_input_str = ''
+ @cursor_position = 0
+ update_cursor_position_px
reset_autocomplete
end
@@ -23154,7 +23917,10 @@ module GTK
@next_candidate_index += 1
@next_candidate_index = 0 if @next_candidate_index >= candidates.length
self.current_input_str = display_autocomplete_candidate(candidate)
+ update_cursor_position_px
end
+ rescue Exception => e
+ puts "* BUG: Tab autocompletion failed. Let us know about this.\n#{e}"
end
def pretty_print_strings_as_table items
@@ -23213,7 +23979,21 @@ S
def render(args, x:, y:)
args.outputs.reserved << font_style.label(x: x, y: y, text: "#{@prompt}#{current_input_str}", color: @text_color)
- args.outputs.reserved << font_style.label(x: x - 2, y: y + 3, text: (" " * (@prompt.length + current_input_str.length)) + "|", color: @cursor_color)
+ args.outputs.reserved << (@cursor_color.to_h.merge x: x + @cursor_position_px + 0.5,
+ y: y + 5,
+ x2: x + @cursor_position_px + 0.5,
+ y2: y + @font_style.letter_size.y + 5)
+
+ args.outputs.reserved << (@cursor_color.to_h.merge x: x + @cursor_position_px + 1,
+ y: y + 5,
+ x2: x + @cursor_position_px + 1,
+ y2: y + @font_style.letter_size.y + 5)
+
+ # debugging rectangle for string
+ # args.outputs.reserved << (@cursor_color.to_h.merge x: x,
+ # y: y + 5,
+ # w: @cursor_position_px,
+ # h: @font_style.letter_size.y).border
end
def tick
@@ -25160,6 +25940,59 @@ module GTK
alias_method :inspect, :to_s
end
+
+ # Provides access to multitouch input
+ #
+ # @gtk
+ class FingerTouch
+
+ # @gtk
+ attr_accessor :moved,
+ :moved_at,
+ :global_moved_at,
+ :touch_order,
+ :x, :y
+
+ def initialize
+ @moved = false
+ @moved_at = 0
+ @global_moved_at = 0
+ @touch_order = 0
+ @x = 0
+ @y = 0
+ end
+
+ def point
+ [@x, @y].point
+ end
+
+ def inside_rect? rect
+ point.inside_rect? rect
+ end
+
+ def inside_circle? center, radius
+ point.point_inside_circle? center, radius
+ end
+
+ alias_method :position, :point
+
+ def serialize
+ result = {}
+ result[:x] = @x
+ result[:y] = @y
+ result[:touch_order] = @touch_order
+ result[:moved] = @moved
+ result[:moved_at] = @moved_at
+
+ result
+ end
+
+ def to_s
+ serialize.to_s
+ end
+
+ alias_method :inspect, :to_s
+ end
end
module GTK
@@ -25180,6 +26013,11 @@ module GTK
# @gtk
attr_reader :mouse
+ # @return {FingerTouch}
+ # @gtk
+ attr_reader :touch
+ attr_accessor :finger_one, :finger_two
+
# @gtk
attr_accessor :text, :history
@@ -25187,6 +26025,9 @@ module GTK
@controllers = [Controller.new, Controller.new]
@keyboard = Keyboard.new
@mouse = Mouse.new
+ @touch = {}
+ @finger_one = nil
+ @finger_two = nil
@text = []
end
@@ -25262,6 +26103,9 @@ module GTK
@mouse.clear
@keyboard.clear
@controllers.each(&:clear)
+ @touch.clear
+ @finger_one = nil
+ @finger_two = nil
end
# @return [Hash]
@@ -25278,6 +26122,1382 @@ module GTK
end
</pre>
+<h1 id='--ios_wizard.rb'>ios_wizard.rb</h1>
+<pre># ./dragon/ios_wizard.rb
+# Copyright 2019 DragonRuby LLC
+# MIT License
+# ios_wizard.rb has been released under MIT (*only this file*).
+
+class WizardException < Exception
+ attr_accessor :console_primitives
+
+ def initialize *console_primitives
+ @console_primitives = console_primitives
+ end
+end
+
+class IOSWizard
+ def initialize
+ @doctor_executed_at = 0
+ end
+
+ def relative_path
+ (File.dirname $gtk.binary_path)
+ end
+
+ def steps
+ [
+ :check_for_xcode,
+ :check_for_brew,
+ :check_for_certs,
+ :check_for_device,
+ :check_for_dev_profile,
+ :determine_app_name,
+ :determine_app_id,
+ :blow_away_temp,
+ :stage_app,
+ :write_info_plist,
+ :write_entitlements_plist,
+ :code_sign,
+ :create_ipa,
+ :deploy,
+ ]
+ end
+
+ def get_reserved_sprite png
+ sprite_path = ".dragonruby/sprites/wizards/ios/#{png}"
+
+ if !$gtk.ivar :rcb_release_mode
+ sprite_path = "deploy_template/#{sprite_path}"
+ $gtk.reset_sprite sprite_path
+ end
+
+ if !$gtk.read_file sprite_path
+ log_error "png #{png} not found."
+ end
+
+ sprite_path
+ end
+
+ def start
+ @certificate_name = nil
+ init_wizard_status
+ log_info "Starting iOS Wizard so we can deploy to your device."
+ @start_at = Kernel.global_tick_count
+ steps.each do |m|
+ begin
+ result = (send m) || :success if @wizard_status[m][:result] != :success
+ @wizard_status[m][:result] = result
+ rescue Exception => e
+ if e.is_a? WizardException
+ $console.log.clear
+ $console.archived_log.clear
+ log "=" * $console.console_text_width
+ e.console_primitives.each do |p|
+ $console.add_primitive p
+ end
+ log "=" * $console.console_text_width
+ else
+ log_error "Step #{m} failed."
+ log_error e.to_s
+ end
+
+ $console.set_command "$wizards.ios.start"
+
+ break
+ end
+ end
+
+ return nil
+ end
+
+ def always_fail
+ return false if $gtk.ivar :rcb_release_mode
+ return true
+ end
+
+ def check_for_xcode
+ if !cli_app_exist?(xcodebuild_cli_app)
+ raise WizardException.new(
+ "* You need Xcode to use $wizards.ios.start.",
+ { w: 75, h: 75, path: get_reserved_sprite("xcode.png") },
+ "** 1. Go to http://developer.apple.com and register.",
+ "** 2. Download Xcode 11.3+ from http://developer.apple.com/downloads.",
+ " NOTE: DO NOT install Xcode from the App Store. Use the link above.",
+ { w: 700, h: 359, path: get_reserved_sprite("xcode-downloads.png") },
+ "** 3. After installing. Open up Xcode to accept the EULA."
+ )
+ end
+ end
+
+ def check_for_brew
+ if !cli_app_exist?('brew')
+ raise WizardException.new(
+ "* You need to install Brew.",
+ { w: 700, h: 388, path: get_reserved_sprite("brew.png") },
+ "** 1. Go to http://brew.sh.",
+ "** 2. Copy the command that starts with `/bin/bash -c` on the site.",
+ "** 3. Open Terminal and run the command you copied from the website.",
+ { w: 700, h: 99, path: get_reserved_sprite("terminal.png") },
+ )
+ end
+ end
+
+ def init_wizard_status
+ @wizard_status = {}
+ steps.each do |m|
+ @wizard_status[m] = { result: :not_started }
+ end
+
+ previous_step = nil
+ next_step = nil
+ steps.each_cons(2) do |current_step, next_step|
+ @wizard_status[current_step][:next_step] = next_step
+ end
+
+ steps.reverse.each_cons(2) do |current_step, previous_step|
+ @wizard_status[current_step][:previous_step] = previous_step
+ end
+ end
+
+ def restart
+ init_wizard_status
+ start
+ end
+
+ def check_for_dev_profile
+ if !($gtk.read_file 'profiles/development.mobileprovision')
+ $gtk.system "mkdir -p #{relative_path}/profiles"
+ $gtk.system "open #{relative_path}/profiles"
+ $gtk.system "echo Download the mobile provisioning profile and place it here with the name development.mobileprovision > #{relative_path}/profiles/README.txt"
+ raise WizardException.new(
+ "* I didn't find a mobile provision.",
+ "** 1. Go to http://developer.apple.com and click \"Certificates, IDs & Profiles\".",
+ "** 2. Add an App Identifier.",
+ "** 3. Select the App IDs option from the list.",
+ { w: 700, h: 75, path: get_reserved_sprite("identifiers.png") },
+ "** 4. Add your Device next. You can use idevice_id -l to get the UUID of your device.",
+ { w: 365, h: 69, path: get_reserved_sprite("device-link.png") },
+ "** 5. Create a Profile. Associate your certs, id, and device.",
+ { w: 300, h: 122, path: get_reserved_sprite("profiles.png") },
+ "** 6. Download the mobile provision and save it to 'profiles/development.mobileprovision'.",
+ { w: 200, h: 124, path: get_reserved_sprite("profiles-folder.png") },
+ )
+ end
+ end
+
+ def determine_app_name
+ @app_name = dev_profile_xml[:children].first[:children].first[:children][1][:children].first[:data]
+ log_info "App name is: #{@app_name}."
+ end
+
+ def dev_profile_xml
+ xml = $gtk.read_file 'profiles/development.mobileprovision'
+ scrubbed = xml.each_line.map do |l|
+ if l.strip.start_with? "<"
+ if l.start_with? '</plist>'
+ '</plist>'
+ elsif l.include? "Apple Inc."
+ nil
+ elsif l.include? '<data>'
+ nil
+ else
+ l
+ end
+ else
+ nil
+ end
+ end.reject { |l| !l }.join
+ $gtk.parse_xml scrubbed
+ end
+
+ def determine_app_id
+ # lol
+ @app_id = dev_profile_xml[:children].first[:children].first[:children][13][:children][1][:children].first[:data]
+ log_info "App Identifier is set to : #{@app_id}"
+ end
+
+ def set_app_name name
+ @app_name = name
+ start
+ end
+
+ def set_dev_profile path
+ if !$gtk.read_file path
+ log_error "I couldn't find a development profile at #{path}."
+ ask_for_dev_profile
+ else
+ @dev_profile_path = path
+ start
+ end
+ end
+
+ def blow_away_temp
+ sh "rm -rf #{tmp_directory}"
+ end
+
+ def stage_app
+ log_info "Staging."
+ sh "mkdir -p #{tmp_directory}"
+ sh "cp -R #{relative_path}/dragonruby-ios.app \"#{tmp_directory}/#{@app_name}.app\""
+ end
+
+ def set_app_id id
+ log_info = "App Id set to: #{id}"
+ @app_id = id
+ start
+ end
+
+ def check_for_device
+ log_info "Looking for device."
+
+ if !cli_app_exist?(idevice_id_cli_app)
+ raise WizardException.new(
+ "* It doesn't look like you have the libimobiledevice iOS protocol library installed.",
+ "** 1. Open Terminal.",
+ { w: 700, h: 99, path: get_reserved_sprite("terminal.png") },
+ "** 2. Run: `brew install libimobiledevice`.",
+ { w: 500, h: 93, path: get_reserved_sprite("brew-install-libimobiledevice.png") },
+ )
+ end
+
+ if connected_devices.length == 0
+ raise WizardException.new("* I couldn't find any connected devices. Connect your iOS device to your Mac and try again.")
+ end
+
+ @device_id = connected_devices.first
+ log_info "I will be using device with UUID #{@device_id}"
+ end
+
+ def check_for_certs
+ log_info "Attempting to find certificates on your computer."
+
+ if !cli_app_exist?(security_cli_app)
+ raise WizardException.new(
+ "* It doesn't look like you have #{security_cli_app}.",
+ "** 1. Open Disk Utility and run First Aid.",
+ { w: 700, h: 148, path: get_reserved_sprite("disk-utility.png") },
+ )
+ end
+
+ if valid_certs.length == 0
+ raise WizardException.new(
+ "* It doesn't look like you have any valid certs installed.",
+ "** 1. Open Xcode.",
+ "** 2. Log into your developer account. Xcode -> Preferences -> Accounts.",
+ { w: 700, h: 98, path: get_reserved_sprite("login-xcode.png") },
+ "** 3. After loggin in, select Manage Certificates...",
+ { w: 700, h: 115, path: get_reserved_sprite("manage-certificates.png") },
+ "** 4. Add a certificate for Apple Development.",
+ { w: 700, h: 217, path: get_reserved_sprite("add-cert.png") },
+ )
+ raise "You do not have any Apple development certs on this computer."
+ end
+
+ @certificate_name = valid_certs.first[:name]
+ log_info "I will be using '#{@certificate_name}' to deploy to your device."
+ end
+
+ def idevice_id_cli_app
+ "idevice_id"
+ end
+
+ def security_cli_app
+ "/usr/bin/security"
+ end
+
+ def xcodebuild_cli_app
+ "xcodebuild"
+ end
+
+ def valid_certs
+ certs = sh("#{security_cli_app} -q find-identity -p codesigning -v").each_line.map do |l|
+ if l.include?(")") && !l.include?("Developer ID") && l.include?("Development")
+ l.strip
+ else
+ nil
+ end
+ end.reject_nil.map do |l|
+ number, id, name = l.split(' ', 3)
+ name = name.gsub("\"", "") if name
+ {
+ number: 1,
+ id: id,
+ name: name
+ }
+ end
+ end
+
+ def connected_devices
+ sh("idevice_id -l").strip.each_line.map do |l|
+ l.strip
+ end.reject { |l| l.length == 0 }
+ end
+
+ def cli_app_exist? app
+ `which #{app}`.strip.length != 0
+ end
+
+ def write_entitlements_plist
+ entitlement_plist_string = <<-XML
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+ <dict>
+ <key>application-identifier</key>
+ <string>:app_id</string>
+ <key>get-task-allow</key>
+ <true/>
+ </dict>
+</plist>
+XML
+
+ log_info "Creating Entitlements.plist"
+
+ $gtk.write_file_root "tmp/ios/Entitlements.plist", entitlement_plist_string.gsub(":app_id", @app_id).strip
+
+ sh "/usr/bin/plutil -convert binary1 \"#{tmp_directory}/Entitlements.plist\""
+ sh "/usr/bin/plutil -convert xml1 \"#{tmp_directory}/Entitlements.plist\""
+
+ @entitlement_plist_written = true
+ end
+
+ def code_sign
+ sh "cp #{@dev_profile_path} \"#{app_path}/embedded.mobileprovision\""
+
+ log_info "Signing app with #{@certificate_name}."
+
+ sh "/usr/bin/plutil -convert binary1 \"#{app_path}/Info.plist\""
+
+ sh "CODESIGN_ALLOCATE=\"/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/codesign_allocate\" /usr/bin/codesign -f -s \"#{@certificate_name}\" --entitlements #{tmp_directory}/Entitlements.plist \"#{tmp_directory}/#{@app_name}.app\""
+
+ @code_sign_completed = true
+ end
+
+ def write_info_plist
+ log_info "Adding Info.plist."
+
+ info_plist_string = <<-XML
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>NSAppTransportSecurity</key>
+ <dict>
+ <key>NSAllowsArbitraryLoads</key>
+ <true/>
+ <key>NSExceptionDomains</key>
+ <dict>
+ <key>google.com</key>
+ <dict>
+ <key>NSExceptionAllowsInsecureHTTPLoads</key>
+ <true/>
+ <key>NSIncludesSubdomains</key>
+ <true/>
+ </dict>
+ </dict>
+ </dict>
+ <key>BuildMachineOSBuild</key>
+ <string>19C57</string>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>en</string>
+ <key>CFBundleDisplayName</key>
+ <string>:app_name</string>
+ <key>CFBundleExecutable</key>
+ <string>Runtime</string>
+ <key>CFBundleIconFiles</key>
+ <array>
+ <string>AppIcon60x60</string>
+ </array>
+ <key>CFBundleIcons</key>
+ <dict>
+ <key>CFBundlePrimaryIcon</key>
+ <dict>
+ <key>CFBundleIconFiles</key>
+ <array>
+ <string>AppIcon60x60</string>
+ </array>
+ <key>CFBundleIconName</key>
+ <string>AppIcon</string>
+ </dict>
+ </dict>
+ <key>CFBundleIcons~ipad</key>
+ <dict>
+ <key>CFBundlePrimaryIcon</key>
+ <dict>
+ <key>CFBundleIconFiles</key>
+ <array>
+ <string>AppIcon60x60</string>
+ <string>AppIcon76x76</string>
+ </array>
+ <key>CFBundleIconName</key>
+ <string>AppIcon</string>
+ </dict>
+ </dict>
+ <key>CFBundleIdentifier</key>
+ <string>com.carlile.swisscheese</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>:app_name</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>1.0</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>CFBundleSupportedPlatforms</key>
+ <array>
+ <string>iPhoneOS</string>
+ </array>
+ <key>CFBundleVersion</key>
+ <string>1.0</string>
+ <key>DTCompiler</key>
+ <string>com.apple.compilers.llvm.clang.1_0</string>
+ <key>DTPlatformBuild</key>
+ <string>17B102</string>
+ <key>DTPlatformName</key>
+ <string>iphoneos</string>
+ <key>DTPlatformVersion</key>
+ <string>13.2</string>
+ <key>DTSDKBuild</key>
+ <string>17B102</string>
+ <key>DTSDKName</key>
+ <string>iphoneos13.2</string>
+ <key>DTXcode</key>
+ <string>01131</string>
+ <key>DTXcodeBuild</key>
+ <string>11C505</string>
+ <key>ITSAppUsesNonExemptEncryption</key>
+ <false/>
+ <key>MinimumOSVersion</key>
+ <string>11.0</string>
+ <key>UIAppFonts</key>
+ <array/>
+ <key>UIBackgroundModes</key>
+ <array/>
+ <key>UIDeviceFamily</key>
+ <array>
+ <integer>1</integer>
+ <integer>2</integer>
+ </array>
+ <key>UILaunchImages</key>
+ <array>
+ <dict>
+ <key>UILaunchImageMinimumOSVersion</key>
+ <string>11.0</string>
+ <key>UILaunchImageName</key>
+ <string>LaunchImage-1100-Portrait-2436h</string>
+ <key>UILaunchImageOrientation</key>
+ <string>Portrait</string>
+ <key>UILaunchImageSize</key>
+ <string>{375, 812}</string>
+ </dict>
+ <dict>
+ <key>UILaunchImageMinimumOSVersion</key>
+ <string>8.0</string>
+ <key>UILaunchImageName</key>
+ <string>LaunchImage-800-Portrait-736h</string>
+ <key>UILaunchImageOrientation</key>
+ <string>Portrait</string>
+ <key>UILaunchImageSize</key>
+ <string>{414, 736}</string>
+ </dict>
+ <dict>
+ <key>UILaunchImageMinimumOSVersion</key>
+ <string>8.0</string>
+ <key>UILaunchImageName</key>
+ <string>LaunchImage-800-667h</string>
+ <key>UILaunchImageOrientation</key>
+ <string>Portrait</string>
+ <key>UILaunchImageSize</key>
+ <string>{375, 667}</string>
+ </dict>
+ <dict>
+ <key>UILaunchImageMinimumOSVersion</key>
+ <string>7.0</string>
+ <key>UILaunchImageName</key>
+ <string>LaunchImage-700</string>
+ <key>UILaunchImageOrientation</key>
+ <string>Portrait</string>
+ <key>UILaunchImageSize</key>
+ <string>{320, 480}</string>
+ </dict>
+ <dict>
+ <key>UILaunchImageMinimumOSVersion</key>
+ <string>7.0</string>
+ <key>UILaunchImageName</key>
+ <string>LaunchImage-700-568h</string>
+ <key>UILaunchImageOrientation</key>
+ <string>Portrait</string>
+ <key>UILaunchImageSize</key>
+ <string>{320, 568}</string>
+ </dict>
+ <dict>
+ <key>UILaunchImageMinimumOSVersion</key>
+ <string>7.0</string>
+ <key>UILaunchImageName</key>
+ <string>LaunchImage-700-Portrait</string>
+ <key>UILaunchImageOrientation</key>
+ <string>Portrait</string>
+ <key>UILaunchImageSize</key>
+ <string>{768, 1024}</string>
+ </dict>
+ </array>
+ <key>UIRequiredDeviceCapabilities</key>
+ <array>
+ <string>arm64</string>
+ </array>
+ <key>UIRequiresFullScreen</key>
+ <true/>
+ <key>UIStatusBarStyle</key>
+ <string>UIStatusBarStyleDefault</string>
+ <key>UISupportedInterfaceOrientations</key>
+ <array>
+ <string>#{device_orientation_xml}</string>
+ </array>
+</dict>
+</plist>
+XML
+
+ # <string>UIInterfaceOrientationPortrait</string>
+ # <string>UIInterfaceOrientationLandscapeRight</string>
+
+ $gtk.write_file_root "tmp/ios/#{@app_name}.app/Info.plist", info_plist_string.gsub(":app_name", @app_name).strip
+
+ @info_plist_written = true
+ end
+
+ def device_orientation_xml
+ return "UIInterfaceOrientationLandscapeRight" if $gtk.logical_width > $gtk.logical_height
+ return "UIInterfaceOrientationPortrait"
+ end
+
+ def tmp_directory
+ "#{relative_path}/tmp/ios"
+ end
+
+ def app_path
+ "#{tmp_directory}/#{@app_name}.app"
+ end
+
+ def root_folder
+ "#{relative_path}/#{$gtk.cli_arguments[:dragonruby]}"
+ end
+
+ def write_ip_address
+ $gtk.write_file "app/server_ip_address.txt", $gtk.ffi_misc.get_local_ip_address.strip
+ end
+
+ def create_ipa
+ write_ip_address
+ sh "rm \"#{@app_name}\".ipa"
+ sh "rm -rf \"#{app_path}/app\""
+ sh "rm -rf \"#{app_path}/sounds\""
+ sh "rm -rf \"#{app_path}/sprites\""
+ sh "rm -rf \"#{app_path}/data\""
+ sh "rm -rf \"#{app_path}/fonts\""
+ sh "cp -r \"#{root_folder}/app/\" \"#{app_path}/app/\""
+ sh "cp -r \"#{root_folder}/sounds/\" \"#{app_path}/sounds/\""
+ sh "cp -r \"#{root_folder}/sprites/\" \"#{app_path}/sprites/\""
+ sh "cp -r \"#{root_folder}/data/\" \"#{app_path}/data/\""
+ sh "cp -r \"#{root_folder}/fonts/\" \"#{app_path}/fonts/\""
+ sh "mkdir -p #{tmp_directory}/ipa_root/Payload"
+ sh "cp -r \"#{app_path}\" \"#{tmp_directory}/ipa_root/Payload\""
+ sh "chmod -R 755 \"#{tmp_directory}/ipa_root/Payload\""
+ do_zip
+ sh "cp \"#{tmp_directory}/ipa_root/archive.zip\" \"#{tmp_directory}/#{@app_name}.ipa\""
+ sh "XCODE_DIR=\"/Applications/Xcode.app/Contents/Developer\" \"#{relative_path}/dragonruby-deploy-ios\" -d \"#{@device_id}\" \"#{tmp_directory}/#{@app_name}.ipa\""
+ cmd_result = `ps -e | grep civetweb`
+ is_civet_running = (`ps -e | grep civetweb`).strip.each_line.to_a.length > 2
+ if !is_civet_running
+ $gtk.system "cp \"#{relative_path}/civetweb\" \"#{tmp_directory}/../src_backup/civetweb\""
+ $gtk.system "open \"#{tmp_directory}/../src_backup/civetweb\" -g"
+ else
+ log "* INFO: civetweb is running already running. No need to start another instance."
+ end
+ log_info "Check your device!!"
+ end
+
+ def do_zip
+ $gtk.write_file_root "tmp/ios/do_zip.sh", <<-SCRIPT
+pushd #{tmp_directory}/ipa_root/
+zip -q -r archive.zip Payload
+popd
+SCRIPT
+
+ sh "sh #{tmp_directory}/do_zip.sh"
+ end
+
+ def sh cmd
+ log_info cmd.strip
+ result = `#{cmd}`
+ if result.strip.length > 0
+ log_info result.strip.each_line.map(&:strip).join("\n")
+ end
+ result
+ end
+
+ def deploy
+ end
+end
+
+</pre>
+<h1 id='--itch_wizard.rb'>itch_wizard.rb</h1>
+<pre># ./dragon/itch_wizard.rb
+# Copyright 2019 DragonRuby LLC
+# MIT License
+# itch_wizard.rb has been released under MIT (*only this file*).
+
+class ItchWizard
+ def steps
+ [
+ :check_metadata,
+ :deploy
+ ]
+ end
+
+ def metadata_file_path
+ "metadata/game_metadata.txt"
+ end
+
+ def get_metadata
+ metadata = $gtk.read_file metadata_file_path
+
+ if !metadata
+ write_blank_metadata
+ metadata = $gtk.read_file metadata_file_path
+ end
+
+ dev_id, dev_title, game_id, game_title, version, icon = *metadata.each_line.to_a
+
+ {
+ dev_id: dev_id.strip,
+ dev_title: dev_title.strip,
+ game_id: game_id.strip,
+ game_title: game_title.strip,
+ version: version.strip,
+ icon: icon.strip
+ }
+ end
+
+ def write_blank_metadata
+ $gtk.write_file metadata_file_path, <<-S.strip
+#devid=myname
+#devtitle=My Name
+#gameid=mygame
+#gametitle=My Game
+#version=0.1
+#icon=metadata/icon.png
+S
+ end
+
+ def check_metadata
+ metadata_text = $gtk.read_file metadata_file_path
+ if !metadata_text
+ write_blank_metadata
+ end
+
+ if metadata_text.each_line.to_a.length != 6
+ write_blank_metadata
+ end
+
+ log "* INFO: Contents of #{metadata_file_path}:"
+ log "#+begin_src txt"
+ metadata_text.each_line { |l| log " #{l}" }
+ log "#+end_src"
+ metadata = get_metadata
+
+ if metadata[:dev_id].start_with?("#") || !@dev_id
+ log "* PROMPT: Please provide your username for Itch."
+ $console.set_command "$wizards.itch.set_dev_id \"your-itch-username\""
+ return :need_dev_id
+ end
+
+ if metadata[:dev_title].start_with?("#") || !@dev_title
+ log "* PROMPT: Please provide developer's/company's name that you want displayed."
+ $console.set_command "$wizards.itch.set_dev_title \"Your Name\""
+ return :need_dev_title
+ end
+
+ if metadata[:game_id].start_with?("#") || !@game_id
+ log "* PROMPT: Please provide the id for you game. This is the id you specified when you set up a new game page on Itch."
+ $console.set_command "$wizards.itch.set_game_id \"your-game-id\""
+ return :need_game_id
+ end
+
+ if metadata[:game_title].start_with?("#") || !@game_title
+ log "* PROMPT: Please provide the display name for your game. (This can include spaces)"
+ $console.set_command "$wizards.itch.set_game_title \"Your Game\""
+ return :need_game_title
+ end
+
+ if metadata[:version].start_with?("#") || !@version
+ log "* PROMPT: Please provide the version for your game."
+ $console.set_command "$wizards.itch.set_version \"1.0\""
+ return :need_version
+ end
+
+ if metadata[:icon].start_with?("#") || !@icon
+ log "* PROPT: Please provide icon path for your game."
+ $console.set_command "$wizards.itch.set_icon \"icon.png\""
+ return :need_icon
+ end
+
+ return :success
+ end
+
+ def set_dev_id value
+ @dev_id = value
+ write_metadata
+ start
+ end
+
+ def set_dev_title value
+ @dev_title = value
+ write_metadata
+ start
+ end
+
+ def set_game_id value
+ @game_id = value
+ write_metadata
+ start
+ end
+
+ def set_game_title value
+ @game_title = value
+ write_metadata
+ start
+ end
+
+ def set_version value
+ @version = value
+ write_metadata
+ start
+ end
+
+ def set_icon value
+ @icon = value
+ write_metadata
+ start
+ end
+
+ def write_metadata
+ text = ""
+ if @dev_id
+ text += "devid=#{@dev_id}\n"
+ else
+ text += "#devid=myname\n"
+ end
+
+ if @dev_title
+ text += "devtitle=#{@dev_title}\n"
+ else
+ text += "#devtitle=My Name\n"
+ end
+
+ if @game_id
+ text += "gameid=#{@game_id}\n"
+ else
+ text += "#gameid=gameid\n"
+ end
+
+ if @game_title
+ text += "gametitle=#{@game_title}\n"
+ else
+ text += "#gametitle=Game Name\n"
+ end
+
+ if @version
+ text += "version=#{@version}\n"
+ else
+ text += "#version=0.1\n"
+ end
+
+ if @icon
+ text += "icon=metadata/#{@icon}\n"
+ else
+ text += "#icon=metadata/icon.png\n"
+ end
+
+ $gtk.write_file metadata_file_path, text
+ end
+
+ def relative_path
+ (File.dirname $gtk.binary_path)
+ end
+
+ def package_command
+ "#{File.join $gtk.get_base_dir, 'dragonruby-publish'}"
+ end
+
+ def deploy
+ log_info "* Running dragonruby-publish: #{package_command}"
+ results = $gtk.exec package_command
+ log "#+begin_src"
+ log results
+ log "#+end_src"
+ :success
+ end
+
+ def start
+ log "================"
+ log "* INFO: Starting Itch Wizard."
+ @start_at = Kernel.global_tick_count
+ steps.each do |m|
+ begin
+ log_info "Running Itch Wizard Step: ~$wizards.itch.#{m}~"
+ result = (send m) || :success if @wizard_status[m][:result] != :success
+ @wizard_status[m][:result] = result
+ if result != :success
+ log_info "Exiting wizard. :#{result}"
+ break
+ end
+ rescue Exception => e
+ if e.is_a? WizardException
+ $console.log.clear
+ $console.archived_log.clear
+ log "=" * $console.console_text_width
+ e.console_primitives.each do |p|
+ $console.add_primitive p
+ end
+ log "=" * $console.console_text_width
+ $console.set_command (e.console_command || "$wizards.itch.start")
+ else
+ log_error "Step #{m} failed."
+ log_error e.to_s
+ $console.set_command "$wizards.itch.start"
+ end
+
+ break
+ end
+ end
+ end
+
+ def reset
+ @dev_id = nil
+ @dev_title = nil
+ @game_id = nil
+ @game_title = nil
+ @version = nil
+ @icon = nil
+ init_wizard_status
+ end
+
+ def restart
+ reset
+ start
+ end
+
+ def initialize
+ reset
+ end
+
+ def init_wizard_status
+ @wizard_status = {}
+
+ steps.each do |m|
+ @wizard_status[m] = { result: :not_started }
+ end
+
+ previous_step = nil
+ next_step = nil
+
+ steps.each_cons(2) do |current_step, next_step|
+ @wizard_status[current_step][:next_step] = next_step
+ end
+
+ steps.reverse.each_cons(2) do |current_step, previous_step|
+ @wizard_status[current_step][:previous_step] = previous_step
+ end
+ end
+end
+
+</pre>
+<h1 id='--layout.rb'>layout.rb</h1>
+<pre># ./dragon/layout.rb
+# Copyright 2019 DragonRuby LLC
+# MIT License
+# layout.rb has been released under MIT (*only this file*).
+
+module GTK
+ class Margin
+ attr :left, :right, :top, :bottom
+
+ def initialize
+ @left = 0
+ @right = 0
+ @top = 0
+ @bottom = 0
+ end
+
+ def serialize
+ {
+ left: @left,
+ right: @right,
+ top: @top,
+ bottom: @bottom,
+ }
+ end
+
+ def inspect
+ serialize.to_s
+ end
+
+ def to_s
+ serialize.to_s
+ end
+ end
+
+ class SafeArea
+ attr :w, :h, :margin
+
+ def initialize
+ @w = 0
+ @h = 0
+ @margin = Margin.new
+ end
+
+ def serialize
+ {
+ w: @w,
+ h: @h,
+ margin: @margin.serialize
+ }
+ end
+
+ def inspect
+ serialize.to_s
+ end
+
+ def to_s
+ serialize.to_s
+ end
+ end
+
+ class GridArea
+ attr :w, :h, :margin, :gutter, :col_count, :row_count, :cell_w, :cell_h, :outer_gutter
+
+ def initialize
+ @w = 0
+ @h = 0
+ @gutter = 0
+ @outer_gutter = 0
+ @col_count = 0
+ @row_count = 0
+ @margin = Margin.new
+ end
+
+ def serialize
+ {
+ w: @w,
+ h: @h,
+ gutter: @gutter,
+ outer_gutter: @outer_gutter,
+ col_count: @col_count,
+ row_count: @row_count,
+ margin: @margin.serialize
+ }
+ end
+
+ def inspect
+ serialize.to_s
+ end
+
+ def to_s
+ serialize.to_s
+ end
+ end
+
+ class ControlArea
+ attr :cell_size, :w, :h, :margin
+
+ def initialize
+ @margin = Margin.new
+ end
+
+ def serialize
+ {
+ cell_size: @cell_size,
+ w: @w,
+ h: @h,
+ margin: @margin.serialize,
+ }
+ end
+
+ def inspect
+ serialize.to_s
+ end
+
+ def to_s
+ serialize.to_s
+ end
+ end
+
+ class Device
+ attr :w, :h, :safe_area, :grid_area, :control_area, :name, :aspect
+
+ def initialize
+ @name = ""
+ @w = 0
+ @h = 0
+ @safe_area = SafeArea.new
+ @grid_area = GridArea.new
+ @control_area = ControlArea.new
+ @aspect = AspectRatio.new
+ end
+
+ def assert! result, message
+ return if result
+ raise message
+ end
+
+ def check_math!
+ assert! (@control_area.w + @control_area.margin.left + @control_area.margin.right) == @w, "Math for Width didn't pan out."
+ assert! (@control_area.h + @control_area.margin.top + @control_area.margin.bottom) == @h, "Math for Height didn't pan out."
+ end
+
+ def serialize
+ {
+ name: @name,
+ w: @w,
+ h: @h,
+ aspect: @aspect.serialize,
+ safe_area: @safe_area.serialize,
+ grid_area: @grid_area.serialize,
+ control_area: @control_area.serialize
+ }
+ end
+
+ def inspect
+ serialize.to_s
+ end
+
+ def to_s
+ serialize.to_s
+ end
+ end
+
+ class AspectRatio
+ attr :w, :h, :u
+
+ def initialize
+ @w = 0
+ @h = 0
+ @u = 0
+ end
+
+ def serialize
+ {
+ w: @w,
+ h: @h,
+ u: @u
+ }
+ end
+
+ def inspect
+ serialize.to_s
+ end
+
+ def to_s
+ serialize.to_s
+ end
+ end
+
+ class Layout
+ attr :w, :h, :rect_cache
+
+ def initialize w, h
+ @w = w
+ @h = h
+ @rect_cache = {}
+ init_device @w, @h
+ end
+
+ def u_for_16x9 w, h
+ u = (w.fdiv 16).floor
+ u = (h.fdiv 9).floor if (u * 9) > h
+
+ {
+ u: u,
+ w: u * 16,
+ h: u * 9
+ }
+ end
+
+ def font_relative_size_enum size_enum
+ base_line_logical = 22
+ base_line_actual = font_size_med
+ target_logical = size_enum
+ target_logical = 1 if target_logical <= 0
+ (base_line_actual / base_line_logical) * target_logical
+ end
+
+ def font_px_to_pt px
+ (px / 1.33333).floor
+ end
+
+ def font_pt_to_px pt
+ pt * 1.333333
+ end
+
+ def font_size_cell
+ (cell_height / 1.33333)
+ end
+
+ def font_size_xl
+ font_size_cell
+ end
+
+ def font_size_lg
+ font_size_cell * 0.8
+ end
+
+ def font_size_med
+ font_size_cell * 0.7
+ end
+
+ def font_size_sm
+ font_size_cell * 0.6
+ end
+
+ def font_size_xs
+ font_size_cell * 0.5
+ end
+
+ def font_size
+ font_size_cell * 0.7
+ end
+
+ def logical_rect
+ @logical_rect ||= { x: 0,
+ y: 0,
+ w: @w,
+ h: @h }
+ end
+
+ def safe_rect
+ @safe_rect ||= { x: 0,
+ y: 0,
+ w: @w,
+ h: @h }
+ end
+
+ def control_rect
+ @control_rect ||= { x: device.control_area.margin.left,
+ y: device.control_area.margin.top,
+ w: device.control_area.w,
+ h: device.control_area.h }
+ end
+
+ def row_count
+ device.grid_area.row_count
+ end
+
+ def col_count
+ device.grid_area.col_count
+ end
+
+ def gutter_height
+ device.grid_area.gutter
+ end
+
+ def gutter_width
+ device.grid_area.gutter
+ end
+
+ def outer_gutter
+ device.grid_area.outer_gutter
+ end
+
+ def cell_height
+ device.control_area.cell_size
+ end
+
+ def cell_width
+ device.control_area.cell_size
+ end
+
+ def rect_defaults
+ {
+ row: nil,
+ col: nil,
+ h: 1,
+ w: 1,
+ dx: 0,
+ dy: 0,
+ rect: :control_rect
+ }
+ end
+
+ def rect opts
+ opts = rect_defaults.merge opts
+ result = send opts[:rect]
+ if opts[:row] && opts[:col] && opts[:w] && opts[:h]
+ col = rect_col opts[:col], opts[:w]
+ row = rect_row opts[:row], opts[:h]
+ result = control_rect.merge x: col.x,
+ y: row.y,
+ w: col.w,
+ h: row.h
+ elsif opts[:row] && !opts[:col]
+ result = rect_row opts[:row], opts[:h]
+ elsif !opts[:row] && opts[:col]
+ result = rect_col opts[:col], opts[:w]
+ else
+ raise "LayoutTheory::rect unable to process opts #{opts}."
+ end
+
+ if opts[:max_height] && opts[:max_height] >= 0
+ if result[:h] > opts[:max_height]
+ delta = (result[:h] - opts[:max_height]) * 2
+ result[:y] += delta
+ result[:h] = opts[:max_height]
+ end
+ end
+
+ if opts[:max_width] && opts[:max_width] >= 0
+ if result[:w] > opts[:max_width]
+ delta = (result[:w] - opts[:max_width]) * 2
+ result[:x] += delta
+ result[:w] = opts[:max_width]
+ end
+ end
+
+ result[:x] += opts[:dx]
+ result[:y] += opts[:dy]
+
+ if opts[:include_row_gutter]
+ result[:x] -= device.grid_area.gutter
+ result[:w] += device.grid_area.gutter * 2
+ end
+
+ if opts[:include_col_gutter]
+ result[:y] -= device.grid_area.gutter
+ result[:h] += device.grid_area.gutter * 2
+ end
+
+ result
+ end
+
+ def rect_center reference, target
+ delta_x = (reference.w - target.w).fdiv 2
+ delta_y = (reference.h - target.h).fdiv 2
+ [target.x - delta_x, target.y - delta_y, target.w, target.h]
+ end
+
+ def rect_row index, h
+ @rect_cache[:row] ||= {}
+ @rect_cache[:row][index] ||= {}
+ return @rect_cache[:row][index][h] if @rect_cache[:row][index][h]
+ row_h = (device.grid_area.gutter * (h - 1)) +
+ (device.control_area.cell_size * h)
+
+ row_h = row_h.to_i
+ row_h -= 1 if row_h.odd?
+
+ row_y = (control_rect.y) +
+ (device.grid_area.gutter * index) +
+ (device.control_area.cell_size * index)
+
+ row_y = row_y.to_i
+ row_y += 1 if row_y.odd? && (index + 1) > @device.grid_area.row_count.half
+ row_y += 1 if row_y.odd? && (index + 1) <= @device.grid_area.row_count.half
+
+ row_y = device.h - row_y - row_h
+
+ result = control_rect.merge y: row_y, h: row_h
+ @rect_cache[:row][index][h] = result
+ @rect_cache[:row][index][h]
+ end
+
+ def rect_col index, w
+ @rect_cache[:col] ||= {}
+ @rect_cache[:col][index] ||= {}
+ return @rect_cache[:col][index][w] if @rect_cache[:col][index][w]
+ col_x = (control_rect.x) +
+ (device.grid_area.gutter * index) +
+ (device.control_area.cell_size * index)
+
+ col_x = col_x.to_i
+ col_x -= 1 if col_x.odd? && (index + 1) < @device.grid_area.col_count.half
+ col_x += 1 if col_x.odd? && (index + 1) >= @device.grid_area.col_count.half
+
+ col_w = (device.grid_area.gutter * (w - 1)) +
+ (device.control_area.cell_size * w)
+
+ col_w = col_w.to_i
+ col_w -= 1 if col_w.odd?
+
+ result = control_rect.merge x: col_x, w: col_w
+ @rect_cache[:col][index][w] = result
+ @rect_cache[:col][index][w]
+ end
+
+ def device
+ @device
+ end
+
+ def init_device w, h
+ @device = Device.new
+ @device.w = w
+ @device.h = h
+ @device.name = "Device"
+ @device.aspect.w = (u_for_16x9 w, h)[:w]
+ @device.aspect.h = (u_for_16x9 w, h)[:h]
+ @device.aspect.u = (u_for_16x9 w, h)[:u]
+ @device.safe_area.w = @device.aspect.u * 16
+ @device.safe_area.h = @device.aspect.u * 9
+ @device.safe_area.margin.left = ((@device.w - @device.safe_area.w).fdiv 2).floor
+ @device.safe_area.margin.right = ((@device.w - @device.safe_area.w).fdiv 2).floor
+ @device.safe_area.margin.top = ((@device.h - @device.safe_area.h).fdiv 2).floor
+ @device.safe_area.margin.bottom = ((@device.h - @device.safe_area.h).fdiv 2).floor
+ @device.grid_area.outer_gutter = @device.w / 80
+ @device.grid_area.gutter = @device.w / 160
+
+ @device.grid_area.w = @device.safe_area.w - (@device.grid_area.outer_gutter * 2)
+ @device.grid_area.h = @device.safe_area.h - (@device.grid_area.outer_gutter * 2)
+
+ @device.grid_area.margin.left = ((@device.w - @device.grid_area.w).fdiv 2).floor
+ @device.grid_area.margin.right = ((@device.w - @device.grid_area.w).fdiv 2).floor
+ @device.grid_area.margin.top = ((@device.h - @device.grid_area.h).fdiv 2).floor
+ @device.grid_area.margin.bottom = ((@device.h - @device.grid_area.h).fdiv 2).floor
+
+ @device.grid_area.col_count = 24
+ @device.grid_area.row_count = 12
+ @device.grid_area.cell_w = ((@device.aspect.w - (@device.grid_area.outer_gutter * 2)) - ((@device.grid_area.col_count - 1) * @device.grid_area.gutter)).fdiv @device.grid_area.col_count
+ @device.grid_area.cell_h = ((@device.aspect.h - (@device.grid_area.outer_gutter * 2)) - ((@device.grid_area.row_count - 1) * @device.grid_area.gutter)).fdiv @device.grid_area.row_count
+
+ @device.control_area.cell_size = @device.grid_area.cell_w
+ @device.control_area.cell_size = @device.grid_area.cell_h if @device.grid_area.cell_h < @device.grid_area.cell_w && @device.grid_area.cell_h > 0
+ @device.control_area.cell_size = @device.control_area.cell_size.floor
+ @device.control_area.w = (@device.control_area.cell_size * @device.grid_area.col_count) + (@device.grid_area.gutter * (@device.grid_area.col_count - 1))
+ @device.control_area.h = (@device.control_area.cell_size * @device.grid_area.row_count) + (@device.grid_area.gutter * (@device.grid_area.row_count - 1))
+ @device.control_area.margin.left = (@device.w - @device.control_area.w).fdiv 2
+ @device.control_area.margin.right = (@device.w - @device.control_area.w).fdiv 2
+ @device.control_area.margin.top = (@device.h - @device.control_area.h).fdiv 2
+ @device.control_area.margin.bottom = (@device.h - @device.control_area.h).fdiv 2
+ @device
+ end
+
+ def serialize
+ {
+ device: @device.serialize,
+ }
+ end
+
+ def inspect
+ serialize.to_s
+ end
+
+ def to_s
+ serialize.to_s
+ end
+ end
+end
+
+</pre>
<h1 id='--log.rb'>log.rb</h1>
<pre># ./dragon/log.rb
# coding: utf-8
@@ -26047,6 +28267,11 @@ S
return 720 - self unless $gtk
$gtk.args.grid.h - self
end
+
+ def from_right
+ return 1280 - self unless $gtk
+ $gtk.args.grid.w - self
+ end
end
class Fixnum
@@ -26194,6 +28419,245 @@ class Integer
end
</pre>
+<h1 id='--runtime/draw.rb'>runtime/draw.rb</h1>
+<pre># ./dragon/runtime/draw.rb
+# Copyright 2019 DragonRuby LLC
+# MIT License
+# draw.rb has been released under MIT (*only this file*).
+
+module GTK
+ class Runtime
+ module Draw
+ def primitives pass
+ if $top_level.respond_to? :primitives_override
+ return $top_level.tick_render @args, pass
+ end
+
+ # Don't change this draw order unless you understand
+ # the implications.
+
+ # pass.solids.each { |s| draw_solid s }
+ # while loops are faster than each with block
+ idx = 0
+ while idx < pass.solids.length
+ draw_solid (pass.solids.value idx) # accessing an array using .value instead of [] is faster
+ idx += 1
+ end
+
+ # pass.static_solids.each { |s| draw_solid s }
+ idx = 0
+ while idx < pass.static_solids.length
+ draw_solid (pass.static_solids.value idx)
+ idx += 1
+ end
+
+ # pass.sprites.each { |s| draw_sprite s }
+ idx = 0
+ while idx < pass.sprites.length
+ draw_sprite (pass.sprites.value idx)
+ idx += 1
+ end
+
+ # pass.static_sprites.each { |s| draw_sprite s }
+ idx = 0
+ while idx < pass.static_sprites.length
+ draw_sprite (pass.static_sprites.value idx)
+ idx += 1
+ end
+
+ # pass.primitives.each { |p| draw_primitive p }
+ idx = 0
+ while idx < pass.primitives.length
+ draw_primitive (pass.primitives.value idx)
+ idx += 1
+ end
+
+ # pass.static_primitives.each { |p| draw_primitive p }
+ idx = 0
+ while idx < pass.static_primitives.length
+ draw_primitive (pass.static_primitives.value idx)
+ idx += 1
+ end
+
+ # pass.labels.each { |l| draw_label l }
+ idx = 0
+ while idx < pass.labels.length
+ draw_label (pass.labels.value idx)
+ idx += 1
+ end
+
+ # pass.static_labels.each { |l| draw_label l }
+ idx = 0
+ while idx < pass.static_labels.length
+ draw_label (pass.static_labels.value idx)
+ idx += 1
+ end
+
+ # pass.lines.each { |l| draw_line l }
+ idx = 0
+ while idx < pass.lines.length
+ draw_line (pass.lines.value idx)
+ idx += 1
+ end
+
+ # pass.static_lines.each { |l| draw_line l }
+ idx = 0
+ while idx < pass.static_lines.length
+ draw_line (pass.static_lines.value idx)
+ idx += 1
+ end
+
+ # pass.borders.each { |b| draw_border b }
+ idx = 0
+ while idx < pass.borders.length
+ draw_border (pass.borders.value idx)
+ idx += 1
+ end
+
+ # pass.static_borders.each { |b| draw_border b }
+ idx = 0
+ while idx < pass.static_borders.length
+ draw_border (pass.static_borders.value idx)
+ idx += 1
+ end
+
+ if !$gtk.production
+ # pass.debug.each { |r| draw_primitive r }
+ idx = 0
+ while idx < pass.debug.length
+ draw_primitive (pass.debug.value idx)
+ idx += 1
+ end
+
+ # pass.static_debug.each { |r| draw_primitive r }
+ idx = 0
+ while idx < pass.static_debug.length
+ draw_primitive (pass.static_debug.value idx)
+ idx += 1
+ end
+ end
+
+ # pass.reserved.each { |r| draw_primitive r }
+ idx = 0
+ while idx < pass.reserved.length
+ draw_primitive (pass.reserved.value idx)
+ idx += 1
+ end
+
+ # pass.static_reserved.each { |r| draw_primitive r }
+ idx = 0
+ while idx < pass.static_reserved.length
+ draw_primitive (pass.static_reserved.value idx)
+ idx += 1
+ end
+ rescue Exception => e
+ pause!
+ pretty_print_exception_and_export! e
+ end
+
+ def draw_solid s
+ return unless s
+ if s.respond_to? :draw_override
+ s.draw_override @ffi_draw
+ else
+ @ffi_draw.draw_solid s.x, s.y, s.w, s.h, s.r, s.g, s.b, s.a
+ end
+ rescue Exception => e
+ raise_conversion_for_rendering_failed s, e, :solid
+ end
+
+ def draw_sprite s
+ return unless s
+ if s.respond_to? :draw_override
+ s.draw_override @ffi_draw
+ else
+ @ffi_draw.draw_sprite_3 s.x, s.y, s.w, s.h,
+ s.path.s_or_default,
+ s.angle,
+ s.a, s.r, s.g, s.b,
+ s.tile_x, s.tile_y, s.tile_w, s.tile_h,
+ !!s.flip_horizontally, !!s.flip_vertically,
+ s.angle_anchor_x, s.angle_anchor_y,
+ s.source_x, s.source_y, s.source_w, s.source_h
+ end
+ rescue Exception => e
+ raise_conversion_for_rendering_failed s, e, :sprite
+ end
+
+ def draw_screenshot s
+ return unless s
+ if s.respond_to? :draw_override
+ s.draw_override @ffi_draw
+ else
+ @ffi_draw.draw_screenshot s.path.s_or_default,
+ s.x, s.y, s.w, s.h,
+ s.angle,
+ s.a, s.r, s.g, s.b,
+ s.tile_x, s.tile_y, s.tile_w, s.tile_h,
+ !!s.flip_horizontally, !!s.flip_vertically,
+ s.angle_anchor_x, s.angle_anchor_y,
+ s.source_x, s.source_y, s.source_w, s.source_h
+ end
+ rescue Exception => e
+ raise_conversion_for_rendering_failed s, e, :screenshot
+ end
+
+ def draw_label l
+ return unless l
+ if l.respond_to? :draw_override
+ l.draw_override @ffi_draw
+ else
+ @ffi_draw.draw_label l.x, l.y, l.text.s_or_default,
+ l.size_enum, l.alignment_enum,
+ l.r, l.g, l.b, l.a,
+ l.font.s_or_default(nil)
+ end
+ rescue Exception => e
+ raise_conversion_for_rendering_failed l, e, :label
+ end
+
+ def draw_line l
+ return unless l
+ if l.respond_to? :draw_override
+ l.draw_override @ffi_draw
+ else
+ @ffi_draw.draw_line l.x, l.y, l.x2, l.y2, l.r, l.g, l.b, l.a
+ end
+ rescue Exception => e
+ raise_conversion_for_rendering_failed l, e, :line
+ end
+
+ def draw_border s
+ return unless s
+ if s.respond_to? :draw_override
+ s.draw_override @ffi_draw
+ else
+ @ffi_draw.draw_border s.x, s.y, s.w, s.h, s.r, s.g, s.b, s.a
+ end
+ rescue Exception => e
+ raise_conversion_for_rendering_failed s, e, :border
+ end
+
+ def draw_screenshots
+ @args.outputs.screenshots.each { |s| draw_screenshot s }
+ end
+
+ def pixel_arrays
+ @args.pixel_arrays.each { |k,v|
+ if v.pixels.length == (v.width * v.height) # !!! FIXME: warning? exception? Different API?
+ @ffi_draw.upload_pixel_array k.to_s, v.width.to_i, v.height.to_i, v.pixels
+ end
+ }
+ rescue Exception => e
+ pause!
+ pretty_print_exception_and_export! e
+ end
+
+ end
+ end
+end
+
+</pre>
<h1 id='--runtime/framerate_diagnostics.rb'>runtime/framerate_diagnostics.rb</h1>
<pre># ./dragon/runtime/framerate_diagnostics.rb
# Copyright 2019 DragonRuby LLC
@@ -26470,6 +28934,10 @@ S
def rtrim!
rstrip!
end
+
+ def serialize
+ self
+ end
end
</pre>
@@ -26623,6 +29091,9 @@ end
# MIT License
# trace.rb has been released under MIT (*only this file*).
+# Contributors outside of DragonRuby who also hold Copyright:
+# - Dan Healy: https://github.com/danhealy
+
module GTK
module Trace
IGNORED_METHODS = [
@@ -26702,10 +29173,19 @@ module GTK
methods.reject { |m| m.start_with? "__trace_" }.reject { |m| IGNORED_METHODS.include? m }
end
+ def self.trace_times_string
+ str = []
+ $trace_performance.sort_by {|method_name, times| -times[:avg] }.each do |method_name, times|
+ str << "#{method_name}: #{times[:sum].round(2)}/#{times[:count]} #{times[:min]}ms min, #{times[:avg].round(2)}ms avg, #{times[:max]}ms max"
+ end
+ str.join("\n")
+ end
+
def self.flush_trace pad_with_newline = false
$trace_puts ||= []
+ puts "(Trace info flushed!)"
if $trace_puts.length > 0
- text = $trace_puts.join("")
+ text = $trace_puts.join("").strip + "\n" + self.trace_times_string + "\n"
if pad_with_newline
$gtk.append_file_root 'logs/trace.txt', "\n" + text.strip
else
@@ -26720,6 +29200,15 @@ module GTK
$trace_history ||= []
$trace_enabled = true
$trace_call_depth ||=0
+ $trace_performance = Hash.new {|h,k|
+ h[k] = {
+ min: 100000,
+ max: 0,
+ avg: 0,
+ sum: 0,
+ count: 0
+ }
+ }
flush_trace
instance = $top_level unless instance
return if Trace.traced_classes.include? instance.class
@@ -26737,15 +29226,27 @@ module GTK
instance.__trace_call_depth__ += 1
$trace_call_depth = instance.__trace_call_depth__
parameters = "#{args}"[1..-2]
+
$trace_puts << "\n #{tab_width}#{m}(#{parameters})"
- execution_time = Time.new.to_i
+
+ execution_time = Time.new
+
$last_method_traced = trace_method_name
$trace_history << [m, parameters]
+
result = send(trace_method_name, *args)
- completion_time = Time.new.to_i
+
+ class_m = "#{instance.class}##{m}"
+ completion_time = ((Time.new - execution_time).to_f * 1000).round(2)
+ $trace_performance[class_m][:min] = [$trace_performance[class_m][:min], completion_time].min
+ $trace_performance[class_m][:max] = [$trace_performance[class_m][:max], completion_time].max
+ $trace_performance[class_m][:count] += 1
+ $trace_performance[class_m][:sum] += completion_time
+ $trace_performance[class_m][:avg] = $trace_performance[class_m][:sum].fdiv($trace_performance[class_m][:count])
+
instance.__trace_call_depth__ -= 1
instance.__trace_call_depth__ = instance.__trace_call_depth__.greater 0
- $trace_puts << "\n #{tab_width} success: #{m}"
+ $trace_puts << "\n #{tab_width} #{completion_time > 10 ? '!!! ' : ''}#{completion_time}ms success: #{m}"
if instance.__trace_call_depth__ == 0
$trace_puts << "\n"
$trace_history.clear
@@ -26771,6 +29272,24 @@ module GTK
end
</pre>
+<h1 id='--wizards.rb'>wizards.rb</h1>
+<pre># ./dragon/wizards.rb
+# Copyright 2019 DragonRuby LLC
+# MIT License
+# wizards.rb has been released under MIT (*only this file*).
+
+module GTK
+ class Wizards
+ attr_accessor :ios, :itch
+
+ def initialize
+ @ios = IOSWizard.new
+ @itch = ItchWizard.new
+ end
+ end
+end
+
+</pre>
</div>
</body>
</html>