Compare commits

...

46 Commits

Author SHA1 Message Date
2cc1382696 Fixed Text border color not getting set correctly due to misnamed color parameter, generate Text border image with true white text since we draw it with the requested color later. 2026-09-20 16:10:10 -05:00
9fca64b643 Added render mode to BackgroundNineSlice 2026-09-19 19:10:39 -05:00
abe99ce949 Ignore sibling elements that aren't visible from fill mode space considerations 2026-09-17 12:25:48 -05:00
66fb136c83 Make elements sizing by like css' border-box box-sizing instead of like content-box for elements with specific width/height that isn't a ratio, tweaks to gui debug display, make F12 key toggle gui debug display for convenience 2026-09-11 13:05:08 -05:00
b23e5c2a42 Fix pending element recalculate requests apparently not getting processed properly... 2026-09-10 18:32:01 -05:00
747822aa61 Fixed flow containers not correctly positioning their children on the x axis when they wrap to the the next line, fix up containers vertical and horizontal alignment 2026-09-10 18:31:19 -05:00
140e7a3619 Make elements publish the :size_changed event when they change their size 2026-09-09 22:30:19 -05:00
349222f387 Flip flop Element#resolution_scaled 2026-09-04 10:31:30 -05:00
9d4bc89174 Fix Widget element unable to find children element tooltips if the widget's parent(s) had been scrolled 2026-09-04 10:31:02 -05:00
8e7c6f8a19 Many ux and api improvements 2026-05-02 18:20:17 -05:00
cd5bd51a02 Added EventBus, a simple global pub-sub event system. 2026-04-23 19:39:36 -05:00
adf517f374 Make Element#styled return 0 for border_thickness, margins, and paddings if the element is not Element#visible? 2026-04-21 13:19:02 -05:00
1c868522b7 Make Result return not okay if @data is nil 2026-04-21 12:12:16 -05:00
a79b9cc20e Added Widget, which acts like a Flow and a button. 2026-04-20 10:16:37 -05:00
c229329542 Send enter and hover events to mouse over element's parent containers- improves ux, disable applying :active styles if element does not have any specificied- fixes element blinking on click. 2026-04-18 12:29:04 -05:00
512d8a5c0f Added initial dialog support 2026-04-17 18:46:27 -05:00
d158d2fdd2 Added initial support for pages, fixed image not accounting for margin refactor 2026-04-17 17:45:58 -05:00
9f654129ef Improved handling of nine slice backgrounds, fixed scroll height, fixed container v/h_align 2026-04-17 11:38:17 -05:00
b694269409 Fixed ListBox/Menu not accounting for its border 2026-04-15 22:09:58 -05:00
3375d2d224 Fixed button image off center if button has margin left/top 2026-04-15 22:07:45 -05:00
a47505e7fa Improved debug_draw to be free of element clipping rectangle, fixed up container vertical and horizontal alignment 2026-04-15 22:06:55 -05:00
e572679f0b Renamed GuiState#menu to GuiState#active_menu to prevent conflict with DSL#menu 2026-04-15 22:02:09 -05:00
4286ca06b0 Fix up container's over estimating their width/height some more 2026-04-15 10:51:10 -05:00
a5636c82b3 Fixed Element's x and y position offset by their containers margin_left and/or margin_top, fixed containers 'inheriting' their parents and/or grandparents margin_left/top causing them to have an outer_height greater than it should be (needs a little more work.) 2026-04-13 20:00:24 -05:00
971ac5ff34 Lazy init Element background and border canvases 2026-03-27 11:20:03 -05:00
3b5902fef5 Reduce usages of Gosu.clip_to in Background 2026-03-27 11:18:55 -05:00
dfb9378c6d Remove unneeded string to integer conversions in Theme 2026-03-27 10:59:22 -05:00
3a23a8f8ca Improve clipping of TextBlock 2026-03-27 10:58:42 -05:00
d735edaec0 Make changing an element's styles trigger element to re-stylize 2026-03-21 22:42:57 -05:00
d2440b50a1 Added every and after timers to DSL 2026-03-21 21:11:51 -05:00
362fafa6fd Fixed HOME/END PAGE UP/DOWN keys incorrectly scrolling container when an EditLine or EditBox has focus 2026-03-21 21:11:51 -05:00
907257879c Fixed EditBox causing crash if parent container is scrolled and EditBox is clicked 2026-03-21 21:11:50 -05:00
e031039a77 Refactored GuiState Element style handling to simplify mutating them 2026-03-21 21:03:58 -05:00
16bd9168e4 Bump version 2026-01-28 18:02:18 -06:00
b946d5efa6 Added Result class to make writing failure resistant code easier 2026-01-28 18:02:18 -06:00
4ce6c1f499 Update README 2026-01-09 09:39:40 -06:00
958d4e65f9 Bump version 2026-01-09 09:36:08 -06:00
0519253e03 Improved GuiState to fully recalculate before returning from draw 2026-01-09 09:29:41 -06:00
97055885a6 Cache TextBlock text width and height 2026-01-09 08:59:06 -06:00
b5912de980 Remove unused code from Background#update, improves performance notably. 2026-01-09 08:34:59 -06:00
b0376d85d9 Reduce allocations in Background 2026-01-09 08:26:55 -06:00
a30d66fafb Refactored Style to remove usage of method_missing 2026-01-09 08:20:27 -06:00
0fac4a0397 Add StackProf hooks for profile recalculate 2026-01-08 22:16:54 -06:00
498cf06916 Style fixes, wip layout speed up work 2025-12-01 10:03:45 -06:00
1f57dfd38c Update to support Gosu 2.0.0 (Gosu::Image taking in a Gosu::Image fails) 2025-11-29 11:27:27 -06:00
76a8bf95c7 Update Gosu.draw_arc to support partial segments 2025-11-29 11:11:53 -06:00
35 changed files with 924 additions and 435 deletions

View File

@@ -1,7 +1,7 @@
PATH PATH
remote: . remote: .
specs: specs:
cyberarm_engine (0.24.4) cyberarm_engine (0.24.5)
gosu (~> 1.1) gosu (~> 1.1)
GEM GEM
@@ -13,6 +13,7 @@ GEM
PLATFORMS PLATFORMS
x64-mingw-ucrt x64-mingw-ucrt
x86_64-linux
DEPENDENCIES DEPENDENCIES
bundler (~> 2.2) bundler (~> 2.2)
@@ -21,4 +22,4 @@ DEPENDENCIES
rake (~> 13.0) rake (~> 13.0)
BUNDLED WITH BUNDLED WITH
2.5.3 2.6.8

View File

@@ -35,7 +35,7 @@ class Hello < CyberarmEngine::GuiState
background Gosu::Color::GRAY background Gosu::Color::GRAY
stack do stack do
label "Hello World!" banner "Hello World!"
button "close" do button "close" do
window.close window.close

View File

@@ -9,6 +9,8 @@ require "json"
require_relative "cyberarm_engine/version" require_relative "cyberarm_engine/version"
require_relative "cyberarm_engine/stats" require_relative "cyberarm_engine/stats"
require_relative "cyberarm_engine/result"
require_relative "cyberarm_engine/event_bus"
require_relative "cyberarm_engine/common" require_relative "cyberarm_engine/common"
@@ -57,6 +59,7 @@ require_relative "cyberarm_engine/ui/elements/image"
require_relative "cyberarm_engine/ui/elements/container" require_relative "cyberarm_engine/ui/elements/container"
require_relative "cyberarm_engine/ui/elements/flow" require_relative "cyberarm_engine/ui/elements/flow"
require_relative "cyberarm_engine/ui/elements/stack" require_relative "cyberarm_engine/ui/elements/stack"
require_relative "cyberarm_engine/ui/elements/widget"
require_relative "cyberarm_engine/ui/elements/check_box" require_relative "cyberarm_engine/ui/elements/check_box"
require_relative "cyberarm_engine/ui/elements/radio" require_relative "cyberarm_engine/ui/elements/radio"
require_relative "cyberarm_engine/ui/elements/progress" require_relative "cyberarm_engine/ui/elements/progress"
@@ -66,5 +69,13 @@ require_relative "cyberarm_engine/ui/elements/menu_item"
require_relative "cyberarm_engine/game_state" require_relative "cyberarm_engine/game_state"
require_relative "cyberarm_engine/ui/gui_state" require_relative "cyberarm_engine/ui/gui_state"
require_relative "cyberarm_engine/ui/dialog"
require_relative "cyberarm_engine/ui/page"
require_relative "cyberarm_engine/builtin/intro_state" require_relative "cyberarm_engine/builtin/intro_state"
if RUBY_ENGINE != "mruby" && defined?(StackProf)
at_exit do
StackProf.results("./_stackprof.dmp")
end
end

View File

@@ -21,50 +21,36 @@ module CyberarmEngine
end end
def draw def draw
Gosu.clip_to(@x, @y, @width, @height) do if @angle.zero?
Gosu.draw_quad( render
@top_left.x, @top_left.y, @paint.top_left, else
@top_right.x, @top_right.y, @paint.top_right, Gosu.clip_to(@x, @y, @width, @height) do
@bottom_right.x, @bottom_right.y, @paint.bottom_right, render
@bottom_left.x, @bottom_left.y, @paint.bottom_left, end
@z
)
end end
debug_outline if @debug debug_outline if @debug
end end
def render
Gosu.draw_quad(
@top_left.x, @top_left.y, @paint.top_left,
@top_right.x, @top_right.y, @paint.top_right,
@bottom_right.x, @bottom_right.y, @paint.bottom_right,
@bottom_left.x, @bottom_left.y, @paint.bottom_left,
@z
)
end
def update def update
origin_x = (@x + (@width / 2)) @top_left.x = @x
origin_y = (@y + (@height / 2)) @top_left.y = @y
@top_right.x = @x + @width
points = [ @top_right.y = @y
@top_left = Vector.new(@x, @y), @bottom_left.x = @x
@top_right = Vector.new(@x + @width, @y), @bottom_left.y = @y + @height
@bottom_left = Vector.new(@x, @y + @height), @bottom_right.x = @x + @width
@bottom_right = Vector.new(@x + @width, @y + @height) @bottom_right.y = @y + @height
]
[@top_left, @top_right, @bottom_left, @bottom_right].each do |vector|
temp_x = vector.x - origin_x
temp_y = vector.y - origin_y
# 90 is up here, while gosu uses 0 for up.
radians = (@angle + 90).gosu_to_radians
vector.x = (@x + (@width / 2)) + ((temp_x * Math.cos(radians)) - (temp_y * Math.sin(radians)))
vector.y = (@y + (@height / 2)) + ((temp_x * Math.sin(radians)) + (temp_y * Math.cos(radians)))
end
# [
# [:top, @top_left, @top_right],
# [:right, @top_right, @bottom_right],
# [:bottom, @bottom_right, @bottom_left],
# [:left, @bottom_left, @top_left]
# ].each do |edge|
# points.each do |point|
# puts "#{edge.first} -> #{shortest_distance(point, edge[1], edge[2])}"
# end
# end
end end
def shortest_distance(point, la, lb) def shortest_distance(point, la, lb)
@@ -168,13 +154,3 @@ module CyberarmEngine
end end
end end
end end
# Add <=> method to support Range based gradients
# NOTE: Disabled, causes stack overflow 🙃
# module Gosu
# class Color
# def <=>(_other)
# self
# end
# end
# end

View File

