Compare commits

24 Commits

Author SHA1 Message Date
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
22 changed files with 1093 additions and 152 deletions

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

@@ -23,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"

View File

@@ -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_ease_in_out(t) def tween_ease_in_quad(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)) (-0.5 * (Math.cos(Math::PI * t) - 1))
end 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

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

@@ -22,18 +22,19 @@ module CyberarmEngine
base_time = Gosu.milliseconds base_time = Gosu.milliseconds
@born_time = Gosu.milliseconds
@continue_after = 5_000
@animators = [ @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 += 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: 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: :ease_in_out) # CyberarmEngine LOGO Animator.new(start_time: Gosu.milliseconds + 250, duration: 500, from: 0.0, to: 1.0, tween: :swing_to) # CyberarmEngine LOGO
] ]
@born_time = Gosu.milliseconds
@continue_after = 5_000
end end
def draw def draw
@@ -60,7 +61,7 @@ module CyberarmEngine
Gosu::Color::WHITE Gosu::Color::WHITE
) )
@title.x = window.width / 2 - @title.width / 2 @title.x = window.width / 2 - @title.text_width / 2
@title.y = (window.height / 2 + (@spacer_height / 2) + @padding) * @animators[1].transition @title.y = (window.height / 2 + (@spacer_height / 2) + @padding) * @animators[1].transition
@title.text = "Powered By" @title.text = "Powered By"
@@ -89,6 +90,8 @@ module CyberarmEngine
) )
end end
end end
Gosu.draw_rect(0, 0, window.width, window.height, Gosu::Color.rgba(0, 0, 0, 255 * @animators[5].transition), 10_000)
end end
def update def update
@@ -112,14 +115,14 @@ module CyberarmEngine
height = @title_size + @caption_size + @spacer_height + 2 * @padding + @spacer_height height = @title_size + @caption_size + @spacer_height + 2 * @padding + @spacer_height
Gosu.record(width.ceil, height.ceil) do Gosu.record(width.ceil, height.ceil) do
@title.x = (width - @padding * 2) / 2 - @title.width / 2 @title.x = (width - @padding * 2) / 2 - @title.text_width / 2
@title.y = @padding @title.y = @padding
@title.draw @title.draw
Gosu.draw_rect(0, @padding + @title_size + @padding, @spacer_width, @spacer_height, Gosu::Color::WHITE) 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) Gosu.draw_rect(1, @padding + @title_size + @padding + 1, @spacer_width - 2, @spacer_height - 2, color_hint)
@caption.x = (width - @padding * 2) / 2 - @caption.width / 2 @caption.x = (width - @padding * 2) / 2 - @caption.text_width / 2
@caption.y = @padding + @title_size + @padding + @spacer_height + @padding @caption.y = @padding + @title_size + @padding + @spacer_height + @padding
@caption.draw @caption.draw
end end

View File

@@ -28,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)
@@ -97,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

@@ -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

@@ -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)
@@ -75,82 +84,100 @@ module CyberarmEngine
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

