lib/cyberarm_engine/ui/element.rb in cyberarm_engine-0.19.0 vs lib/cyberarm_engine/ui/element.rb in cyberarm_engine-0.19.1

- old
+ new

@@ -1,488 +1,488 @@ -module CyberarmEngine - class Element - include Theme - include Event - include Common - - attr_accessor :x, :y, :z, :enabled, :tip - attr_reader :parent, :options, :style, :event_handler, :background_canvas, :border_canvas - - def initialize(options = {}, block = nil) - @parent = options.delete(:parent) # parent Container (i.e. flow/stack) - options = theme_defaults(options) - @options = options - @block = block - - @focus = @options[:focus].nil? ? false : @options[:focus] - @enabled = @options[:enabled].nil? ? true : @options[:enabled] - @visible = @options[:visible].nil? ? true : @options[:visible] - @tip = @options[:tip] || "" - - @debug_color = @options[:debug_color].nil? ? Gosu::Color::RED : @options[:debug_color] - - @style = Style.new(options) - - @root ||= nil - @gui_state ||= nil - - @x = @style.x - @y = @style.y - @z = @style.z - - @width = 0 - @height = 0 - - @style.width = default(:width) || nil - @style.height = default(:height) || nil - - @style.background_canvas = Background.new - @style.background_nine_slice_canvas = BackgroundNineSlice.new - @style.border_canvas = BorderCanvas.new(element: self) - - @style_event = :default - - stylize - - default_events - - root.gui_state.request_focus(self) if @options[:autofocus] - end - - def stylize - set_static_position - - set_padding - set_margin - - set_background - set_background_nine_slice - - set_border_thickness - set_border_color - end - - def safe_style_fetch(*args) - @style.hash.dig(@style_event, *args) || @style.hash.dig(:default, *args) || default(*args) - end - - def set_static_position - @x = @style.x if @style.x != 0 - @y = @style.y if @style.y != 0 - end - - def set_background - @style.background = safe_style_fetch(:background) - - @style.background_canvas.background = @style.background - end - - def set_background_nine_slice - @style.background_nine_slice = safe_style_fetch(:background_nine_slice) - - @style.background_nine_slice_mode = safe_style_fetch(:background_nine_slice_mode) || :stretch - @style.background_nine_slice_color = safe_style_fetch(:background_nine_slice_color) || Gosu::Color::WHITE - @style.background_nine_slice_canvas.color = @style.background_nine_slice_color - - @style.background_nine_slice_from_edge = safe_style_fetch(:background_nine_slice_from_edge) - - @style.background_nine_slice_left = safe_style_fetch(:background_nine_slice_left) || @style.background_nine_slice_from_edge - @style.background_nine_slice_top = safe_style_fetch(:background_nine_slice_top) || @style.background_nine_slice_from_edge - @style.background_nine_slice_right = safe_style_fetch(:background_nine_slice_right) || @style.background_nine_slice_from_edge - @style.background_nine_slice_bottom = safe_style_fetch(:background_nine_slice_bottom) || @style.background_nine_slice_from_edge - end - - def set_border_thickness - @style.border_thickness = safe_style_fetch(:border_thickness) - - @style.border_thickness_left = safe_style_fetch(:border_thickness_left) || @style.border_thickness - @style.border_thickness_right = safe_style_fetch(:border_thickness_right) || @style.border_thickness - @style.border_thickness_top = safe_style_fetch(:border_thickness_top) || @style.border_thickness - @style.border_thickness_bottom = safe_style_fetch(:border_thickness_bottom) || @style.border_thickness - end - - def set_border_color - @style.border_color = safe_style_fetch(:border_color) - - @style.border_color_left = safe_style_fetch(:border_color_left) || @style.border_color - @style.border_color_right = safe_style_fetch(:border_color_right) || @style.border_color - @style.border_color_top = safe_style_fetch(:border_color_top) || @style.border_color - @style.border_color_bottom = safe_style_fetch(:border_color_bottom) || @style.border_color - - @style.border_canvas.color = [ - @style.border_color_top, - @style.border_color_right, - @style.border_color_bottom, - @style.border_color_left - ] - end - - def set_padding - @style.padding = safe_style_fetch(:padding) - - @style.padding_left = safe_style_fetch(:padding_left) || @style.padding - @style.padding_right = safe_style_fetch(:padding_right) || @style.padding - @style.padding_top = safe_style_fetch(:padding_top) || @style.padding - @style.padding_bottom = safe_style_fetch(:padding_bottom) || @style.padding - end - - def set_margin - @style.margin = safe_style_fetch(:margin) - - @style.margin_left = safe_style_fetch(:margin_left) || @style.margin - @style.margin_right = safe_style_fetch(:margin_right) || @style.margin - @style.margin_top = safe_style_fetch(:margin_top) || @style.margin - @style.margin_bottom = safe_style_fetch(:margin_bottom) || @style.margin - end - - def update_styles(event = :default) - old_width = width - old_height = height - - _style = @style.send(event) - @style_event = event - - if @text.is_a?(CyberarmEngine::Text) - @text.color = _style&.dig(:color) || @style.default[:color] - @text.swap_font(_style&.dig(:text_size) || @style.default[:text_size], _style&.dig(:font) || @style.default[:font]) - end - - return if self.is_a?(ToolTip) - - if old_width != width || old_height != height - (root&.gui_state || @gui_state).request_recalculate - else - stylize - end - end - - def default_events - %i[left middle right].each do |button| - event(:"#{button}_mouse_button") - event(:"released_#{button}_mouse_button") - event(:"clicked_#{button}_mouse_button") - event(:"holding_#{button}_mouse_button") - end - - event(:mouse_wheel_up) - event(:mouse_wheel_down) - - event(:enter) - event(:hover) - event(:leave) - - event(:focus) - event(:blur) - - event(:changed) - end - - def enter(_sender) - @focus = false unless window.button_down?(Gosu::MsLeft) - - if !@enabled - update_styles(:disabled) - elsif @focus - update_styles(:active) - else - update_styles(:hover) - end - - :handled - end - - def left_mouse_button(_sender, _x, _y) - @focus = true - - unless @enabled - update_styles(:disabled) - else - update_styles(:active) - end - - window.current_state.focus = self - - :handled - end - - def released_left_mouse_button(sender, _x, _y) - enter(sender) - - :handled - end - - def clicked_left_mouse_button(_sender, _x, _y) - @block&.call(self) if @enabled && !self.is_a?(Container) - - :handled - end - - def leave(_sender) - if @enabled - update_styles - else - update_styles(:disabled) - end - - :handled - end - - def blur(_sender) - @focus = false - - if @enabled - update_styles - else - update_styles(:disabled) - end - - :handled - end - - def enabled? - @enabled - end - - def visible? - @visible - end - - def toggle - @visible = !@visible - root.gui_state.request_recalculate - end - - def show - bool = visible? - @visible = true - root.gui_state.request_recalculate unless bool - end - - def hide - bool = visible? - @visible = false - root.gui_state.request_recalculate if bool - end - - def draw - return unless visible? - - @style.background_canvas.draw - @style.background_nine_slice_canvas.draw - @style.border_canvas.draw - - Gosu.clip_to(@x, @y, width, height) do - render - end - end - - def debug_draw - return if defined?(GUI_DEBUG_ONLY_ELEMENT) && self.class == GUI_DEBUG_ONLY_ELEMENT - - Gosu.draw_line( - x, y, @debug_color, - x + outer_width, y, @debug_color, - Float::INFINITY - ) - Gosu.draw_line( - x + outer_width, y, @debug_color, - x + outer_width, y + outer_height, @debug_color, - Float::INFINITY - ) - Gosu.draw_line( - x + outer_width, y + outer_height, @debug_color, - x, y + outer_height, @debug_color, - Float::INFINITY - ) - Gosu.draw_line( - x, outer_height, @debug_color, - x, y, @debug_color, - Float::INFINITY - ) - end - - def update - end - - def button_down(id) - end - - def button_up(id) - end - - def draggable?(_button) - false - end - - def render - end - - def hit?(x, y) - x.between?(@x, @x + width) && - y.between?(@y, @y + height) - end - - def width - if visible? - inner_width + @width - else - 0 - end - end - - def content_width - @width - end - - def noncontent_width - (inner_width + outer_width) - width - end - - def outer_width - @style.margin_left + width + @style.margin_right - end - - def inner_width - (@style.border_thickness_left + @style.padding_left) + (@style.padding_right + @style.border_thickness_right) - end - - def height - if visible? - inner_height + @height - else - 0 - end - end - - def content_height - @height - end - - def noncontent_height - (inner_height + outer_height) - height - end - - def outer_height - @style.margin_top + height + @style.margin_bottom - end - - def inner_height - (@style.border_thickness_top + @style.padding_top) + (@style.padding_bottom + @style.border_thickness_bottom) - end - - def scroll_width - @children.sum(&:width) + noncontent_width - end - - def scroll_height - @children.sum(&:height) + noncontent_height - end - - def max_scroll_width - scroll_width - width - end - - def max_scroll_height - scroll_height - height - end - - def dimensional_size(size, dimension) - raise "dimension must be either :width or :height" unless %i[width height].include?(dimension) - - if size.is_a?(Numeric) && size.between?(0.0, 1.0) - (@parent.send(:"content_#{dimension}") * size).round - send(:"noncontent_#{dimension}").round - else - size - end - end - - def background=(_background) - @style.background_canvas.background = _background - update_background - end - - def update_background - @style.background_canvas.x = @x - @style.background_canvas.y = @y - @style.background_canvas.z = @z - @style.background_canvas.width = width - @style.background_canvas.height = height - - @style.background_canvas.update - update_background_nine_slice - @style.border_canvas.update - end - - def background_nine_slice=(_image_path) - @style.background_nine_slice_canvas.image = _image_path - update_background_nine_slice - end - - def update_background_nine_slice - @style.background_nine_slice_canvas.x = @x - @style.background_nine_slice_canvas.y = @y - @style.background_nine_slice_canvas.z = @z - @style.background_nine_slice_canvas.width = width - @style.background_nine_slice_canvas.height = height - - @style.background_nine_slice_canvas.mode = @style.background_nine_slice_mode - - @style.background_nine_slice_canvas.color = @style.background_nine_slice_color - - @style.background_nine_slice_canvas.left = @style.background_nine_slice_left - @style.background_nine_slice_canvas.top = @style.background_nine_slice_top - @style.background_nine_slice_canvas.right = @style.background_nine_slice_right - @style.background_nine_slice_canvas.bottom = @style.background_nine_slice_bottom - - @style.background_nine_slice_canvas.image = @style.background_nine_slice - end - - def root - return self if is_root? - - unless @root && @root.parent.nil? - @root = parent - - loop do - break unless @root&.parent - - @root = @root.parent - end - end - - @root - end - - def is_root? - @gui_state != nil - end - - def focus(_) - warn "#{self.class}#focus was not overridden!" - - :handled - end - - def recalculate - raise "#{self.class}#recalculate was not overridden!" - end - - def reposition - end - - def value - raise "#{self.class}#value was not overridden!" - end - - def value=(_value) - raise "#{self.class}#value= was not overridden!" - end - - def to_s - "#{self.class} x=#{x} y=#{y} width=#{width} height=#{height} value=#{value.is_a?(String) ? "\"#{value}\"" : value}" - end - - def inspect - to_s - end - end -end +module CyberarmEngine + class Element + include Theme + include Event + include Common + + attr_accessor :x, :y, :z, :enabled, :tip + attr_reader :parent, :options, :style, :event_handler, :background_canvas, :border_canvas + + def initialize(options = {}, block = nil) + @parent = options.delete(:parent) # parent Container (i.e. flow/stack) + options = theme_defaults(options) + @options = options + @block = block + + @focus = @options[:focus].nil? ? false : @options[:focus] + @enabled = @options[:enabled].nil? ? true : @options[:enabled] + @visible = @options[:visible].nil? ? true : @options[:visible] + @tip = @options[:tip] || "" + + @debug_color = @options[:debug_color].nil? ? Gosu::Color::RED : @options[:debug_color] + + @style = Style.new(options) + + @root ||= nil + @gui_state ||= nil + + @x = @style.x + @y = @style.y + @z = @style.z + + @width = 0 + @height = 0 + + @style.width = default(:width) || nil + @style.height = default(:height) || nil + + @style.background_canvas = Background.new + @style.background_nine_slice_canvas = BackgroundNineSlice.new + @style.border_canvas = BorderCanvas.new(element: self) + + @style_event = :default + + stylize + + default_events + + root.gui_state.request_focus(self) if @options[:autofocus] + end + + def stylize + set_static_position + + set_padding + set_margin + + set_background + set_background_nine_slice + + set_border_thickness + set_border_color + end + + def safe_style_fetch(*args) + @style.hash.dig(@style_event, *args) || @style.hash.dig(:default, *args) || default(*args) + end + + def set_static_position + @x = @style.x if @style.x != 0 + @y = @style.y if @style.y != 0 + end + + def set_background + @style.background = safe_style_fetch(:background) + + @style.background_canvas.background = @style.background + end + + def set_background_nine_slice + @style.background_nine_slice = safe_style_fetch(:background_nine_slice) + + @style.background_nine_slice_mode = safe_style_fetch(:background_nine_slice_mode) || :stretch + @style.background_nine_slice_color = safe_style_fetch(:background_nine_slice_color) || Gosu::Color::WHITE + @style.background_nine_slice_canvas.color = @style.background_nine_slice_color + + @style.background_nine_slice_from_edge = safe_style_fetch(:background_nine_slice_from_edge) + + @style.background_nine_slice_left = safe_style_fetch(:background_nine_slice_left) || @style.background_nine_slice_from_edge + @style.background_nine_slice_top = safe_style_fetch(:background_nine_slice_top) || @style.background_nine_slice_from_edge + @style.background_nine_slice_right = safe_style_fetch(:background_nine_slice_right) || @style.background_nine_slice_from_edge + @style.background_nine_slice_bottom = safe_style_fetch(:background_nine_slice_bottom) || @style.background_nine_slice_from_edge + end + + def set_border_thickness + @style.border_thickness = safe_style_fetch(:border_thickness) + + @style.border_thickness_left = safe_style_fetch(:border_thickness_left) || @style.border_thickness + @style.border_thickness_right = safe_style_fetch(:border_thickness_right) || @style.border_thickness + @style.border_thickness_top = safe_style_fetch(:border_thickness_top) || @style.border_thickness + @style.border_thickness_bottom = safe_style_fetch(:border_thickness_bottom) || @style.border_thickness + end + + def set_border_color + @style.border_color = safe_style_fetch(:border_color) + + @style.border_color_left = safe_style_fetch(:border_color_left) || @style.border_color + @style.border_color_right = safe_style_fetch(:border_color_right) || @style.border_color + @style.border_color_top = safe_style_fetch(:border_color_top) || @style.border_color + @style.border_color_bottom = safe_style_fetch(:border_color_bottom) || @style.border_color + + @style.border_canvas.color = [ + @style.border_color_top, + @style.border_color_right, + @style.border_color_bottom, + @style.border_color_left + ] + end + + def set_padding + @style.padding = safe_style_fetch(:padding) + + @style.padding_left = safe_style_fetch(:padding_left) || @style.padding + @style.padding_right = safe_style_fetch(:padding_right) || @style.padding + @style.padding_top = safe_style_fetch(:padding_top) || @style.padding + @style.padding_bottom = safe_style_fetch(:padding_bottom) || @style.padding + end + + def set_margin + @style.margin = safe_style_fetch(:margin) + + @style.margin_left = safe_style_fetch(:margin_left) || @style.margin + @style.margin_right = safe_style_fetch(:margin_right) || @style.margin + @style.margin_top = safe_style_fetch(:margin_top) || @style.margin + @style.margin_bottom = safe_style_fetch(:margin_bottom) || @style.margin + end + + def update_styles(event = :default) + old_width = width + old_height = height + + _style = @style.send(event) + @style_event = event + + if @text.is_a?(CyberarmEngine::Text) + @text.color = _style&.dig(:color) || @style.default[:color] + @text.swap_font(_style&.dig(:text_size) || @style.default[:text_size], _style&.dig(:font) || @style.default[:font]) + end + + return if self.is_a?(ToolTip) + + if old_width != width || old_height != height + (root&.gui_state || @gui_state).request_recalculate + else + stylize + end + end + + def default_events + %i[left middle right].each do |button| + event(:"#{button}_mouse_button") + event(:"released_#{button}_mouse_button") + event(:"clicked_#{button}_mouse_button") + event(:"holding_#{button}_mouse_button") + end + + event(:mouse_wheel_up) + event(:mouse_wheel_down) + + event(:enter) + event(:hover) + event(:leave) + + event(:focus) + event(:blur) + + event(:changed) + end + + def enter(_sender) + @focus = false unless window.button_down?(Gosu::MsLeft) + + if !@enabled + update_styles(:disabled) + elsif @focus + update_styles(:active) + else + update_styles(:hover) + end + + :handled + end + + def left_mouse_button(_sender, _x, _y) + @focus = true + + unless @enabled + update_styles(:disabled) + else + update_styles(:active) + end + + window.current_state.focus = self + + :handled + end + + def released_left_mouse_button(sender, _x, _y) + enter(sender) + + :handled + end + + def clicked_left_mouse_button(_sender, _x, _y) + @block&.call(self) if @enabled && !self.is_a?(Container) + + :handled + end + + def leave(_sender) + if @enabled + update_styles + else + update_styles(:disabled) + end + + :handled + end + + def blur(_sender) + @focus = false + + if @enabled + update_styles + else + update_styles(:disabled) + end + + :handled + end + + def enabled? + @enabled + end + + def visible? + @visible + end + + def toggle + @visible = !@visible + root.gui_state.request_recalculate + end + + def show + bool = visible? + @visible = true + root.gui_state.request_recalculate unless bool + end + + def hide + bool = visible? + @visible = false + root.gui_state.request_recalculate if bool + end + + def draw + return unless visible? + + @style.background_canvas.draw + @style.background_nine_slice_canvas.draw + @style.border_canvas.draw + + Gosu.clip_to(@x, @y, width, height) do + render + end + end + + def debug_draw + return if defined?(GUI_DEBUG_ONLY_ELEMENT) && self.class == GUI_DEBUG_ONLY_ELEMENT + + Gosu.draw_line( + x, y, @debug_color, + x + outer_width, y, @debug_color, + Float::INFINITY + ) + Gosu.draw_line( + x + outer_width, y, @debug_color, + x + outer_width, y + outer_height, @debug_color, + Float::INFINITY + ) + Gosu.draw_line( + x + outer_width, y + outer_height, @debug_color, + x, y + outer_height, @debug_color, + Float::INFINITY + ) + Gosu.draw_line( + x, outer_height, @debug_color, + x, y, @debug_color, + Float::INFINITY + ) + end + + def update + end + + def button_down(id) + end + + def button_up(id) + end + + def draggable?(_button) + false + end + + def render + end + + def hit?(x, y) + x.between?(@x, @x + width) && + y.between?(@y, @y + height) + end + + def width + if visible? + inner_width + @width + else + 0 + end + end + + def content_width + @width + end + + def noncontent_width + (inner_width + outer_width) - width + end + + def outer_width + @style.margin_left + width + @style.margin_right + end + + def inner_width + (@style.border_thickness_left + @style.padding_left) + (@style.padding_right + @style.border_thickness_right) + end + + def height + if visible? + inner_height + @height + else + 0 + end + end + + def content_height + @height + end + + def noncontent_height + (inner_height + outer_height) - height + end + + def outer_height + @style.margin_top + height + @style.margin_bottom + end + + def inner_height + (@style.border_thickness_top + @style.padding_top) + (@style.padding_bottom + @style.border_thickness_bottom) + end + + def scroll_width + @children.sum(&:width) + noncontent_width + end + + def scroll_height + @children.sum(&:height) + noncontent_height + end + + def max_scroll_width + scroll_width - width + end + + def max_scroll_height + scroll_height - height + end + + def dimensional_size(size, dimension) + raise "dimension must be either :width or :height" unless %i[width height].include?(dimension) + + if size.is_a?(Numeric) && size.between?(0.0, 1.0) + (@parent.send(:"content_#{dimension}") * size).round - send(:"noncontent_#{dimension}").round + else + size + end + end + + def background=(_background) + @style.background_canvas.background = _background + update_background + end + + def update_background + @style.background_canvas.x = @x + @style.background_canvas.y = @y + @style.background_canvas.z = @z + @style.background_canvas.width = width + @style.background_canvas.height = height + + @style.background_canvas.update + update_background_nine_slice + @style.border_canvas.update + end + + def background_nine_slice=(_image_path) + @style.background_nine_slice_canvas.image = _image_path + update_background_nine_slice + end + + def update_background_nine_slice + @style.background_nine_slice_canvas.x = @x + @style.background_nine_slice_canvas.y = @y + @style.background_nine_slice_canvas.z = @z + @style.background_nine_slice_canvas.width = width + @style.background_nine_slice_canvas.height = height + + @style.background_nine_slice_canvas.mode = @style.background_nine_slice_mode + + @style.background_nine_slice_canvas.color = @style.background_nine_slice_color + + @style.background_nine_slice_canvas.left = @style.background_nine_slice_left + @style.background_nine_slice_canvas.top = @style.background_nine_slice_top + @style.background_nine_slice_canvas.right = @style.background_nine_slice_right + @style.background_nine_slice_canvas.bottom = @style.background_nine_slice_bottom + + @style.background_nine_slice_canvas.image = @style.background_nine_slice + end + + def root + return self if is_root? + + unless @root && @root.parent.nil? + @root = parent + + loop do + break unless @root&.parent + + @root = @root.parent + end + end + + @root + end + + def is_root? + @gui_state != nil + end + + def focus(_) + warn "#{self.class}#focus was not overridden!" + + :handled + end + + def recalculate + raise "#{self.class}#recalculate was not overridden!" + end + + def reposition + end + + def value + raise "#{self.class}#value was not overridden!" + end + + def value=(_value) + raise "#{self.class}#value= was not overridden!" + end + + def to_s + "#{self.class} x=#{x} y=#{y} width=#{width} height=#{height} value=#{value.is_a?(String) ? "\"#{value}\"" : value}" + end + + def inspect + to_s + end + end +end