@@ -1,10 +1,13 @@
module CyberarmEngine module CyberarmEngine
class BackgroundNineSlice class BackgroundNineSlice
include CyberarmEngine::Common include CyberarmEngine::Common
attr_accessor :x, :y, :z, :width, :height, :left, :top, :right, :bottom, :mode, :color
attr_reader :image
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) CACHE = {}
attr_accessor :x, :y, :z, :width, :height, :mode, :color, :render_mode
attr_reader :image, :left, :top, :right, :bottom
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, render_mode: :default)
@image = get_image(image_path) if image_path @image = get_image(image_path) if image_path
@x = x @x = x
@@ -22,6 +25,7 @@ module CyberarmEngine
@bottom = bottom @bottom = bottom
@color = color @color = color
@render_mode = render_mode
nine_slice if @image nine_slice if @image
end end
@@ -32,8 +36,43 @@ module CyberarmEngine
nine_slice if @image && old_image != @image nine_slice if @image && old_image != @image
end end
def set_edges(left: @left, right: @right, top: @top, bottom: @bottom)
changed = [left == @left, right == @right, top == @top, bottom == @bottom].any?(false)
@left = left
@top = top
@right = right
@bottom = bottom
nine_slice if changed
end
def left=(n)
nine_slice if n != @left
@left = n
n
end
def right=(n)
nine_slice if n != @right
@right = n
n
end
def top=(n)
nine_slice if n != @top
@top = n
n
end
def bottom=(n)
nine_slice if n != @bottom
@bottom = n
n
end
def nine_slice def nine_slice
# pp [@left, @top, @right, @bottom, @image.width] # pp [@left, @top, @right, @bottom, @image.width, @image.height]
@segment_top_left = @image.subimage(0, 0, @left, @top) @segment_top_left = @image.subimage(0, 0, @left, @top)
@segment_top_right = @image.subimage(@image.width - @right, 0, @right, @top) @segment_top_right = @image.subimage(@image.width - @right, 0, @right, @top)
@@ -83,57 +122,58 @@ module CyberarmEngine
end end
def draw_stretched def draw_stretched
@segment_top_left.draw(@x, @y, @z, 1, 1, @color) @segment_top_left.draw(@x, @y, @z, 1, 1, @color, @render_mode)
@segment_top.draw(@x + @segment_top_left.width, @y, @z, width_scale, 1, @color) # SCALE X @segment_top.draw(@x + @segment_top_left.width, @y, @z, width_scale, 1, @color, @render_mode) # SCALE X
@segment_top_right.draw((@x + @width) - @segment_top_right.width, @y, @z, 1, 1, @color) @segment_top_right.draw((@x + @width) - @segment_top_right.width, @y, @z, 1, 1, @color, @render_mode)
@segment_right.draw((@x + @width) - @segment_right.width, @y + @top, @z, 1, height_scale, @color) # SCALE Y @segment_right.draw((@x + @width) - @segment_right.width, @y + @top, @z, 1, height_scale, @color, @render_mode) # SCALE Y
@segment_bottom_right.draw((@x + @width) - @segment_bottom_right.width, @y + @height - @segment_bottom_right.height, @z, 1, 1, @color) @segment_bottom_right.draw((@x + @width) - @segment_bottom_right.width, @y + @height - @segment_bottom_right.height, @z, 1, 1, @color, @render_mode)
@segment_bottom.draw(@x + @segment_bottom_left.width, (@y + @height) - @segment_bottom.height, @z, width_scale, 1, @color) # SCALE X @segment_bottom.draw(@x + @segment_bottom_left.width, (@y + @height) - @segment_bottom.height, @z, width_scale, 1, @color, @render_mode) # SCALE X
@segment_bottom_left.draw(@x, (@y + @height) - @segment_bottom_left.height, @z, 1, 1, @color) @segment_bottom_left.draw(@x, (@y + @height) - @segment_bottom_left.height, @z, 1, 1, @color, @render_mode)
@segment_left.draw(@x, @y + @top, @z, 1, height_scale, @color) # SCALE Y @segment_left.draw(@x, @y + @top, @z, 1, height_scale, @color, @render_mode) # 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
@segment_middle.draw(@x + @segment_top_left.width, @y + @segment_top.height, @z, width_scale, height_scale, @color, @render_mode) # SCALE X and SCALE Y
end end
def draw_tiled def draw_tiled
@segment_top_left.draw(@x, @y, @z, 1, 1, @color) @segment_top_left.draw(@x, @y, @z, 1, 1, @color, @render_mode)
# p [width_scale, height_scale] # 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, 1, 1, @color) # SCALE X @segment_top.draw(@x + @segment_top_left.width + (@segment_top.width * i), @y, @z, 1, 1, @color, @render_mode) # SCALE X
end end
end end
@segment_top_right.draw((@x + @width) - @segment_top_right.width, @y, @z, 1, 1, @color) @segment_top_right.draw((@x + @width) - @segment_top_right.width, @y, @z, 1, 1, @color, @render_mode)
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, 1, 1, @color) # SCALE Y @segment_right.draw((@x + @width) - @segment_right.width, @y + @top + (@segment_right.height * i), @z, 1, 1, @color, @render_mode) # SCALE Y
end end
end end
@segment_bottom_right.draw((@x + @width) - @segment_bottom_right.width, @y + @height - @segment_bottom_right.height, @z, 1, 1, @color) @segment_bottom_right.draw((@x + @width) - @segment_bottom_right.width, @y + @height - @segment_bottom_right.height, @z, 1, 1, @color, @render_mode)
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, 1, 1, @color) # SCALE X @segment_bottom.draw(@x + @segment_bottom_left.width + (@segment_bottom.width * i), (@y + @height) - @segment_bottom.height, @z, 1, 1, @color, @render_mode) # SCALE X
end end
end end
@segment_bottom_left.draw(@x, (@y + @height) - @segment_bottom_left.height, @z, 1, 1, @color) @segment_bottom_left.draw(@x, (@y + @height) - @segment_bottom_left.height, @z, 1, 1, @color, @render_mode)
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, 1, 1, @color) # SCALE Y @segment_left.draw(@x, @y + @top + (@segment_left.height * i), @z, 1, 1, @color, @render_mode) # 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, 1, 1, @color) # 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, @render_mode) # SCALE X and SCALE Y
end end
end end
end end

View File

@@ -1,5 +1,9 @@
module CyberarmEngine module CyberarmEngine
GUI_DEBUG = false
module Common module Common
ImageBlob = Data.define(:to_blob, :columns, :rows)
def push_state(klass, options = {}) def push_state(klass, options = {})
window.push_state(klass, options) window.push_state(klass, options)
end end
@@ -85,7 +89,8 @@ module CyberarmEngine
unless asset unless asset
instance = nil instance = nil
instance = if klass == Gosu::Image instance = if klass == Gosu::Image
klass.new(path, retro: retro, tileable: tileable) path_or_blob = path.is_a?(String) ? path : ImageBlob.new(path.to_blob, path.width, path.height)
klass.new(path_or_blob, retro: retro, tileable: tileable)
else else
klass.new(path) klass.new(path)
end end

View File

@@ -0,0 +1,39 @@
module CyberarmEngine
class EventBus
Subscriber = Data.define(:event, :listener, :method, :callback)
@subscribers = []
def self.subscribe(event, listener = nil, method = nil, &block)
raise "Subscriber cannot have both a method AND a callback block" if method && block_given?
subscriber = Subscriber.new(event, listener, method, block)
@subscribers << subscriber
subscriber
end
def self.unsubscribe(event, listener_or_subscriber)
@subscribers.delete_if { |s| s.event == event && (s == listener_or_subscriber || s.listener == listener_or_subscriber) }
end
def self.unsubscribe!(event)
@subscribers.delete_if { |s| s.event == event }
end
def self.announce(event, *payload)
subscribers = @subscribers.select { |s| s.event == event }
subscribers.each do |subscriber|
subscriber&.listener&.send(subscriber.method, *payload) if subscriber&.method
subscriber.callback&.call(*payload)
rescue StandardError => e
# TODO
warn "Failed to deliver event (#{event}) to listener (#{subscriber.inspect}): #{e}"
end
end
def self.publish(*args)
announce(*args)
end
end
end

View File

@@ -3,11 +3,12 @@ module CyberarmEngine
include Common include Common
attr_accessor :options, :global_pause attr_accessor :options, :global_pause
attr_reader :game_objects attr_reader :game_objects, :timers
def initialize(options = {}) def initialize(options = {})
@options = options @options = options
@game_objects = [] @game_objects = []
@timers = []
@global_pause = false @global_pause = false
window.text_input = nil unless options[:preserve_text_input] window.text_input = nil unless options[:preserve_text_input]
@@ -28,6 +29,8 @@ module CyberarmEngine
def update def update
@game_objects.each(&:update) @game_objects.each(&:update)
@timers.each(&:update)
@timers.delete_if(&:dead?)
end end
def needs_redraw? def needs_redraw?
@@ -120,5 +123,9 @@ module CyberarmEngine
def add_game_object(object) def add_game_object(object)
@game_objects << object @game_objects << object
end end
def add_timer(timer)
@timers << timer
end
end end
end end

View File

@@ -33,12 +33,23 @@ module Gosu
# #
# @return [void] # @return [void]
def self.draw_arc(x, y, radius, percentage = 1.0, segments = 128, thickness = 4, color = Gosu::Color::WHITE, z = 0, mode = :default) def self.draw_arc(x, y, radius, percentage = 1.0, segments = 128, thickness = 4, color = Gosu::Color::WHITE, z = 0, mode = :default)
segments = 360.0 / segments
return if percentage == 0.0 return if percentage == 0.0
0.step((359 * percentage), percentage > 0 ? segments : -segments) do |angle| angle_per_segment = 360.0 / segments
angle2 = angle + segments arc_completion = 360 * percentage
next_segment_angle = angle_per_segment
angle = 0
loop do
break if angle >= arc_completion
if angle + angle_per_segment > arc_completion
next_segment_angle = arc_completion - angle
else
next_segment_angle = angle_per_segment
end
angle2 = angle + next_segment_angle
point_a_left_x = x + Gosu.offset_x(angle, radius - thickness) point_a_left_x = x + Gosu.offset_x(angle, radius - thickness)
point_a_left_y = y + Gosu.offset_y(angle, radius - thickness) point_a_left_y = y + Gosu.offset_y(angle, radius - thickness)
@@ -93,6 +104,8 @@ module Gosu
z, mode z, mode
) )
end end
angle += next_segment_angle
end end
end end
end end

View File

@@ -0,0 +1,19 @@
module CyberarmEngine
# result pattern
class Result
attr_accessor :error, :data
def initialize(data: nil, error: nil)
@data = data
@error = error
end
def okay?
!error?
end
def error?
@error || @data.nil?
end
end
end

View File

