Compare commits

37 Commits

Author SHA1 Message Date
54802e1254 Added support 'marquee' style progress bars i.e. non-linear progress bar with scrolling bar 2021-12-02 08:15:39 -06:00
6cf4cd73dd Fixed toggling Element enabled state not visually shown 2021-11-18 15:37:57 -06:00
5e5f8ba7ea Added set_color and set_font to Element- fixes image elements unable to change their color when hovered, fixes hovered text changing its color incorrectly 2021-11-18 13:11:02 -06:00
63a51d9d2f Improved scroll_height calculation to adapt to how Flow's flow 2021-11-18 11:58:11 -06:00
6af384536a Fixed locking up when performing text wrapping due to text width check not being satisfied 2021-11-17 22:30:30 -06:00
c1b25d2045 Fixed centered text with unequal margins/paddings/border thicknesses being offset, fixed text wrapping using parents #width (which includes padding and margins) instead of the correct #content_width (which is only the parents content width; which the text is part of) 2021-11-17 17:23:48 -06:00
a915a25699 Updated required gems, reimplemented text wrapping to use a proper binary search and correct inserting newlines in the wrong spot for certain text lengths 2021-11-16 23:35:49 -06:00
0aa9b59316 Containers that need to be recalculated but will not affect their current size can request to have themselves directly recalculated on the next update (fixes scrolling triggering many unneed recalculations), list_box now excludes the currently selected item from the menu, probably fixed list_box callback being called twice instead of the correct once. 2021-11-15 10:17:49 -06:00
d2bf406d29 Bump version 2021-09-23 14:36:19 -05:00
f82c101728 Fixed ListBox sometimes returning self in callback instead of self.value 2021-09-23 14:35:46 -05:00
2f727e9bf2 Bump version 2021-09-22 09:23:24 -05:00
24be9bfb29 Imported Console from I-MIC FPS 2021-06-26 13:12:44 +00:00
5452d149c3 Possible fix for being able to click a button that isn't visible due to scrolling, by ensuring that the host container is hit before checking children. 2021-06-18 02:57:49 +00:00
cdee6548e3 Added support for 9 sliced backgrounds in UI, fixed events not propagating to subscribers if element threw :handled in its own event handler 2021-06-04 03:00:31 +00:00
bd54fafc3f Added Text#text_width method, Text#width now returns value of Text#markup_width 2021-06-03 13:05:11 +00:00
a92d1ad746 Made Text account for shadow and border effects in width/height methods, made TextBlock support disabled state styling 2021-06-03 01:00:13 +00:00
1b080f9fb9 Use shorthand (&:method) for Element scroll_width/height max_scroll_width, possibly fix dimentional_size returning the wrong size when element has nonzero padding/margin/border_thickness 2021-05-31 01:07:24 +00:00
8057bca818 Added rendered vertices count to OpenGLRenderer 2021-05-30 14:10:32 +00:00
c1310f3073 Renamed Text's shadow to border and added proper text shadow effect 2021-05-29 00:10:44 +00:00
850bb610bb Fixed GUI having a total recalculation whenever a style changed, made tooltip not be centered on mouse, removed duplicate loc from Text 2021-05-28 00:43:40 +00:00
0b63986b64 Improved 9 slice background 2021-05-06 03:20:49 +00:00
165fc6f307 Fixed draw_rect helper missing drawing mode argument, set max view distance to a large number 2021-04-25 23:22:58 +00:00
e3c8e3bcc2 Added a bunch of tweens to the Animator, replaced :ease_in_out tween for Intro state engine logo to :swing_to 2021-04-25 14:58:12 +00:00
551a55f894 Add fade out to intro scene 2021-04-24 20:13:06 +00:00
01292ead10 Bump version 2021-04-19 19:26:42 +00:00
7ca6e1bc58 Update README 2021-04-19 19:22:43 +00:00
a70f260bc6 Update README 2021-04-19 19:17:10 +00:00
8002708695 Updated Animator to be usable, added logo texture/image, 'finished' Intro state, added Window#shift_state 2021-04-19 19:13:15 +00:00
676545f3c7 Added wip engine intro state 2021-04-19 14:42:49 +00:00
4b417f9ab7 More changes to how styles are applied, fixed bug where a Container would re-evaluate its build block when clicked, fixed background method in DSL having no effect, fixed edit line losing 'focus' when mouse moved out, misc. other changes and fixes. 2021-03-29 08:51:26 -05:00
a70c106387 Added Link element which is basically a button without a border or background, WIP: Element border, margin, padding, and other styles are now easily changable; work is needed to make style changes survive a recalculation 2021-03-29 08:51:26 -05:00
f662fabc56 Use GL_SRGB_ALPHA for textures 2021-03-29 08:13:59 -05:00
abb989f842 Added flags to use ffi-gosu optionally instead of always trying, made tooltip affected by theme (the last set theme in setup will be used to style the tooltip) 2021-03-12 08:33:16 -06:00
1e0d2004b5 More adjustments to scrolling and scroll_top, added debug_draw to elements that draws a box around the element that is atop all rendered things 2021-02-13 22:57:18 -06:00
92dd63dc1d Probably fixed scrolling for real this time, added scroll_top and scroll_top = n methods 2021-02-13 20:03:10 -06:00
20970e5aa9 Bump version 2021-02-11 09:32:37 -06:00
76eb1a85d5 Added focus event, elements can request focus 2021-02-11 09:31:44 -06:00
34 changed files with 1570 additions and 469 deletions

View File

@@ -1,4 +1,4 @@
# CyberarmEngine ![CyberarmEngine](https://raw.githubusercontent.com/cyberarm/cyberarm_engine/master/assets/textures/logo.png)
Yet Another Game Engine On Top Of Gosu Yet Another Game Engine On Top Of Gosu
@@ -32,6 +32,8 @@ require "cyberarm_engine"
class Hello < CyberarmEngine::GuiState class Hello < CyberarmEngine::GuiState
def setup def setup
background Gosu::Color::GRAY
stack do stack do
label "Hello World!" label "Hello World!"
@@ -43,15 +45,14 @@ class Hello < CyberarmEngine::GuiState
end end
class Window < CyberarmEngine::Window class Window < CyberarmEngine::Window
def initialize def setup
super
self.show_cursor = true self.show_cursor = true
push_state(Hello) push_state(Hello)
end end
end end
Window.new.show Window.new(width: 800, height: 600, fullscreen: false, resizable: true).show
``` ```
## Development ## Development

BIN
assets/textures/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View File

@@ -27,8 +27,8 @@ Gem::Specification.new do |spec|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
spec.require_paths = %w[lib assets] spec.require_paths = %w[lib assets]
spec.add_dependency "clipboard", "~> 1.3.5" spec.add_dependency "clipboard", "~> 1.3"
spec.add_dependency "excon", "~> 0.78.0" spec.add_dependency "excon", "~> 0.88"
spec.add_dependency "gosu", "~> 1.1" spec.add_dependency "gosu", "~> 1.1"
spec.add_dependency "gosu_more_drawables", "~> 0.3" spec.add_dependency "gosu_more_drawables", "~> 0.3"
# spec.add_dependency "ffi", :platforms => [:mswin, :mingw] # Required by Clipboard on Windows # spec.add_dependency "ffi", :platforms => [:mswin, :mingw] # Required by Clipboard on Windows

View File

@@ -1,9 +1,8 @@
CYBERARM_ENGINE_ROOT_PATH = File.expand_path("..", __dir__) CYBERARM_ENGINE_ROOT_PATH = File.expand_path("..", __dir__)
begin if ARGV.join.include?("--ffi-gosu")
require File.expand_path("../../ffi-gosu/lib/gosu", File.dirname(__FILE__)) require File.expand_path("../../ffi-gosu/lib/gosu", __dir__)
rescue LoadError => e else
pp e
require "gosu" require "gosu"
end end
require "json" require "json"
@@ -24,12 +23,18 @@ require_relative "cyberarm_engine/vector"
require_relative "cyberarm_engine/transform" require_relative "cyberarm_engine/transform"
require_relative "cyberarm_engine/ray" require_relative "cyberarm_engine/ray"
require_relative "cyberarm_engine/background" require_relative "cyberarm_engine/background"
require_relative "cyberarm_engine/background_nine_slice"
require_relative "cyberarm_engine/animator" require_relative "cyberarm_engine/animator"
require_relative "cyberarm_engine/text" require_relative "cyberarm_engine/text"
require_relative "cyberarm_engine/timer" require_relative "cyberarm_engine/timer"
require_relative "cyberarm_engine/config_file" require_relative "cyberarm_engine/config_file"
require_relative "cyberarm_engine/console"
require_relative "cyberarm_engine/console/command"
require_relative "cyberarm_engine/console/subcommand"
require_relative "cyberarm_engine/console/commands/help_command"
require_relative "cyberarm_engine/ui/dsl" require_relative "cyberarm_engine/ui/dsl"
require_relative "cyberarm_engine/ui/theme" require_relative "cyberarm_engine/ui/theme"
@@ -62,3 +67,5 @@ require_relative "cyberarm_engine/model/model_object"
require_relative "cyberarm_engine/model/parser" require_relative "cyberarm_engine/model/parser"
require_relative "cyberarm_engine/model/parsers/wavefront_parser" require_relative "cyberarm_engine/model/parsers/wavefront_parser"
require_relative "cyberarm_engine/model/parsers/collada_parser" if defined?(Nokogiri) require_relative "cyberarm_engine/model/parsers/collada_parser" if defined?(Nokogiri)
require_relative "cyberarm_engine/builtin/intro_state"

View File

