Refactored Container to be an Element, removed button_up/button_down passing to elements, moved Gui out of GameState and into GuiState, added scaffolding for events (Publish/Subscribe pattern). Element input is broken ATM.

This commit is contained in:
2019-03-01 09:41:51 -06:00
parent 04ec6682ee
commit 18ff0a1ea9
15 changed files with 258 additions and 234 deletions

View File

@@ -11,6 +11,8 @@ require_relative "cyberarm_engine/objects/text"
require_relative "cyberarm_engine/objects/timer"
require_relative "cyberarm_engine/objects/multi_line_text"
require_relative "cyberarm_engine/ui/theme"
require_relative "cyberarm_engine/ui/event"
require_relative "cyberarm_engine/ui/element"
require_relative "cyberarm_engine/ui/label"
require_relative "cyberarm_engine/ui/button"
@@ -24,3 +26,4 @@ require_relative "cyberarm_engine/ui/stack"
require_relative "cyberarm_engine/ui/dsl"
require_relative "cyberarm_engine/game_state"
require_relative "cyberarm_engine/ui/gui_state"

View File

@@ -23,5 +23,9 @@ module CyberarmEngine
def draw_rect(x, y, width, height, color, z = 0)
$window.draw_rect(x,y,width,height,color,z)
end
def window
$window
end
end
end

View File

@@ -1,4 +1,6 @@
module CyberarmEngine
Vector = Struct.new(:x, :y, :z, :weight)
class Engine < Gosu::Window
attr_accessor :show_cursor
attr_reader :current_state, :last_frame_time

View File

@@ -3,7 +3,6 @@ module CyberarmEngine
INSTANCES = []
IMAGES = {}
SAMPLES= {}
Vertex = Struct.new(:x, :y)
attr_accessor :image, :x, :y, :z, :angle, :center_x, :center_y, :scale_x, :scale_y,
:color, :alpha, :mode, :options, :paused, :radius, :last_x, :last_y
attr_reader :world_center_point
@@ -31,7 +30,7 @@ module CyberarmEngine
@paused = false
@speed = 0
@debug_color = Gosu::Color::GREEN
@world_center_point = Vertex.new(0,0)
@world_center_point = Vector.new(0,0)
setup
@debug_text = MultiLineText.new("", color: @debug_color, y: self.y-(self.height*self.scale), z: 9999)
@debug_text.x = self.x
@@ -157,7 +156,7 @@ module CyberarmEngine
_x = @x+(ahead_by*Math.cos(direction))
_y = @y+(ahead_by*Math.sin(direction))
return direction if angle_only
return Vertex.new(_x, _y) unless angle_only
return Vector.new(_x, _y) unless angle_only
end
def show_debug_heading

View File

@@ -1,7 +1,6 @@
module CyberarmEngine
class GameState
include Common
include DSL
attr_accessor :options, :global_pause
attr_reader :game_objects, :containers
@@ -11,10 +10,6 @@ module CyberarmEngine
@game_objects = []
@global_pause = false
@root_container = Stack.new(x: 0, y: 0, width: $window.width, height: $window.height)
@game_objects << @root_container
@containers = [@root_container]
setup
end
@@ -34,6 +29,12 @@ module CyberarmEngine
@game_objects = nil
end
def down_up(id)
@game_objects.each do |o|
o.button_down(id)
end
end
def button_up(id)
@game_objects.each do |o|
o.button_up(id)

View File

@@ -9,7 +9,7 @@ module CyberarmEngine
@text = text || ""
@options = options
@size = options[:size] || 18
@font = options[:font] || Gosu.default_font_name
@font = options[:font] || "sans-serif"#Gosu.default_font_name
@x = options[:x] || 0
@y = options[:y] || 0
@z = options[:z] || 1025

View File

@@ -9,6 +9,8 @@ module CyberarmEngine
@text.draw
end
def mouse_over?; false; end
def draw_button
$window.draw_rect(relative_x, relative_y, width, height, @options[:element_background], @z+1)

View File