@@ -23,7 +23,7 @@ module CyberarmEngine
@color = Gosu::Color::WHITE @color = Gosu::Color::WHITE
end end
@mode = options[:mode] || :default @mode = options[:mode] || :default
@alignment = options[:alignment] || nil @alignment = options[:alignment] || :left
@border = options[:border] @border = options[:border]
@border = true if options[:border].nil? @border = true if options[:border].nil?
@@ -39,17 +39,6 @@ module CyberarmEngine
@static = options[:static] || (options[:static].nil? || options[:static] == false ? false : true) @static = options[:static] || (options[:static].nil? || options[:static] == false ? false : true)
@textobject = check_cache(@size, @font) @textobject = check_cache(@size, @font)
if @alignment
case @alignment
when :left
@x = 0 + BUTTON_PADDING
when :center
@x = (CyberarmEngine::Window.instance.width / 2) - (@textobject.text_width(@text) / 2)
when :right
@x = CyberarmEngine::Window.instance.width - BUTTON_PADDING - @textobject.text_width(@text)
end
end
end end
def check_cache(size, font_name) def check_cache(size, font_name)
@@ -113,6 +102,38 @@ module CyberarmEngine
invalidate_cache! if old_color != color invalidate_cache! if old_color != color
end end
def alignment=(value)
invalidate_cache! if @alignment != value
@alignment = value
end
def shadow=(value)
invalidate_cache! if @shadow != value
@shadow = value
end
def shadow_color=(color)
old_color = @shadow_color
if color
@shadow_color = color.is_a?(Gosu::Color) ? color : Gosu::Color.new(color)
else
raise "color cannot be nil"
end
invalidate_cache! if old_color != color
end
def shadow_alpha=(value)
invalidate_cache! if @shadow_alpha != value
@shadow_alpha = value
end
def shadow_size=(value)
invalidate_cache! if @shadow_size != value
@shadow_size = value
end
def border=(boolean) def border=(boolean)
invalidate_cache! if @border != boolean invalidate_cache! if @border != boolean
@border = boolean @border = boolean
@@ -128,9 +149,16 @@ module CyberarmEngine
@border_alpha = n @border_alpha = n
end end
def border_color=(n) def border_color=(color)
invalidate_cache! if @border_color != n old_color = @border_color
@border_color = n
if color
@border_color = color.is_a?(Gosu::Color) ? color : Gosu::Color.new(color)
else
raise "color cannot be nil"
end
invalidate_cache! if old_color != color
end end
def width(text = @text) def width(text = @text)
@@ -180,23 +208,23 @@ module CyberarmEngine
img = Gosu::Image.send(:"from_#{method.to_s.split("_").last}", @text, @size, font: @font) img = Gosu::Image.send(:"from_#{method.to_s.split("_").last}", @text, @size, font: @font)
@cached_text_border_image = Gosu.render((_width + (@border_size * 2)).ceil, (height + (@border_size * 2)).ceil) do @cached_text_border_image = Gosu.render((_width + (@border_size * 2)).ceil, (height + (@border_size * 2)).ceil) do
img.draw(-_x, 0, @z, @factor_x, @factor_y, @border_color, @mode) img.draw(-_x, 0, @z, @factor_x, @factor_y, Gosu::Color::WHITE, @mode)
img.draw(-_x, -_y, @z, @factor_x, @factor_y, @border_color, @mode) img.draw(-_x, -_y, @z, @factor_x, @factor_y, Gosu::Color::WHITE, @mode)
img.draw(0, -_y, @z, @factor_x, @factor_y, @border_color, @mode) img.draw(0, -_y, @z, @factor_x, @factor_y, Gosu::Color::WHITE, @mode)
img.draw(_x, -_y, @z, @factor_x, @factor_y, @border_color, @mode) img.draw(_x, -_y, @z, @factor_x, @factor_y, Gosu::Color::WHITE, @mode)
img.draw(_x, 0, @z, @factor_x, @factor_y, @border_color, @mode) img.draw(_x, 0, @z, @factor_x, @factor_y, Gosu::Color::WHITE, @mode)
img.draw(_x, _y, @z, @factor_x, @factor_y, @border_color, @mode) img.draw(_x, _y, @z, @factor_x, @factor_y, Gosu::Color::WHITE, @mode)
img.draw(0, _y, @z, @factor_x, @factor_y, @border_color, @mode) img.draw(0, _y, @z, @factor_x, @factor_y, Gosu::Color::WHITE, @mode)
img.draw(-_x, _y, @z, @factor_x, @factor_y, @border_color, @mode) img.draw(-_x, _y, @z, @factor_x, @factor_y, Gosu::Color::WHITE, @mode)
end end
end end
@cached_text_shadow_image ||= Gosu::Image.send(:"from_#{method.to_s.split("_").last}", @text, @size, font: @font) if @shadow @cached_text_shadow_image ||= Gosu::Image.send(:"from_#{method.to_s.split("_").last}", @text, @size, font: @font, align: @alignment) if @shadow
@gosu_cached_text_image ||= Gosu::Image.send(:"from_#{method.to_s.split("_").last}", @text, @size, font: @font) @gosu_cached_text_image ||= Gosu::Image.send(:"from_#{method.to_s.split("_").last}", @text, @size, font: @font, align: @alignment)
@cached_text_border_image.draw(@x, @y, @z, @factor_x, @factor_y, @border_color, @mode) if @border @cached_text_border_image.draw(@x, @y, @z, @factor_x, @factor_y, @border_color, @mode) if @border
@@ -228,7 +256,7 @@ module CyberarmEngine
@textobject.send(method, @text, _x + @border_size, _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)
end end
@cached_text_border_image.draw(@x - @border_size, @y - @border_size, @z, @factor_x, @factor_y, border_color) @cached_text_border_image.draw(@x - @border_size, @y - @border_size, @z, @factor_x, @factor_y, border_color, @mode)
end end
if @shadow if @shadow

View File

@@ -19,5 +19,9 @@ module CyberarmEngine
@block.call if @block @block.call if @block
end end
end end
def dead?
@triggered && !@looping
end
end end
end end

View File

@@ -62,36 +62,36 @@ module CyberarmEngine
def update def update
# TOP # TOP
@top.x = @element.x + @element.style.border_thickness_left @top.x = @element.x + @element.styled(:margin_left) + @element.styled(:border_thickness_left)
@top.y = @element.y @top.y = @element.y + @element.styled(:margin_top)
@top.z = @element.z @top.z = @element.z
@top.width = @element.width - @element.style.border_thickness_left @top.width = @element.width - @element.styled(:border_thickness_left)
@top.height = @element.style.border_thickness_top @top.height = @element.styled(:border_thickness_top)
# RIGHT # RIGHT
@right.x = @element.x + @element.width @right.x = @element.x + @element.styled(:margin_left) + @element.width
@right.y = @element.y + @element.style.border_thickness_top @right.y = @element.y + @element.styled(:margin_top) + @element.styled(:border_thickness_top)
@right.z = @element.z @right.z = @element.z
@right.width = -@element.style.border_thickness_right @right.width = -@element.styled(:border_thickness_right)
@right.height = @element.height - @element.style.border_thickness_top @right.height = @element.height - @element.styled(:border_thickness_top)
# BOTTOM # BOTTOM
@bottom.x = @element.x @bottom.x = @element.x + @element.styled(:margin_left)
@bottom.y = @element.y + @element.height @bottom.y = @element.y + @element.styled(:margin_top) + @element.height
@bottom.z = @element.z @bottom.z = @element.z
@bottom.width = @element.width - @element.style.border_thickness_right @bottom.width = @element.width - @element.styled(:border_thickness_right)
@bottom.height = -@element.style.border_thickness_bottom @bottom.height = -@element.styled(:border_thickness_bottom)
# LEFT # LEFT
@left.x = @element.x @left.x = @element.x + @element.styled(:margin_left)
@left.y = @element.y @left.y = @element.y + @element.styled(:margin_top)
@left.z = @element.z @left.z = @element.z
@left.width = @element.style.border_thickness_left @left.width = @element.styled(:border_thickness_left)
@left.height = @element.height - @element.style.border_thickness_bottom @left.height = @element.height - @element.styled(:border_thickness_bottom)
@top.update @top.update
@right.update @right.update

View File

@@ -0,0 +1,24 @@
module CyberarmEngine
class Dialog < CyberarmEngine::GuiState
def draw
previous_state&.draw
Gosu.flush
super
end
def update
super
return unless window.current_state == self
window.states.reverse.each do |state|
# Don't update ourselves, forever
next if state == self && state.is_a?(CyberarmEngine::GuiState)
state.update
end
end
end
end

View File

@@ -1,5 +1,17 @@
module CyberarmEngine module CyberarmEngine
module DSL module DSL
def every(milliseconds, &block)
element_parent.root.gui_state.add_timer(
CyberarmEngine::Timer.new(milliseconds, true, &block)
)
end
def after(milliseconds, &block)
element_parent.root.gui_state.add_timer(
CyberarmEngine::Timer.new(milliseconds, false, &block)
)
end
def flow(options = {}, &block) def flow(options = {}, &block)
container(CyberarmEngine::Element::Flow, options, &block) container(CyberarmEngine::Element::Flow, options, &block)
end end
@@ -8,6 +20,10 @@ module CyberarmEngine
container(CyberarmEngine::Element::Stack, options, &block) container(CyberarmEngine::Element::Stack, options, &block)
end end
def widget(options = {}, &block)
container(CyberarmEngine::Element::Widget, options, &block)
end
def menu(options = {}, &block) def menu(options = {}, &block)
container(CyberarmEngine::Element::Menu, options, &block) container(CyberarmEngine::Element::Menu, options, &block)
end end
@@ -100,8 +116,20 @@ module CyberarmEngine
add_element(Element::Slider.new(options, block)) add_element(Element::Slider.new(options, block))
end end
def page(klass, options = {})
element_parent.root.gui_state.page(klass, options)
end
def current_page
element_parent.root.gui_state.current_page
end
def dialog(klass, options = {})
element_parent.root.gui_state.window.push_state(klass, options)
end
def background(color = Gosu::Color::NONE) def background(color = Gosu::Color::NONE)
element_parent.style.default[:background] = color element_parent.style.background = color
end end
def theme(theme) def theme(theme)

View File