@@ -1,11 +1,11 @@
module CyberarmEngine module CyberarmEngine
class Animator class Animator
DEFAULT_TWEEN = :linear def initialize(start_time:, duration:, from:, to:, tween: :linear, &block)
def initialize(start_time:, duration:, from:, to:, &block)
@start_time = start_time @start_time = start_time
@duration = duration @duration = duration
@from = from.dup @from = from.dup
@to = to.dup @to = to.dup
@tween = tween
@block = block @block = block
end end
@@ -14,18 +14,18 @@ module CyberarmEngine
end end
def progress def progress
(@start_time.to_f + (Gosu.milliseconds - @start_time)) / (@start_time + @duration.to_f) ((Gosu.milliseconds - @start_time) / @duration.to_f).clamp(0.0, 1.0)
end end
def complete? def complete?
progress >= 1.0 progress >= 1.0
end end
def transition(from, to, tween = DEFAULT_TWEEN) def transition(from = @from, to = @to, tween = @tween)
from + (to - from) * send("tween_#{tween}", progress) from + (to - from) * send("tween_#{tween}", progress)
end end
def color_transition(from, to, _tween = DEFAULT_TWEEN) def color_transition(from = @from, to = @to, _tween = @tween)
r = transition(from.red, to.red) r = transition(from.red, to.red)
g = transition(from.green, to.green) g = transition(from.green, to.green)
b = transition(from.blue, to.blue) b = transition(from.blue, to.blue)
@@ -34,7 +34,7 @@ module CyberarmEngine
Gosu::Color.rgba(r, g, b, a) Gosu::Color.rgba(r, g, b, a)
end end
def color_hsv_transition(from, to, tween = DEFAULT_TWEEN) def color_hsv_transition(from = @from, to = @to, tween = @tween)
hue = transition(from.hue, to.hue, tween) hue = transition(from.hue, to.hue, tween)
saturation = transition(from.saturation, to.saturation, tween) saturation = transition(from.saturation, to.saturation, tween)
value = transition(from.value, to.value, tween) value = transition(from.value, to.value, tween)
@@ -43,14 +43,177 @@ module CyberarmEngine
Gosu::Color.from_ahsv(alpha, hue, saturation, value) Gosu::Color.from_ahsv(alpha, hue, saturation, value)
end end
# NOTE: Use this for future reference? https://github.com/danro/easing-js/blob/master/easing.js # Tween functions based on those provided here: https://github.com/danro/easing-js/blob/master/easing.js
# Under MIT / BSD
def tween_linear(t) def tween_linear(t)
t t
end end
def tween_sine(t) def tween_ease_in_quad(t)
Math.sin(t) * t t ** 2
end
def tween_ease_out_quad(t)
-((t - 1) ** 2) -1
end
def tween_ease_in_out_quad(t)
return 0.5 * (t ** 2) if (t /= 0.5) < 1
return -0.5 * ((t -= 2) * t - 2)
end
def tween_ease_in_cubic(t)
t ** 3
end
def tween_ease_out_cubic(t)
((t - 1) ** 3) + 1
end
def tween_ease_in_out_cubic(t)
return 0.5 * (t ** 3) if ((t /= 0.5) < 1)
return 0.5 * ((t - 2) ** 3) + 2
end
def tween_ease_in_quart(t)
t ** 4
end
def tween_ease_out_quart(t)
-((t - 1) ** 4) - 1
end
def tween_ease_in_out_quart(t)
return 0.5 * (t ** 4) if ((t /= 0.5) < 1)
return -0.5 * ((t -= 2) * (t ** 3) - 2)
end
def tween_ease_in_quint(t)
t ** 5
end
def tween_ease_out_quint(t)
((t - 1) ** 5) + 1
end
def tween_ease_in_out_quint(t)
return 0.5 * (t ** 5) if ((t /= 0.5) < 1)
return 0.5 * ((t - 2) ** 5) + 2
end
def tween_ease_in(t) # sine
-Math.cos(t * (Math::PI / 2)) + 1
end
def tween_ease_out(t) # sine
Math.sin(t * (Math::PI / 2))
end
def tween_ease_in_out(t) # sine
(-0.5 * (Math.cos(Math::PI * t) - 1))
end
def tween_ease_in_expo(t)
(t == 0) ? 0 : 2 ** 10 * (t - 1)
end
def tween_ease_out_expo(t)
(t == 1) ? 1 : -(2 ** -10 * t) + 1
end
def tween_ease_in_out_expo(t)
return 0 if (t == 0)
return 1 if (t == 1)
return 0.5 * (2 ** 10 * (t - 1)) if ((t /= 0.5) < 1)
return 0.5 * (-(2 ** -10 * (t -= 1)) + 2)
end
def tween_ease_in_circ(t)
-(Math.sqrt(1 - (t * t)) - 1)
end
def tween_ease_out_circ(t)
Math.sqrt(1 - ((t - 1) ** 2))
end
def tween_ease_in_out_circ(t)
return -0.5 * (Math.sqrt(1 - t * t) - 1) if ((t /= 0.5) < 1)
return 0.5 * (Math.sqrt(1 - (t -= 2) * t) + 1)
end
def tween_ease_in_back(t)
s = 1.70158
t * t * ((s + 1) * t - s)
end
def tween_ease_out_back(t)
s = 1.70158
(t = t - 1) * t * ((s + 1) * t + s) + 1
end
def tween_ease_in_out_back(t)
s = 1.70158
return 0.5 * (t * t * (((s *= (1.525)) + 1) * t - s)) if ((t /= 0.5) < 1)
return 0.5 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2)
end
def tween_elastic(t)
-1 * (4 ** (-8 * t)) * Math.sin((t * 6 - 1) * (2 * Math::PI) / 2) + 1
end
def tween_swing_from_to(t)
s = 1.70158
return 0.5 * (t * t * (((s *= (1.525)) + 1) * t - s)) if (t /= 0.5) < 1
return 0.5 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2)
end
def tween_swing_from(t)
s = 1.70158;
t * t * ((s + 1) * t - s)
end
def tween_swing_to(t)
s = 1.70158
(t -= 1) * t * ((s + 1) * t + s) + 1
end
def tween_bounce(t)
if (t < (1 / 2.75))
(7.5625 * t * t)
elsif (t < (2 / 2.75))
(7.5625 * (t -= (1.5 / 2.75)) * t + 0.75)
elsif (t < (2.5 / 2.75))
(7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375)
else
(7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375)
end
end
def tween_bounce_past(t)
if (t < (1 / 2.75))
# missing "2 -"?
(7.5625 * t * t)
elsif (t < (2 / 2.75))
2 - (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75)
elsif (t < (2.5 / 2.75))
2 - (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375)
else
2 - (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375)
end
end
def tween_ease_from_to(t)
return 0.5 * (t ** 4) if ((t /= 0.5) < 1)
return -0.5 * ((t -= 2) * (t ** 3) - 2)
end
def tween_ease_from(t)
t ** 4
end
def tween_ease_to(t)
t ** 0.25
end end
end end
end end

View File

@@ -1,10 +1,11 @@
module CyberarmEngine module CyberarmEngine
class BackgroundNineSlice class BackgroundNineSlice
include CyberarmEngine::Common include CyberarmEngine::Common
attr_accessor :x, :y, :z, :width, :height attr_accessor :x, :y, :z, :width, :height, :left, :top, :right, :bottom, :mode, :color
attr_reader :image
def initialize(image_path:, x: 0, y: 0, z: 0, width: 64, height: 64, mode: :tiled, left: 4, top: 4, right: 56, bottom: 56) def initialize(image_path: nil, x: 0, y: 0, z: 0, width: 0, height: 0, mode: :tiled, left: 1, top: 1, right: 1, bottom: 1, color: Gosu::Color::WHITE)
@image = get_image(image_path) @image = get_image(image_path) if image_path
@x = x @x = x
@y = y @y = y
@@ -20,23 +21,33 @@ module CyberarmEngine
@right = right @right = right
@bottom = bottom @bottom = bottom
nine_slice @color = color
nine_slice if @image
end
def image=(image_path)
old_image = @image
@image = image_path ? get_image(image_path) : image_path
nine_slice if @image && old_image != @image
end end
def nine_slice def nine_slice
@segment_top_left = Gosu.render(@left, @top) { @image.draw(0, 0, 0) } # pp [@left, @top, @right, @bottom, @image.width]
@segment_top_right = Gosu.render(@image.width - @right, @top) { @image.draw(-@right, 0, 0) }
@segment_left = Gosu.render(@left, @bottom - @top) { @image.draw(0, -@top, 0) } @segment_top_left = @image.subimage(0, 0, @left, @top)
@segment_right = Gosu.render(@image.width - @right, @bottom - @top) { @image.draw(-@right, -@top, 0) } @segment_top_right = @image.subimage(@image.width - @right, 0, @right, @top)
@segment_bottom_left = Gosu.render(@left, @image.height - @bottom) { @image.draw(0, -@bottom, 0) } @segment_left = @image.subimage(0, @top, @left, @image.height - (@top + @bottom))
@segment_bottom_right = Gosu.render(@image.width - @right, @image.height - @bottom) { @image.draw(-@right, -@bottom, 0) } @segment_right = @image.subimage(@image.width - @right, @top, @left, @image.height - (@top + @bottom))
@segment_top = Gosu.render(@right - @left, @top) { @image.draw(-@left, 0, 0) } @segment_bottom_left = @image.subimage(0, @image.height - @bottom, @left, @bottom)
@segment_bottom = Gosu.render(@right - @left, @image.height - @bottom) { @image.draw(-@left, -@bottom, 0) } @segment_bottom_right = @image.subimage(@image.width - @right, @image.height - @bottom, @right, @bottom)
@segment_middle = Gosu.render(@right - @left, @bottom - @top) { @image.draw(-@left, -@top, 0) } @segment_top = @image.subimage(@left, 0, @image.width - (@left + @right), @top)
@segment_bottom = @image.subimage(@left, @image.height - @bottom, @image.width - (@left + @right), @bottom)
@segment_middle = @image.subimage(@left, @top, @image.width - (@left + @right), @image.height - (@top + @bottom))
end end
def cx def cx
@@ -56,67 +67,73 @@ module CyberarmEngine
end end
def width_scale def width_scale
width_scale = (@width - (@left + (@image.width - @right))).to_f / (@right - @left) scale = (@width.to_f - (@left + @right)) / (@image.width - (@left + @right))
scale.abs
end end
def height_scale def height_scale
height_scale = (@height - (@top + (@image.height - @bottom))).to_f / (@bottom - @top) scale = (@height - (@top + @bottom)).to_f / (@image.height - (@top + @bottom))
scale.abs
end end
def draw def draw
return unless @image && @segment_top_left
@mode == :tiled ? draw_tiled : draw_stretched @mode == :tiled ? draw_tiled : draw_stretched
end end
def draw_stretched def draw_stretched
@segment_top_left.draw(@x, @y, @z) @segment_top_left.draw(@x, @y, @z, 1, 1, @color)
@segment_top.draw(@x + @segment_top_left.width, @y, @z, width_scale) # SCALE X @segment_top.draw(@x + @segment_top_left.width, @y, @z, width_scale, 1, @color) # SCALE X
@segment_top_right.draw((@x + @width) - @segment_top_right.width, @y, @z) @segment_top_right.draw((@x + @width) - @segment_top_right.width, @y, @z, 1, 1, @color)
@segment_right.draw((@x + @width) - @segment_right.width, @y + @top, @z, 1, height_scale) # SCALE Y @segment_right.draw((@x + @width) - @segment_right.width, @y + @top, @z, 1, height_scale, @color) # SCALE Y
@segment_bottom_right.draw((@x + @width) - @segment_bottom_right.width, @y + @height - @segment_bottom_right.height, @z) @segment_bottom_right.draw((@x + @width) - @segment_bottom_right.width, @y + @height - @segment_bottom_right.height, @z, 1, 1, @color)
@segment_bottom.draw(@x + @segment_bottom_left.width, (@y + @height) - @segment_bottom.height, @z, width_scale) # SCALE X @segment_bottom.draw(@x + @segment_bottom_left.width, (@y + @height) - @segment_bottom.height, @z, width_scale, 1, @color) # SCALE X
@segment_bottom_left.draw(@x, (@y + @height) - @segment_bottom_left.height, @z) @segment_bottom_left.draw(@x, (@y + @height) - @segment_bottom_left.height, @z, 1, 1, @color)
@segment_left.draw(@x, @y + @top, @z, 1, height_scale) # SCALE Y @segment_left.draw(@x, @y + @top, @z, 1, height_scale, @color) # SCALE Y
@segment_middle.draw(@x + @segment_top_left.width, @y + @segment_top.height, @z, width_scale, height_scale) # SCALE X and SCALE Y @segment_middle.draw(@x + @segment_top_left.width, @y + @segment_top.height, @z, width_scale, height_scale, @color) # SCALE X and SCALE Y
end end
def draw_tiled def draw_tiled
@segment_top_left.draw(@x, @y, @z) @segment_top_left.draw(@x, @y, @z, 1, 1, @color)
# p [width_scale, height_scale]
Gosu.clip_to(@x + @segment_top_left.width, @y, @segment_top.width * width_scale, @segment_top.height) do Gosu.clip_to(@x + @segment_top_left.width, @y, @segment_top.width * width_scale, @segment_top.height) do
width_scale.ceil.times do |i| width_scale.ceil.times do |i|
@segment_top.draw(@x + @segment_top_left.width + (@segment_top.width * i), @y, @z) # SCALE X @segment_top.draw(@x + @segment_top_left.width + (@segment_top.width * i), @y, @z, 1, 1, @color) # SCALE X
end end
end end
@segment_top_right.draw((@x + @width) - @segment_top_right.width, @y, @z) @segment_top_right.draw((@x + @width) - @segment_top_right.width, @y, @z, 1, 1, @color)
Gosu.clip_to(@x + @width - @segment_top_right.width, @y + @top, @segment_right.width, @segment_right.height * height_scale) do Gosu.clip_to(@x + @width - @segment_top_right.width, @y + @top, @segment_right.width, @segment_right.height * height_scale) do
height_scale.ceil.times do |i| height_scale.ceil.times do |i|
@segment_right.draw((@x + @width) - @segment_right.width, @y + @top + (@segment_right.height * i), @z) # SCALE Y @segment_right.draw((@x + @width) - @segment_right.width, @y + @top + (@segment_right.height * i), @z, 1, 1, @color) # SCALE Y
end end
end end
@segment_bottom_right.draw((@x + @width) - @segment_bottom_right.width, @y + @height - @segment_bottom_right.height, @z) @segment_bottom_right.draw((@x + @width) - @segment_bottom_right.width, @y + @height - @segment_bottom_right.height, @z, 1, 1, @color)
Gosu.clip_to(@x + @segment_top_left.width, @y + @height - @segment_bottom.height, @segment_top.width * width_scale, @segment_bottom.height) do Gosu.clip_to(@x + @segment_top_left.width, @y + @height - @segment_bottom.height, @segment_top.width * width_scale, @segment_bottom.height) do
width_scale.ceil.times do |i| width_scale.ceil.times do |i|
@segment_bottom.draw(@x + @segment_bottom_left.width + (@segment_bottom.width * i), (@y + @height) - @segment_bottom.height, @z) # SCALE X @segment_bottom.draw(@x + @segment_bottom_left.width + (@segment_bottom.width * i), (@y + @height) - @segment_bottom.height, @z, 1, 1, @color) # SCALE X
end end
end end
@segment_bottom_left.draw(@x, (@y + @height) - @segment_bottom_left.height, @z) @segment_bottom_left.draw(@x, (@y + @height) - @segment_bottom_left.height, @z, 1, 1, @color)
Gosu.clip_to(@x, @y + @top, @segment_left.width, @segment_left.height * height_scale) do Gosu.clip_to(@x, @y + @top, @segment_left.width, @segment_left.height * height_scale) do
height_scale.ceil.times do |i| height_scale.ceil.times do |i|
@segment_left.draw(@x, @y + @top + (@segment_left.height * i), @z) # SCALE Y @segment_left.draw(@x, @y + @top + (@segment_left.height * i), @z, 1, 1, @color) # SCALE Y
end end
end end
Gosu.clip_to(@x + @segment_top_left.width, @y + @segment_top.height, @width - (@segment_left.width + @segment_right.width), @height - (@segment_top.height + @segment_bottom.height)) do Gosu.clip_to(@x + @segment_top_left.width, @y + @segment_top.height, @width - (@segment_left.width + @segment_right.width), @height - (@segment_top.height + @segment_bottom.height)) do
height_scale.ceil.times do |y| height_scale.ceil.times do |y|
width_scale.ceil.times do |x| width_scale.ceil.times do |x|
@segment_middle.draw(@x + @segment_top_left.width + (@segment_middle.width * x), @y + @segment_top.height + (@segment_middle.height * y), @z) # SCALE X and SCALE Y @segment_middle.draw(@x + @segment_top_left.width + (@segment_middle.width * x), @y + @segment_top.height + (@segment_middle.height * y), @z, 1, 1, @color) # SCALE X and SCALE Y
end end
end end
end end

