From 5d7e2028b10496ec0583048e4988571757984ad0 Mon Sep 17 00:00:00 2001 From: Cyberarm Date: Tue, 16 Jun 2020 00:16:13 -0500 Subject: [PATCH] Added support for image icon for ToggleButton, added initial implementation of EditBox, fixed a few ruby -w warnings by initializing select instance nilable variables, added clipboard support to EditLine, added drag selection to EditLine, added keyboard shortcuts to EditLine, GuiState now passes button_down/button_up callbacks to @focus --- cyberarm_engine.gemspec | 2 + lib/cyberarm_engine.rb | 1 + lib/cyberarm_engine/ui/dsl.rb | 7 ++ lib/cyberarm_engine/ui/element.rb | 3 + lib/cyberarm_engine/ui/elements/button.rb | 3 +- lib/cyberarm_engine/ui/elements/edit_box.rb | 56 ++++++++++- lib/cyberarm_engine/ui/elements/edit_line.rb | 95 +++++++++++++++++-- lib/cyberarm_engine/ui/elements/label.rb | 7 +- .../ui/elements/toggle_button.rb | 31 ++++-- lib/cyberarm_engine/ui/gui_state.rb | 6 ++ 10 files changed, 192 insertions(+), 19 deletions(-) diff --git a/cyberarm_engine.gemspec b/cyberarm_engine.gemspec index cfef862..86bd328 100644 --- a/cyberarm_engine.gemspec +++ b/cyberarm_engine.gemspec @@ -30,6 +30,8 @@ Gem::Specification.new do |spec| spec.add_dependency "gosu", "~> 0.15.0" spec.add_dependency "gosu_more_drawables", "~> 0.3" + spec.add_dependency "clipboard", "~> 1.3.4" + # spec.add_dependency "ffi", :platforms => [:mswin, :mingw] # Required by Clipboard on Windows spec.add_development_dependency "bundler", "~> 1.16" spec.add_development_dependency "rake", "~> 13.0" diff --git a/lib/cyberarm_engine.rb b/lib/cyberarm_engine.rb index 3952397..43c9250 100644 --- a/lib/cyberarm_engine.rb +++ b/lib/cyberarm_engine.rb @@ -6,6 +6,7 @@ rescue LoadError => e end require "json" require "gosu_more_drawables" +require "clipboard" require_relative "cyberarm_engine/version" require_relative "cyberarm_engine/stats" diff --git a/lib/cyberarm_engine/ui/dsl.rb b/lib/cyberarm_engine/ui/dsl.rb index 0628cb3..272e7de 100644 --- a/lib/cyberarm_engine/ui/dsl.rb +++ b/lib/cyberarm_engine/ui/dsl.rb @@ -29,6 +29,13 @@ module CyberarmEngine add_element( Element::EditLine.new(text, options, block) ) end + def edit_box(text, options = {}, &block) + options[:parent] = element_parent + options[:theme] = current_theme + + add_element( Element::EditBox.new(text, options, block) ) + end + def toggle_button(options = {}, &block) options[:parent] = element_parent options[:theme] = current_theme diff --git a/lib/cyberarm_engine/ui/element.rb b/lib/cyberarm_engine/ui/element.rb index 4d29f79..6a86a1e 100644 --- a/lib/cyberarm_engine/ui/element.rb +++ b/lib/cyberarm_engine/ui/element.rb @@ -20,6 +20,9 @@ module CyberarmEngine @style = Style.new(options) + @root ||= nil + @gui_state ||= nil + @x = @style.x @y = @style.y @z = @style.z diff --git a/lib/cyberarm_engine/ui/elements/button.rb b/lib/cyberarm_engine/ui/elements/button.rb index 74d730a..0ce7b86 100644 --- a/lib/cyberarm_engine/ui/elements/button.rb +++ b/lib/cyberarm_engine/ui/elements/button.rb @@ -2,9 +2,10 @@ module CyberarmEngine class Element class Button < Label def initialize(text_or_image, options = {}, block = nil) + @image, @scale_x, @scale_y = nil, 1, 1 + if text_or_image.is_a?(Gosu::Image) @image = text_or_image - @scale_x, @scale_y = 1, 1 end super(text_or_image, options, block) diff --git a/lib/cyberarm_engine/ui/elements/edit_box.rb b/lib/cyberarm_engine/ui/elements/edit_box.rb index 1a68fde..2f4a763 100644 --- a/lib/cyberarm_engine/ui/elements/edit_box.rb +++ b/lib/cyberarm_engine/ui/elements/edit_box.rb @@ -1,6 +1,60 @@ module CyberarmEngine class Element - class EditBox < Element + class EditBox < EditLine + def initialize(*args) + super(*args) + + @active_line = 0 + end + + def draw_caret + Gosu.draw_rect(caret_position, @text.y + @active_line * @text.textobject.height, @caret_width, @caret_height, @caret_color, @z) + end + + def draw_selection + selection_width = caret_position - selection_start_position + + Gosu.draw_rect(selection_start_position, @text.y, selection_width, @text.textobject.height, default(:selection_color), @z) + end + + def text_input_position_for(method) + if @type == :password + @text.x + @text.width(default(:password_character) * @text_input.text[0...@text_input.send(method)].length) + else + @text.x + @text.width(@text_input.text[0...@text_input.send(method)]) + end + end + + def caret_position_under_mouse(mouse_x, mouse_y) + 1.upto(@text.text.length) do |i| + if mouse_x < @text.x - @offset_x + @text.textobject.text_width(@text.text[0...i]) + return i - 1; + end + end + + @text_input.text.length + end + + def move_caret_to_mouse(mouse_x, mouse_y) + @text_input.caret_pos = @text_input.selection_start = caret_position_under_mouse(mouse_x, mouse_y) + end + + def button_down(id) + super + + case id + when Gosu::KB_ENTER, Gosu::KB_RETURN + caret_pos = @text_input.caret_pos + @text_input.text = @text_input.text.insert(@text_input.caret_pos, "\n") + @text_input.caret_pos = @text_input.selection_start = caret_pos + 1 + end + end + + def drag_update(sender, x, y, button) + @text_input.caret_pos = caret_position_under_mouse(x, y) + + :handled + end end end end \ No newline at end of file diff --git a/lib/cyberarm_engine/ui/elements/edit_line.rb b/lib/cyberarm_engine/ui/elements/edit_line.rb index cbdcab2..5502feb 100644 --- a/lib/cyberarm_engine/ui/elements/edit_line.rb +++ b/lib/cyberarm_engine/ui/elements/edit_line.rb @@ -7,7 +7,7 @@ module CyberarmEngine @type = default(:type) @caret_width = default(:caret_width) - @caret_height= @text.height + @caret_height= @text.textobject.height @caret_color = default(:caret_color) @caret_interval = default(:caret_interval) @caret_last_interval = Gosu.milliseconds @@ -15,15 +15,18 @@ module CyberarmEngine @text_input = Gosu::TextInput.new @text_input.text = text + @last_text_value = text - @offset_x = 0 + @offset_x, @offset_y = 0, 0 - return self + event(:begin_drag) + event(:drag_update) + event(:end_drag) end def render Gosu.clip_to(@text.x, @text.y, @width, @height) do - Gosu.translate(-@offset_x, 0) do + Gosu.translate(-@offset_x, -@offset_y) do draw_selection draw_caret if @focus && @show_caret draw_text @@ -48,6 +51,12 @@ module CyberarmEngine @text.text = @text_input.text end + if @last_text_value != self.value + @last_text_value = self.value + + publish(:changed, self.value) + end + if Gosu.milliseconds >= @caret_last_interval + @caret_interval @caret_last_interval = Gosu.milliseconds @@ -57,15 +66,60 @@ module CyberarmEngine keep_caret_visible end - def move_caret_to_mouse(mouse_x) + def button_down(id) + handle_keyboard_shortcuts(id) + end + + def handle_keyboard_shortcuts(id) + return unless @focus && @enabled + if Gosu.button_down?(Gosu::KB_LEFT_CONTROL) or Gosu.button_down?(Gosu::KB_RIGHT_CONTROL) + case id + when Gosu::KB_A + @text_input.selection_start = 0 + @text_input.caret_pos = @text_input.text.length + + when Gosu::KB_C + if @text_input.selection_start < @text_input.caret_pos + Clipboard.copy(@text_input.text[@text_input.selection_start...@text_input.caret_pos]) + else + Clipboard.copy(@text_input.text[@text_input.caret_pos...@text_input.selection_start]) + end + + when Gosu::KB_X + chars = @text_input.text.chars + + if @text_input.selection_start < @text_input.caret_pos + Clipboard.copy(@text_input.text[@text_input.selection_start...@text_input.caret_pos]) + chars.slice!(@text_input.selection_start, @text_input.caret_pos) + else + Clipboard.copy(@text_input.text[@text_input.caret_pos...@text_input.selection_start]) + chars.slice!(@text_input.caret_pos, @text_input.selection_start) + end + + @text_input.text = chars.join + + when Gosu::KB_V + if instance_of?(EditLine) # EditLine assumes a single line of text + @text_input.text = @text_input.text.insert(@text_input.caret_pos, Clipboard.paste.encode("UTF-8").gsub("\n", "")) + else + @text_input.text = @text_input.text.insert(@text_input.caret_pos, Clipboard.paste.encode("UTF-8")) + end + end + end + end + + def caret_position_under_mouse(mouse_x) 1.upto(@text.text.length) do |i| if mouse_x < @text.x - @offset_x + @text.textobject.text_width(@text.text[0...i]) - @text_input.caret_pos = @text_input.selection_start = i - 1; - return + return i - 1; end end - @text_input.caret_pos = @text_input.selection_start = @text_input.text.length + @text_input.text.length + end + + def move_caret_to_mouse(mouse_x, mouse_y) + @text_input.caret_pos = @text_input.selection_start = caret_position_under_mouse(mouse_x) end def keep_caret_visible @@ -120,7 +174,7 @@ module CyberarmEngine @caret_last_interval = Gosu.milliseconds @show_caret = true - move_caret_to_mouse(x) + move_caret_to_mouse(x, y) return :handled end @@ -154,6 +208,29 @@ module CyberarmEngine return :handled end + def draggable?(button) + button == :left + end + + def begin_drag(sender, x, y, button) + @drag_start = x + @offset_drag_start = @offset_x + @drag_caret_position = @text_input.caret_pos + + :handled + end + + def drag_update(sender, x, y, button) + @text_input.caret_pos = caret_position_under_mouse(x) + + :handled + end + + def end_drag(sender, x, y, button) + + :handled + end + def recalculate super diff --git a/lib/cyberarm_engine/ui/elements/label.rb b/lib/cyberarm_engine/ui/elements/label.rb index 2321dc3..6dc951a 100644 --- a/lib/cyberarm_engine/ui/elements/label.rb +++ b/lib/cyberarm_engine/ui/elements/label.rb @@ -4,7 +4,12 @@ module CyberarmEngine def initialize(text, options = {}, block = nil) super(options, block) - @text = Text.new(text, font: @options[:font], z: @z, color: @options[:color], size: @options[:text_size], shadow: @options[:text_shadow]) + @text = Text.new( + text, font: @options[:font], z: @z, color: @options[:color], + size: @options[:text_size], shadow: @options[:text_shadow], + shadow_size: @options[:text_shadow_size], + shadow_color: @options[:text_shadow_color] + ) end def render diff --git a/lib/cyberarm_engine/ui/elements/toggle_button.rb b/lib/cyberarm_engine/ui/elements/toggle_button.rb index 8b66b0b..a18b60d 100644 --- a/lib/cyberarm_engine/ui/elements/toggle_button.rb +++ b/lib/cyberarm_engine/ui/elements/toggle_button.rb @@ -4,11 +4,22 @@ module CyberarmEngine attr_reader :toggled def initialize(options, block = nil) - super(options[:checkmark], options, block) + if options.dig(:theme, :ToggleButton, :checkmark_image) + options[:theme][:ToggleButton][:image_width] ||= options[:theme][:Label][:text_size] + super(get_image(options.dig(:theme, :ToggleButton, :checkmark_image)), options, block) + + @_image = @image + else + super(options[:checkmark], options, block) + end + @value = options[:checked] || false + if @value + @image = @_image if @_image @text.text = @options[:checkmark] else + @image = nil @text.text = "" end @@ -24,15 +35,19 @@ module CyberarmEngine end def recalculate - super + if @image + super + else + super - _width = dimensional_size(@style.width, :width) - _height= dimensional_size(@style.height,:height) + _width = dimensional_size(@style.width, :width) + _height= dimensional_size(@style.height,:height) - @width = _width ? _width : @text.textobject.text_width(@options[:checkmark]) - @height = _height ? _height : @text.height + @width = _width ? _width : @text.textobject.text_width(@options[:checkmark]) + @height = _height ? _height : @text.height - update_background + update_background + end end def value @@ -43,8 +58,10 @@ module CyberarmEngine @value = boolean if boolean + @image = @_image if @_image @text.text = @options[:checkmark] else + @image = nil @text.text = "" end diff --git a/lib/cyberarm_engine/ui/gui_state.rb b/lib/cyberarm_engine/ui/gui_state.rb index 8b2d65c..39c8ef7 100644 --- a/lib/cyberarm_engine/ui/gui_state.rb +++ b/lib/cyberarm_engine/ui/gui_state.rb @@ -21,6 +21,8 @@ module CyberarmEngine @mouse_over = nil @mouse_down_on = {} @mouse_down_position = {} + @last_mouse_pos = nil + @dragging_element = nil @pending_recalculate_request = false @tip = CyberarmEngine::Text.new("", size: 22, z: Float::INFINITY) @@ -110,6 +112,8 @@ module CyberarmEngine when Gosu::KbF5 request_recalculate end + + @focus.button_down(id) if @focus.respond_to?(:button_down) end def button_up(id) @@ -127,6 +131,8 @@ module CyberarmEngine when Gosu::MsWheelDown redirect_mouse_wheel(:down) end + + @focus.button_up(id) if @focus.respond_to?(:button_up) end def redirect_mouse_button(button)