@@ -18,6 +18,7 @@ module CyberarmEngine
@visible = !@options.key?(:visible) ? true : @options[:visible] @visible = !@options.key?(:visible) ? true : @options[:visible]
@tip = @options[:tip] || "" @tip = @options[:tip] || ""
@debug = @options[:debug].nil? ? nil : @options[:debug]
@debug_color = @options[:debug_color].nil? ? Gosu::Color::RED : @options[:debug_color] @debug_color = @options[:debug_color].nil? ? Gosu::Color::RED : @options[:debug_color]
@style = Style.new(options) @style = Style.new(options)
@@ -35,15 +36,15 @@ module CyberarmEngine
@width = 0 @width = 0
@height = 0 @height = 0
@style.width = default(:width) || nil @style.width = default(:width) || nil
@style.height = default(:height) || nil @style.height = default(:height) || nil
@style.background_canvas = Background.new @background_canvas = nil # Background.new
@style.background_nine_slice_canvas = BackgroundNineSlice.new @background_nine_slice_canvas = nil # BackgroundNineSlice.new
@style.background_image_canvas = BackgroundImage.new @background_image_canvas = nil # BackgroundImage.new
@style.border_canvas = BorderCanvas.new(element: self) @border_canvas = nil # BorderCanvas.new(element: self)
@style_event = :default @style_event = enabled? ? :default : :disabled
stylize stylize
@@ -52,33 +53,52 @@ module CyberarmEngine
root.gui_state.request_focus(self) if @options[:autofocus] root.gui_state.request_focus(self) if @options[:autofocus]
end end
def resolution_scaled(n)
return n unless @style.design_width
((Gosu.screen_width(window).to_f / @style.design_width) * n).floor
end
def stylize def stylize
@style_event = :disabled unless enabled?
set_static_position set_static_position
set_color set_color
set_font set_font
set_padding
set_margin
set_background set_background
set_background_nine_slice set_background_nine_slice
set_background_image set_background_image
set_border_thickness set_border
set_border_color
root.gui_state.request_repaint root.gui_state.request_repaint
end end
def styled(key)
case key
when :border_color_bottom, :border_color_left, :border_color_right, :border_color_top
safe_style_fetch(key, :border_color)
when :border_thickness_bottom, :border_thickness_left, :border_thickness_right, :border_thickness_top
visible? ? safe_style_fetch(key, :border_thickness) : 0
when :margin_bottom, :margin_left, :margin_right, :margin_top
visible? ? safe_style_fetch(key, :margin) : 0
when :padding_bottom, :padding_left, :padding_right, :padding_top
visible? ? safe_style_fetch(key, :padding) : 0
else
safe_style_fetch(key)
end
end
def safe_style_fetch(key, fallback_key = nil) def safe_style_fetch(key, fallback_key = nil)
# Attempt to return value for requested key # Attempt to return value for requested key
v = @style.hash.dig(@style_event, key) v = @style.send(@style_event)&.send(key) || @style.send(key)
return v if v return v if v
# Attempt to return overriding value # Attempt to return overriding value
if fallback_key if fallback_key
v = @style.hash.dig(@style_event, fallback_key) v = @style.send(@style_event)&.send(fallback_key) || @style.send(fallback_key)
return v if v return v if v
end end
@@ -92,86 +112,73 @@ module CyberarmEngine
end end
def set_color def set_color
@style.color = safe_style_fetch(:color) @text&.color = safe_style_fetch(:color)
@text&.color = @style.color
end end
def set_font def set_font
@text&.swap_font(safe_style_fetch(:text_size), safe_style_fetch(:font)) @text&.swap_font(resolution_scaled(safe_style_fetch(:text_size)), safe_style_fetch(:font))
end end
def set_background def set_background
@style.background = safe_style_fetch(:background) return unless bg = safe_style_fetch(:background)
@style.background_canvas.background = @style.background @background_canvas ||= Background.new
@background_canvas.background = bg
end end
def set_background_nine_slice def set_background_nine_slice
@style.background_nine_slice = safe_style_fetch(:background_nine_slice) return unless img = safe_style_fetch(:background_nine_slice)
@style.background_nine_slice_mode = safe_style_fetch(:background_nine_slice_mode) || :stretch @background_nine_slice_canvas ||= BackgroundNineSlice.new
@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) @background_nine_slice_canvas.image = img
@style.background_nine_slice_left = safe_style_fetch(:background_nine_slice_left, :background_nine_slice_from_edge) @background_nine_slice_canvas.x = @x + styled(:margin_left)
@style.background_nine_slice_top = safe_style_fetch(:background_nine_slice_top, :background_nine_slice_from_edge) @background_nine_slice_canvas.y = @y + styled(:margin_top)
@style.background_nine_slice_right = safe_style_fetch(:background_nine_slice_right, :background_nine_slice_from_edge) @background_nine_slice_canvas.z = @z
@style.background_nine_slice_bottom = safe_style_fetch(:background_nine_slice_bottom, :background_nine_slice_from_edge) @background_nine_slice_canvas.width = width
@background_nine_slice_canvas.height = height
@background_nine_slice_canvas.mode = safe_style_fetch(:background_nine_slice_mode) || :stretch
@background_nine_slice_canvas.color = safe_style_fetch(:background_nine_slice_color) || Gosu::Color::WHITE
@background_nine_slice_canvas.set_edges(
left: safe_style_fetch(:background_nine_slice_left, :background_nine_slice_from_edge) || 1,
top: safe_style_fetch(:background_nine_slice_top, :background_nine_slice_from_edge) || 1,
right: safe_style_fetch(:background_nine_slice_right, :background_nine_slice_from_edge) || 1,
bottom: safe_style_fetch(:background_nine_slice_bottom, :background_nine_slice_from_edge) || 1
)
end end
def set_background_image def set_background_image
@style.background_image = safe_style_fetch(:background_image) return unless img = safe_style_fetch(:background_image)
@style.background_image_mode = safe_style_fetch(:background_image_mode) || :stretch
@style.background_image_color = safe_style_fetch(:background_image_color) || Gosu::Color::WHITE @background_image_canvas ||= BackgroundImage.new
@style.background_image_canvas.mode = @style.background_image_mode
@style.background_image_canvas.color = @style.background_image_color @background_image_canvas.image = img
@background_image_canvas.mode = safe_style_fetch(:background_image_mode) || :stretch
@background_image_canvas.color = safe_style_fetch(:background_image_color) || Gosu::Color::WHITE
end end
def set_border_thickness def set_border
@style.border_thickness = safe_style_fetch(:border_thickness) return unless [
safe_style_fetch(:border_thickness_bottom, :border_thickness),
safe_style_fetch(:border_thickness_left, :border_thickness),
safe_style_fetch(:border_thickness_right, :border_thickness),
safe_style_fetch(:border_thickness_top, :border_thickness),
].any?(&:positive?)
@style.border_thickness_left = safe_style_fetch(:border_thickness_left, :border_thickness) @border_canvas ||= BorderCanvas.new(element: self)
@style.border_thickness_right = safe_style_fetch(:border_thickness_right, :border_thickness)
@style.border_thickness_top = safe_style_fetch(:border_thickness_top, :border_thickness)
@style.border_thickness_bottom = safe_style_fetch(:border_thickness_bottom, :border_thickness)
end
def set_border_color @border_canvas&.color = [
@style.border_color = safe_style_fetch(:border_color) styled(:border_color_top),
styled(:border_color_right),
@style.border_color_left = safe_style_fetch(:border_color_left, :border_color) styled(:border_color_bottom),
@style.border_color_right = safe_style_fetch(:border_color_right, :border_color) styled(:border_color_left)
@style.border_color_top = safe_style_fetch(:border_color_top, :border_color)
@style.border_color_bottom = safe_style_fetch(:border_color_bottom, :border_color)
@style.border_canvas.color = [
@style.border_color_top,
@style.border_color_right,
@style.border_color_bottom,
@style.border_color_left
] ]
end end
def set_padding
@style.padding = safe_style_fetch(:padding)
@style.padding_left = safe_style_fetch(:padding_left, :padding)
@style.padding_right = safe_style_fetch(:padding_right, :padding)
@style.padding_top = safe_style_fetch(:padding_top, :padding)
@style.padding_bottom = safe_style_fetch(:padding_bottom, :padding)
end
def set_margin
@style.margin = safe_style_fetch(:margin)
@style.margin_left = safe_style_fetch(:margin_left, :margin)
@style.margin_right = safe_style_fetch(:margin_right, :margin)
@style.margin_top = safe_style_fetch(:margin_top, :margin)
@style.margin_bottom = safe_style_fetch(:margin_bottom, :margin)
end
def update_styles(event = :default) def update_styles(event = :default)
old_width = width old_width = width
old_height = height old_height = height
@@ -208,6 +215,7 @@ module CyberarmEngine
event(:blur) event(:blur)
event(:changed) event(:changed)
event(:size_changed)
end end
def enter(_sender) def enter(_sender)
@@ -215,7 +223,7 @@ module CyberarmEngine
if !@enabled if !@enabled
update_styles(:disabled) update_styles(:disabled)
elsif @focus elsif @focus && !@style.active.empty?
update_styles(:active) update_styles(:active)
else else
update_styles(:hover) update_styles(:hover)
@@ -230,7 +238,7 @@ module CyberarmEngine
unless @enabled unless @enabled
update_styles(:disabled) update_styles(:disabled)
else else
update_styles(:active) update_styles(:active) unless @style.active.empty?
end end
window.current_state.focus = self window.current_state.focus = self
@@ -276,6 +284,7 @@ module CyberarmEngine
root.gui_state.request_repaint if @enabled != boolean root.gui_state.request_repaint if @enabled != boolean
@enabled = boolean @enabled = boolean
@style_event = :default if boolean && @style_event == :disabled
recalculate recalculate
@@ -322,15 +331,16 @@ module CyberarmEngine
return unless visible? return unless visible?
return unless element_visible? return unless element_visible?
@style.background_canvas.draw @background_canvas&.draw
@style.background_nine_slice_canvas.draw @background_nine_slice_canvas&.draw
@style.background_image_canvas.draw @background_image_canvas&.draw
@style.border_canvas.draw @border_canvas&.draw
render render
end end
def debug_draw def debug_draw
return if @debug == false || !CyberarmEngine::GUI_DEBUG # allow elements to opt out of debug drawing, makes debugging some things easier.
return if CyberarmEngine.const_defined?("GUI_DEBUG_ONLY_ELEMENT") && self.class == GUI_DEBUG_ONLY_ELEMENT return if CyberarmEngine.const_defined?("GUI_DEBUG_ONLY_ELEMENT") && self.class == GUI_DEBUG_ONLY_ELEMENT
Gosu.draw_line( Gosu.draw_line(
@@ -357,6 +367,11 @@ module CyberarmEngine
def update def update
recalculate_if_size_changed recalculate_if_size_changed
if @style.dirty?
@style.mark_clean!
stylize
end
end end
def button_down(id) def button_down(id)
@@ -373,13 +388,13 @@ module CyberarmEngine
end end
def hit?(x, y) def hit?(x, y)
x.between?(@x, @x + width) && x.between?(@x + styled(:margin_left), @x + styled(:margin_left) + width) &&
y.between?(@y, @y + height) y.between?(@y + styled(:margin_top), @y + styled(:margin_top) + height)
end end
def width def width
if visible? if visible?
inner_width + @width inner_width + content_width
else else
0 0
end end
@@ -394,16 +409,16 @@ module CyberarmEngine
end end
def outer_width def outer_width
@style.margin_left + width + @style.margin_right styled(:margin_left) + width + styled(:margin_right)
end end
def inner_width def inner_width
(@style.border_thickness_left + @style.padding_left) + (@style.padding_right + @style.border_thickness_right) (styled(:border_thickness_left) + styled(:padding_left)) + (styled(:padding_right) + styled(:border_thickness_right))
end end
def height def height
if visible? if visible?
inner_height + @height inner_height + content_height
else else
0 0
end end
@@ -418,11 +433,11 @@ module CyberarmEngine
end end
def outer_height def outer_height
@style.margin_top + height + @style.margin_bottom styled(:margin_top) + height + styled(:margin_bottom)
end end
def inner_height def inner_height
(@style.border_thickness_top + @style.padding_top) + (@style.padding_bottom + @style.border_thickness_bottom) (styled(:border_thickness_top) + styled(:padding_top)) + (styled(:padding_bottom) + styled(:border_thickness_bottom))
end end
def scroll_width def scroll_width
@@ -454,9 +469,9 @@ module CyberarmEngine
pairs_ << a_ unless pairs_.last == a_ pairs_ << a_ unless pairs_.last == a_
@cached_scroll_height = pairs_.sum { |pair| + @style.padding_top + @style.border_thickness_top + pair.map(&:outer_height).max } + @style.padding_bottom + @style.border_thickness_bottom @cached_scroll_height = pairs_.sum { |pair| pair.map(&:outer_height).max } + noncontent_height
else else
@cached_scroll_height = @style.padding_top + @style.border_thickness_top + @children.sum(&:outer_height) + @style.padding_bottom + @style.border_thickness_bottom @cached_scroll_height = noncontent_height + @children.sum(&:outer_height)
end end
end end
@@ -473,84 +488,67 @@ module CyberarmEngine
new_size = if size.is_a?(Float) && size.between?(0.0, 1.0) new_size = if size.is_a?(Float) && size.between?(0.0, 1.0)
(@parent.send(:"content_#{dimension}") * size).floor - send(:"noncontent_#{dimension}").floor (@parent.send(:"content_#{dimension}") * size).floor - send(:"noncontent_#{dimension}").floor
elsif size.is_a?(Numeric)
(size - send(:"noncontent_#{dimension}")).floor
else else
size size
end end
# Handle fill behavior # Handle fill behavior
if @parent && @style.fill && if @parent && styled(:fill) &&
(dimension == :width && @parent.is_a?(Flow) || (dimension == :width && @parent.is_a?(Flow) ||
dimension == :height && @parent.is_a?(Stack)) dimension == :height && @parent.is_a?(Stack))
new_size = space_available_width - noncontent_width if dimension == :width && @parent.is_a?(Flow) new_size = space_available_width - noncontent_width if dimension == :width && @parent.is_a?(Flow)
new_size = space_available_height - noncontent_height if dimension == :height && @parent.is_a?(Stack) new_size = space_available_height - noncontent_height if dimension == :height && @parent.is_a?(Stack)
end end
return @style.send(:"min_#{dimension}") if @style.send(:"min_#{dimension}") && new_size.to_f < @style.send(:"min_#{dimension}") return styled(:"min_#{dimension}") if styled(:"min_#{dimension}") && new_size.to_f < styled(:"min_#{dimension}")
return @style.send(:"max_#{dimension}") if @style.send(:"max_#{dimension}") && new_size.to_f > @style.send(:"max_#{dimension}") return styled(:"max_#{dimension}") if styled(:"max_#{dimension}") && new_size.to_f > styled(:"max_#{dimension}")
new_size new_size
end end
def space_available_width def space_available_width
# TODO: This may get expensive if there are a lot of children, probably should cache it somehow # TODO: This may get expensive if there are a lot of children, probably should cache it somehow
fill_siblings = @parent.children.select { |c| c.style.fill }.count.to_f # include self since we're dividing fill_siblings = @parent.children.select { |c| c.styled(:fill) }.count.to_f # include self since we're dividing
available_space = ((@parent.content_width - (@parent.children.reject { |c| c.style.fill }).map(&:outer_width).sum) / fill_siblings) available_space = ((@parent.content_width - (@parent.children.reject { |c| c.styled(:fill) }).map(&:outer_width).sum) / fill_siblings)
(available_space.nan? || available_space.infinite?) ? 0 : available_space.floor # The parent element might not have its dimensions, yet. (available_space.nan? || available_space.infinite?) ? 0 : available_space.floor # The parent element might not have its dimensions, yet.
end end
def space_available_height def space_available_height
# TODO: This may get expensive if there are a lot of children, probably should cache it somehow # TODO: This may get expensive if there are a lot of children, probably should cache it somehow
fill_siblings = @parent.children.select { |c| c.style.fill }.count.to_f # include self since we're dividing fill_siblings = @parent.children.select { |c| c.styled(:fill) && c.visible? }.count.to_f # include self since we're dividing
available_space = ((@parent.content_height - (@parent.children.reject { |c| c.style.fill }).map(&:outer_height).sum) / fill_siblings) available_space = ((@parent.content_height - (@parent.children.reject { |c| c.styled(:fill) }).map(&:outer_height).sum) / fill_siblings)
(available_space.nan? || available_space.infinite?) ? 0 : available_space.floor # The parent element might not have its dimensions, yet. (available_space.nan? || available_space.infinite?) ? 0 : available_space.floor # The parent element might not have its dimensions, yet.
end end
def background=(_background) def background=(_background)
root.gui_state.request_repaint root.gui_state.request_repaint
@style.background_canvas.background = _background @background_canvas.background = _background
update_background update_background
end end
def update_background def update_background
@style.background_canvas.x = @x @background_canvas&.x = @x + styled(:margin_left)
@style.background_canvas.y = @y @background_canvas&.y = @y + styled(:margin_top)
@style.background_canvas.z = @z @background_canvas&.z = @z
@style.background_canvas.width = width @background_canvas&.width = width
@style.background_canvas.height = height @background_canvas&.height = height
@style.background_canvas.update @background_canvas&.update
update_background_nine_slice set_background_nine_slice
update_background_image update_background_image
@style.border_canvas.update @border_canvas&.update
end end
def background_nine_slice=(_image_path) def background_nine_slice=(_image_path)
root.gui_state.request_repaint root.gui_state.request_repaint
@style.background_nine_slice_canvas.image = _image_path @background_nine_slice_canvas.image = _image_path
update_background_nine_slice set_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 end
def background_image=(image_path) def background_image=(image_path)
@@ -561,16 +559,18 @@ module CyberarmEngine
end end
def update_background_image def update_background_image
@style.background_image_canvas.x = @x return unless @background_image_canvas
@style.background_image_canvas.y = @y
@style.background_image_canvas.z = @z
@style.background_image_canvas.width = width
@style.background_image_canvas.height = height
@style.background_image_canvas.mode = @style.background_image_mode @background_image_canvas.x = @x + styled(:margin_left)
@style.background_image_canvas.color = @style.background_image_color @background_image_canvas.y = @y + styled(:margin_top)
@background_image_canvas.z = @z
@background_image_canvas.width = width
@background_image_canvas.height = height
@style.background_image_canvas.image = @style.background_image @background_image_canvas.mode = safe_style_fetch(:background_image_mode) || :stretch
@background_image_canvas.color = safe_style_fetch(:background_image_color) || Gosu::Color::WHITE
@background_image_canvas.image = safe_style_fetch(:background_image)
end end
def recalculate_if_size_changed def recalculate_if_size_changed
@@ -579,6 +579,8 @@ module CyberarmEngine
@old_width = width @old_width = width
@old_height = height @old_height = height
publish(:size_changed)
end end
end end
@@ -634,7 +636,7 @@ module CyberarmEngine
root.gui_state.request_recalculate if @parent && !is_a?(ToolTip) && (width != old_width || height != old_height) root.gui_state.request_recalculate if @parent && !is_a?(ToolTip) && (width != old_width || height != old_height)
root.gui_state.request_repaint if width != old_width || height != old_height root.gui_state.request_repaint if width != old_width || height != old_height
root.gui_state.menu.recalculate if root.gui_state.menu && root.gui_state.menu.parent == self root.gui_state.active_menu.recalculate if root.gui_state.active_menu && root.gui_state.active_menu.parent == self
end end
def layout def layout

View File

@@ -10,7 +10,7 @@ module CyberarmEngine
super(text_or_image, options, block) super(text_or_image, options, block)
@style.background_canvas.background = @style.background @background_canvas.background = styled(:background)
end end
def render def render
@@ -23,8 +23,8 @@ module CyberarmEngine
def draw_image def draw_image
@image.draw( @image.draw(
@style.border_thickness_left + @style.padding_left + @x, styled(:border_thickness_left) + styled(:margin_left) + styled(:padding_left) + @x,
@style.border_thickness_top + @style.padding_top + @y, styled(:border_thickness_top) + styled(:margin_top) + styled(:padding_top) + @y,
@z + 2, @z + 2,
@scale_x, @scale_y, @text.color @scale_x, @scale_y, @text.color
) )
@@ -35,20 +35,15 @@ module CyberarmEngine
end end
def layout def layout
unless @enabled @background_canvas.background = styled(:background)
@style.background_canvas.background = @style.disabled[:background] @text.color = styled(:color)
@text.color = @style.disabled[:color]
else
@style.background_canvas.background = @style.background
@text.color = @style.color
end
if @image if @image
@width = 0 @width = 0
@height = 0 @height = 0
_width = dimensional_size(@style.image_width, :width) _width = dimensional_size(styled(:image_width), :width)
_height = dimensional_size(@style.image_height, :height) _height = dimensional_size(styled(:image_height), :height)
if _width && _height if _width && _height
@scale_x = _width.to_f / @image.width @scale_x = _width.to_f / @image.width
@@ -78,6 +73,8 @@ module CyberarmEngine
end end
def value=(value) def value=(value)
old_value = self.value
if value.is_a?(Gosu::Image) if value.is_a?(Gosu::Image)
@image = value @image = value
else else
@@ -93,7 +90,9 @@ module CyberarmEngine
recalculate recalculate
end end
publish(:changed, self.value) publish(:changed, self.value) if value != old_value
self.value
end end
end end
end end

View File

@@ -46,8 +46,12 @@ module CyberarmEngine
end end
def value=(bool) def value=(bool)
old_value = this.value
@toggle_button.value = bool @toggle_button.value = bool
publish(:changed, @toggle_button.value) publish(:changed, bool) if bool != old_value
bool
end end
end end
end end

View File

@@ -7,13 +7,13 @@ module CyberarmEngine
attr_reader :children, :gui_state, :scroll_position, :scroll_target_position attr_reader :children, :gui_state, :scroll_position, :scroll_target_position
def self.current_container def self.current_container
@@current_container @current_container
end end
def self.current_container=(container) def self.current_container=(container)
raise ArgumentError, "Expected container to an an instance of CyberarmEngine::Element::Container, got #{container.class}" unless container.is_a?(CyberarmEngine::Element::Container) raise ArgumentError, "Expected container to an an instance of CyberarmEngine::Element::Container, got #{container.class}" unless container.is_a?(CyberarmEngine::Element::Container)
@@current_container = container @current_container = container
end end
def initialize(options = {}, block = nil) def initialize(options = {}, block = nil)
@@ -26,6 +26,11 @@ module CyberarmEngine
@scroll_chunk = 120 @scroll_chunk = 120
@scroll_speed = 40 @scroll_speed = 40
if @gui_state
@width = window.width
@height = window.height
end
@text_color = options[:color] @text_color = options[:color]
@children = [] @children = []
@@ -34,7 +39,7 @@ module CyberarmEngine
end end
def build def build
@block.call(self) if @block @block&.call(self)
root.gui_state.request_recalculate_for(self) root.gui_state.request_recalculate_for(self)
end end
@@ -53,7 +58,7 @@ module CyberarmEngine
old_container = CyberarmEngine::Element::Container.current_container old_container = CyberarmEngine::Element::Container.current_container
CyberarmEngine::Element::Container.current_container = self CyberarmEngine::Element::Container.current_container = self
block.call(self) if block block&.call(self)
CyberarmEngine::Element::Container.current_container = old_container CyberarmEngine::Element::Container.current_container = old_container
@@ -66,7 +71,7 @@ module CyberarmEngine
old_container = CyberarmEngine::Element::Container.current_container old_container = CyberarmEngine::Element::Container.current_container
CyberarmEngine::Element::Container.current_container = self CyberarmEngine::Element::Container.current_container = self
block.call(self) if block block&.call(self)
CyberarmEngine::Element::Container.current_container = old_container CyberarmEngine::Element::Container.current_container = old_container
@@ -75,33 +80,41 @@ module CyberarmEngine
def render def render
Gosu.clip_to( Gosu.clip_to(
@x + @style.border_thickness_left + @style.padding_left, @x - 1 + styled(:margin_left) + styled(:border_thickness_left) + styled(:padding_left),
@y + @style.border_thickness_top + @style.padding_top, @y - 1 + styled(:margin_top) + styled(:border_thickness_top) + styled(:padding_top),
content_width + 1, content_width + 1,
content_height + 1 content_height + 1
) do ) do
Gosu.translate(@scroll_position.x, @scroll_position.y) do Gosu.translate(@scroll_position.x, @scroll_position.y) do
@children.each(&:draw) @children.each(&:draw)
@children.each do |child|
next unless child.visible?
child.debug_draw
end
end end
end end
debug_draw
end end
def debug_draw def debug_draw
super super
@children.each do |child|
child.debug_draw
end
end end
def update def update
update_scroll if @style.scroll update_scroll if styled(:scroll)
@children.each(&:update) @children.each(&:update)
end end
def hit_element?(x, y) # return nil if element was not hit, or array of hit elements if hit, includes self.
def hit_element?(x, y, elements = [])
return unless hit?(x, y) return unless hit?(x, y)
elements << self
# Offset child hit point by scroll position/offset # Offset child hit point by scroll position/offset
child_x = x - @scroll_position.x child_x = x - @scroll_position.x
child_y = y - @scroll_position.y child_y = y - @scroll_position.y
@@ -111,20 +124,23 @@ module CyberarmEngine
case child case child
when Container when Container
if element = child.hit_element?(child_x, child_y) if (child.hit_element?(child_x, child_y, elements))
return element return elements
end end
else else
return child if child.hit?(child_x, child_y) if child.hit?(child_x, child_y)
elements << child
return elements
end
end end
end end
self if hit?(x, y) elements
end end
def update_child_element_visibity(child) def update_child_element_visibity(child)
child.element_visible = child.x >= (@x - @scroll_position.x) - child.width && child.x <= (@x - @scroll_position.x) + width && child.element_visible = child.x >= ((styled(:margin_left) + @x) - @scroll_position.x) - child.width && child.x <= ((styled(:margin_left) + @x) - @scroll_position.x) + width &&
child.y >= (@y - @scroll_position.y) - child.height && child.y <= (@y - @scroll_position.y) + height child.y >= ((styled(:margin_top) + @y) - @scroll_position.y) - child.height && child.y <= ((styled(:margin_top) + @y) - @scroll_position.y) + height
end end
def update_scroll def update_scroll
@@ -151,7 +167,7 @@ module CyberarmEngine
end end
def recalculate def recalculate
@current_position = Vector.new(@style.margin_left + @style.padding_left, @style.margin_top + @style.padding_top) @current_position = Vector.new(styled(:margin_left) + styled(:padding_left), styled(:margin_top) + styled(:padding_top))
return unless visible? return unless visible?
@@ -170,7 +186,7 @@ module CyberarmEngine
@cached_scroll_height = nil @cached_scroll_height = nil
if is_root? if is_root?
@width = @style.width = window.width @width = @style.width = window.width
@height = @style.height = window.height @height = @style.height = window.height
else else
@width = 0 @width = 0
@@ -179,39 +195,39 @@ module CyberarmEngine
_width = dimensional_size(@style.width, :width) _width = dimensional_size(@style.width, :width)
_height = dimensional_size(@style.height, :height) _height = dimensional_size(@style.height, :height)
@width = _width || (@children.map { |c| c.x + c.outer_width }.max || 0).floor @width = _width || (@children.map { |c| c.x + c.outer_width }.max.to_f - (styled(:margin_left) + styled(:padding_left))).floor
@height = _height || (@children.map { |c| c.y + c.outer_height }.max || 0).floor @height = _height || (@children.map { |c| c.y + c.outer_height }.max.to_f - (styled(:margin_top) + styled(:padding_top))).floor
end end
# FIXME: Correctly handle alignment when element has siblings # FIXME: Correctly handle alignment when element has siblings
# FIXME: Enable alignment for any element, not just containers # FIXME: Enable alignment for any element, not just containers
if @style.v_align if styled(:v_align)
space = space_available_height space = space_available_height
case @style.v_align case styled(:v_align)
when :center when :center
@y = parent.height / 2 - height / 2 @y = parent.y + parent.outer_height / 2 - outer_height / 2
when :bottom when :bottom
@y = parent.height - height @y = parent.y + parent.outer_height - outer_height
end end
end end
if @style.h_align if styled(:h_align)
space = space_available_width space = space_available_width
case @style.h_align case styled(:h_align)
when :center when :center
@x = parent.width / 2 - width / 2 @x = parent.x + parent.outer_width / 2 - outer_width / 2
when :right when :right
@x = parent.width - width @x = parent.x + parent.outer_width - outer_width
end end
end end
# t = Gosu.milliseconds # t = Gosu.milliseconds
# Move children to parent after positioning # Move children to parent after positioning
@children.each do |child| @children.each do |child|
child.x += (@x + @style.border_thickness_left) - style.margin_left child.x += (@x + styled(:border_thickness_left))
child.y += (@y + @style.border_thickness_top) - style.margin_top child.y += (@y + styled(:border_thickness_top))
child.stylize child.stylize
child.recalculate child.recalculate
@@ -221,7 +237,7 @@ module CyberarmEngine
update_child_element_visibity(child) update_child_element_visibity(child)
end end
# puts "TOOK: #{Gosu.milliseconds - t}ms to recalculate #{self.class}:0x#{self.object_id.to_s(16)}'s #{@children.count} children" # puts "TOOK: #{Gosu.milliseconds - t}ms to recalculate #{self.class}:0x#{object_id.to_s(16)}'s #{@children.count} children" if is_root?
update_background update_background
@@ -246,13 +262,6 @@ module CyberarmEngine
end end
def max_width def max_width
# _width = dimensional_size(@style.width, :width)
# if _width
# outer_width
# else
# window.width - (@parent ? @parent.style.margin_right + @style.margin_right : @style.margin_right)
# end
outer_width outer_width
end end
@@ -262,8 +271,8 @@ module CyberarmEngine
end end
def position_on_current_line(element) # Flow def position_on_current_line(element) # Flow
element.x = element.style.margin_left + @current_position.x element.x = @current_position.x
element.y = element.style.margin_top + @current_position.y element.y = @current_position.y
@current_position.x += element.outer_width @current_position.x += element.outer_width
end end
@@ -279,87 +288,87 @@ module CyberarmEngine
end end
def position_on_next_line(element) # Flow def position_on_next_line(element) # Flow
@current_position.x = @style.margin_left + @style.padding_left @current_position.x = styled(:margin_left) + styled(:padding_left)
@current_position.y += tallest_neighbor(element, @current_position.y).outer_height @current_position.y += tallest_neighbor(element, @current_position.y).outer_height
element.x = element.style.margin_left + @current_position.x element.x = @current_position.x
element.y = element.style.margin_top + @current_position.y element.y = @current_position.y
@current_position.x += element.outer_width @current_position.x += element.outer_width
end end
def move_to_next_line(element) # Stack def move_to_next_line(element) # Stack
element.x = element.style.margin_left + @current_position.x element.x = @current_position.x
element.y = element.style.margin_top + @current_position.y element.y = @current_position.y
@current_position.y += element.outer_height @current_position.y += element.outer_height
end end
def mouse_wheel_up(sender, x, y) def mouse_wheel_up(sender, x, y)
return unless @style.scroll return unless styled(:scroll)
# Allow overscrolling UP, only if one can scroll DOWN # Allow overscrolling UP, only if one can scroll DOWN
if height < scroll_height return unless height < scroll_height
if @scroll_target_position.y > 0
@scroll_target_position.y = @scroll_chunk
else
@scroll_target_position.y += @scroll_chunk
end
return :handled if @scroll_target_position.y.positive?
@scroll_target_position.y = @scroll_chunk
else
@scroll_target_position.y += @scroll_chunk
end end
:handled
end end
def mouse_wheel_down(sender, x, y) def mouse_wheel_down(sender, x, y)
return unless @style.scroll return unless styled(:scroll)
return unless height < scroll_height return unless height < scroll_height
if @scroll_target_position.y > 0 if @scroll_target_position.y.positive?
@scroll_target_position.y = -@scroll_chunk @scroll_target_position.y = -@scroll_chunk
else else
@scroll_target_position.y -= @scroll_chunk @scroll_target_position.y -= @scroll_chunk
end end
return :handled :handled
end end
def scroll_jump_to_top(sender, x, y) def scroll_jump_to_top(sender, x, y)
return unless @style.scroll return unless styled(:scroll)
@scroll_position.y = 0 @scroll_position.y = 0
@scroll_target_position.y = 0 @scroll_target_position.y = 0
return :handled :handled
end end
def scroll_jump_to_end(sender, x, y) def scroll_jump_to_end(sender, x, y)
return unless @style.scroll return unless styled(:scroll)
@scroll_position.y = -max_scroll_height @scroll_position.y = -max_scroll_height
@scroll_target_position.y = -max_scroll_height @scroll_target_position.y = -max_scroll_height
return :handled :handled
end end
def scroll_page_up(sender, x, y) def scroll_page_up(sender, x, y)
return unless @style.scroll return unless styled(:scroll)
@scroll_position.y += height @scroll_position.y += height
@scroll_position.y = 0 if @scroll_position.y > 0 @scroll_position.y = 0 if @scroll_position.y > 0
@scroll_target_position.y = @scroll_position.y @scroll_target_position.y = @scroll_position.y
return :handled :handled
end end
def scroll_page_down(sender, x, y) def scroll_page_down(sender, x, y)
return unless @style.scroll return unless styled(:scroll)
@scroll_position.y -= height @scroll_position.y -= height
@scroll_position.y = -max_scroll_height if @scroll_position.y < -max_scroll_height @scroll_position.y = -max_scroll_height if @scroll_position.y < -max_scroll_height
@scroll_target_position.y = @scroll_position.y @scroll_target_position.y = @scroll_position.y
return :handled :handled
end end
def scroll_top def scroll_top

View File

@@ -45,19 +45,23 @@ module CyberarmEngine
def draw_selection def draw_selection
selection_width = caret_position - selection_start_position selection_width = caret_position - selection_start_position
selection_start_line = calculate_line(@text_input.caret_pos)
selection_end_line = calculate_line(@text_input.selection_start)
Gosu.draw_rect(selection_start_position, @text.y, selection_width, @text.textobject.height, # pp [selection_width, selection_start_line, selection_end_line]
Gosu.draw_rect(selection_start_position, @text.y + selection_start_line * @text.textobject.height, selection_width, @text.textobject.height,
default(:selection_color), @z) default(:selection_color), @z)
end end
def text_input_position_for(_method) def text_input_position_for(method)
line = @text_input.text[0...@text_input.caret_pos].lines.last line = @text_input.text[0...@text_input.send(method)].lines.last
_x = @text.x + @offset_x _x = @text.x + @offset_x
if @type == :password if @type == :password
_x + @text.width(default(:password_character) * line.length) _x + @text.width(default(:password_character) * line.length) - @style.border_thickness_left
else else
_x + @text.width(line) _x + @text.width(line) - @style.border_thickness_left
end end
end end
@@ -66,9 +70,12 @@ module CyberarmEngine
@text_input.selection_start = @text_input.caret_pos = int @text_input.selection_start = @text_input.caret_pos = int
end end
def calculate_line(caret_pos = @text_input.caret_pos)
sub_text = @text_input.text[0...caret_pos].lines.size - 1
end
def calculate_active_line def calculate_active_line
sub_text = @text_input.text[0...@text_input.caret_pos] @active_line = calculate_line(@text_input.caret_pos)
@active_line = sub_text.lines.size - 1
end end
def caret_stay_left_of_last_newline def caret_stay_left_of_last_newline
@@ -79,8 +86,20 @@ module CyberarmEngine
end end
def caret_position_under_mouse(mouse_x, mouse_y) def caret_position_under_mouse(mouse_x, mouse_y)
active_line = row_at(mouse_y) # get y scroll offset of element to get EditBox's selected row
right_offset = column_at(mouse_x, mouse_y) y_scroll_offset = 0
e = parent
while (e)
if e.is_a?(Container)
y_scroll_offset += e.scroll_position.y
# root element has no parent so loop will terminate
e = e.parent
end
end
active_line = row_at(mouse_y - y_scroll_offset)
right_offset = column_at(mouse_x, mouse_y - y_scroll_offset)
buffer = @text_input.text.lines[0..active_line].join if active_line != 0 buffer = @text_input.text.lines[0..active_line].join if active_line != 0
buffer = @text_input.text.lines.first if active_line == 0 buffer = @text_input.text.lines.first if active_line == 0
@@ -164,6 +183,8 @@ module CyberarmEngine
pos = text.length pos = text.length
end end
pp [direction, pos, @active_line]
set_position(pos) set_position(pos)
end end

View File

@@ -197,9 +197,9 @@ module CyberarmEngine
def text_input_position_for(method) def text_input_position_for(method)
if @type == :password if @type == :password
@text.x + @text.width(default(:password_character) * @text_input.text[0...@text_input.send(method)].length) - @style.border_thickness_left @text.x + @text.width(default(:password_character) * @text_input.text[0...@text_input.send(method)].length) - styled(:border_thickness_left)
else else
@text.x + @text.width(@text_input.text[0...@text_input.send(method)]) - @style.border_thickness_left @text.x + @text.width(@text_input.text[0...@text_input.send(method)]) - styled(:border_thickness_left)
end end
end end
@@ -291,6 +291,8 @@ module CyberarmEngine
def value=(string) def value=(string)
@text_input.text = string @text_input.text = string
string
end end
end end
end end

View File

@@ -14,10 +14,10 @@ module CyberarmEngine
def render def render
@image.draw( @image.draw(
@style.border_thickness_left + @style.padding_left + @x, styled(:margin_left) + styled(:border_thickness_left) + styled(:padding_left) + @x,
@style.border_thickness_top + @style.padding_top + @y, styled(:margin_top) + styled(:border_thickness_top) + styled(:padding_top) + @y,
@z + 2, @z + 2,
@scale_x, @scale_y, @style.color @scale_x, @scale_y, styled(:color)
) )
end end
@@ -56,12 +56,18 @@ module CyberarmEngine
end end
def value=(path_or_image, retro: false, tileable: false) def value=(path_or_image, retro: false, tileable: false)
old_value = self.value
@path = path_or_image if path_or_image.is_a?(String) @path = path_or_image if path_or_image.is_a?(String)
@image = Gosu::Image.new(path_or_image, retro: retro, tileable: tileable) if @path @image = Gosu::Image.new(path_or_image, retro: retro, tileable: tileable) if @path
@image = path_or_image unless @path @image = path_or_image unless @path
recalculate recalculate
publish(:changed, @image) if old_value != self.value
self.value
end end
def path def path

View File

@@ -10,7 +10,7 @@ module CyberarmEngine
super(@choose, options, block) super(@choose, options, block)
@style.background_canvas.background = default(:background) @background_canvas.background = default(:background)
@menu = Menu.new(parent: self, theme: @options[:theme]) @menu = Menu.new(parent: self, theme: @options[:theme])
@@ -21,7 +21,7 @@ module CyberarmEngine
super super
w = @text.textobject.text_width("") w = @text.textobject.text_width("")
@text.textobject.draw_text("", @x + content_width - w, @y + @style.padding_top, @z, 1, 1, @text.color) @text.textobject.draw_text("", @x + content_width - w, @y + styled(:padding_top), @z, 1, 1, @text.color)
end end
def choose=(item) def choose=(item)
@@ -56,7 +56,7 @@ module CyberarmEngine
def show_menu def show_menu
@menu.clear do @menu.clear do
@menu.style.width = width @menu.style.width = width - (@menu.styled(:border_thickness_left) + @menu.styled(:border_thickness_right))
@items.each do |item| @items.each do |item|
# prevent already selected item from appearing in list # prevent already selected item from appearing in list

View File

@@ -11,10 +11,10 @@ module CyberarmEngine
# FIXME: properly find scrollable parent, if any. # FIXME: properly find scrollable parent, if any.
parent_scroll_top = parent&.parent ? parent.parent.scroll_top : 0 parent_scroll_top = parent&.parent ? parent.parent.scroll_top : 0
@x = @parent.x @x = @parent.x + @parent.styled(:border_thickness_left)
@y = parent_scroll_top + @parent.y + @parent.height @y = parent_scroll_top + @parent.y + @parent.height + @parent.styled(:border_thickness_top)
@y = (parent_scroll_top + @parent.y) - height if @y + height > window.height @y = (parent_scroll_top + @parent.y + @parent.styled(:border_thickness_top)) - height if @y + height > window.height
end end
def show def show

View File

@@ -11,7 +11,7 @@ module CyberarmEngine
@marquee_offset = 0 @marquee_offset = 0
@marquee_animation_time = Gosu.milliseconds @marquee_animation_time = Gosu.milliseconds
@type = options[:type] || :linear @type = options[:type] || :linear
@fraction_background = Background.new(background: @style.fraction_background) @fraction_background = Background.new(background: styled(:fraction_background))
self.value = options[:fraction] || 0.0 self.value = options[:fraction] || 0.0
end end
@@ -31,13 +31,13 @@ module CyberarmEngine
def update_background def update_background
super super
@fraction_background.x = (@style.border_thickness_left + @style.padding_left + @x) + @marquee_offset @fraction_background.x = (styled(:border_thickness_left) + styled(:padding_left) + @x) + @marquee_offset
@fraction_background.y = @style.border_thickness_top + @style.padding_top + @y @fraction_background.y = styled(:border_thickness_top) + styled(:padding_top) + @y
@fraction_background.z = @z @fraction_background.z = @z
@fraction_background.width = @width * (@type == :marquee ? @marquee_width : @fraction) @fraction_background.width = @width * (@type == :marquee ? @marquee_width : @fraction)
@fraction_background.height = @height @fraction_background.height = @height
@fraction_background.background = @style.fraction_background @fraction_background.background = styled(:fraction_background)
end end
def update def update
@@ -83,9 +83,11 @@ module CyberarmEngine
@fraction = decimal.clamp(0.0, 1.0) @fraction = decimal.clamp(0.0, 1.0)
update_background update_background
root.gui_state.request_repaint if @fraction != old_value if @fraction != old_value
root.gui_state.request_repaint
publish(:changed, @fraction)
end
publish(:changed, @fraction)
@fraction @fraction
end end
end end

View File

@@ -62,10 +62,10 @@ module CyberarmEngine
end end
def position_handle def position_handle
@handle.x = @x + @handle.style.margin_left + @style.padding_left + @style.border_thickness_left + @handle.x = styled(:margin_left) + @x + @handle.styled(:margin_left) + styled(:padding_left) + styled(:border_thickness_left) +
((content_width - @handle.outer_width) * (@value - @range.min) / (@range.max - @range.min).to_f) ((content_width - @handle.outer_width) * (@value - @range.min) / (@range.max - @range.min).to_f)
@handle.y = @y + @handle.style.margin_top + @style.border_thickness_top + @style.padding_top @handle.y = styled(:margin_top) + @y + @handle.styled(:margin_top) + styled(:border_thickness_top) + styled(:padding_top)
end end
def draw def draw
@@ -94,13 +94,17 @@ module CyberarmEngine
end end
def value=(n) def value=(n)
old_value = this.value
@value = n @value = n
position_handle position_handle
@handle.recalculate @handle.recalculate
root.gui_state.request_repaint root.gui_state.request_repaint
publish(:changed, @value) publish(:changed, @value) if old_value != n
n
end end
end end
end end

View File

@@ -8,6 +8,7 @@ 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],
static: @options[:text_static], static: @options[:text_static],
alignment: @options[:text_align],
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: @options[:text_border],
@@ -16,6 +17,8 @@ module CyberarmEngine
) )
@raw_text = text @raw_text = text
@text_width = @text.width
@text_height = @text.height
end end
def update def update
@@ -29,8 +32,8 @@ module CyberarmEngine
def render def render
# Gosu.clip_to is too expensive to always use so check if we actually need it. # Gosu.clip_to is too expensive to always use so check if we actually need it.
if @text.width > width || @text.height > height if @text_width > width || @text_height > height
Gosu.clip_to(@x, @y, width, height) do Gosu.clip_to(@text.x, @text.y, @width, @height) do
@text.draw @text.draw
end end
else else
@@ -39,11 +42,18 @@ module CyberarmEngine
end end
def layout def layout
unless @enabled @text.color = styled(:color)
@text.color = @style.disabled[:color] @text.alignment = styled(:text_align)
else
@text.color = @style.color @text.shadow = styled(:text_shadow)
end @text.shadow_color = styled(:text_shadow_color)
@text.shadow_alpha = styled(:text_shadow_alpha)
@text.shadow_size = styled(:text_shadow_size)
@text.border = styled(:text_border)
@text.border_color = styled(:text_border_color)
@text.border_alpha = styled(:text_border_alpha)
@text.border_size = styled(:text_border_size)
@width = 0 @width = 0
@height = 0 @height = 0
@@ -53,37 +63,41 @@ module CyberarmEngine
handle_text_wrapping(_width) handle_text_wrapping(_width)
@width = _width || @text.width.floor # Update cached text width and height
@height = _height || @text.height.floor @text_width = @text.width
@text_height = @text.height
@text.y = @style.border_thickness_top + @style.padding_top + @y @width = _width || @text_width.floor
@height = _height || @text_height.floor
@text.y = styled(:margin_top) + styled(:border_thickness_top) + styled(:padding_top) + @y
@text.z = @z + 3 @text.z = @z + 3
if (text_alignment = @options[:text_align] || @options[:text_h_align]) if (text_alignment = @options[:text_align] || @options[:text_h_align])
case text_alignment case text_alignment
when :left when :left
@text.x = @style.border_thickness_left + @style.padding_left + @x @text.x = styled(:margin_left) + styled(:border_thickness_left) + styled(:padding_left) + @x
when :center when :center
@text.x = if @text.width <= width @text.x = if @text_width <= width
@x + width / 2 - @text.width / 2 @x + styled(:margin_left) + width / 2 - @text_width / 2
else # Act as left aligned else # Act as left aligned
@style.border_thickness_left + @style.padding_left + @x styled(:margin_left) + styled(:border_thickness_left) + styled(:padding_left) + @x
end end
when :right when :right
@text.x = @x + outer_width - (@text.width + @style.border_thickness_right + @style.padding_right) @text.x = @x + styled(:margin_left) + outer_width - (@text_width + styled(:border_thickness_right) + styled(:padding_right))
end end
end end
if (vertical_alignment = @options[:text_v_align]) if (vertical_alignment = @options[:text_v_align])
case vertical_alignment case vertical_alignment
when :center when :center
@text.y = if @text.height <= height @text.y = if @text_height <= height
@y + height / 2 - @text.height / 2 @y + styled(:margin_top) + height / 2 - @text_height / 2
else else
@style.border_thickness_top + @style.padding_top + @y styled(:margin_top) + styled(:border_thickness_top) + styled(:padding_top) + @y
end end
when :bottom when :bottom
@text.y = @y + outer_height - (@text.height + @style.border_thickness_bottom + @style.padding_bottom) @text.y = @y + styled(:margin_top) + outer_height - (@text_height + styled(:border_thickness_bottom) + styled(:padding_bottom))
end end
end end
@@ -93,7 +107,7 @@ module CyberarmEngine
def handle_text_wrapping(max_width) def handle_text_wrapping(max_width)
max_width ||= @parent&.content_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 = styled(:text_wrap)
copy = @raw_text.to_s.dup copy = @raw_text.to_s.dup
# Only perform text wrapping: if it is enabled, is possible to wrap, and text is too long to fit on one line # Only perform text wrapping: if it is enabled, is possible to wrap, and text is too long to fit on one line
@@ -180,9 +194,12 @@ module CyberarmEngine
recalculate recalculate
end end
root.gui_state.request_repaint if old_value != @raw_text if old_value != @raw_text
root.gui_state.request_repaint
publish(:changed, @raw_text)
end
publish(:changed, self.value) value
end end
end end
@@ -208,6 +225,9 @@ module CyberarmEngine
end end
class ToolTip < TextBlock class ToolTip < TextBlock
def needs_repaint?
@needs_repaint || !value.to_s.empty?
end
end end
class Link < TextBlock class Link < TextBlock