View File

@@ -0,0 +1,131 @@
module CyberarmEngine
class IntroState < CyberarmEngine::GameState
def setup
@display_width = 800
@display_height = 600
@title_size = 56
@caption_size = 24
@title = CyberarmEngine::Text.new("", size: @title_size, shadow_color: 0xaa_222222)
@caption = CyberarmEngine::Text.new("", size: @caption_size, shadow_color: 0xaa_222222)
@spacer_width = 256
@spacer_height = 6
@padding = 6
@cyberarm_engine_logo = get_image "#{CYBERARM_ENGINE_ROOT_PATH}/assets/textures/logo.png"
@gosu_logo = generate_proxy("Gosu", "Game Library", 0xff_111111)
@ruby_logo = generate_proxy("Ruby", "Programming Language", 0xff_880000)
@opengl_logo = generate_proxy("OpenGL", "Graphics API", 0xff_5586a4) if defined?(OpenGL)
base_time = Gosu.milliseconds
@born_time = Gosu.milliseconds
@continue_after = 5_000
@animators = [
Animator.new(start_time: base_time += 1000, duration: 100, from: 0.0, to: 1.0, tween: :ease_in_out),
Animator.new(start_time: base_time += -500, duration: 1_000, from: 0.0, to: 1.0, tween: :ease_in_out),
Animator.new(start_time: base_time += 500, duration: 1_000, from: 0.0, to: 1.0, tween: :ease_in_out),
Animator.new(start_time: base_time += 500, duration: 1_000, from: 0.0, to: 1.0, tween: :ease_in_out),
Animator.new(start_time: base_time + 500, duration: 1_000, from: 0.0, to: 1.0, tween: :ease_in_out),
Animator.new(start_time: Gosu.milliseconds + @continue_after - 1_000, duration: 1_000, from: 0.0, to: 1.0, tween: :ease_in_out),
Animator.new(start_time: Gosu.milliseconds + 250, duration: 500, from: 0.0, to: 1.0, tween: :swing_to) # CyberarmEngine LOGO
]
end
def draw
Gosu.draw_rect(0, 0, window.width, window.height, 0xff_222222)
scale = (@display_width - @padding * 2).to_f / @cyberarm_engine_logo.width * @animators.last.transition
@cyberarm_engine_logo.draw_rot(
window.width / 2,
(window.height) / 2 - @cyberarm_engine_logo.height / 2 - @padding * 2,
2,
0,
0.5,
0.5,
scale,
scale
)
Gosu.draw_rect(
window.width / 2 - (@display_width / 2 + @padding),
window.height / 2 - @spacer_height / 2,
@display_width + @padding,
@spacer_height * @animators[0].transition,
Gosu::Color::WHITE
)
@title.x = window.width / 2 - @title.text_width / 2
@title.y = (window.height / 2 + (@spacer_height / 2) + @padding) * @animators[1].transition
@title.text = "Powered By"
Gosu.clip_to(0, window.height / 2 + (@spacer_height / 2), window.width, @title.height) do
@title.draw
end
y = @title.y + @title.height * 2
Gosu.clip_to(0, y, window.width, @gosu_logo.height) do
Gosu.translate(@opengl_logo.nil? ? @ruby_logo.width / 2 : 0, 0) do
@gosu_logo.draw(
window.width.to_f / 2 - @ruby_logo.width / 2 - (@ruby_logo.width - @padding),
y * @animators[2].transition,
2
)
@ruby_logo.draw(
window.width.to_f / 2 - @ruby_logo.width / 2,
y * @animators[3].transition,
2
)
@opengl_logo&.draw(
window.width.to_f / 2 - @ruby_logo.width / 2 + (@ruby_logo.width - @padding),
y * @animators[4].transition,
2
)
end
end
Gosu.draw_rect(0, 0, window.width, window.height, Gosu::Color.rgba(0, 0, 0, 255 * @animators[5].transition), 10_000)
end
def update
@animators.each(&:update)
return unless Gosu.milliseconds - @born_time >= @continue_after
pop_state
push_state(@options[:forward], @options[:forward_options] || {}) if @options[:forward]
end
def button_down(_id)
@continue_after = 0
end
def generate_proxy(title, caption, color_hint)
@title.text = title
@caption.text = caption
width = @spacer_width + 2 * @padding
height = @title_size + @caption_size + @spacer_height + 2 * @padding + @spacer_height
Gosu.record(width.ceil, height.ceil) do
@title.x = (width - @padding * 2) / 2 - @title.text_width / 2
@title.y = @padding
@title.draw
Gosu.draw_rect(0, @padding + @title_size + @padding, @spacer_width, @spacer_height, Gosu::Color::WHITE)
Gosu.draw_rect(1, @padding + @title_size + @padding + 1, @spacer_width - 2, @spacer_height - 2, color_hint)
@caption.x = (width - @padding * 2) / 2 - @caption.text_width / 2
@caption.y = @padding + @title_size + @padding + @spacer_height + @padding
@caption.draw
end
end
end
end

View File

@@ -16,6 +16,10 @@ module CyberarmEngine
window.pop_state window.pop_state
end end
def shift_state
window.shift_state
end
def show_cursor def show_cursor
window.show_cursor window.show_cursor
end end
@@ -24,8 +28,8 @@ module CyberarmEngine
window.show_cursor = boolean window.show_cursor = boolean
end end
def draw_rect(x, y, width, height, color, z = 0) def draw_rect(x, y, width, height, color, z = 0, mode = :default)
Gosu.draw_rect(x, y, width, height, color, z) Gosu.draw_rect(x, y, width, height, color, z, mode)
end end
def fill(color, z = 0) def fill(color, z = 0)
@@ -70,6 +74,7 @@ module CyberarmEngine
else else
klass.new(path) klass.new(path)
end end
hash[path] = instance hash[path] = instance
asset = instance asset = instance
end end
@@ -92,5 +97,17 @@ module CyberarmEngine
def window def window
$window $window
end end
def control_down?
Gosu.button_down?(Gosu::KB_LEFT_CONTROL) || Gosu.button_down?(Gosu::KB_RIGHT_CONTROL)
end
def shift_down?
Gosu.button_down?(Gosu::KB_LEFT_SHIFT) || Gosu.button_down?(Gosu::KB_RIGHT_SHIFT)
end
def alt_down?
Gosu.button_down?(Gosu::KB_LEFT_ALT) || Gosu.button_down?(Gosu::KB_RIGHT_ALT)
end
end end
end end

View File

@@ -0,0 +1,248 @@
# frozen_string_literal: true
module CyberarmEngine
class Console
Z = 100_000
PADDING = 2
include Common
attr_reader :text_input
def initialize(font: Gosu.default_font_name)
@text_input = Gosu::TextInput.new
@width = window.width / 4 * 3
@height = window.height / 4 * 3
@input = Text.new("", x: 4, y: @height - (PADDING * 2), z: Console::Z + 1, font: font)
@input.y -= @input.height
@history = Text.new("", x: 4, z: Console::Z + 1, font: font, border: true, border_color: Gosu::Color::BLACK)
update_history_y
@command_history = []
@command_history_index = 0
@memory = ""
@background_color = Gosu::Color.rgba(0, 0, 0, 200)
@foreground_color = Gosu::Color.rgba(100, 100, 100, 100)
@input_color = Gosu::Color.rgba(100, 100, 100, 200)
@showing_cursor = false
@active_text_input = nil
@show_caret = true
@caret_last_change = Gosu.milliseconds
@caret_interval = 250
@caret_color = Gosu::Color::WHITE
@selection_color = Gosu::Color.new(0x5522ff22)
end
def draw
# Background/Border
draw_rect(0, 0, @width, @height, @background_color, Console::Z)
# Foregound/History
draw_rect(PADDING, PADDING, @width - (PADDING * 2), @height - (PADDING * 2), @foreground_color, Console::Z)
# Text bar
draw_rect(2, @input.y, @width - (PADDING * 2), @input.height, @input_color, Console::Z)
@history.draw
@input.draw
# Caret
if @show_caret
draw_rect(@input.x + caret_from_left, @input.y, Console::PADDING, @input.height, @caret_color, Console::Z + 2)
end
# Caret selection
if caret_start != caret_end
if caret_start < @text_input.selection_start
draw_rect(@input.x + caret_from_left, @input.y, caret_selection_width, @input.height, @selection_color, Console::Z)
else
draw_rect((@input.x + caret_from_left) - caret_selection_width, @input.y, caret_selection_width, @input.height, @selection_color, Console::Z)
end
end
end
def caret_from_left
return 0 if @text_input.caret_pos.zero?
@input.textobject.text_width(@text_input.text[0..@text_input.caret_pos - 1])
end
def caret_selection_width
@input.textobject.text_width(@text_input.text[caret_start..(caret_end - 1)])
end
def caret_pos
@text_input.caret_pos
end
def caret_start
@text_input.selection_start < @text_input.caret_pos ? @text_input.selection_start : @text_input.caret_pos
end
def caret_end
@text_input.selection_start > @text_input.caret_pos ? @text_input.selection_start : @text_input.caret_pos
end
def update
if Gosu.milliseconds - @caret_last_change >= @caret_interval
@caret_last_change = Gosu.milliseconds
@show_caret = !@show_caret
end
if @width != window.width || @height != @height
@width = window.width / 4 * 3
@height = window.height / 4 * 3
@input.y = @height - (PADDING * 2 + @input.height)
update_history_y
end
@input.text = @text_input.text
end
def button_down(id)
case id
when Gosu::KbEnter, Gosu::KbReturn
return unless @text_input.text.length.positive?
@history.text += "\n<c=999999>> #{@text_input.text}</c>"
@command_history << @text_input.text
@command_history_index = @command_history.size
update_history_y
handle_command
@text_input.text = ""
when Gosu::KbUp
@command_history_index -= 1
@command_history_index = 0 if @command_history_index.negative?
@text_input.text = @command_history[@command_history_index]
when Gosu::KbDown
@command_history_index += 1
if @command_history_index > @command_history.size - 1
@text_input.text = "" unless @command_history_index > @command_history.size
@command_history_index = @command_history.size
else
@text_input.text = @command_history[@command_history_index]
end
when Gosu::KbTab
split = @text_input.text.split(" ")
if !@text_input.text.end_with?(" ") && split.size == 1
list = abbrev_search(Console::Command.list_commands.map { |cmd| cmd.command.to_s }, @text_input.text)
if list.size == 1
@text_input.text = "#{list.first} "
elsif list.size.positive?
stdin("\n#{list.map { |cmd| Console::Style.highlight(cmd) }.join(', ')}")
end
elsif split.size.positive? && cmd = Console::Command.find(split.first)
cmd.autocomplete(self)
end
when Gosu::KbBacktick
# Remove backtick character from input
@text_input.text = if @text_input.text.size > 1
@text_input.text[0..@text_input.text.size - 2]
else
""
end
# Copy
when Gosu::KbC
if control_down? && shift_down?
@memory = @text_input.text[caret_start..caret_end - 1] if caret_start != caret_end
p @memory
elsif control_down?
@text_input.text = ""
end
# Paste
when Gosu::KbV
if control_down? && shift_down?
string = @text_input.text.chars.insert(caret_pos, @memory).join
_caret_pos = caret_pos
@text_input.text = string
@text_input.caret_pos = _caret_pos + @memory.length
@text_input.selection_start = _caret_pos + @memory.length
end
# Cut
when Gosu::KbX
if control_down? && shift_down?
@memory = @text_input.text[caret_start..caret_end - 1] if caret_start != caret_end
string = @text_input.text.chars
Array(caret_start..caret_end - 1).each_with_index do |i, j|
string.delete_at(i - j)
end
@text_input.text = string.join
end
# Delete word to left of caret
when Gosu::KbW
if control_down?
split = @text_input.text.split(" ")
split.delete(split.last)
@text_input.text = split.join(" ")
end
# Clear history
when Gosu::KbL
@history.text = "" if control_down?
end
end
def button_up(id)
end
def update_history_y
@history.y = @height - (PADDING * 2) - @input.height - (@history.text.lines.count * @history.textobject.height)
end
def handle_command
string = @text_input.text
split = string.split(" ")
command = split.first
arguments = split.length.positive? ? split[1..split.length - 1] : []
CyberarmEngine::Console::Command.use(command, arguments, self)
end
def abbrev_search(array, text)
return [] unless text.length.positive?
list = []
Abbrev.abbrev(array).each do |abbrev, value|
next unless abbrev&.start_with?(text)
list << value
end
list.uniq
end
def stdin(string)
@history.text += "\n#{string}"
update_history_y
end
def focus
@active_text_input = window.text_input
window.text_input = @text_input
@showing_cursor = window.needs_cursor
window.needs_cursor = true
@show_caret = true
@caret_last_change = Gosu.milliseconds
end
def blur
window.text_input = @active_text_input
window.needs_cursor = @showing_cursor
end
end
end