@@ -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)
@@ -36,6 +36,7 @@ module CyberarmEngine
@style.height = default(:height) || nil @style.height = default(:height) || nil
@style.background_canvas = Background.new @style.background_canvas = Background.new
@style.background_nine_slice_canvas = BackgroundNineSlice.new
@style.border_canvas = BorderCanvas.new(element: self) @style.border_canvas = BorderCanvas.new(element: self)
@style_event = :default @style_event = :default
@@ -50,10 +51,14 @@ module CyberarmEngine
def stylize def stylize
set_static_position set_static_position
set_color
set_font
set_padding set_padding
set_margin set_margin
set_background set_background
set_background_nine_slice
set_border_thickness set_border_thickness
set_border_color set_border_color
@@ -68,12 +73,36 @@ module CyberarmEngine
@y = @style.y if @style.y != 0 @y = @style.y if @style.y != 0
end end
def set_color
@style.color = safe_style_fetch(:color)
@text&.color = @style.color
end
def set_font
@text&.swap_font(safe_style_fetch(:text_size), safe_style_fetch(:font))
end
def set_background def set_background
@style.background = safe_style_fetch(:background) @style.background = safe_style_fetch(:background)
@style.background_canvas.background = @style.background @style.background_canvas.background = @style.background
end end
def set_background_nine_slice
@style.background_nine_slice = safe_style_fetch(:background_nine_slice)
@style.background_nine_slice_mode = safe_style_fetch(:background_nine_slice_mode) || :stretch
@style.background_nine_slice_color = safe_style_fetch(:background_nine_slice_color) || Gosu::Color::WHITE
@style.background_nine_slice_canvas.color = @style.background_nine_slice_color
@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
def set_border_thickness def set_border_thickness
@style.border_thickness = safe_style_fetch(:border_thickness) @style.border_thickness = safe_style_fetch(:border_thickness)
@@ -118,15 +147,18 @@ module CyberarmEngine
end end
def update_styles(event = :default) def update_styles(event = :default)
_style = @style.send(event) old_width = width
old_height = height
@style_event = event @style_event = event
if @text.is_a?(CyberarmEngine::Text) return if self.is_a?(ToolTip)
@text.color = _style&.dig(:color) || @style.default[:color]
@text.swap_font(_style&.dig(:text_size) || @style.default[:text_size], _style&.dig(:font) || @style.default[:font])
end
if old_width != width || old_height != height
(root&.gui_state || @gui_state).request_recalculate (root&.gui_state || @gui_state).request_recalculate
else
stylize
end
end end
def default_events def default_events
@@ -212,6 +244,14 @@ module CyberarmEngine
:handled :handled
end end
def enabled=(boolean)
@enabled = boolean
recalculate
@enabled
end
def enabled? def enabled?
@enabled @enabled
end end
@@ -241,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
@@ -343,32 +384,53 @@ 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
@@ -383,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?

View File

@@ -75,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?
@@ -192,7 +194,9 @@ module CyberarmEngine
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
@@ -206,7 +210,9 @@ module CyberarmEngine
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

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

@@ -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
@@ -19,6 +22,12 @@ module CyberarmEngine
end 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
@@ -38,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
@@ -52,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
# Perform a binary search to find length of line
while search_start < search_end
midpoint = ((search_start.to_f + search_end) / 2.0).floor
if line_width(copy[line_start..midpoint]) > max_width
search_end = midpoint
else
search_start = midpoint + 1
end
end
if wrap_behavior == :word_wrap if wrap_behavior == :word_wrap
max_reach.times do |i| word_search_end = search_end
reach = i failed = false
break if copy[line_end.floor - i].to_s.match(/[[:punct:]]| /)
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 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_start = failed ? search_end : word_search_end + 1 # walk in front of punctuation
line_end = line_end.floor - reach + 1 if reach != max_reach # Add +1 to walk in front of punctuation else
line_start = search_end
end end
breaks << line_end.floor breaks << line_start
line_start = line_end.floor
line_end = copy.length
break if entering_line_end == copy.length || reach == max_reach # Prevent locking up due to outer while loop text width < max_width check not being satisfied.
end 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|

View File

@@ -15,11 +15,18 @@ 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

View File

@@ -25,6 +25,7 @@ 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 = []
@menu = nil @menu = nil
@min_drag_distance = 0 @min_drag_distance = 0
@@ -56,6 +57,7 @@ module CyberarmEngine
if @tip.value.length.positive? if @tip.value.length.positive?
Gosu.flush Gosu.flush
@tip.draw @tip.draw
end end
@@ -73,8 +75,11 @@ 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 end
@pending_element_recalculate_requests.each(&:recalculate)
if @pending_focus_request if @pending_focus_request
@pending_focus_request = false @pending_focus_request = false
@@ -103,7 +108,7 @@ 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 + 5) @tip.y = window.mouse_y - (@tip.height + 5)
@@ -231,6 +236,13 @@ 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) def request_focus(element)
@pending_focus_request = true @pending_focus_request = true
@pending_focus_element = element @pending_focus_element = element

View File

@@ -115,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

View File

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