View File

@@ -50,6 +50,8 @@ module CyberarmEngine
end end
def value=(boolean) def value=(boolean)
old_value = self.value
@value = boolean @value = boolean
if boolean if boolean
@@ -60,7 +62,9 @@ module CyberarmEngine
recalculate recalculate
publish(:changed, @value) publish(:changed, @value) if old_value != boolean
boolean
end end
end end
end end

View File

@@ -0,0 +1,45 @@
module CyberarmEngine
class Element
# Special container that has a layout like a Flow
# and makes all its children mirror its styling (i.e. hover, active, disabled...)
class Widget < Flow
def update_styles(style = :default)
super
@children.each do |child|
recursive_styles(style, self)
end
end
# Make child elements mirror the widgets styling
# disabled elements will not have their styling overridden
def recursive_styles(style, container)
container.children.each do |child|
child.update_styles(style)
recursive_styles(style, child) if child.is_a?(Container)
end
end
# Enable child elements to display their tooltips
# but fall back to the Widget if no hit element has a tip
def tip
parent_scroll_position = CyberarmEngine::Vector.new
element = self
while (parent_element = element.parent)
parent_scroll_position += parent_element.scroll_position
element = parent_element
end
elements = hit_element?(window.mouse_x - parent_scroll_position.x, window.mouse_y - parent_scroll_position.y)
return @tip unless elements
elements.delete(self) # prevent infinite recursive loop (Widget#tip)
elements.reverse.find { |e| !e.tip.empty? }&.tip || @tip
end
end
end
end