View File

@@ -0,0 +1,158 @@
# frozen_string_literal: true
module CyberarmEngine
class Console
module Style
def self.error(string)
"<c=ff5555>#{string}</c>"
end
def self.warn(string)
"<c=ff7700>#{string}</c>"
end
def self.notice(string)
"<c=55ff55>#{string}</c>"
end
def self.highlight(string, color = "5555ff")
"<c=#{color}>#{string}</c>"
end
end
class Command
def self.inherited(subclass)
@list ||= []
@commands ||= []
@list << subclass
end
def self.setup
@list ||= []
@commands = []
@list.each do |subclass|
cmd = subclass.new
if @commands.detect { |c| c.command == cmd.command }
raise "Command '#{cmd.command}' from '#{cmd.class}' already exists!"
end
@commands << cmd
end
end
def self.use(command, arguments, console)
found_command = @commands.detect { |cmd| cmd.command == command.to_sym }
if found_command
found_command.handle(arguments, console)
else
console.stdin("Command #{Style.error(command)} not found.")
end
end
def self.find(command)
@commands.detect { |cmd| cmd.command == command.to_sym }
end
def self.list_commands
@commands
end
def initialize
@store = {}
@subcommands = []
setup
end
def setup
end
def subcommand(command, type)
if @subcommands.detect { |subcmd| subcmd.command == command.to_sym }
raise "Subcommand '#{command}' for '#{self.command}' already exists!"
end
@subcommands << SubCommand.new(self, command, type)
end
def get(key)
@store[key]
end
def set(key, value)
@store[key] = value
end
def group
raise NotImplementedError
end
def command
raise NotImplementedError
end
def handle(arguments, console)
raise NotImplementedError
end
def autocomplete(console)
split = console.text_input.text.split(" ")
if @subcommands.size.positive?
if !console.text_input.text.end_with?(" ") && split.size == 2
list = console.abbrev_search(@subcommands.map { |cmd| cmd.command.to_s }, split.last)
if list.size == 1
console.text_input.text = "#{split.first} #{list.first} "
else
return unless list.size.positive?
console.stdin(list.map { |cmd| Console::Style.highlight(cmd) }.join(", ").to_s)
end
# List available options on subcommand
elsif (console.text_input.text.end_with?(" ") && split.size == 2) || !console.text_input.text.end_with?(" ") && split.size == 3
subcommand = @subcommands.detect { |cmd| cmd.command.to_s == (split[1]) }
if subcommand
if split.size == 2
console.stdin("Available options: #{subcommand.values.map { |value| Console::Style.highlight(value) }.join(',')}")
else
list = console.abbrev_search(subcommand.values, split.last)
if list.size == 1
console.text_input.text = "#{split.first} #{split[1]} #{list.first} "
elsif list.size.positive?
console.stdin("Available options: #{list.map { |value| Console::Style.highlight(value) }.join(',')}")
end
end
end
# List available subcommands if command was entered and has only a space after it
elsif console.text_input.text.end_with?(" ") && split.size == 1
console.stdin("Available subcommands: #{@subcommands.map { |cmd| Console::Style.highlight(cmd.command) }.join(', ')}")
end
end
end
def handle_subcommand(arguments, console)
if arguments.size.zero?
console.stdin(usage)
return
end
subcommand = arguments.delete_at(0)
found_command = @subcommands.detect { |cmd| cmd.command == subcommand.to_sym }
if found_command
found_command.handle(arguments, console)
else
console.stdin("Unknown subcommand #{Style.error(subcommand)} for #{Style.highlight(command)}")
end
end
def usage
raise NotImplementedError
end
end
end
end

View File

@@ -0,0 +1,43 @@
# frozen_string_literal: true
module CyberarmEngine
class Console
class HelpCommand < CyberarmEngine::Console::Command
def group
:global
end
def command
:help
end
def handle(arguments, console)
console.stdin(usage(arguments.first))
end
def autocomplete(console)
split = console.text_input.text.split(" ")
if !console.text_input.text.start_with?(" ") && split.size == 2
list = console.abbrev_search(Command.list_commands.map { |cmd| cmd.command.to_s }, split.last)
if list.size == 1
console.text_input.text = "#{split.first} #{list.first} "
elsif list.size > 1
console.stdin(list.map { |cmd| Style.highlight(cmd) }.join(", "))
end
end
end
def usage(command = nil)
if command
if cmd = Command.find(command)
cmd.usage
else
"#{Style.error(command)} is not a command"
end
else
"Available commands:\n#{Command.list_commands.map { |cmd| Style.highlight(cmd.command).to_s }.join(', ')}"
end
end
end
end
end

View File

@@ -0,0 +1,100 @@
# frozen_string_literal: true
module CyberarmEngine
class Console
class Command
class SubCommand
def initialize(parent, command, type)
@parent = parent
@command = command
@type = type
end
attr_reader :command
def handle(arguments, console)
if arguments.size > 1
console.stdin("to many arguments for #{Style.highlight(command.to_s)}, got #{Style.error(arguments.size)} expected #{Style.notice(1)}.")
return
end
case @type
when :boolean
case arguments.last
when "", nil
var = @parent.get(command.to_sym) || false
console.stdin("#{command}: #{Style.highlight(var)}")
when "on"
var = @parent.set(command.to_sym, true)
console.stdin("#{command} => #{Style.highlight(var)}")
when "off"
var = @parent.set(command.to_sym, false)
console.stdin("#{command} => #{Style.highlight(var)}")
else
console.stdin("Invalid argument for #{Style.highlight(command.to_s)}, got #{Style.error(arguments.last)} expected #{Style.notice('on')}, or #{Style.notice('off')}.")
end
when :string
case arguments.last
when "", nil
var = @parent.get(command.to_sym) || "\"\""
console.stdin("#{command}: #{Style.highlight(var)}")
else
var = @parent.set(command.to_sym, arguments.last)
console.stdin("#{command} => #{Style.highlight(var)}")
end
when :integer
case arguments.last
when "", nil
var = @parent.get(command.to_sym) || "nil"
console.stdin("#{command}: #{Style.highlight(var)}")
else
begin
var = @parent.set(command.to_sym, Integer(arguments.last))
console.stdin("#{command} => #{Style.highlight(var)}")
rescue ArgumentError
console.stdin("Error: #{Style.error("Expected an integer, got '#{arguments.last}'")}")
end
end
when :decimal
case arguments.last
when "", nil
var = @parent.get(command.to_sym) || "nil"
console.stdin("#{command}: #{Style.highlight(var)}")
else
begin
var = @parent.set(command.to_sym, Float(arguments.last))
console.stdin("#{command} => #{Style.highlight(var)}")
rescue ArgumentError
console.stdin("Error: #{Style.error("Expected a decimal or integer, got '#{arguments.last}'")}")
end
end
else
raise RuntimeError
end
end
def values
case @type
when :boolean
%w[on off]
else
[]
end
end
def usage
case @type
when :boolean
"#{Style.highlight(command)} #{Style.notice('[on|off]')}"
when :string
"#{Style.highlight(command)} #{Style.notice('[string]')}"
when :integer
"#{Style.highlight(command)} #{Style.notice('[0]')}"
when :decimal
"#{Style.highlight(command)} #{Style.notice('[0.0]')}"
end
end
end
end
end
end

View File

@@ -17,6 +17,11 @@ module CyberarmEngine
def setup def setup
end end
# Called immediately after setup returns.
# GuiState uses this to set current_theme for ToolTip
def post_setup
end
def draw def draw
@game_objects.each(&:draw) @game_objects.each(&:draw)
end end

View File

@@ -3,7 +3,8 @@ module CyberarmEngine
attr_accessor :objects, :materials, :vertices, :uvs, :texures, :normals, :faces, :colors, :bones, :material_file, attr_accessor :objects, :materials, :vertices, :uvs, :texures, :normals, :faces, :colors, :bones, :material_file,
:current_material, :current_object, :vertex_count, :smoothing :current_material, :current_object, :vertex_count, :smoothing
attr_reader :position, :bounding_box, :textured_material, :file_path, :positions_buffer_id, :colors_buffer_id, attr_reader :position, :bounding_box, :textured_material, :file_path, :positions_buffer_id, :colors_buffer_id,
:normals_buffer_id, :uvs_buffer_id, :textures_buffer_id, :vertex_array_id, :aabb_tree :normals_buffer_id, :uvs_buffer_id, :textures_buffer_id, :vertex_array_id, :aabb_tree,
:vertices_count
def initialize(file_path:) def initialize(file_path:)
@file_path = file_path @file_path = file_path
@@ -23,6 +24,8 @@ module CyberarmEngine
@bones = [] @bones = []
@smoothing = 0 @smoothing = 0
@vertices_count = 0
@bounding_box = BoundingBox.new @bounding_box = BoundingBox.new
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond) start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond)
@@ -32,6 +35,8 @@ module CyberarmEngine
parse(parser) parse(parser)
@vertices_count = @vertices.size
@has_texture = false @has_texture = false
@materials.each do |_key, material| @materials.each do |_key, material|

View File

@@ -3,8 +3,8 @@ module CyberarmEngine
attr_accessor :position, :orientation, :aspect_ratio, :field_of_view, attr_accessor :position, :orientation, :aspect_ratio, :field_of_view,
:min_view_distance, :max_view_distance :min_view_distance, :max_view_distance
def initialize(position:, aspect_ratio:, orientation: Vector.new(0, 0, def initialize(position:, aspect_ratio:, orientation: Vector.new(0, 0, 0),
0), field_of_view: 70.0, min_view_distance: 0.1, max_view_distance: 155.0) field_of_view: 70.0, min_view_distance: 0.1, max_view_distance: 1024.0)
@position = position @position = position
@orientation = orientation @orientation = orientation

View File

