lib/cyberarm_engine/ui/elements/container.rb in cyberarm_engine-0.23.0 vs lib/cyberarm_engine/ui/elements/container.rb in cyberarm_engine-0.24.0

- old
+ new

@@ -18,11 +18,14 @@ def initialize(options = {}, block = nil) @gui_state = options.delete(:gui_state) super + @last_scroll_position = Vector.new(0, 0) @scroll_position = Vector.new(0, 0) + @scroll_target_position = Vector.new(0, 0) + @scroll_chunk = 120 @scroll_speed = 40 @text_color = options[:color] @children = [] @@ -31,21 +34,21 @@ end def build @block.call(self) if @block - root.gui_state.request_recalculate + root.gui_state.request_recalculate_for(self) end def add(element) @children << element - root.gui_state.request_recalculate + root.gui_state.request_recalculate_for(self) end def remove(element) - root.gui_state.request_recalculate if @children.delete(element) + root.gui_state.request_recalculate_for(self) if @children.delete(element) end def clear(&block) @children.clear @@ -54,32 +57,34 @@ CyberarmEngine::Element::Container.current_container = self block.call(self) if block CyberarmEngine::Element::Container.current_container = old_container - root.gui_state.request_recalculate + root.gui_state.request_recalculate_for(self) end def append(&block) old_container = CyberarmEngine::Element::Container.current_container CyberarmEngine::Element::Container.current_container = self block.call(self) if block CyberarmEngine::Element::Container.current_container = old_container - root.gui_state.request_recalculate + root.gui_state.request_recalculate_for(self) end def render Gosu.clip_to( @x + @style.border_thickness_left + @style.padding_left, @y + @style.border_thickness_top + @style.padding_top, content_width + 1, content_height + 1 ) do - @children.each(&:draw) + Gosu.translate(@scroll_position.x, @scroll_position.y) do + @children.each(&:draw) + end end end def debug_draw super @@ -88,49 +93,91 @@ child.debug_draw end end def update + update_scroll @children.each(&:update) end def hit_element?(x, y) return unless hit?(x, y) + # Offset child hit point by scroll position/offset + child_x = x - @scroll_position.x + child_y = y - @scroll_position.y + @children.reverse_each do |child| next unless child.visible? case child when Container - if element = child.hit_element?(x, y) + if element = child.hit_element?(child_x, child_y) return element end else - return child if child.hit?(x, y) + return child if child.hit?(child_x, child_y) end end self if hit?(x, y) end + def update_child_element_visibity(child) + child.element_visible = child.x >= (@x - @scroll_position.x) - child.width && child.x <= (@x - @scroll_position.x) + width && + child.y >= (@y - @scroll_position.y) - child.height && child.y <= (@y - @scroll_position.y) + height + end + + def update_scroll + dt = window.dt > 1 ? 1.0 : window.dt + + scroll_x_diff = (@scroll_target_position.x - @scroll_position.x) + scroll_y_diff = (@scroll_target_position.y - @scroll_position.y) + + @scroll_position.x += (scroll_x_diff * @scroll_speed * 0.25 * dt).round + @scroll_position.y += (scroll_y_diff * @scroll_speed * 0.25 * dt).round + + @scroll_position.x = @scroll_target_position.x if scroll_x_diff.abs < 1.0 + @scroll_position.y = @scroll_target_position.y if scroll_y_diff.abs < 1.0 + + # Scrolled PAST top + if @scroll_position.y > 0 + @scroll_target_position.y = 0 + + # Scrolled PAST bottom + elsif @scroll_position.y.abs > max_scroll_height + @scroll_target_position.y = -max_scroll_height + end + + if @last_scroll_position != @scroll_position + @children.each { |child| update_child_element_visibity(child) } + root.gui_state.request_repaint + end + + @last_scroll_position.x = @scroll_position.x + @last_scroll_position.y = @scroll_position.y + end + def recalculate @current_position = Vector.new(@style.margin_left + @style.padding_left, @style.margin_top + @style.padding_top) - @current_position += @scroll_position return unless visible? - Stats.increment(:gui_recalculations_last_frame, 1) + Stats.frame.increment(:gui_recalculations) stylize # s = Gosu.milliseconds layout old_width = @width old_height = @height + @cached_scroll_width = nil + @cached_scroll_height = nil + if is_root? @width = @style.width = window.width @height = @style.height = window.height else @width = 0 @@ -174,21 +221,34 @@ child.stylize child.recalculate child.reposition # TODO: Implement top,bottom,left,center, and right positioning - Stats.increment(:gui_recalculations_last_frame, 1) + Stats.frame.increment(:gui_recalculations) - child.element_visible = child.x >= @x - child.width && child.x <= @x + width && - child.y >= @y - child.height && child.y <= @y + height + update_child_element_visibity(child) end # puts "TOOK: #{Gosu.milliseconds - s}ms to recalculate #{self.class}:0x#{self.object_id.to_s(16)}" update_background + # Fixes resized container scrolled past bottom + self.scroll_top = -@scroll_position.y + @scroll_target_position.y = @scroll_position.y + + # NOTE: Experiment for removing need to explicitly call gui_state#recalculate at least 3 times for layout to layout... + if old_width != @width || old_height != @height + if @parent + root.gui_state.request_recalculate_for(@parent) + else + root.gui_state.request_recalculate + end + end + root.gui_state.request_repaint if @width != old_width || @height != old_height + recalculate_if_size_changed end def layout raise "Not overridden" end @@ -244,34 +304,33 @@ end def mouse_wheel_up(sender, x, y) return unless @style.scroll - if @scroll_position.y < 0 - @scroll_position.y += @scroll_speed - @scroll_position.y = 0 if @scroll_position.y > 0 + # Allow overscrolling UP, only if one can scroll DOWN + if height < scroll_height + if @scroll_target_position.y > 0 + @scroll_target_position.y = @scroll_chunk + else + @scroll_target_position.y += @scroll_chunk + end - root.gui_state.request_recalculate_for(self) - root.gui_state.request_repaint - return :handled end end def mouse_wheel_down(sender, x, y) return unless @style.scroll return unless height < scroll_height - if @scroll_position.y.abs < max_scroll_height - @scroll_position.y -= @scroll_speed - @scroll_position.y = -max_scroll_height if @scroll_position.y.abs > max_scroll_height - - root.gui_state.request_recalculate_for(self) - root.gui_state.request_repaint - - return :handled + if @scroll_target_position.y > 0 + @scroll_target_position.y = -@scroll_chunk + else + @scroll_target_position.y -= @scroll_chunk end + + return :handled end def scroll_top @scroll_position.y end