View File

@@ -13,7 +13,7 @@ module CyberarmEngine
def publish(event, *args) def publish(event, *args)
raise ArgumentError, "#{self.class} does not handle #{event.inspect}" unless @event_handler.include?(event) raise ArgumentError, "#{self.class} does not handle #{event.inspect}" unless @event_handler.include?(event)
return unless enabled? return if !enabled? && event != :changed
was_handled = false was_handled = false

View File

@@ -6,6 +6,7 @@ module CyberarmEngine
def initialize(options = {}) def initialize(options = {})
@options = options @options = options
@game_objects = [] @game_objects = []
@timers = []
@global_pause = false @global_pause = false
@down_keys = {} @down_keys = {}
@@ -17,6 +18,10 @@ module CyberarmEngine
@active_width = window.width @active_width = window.width
@active_height = window.height @active_height = window.height
@pages = {}
@page_host = nil
@page = nil
@menu = nil @menu = nil
@focus = nil @focus = nil
@mouse_over = nil @mouse_over = nil
@@ -26,6 +31,7 @@ module CyberarmEngine
@dragging_element = nil @dragging_element = nil
@pending_recalculate_request = false @pending_recalculate_request = false
@pending_element_recalculate_requests = [] @pending_element_recalculate_requests = []
@hit_elements = []
@needs_repaint = false @needs_repaint = false
@@ -38,14 +44,45 @@ module CyberarmEngine
@tip = Element::ToolTip.new("", parent: @root_container, z: Float::INFINITY, theme: current_theme) @tip = Element::ToolTip.new("", parent: @root_container, z: Float::INFINITY, theme: current_theme)
end end
def menu def active_menu
@menu @menu
end end
def page(klass, options = {})
@page&.blur
@pages[klass] = klass.new(parent: options[:parent] || self) unless @pages[klass]
@page = @pages[klass]
@page.options = options
page_host.clear do
@page.setup
end
@page.focus
end
def current_page
@page
end
def page_host
return @page.parent if @page.parent.is_a?(CyberarmEngine::Element::Container)
return @page_host if @page_host && @page_host.is_a?(CyberarmEngine::Element::Container)
@root_container
end
def page_host=(container)
raise "page host must be a CyberarmEngine::Element::Container" unless container.is_a?(Container)
@page_host = container
end
# throws :blur event to focused element and sets GuiState focused element # throws :blur event to focused element and sets GuiState focused element
# Does NOT throw :focus event at element or set element as focused # Does NOT throw :focus event at element or set element as focused
def focus=(element) def focus=(element)
@focus.publish(:blur) if @focus && element && @focus != element @focus.publish(:blur) if @focus && element && @focus != element
@hit_elements.delete(@focus)
@focus = element @focus = element
end end
@@ -54,24 +91,37 @@ module CyberarmEngine
end end
def draw def draw
# t = Gosu.milliseconds
# report_recalculate_time = @pending_recalculate_request || @pending_element_recalculate_requests.size.positive?
StackProf.start(mode: :wall) if RUBY_ENGINE != "mruby" && defined?(StackProf)
Stats.frame.start_timing(:gui_element_recalculate_requests) Stats.frame.start_timing(:gui_element_recalculate_requests)
# puts "PENDING REQUESTS: #{@pending_element_recalculate_requests.size}" if @pending_element_recalculate_requests.size.positive? # puts "PENDING REQUESTS: #{@pending_element_recalculate_requests.size}" if @pending_element_recalculate_requests.size.positive?
@pending_element_recalculate_requests.each(&:recalculate) while (e = @pending_element_recalculate_requests.shift)
@pending_element_recalculate_requests.clear # its pointless to recalculate individual elements when we've been asked to recalculate the whole gui
if @pending_recalculate_request
@pending_element_recalculate_requests.clear
break
end
e.recalculate
end
Stats.frame.end_timing(:gui_element_recalculate_requests) Stats.frame.end_timing(:gui_element_recalculate_requests)
if @pending_recalculate_request Stats.frame.start_timing(:gui_recalculate)
Stats.frame.start_timing(:gui_recalculate)
@root_container.recalculate
while(@pending_recalculate_request)
@pending_recalculate_request = false @pending_recalculate_request = false
Stats.frame.end_timing(:gui_recalculate) @root_container.recalculate
end end
StackProf.stop if RUBY_ENGINE != "mruby" && defined?(StackProf)
Stats.frame.end_timing(:gui_recalculate)
# puts "TOOK: #{Gosu.milliseconds - t}ms to recalculate #{self.class}:0x#{object_id.to_s(16)}" if report_recalculate_time && Gosu.milliseconds - t > 0
super super
if @menu if @menu
@@ -92,6 +142,8 @@ module CyberarmEngine
end end
@needs_repaint = false @needs_repaint = false
@page&.draw
end end
def needs_repaint? def needs_repaint?
@@ -121,22 +173,37 @@ module CyberarmEngine
return unless window.has_focus? return unless window.has_focus?
return unless window.current_state == self return unless window.current_state == self
new_mouse_over = @menu.hit_element?(window.mouse_x, window.mouse_y) if @menu # list of containers decending down to hit element
new_mouse_over ||= @root_container.hit_element?(window.mouse_x, window.mouse_y) new_hit_elements = (@menu || @root_container).hit_element?(window.mouse_x, window.mouse_y)
# element the mouse is over, if any.
new_mouse_over = new_hit_elements&.find { |c| c.is_a?(CyberarmEngine::Element::Widget) } || new_hit_elements&.last
if new_mouse_over # is the currently hit element the same as last hit element?
new_mouse_over.publish(:enter) if new_mouse_over != @mouse_over same_element = @mouse_over && new_mouse_over == @mouse_over
new_mouse_over.publish(:hover)
# puts "#{new_mouse_over.class}[#{new_mouse_over.value}]: #{new_mouse_over.x}:#{new_mouse_over.y} #{new_mouse_over.width}:#{new_mouse_over.height}" if new_mouse_over != @mouse_over unless @hit_elements == new_hit_elements
added_hit_elements = (new_hit_elements || []) - @hit_elements
removed_hit_elements = @hit_elements - (new_hit_elements || [])
added_hit_elements.each do |e|
e.publish(:enter)
e.publish(:hover)
end
removed_hit_elements.each do |e|
e.publish(:leave)
end
end end
@mouse_over.publish(:leave) if @mouse_over && new_mouse_over != @mouse_over
@mouse_over = new_mouse_over @mouse_over = new_mouse_over
@hit_elements = new_hit_elements || []
redirect_holding_mouse_button(:left) if @mouse_over && Gosu.button_down?(Gosu::MS_LEFT) redirect_holding_mouse_button(:left) if @mouse_over && Gosu.button_down?(Gosu::MS_LEFT)
redirect_holding_mouse_button(:middle) if @mouse_over && Gosu.button_down?(Gosu::MS_MIDDLE) redirect_holding_mouse_button(:middle) if @mouse_over && Gosu.button_down?(Gosu::MS_MIDDLE)
redirect_holding_mouse_button(:right) if @mouse_over && Gosu.button_down?(Gosu::MS_RIGHT) redirect_holding_mouse_button(:right) if @mouse_over && Gosu.button_down?(Gosu::MS_RIGHT)
if Vector.new(window.mouse_x, window.mouse_y) == @last_mouse_pos # handle tooltip
if same_element
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.x = window.mouse_x
@@ -156,6 +223,8 @@ module CyberarmEngine
@last_mouse_pos = Vector.new(window.mouse_x, window.mouse_y) @last_mouse_pos = Vector.new(window.mouse_x, window.mouse_y)
@mouse_pos = @last_mouse_pos.clone @mouse_pos = @last_mouse_pos.clone
@page&.update
end end
def button_down(id) def button_down(id)
@@ -170,9 +239,14 @@ module CyberarmEngine
redirect_mouse_button(:right) redirect_mouse_button(:right)
when Gosu::KB_F5 when Gosu::KB_F5
request_recalculate request_recalculate
when Gosu::KB_F12
CyberarmEngine.const_set(:GUI_DEBUG, !CyberarmEngine::GUI_DEBUG)
request_repaint
end end
@focus.button_down(id) if @focus.respond_to?(:button_down) @focus.button_down(id) if @focus.respond_to?(:button_down)
@page&.button_down(id)
end end
def button_up(id) def button_up(id)
@@ -203,6 +277,8 @@ module CyberarmEngine
# Prevents menu from popping back up if the listbox is clicked to hide it. # Prevents menu from popping back up if the listbox is clicked to hide it.
@hid_menu_for = nil @hid_menu_for = nil
@page&.button_up(id)
end end
def tool_tip_delay def tool_tip_delay
@@ -213,6 +289,7 @@ module CyberarmEngine
hide_menu unless @menu && (@menu == @mouse_over) || (@mouse_over&.parent == @menu) hide_menu unless @menu && (@menu == @mouse_over) || (@mouse_over&.parent == @menu)
if @focus && @mouse_over != @focus if @focus && @mouse_over != @focus
@hit_elements.delete(@focus)
@focus.publish(:blur) @focus.publish(:blur)
@focus = nil @focus = nil
end end
@@ -266,10 +343,14 @@ module CyberarmEngine
end end
def redirect_scroll_jump_to(edge) def redirect_scroll_jump_to(edge)
return if window.text_input
@mouse_over.publish(:"scroll_jump_to_#{edge}", window.mouse_x, window.mouse_y) if (@mouse_over && !@menu) || (@mouse_over && @mouse_over == @menu) @mouse_over.publish(:"scroll_jump_to_#{edge}", window.mouse_x, window.mouse_y) if (@mouse_over && !@menu) || (@mouse_over && @mouse_over == @menu)
end end
def redirect_scroll_page(edge) def redirect_scroll_page(edge)
return if window.text_input
@mouse_over.publish(:"scroll_page_#{edge}", window.mouse_x, window.mouse_y) if (@mouse_over && !@menu) || (@mouse_over && @mouse_over == @menu) @mouse_over.publish(:"scroll_page_#{edge}", window.mouse_x, window.mouse_y) if (@mouse_over && !@menu) || (@mouse_over && @mouse_over == @menu)
end end