@@ -3,12 +3,15 @@ module CyberarmEngine
@@immediate_mode_warning = false @@immediate_mode_warning = false
attr_accessor :show_wireframe attr_accessor :show_wireframe
attr_reader :number_of_vertices
def initialize(width:, height:, show_wireframe: false) def initialize(width:, height:, show_wireframe: false)
@width = width @width = width
@height = height @height = height
@show_wireframe = show_wireframe @show_wireframe = show_wireframe
@number_of_vertices = 0
@g_buffer = GBuffer.new(width: @width, height: @height) @g_buffer = GBuffer.new(width: @width, height: @height)
end end
@@ -20,6 +23,8 @@ module CyberarmEngine
end end
def render(camera, lights, entities) def render(camera, lights, entities)
@number_of_vertices = 0
glViewport(0, 0, @width, @height) glViewport(0, 0, @width, @height)
glEnable(GL_DEPTH_TEST) glEnable(GL_DEPTH_TEST)
@@ -44,6 +49,8 @@ module CyberarmEngine
gl_error? gl_error?
draw_model(entity.model, shader) draw_model(entity.model, shader)
entity.draw entity.draw
@number_of_vertices += entity.model.vertices_count
end end
end end
@@ -90,6 +97,8 @@ module CyberarmEngine
draw_mesh(entity.model) draw_mesh(entity.model)
entity.draw entity.draw
glPopMatrix glPopMatrix
@number_of_vertices += entity.model.vertices_count
end end
end end

View File

@@ -54,7 +54,7 @@ module CyberarmEngine
texture_id = tex_names_buf.unpack1("L2") texture_id = tex_names_buf.unpack1("L2")
glBindTexture(GL_TEXTURE_2D, texture_id) glBindTexture(GL_TEXTURE_2D, texture_id)
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.width, image.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, array_of_pixels) glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB_ALPHA, image.width, image.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, array_of_pixels)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST) if @retro glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST) if @retro

View File

@@ -3,7 +3,9 @@ module CyberarmEngine
CACHE = {} CACHE = {}
attr_accessor :x, :y, :z, :size, :options attr_accessor :x, :y, :z, :size, :options
attr_reader :text, :textobject, :factor_x, :factor_y, :color, :shadow, :shadow_size, :shadow_alpha, :shadow_color attr_reader :text, :textobject, :factor_x, :factor_y, :color,
:border, :border_size, :border_alpha, :border_color,
:shadow, :shadow_size, :shadow_alpha, :shadow_color
def initialize(text, options = {}) def initialize(text, options = {})
@text = text.to_s || "" @text = text.to_s || ""
@@ -15,16 +17,25 @@ module CyberarmEngine
@z = options[:z] || 1025 @z = options[:z] || 1025
@factor_x = options[:factor_x] || 1 @factor_x = options[:factor_x] || 1
@factor_y = options[:factor_y] || 1 @factor_y = options[:factor_y] || 1
@color = options[:color] || Gosu::Color::WHITE if options[:color]
@color = options[:color].is_a?(Gosu::Color) ? options[:color] : Gosu::Color.new(options[:color])
else
@color = Gosu::Color::WHITE
end
@mode = options[:mode] || :default @mode = options[:mode] || :default
@alignment = options[:alignment] || nil @alignment = options[:alignment] || nil
@shadow = true if options[:shadow] == true
@shadow = false if options[:shadow] == false @border = options[:border]
@shadow = true if options[:shadow].nil? @border = true if options[:border].nil?
@shadow_size = options[:shadow_size] || 1 @border_size = options[:border_size] || 1
@shadow_alpha = options[:shadow_alpha] || 30 @border_alpha = options[:border_alpha] || 30
@border_color = options[:border_color]
@shadow = options[:shadow]
@shadow_size = options[:shadow_size] || 2
@shadow_alpha = options[:shadow_alpha] || 30 @shadow_alpha = options[:shadow_alpha] || 30
@shadow_color = options[:shadow_color] @shadow_color = options[:shadow_color]
@textobject = check_cache(@size, @font) @textobject = check_cache(@size, @font)
if @alignment if @alignment
@@ -37,8 +48,6 @@ module CyberarmEngine
@x = $window.width - BUTTON_PADDING - @textobject.text_width(@text) @x = $window.width - BUTTON_PADDING - @textobject.text_width(@text)
end end
end end
self
end end
def check_cache(size, font_name) def check_cache(size, font_name)
@@ -65,82 +74,110 @@ module CyberarmEngine
font font
end end
def swap_font(size, font_name = @font)
if @size != size || @font != font_name
@size = size
@font = font_name
@textobject = check_cache(size, font_name)
end
end
def text=(string) def text=(string)
@rendered_shadow = nil @rendered_border = nil
@text = string @text = string
end end
def factor_x=(n) def factor_x=(n)
@rendered_shadow = nil @rendered_border = nil
@factor_x = n @factor_x = n
end end
def factor_y=(n) def factor_y=(n)
@rendered_shadow = nil @rendered_border = nil
@factor_y = n @factor_y = n
end end
def color=(color) def color=(color)
@rendered_shadow = nil @rendered_border = nil
@color = color if color
@color = color.is_a?(Gosu::Color) ? color : Gosu::Color.new(color)
else
raise "color cannot be nil"
end
end end
def shadow=(boolean) def border=(boolean)
@rendered_shadow = nil @rendered_border = nil
@shadow = boolean @border = boolean
end end
def shadow_size=(n) def border_size=(n)
@rendered_shadow = nil @rendered_border = nil
@shadow_size = n @border_size = n
end end
def shadow_alpha=(n) def border_alpha=(n)
@rendered_shadow = nil @rendered_border = nil
@shadow_alpha = n @border_alpha = n
end end
def shadow_color=(n) def border_color=(n)
@rendered_shadow = nil @rendered_border = nil
@shadow_color = n @border_color = n
end end
def width(text = @text) def width(text = @text)
textobject.text_width(text) markup_width(text)
end
def text_width(text = @text)
textobject.text_width(text) + @border_size + @shadow_size
end end
def markup_width(text = @text) def markup_width(text = @text)
textobject.markup_width(text) textobject.markup_width(text) + @border_size + @shadow_size
end end
def height(text = @text) def height(text = @text)
text.lines.count > 0 ? text.lines.count * textobject.height : @textobject.height if text.lines.count > 0
text.lines.count * textobject.height + @border_size + @shadow_size
else
@textobject.height + @border_size + @shadow_size
end
end end
def draw(method = :draw_markup) def draw(method = :draw_markup)
if @shadow && !ARGV.join.include?("--no-shadow") if @border && !ARGV.join.include?("--no-border")
shadow_alpha = @color.alpha <= 30 ? @color.alpha : @shadow_alpha border_alpha = @color.alpha <= 30 ? @color.alpha : @border_alpha
shadow_color = @shadow_color || Gosu::Color.rgba(@color.red, @color.green, @color.blue, border_color = @border_color || Gosu::Color.rgba(@color.red, @color.green, @color.blue,
shadow_alpha) border_alpha)
white = Gosu::Color::WHITE white = Gosu::Color::WHITE
_x = @shadow_size _x = @border_size
_y = @shadow_size _y = @border_size
_width = method == :draw_markup ? text_width : markup_width
@rendered_shadow ||= Gosu.render((width + (shadow_size * 2)).ceil, (height + (@shadow_size * 2)).ceil) do @rendered_border ||= Gosu.render((_width + (border_size * 2)).ceil, (height + (@border_size * 2)).ceil) do
@textobject.send(method, @text, _x - @shadow_size, _y, @z, @factor_x, @factor_y, white, :add) @textobject.send(method, @text, _x - @border_size, _y, @z, @factor_x, @factor_y, white, @mode)
@textobject.send(method, @text, _x - @shadow_size, _y - @shadow_size, @z, @factor_x, @factor_y, white, :add) @textobject.send(method, @text, _x - @border_size, _y - @border_size, @z, @factor_x, @factor_y, white, @mode)
@textobject.send(method, @text, _x, _y - @shadow_size, @z, @factor_x, @factor_y, white, :add) @textobject.send(method, @text, _x, _y - @border_size, @z, @factor_x, @factor_y, white, @mode)
@textobject.send(method, @text, _x + @shadow_size, _y - @shadow_size, @z, @factor_x, @factor_y, white, :add) @textobject.send(method, @text, _x + @border_size, _y - @border_size, @z, @factor_x, @factor_y, white, @mode)
@textobject.send(method, @text, _x, _y + @shadow_size, @z, @factor_x, @factor_y, white, :add) @textobject.send(method, @text, _x, _y + @border_size, @z, @factor_x, @factor_y, white, @mode)
@textobject.send(method, @text, _x - @shadow_size, _y + @shadow_size, @z, @factor_x, @factor_y, white, :add) @textobject.send(method, @text, _x - @border_size, _y + @border_size, @z, @factor_x, @factor_y, white, @mode)
@textobject.send(method, @text, _x + @shadow_size, _y, @z, @factor_x, @factor_y, white, :add) @textobject.send(method, @text, _x + @border_size, _y, @z, @factor_x, @factor_y, white, @mode)
@textobject.send(method, @text, _x + @shadow_size, _y + @shadow_size, @z, @factor_x, @factor_y, white, :add) @textobject.send(method, @text, _x + @border_size, _y + @border_size, @z, @factor_x, @factor_y, white, @mode)
end end
@rendered_shadow.draw(@x - @shadow_size, @y - @shadow_size, @z, @factor_x, @factor_y, shadow_color)
@rendered_border.draw(@x - @border_size, @y - @border_size, @z, @factor_x, @factor_y, border_color)
end
if @shadow
shadow_color = @shadow_color || Gosu::Color.rgba(@color.red, @color.green, @color.blue, @shadow_alpha)
@textobject.send(method, @text, @x + @shadow_size, @y + @shadow_size, @z, @factor_x, @factor_y, shadow_color, @mode)
end end
@textobject.send(method, @text, @x, @y, @z, @factor_x, @factor_y, @color, @mode) @textobject.send(method, @text, @x, @y, @z, @factor_x, @factor_y, @color, @mode)

View File

@@ -23,7 +23,8 @@ module CyberarmEngine
"Tagline", "Tagline",
"Caption", "Caption",
"Para", "Para",
"Inscription" "Inscription",
"Link"
].each do |const| ].each do |const|
define_method(:"#{const.downcase}") do |text, options = {}, &block| define_method(:"#{const.downcase}") do |text, options = {}, &block|
options[:parent] = element_parent options[:parent] = element_parent
@@ -97,7 +98,7 @@ module CyberarmEngine
end end
def background(color = Gosu::Color::NONE) def background(color = Gosu::Color::NONE)
element_parent.style.background = color element_parent.style.default[:background] = color
end end
def theme(theme) def theme(theme)

View File

