From c22932954201d80682e3fdab0a365a205a4261ae Mon Sep 17 00:00:00 2001 From: Cyberarm Date: Sat, 18 Apr 2026 12:29:04 -0500 Subject: [PATCH] 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. --- lib/cyberarm_engine/ui/element.rb | 4 +-- lib/cyberarm_engine/ui/elements/container.rb | 16 ++++++--- lib/cyberarm_engine/ui/gui_state.rb | 34 +++++++++++++++----- lib/cyberarm_engine/ui/style.rb | 4 +++ 4 files changed, 43 insertions(+), 15 deletions(-) diff --git a/lib/cyberarm_engine/ui/element.rb b/lib/cyberarm_engine/ui/element.rb index 7d1232b..314c33e 100644 --- a/lib/cyberarm_engine/ui/element.rb +++ b/lib/cyberarm_engine/ui/element.rb @@ -214,7 +214,7 @@ module CyberarmEngine if !@enabled update_styles(:disabled) - elsif @focus + elsif @focus && !@style.active.empty? update_styles(:active) else update_styles(:hover) @@ -229,7 +229,7 @@ module CyberarmEngine unless @enabled update_styles(:disabled) else - update_styles(:active) + update_styles(:active) unless @style.active.empty? end window.current_state.focus = self diff --git a/lib/cyberarm_engine/ui/elements/container.rb b/lib/cyberarm_engine/ui/elements/container.rb index dc98af4..15ac027 100644 --- a/lib/cyberarm_engine/ui/elements/container.rb +++ b/lib/cyberarm_engine/ui/elements/container.rb @@ -106,9 +106,12 @@ module CyberarmEngine @children.each(&:update) 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) + elements << self + # Offset child hit point by scroll position/offset child_x = x - @scroll_position.x child_y = y - @scroll_position.y @@ -118,15 +121,18 @@ module CyberarmEngine case child when Container - if (element = child.hit_element?(child_x, child_y)) - return element + if (child.hit_element?(child_x, child_y, elements)) + return elements end else - return child if child.hit?(child_x, child_y) + if child.hit?(child_x, child_y) + elements << child + return elements + end end end - self if hit?(x, y) + elements end def update_child_element_visibity(child) diff --git a/lib/cyberarm_engine/ui/gui_state.rb b/lib/cyberarm_engine/ui/gui_state.rb index d13c423..6002fce 100644 --- a/lib/cyberarm_engine/ui/gui_state.rb +++ b/lib/cyberarm_engine/ui/gui_state.rb @@ -31,6 +31,7 @@ module CyberarmEngine @dragging_element = nil @pending_recalculate_request = false @pending_element_recalculate_requests = [] + @hit_elements = [] @needs_repaint = false @@ -81,6 +82,7 @@ module CyberarmEngine # Does NOT throw :focus event at element or set element as focused def focus=(element) @focus.publish(:blur) if @focus && element && @focus != element + @hit_elements.delete(@focus) @focus = element end @@ -163,22 +165,37 @@ module CyberarmEngine return unless window.has_focus? return unless window.current_state == self - new_mouse_over = @menu.hit_element?(window.mouse_x, window.mouse_y) if @menu - new_mouse_over ||= @root_container.hit_element?(window.mouse_x, window.mouse_y) + # list of containers decending down to hit element + 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&.last - if new_mouse_over - new_mouse_over.publish(:enter) if 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 + # is the currently hit element the same as last hit element? + same_element = @mouse_over && 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 - @mouse_over.publish(:leave) if @mouse_over && new_mouse_over != @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(:middle) if @mouse_over && Gosu.button_down?(Gosu::MS_MIDDLE) 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 @tip.value = @mouse_over.tip if @mouse_over @tip.x = window.mouse_x @@ -261,6 +278,7 @@ module CyberarmEngine hide_menu unless @menu && (@menu == @mouse_over) || (@mouse_over&.parent == @menu) if @focus && @mouse_over != @focus + @hit_elements.delete(@focus) @focus.publish(:blur) @focus = nil end diff --git a/lib/cyberarm_engine/ui/style.rb b/lib/cyberarm_engine/ui/style.rb index faa0e4b..ea3401a 100644 --- a/lib/cyberarm_engine/ui/style.rb +++ b/lib/cyberarm_engine/ui/style.rb @@ -58,6 +58,10 @@ module CyberarmEngine def mark_clean! @dirty = false end + + def empty? + @hash.empty? + end end class Style < StyleData