View File

@@ -0,0 +1,39 @@
module CyberarmEngine
class Page
include CyberarmEngine::DSL
include CyberarmEngine::Common
attr_reader :parent
def initialize(parent:)
@parent = parent
@options = {}
end
def options=(options)
@options = options
end
def setup
end
def focus
end
def blur
end
def draw
end
def update
end
def button_down(id)
end
def button_up(id)
end
end
end

View File

@@ -16,35 +16,79 @@ module Gosu
end end
module CyberarmEngine module CyberarmEngine
class Style class StyleData
attr_reader :hash
def initialize(hash = {}) def initialize(hash = {})
h = hash @hash = hash
# h = Marshal.load(Marshal.dump(hash)) @dirty = false
h[:default] = {}
h.each do |key, value|
next if value.is_a?(Hash)
h[:default][key] = value
end
@hash = h
end end
def method_missing(method, *args) %i[
if method.to_s.end_with?("=") x y z design_width width height min_width min_height max_width max_height color background
raise "Did not expect more than 1 argument" if args.size > 1 background_image background_image_mode background_image_color
background_nine_slice background_nine_slice_mode background_nine_slice_color background_nine_slice_from_edge
background_nine_slice_left background_nine_slice_top background_nine_slice_right background_nine_slice_bottom
border_color border_color_left border_color_right border_color_top border_color_bottom
border_thickness border_thickness_left border_thickness_right border_thickness_top border_thickness_bottom
padding padding_left padding_right padding_top padding_bottom
margin margin_left margin_right margin_top margin_bottom
aspect_ratio
@hash[method.to_s.sub("=", "").to_sym] = args.first fraction_background scroll fill text_wrap v_align h_align delay tag font
text_align text_static text_size
elsif args.empty? text_shadow text_shadow_size text_shadow_alpha text_shadow_color
@hash[method] text_border text_border_size text_border_alpha text_border_color
else image_width image_height
raise ArgumentError, "Did not expect arguments" ].each do |item|
define_method(item) do
@hash[item]
end end
define_method(:"#{item}=") do |value|
@dirty = true if @hash[item] != value
@hash[item] = value
end
end
# NOTE: do not change return value
def default
nil
end
def dirty?
@dirty
end
def mark_clean!
@dirty = false
end
def empty?
@hash.empty?
end
end
class Style < StyleData
attr_reader :hash, :hover, :active, :disabled
def initialize(hash = {})
@hash = hash
@hover = StyleData.new(hash[:hover] || {})
@active = StyleData.new(hash[:active] || {})
@disabled = StyleData.new(hash[:disabled] || {})
@substyles = [@hover, @active, @disabled]
super
end
def dirty?
@dirty || @substyles.any?(&:dirty?)
end
def mark_clean!
@substyles.each(&:mark_clean!)
@dirty = false
end end
end end
end end