@@ -4,7 +4,7 @@ module CyberarmEngine
include Event include Event
include Common include Common
attr_accessor :x, :y, :z, :enabled, :tip attr_accessor :x, :y, :z, :tip
attr_reader :parent, :options, :style, :event_handler, :background_canvas, :border_canvas attr_reader :parent, :options, :style, :event_handler, :background_canvas, :border_canvas
def initialize(options = {}, block = nil) def initialize(options = {}, block = nil)
@@ -18,6 +18,8 @@ module CyberarmEngine
@visible = @options[:visible].nil? ? true : @options[:visible] @visible = @options[:visible].nil? ? true : @options[:visible]
@tip = @options[:tip] || "" @tip = @options[:tip] || ""
@debug_color = @options[:debug_color].nil? ? Gosu::Color::RED : @options[:debug_color]
@style = Style.new(options) @style = Style.new(options)
@root ||= nil @root ||= nil
@@ -34,23 +36,36 @@ module CyberarmEngine
@style.height = default(:height) || nil @style.height = default(:height) || nil
@style.background_canvas = Background.new @style.background_canvas = Background.new
@style.border_canvas = BorderCanvas.new(element: self) @style.background_nine_slice_canvas = BackgroundNineSlice.new
@style.border_canvas = BorderCanvas.new(element: self)
@style_event = :default
stylize stylize
default_events default_events
root.gui_state.request_focus(self) if @options[:autofocus]
end end
def stylize def stylize
set_static_position set_static_position
set_border_thickness(@style.border_thickness)
set_padding(@style.padding) set_color
set_font
set_margin(@style.margin) set_padding
set_margin
set_background(@style.background) set_background
set_border_color(@style.border_color) set_background_nine_slice
set_border_thickness
set_border_color
end
def safe_style_fetch(*args)
@style.hash.dig(@style_event, *args) || @style.hash.dig(:default, *args) || default(*args)
end end
def set_static_position def set_static_position
@@ -58,47 +73,92 @@ module CyberarmEngine
@y = @style.y if @style.y != 0 @y = @style.y if @style.y != 0
end end
def set_background(background) def set_color
@style.background = background @style.color = safe_style_fetch(:color)
@style.background_canvas.background = background @text&.color = @style.color
end end
def set_border_thickness(border_thickness) def set_font
@style.border_thickness = border_thickness @text&.swap_font(safe_style_fetch(:text_size), safe_style_fetch(:font))
@style.border_thickness_left = default(:border_thickness_left) || @style.border_thickness
@style.border_thickness_right = default(:border_thickness_right) || @style.border_thickness
@style.border_thickness_top = default(:border_thickness_top) || @style.border_thickness
@style.border_thickness_bottom = default(:border_thickness_bottom) || @style.border_thickness
end end
def set_border_color(color) def set_background
@style.border_color = color @style.background = safe_style_fetch(:background)
@style.border_color_left = default(:border_color_left) || @style.border_color @style.background_canvas.background = @style.background
@style.border_color_right = default(:border_color_right) || @style.border_color
@style.border_color_top = default(:border_color_top) || @style.border_color
@style.border_color_bottom = default(:border_color_bottom) || @style.border_color
@style.border_canvas.color = color
end end
def set_padding(padding) def set_background_nine_slice
@style.padding = padding @style.background_nine_slice = safe_style_fetch(:background_nine_slice)
@style.padding_left = default(:padding_left) || @style.padding @style.background_nine_slice_mode = safe_style_fetch(:background_nine_slice_mode) || :stretch
@style.padding_right = default(:padding_right) || @style.padding @style.background_nine_slice_color = safe_style_fetch(:background_nine_slice_color) || Gosu::Color::WHITE
@style.padding_top = default(:padding_top) || @style.padding @style.background_nine_slice_canvas.color = @style.background_nine_slice_color
@style.padding_bottom = default(:padding_bottom) || @style.padding
@style.background_nine_slice_from_edge = safe_style_fetch(:background_nine_slice_from_edge)
@style.background_nine_slice_left = safe_style_fetch(:background_nine_slice_left) || @style.background_nine_slice_from_edge
@style.background_nine_slice_top = safe_style_fetch(:background_nine_slice_top) || @style.background_nine_slice_from_edge
@style.background_nine_slice_right = safe_style_fetch(:background_nine_slice_right) || @style.background_nine_slice_from_edge
@style.background_nine_slice_bottom = safe_style_fetch(:background_nine_slice_bottom) || @style.background_nine_slice_from_edge
end end
def set_margin(margin) def set_border_thickness
@style.margin = margin @style.border_thickness = safe_style_fetch(:border_thickness)
@style.margin_left = default(:margin_left) || @style.margin @style.border_thickness_left = safe_style_fetch(:border_thickness_left) || @style.border_thickness
@style.margin_right = default(:margin_right) || @style.margin @style.border_thickness_right = safe_style_fetch(:border_thickness_right) || @style.border_thickness
@style.margin_top = default(:margin_top) || @style.margin @style.border_thickness_top = safe_style_fetch(:border_thickness_top) || @style.border_thickness
@style.margin_bottom = default(:margin_bottom) || @style.margin @style.border_thickness_bottom = safe_style_fetch(:border_thickness_bottom) || @style.border_thickness
end
def set_border_color
@style.border_color = safe_style_fetch(:border_color)
@style.border_color_left = safe_style_fetch(:border_color_left) || @style.border_color
@style.border_color_right = safe_style_fetch(:border_color_right) || @style.border_color
@style.border_color_top = safe_style_fetch(:border_color_top) || @style.border_color
@style.border_color_bottom = safe_style_fetch(:border_color_bottom) || @style.border_color
@style.border_canvas.color = [
@style.border_color_top,
@style.border_color_right,
@style.border_color_bottom,
@style.border_color_left
]
end
def set_padding
@style.padding = safe_style_fetch(:padding)
@style.padding_left = safe_style_fetch(:padding_left) || @style.padding
@style.padding_right = safe_style_fetch(:padding_right) || @style.padding
@style.padding_top = safe_style_fetch(:padding_top) || @style.padding
@style.padding_bottom = safe_style_fetch(:padding_bottom) || @style.padding
end
def set_margin
@style.margin = safe_style_fetch(:margin)
@style.margin_left = safe_style_fetch(:margin_left) || @style.margin
@style.margin_right = safe_style_fetch(:margin_right) || @style.margin
@style.margin_top = safe_style_fetch(:margin_top) || @style.margin
@style.margin_bottom = safe_style_fetch(:margin_bottom) || @style.margin
end
def update_styles(event = :default)
old_width = width
old_height = height
@style_event = event
return if self.is_a?(ToolTip)
if old_width != width || old_height != height
(root&.gui_state || @gui_state).request_recalculate
else
stylize
end
end end
def default_events def default_events
@@ -116,11 +176,82 @@ module CyberarmEngine
event(:hover) event(:hover)
event(:leave) event(:leave)
event(:focus)
event(:blur) event(:blur)
event(:changed) event(:changed)
end end
def enter(_sender)
@focus = false unless window.button_down?(Gosu::MsLeft)
if !@enabled
update_styles(:disabled)
elsif @focus
update_styles(:active)
else
update_styles(:hover)
end
:handled
end
def left_mouse_button(_sender, _x, _y)
@focus = true
unless @enabled
update_styles(:disabled)
else
update_styles(:active)
end
window.current_state.focus = self
:handled
end
def released_left_mouse_button(sender, _x, _y)
enter(sender)
:handled
end
def clicked_left_mouse_button(_sender, _x, _y)
@block&.call(self) if @enabled && !self.is_a?(Container)
:handled
end
def leave(_sender)
if @enabled
update_styles
else
update_styles(:disabled)
end
:handled
end
def blur(_sender)
@focus = false
if @enabled
update_styles
else
update_styles(:disabled)
end
:handled
end
def enabled=(boolean)
@enabled = boolean
recalculate
@enabled
end
def enabled? def enabled?
@enabled @enabled
end end
@@ -150,6 +281,7 @@ module CyberarmEngine
return unless visible? return unless visible?
@style.background_canvas.draw @style.background_canvas.draw
@style.background_nine_slice_canvas.draw
@style.border_canvas.draw @style.border_canvas.draw
Gosu.clip_to(@x, @y, width, height) do Gosu.clip_to(@x, @y, width, height) do
@@ -157,6 +289,31 @@ module CyberarmEngine
end end
end end
def debug_draw
return if defined?(GUI_DEBUG_ONLY_ELEMENT) && self.class == GUI_DEBUG_ONLY_ELEMENT
Gosu.draw_line(
x, y, @debug_color,
x + outer_width, y, @debug_color,
Float::INFINITY
)
Gosu.draw_line(
x + outer_width, y, @debug_color,
x + outer_width, y + outer_height, @debug_color,
Float::INFINITY
)
Gosu.draw_line(
x + outer_width, y + outer_height, @debug_color,
x, y + outer_height, @debug_color,
Float::INFINITY
)
Gosu.draw_line(
x, outer_height, @debug_color,
x, y, @debug_color,
Float::INFINITY
)
end
def update def update
end end
@@ -227,35 +384,56 @@ module CyberarmEngine
end end
def scroll_width def scroll_width
@children.sum { |c| c.width } + noncontent_width @children.sum(&:outer_width)
end end
def scroll_height def scroll_height
@children.sum { |c| c.height } + noncontent_height if is_a?(CyberarmEngine::Element::Flow)
return 0 if @children.size.zero?
pairs_ = []
sorted_children_ = @children.sort_by(&:y)
a_ = []
y_position_ = sorted_children_.first.y
sorted_children_.each do |child|
unless child.y == y_position_
y_position_ = child.y
pairs_ << a_
a_ = []
end
a_ << child
end
pairs_ << a_ unless pairs_.last == a_
pairs_.sum { |pair| pair.map(&:outer_height).max } + @style.padding_bottom + @style.border_thickness_bottom
else
@children.sum(&:outer_height) + @style.padding_bottom + @style.border_thickness_bottom
end
end end
def max_scroll_width def max_scroll_width
scroll_width - width scroll_width - outer_width
end end
def max_scroll_height def max_scroll_height
scroll_height - height scroll_height - outer_height
end end
def dimensional_size(size, dimension) def dimensional_size(size, dimension)
raise "dimension must be either :width or :height" unless %i[width height].include?(dimension) raise "dimension must be either :width or :height" unless %i[width height].include?(dimension)
if size && size.is_a?(Numeric) if size.is_a?(Numeric) && size.between?(0.0, 1.0)
if size.between?(0.0, 1.0) (@parent.send(:"content_#{dimension}") * size).round - send(:"noncontent_#{dimension}").round
((@parent.send(:"content_#{dimension}") - send(:"noncontent_#{dimension}")) * size).round else
else size
size
end
end end
end end
def background=(_background) def background=(_background)
@style.background_canvas.background = (_background) @style.background_canvas.background = _background
update_background update_background
end end
@@ -267,10 +445,34 @@ module CyberarmEngine
@style.background_canvas.height = height @style.background_canvas.height = height
@style.background_canvas.update @style.background_canvas.update
update_background_nine_slice
@style.border_canvas.update @style.border_canvas.update
end end
def background_nine_slice=(_image_path)
@style.background_nine_slice_canvas.image = _image_path
update_background_nine_slice
end
def update_background_nine_slice
@style.background_nine_slice_canvas.x = @x
@style.background_nine_slice_canvas.y = @y
@style.background_nine_slice_canvas.z = @z
@style.background_nine_slice_canvas.width = width
@style.background_nine_slice_canvas.height = height
@style.background_nine_slice_canvas.mode = @style.background_nine_slice_mode
@style.background_nine_slice_canvas.color = @style.background_nine_slice_color
@style.background_nine_slice_canvas.left = @style.background_nine_slice_left
@style.background_nine_slice_canvas.top = @style.background_nine_slice_top
@style.background_nine_slice_canvas.right = @style.background_nine_slice_right
@style.background_nine_slice_canvas.bottom = @style.background_nine_slice_bottom
@style.background_nine_slice_canvas.image = @style.background_nine_slice
end
def root def root
return self if is_root? return self if is_root?
@@ -278,11 +480,9 @@ module CyberarmEngine
@root = parent @root = parent
loop do loop do
if @root.parent.nil? break unless @root&.parent
break
else @root = @root.parent
@root = @root.parent
end
end end
end end
@@ -293,6 +493,12 @@ module CyberarmEngine
@gui_state != nil @gui_state != nil
end end
def focus(_)
warn "#{self.class}#focus was not overridden!"
:handled
end
def recalculate def recalculate
raise "#{self.class}#recalculate was not overridden!" raise "#{self.class}#recalculate was not overridden!"
end end
@@ -311,5 +517,9 @@ module CyberarmEngine
def to_s def to_s
"#{self.class} x=#{x} y=#{y} width=#{width} height=#{height} value=#{value.is_a?(String) ? "\"#{value}\"" : value}" "#{self.class} x=#{x} y=#{y} width=#{width} height=#{height} value=#{value.is_a?(String) ? "\"#{value}\"" : value}"
end end
def inspect
to_s
end
end end
end end

View File

@@ -34,69 +34,6 @@ module CyberarmEngine
@text.draw @text.draw
end end
def enter(_sender)
@focus = false unless window.button_down?(Gosu::MsLeft)
if !@enabled
@style.background_canvas.background = @style.disabled[:background]
@text.color = @style.disabled[:color]
elsif @focus
@style.background_canvas.background = @style.active[:background]
@text.color = @style.active[:color]
else
@style.background_canvas.background = @style.hover[:background]
@text.color = @style.hover[:color]
end
:handled
end
def left_mouse_button(_sender, _x, _y)
@focus = true
unless @enabled
@style.background_canvas.background = @style.disabled[:background]
@text.color = @style.disabled[:color]
else
@style.background_canvas.background = @style.active[:background]
@text.color = @style.active[:color]
end
window.current_state.focus = self
:handled
end
def released_left_mouse_button(sender, _x, _y)
enter(sender)
:handled
end
def clicked_left_mouse_button(_sender, _x, _y)
@block.call(self) if @enabled && @block
:handled
end
def leave(_sender)
unless @enabled
@style.background_canvas.background = @style.disabled[:background]
@text.color = @style.disabled[:color]
else
@style.background_canvas.background = @style.background
@text.color = @style.color
end
:handled
end
def blur(_sender)
@focus = false
:handled
end
def recalculate def recalculate
unless @enabled unless @enabled
@style.background_canvas.background = @style.disabled[:background] @style.background_canvas.background = @style.disabled[:background]