@@ -1,11 +1,10 @@
module CyberarmEngine
class Container
class Container < Element
include Common
attr_accessor :stroke_color, :fill_color, :background_color, :x, :y, :z, :width, :height
attr_reader :elements, :children, :options, :parent
attr_reader :scroll_x, :scroll_y, :internal_width, :internal_height
attr_reader :origin_x, :origin_y, :origin_width, :origin_height
attr_reader :children, :options, :parent
attr_reader :scroll_x, :scroll_y
def initialize(options = {}, block = nil)
@parent = options[:parent] || nil
@@ -29,21 +28,18 @@ module CyberarmEngine
raise "#{self.class} 'height' must be a number" unless height.is_a?(Numeric)
raise "#{self.class} 'options' must be a Hash" unless options.is_a?(Hash)
@x, @y, @z, @width, @height, @internal_width, @internal_height = x, y, z, width, height, width, height
@x, @y, @z, @width, @height, = x, y, z, width, height
@origin_x, @origin_x = @x, @x
@origin_width, @origin_height = @width, @height
@scroll_x, @scroll_y = 0, 0
@scroll_speed = 10
@layout = options[:layout] || :match_parent # or :wrap_content
@block = block
@options = options
@text_color = options[:text_color] || Element::THEME[:stroke]
@background_color = Element::THEME[:background]
@elements = []
@children = []
@theme = {}
@@ -58,16 +54,8 @@ module CyberarmEngine
recalculate
end
def add_child(container)
@children << container
@elements << container
recalculate
@internal_height+=container.height
end
def add(element)
@elements << element
@children << element
recalculate
end
@@ -76,72 +64,16 @@ module CyberarmEngine
Gosu.clip_to(@x, @y, @width, @height) do
background
Gosu.translate(@scroll_x, @scroll_y) do
@elements.each(&:draw)
end
@children.each(&:draw)
end
end
def update
@elements.each(&:update)
end
def button_up(id)
unless active_container
@children.each {|child| child.button_up(id)}
return
end
case id
when Gosu::MsWheelUp
scroll_down if mouse_over? && active_container
when Gosu::MsWheelDown
scroll_up if mouse_over? && active_container
end
@elements.each {|e| if e.active_element; e.button_up(id) end } if mouse_over? && active_container
end
def scroll_down
return if @height == @internal_height
puts "ROOT down #{$window.current_state.containers.first.scroll_y}"
puts "#{@scroll_y} -> internal_height: #{@internal_height}, height: #{@height}, #{@y}"
@scroll_y += @scroll_speed
if @scroll_y > 0
@scroll_y = 0
@parent.scroll_down if @parent
end
end
def scroll_up
return if @height == @internal_height
puts "ROOT UP #{$window.current_state.containers.first.scroll_y}"
@scroll_y -= @scroll_speed
puts "#{@scroll_y} -> internal_height: #{@internal_height}, height: #{@height}, #{@y}"
if @scroll_y < @height - @internal_height
@scroll_y = @height - @internal_height
@parent.scroll_up if @parent
end
end
def deep_scroll_y
scroll = @scroll_y
arch = @parent if parent
while(arch)
scroll += arch.scroll_y
arch = arch.parent
end
return scroll
@children.each(&:update)
end
def mouse_over?
$window.mouse_x.between?(@x + @scroll_x, (@x + @scroll_x) + @width) && $window.mouse_y.between?(@y + @scroll_y, (@y + @scroll_y) + @height)
$window.mouse_x.between?(@x, @x + @width) && $window.mouse_y.between?(@y, @y + @height)
end
def theme
@@ -160,116 +92,52 @@ module CyberarmEngine
Gosu.draw_rect(@x, @y, @width, @height, @background_color, @z)
end
def active_container
active = true
if mouse_over?
@children.each do |child|
if child.mouse_over?
active = false
break
def hit_element?(x, y)
@children.reverse_each do |child|
case child
when Container
if element = child.hit_element?(x, y)
return element
end
else
return child if child.hit?(x, y)
end
end
return active
end
def active_element
false
self if hit?(x, y)
end
def recalculate
raise "mode was not defined!" unless @mode
@current_position = Vector.new(@x, @y)
if @parent
neighbors = @parent.children.size > 0 ? @parent.children.size : 1
if @layout == :match_parent
if @mode == :flow
@width = @parent.width
@height = @parent.height / neighbors
else # :stack
@width = @parent.width / neighbors
@height = @parent.height
end
else # :wrap_content
raise "Not implemented"
end
else
@width = $window.width
@height = $window.height
layout
end
position_elements
puts "<#{self.class} X: #{@x}, Y: #{@y}, width: #{@width}, height: #{@height}, internal_width: #{@internal_width}, internal_height: #{@internal_height} (children: #{@children.count})"
def layout
raise "Not overridden"
end
def position_elements
@packing_x = 0
@packing_y = 0
widest_element = nil
last_element = nil
@elements.each do |element|
flow(element) if @mode == :flow
stack(element) if @mode == :stack
if element.is_a?(Element)
widest_element ||= element
highest_element ||= element
widest_element = element if element.width > widest_element.width
last_element = element
def max_width
@max_width ? @max_width : window.width
end
margin = 0
margin = element.margin if defined?(element.margin)
case @mode
when :flow
@internal_width += element.width + margin unless @origin_width.nonzero?
@internal_height = element.height + margin if element.height + margin > @internal_height + margin unless @origin_height.nonzero?
when :stack
@internal_width = element.width + margin if element.width + margin > @internal_width + margin unless @origin_width.nonzero?
@internal_height += element.height + margin unless @origin_height.nonzero?
end
def fits_on_line?(element)
@current_position.x + element.width <= max_width
end
# @internal_width = @width if @width < @internal_width
# @internal_height = @height if @height < @internal_height
def position_on_current_line(element)
element.x = @current_position.x
element.y = @current_position.y
@current_position.x += element.width
# @internal_width += widest_element.margin if widest_element && !@origin_width.nonzero?
# @internal_height += last_element.margin if last_element && !@origin_height.nonzero?
@children.each(&:recalculate)
@current_position.x = @x if @current_position.x >= max_width
end
def flow(element)
element.x = @packing_x
if element.is_a?(Container)
element.y = @y
else
element.y = 0
end
element.recalculate
@packing_x += element.margin if defined?(element.margin)
@packing_x += element.width
end
def stack(element)
if element.is_a?(Container)
element.x = @x
else
element.x = 0
end
element.y = @packing_y
element.recalculate
@packing_y += element.margin if defined?(element.margin)
@packing_y += element.height
def move_to_next_line(element)
element.x = @current_position.x
element.y = @current_position.y
@current_position.y += element.height
end
end
end

