Compare commits

...

22 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
27 changed files with 532 additions and 136 deletions

View File

@@ -10,6 +10,7 @@ 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/result"
require_relative "cyberarm_engine/event_bus"
require_relative "cyberarm_engine/common" require_relative "cyberarm_engine/common"
@@ -58,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"
@@ -67,6 +69,8 @@ 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"

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,4 +1,6 @@
module CyberarmEngine module CyberarmEngine
GUI_DEBUG = false
module Common module Common
ImageBlob = Data.define(:to_blob, :columns, :rows) ImageBlob = Data.define(:to_blob, :columns, :rows)

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

@@ -9,7 +9,7 @@ module CyberarmEngine
end end
def okay? def okay?
!@error !error?
end end
def error? def error?
@@ -17,4 +17,3 @@ module CyberarmEngine
end end
end 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

@@ -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,13 +1,13 @@
module CyberarmEngine module CyberarmEngine
module DSL module DSL
def every(milliseconds, &block) def every(milliseconds, &block)
element_parent.gui_state.add_timer( element_parent.root.gui_state.add_timer(
CyberarmEngine::Timer.new(milliseconds, true, &block) CyberarmEngine::Timer.new(milliseconds, true, &block)
) )
end end
def after(milliseconds, &block) def after(milliseconds, &block)
element_parent.gui_state.add_timer( element_parent.root.gui_state.add_timer(
CyberarmEngine::Timer.new(milliseconds, false, &block) CyberarmEngine::Timer.new(milliseconds, false, &block)
) )
end end
@@ -20,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
@@ -112,6 +116,18 @@ 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.background = color element_parent.style.background = color
end end

View File