View File

@@ -5,8 +5,11 @@ module CyberarmEngine
super(options, block) super(options, block)
options[:toggled] = options[:checked] options[:toggled] = options[:checked]
options[:parent] = self
@toggle_button = ToggleButton.new(options) @toggle_button = ToggleButton.new(options)
@label = TextBlock.new(text, options)
options[:parent] = self
@label = TextBlock.new(text, options)
@label.subscribe(:holding_left_mouse_button) do |sender, x, y| @label.subscribe(:holding_left_mouse_button) do |sender, x, y|
@toggle_button.left_mouse_button(sender, x, y) @toggle_button.left_mouse_button(sender, x, y)

View File

@@ -60,30 +60,13 @@ module CyberarmEngine
Gosu.clip_to(@x, @y, width, height) do Gosu.clip_to(@x, @y, width, height) do
@children.each(&:draw) @children.each(&:draw)
end end
end
if false # DEBUG def debug_draw
Gosu.flush super
Gosu.draw_line( @children.each do |child|
x, y, Gosu::Color::RED, child.debug_draw
x + outer_width, y, Gosu::Color::RED,
Float::INFINITY
)
Gosu.draw_line(
x + outer_width, y, Gosu::Color::RED,
x + outer_width, y + outer_height, Gosu::Color::RED,
Float::INFINITY
)
Gosu.draw_line(
x + outer_width, y + outer_height, Gosu::Color::RED,
x, y + outer_height, Gosu::Color::RED,
Float::INFINITY
)
Gosu.draw_line(
x, outer_height, Gosu::Color::RED,
x, y, Gosu::Color::RED,
Float::INFINITY
)
end end
end end
@@ -92,6 +75,8 @@ module CyberarmEngine
end end
def hit_element?(x, y) def hit_element?(x, y)
return unless hit?(x, y)
@children.reverse_each do |child| @children.reverse_each do |child|
next unless child.visible? next unless child.visible?
@@ -205,12 +190,13 @@ module CyberarmEngine
def mouse_wheel_up(sender, x, y) def mouse_wheel_up(sender, x, y)
return unless @style.scroll return unless @style.scroll
return if height < max_scroll_height
if @scroll_position.y < 0 if @scroll_position.y < 0
@scroll_position.y += @scroll_speed @scroll_position.y += @scroll_speed
@scroll_position.y = 0 if @scroll_position.y > 0 @scroll_position.y = 0 if @scroll_position.y > 0
recalculate # recalculate
root.gui_state.request_recalculate_for(self)
return :handled return :handled
end end
@@ -218,17 +204,35 @@ module CyberarmEngine
def mouse_wheel_down(sender, x, y) def mouse_wheel_down(sender, x, y)
return unless @style.scroll return unless @style.scroll
return if height < max_scroll_height
return unless height < scroll_height
if @scroll_position.y.abs < max_scroll_height if @scroll_position.y.abs < max_scroll_height
@scroll_position.y -= @scroll_speed @scroll_position.y -= @scroll_speed
@scroll_position.y = -max_scroll_height if @scroll_position.y.abs > max_scroll_height @scroll_position.y = -max_scroll_height if @scroll_position.y.abs > max_scroll_height
recalculate # recalculate
root.gui_state.request_recalculate_for(self)
return :handled return :handled
end end
end end
def scroll_top
@scroll_position.y
end
def scroll_top=(n)
n = 0 if n <= 0
@scroll_position.y = -n
if max_scroll_height.positive?
@scroll_position.y = -max_scroll_height if @scroll_position.y.abs > max_scroll_height
else
@scroll_position.y = 0
end
end
def value def value
@children.map { |c| c.class }.join(", ") @children.map { |c| c.class }.join(", ")
end end

View File

@@ -59,6 +59,8 @@ module CyberarmEngine
end end
def update def update
@style_event = :active if @focus
@text.text = if @type == :password @text.text = if @type == :password
default(:password_character) * @text_input.text.length default(:password_character) * @text_input.text.length
else else
@@ -194,28 +196,27 @@ module CyberarmEngine
:handled :handled
end end
def enter(_sender) def focus(sender)
if @focus super
@style.background_canvas.background = default(:active, :background)
@text.color = default(:active, :color) window.text_input = @text_input
else @text_input.caret_pos = @text_input.selection_start = @text_input.text.length
@style.background_canvas.background = default(:hover, :background)
@text.color = default(:hover, :color)
end
:handled :handled
end end
def leave(sender) def enter(sender)
super unless @focus _has_focus = @focus
super
@focus = _has_focus
:handled :handled
end end
def blur(_sender) def blur(_sender)
@focus = false super
@style.background_canvas.background = default(:background)
@text.color = default(:color)
window.text_input = nil window.text_input = nil
:handled :handled

View File

@@ -1,156 +0,0 @@
module CyberarmEngine
class Element
class TextBlock < Element
def initialize(text, options = {}, block = nil)
super(options, block)
@text = Text.new(
text, font: @options[:font], z: @z, color: @options[:color],
size: @options[:text_size], shadow: @options[:text_shadow],
shadow_size: @options[:text_shadow_size],
shadow_color: @options[:text_shadow_color]
)
@raw_text = text
end
def render
@text.draw
end
def clicked_left_mouse_button(_sender, _x, _y)
@block&.call(self) if @enabled
# return :handled
end
def recalculate
@width = 0
@height = 0
_width = dimensional_size(@style.width, :width)
_height = dimensional_size(@style.height, :height)
handle_text_wrapping(_width)
@width = _width || @text.width.round
@height = _height || @text.height.round
@text.y = @style.border_thickness_top + @style.padding_top + @y
@text.z = @z + 3
if (text_alignment = @options[:text_align])
case text_alignment
when :left
@text.x = @style.border_thickness_left + @style.padding_left + @x
when :center
@text.x = if @text.width <= outer_width
@x + outer_width / 2 - @text.width / 2
else # Act as left aligned
@style.border_thickness_left + @style.padding_left + @x
end
when :right
@text.x = @x + outer_width - (@text.width + @style.border_thickness_right + @style.padding_right)
end
end
update_background
end
def handle_text_wrapping(max_width)
max_width ||= @parent&.width
max_width ||= @x - (window.width + noncontent_width)
wrap_behavior = style.text_wrap
copy = @raw_text.to_s.dup
if max_width >= line_width(copy[0]) && line_width(copy) > max_width && wrap_behavior != :none
breaks = []
line_start = 0
line_end = copy.length
while line_start != copy.length
if line_width(copy[line_start...line_end]) > max_width
line_end = ((line_end - line_start) / 2.0)
elsif line_end < copy.length && line_width(copy[line_start...line_end + 1]) < max_width
# To small, grow!
# TODO: find a more efficient way
line_end += 1
else # FOUND IT!
entering_line_end = line_end.floor
max_reach = line_end.floor - line_start < 63 ? line_end.floor - line_start : 63
reach = 0
if wrap_behavior == :word_wrap
max_reach.times do |i|
reach = i
break if copy[line_end.floor - i].to_s.match(/[[:punct:]]| /)
end
puts "Max width: #{max_width}/#{line_width(@raw_text)} Reach: {#{reach}/#{max_reach}} Line Start: #{line_start}/#{line_end.floor} (#{copy.length}|#{@raw_text.length}) [#{entering_line_end}] '#{copy}' {#{copy[line_start...line_end]}}"
line_end = line_end.floor - reach + 1 if reach != max_reach # Add +1 to walk in front of punctuation
end
breaks << line_end.floor
line_start = line_end.floor
line_end = copy.length
break if entering_line_end == copy.length || reach == max_reach
end
end
breaks.each_with_index do |pos, index|
copy.insert(pos + index, "\n") if pos + index >= 0 && pos + index < copy.length
end
end
@text.text = copy
end
def line_width(text)
(@x + @text.textobject.markup_width(text) + noncontent_width)
end
def value
@raw_text
end
def value=(value)
@raw_text = value.to_s.chomp
old_width = width
old_height = height
recalculate
root.gui_state.request_recalculate if old_width != width || old_height != height
publish(:changed, self.value)
end
end
class Banner < TextBlock
end
class Title < TextBlock
end
class Subtitle < TextBlock
end
class Tagline < TextBlock
end
class Caption < TextBlock
end
class Para < TextBlock
end
class Inscription < TextBlock
end
# TODO: Remove in version 0.16.0+
class Label < TextBlock
end
end
end

View File

@@ -46,10 +46,18 @@ module CyberarmEngine
:handled :handled
end end
def clicked_left_mouse_button(_sender, _x, _y)
# @block&.call(self.value) if @enabled
:handled
end
def show_menu def show_menu
@menu.clear @menu.clear
@items.each do |item| @items.each do |item|
next if item == self.value
btn = Button.new( btn = Button.new(
item, item,
{ {
@@ -61,7 +69,7 @@ module CyberarmEngine
}, },
proc do proc do
self.choose = item self.choose = item
@block&.call(item) @block&.call(self.value)
end end
) )

View File

@@ -1,9 +1,16 @@
module CyberarmEngine module CyberarmEngine
class Element class Element
class Progress < Element class Progress < Element
attr_reader :type
def initialize(options = {}, block = nil) def initialize(options = {}, block = nil)
super(options, block) super(options, block)
@animation_speed = options[:animation_speed] || 3_000
@marquee_width = options[:marquee_width] || 0.25
@marquee_offset = 0
@marquee_animation_time = Gosu.milliseconds
@type = options[:type] || :linear
@fraction_background = Background.new(background: @style.fraction_background) @fraction_background = Background.new(background: @style.fraction_background)
self.value = options[:fraction] || 0.0 self.value = options[:fraction] || 0.0
end end
@@ -24,15 +31,45 @@ module CyberarmEngine
def update_background def update_background
super super
@fraction_background.x = @style.border_thickness_left + @style.padding_left + @x @fraction_background.x = (@style.border_thickness_left + @style.padding_left + @x) + @marquee_offset
@fraction_background.y = @style.border_thickness_top + @style.padding_top + @y @fraction_background.y = @style.border_thickness_top + @style.padding_top + @y
@fraction_background.z = @z @fraction_background.z = @z
@fraction_background.width = @width * @fraction @fraction_background.width = @width * (@type == :marquee ? @marquee_width : @fraction)
@fraction_background.height = @height @fraction_background.height = @height
@fraction_background.background = @style.fraction_background @fraction_background.background = @style.fraction_background
end end
def update
super
return unless @type == :marquee
marquee_width = @width * @marquee_width
range = @width + marquee_width
@marquee_offset = (@width * (Gosu.milliseconds - @marquee_animation_time) / @animation_speed) - marquee_width
@marquee_animation_time = Gosu.milliseconds if @marquee_offset > range
update_background
end
def type=(type)
@type = type
case type
when :linear
@marquee_offset = 0
when :marquee
@marquee_offset = 0
@marquee_animation_time = Gosu.milliseconds
else
raise ArgumentError, "Only types :linear and :marquee are supported"
end
update_background
end
def value def value
@fraction @fraction
end end

View File