View File

@@ -6,7 +6,7 @@ module CyberarmEngine
_container = Flow.new(options, block)
@containers << _container
_container.build
options[:parent].add_child(_container)
options[:parent].add(_container)
@containers.pop
return _container
@@ -18,7 +18,7 @@ module CyberarmEngine
_container = Stack.new(options, block)
@containers << _container
_container.build
options[:parent].add_child(_container)
options[:parent].add(_container)
@containers.pop
return _container

View File

@@ -1,47 +1,9 @@
module CyberarmEngine
class Element
DEFAULTS = {
x: 0,
y: 0,
z: 30,
include Theme
include Event
width: 0,
height: 0
}
THEME = {
stroke: Gosu::Color::WHITE,
fill: Gosu::Color::NONE,
background: Gosu::Color::NONE,
checkmark: "", # √
padding: 20,
margin: 2,
element_background: Gosu::Color.rgb(12,12,12),
interactive_stroke: Gosu::Color::WHITE,
interactive_active_stroke: Gosu::Color::GRAY,
interactive_background: Gosu::Color::GRAY,
interactive_hover_background: Gosu::Color.rgb(100, 100, 100),
interactive_active_background: Gosu::Color.rgb(50, 50, 50),
interactive_border_size: 1,
edit_line_width: 200,
edit_line_password_character: "", # •
caret_width: 2,
caret_color: Gosu::Color.rgb(50,50,25),
caret_interval: 500,
image_retro: false,
text_size: 22,
text_shadow: true,
font: "Consolas"
}
attr_accessor :x, :y, :z, :width, :height, :padding, :margin, :focus
attr_accessor :x, :y, :z, :width, :height, :padding, :margin, :enabled
def initialize(options = {}, block = nil)
@parent = options[:parent] # parent Container (i.e. flow/stack)
@@ -53,13 +15,23 @@ module CyberarmEngine
@y = options[:y]
@z = options[:z]
@fixed_x = @x if @x != 0
@fixed_y = @y if @y != 0
@width = options[:width]
@height = options[:width]
@max_width = @width if @width != 0
@max_height = @height if @height != 0
@padding = options[:padding]
@margin = options[:margin]
@focus = false
@enabled = true
end
def enabled?
@enabled
end
def draw
@@ -74,14 +46,9 @@ module CyberarmEngine
def button_up(id)
end
def mouse_over?
@parent.mouse_over? &&
$window.mouse_x.between?(relative_x, relative_x + width) &&
$window.mouse_y.between?(relative_y, relative_y + height)
end
def active_element
mouse_over? || @focus
def hit?(x, y)
x.between?(relative_x, relative_x + width) &&
y.between?(relative_y, relative_y + height)
end
def width
@@ -93,11 +60,11 @@ module CyberarmEngine
end
def relative_x
@parent.x + @parent.scroll_x + @x + @margin
@x# + @margin
end
def relative_y
@parent.y + @parent.scroll_y + @y + @margin
@y# + @margin
end
def recalculate

View File

