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