diff --git a/lib/cyberarm_engine.rb b/lib/cyberarm_engine.rb index 3fdf0cb..7771e1c 100644 --- a/lib/cyberarm_engine.rb +++ b/lib/cyberarm_engine.rb @@ -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" diff --git a/lib/cyberarm_engine/common.rb b/lib/cyberarm_engine/common.rb index 37a8265..b02a3fe 100644 --- a/lib/cyberarm_engine/common.rb +++ b/lib/cyberarm_engine/common.rb @@ -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 \ No newline at end of file diff --git a/lib/cyberarm_engine/engine.rb b/lib/cyberarm_engine/engine.rb index 80c3219..0c6f421 100644 --- a/lib/cyberarm_engine/engine.rb +++ b/lib/cyberarm_engine/engine.rb @@ -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 diff --git a/lib/cyberarm_engine/game_object.rb b/lib/cyberarm_engine/game_object.rb index 0bd675f..4ff1191 100644 --- a/lib/cyberarm_engine/game_object.rb +++ b/lib/cyberarm_engine/game_object.rb @@ -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 diff --git a/lib/cyberarm_engine/game_state.rb b/lib/cyberarm_engine/game_state.rb index ebc2b0a..b14e109 100644 --- a/lib/cyberarm_engine/game_state.rb +++ b/lib/cyberarm_engine/game_state.rb @@ -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) diff --git a/lib/cyberarm_engine/objects/text.rb b/lib/cyberarm_engine/objects/text.rb index 16e2103..3e5a4cc 100644 --- a/lib/cyberarm_engine/objects/text.rb +++ b/lib/cyberarm_engine/objects/text.rb @@ -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 @@ -93,4 +93,4 @@ module CyberarmEngine def update; end end -end \ No newline at end of file +end diff --git a/lib/cyberarm_engine/ui/button.rb b/lib/cyberarm_engine/ui/button.rb index 447d653..a1389a3 100644 --- a/lib/cyberarm_engine/ui/button.rb +++ b/lib/cyberarm_engine/ui/button.rb @@ -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) diff --git a/lib/cyberarm_engine/ui/container.rb b/lib/cyberarm_engine/ui/container.rb index b460f98..b93331a 100644 --- a/lib/cyberarm_engine/ui/container.rb +++ b/lib/cyberarm_engine/ui/container.rb @@ -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 - 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})" + layout 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 - 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 - end - - # @internal_width = @width if @width < @internal_width - # @internal_height = @height if @height < @internal_height - - # @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) + def layout + raise "Not overridden" 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 + def max_width + @max_width ? @max_width : window.width end - def stack(element) - if element.is_a?(Container) - element.x = @x - else - element.x = 0 - end - element.y = @packing_y - element.recalculate + def fits_on_line?(element) + @current_position.x + element.width <= max_width + end - @packing_y += element.margin if defined?(element.margin) - @packing_y += element.height + def position_on_current_line(element) + element.x = @current_position.x + element.y = @current_position.y + @current_position.x += element.width + + @current_position.x = @x if @current_position.x >= max_width + end + + def move_to_next_line(element) + element.x = @current_position.x + element.y = @current_position.y + @current_position.y += element.height end end end \ No newline at end of file diff --git a/lib/cyberarm_engine/ui/dsl.rb b/lib/cyberarm_engine/ui/dsl.rb index 0e32b8d..48c9dea 100644 --- a/lib/cyberarm_engine/ui/dsl.rb +++ b/lib/cyberarm_engine/ui/dsl.rb @@ -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 diff --git a/lib/cyberarm_engine/ui/element.rb b/lib/cyberarm_engine/ui/element.rb index 94114ba..0360356 100644 --- a/lib/cyberarm_engine/ui/element.rb +++ b/lib/cyberarm_engine/ui/element.rb @@ -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 diff --git a/lib/cyberarm_engine/ui/event.rb b/lib/cyberarm_engine/ui/event.rb new file mode 100644 index 0000000..55d727b --- /dev/null +++ b/lib/cyberarm_engine/ui/event.rb @@ -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 \ No newline at end of file diff --git a/lib/cyberarm_engine/ui/flow.rb b/lib/cyberarm_engine/ui/flow.rb index f57b7ba..0d86592 100644 --- a/lib/cyberarm_engine/ui/flow.rb +++ b/lib/cyberarm_engine/ui/flow.rb @@ -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 \ No newline at end of file diff --git a/lib/cyberarm_engine/ui/gui_state.rb b/lib/cyberarm_engine/ui/gui_state.rb new file mode 100644 index 0000000..9f71140 --- /dev/null +++ b/lib/cyberarm_engine/ui/gui_state.rb @@ -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 \ No newline at end of file diff --git a/lib/cyberarm_engine/ui/stack.rb b/lib/cyberarm_engine/ui/stack.rb index 0c26484..9fe97b2 100644 --- a/lib/cyberarm_engine/ui/stack.rb +++ b/lib/cyberarm_engine/ui/stack.rb @@ -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 \ No newline at end of file diff --git a/lib/cyberarm_engine/ui/theme.rb b/lib/cyberarm_engine/ui/theme.rb new file mode 100644 index 0000000..ea84e53 --- /dev/null +++ b/lib/cyberarm_engine/ui/theme.rb @@ -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 \ No newline at end of file