@@ -0,0 +1,21 @@
module CyberarmEngine
module Event
def subscribe(event, method = nil, &block)
end
def unsubscribe(event)
end
def publish(event, *args)
# block.call(*args)
end
def event(event)
@event_handler ||= Hash.new
@event_handler[event] ||= []
end
end
class Subscription
end
end

View File

@@ -6,5 +6,20 @@ module CyberarmEngine
@mode = :flow
super
end
def layout
@children.each do |child|
if fits_on_line?(child)
position_on_current_line(child)
else
move_to_next_line(child)
end
child.recalculate
end
@width = @max_width ? @max_width : (@children.map {|c| c.x + c.width}.max || 0)
@height = @max_height ? @max_height : (@children.map {|c| c.y + c.height}.max || 0)
end
end
end

View File

@@ -0,0 +1,87 @@
module CyberarmEngine
class GuiState < GameState
include Common
include DSL
def initialize(options = {})
@options = options
@game_objects = []
@global_pause = false
@root_container = Stack.new
@game_objects << @root_container
@containers = [@root_container]
@focus = nil
@mouse_over = nil
@mouse_down_on = {}
@mouse_down_position = {}
setup
end
def focus=(element)
@focus.publish(:blur) if @focus and element
@focus = element
end
def update
super
new_mouse_over = @root_container.hit_element?(window.mouse_x, window.mouse_y)
@mouse_over.publish(:leave) if @mouse_over && new_mouse_over != @mouse_over
@mouse_over = new_mouse_over
redirect_holding_mouse_button(:left) if @mouse_over && Gosu.button_down?(Gosu::MsLeft)
redirect_holding_mouse_button(:middle) if @mouse_over && Gosu.button_down?(Gosu::MsMiddle)
redirect_holding_mouse_button(:right) if @mouse_over && Gosu.button_down?(Gosu::MsRight)
end
def button_down(id)
super
case id
when Gosu::MsLeft
redirect_mouse_button(:left)
when Gosu::MsMiddle
redirect_mouse_button(:middle)
when Gosu::MsRight
redirect_mouse_button(:right)
end
end
def button_up(id)
super
case id
when Gosu::MsLeft
redirect_released_mouse_button(:left)
when Gosu::MsMiddle
redirect_released_mouse_button(:middle)
when Gosu::MsRight
redirect_released_mouse_button(:right)
when Gosu::MsWheelUp
redirect_mouse_wheel(:up)
when Gosu::MsWheelDown
redirect_mouse_wheel(:down)
end
end
def redirect_mouse_button(button)
@mouse_over.publish(:"released_#{button}_mouse_button", window.mouse_x, window.mouse_y) if @mouse_over
end
def redirect_released_mouse_button(button)
@mouse_over.publish(:"released_#{button}_mouse_button", window.mouse_x, window.mouse_y) if @mouse_over
end
def redirect_holding_mouse_button(button)
@mouse_over.publish(:"holding_#{button}_mouse_button", window.mouse_x, window.mouse_y) if @mouse_over
end
def redirect_mouse_wheel(button)
@mouse_over.publish(:"scroll_#{button}", window.mouse_x, window.mouse_y) if @mouse_over
end
end
end

View File

@@ -6,5 +6,16 @@ module CyberarmEngine
@mode = :stack
super
end
def layout
@children.each do |child|
move_to_next_line(child)
child.recalculate
end
@width = @max_width ? @max_width : (@children.map {|c| c.x + c.width}.max || 0)
@height = @max_height ? @max_height : (@children.map {|c| c.y + c.height}.max || 0)
end
end
end

View File

@@ -0,0 +1,44 @@
module CyberarmEngine
module Theme
DEFAULTS = {
x: 0,
y: 0,
z: 30,
width: 0,
height: 0
}
THEME = {
stroke: Gosu::Color::WHITE,
fill: Gosu::Color::NONE,
background: Gosu::Color::NONE,
checkmark: "", # √
padding: 20,
margin: 2,
element_background: Gosu::Color.rgb(12,12,12),
interactive_stroke: Gosu::Color::WHITE,
interactive_active_stroke: Gosu::Color::GRAY,
interactive_background: Gosu::Color::GRAY,
interactive_hover_background: Gosu::Color.rgb(100, 100, 100),
interactive_active_background: Gosu::Color.rgb(50, 50, 50),
interactive_border_size: 1,
edit_line_width: 200,
edit_line_password_character: "", # •
caret_width: 2,
caret_color: Gosu::Color.rgb(50,50,25),
caret_interval: 500,
image_retro: false,
text_size: 22,
text_shadow: true,
font: "Consolas"
}
end
end