@@ -18,7 +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] @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)
@@ -36,7 +36,7 @@ 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
@background_canvas = nil # Background.new @background_canvas = nil # Background.new
@@ -44,7 +44,7 @@ module CyberarmEngine
@background_image_canvas = nil # BackgroundImage.new @background_image_canvas = nil # BackgroundImage.new
@border_canvas = nil # BorderCanvas.new(element: self) @border_canvas = nil # BorderCanvas.new(element: self)
@style_event = :default @style_event = enabled? ? :default : :disabled
stylize stylize
@@ -53,7 +53,15 @@ 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
@@ -73,11 +81,11 @@ module CyberarmEngine
when :border_color_bottom, :border_color_left, :border_color_right, :border_color_top when :border_color_bottom, :border_color_left, :border_color_right, :border_color_top
safe_style_fetch(key, :border_color) safe_style_fetch(key, :border_color)
when :border_thickness_bottom, :border_thickness_left, :border_thickness_right, :border_thickness_top when :border_thickness_bottom, :border_thickness_left, :border_thickness_right, :border_thickness_top
safe_style_fetch(key, :border_thickness) visible? ? safe_style_fetch(key, :border_thickness) : 0
when :margin_bottom, :margin_left, :margin_right, :margin_top when :margin_bottom, :margin_left, :margin_right, :margin_top
safe_style_fetch(key, :margin) visible? ? safe_style_fetch(key, :margin) : 0
when :padding_bottom, :padding_left, :padding_right, :padding_top when :padding_bottom, :padding_left, :padding_right, :padding_top
safe_style_fetch(key, :padding) visible? ? safe_style_fetch(key, :padding) : 0
else else
safe_style_fetch(key) safe_style_fetch(key)
end end
@@ -108,7 +116,7 @@ module CyberarmEngine
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
@@ -135,10 +143,12 @@ module CyberarmEngine
@background_nine_slice_canvas.color = safe_style_fetch(:background_nine_slice_color) || Gosu::Color::WHITE @background_nine_slice_canvas.color = safe_style_fetch(:background_nine_slice_color) || Gosu::Color::WHITE
@background_nine_slice_canvas.left = safe_style_fetch(:background_nine_slice_left, :background_nine_slice_from_edge) @background_nine_slice_canvas.set_edges(
@background_nine_slice_canvas.top = safe_style_fetch(:background_nine_slice_top, :background_nine_slice_from_edge) left: safe_style_fetch(:background_nine_slice_left, :background_nine_slice_from_edge) || 1,
@background_nine_slice_canvas.right = safe_style_fetch(:background_nine_slice_right, :background_nine_slice_from_edge) top: safe_style_fetch(:background_nine_slice_top, :background_nine_slice_from_edge) || 1,
@background_nine_slice_canvas.bottom = safe_style_fetch(:background_nine_slice_bottom, :background_nine_slice_from_edge) 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
@@ -205,6 +215,7 @@ module CyberarmEngine
event(:blur) event(:blur)
event(:changed) event(:changed)
event(:size_changed)
end end
def enter(_sender) def enter(_sender)
@@ -212,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)
@@ -227,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
@@ -273,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
@@ -325,12 +337,10 @@ module CyberarmEngine
@border_canvas&.draw @border_canvas&.draw
render render
debug_draw if @debug
end end
def debug_draw def debug_draw
return if @debug == false # allow elements to opt out of debug drawing, makes debugging some things easier. 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(
@@ -384,7 +394,7 @@ module CyberarmEngine
def width def width
if visible? if visible?
inner_width + @width inner_width + content_width
else else
0 0
end end
@@ -408,7 +418,7 @@ module CyberarmEngine
def height def height
if visible? if visible?
inner_height + @height inner_height + content_height
else else
0 0
end end
@@ -459,9 +469,9 @@ module CyberarmEngine
pairs_ << a_ unless pairs_.last == a_ pairs_ << a_ unless pairs_.last == a_
@cached_scroll_height = pairs_.sum { |pair| + styled(:padding_top) + styled(:border_thickness_top) + pair.map(&:outer_height).max } + styled(:padding_bottom) + styled(:border_thickness_bottom) @cached_scroll_height = pairs_.sum { |pair| pair.map(&:outer_height).max } + noncontent_height
else else
@cached_scroll_height = styled(:padding_top) + styled(:border_thickness_top) + @children.sum(&:outer_height) + styled(:padding_bottom) + styled(:border_thickness_bottom) @cached_scroll_height = noncontent_height + @children.sum(&:outer_height)
end end
end end
@@ -478,6 +488,8 @@ 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
@@ -506,7 +518,7 @@ module CyberarmEngine
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.styled(: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.styled(: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.
@@ -567,6 +579,8 @@ module CyberarmEngine
@old_width = width @old_width = width
@old_height = height @old_height = height
publish(:size_changed)
end end
end end
@@ -622,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

@@ -23,8 +23,8 @@ module CyberarmEngine
def draw_image def draw_image
@image.draw( @image.draw(
styled(:border_thickness_left) + styled(:padding_left) + @x, styled(:border_thickness_left) + styled(:margin_left) + styled(:padding_left) + @x,
styled(:border_thickness_top) + styled(: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,13 +35,8 @@ module CyberarmEngine
end end
def layout def layout
unless @enabled @background_canvas.background = styled(:background)
@background_canvas.background = @style.disabled.background @text.color = styled(:color)
@text.color = @style.disabled.color
else
@background_canvas.background = styled(:background)
@text.color = styled(:color)
end
if @image if @image
@width = 0 @width = 0
@@ -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

@@ -88,14 +88,20 @@ module CyberarmEngine
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(&:debug_draw)
end end
def update def update
@@ -103,9 +109,12 @@ module CyberarmEngine
@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
@@ -115,15 +124,18 @@ 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)
@@ -194,9 +206,9 @@ module CyberarmEngine
case styled(: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
@@ -205,9 +217,9 @@ module CyberarmEngine
case styled(: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
@@ -276,7 +288,7 @@ module CyberarmEngine
end end
def position_on_next_line(element) # Flow def position_on_next_line(element) # Flow
@current_position.x = 0 @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 = @current_position.x element.x = @current_position.x

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
@@ -92,7 +99,7 @@ module CyberarmEngine
end end
active_line = row_at(mouse_y - y_scroll_offset) active_line = row_at(mouse_y - y_scroll_offset)
right_offset = column_at(mouse_x, mouse_y) 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
@@ -176,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

@@ -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,8 +14,8 @@ module CyberarmEngine
def render def render
@image.draw( @image.draw(
styled(:border_thickness_left) + styled(:padding_left) + @x, styled(:margin_left) + styled(:border_thickness_left) + styled(:padding_left) + @x,
styled(:border_thickness_top) + styled(:padding_top) + @y, styled(:margin_top) + styled(:border_thickness_top) + styled(:padding_top) + @y,
@z + 2, @z + 2,
@scale_x, @scale_y, styled(:color) @scale_x, @scale_y, styled(:color)
) )
@@ -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

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

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

@@ -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],
@@ -41,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 = styled(: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
@@ -186,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
@@ -214,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

@@ -18,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
@@ -27,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
@@ -39,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
@@ -62,7 +98,15 @@ module CyberarmEngine
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.shift(&:recalculate) while (e = @pending_element_recalculate_requests.shift)
# 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)
@@ -98,6 +142,8 @@ module CyberarmEngine
end end
@needs_repaint = false @needs_repaint = false
@page&.draw
end end
def needs_repaint? def needs_repaint?
@@ -127,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
@@ -162,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)
@@ -176,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)
@@ -209,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
@@ -219,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

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

@@ -23,7 +23,7 @@ module CyberarmEngine
end end
%i[ %i[
x y z width height min_width min_height max_width max_height color background x y z design_width width height min_width min_height max_width max_height color background
background_image background_image_mode background_image_color 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 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 background_nine_slice_left background_nine_slice_top background_nine_slice_right background_nine_slice_bottom
@@ -33,7 +33,10 @@ module CyberarmEngine
margin margin_left margin_right margin_top margin_bottom margin margin_left margin_right margin_top margin_bottom
aspect_ratio aspect_ratio
fraction_background scroll fill text_wrap v_align h_align delay tag font text_size fraction_background scroll fill text_wrap v_align h_align delay tag font
text_align text_static text_size
text_shadow text_shadow_size text_shadow_alpha text_shadow_color
text_border text_border_size text_border_alpha text_border_color
image_width image_height image_width image_height
].each do |item| ].each do |item|
define_method(item) do define_method(item) do
@@ -58,6 +61,10 @@ module CyberarmEngine
def mark_clean! def mark_clean!
@dirty = false @dirty = false
end end
def empty?
@hash.empty?
end
end end
class Style < StyleData class Style < StyleData

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,7 +74,7 @@ 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,
@@ -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,