Compare commits

36 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
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
33 changed files with 1531 additions and 467 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
@@ -32,6 +32,8 @@ require "cyberarm_engine"
class Hello < CyberarmEngine::GuiState
def setup
background Gosu::Color::GRAY
stack do
label "Hello World!"
@@ -43,15 +45,14 @@ class Hello < CyberarmEngine::GuiState
end
class Window < CyberarmEngine::Window
def initialize
super
def setup
self.show_cursor = true
push_state(Hello)
end
end
Window.new.show
Window.new(width: 800, height: 600, fullscreen: false, resizable: true).show
```
## 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.require_paths = %w[lib assets]
spec.add_dependency "clipboard", "~> 1.3.5"
spec.add_dependency "excon", "~> 0.78.0"
spec.add_dependency "clipboard", "~> 1.3"
spec.add_dependency "excon", "~> 0.88"
spec.add_dependency "gosu", "~> 1.1"
spec.add_dependency "gosu_more_drawables", "~> 0.3"
# 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__)
begin
require File.expand_path("../../ffi-gosu/lib/gosu", File.dirname(__FILE__))
rescue LoadError => e
pp e
if ARGV.join.include?("--ffi-gosu")
require File.expand_path("../../ffi-gosu/lib/gosu", __dir__)
else
require "gosu"
end
require "json"
@@ -24,12 +23,18 @@ require_relative "cyberarm_engine/vector"
require_relative "cyberarm_engine/transform"
require_relative "cyberarm_engine/ray"
require_relative "cyberarm_engine/background"
require_relative "cyberarm_engine/background_nine_slice"
require_relative "cyberarm_engine/animator"
require_relative "cyberarm_engine/text"
require_relative "cyberarm_engine/timer"
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/theme"
@@ -62,3 +67,5 @@ require_relative "cyberarm_engine/model/model_object"
require_relative "cyberarm_engine/model/parser"
require_relative "cyberarm_engine/model/parsers/wavefront_parser"
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
class Animator
DEFAULT_TWEEN = :linear
def initialize(start_time:, duration:, from:, to:, &block)
def initialize(start_time:, duration:, from:, to:, tween: :linear, &block)
@start_time = start_time
@duration = duration
@from = from.dup
@to = to.dup
@tween = tween
@block = block
end
@@ -14,18 +14,18 @@ module CyberarmEngine
end
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
def complete?
progress >= 1.0
end
def transition(from, to, tween = DEFAULT_TWEEN)
def transition(from = @from, to = @to, tween = @tween)
from + (to - from) * send("tween_#{tween}", progress)
end
def color_transition(from, to, _tween = DEFAULT_TWEEN)
def color_transition(from = @from, to = @to, _tween = @tween)
r = transition(from.red, to.red)
g = transition(from.green, to.green)
b = transition(from.blue, to.blue)
@@ -34,7 +34,7 @@ module CyberarmEngine
Gosu::Color.rgba(r, g, b, a)
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)
saturation = transition(from.saturation, to.saturation, tween)
value = transition(from.value, to.value, tween)
@@ -43,14 +43,177 @@ module CyberarmEngine
Gosu::Color.from_ahsv(alpha, hue, saturation, value)
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)
t
end
def tween_sine(t)
Math.sin(t) * 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))
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

View File

@@ -1,10 +1,11 @@
module CyberarmEngine
class BackgroundNineSlice
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)
@image = get_image(image_path)
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) if image_path
@x = x
@y = y
@@ -20,23 +21,33 @@ module CyberarmEngine
@right = right
@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
def nine_slice
@segment_top_left = Gosu.render(@left, @top) { @image.draw(0, 0, 0) }
@segment_top_right = Gosu.render(@image.width - @right, @top) { @image.draw(-@right, 0, 0) }
# pp [@left, @top, @right, @bottom, @image.width]
@segment_left = Gosu.render(@left, @bottom - @top) { @image.draw(0, -@top, 0) }
@segment_right = Gosu.render(@image.width - @right, @bottom - @top) { @image.draw(-@right, -@top, 0) }
@segment_top_left = @image.subimage(0, 0, @left, @top)
@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_bottom_right = Gosu.render(@image.width - @right, @image.height - @bottom) { @image.draw(-@right, -@bottom, 0) }
@segment_left = @image.subimage(0, @top, @left, @image.height - (@top + @bottom))
@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 = Gosu.render(@right - @left, @image.height - @bottom) { @image.draw(-@left, -@bottom, 0) }
@segment_bottom_left = @image.subimage(0, @image.height - @bottom, @left, @bottom)
@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
def cx
@@ -56,67 +67,73 @@ module CyberarmEngine
end
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
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
def draw
return unless @image && @segment_top_left
@mode == :tiled ? draw_tiled : draw_stretched
end
def draw_stretched
@segment_top_left.draw(@x, @y, @z)
@segment_top.draw(@x + @segment_top_left.width, @y, @z, width_scale) # SCALE X
@segment_top_right.draw((@x + @width) - @segment_top_right.width, @y, @z)
@segment_top_left.draw(@x, @y, @z, 1, 1, @color)
@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, 1, 1, @color)
@segment_right.draw((@x + @width) - @segment_right.width, @y + @top, @z, 1, height_scale) # SCALE Y
@segment_bottom_right.draw((@x + @width) - @segment_bottom_right.width, @y + @height - @segment_bottom_right.height, @z)
@segment_bottom.draw(@x + @segment_bottom_left.width, (@y + @height) - @segment_bottom.height, @z, width_scale) # SCALE X
@segment_bottom_left.draw(@x, (@y + @height) - @segment_bottom_left.height, @z)
@segment_left.draw(@x, @y + @top, @z, 1, height_scale) # 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_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, 1, 1, @color)
@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, 1, 1, @color)
@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, @color) # SCALE X and SCALE Y
end
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
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
@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
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
@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
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
@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
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
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|
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

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
end
def shift_state
window.shift_state
end
def show_cursor
window.show_cursor
end
@@ -24,8 +28,8 @@ module CyberarmEngine
window.show_cursor = boolean
end
def draw_rect(x, y, width, height, color, z = 0)
Gosu.draw_rect(x, y, width, height, color, z)
def draw_rect(x, y, width, height, color, z = 0, mode = :default)
Gosu.draw_rect(x, y, width, height, color, z, mode)
end
def fill(color, z = 0)
@@ -70,6 +74,7 @@ module CyberarmEngine
else
klass.new(path)
end
hash[path] = instance
asset = instance
end
@@ -92,5 +97,17 @@ module CyberarmEngine
def window
$window
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

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
end
# Called immediately after setup returns.
# GuiState uses this to set current_theme for ToolTip
def post_setup
end
def draw
@game_objects.each(&:draw)
end

View File

@@ -3,7 +3,8 @@ module CyberarmEngine
attr_accessor :objects, :materials, :vertices, :uvs, :texures, :normals, :faces, :colors, :bones, :material_file,
:current_material, :current_object, :vertex_count, :smoothing
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:)
@file_path = file_path
@@ -23,6 +24,8 @@ module CyberarmEngine
@bones = []
@smoothing = 0
@vertices_count = 0
@bounding_box = BoundingBox.new
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond)
@@ -32,6 +35,8 @@ module CyberarmEngine
parse(parser)
@vertices_count = @vertices.size
@has_texture = false
@materials.each do |_key, material|

View File

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

View File

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

View File

@@ -54,7 +54,7 @@ module CyberarmEngine
texture_id = tex_names_buf.unpack1("L2")
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_T, GL_REPEAT)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST) if @retro

View File

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

View File

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

View File

@@ -4,7 +4,7 @@ module CyberarmEngine
include Event
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
def initialize(options = {}, block = nil)
@@ -18,6 +18,8 @@ module CyberarmEngine
@visible = @options[:visible].nil? ? true : @options[:visible]
@tip = @options[:tip] || ""
@debug_color = @options[:debug_color].nil? ? Gosu::Color::RED : @options[:debug_color]
@style = Style.new(options)
@root ||= nil
@@ -34,23 +36,36 @@ module CyberarmEngine
@style.height = default(:height) || nil
@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
default_events
root.gui_state.request_focus(self) if @options[:autofocus]
end
def stylize
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_border_color(@style.border_color)
set_background
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
def set_static_position
@@ -58,47 +73,92 @@ module CyberarmEngine
@y = @style.y if @style.y != 0
end
def set_background(background)
@style.background = background
@style.background_canvas.background = background
def set_color
@style.color = safe_style_fetch(:color)
@text&.color = @style.color
end
def set_border_thickness(border_thickness)
@style.border_thickness = border_thickness
@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
def set_font
@text&.swap_font(safe_style_fetch(:text_size), safe_style_fetch(:font))
end
def set_border_color(color)
@style.border_color = color
def set_background
@style.background = safe_style_fetch(:background)
@style.border_color_left = default(:border_color_left) || @style.border_color
@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
@style.background_canvas.background = @style.background
end
def set_padding(padding)
@style.padding = padding
def set_background_nine_slice
@style.background_nine_slice = safe_style_fetch(:background_nine_slice)
@style.padding_left = default(:padding_left) || @style.padding
@style.padding_right = default(:padding_right) || @style.padding
@style.padding_top = default(:padding_top) || @style.padding
@style.padding_bottom = default(:padding_bottom) || @style.padding
@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_margin(margin)
@style.margin = margin
def set_border_thickness
@style.border_thickness = safe_style_fetch(:border_thickness)
@style.margin_left = default(:margin_left) || @style.margin
@style.margin_right = default(:margin_right) || @style.margin
@style.margin_top = default(:margin_top) || @style.margin
@style.margin_bottom = default(:margin_bottom) || @style.margin
@style.border_thickness_left = safe_style_fetch(:border_thickness_left) || @style.border_thickness
@style.border_thickness_right = safe_style_fetch(:border_thickness_right) || @style.border_thickness
@style.border_thickness_top = safe_style_fetch(:border_thickness_top) || @style.border_thickness
@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
def default_events
@@ -116,11 +176,82 @@ module CyberarmEngine
event(:hover)
event(:leave)
event(:focus)
event(:blur)
event(:changed)
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?
@enabled
end
@@ -150,6 +281,7 @@ module CyberarmEngine
return unless visible?
@style.background_canvas.draw
@style.background_nine_slice_canvas.draw
@style.border_canvas.draw
Gosu.clip_to(@x, @y, width, height) do
@@ -157,6 +289,31 @@ module CyberarmEngine
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
end
@@ -227,35 +384,56 @@ module CyberarmEngine
end
def scroll_width
@children.sum { |c| c.width } + noncontent_width
@children.sum(&:outer_width)
end
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
def max_scroll_width
scroll_width - width
scroll_width - outer_width
end
def max_scroll_height
scroll_height - height
scroll_height - outer_height
end
def dimensional_size(size, dimension)
raise "dimension must be either :width or :height" unless %i[width height].include?(dimension)
if size && size.is_a?(Numeric)
if size.between?(0.0, 1.0)
((@parent.send(:"content_#{dimension}") - send(:"noncontent_#{dimension}")) * size).round
else
size
end
if size.is_a?(Numeric) && size.between?(0.0, 1.0)
(@parent.send(:"content_#{dimension}") * size).round - send(:"noncontent_#{dimension}").round
else
size
end
end
def background=(_background)
@style.background_canvas.background = (_background)
@style.background_canvas.background = _background
update_background
end
@@ -267,10 +445,34 @@ module CyberarmEngine
@style.background_canvas.height = height
@style.background_canvas.update
update_background_nine_slice
@style.border_canvas.update
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
return self if is_root?
@@ -278,11 +480,9 @@ module CyberarmEngine
@root = parent
loop do
if @root.parent.nil?
break
else
@root = @root.parent
end
break unless @root&.parent
@root = @root.parent
end
end
@@ -293,6 +493,12 @@ module CyberarmEngine
@gui_state != nil
end
def focus(_)
warn "#{self.class}#focus was not overridden!"
:handled
end
def recalculate
raise "#{self.class}#recalculate was not overridden!"
end
@@ -311,5 +517,9 @@ module CyberarmEngine
def to_s
"#{self.class} x=#{x} y=#{y} width=#{width} height=#{height} value=#{value.is_a?(String) ? "\"#{value}\"" : value}"
end
def inspect
to_s
end
end
end

View File

@@ -34,69 +34,6 @@ module CyberarmEngine
@text.draw
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
unless @enabled
@style.background_canvas.background = @style.disabled[:background]

View File

@@ -5,8 +5,11 @@ module CyberarmEngine
super(options, block)
options[:toggled] = options[:checked]
options[:parent] = self
@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|
@toggle_button.left_mouse_button(sender, x, y)

View File

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

View File

@@ -59,6 +59,8 @@ module CyberarmEngine
end
def update
@style_event = :active if @focus
@text.text = if @type == :password
default(:password_character) * @text_input.text.length
else
@@ -194,28 +196,27 @@ module CyberarmEngine
:handled
end
def enter(_sender)
if @focus
@style.background_canvas.background = default(:active, :background)
@text.color = default(:active, :color)
else
@style.background_canvas.background = default(:hover, :background)
@text.color = default(:hover, :color)
end
def focus(sender)
super
window.text_input = @text_input
@text_input.caret_pos = @text_input.selection_start = @text_input.text.length
:handled
end
def leave(sender)
super unless @focus
def enter(sender)
_has_focus = @focus
super
@focus = _has_focus
:handled
end
def blur(_sender)
@focus = false
@style.background_canvas.background = default(:background)
@text.color = default(:color)
super
window.text_input = nil
: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
end
def clicked_left_mouse_button(_sender, _x, _y)
# @block&.call(self.value) if @enabled
:handled
end
def show_menu
@menu.clear
@items.each do |item|
next if item == self.value
btn = Button.new(
item,
{
@@ -61,7 +69,7 @@ module CyberarmEngine
},
proc do
self.choose = item
@block&.call(item)
@block&.call(self.value)
end
)

View File

@@ -8,7 +8,10 @@ module CyberarmEngine
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]
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
@@ -18,13 +21,13 @@ module CyberarmEngine
@text.draw
end
def clicked_left_mouse_button(_sender, _x, _y)
@block&.call(self) if @enabled
# return :handled
end
def recalculate
unless @enabled
@text.color = @style.disabled[:color]
else
@text.color = @style.color
end
@width = 0
@height = 0
@@ -44,8 +47,8 @@ module CyberarmEngine
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
@text.x = if @text.width <= width
@x + width / 2 - @text.width / 2
else # Act as left aligned
@style.border_thickness_left + @style.padding_left + @x
end
@@ -58,46 +61,64 @@ module CyberarmEngine
end
def handle_text_wrapping(max_width)
max_width ||= @parent&.width
max_width ||= @parent&.content_width
max_width ||= @x - (window.width + noncontent_width)
wrap_behavior = style.text_wrap
copy = @raw_text.to_s.dup
if line_width(copy[0]) <= max_width && line_width(copy) > max_width && wrap_behavior != :none
breaks = []
# Only perform text wrapping: if it is enabled, is possible to wrap, and text is too long to fit on one line
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_end = copy.length
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)
line_end = 1.0 if line_end <= 1
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
stalled = false
stalled_interations = 0
max_stalled_iterations = 10
checked_copy_length = line_width(copy[line_start..line_end])
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
# find length of lines
while line_width(copy[line_start..line_end]) > max_width && stalled_interations < max_stalled_iterations
search_start = line_start
search_end = line_end
if wrap_behavior == :word_wrap
max_reach.times do |i|
reach = i
break if copy[line_end.floor - i].to_s.match(/[[:punct:]]| /)
# 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
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
# 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
line_start = failed ? search_end : word_search_end + 1 # walk in front of punctuation
else
line_start = search_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
breaks.each_with_index do |pos, index|
@@ -152,5 +173,8 @@ module CyberarmEngine
class ToolTip < TextBlock
end
class Link < TextBlock
end
end
end

View File

@@ -15,12 +15,19 @@ module CyberarmEngine
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|
return :handled if handler.call(self, *args) == :handled
if handler.call(self, *args) == :handled
was_handled = true
break
end
end
return :handled if was_handled
parent.publish(event, *args) if parent
nil
end

View File

@@ -25,13 +25,17 @@ module CyberarmEngine
@last_mouse_pos = nil
@dragging_element = nil
@pending_recalculate_request = false
@pending_element_recalculate_requests = []
@tip = Element::ToolTip.new("", parent: @root_container, z: Float::INFINITY)
@menu = nil
@min_drag_distance = 0
@mouse_pos = Vector.new
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
# Does NOT throw :focus event at element or set element as focused
def focus=(element)
@@ -53,8 +57,15 @@ module CyberarmEngine
if @tip.value.length.positive?
Gosu.flush
@tip.draw
end
if defined?(GUI_DEBUG)
Gosu.flush
@root_container.debug_draw
end
end
def update
@@ -64,6 +75,16 @@ module CyberarmEngine
@root_container.recalculate
@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
@menu&.update
@@ -87,10 +108,10 @@ module CyberarmEngine
if Vector.new(window.mouse_x, window.mouse_y) == @last_mouse_pos
if @mouse_over && (Gosu.milliseconds - @mouse_moved_at) > tool_tip_delay
@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 = 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 = window.height - @tip.height if @tip.y + @tip.height > window.height
@tip.update
@@ -215,6 +236,18 @@ module CyberarmEngine
@pending_recalculate_request = true
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)
@menu = list_box
end
@@ -222,5 +255,14 @@ module CyberarmEngine
def hide_menu
@menu = nil
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

View File

@@ -17,8 +17,20 @@ end
module CyberarmEngine
class Style
attr_reader :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
def method_missing(method, *args)
@@ -27,9 +39,8 @@ module CyberarmEngine
@hash[method.to_s.sub("=", "").to_sym] = args.first
elsif args.size == 0
elsif args.empty?
@hash[method]
else
raise ArgumentError, "Did not expect arguments"
end

View File

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

View File

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

View File

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