@@ -8,7 +8,10 @@ module CyberarmEngine
text, font: @options[:font], z: @z, color: @options[:color], text, font: @options[:font], z: @z, color: @options[:color],
size: @options[:text_size], shadow: @options[:text_shadow], size: @options[:text_size], shadow: @options[:text_shadow],
shadow_size: @options[:text_shadow_size], shadow_size: @options[:text_shadow_size],
shadow_color: @options[:text_shadow_color] shadow_color: @options[:text_shadow_color],
border: @options[:text_border],
border_size: @options[:text_border_size],
border_color: @options[:text_border_color]
) )
@raw_text = text @raw_text = text
@@ -18,13 +21,13 @@ module CyberarmEngine
@text.draw @text.draw
end end
def clicked_left_mouse_button(_sender, _x, _y)
@block&.call(self) if @enabled
# return :handled
end
def recalculate def recalculate
unless @enabled
@text.color = @style.disabled[:color]
else
@text.color = @style.color
end
@width = 0 @width = 0
@height = 0 @height = 0
@@ -44,8 +47,8 @@ module CyberarmEngine
when :left when :left
@text.x = @style.border_thickness_left + @style.padding_left + @x @text.x = @style.border_thickness_left + @style.padding_left + @x
when :center when :center
@text.x = if @text.width <= outer_width @text.x = if @text.width <= width
@x + outer_width / 2 - @text.width / 2 @x + width / 2 - @text.width / 2
else # Act as left aligned else # Act as left aligned
@style.border_thickness_left + @style.padding_left + @x @style.border_thickness_left + @style.padding_left + @x
end end
@@ -58,46 +61,64 @@ module CyberarmEngine
end end
def handle_text_wrapping(max_width) def handle_text_wrapping(max_width)
max_width ||= @parent&.width max_width ||= @parent&.content_width
max_width ||= @x - (window.width + noncontent_width) max_width ||= @x - (window.width + noncontent_width)
wrap_behavior = style.text_wrap wrap_behavior = style.text_wrap
copy = @raw_text.to_s.dup copy = @raw_text.to_s.dup
if line_width(copy[0]) <= max_width && line_width(copy) > max_width && wrap_behavior != :none # Only perform text wrapping: if it is enabled, is possible to wrap, and text is too long to fit on one line
breaks = [] if wrap_behavior != :none && line_width(copy[0]) <= max_width && line_width(copy) > max_width
breaks = [] # list of indexes to insert a line break
line_start = 0 line_start = 0
line_end = copy.length line_end = copy.length
while line_start != copy.length stalled = false
if line_width(copy[line_start...line_end]) > max_width stalled_interations = 0
line_end = ((line_end - line_start) / 2.0) max_stalled_iterations = 10
line_end = 1.0 if line_end <= 1 checked_copy_length = line_width(copy[line_start..line_end])
elsif line_end < copy.length && line_width(copy[line_start...line_end + 1]) < max_width
# To small, grow!
# TODO: find a more efficient way
line_end += 1
else # FOUND IT! # find length of lines
entering_line_end = line_end.floor while line_width(copy[line_start..line_end]) > max_width && stalled_interations < max_stalled_iterations
max_reach = line_end.floor - line_start < 63 ? line_end.floor - line_start : 63 search_start = line_start
reach = 0 search_end = line_end
if wrap_behavior == :word_wrap # Perform a binary search to find length of line
max_reach.times do |i| while search_start < search_end
reach = i midpoint = ((search_start.to_f + search_end) / 2.0).floor
break if copy[line_end.floor - i].to_s.match(/[[:punct:]]| /)
if line_width(copy[line_start..midpoint]) > max_width
search_end = midpoint
else
search_start = midpoint + 1
end
end
if wrap_behavior == :word_wrap
word_search_end = search_end
failed = false
until(copy[word_search_end].to_s.match(/[[:punct:]]| /))
word_search_end -= 1
if word_search_end <= 1 || word_search_end < line_start
failed = true
break
end end
# puts "Max width: #{max_width}/#{line_width(@raw_text)} Reach: {#{reach}/#{max_reach}} Line Start: #{line_start}/#{line_end.floor} (#{copy.length}|#{@raw_text.length}) [#{entering_line_end}] '#{copy}' {#{copy[line_start...line_end]}}"
line_end = line_end.floor - reach + 1 if reach != max_reach # Add +1 to walk in front of punctuation
end end
breaks << line_end.floor line_start = failed ? search_end : word_search_end + 1 # walk in front of punctuation
line_start = line_end.floor else
line_end = copy.length line_start = search_end
break if entering_line_end == copy.length || reach == max_reach
end end
breaks << line_start
# Prevent locking up due to outer while loop text width < max_width check not being satisfied.
stalled = checked_copy_length == line_width(copy[line_start..line_end])
checked_copy_length = line_width(copy[line_start..line_end])
stalled_interations += 1 if stalled
stalled_interations = 0 unless stalled
end end
breaks.each_with_index do |pos, index| breaks.each_with_index do |pos, index|
@@ -152,5 +173,8 @@ module CyberarmEngine
class ToolTip < TextBlock class ToolTip < TextBlock
end end
class Link < TextBlock
end
end end
end end

View File

@@ -15,12 +15,19 @@ module CyberarmEngine
return unless enabled? return unless enabled?
return :handled if respond_to?(event) && (send(event, self, *args) == :handled) was_handled = false
was_handled = true if respond_to?(event) && (send(event, self, *args) == :handled)
@event_handler[event].reverse_each do |handler| @event_handler[event].reverse_each do |handler|
return :handled if handler.call(self, *args) == :handled if handler.call(self, *args) == :handled
was_handled = true
break
end
end end
return :handled if was_handled
parent.publish(event, *args) if parent parent.publish(event, *args) if parent
nil nil
end end

View File

@@ -25,13 +25,17 @@ module CyberarmEngine
@last_mouse_pos = nil @last_mouse_pos = nil
@dragging_element = nil @dragging_element = nil
@pending_recalculate_request = false @pending_recalculate_request = false
@pending_element_recalculate_requests = []
@tip = Element::ToolTip.new("", parent: @root_container, z: Float::INFINITY)
@menu = nil @menu = nil
@min_drag_distance = 0 @min_drag_distance = 0
@mouse_pos = Vector.new @mouse_pos = Vector.new
end end
def post_setup
@tip = Element::ToolTip.new("", parent: @root_container, z: Float::INFINITY, theme: current_theme)
end
# throws :blur event to focused element and sets GuiState focused element # throws :blur event to focused element and sets GuiState focused element
# Does NOT throw :focus event at element or set element as focused # Does NOT throw :focus event at element or set element as focused
def focus=(element) def focus=(element)
@@ -53,8 +57,15 @@ module CyberarmEngine
if @tip.value.length.positive? if @tip.value.length.positive?
Gosu.flush Gosu.flush
@tip.draw @tip.draw
end end
if defined?(GUI_DEBUG)
Gosu.flush
@root_container.debug_draw
end
end end
def update def update
@@ -64,6 +75,16 @@ module CyberarmEngine
@root_container.recalculate @root_container.recalculate
@pending_recalculate_request = false @pending_recalculate_request = false
@pending_element_recalculate_requests.clear # GUI has already been recalculated
end
@pending_element_recalculate_requests.each(&:recalculate)
if @pending_focus_request
@pending_focus_request = false
self.focus = @pending_focus_element
@pending_focus_element.publish(:focus)
end end
@menu&.update @menu&.update
@@ -87,10 +108,10 @@ module CyberarmEngine
if Vector.new(window.mouse_x, window.mouse_y) == @last_mouse_pos if Vector.new(window.mouse_x, window.mouse_y) == @last_mouse_pos
if @mouse_over && (Gosu.milliseconds - @mouse_moved_at) > tool_tip_delay if @mouse_over && (Gosu.milliseconds - @mouse_moved_at) > tool_tip_delay
@tip.value = @mouse_over.tip if @mouse_over @tip.value = @mouse_over.tip if @mouse_over
@tip.x = window.mouse_x - @tip.width / 2 @tip.x = window.mouse_x
@tip.x = 0 if @tip.x < 0 @tip.x = 0 if @tip.x < 0
@tip.x = window.width - @tip.width if @tip.x + @tip.width > window.width @tip.x = window.width - @tip.width if @tip.x + @tip.width > window.width
@tip.y = window.mouse_y - @tip.height @tip.y = window.mouse_y - (@tip.height + 5)
@tip.y = 0 if @tip.y < 0 @tip.y = 0 if @tip.y < 0
@tip.y = window.height - @tip.height if @tip.y + @tip.height > window.height @tip.y = window.height - @tip.height if @tip.y + @tip.height > window.height
@tip.update @tip.update
@@ -215,6 +236,18 @@ module CyberarmEngine
@pending_recalculate_request = true @pending_recalculate_request = true
end end
def request_recalculate_for(element)
# element is already queued
return if @pending_element_recalculate_requests.detect { |e| e == element }
@pending_element_recalculate_requests << element
end
def request_focus(element)
@pending_focus_request = true
@pending_focus_element = element
end
def show_menu(list_box) def show_menu(list_box)
@menu = list_box @menu = list_box
end end
@@ -222,5 +255,14 @@ module CyberarmEngine
def hide_menu def hide_menu
@menu = nil @menu = nil
end end
def to_s
# "#{self.class} children=#{@children.map { |c| c.to_s }}"
@root_container.to_s
end
def inspect
to_s
end
end end
end end

View File

@@ -17,8 +17,20 @@ end
module CyberarmEngine module CyberarmEngine
class Style class Style
attr_reader :hash
def initialize(hash = {}) def initialize(hash = {})
@hash = Marshal.load(Marshal.dump(hash)) h = Marshal.load(Marshal.dump(hash))
h[:default] = {}
h.each do |key, value|
next if value.is_a?(Hash)
h[:default][key] = value
end
@hash = h
end end
def method_missing(method, *args) def method_missing(method, *args)
@@ -27,9 +39,8 @@ module CyberarmEngine
@hash[method.to_s.sub("=", "").to_sym] = args.first @hash[method.to_s.sub("=", "").to_sym] = args.first
elsif args.size == 0 elsif args.empty?
@hash[method] @hash[method]
else else
raise ArgumentError, "Did not expect arguments" raise ArgumentError, "Did not expect arguments"
end end

View File

@@ -64,6 +64,10 @@ module CyberarmEngine
border_radius: 0 border_radius: 0
}, },
Container: { # < Element (Base class for Stack and Flow)
debug_color: Gosu::Color::YELLOW
},
Button: { # < Label Button: { # < Label
margin: 1, margin: 1,
padding: 4, padding: 4,
@@ -111,10 +115,14 @@ module CyberarmEngine
text_size: 28, text_size: 28,
text_wrap: :word_wrap, # :word_wrap, :break_word, :none text_wrap: :word_wrap, # :word_wrap, :break_word, :none
text_shadow: false, text_shadow: false,
text_border: false,
text_align: :left, text_align: :left,
font: "Arial", font: "Arial",
margin: 0, margin: 0,
padding: 2 padding: 2,
disabled: {
color: Gosu::Color.rgb(175, 175, 175),
}
}, },
Banner: { # < TextBlock Banner: { # < TextBlock
@@ -155,11 +163,28 @@ module CyberarmEngine
border_color: 0xffaaaaaa, border_color: 0xffaaaaaa,
background: 0xff404040 background: 0xff404040
}, },
Link: { # < TextBlock
color: Gosu::Color::BLUE,
border_thickness: 1,
border_bottom_color: Gosu::Color::BLUE,
hover: {
color: 0xff_ff00ff,
border_bottom_color: 0xff_ff00ff
},
active: {
color: 0xff_ff0000,
border_bottom_color: 0xff_ff0000
}
},
ToggleButton: { # < Button ToggleButton: { # < Button
checkmark: "" checkmark: ""
}, },
CheckBox: { # < Flow
text_wrap: :none
},
Progress: { # < Element Progress: { # < Element
width: 250, width: 250,
height: 36, height: 36,

View File

@@ -1,4 +1,4 @@
module CyberarmEngine module CyberarmEngine
NAME = "InDev".freeze NAME = "InDev".freeze
VERSION = "0.17.0".freeze VERSION = "0.19.1".freeze
end end

View File

@@ -77,10 +77,12 @@ module CyberarmEngine
if klass.instance_of?(klass.class) && defined?(klass.options) if klass.instance_of?(klass.class) && defined?(klass.options)
@states << klass @states << klass
klass.setup if options[:setup] klass.setup if options[:setup]
klass.post_setup if options[:setup]
else else
@states << klass.new(options) if child_of?(klass, GameState) @states << klass.new(options) if child_of?(klass, GameState)
@states << klass.new if child_of?(klass, Element::Container) @states << klass.new if child_of?(klass, Element::Container)
current_state.setup if current_state.instance_of?(klass) && options[:setup] current_state.setup if current_state.instance_of?(klass) && options[:setup]
current_state.post_setup if current_state.instance_of?(klass) && options[:setup]
end end
end end
@@ -93,7 +95,7 @@ module CyberarmEngine
end end
def previous_state def previous_state
if @states.size > 1 && state = @states[@states.size - 2] if @states.size > 1 && (state = @states[@states.size - 2])
state state
end end
end end
@@ -102,6 +104,10 @@ module CyberarmEngine
@states.pop @states.pop
end end
def shift_state
@states.shift
end
# Sourced from https://gist.github.com/ippa/662583 # Sourced from https://gist.github.com/ippa/662583
def draw_circle(cx, cy, r, z = 9999, color = Gosu::Color::GREEN, step = 10) def draw_circle(cx, cy, r, z = 9999, color = Gosu::Color::GREEN, step = 10)
0.step(360, step) do |a1| 0.step(360, step) do |a1|