View File

@@ -30,6 +30,11 @@ module CyberarmEngine
end end
end end
# css-like style classes, however First In, First Out- that is,
options[:style_class]&.each do |klass|
hash = deep_merge(hash, _theme[klass]) if _theme[klass]
end
deep_merge(hash, options) deep_merge(hash, options)
end end
@@ -53,6 +58,7 @@ module CyberarmEngine
y: 0, y: 0,
z: 30, z: 30,
design_width: nil,
width: nil, width: nil,
height: nil, height: nil,
color: Gosu::Color::WHITE, color: Gosu::Color::WHITE,
@@ -68,25 +74,25 @@ module CyberarmEngine
debug_color: Gosu::Color::YELLOW debug_color: Gosu::Color::YELLOW
}, },
Button: { # < Label Button: { # < TextBlock
margin: 1, margin: 1,
padding: 4, padding: 4,
border_thickness: 1, border_thickness: 1,
border_color: ["ffd59674".hex, "ffff8746".hex], border_color: [0xffd59674, 0xffff8746],
border_radius: 0, border_radius: 0,
background: ["ffc75e61".to_i(16), "ffe26623".to_i(16)], background: [0xffc75e61, 0xffe26623],
text_align: :center, text_align: :center,
text_v_align: :center, text_v_align: :center,
text_wrap: :none, text_wrap: :none,
hover: { hover: {
color: Gosu::Color.rgb(200, 200, 200), color: Gosu::Color.rgb(200, 200, 200),
background: ["ffB23E41".to_i(16), "ffFF7C00".to_i(16)] background: [0xffB23E41, 0xffFF7C00]
}, },
active: { active: {
color: Gosu::Color::BLACK, color: Gosu::Color::BLACK,
background: ["ffB23E41".to_i(16)] background: [0xffB23E41]
}, },
disabled: { disabled: {
@@ -121,7 +127,13 @@ 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_shadow_color: 0,
text_shadow_alpha: 30,
text_shadow_size: 2,
text_border: false, text_border: false,
text_border_color: 0,
text_border_alpha: 30,
text_border_size: 2,
text_align: :left, text_align: :left,
font: "Arial", font: "Arial",
margin: 0, margin: 0,

View File

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