diff options
| -rw-r--r-- | dragon/args.rb | 64 | ||||
| -rw-r--r-- | dragon/attr_gtk.rb | 1 | ||||
| -rw-r--r-- | dragon/attr_sprite.rb | 4 | ||||
| -rw-r--r-- | dragon/config.rb | 399 | ||||
| -rw-r--r-- | dragon/console.rb | 97 | ||||
| -rw-r--r-- | dragon/controller.rb | 122 | ||||
| -rw-r--r-- | dragon/directional_input_helper_methods.rb | 21 | ||||
| -rw-r--r-- | dragon/geometry.rb | 31 | ||||
| -rw-r--r-- | dragon/grid.rb | 129 | ||||
| -rw-r--r-- | dragon/inputs.rb | 295 | ||||
| -rw-r--r-- | dragon/keys.rb | 51 |
11 files changed, 1011 insertions, 203 deletions
diff --git a/dragon/args.rb b/dragon/args.rb index e2e8a5c..0e6ff6f 100644 --- a/dragon/args.rb +++ b/dragon/args.rb @@ -4,11 +4,50 @@ # args.rb has been released under MIT (*only this file*). module GTK + # This class is the one you'll interact with the most. It's + # constructed by the DragonRuby Runtime and is provided to you on + # each tick. class Args include ArgsDeprecated - - attr_accessor :inputs, :outputs, :passes, :runtime, - :grid, :recording, :geometry + include Serialize + + # Contains information related to input devices and input events. + # + # @return [Inputs] + attr_accessor :inputs + + # Contains the means to interact with output devices such as the screen. + # + # @return [Outputs] + attr_accessor :outputs + + # Contains display size information to assist in positioning things on the screen. + # + # @return [Grid] + attr_accessor :grid + + # Provides access to game play recording facilities within Game Toolkit. + # + # @return [Recording] + attr_accessor :recording + + # Gives you access to geometry related functions. + # + # @return [Geometry] + attr_accessor :geometry + + # This is where you'll put state associated with your video game. + # + # @return [OpenEntity] + attr_accessor :state + + # Gives you access to the top level DragonRuby runtime. + # + # @return [Runtime] + attr_accessor :runtime + alias_method :gtk, :runtime + + attr_accessor :passes def initialize runtime, recording @inputs = Inputs.new @@ -24,6 +63,9 @@ module GTK @geometry = GTK::Geometry end + # The number of ticks since the start of the game. + # + # @return [Integer] def tick_count @state.tick_count end @@ -32,18 +74,6 @@ module GTK @state.tick_count = value end - def gtk - @runtime - end - - def state - @state - end - - def state= value - @state = value - end - def serialize { state: state.as_hash, @@ -139,10 +169,14 @@ module GTK @inputs.mouse end + # @see Inputs#controller_one + # @return (see Inputs#controller_one) def controller_one @inputs.controller_one end + # @see Inputs#controller_two + # @return (see Inputs#controller_two) def controller_two @inputs.controller_two end diff --git a/dragon/attr_gtk.rb b/dragon/attr_gtk.rb index cf0b9f0..b72f946 100644 --- a/dragon/attr_gtk.rb +++ b/dragon/attr_gtk.rb @@ -3,6 +3,7 @@ # MIT License # attr_gtk.rb has been released under MIT (*only this file*). +# @private module AttrGTK attr_accessor :args diff --git a/dragon/attr_sprite.rb b/dragon/attr_sprite.rb index 7865293..e47e051 100644 --- a/dragon/attr_sprite.rb +++ b/dragon/attr_sprite.rb @@ -2,12 +2,14 @@ # MIT License # attr_sprite.rb has been released under MIT (*only this file*). +# @private module AttrSprite include GTK::Geometry attr_accessor :x, :y, :w, :h, :path, :angle, :a, :r, :g, :b, :tile_x, :tile_y, :tile_w, :tile_h, :flip_horizontally, - :flip_vertically, :angle_anchor_x, :angle_anchor_y, :id + :flip_vertically, :angle_anchor_x, :angle_anchor_y, :id, + :source_x, :source_y, :source_w, :source_h def primitive_marker :sprite diff --git a/dragon/config.rb b/dragon/config.rb new file mode 100644 index 0000000..de57085 --- /dev/null +++ b/dragon/config.rb @@ -0,0 +1,399 @@ +# coding: utf-8 +# Copyright 2019 DragonRuby LLC +# MIT License +# controller/config.rb has been released under MIT (*only this file*). + +# !!! FIXME: add console command to forget custom binding(s) +# !!! FIXME: add console command to forget replace existing binding(s) +# !!! FIXME: add console command go into play_around mode to make sure controller isn't wonky. + +module GTK + class Controller + class Config + def initialize runtime + @runtime = runtime + @raw_joysticks = {} # things that aren't game controllers to try to configure. + @target = nil + @animation_duration = (1.5).seconds + @toggled_at = 0 + @fading = 0 + @current_part = 0 + @part_alpha = 0 + @part_alpha_increment = 10 + @joystick_state = {} + @playing_around = false + @used_bindings = {} + @bindings = [] + @parts = [ + [ 919, 282, 'A button', 'a' ], + [ 960, 323, 'B button', 'b' ], + [ 878, 323, 'X button', 'x' ], + [ 919, 365, 'Y button', 'y' ], + [ 433, 246, 'left stick left', '-leftx' ], + [ 497, 246, 'left stick right', '+leftx' ], + [ 466, 283, 'left stick up', '-lefty' ], + [ 466, 218, 'left stick down', '+lefty' ], + [ 466, 246, 'left stick button', 'leftstick' ], + [ 741, 246, 'right stick left', '-rightx' ], + [ 802, 246, 'right stick right', '+rightx' ], + [ 773, 283, 'right stick up', '-righty' ], + [ 773, 218, 'right stick down', '+righty' ], + [ 772, 246, 'right stick button', 'rightstick' ], + [ 263, 465, 'left shoulder button', 'leftshoulder' ], + [ 263, 503, 'left trigger', 'lefttrigger' ], + [ 977, 465, 'right shoulder button', 'rightshoulder' ], + [ 977, 503, 'right trigger', 'righttrigger' ], + [ 318, 365, 'D-pad up', 'dpup' ], + [ 360, 322, 'D-pad right', 'dpright' ], + [ 318, 280, 'D-pad down', 'dpdown' ], + [ 275, 322, 'D-pad left', 'dpleft' ], + [ 570, 402, 'select/back button', 'back'], + [ 619, 448, 'guide/home button', 'guide' ], + [ 669, 402, 'start button', 'start' ], + ] + end + + def rawjoystick_connected jid, joystickname, guid + return if jid < 0 + @raw_joysticks[jid] = { name: joystickname, guid: guid } + end + + def rawjoystick_disconnected jid + return if jid < 0 + if @raw_joysticks[jid] != nil + @raw_joysticks.delete(jid) + @runtime.ffi_misc.close_raw_joystick(jid) + # Fade out the config screen if we were literally configuring this controller right now. + if [email protected]? && @target[0] == jid + @target[0] = nil + @toggled_at = Kernel.global_tick_count + @fading = -1 + end + end + end + + def build_binding_string + bindingstr = '' + skip = false + + for i in [email protected] + if skip ; skip = false ; next ; end + + binding = @bindings[i] + next if binding.nil? + + part = @parts[i][3] + + # clean up string: + # if axis uses -a0 for negative and +a0 for positive, just make it "leftx:a0" instead of "-leftx:-a0,+leftx:+a0" + # if axis uses +a0 for negative and -a0 for positive, just make it "leftx:a0~" instead of "-leftx:+a0,+leftx:-a0" + if part == '-leftx' || part == '-lefty' || part == '-rightx' || part == '-righty' + nextbinding = @bindings[i+1] + if binding.start_with?('-a') && nextbinding.start_with?('+a') && binding[2..-1] == nextbinding[2..-1] + skip = true + part = part[1..-1] + binding = binding[1..-1] + elsif binding.start_with?('+a') && nextbinding.start_with?('-a') && binding[2..-1] == nextbinding[2..-1] + skip = true + part = part[1..-1] + binding = "#{binding[1..-1]}~" + end + end + + bindingstr += "#{!bindingstr.empty? ? ',' : ''}#{part}:#{binding}" + end + + details = @target[1] + + # !!! FIXME: no String.delete in mRuby?!?! Maybe so when upgrading. + #name = details[:name].delete(',') + # !!! FIXME: ...no regexp either... :/ + #name = details[:name].gsub(/,/, ' ') # !!! FIXME: will SDL let you escape these instead? + unescaped = details[:name] + name = '' + for i in 0..unescaped.length-1 + ch = unescaped[i] + name += (ch == ',') ? ' ' : ch + end + return "#{details[:guid]},#{name},platform:#{@runtime.platform},#{bindingstr}" + end + + def move_to_different_part part + if !@joystick_state[:axes].nil? + @joystick_state[:axes].each { |i| i[:farthestval] = i[:startingval] if !i.nil? } + end + @current_part = part + end + + def previous_part + if @current_part > 0 + # remove the binding that we previous had here so it can be reused. + bindstr = @bindings[@current_part - 1] + @bindings[@current_part - 1] = nil + @used_bindings[bindstr] = nil + move_to_different_part @current_part - 1 + end + end + + def next_part + if @current_part < (@parts.length - 1) + move_to_different_part @current_part + 1 + else + @playing_around = true + end + end + + def set_binding bindstr + return false if !@used_bindings[bindstr].nil? + @used_bindings[bindstr] = @current_part + @bindings[@current_part] = bindstr + return true + end + + # Called when a lowlevel joystick moves an axis. + def rawjoystick_axis jid, axis, value + return if @target.nil? || jid != @target[0] || @fading != 0 # skip if not currently considering this joystick. + + @joystick_state[:axes] ||= [] + @joystick_state[:axes][axis] ||= { + moving: false, + startingval: 0, + currentval: 0, + farthestval: 0 + } + + # this is the logic from SDL's controllermap.c, more or less, since this is hard to get right from scratch. + state = @joystick_state[:axes][axis] + state[:currentval] = value + if !state[:moving] + state[:moving] = true + state[:startingval] = value + state[:farthestval] = value + end + + current_distance = (value - state[:startingval]).abs + farthest_distance = (state[:farthestval] - state[:startingval]).abs + if current_distance > farthest_distance + state[:farthestval] = value + farthest_distance = (state[:farthestval] - state[:startingval]).abs + end + + # If we've gone out far enough and started to come back, let's bind this axis + if (farthest_distance >= 16000) && (current_distance <= 10000) + next_part if set_binding("#{(state[:farthestval] < 0) ? '-' : '+'}a#{axis}") + end + end + + # Called when a lowlevel joystick moves a hat. + def rawjoystick_hat jid, hat, value + return if @target.nil? || jid != @target[0] || @fading != 0 # skip if not currently considering this joystick. + + @joystick_state[:hats] ||= [] + @joystick_state[:hats][hat] = value + + return if value == 0 # 0 == centered, skip it + next_part if set_binding("h#{hat}.#{value}") + end + + # Called when a lowlevel joystick moves a button. + def rawjoystick_button jid, button, pressed + return if @target.nil? || jid != @target[0] || @fading != 0 # skip if not currently considering this joystick. + + @joystick_state[:buttons] ||= [] + @joystick_state[:buttons][button] = pressed + + return if !pressed + next_part if set_binding("b#{button}") + end + + def calc_fading + if @fading == 0 + return 255 + elsif @fading > 0 # fading in + percent = @toggled_at.global_ease(@animation_duration, :flip, :quint, :flip) + if percent >= 1.0 + percent = 1.0 + @fading = 0 + end + else # fading out + percent = @toggled_at.global_ease(@animation_duration, :flip, :quint) + if percent <= 0.0 + percent = 0.0 + @fading = 0 + end + end + + return (percent * 255.0).to_i + end + + def render_basics args, msg, fade=255 + joystickname = @target[1][:name] + args.outputs.primitives << [0, 0, $gtk.logical_width, $gtk.logical_height, 255, 255, 255, fade].solid + args.outputs.primitives << [0, 0, $gtk.logical_width, $gtk.logical_height, 'dragonruby-controller.png', 0, fade, 255, 255, 255].sprite + args.outputs.primitives << [$gtk.logical_width / 2, 700, joystickname, 2, 1, 0, 0, 0, fade].label + args.outputs.primitives << [$gtk.logical_height / 2, 650, msg, 0, 1, 0, 0, 0, 255].label if !msg.empty? + end + + def render_part_highlight args, part, alpha=255 + partsize = 41 + args.outputs.primitives << [part[0], part[1], partsize, partsize, 255, 0, 0, alpha].border + args.outputs.primitives << [part[0]-1, part[1]-1, partsize+2, partsize+2, 255, 0, 0, alpha].border + args.outputs.primitives << [part[0]-2, part[1]-2, partsize+4, partsize+4, 255, 0, 0, alpha].border + end + + def choose_target + if @target.nil? + while !@raw_joysticks.empty? + t = @raw_joysticks.shift # see if there's a joystick waiting on us. + next if t[0] < 0 # just in case. + next if t[1][:guid].nil? # did we already handle this guid? Dump it. + @target = t + break + end + return false if @target.nil? # nothing to configure at the moment. + @toggled_at = Kernel.global_tick_count + @fading = 1 + @current_part = 0 + @part_alpha = 0 + @part_alpha_increment = 10 + @joystick_state = {} + @used_bindings = {} + @playing_around = false + @bindings = [] + end + return true + end + + def render_part_highlight_from_bindstr args, bindstr, alpha=255 + partidx = @used_bindings[bindstr] + return if partidx.nil? + render_part_highlight args, @parts[partidx], alpha + end + + def play_around args + return false if !@playing_around + + if args.inputs.keyboard.key_down.escape + @current_part = 0 + @part_alpha = 0 + @part_alpha_increment = 10 + @used_bindings = {} + @playing_around = false + @bindings = [] + elsif args.inputs.keyboard.key_down.space + jid = @target[0] + bindingstr = build_binding_string + #puts("new controller binding: '#{bindingstr}'") + @runtime.ffi_misc.add_controller_config bindingstr + @runtime.ffi_misc.convert_rawjoystick_to_controller jid + @target[0] = -1 # Conversion closes the raw joystick. + + # Handle any other pending joysticks that have the same GUID (so if you plug in four of the same model, we're already done!) + guid = @target[1][:guid] + @raw_joysticks.each { |jid, details| + if details[:guid] == guid + @runtime.ffi_misc.convert_rawjoystick_to_controller jid + details[:guid] = nil + end + } + + # Done with this guy. + @playing_around = false + @toggled_at = Kernel.global_tick_count + @fading = -1 + return false + end + + render_basics args, 'Now play around with the controller, and make sure it feels right!' + args.outputs.primitives << [$gtk.logical_width / 2, 90, '[ESCAPE]: Reconfigure, [SPACE]: Save this configuration', 0, 1, 0, 0, 0, 255].label + + axes = @joystick_state[:axes] + if !axes.nil? + for i in 0..axes.length-1 + next if axes[i].nil? + value = axes[i][:currentval] + next if value.nil? || (value.abs < 16000) + render_part_highlight_from_bindstr args, "#{value < 0 ? '-' : '+'}a#{i}" + end + end + + hats = @joystick_state[:hats] + if !hats.nil? + for i in 0..hats.length-1 + value = hats[i] + next if value.nil? || (value == 0) + render_part_highlight_from_bindstr args, "h#{i}.#{value}" + end + end + + buttons = @joystick_state[:buttons] + if !buttons.nil? + for i in 0..buttons.length-1 + value = buttons[i] + next if value.nil? || !value + render_part_highlight_from_bindstr args, "b#{i}" + end + end + + return true + end + + def should_tick? + return true if @play_around + return true if @target + return false + end + + def tick args + return true if play_around args + return false if !choose_target + + jid = @target[0] + + if @fading == 0 + # Cancel config? + if args.inputs.keyboard.key_down.escape + # !!! FIXME: prompt to ignore this joystick forever or just this run + @toggled_at = Kernel.global_tick_count + @fading = -1 + end + end + + if @fading == 0 + if args.inputs.keyboard.key_down.backspace + previous_part + elsif args.inputs.keyboard.key_down.space + next_part + end + end + + fade = calc_fading + if (@fading < 0) && (fade == 0) + @runtime.ffi_misc.close_raw_joystick(jid) if jid >= 0 + @target = nil # done with this controller + return false + end + + render_basics args, (@fading >= 0) ? "We don't recognize this controller, so tell us about it!" : '', fade + + return true if fade < 255 # all done for now + + part = @parts[@current_part] + args.outputs.primitives << [$gtk.logical_width / 2, 575, "Please press the #{part[2]}.", 0, 1, 0, 0, 0, 255].label + render_part_highlight args, part, @part_alpha + args.outputs.primitives << [$gtk.logical_width / 2, 90, '[ESCAPE]: Ignore controller, [BACKSPACE]: Go back one button, [SPACE]: Skip this button', 0, 1, 0, 0, 0, 255].label + + @part_alpha += @part_alpha_increment + if (@part_alpha_increment > 0) && (@part_alpha >= 255) + @part_alpha = 255 + @part_alpha_increment = -10 + elsif (@part_alpha_increment < 0) && (@part_alpha <= 0) + @part_alpha = 0 + @part_alpha_increment = 10 + end + + return true + end + end + end +end diff --git a/dragon/console.rb b/dragon/console.rb index 9a8b55f..4f2abab 100644 --- a/dragon/console.rb +++ b/dragon/console.rb @@ -2,6 +2,9 @@ # MIT License # console.rb has been released under MIT (*only this file*). +# Contributors outside of DragonRuby who also hold Copyright: +# - Kevin Fischer: https://github.com/kfischer-okarin + module GTK class Console class Color @@ -55,14 +58,15 @@ module GTK end class Prompt - attr_accessor :current_input_str, :font_style + attr_accessor :current_input_str, :font_style, :console_text_width - def initialize(font_style:, text_color:) + def initialize(font_style:, text_color:, console_text_width:) @prompt = '-> ' @current_input_str = '' @font_style = font_style @text_color = text_color @cursor_color = Color.new [187, 21, 6] + @console_text_width = console_text_width @last_autocomplete_prefix = nil @next_candidate_index = 0 @@ -87,7 +91,8 @@ module GTK if !@last_autocomplete_prefix @last_autocomplete_prefix = calc_autocomplete_prefix - puts method_candidates(@last_autocomplete_prefix) + puts "* AUTOCOMPLETE CANDIDATES: #{current_input_str}.." + pretty_print_strings_as_table method_candidates(@last_autocomplete_prefix) else candidates = method_candidates(@last_autocomplete_prefix) return if candidates.empty? @@ -100,6 +105,60 @@ module GTK end end + def pretty_print_strings_as_table items + if items.length == 0 + puts <<-S.strip ++--------+ +| (none) | ++--------+ +S + else + # figure out the largest string + string_width = items.sort_by { |c| -c.to_s.length }.first + + # add spacing to each side of the string which represents the cell width + cell_width = string_width.length + 2 + + # add spacing to each side of the cell to represent the column width + column_width = cell_width + 2 + + # determine the max number of columns that can fit on the screen + columns = @console_text_width.idiv column_width + columns = items.length if items.length < columns + + # partition the original list of items into a string to be printed + items.each_slice(columns).each_with_index do |cells, i| + pretty_print_row_seperator string_width, cell_width, column_width, columns + pretty_print_row cells, string_width, cell_width, column_width, columns + end + + pretty_print_row_seperator string_width, cell_width, column_width, columns + end + end + + def pretty_print_row cells, string_width, cell_width, column_width, columns + # if the number of cells doesn't match the number of columns, then pad the array with empty values + cells += (columns - cells.length).map { "" } + + # right align each cell value + formated_row = "|" + cells.map do |c| + "#{" " * (string_width.length - c.length) } #{c} |" + end.join + + # remove seperators between empty values + formated_row = formated_row.gsub(" | ", " ") + + puts formated_row + end + + def pretty_print_row_seperator string_width, cell_width, column_width, columns + # this is a joint: +-------- + column_joint = "+#{"-" * cell_width}" + + # multiple joints create a row seperator: +----+----+ + puts (column_joint * columns) + "+" + end + def render(args, x:, y:) args.outputs.reserved << font_style.label(x: x, y: y, text: "#{@prompt}#{current_input_str}", color: @text_color) args.outputs.reserved << font_style.label(x: x - 2, y: y + 3, text: (" " * (@prompt.length + current_input_str.length)) + "|", color: @cursor_color) @@ -128,7 +187,7 @@ module GTK end def method_candidates(prefix) - current_object.methods.map(&:to_s).select { |m| m.start_with? prefix } + current_object.autocomplete_methods.map(&:to_s).select { |m| m.start_with? prefix } end def display_autocomplete_candidate(candidate) @@ -179,7 +238,7 @@ module GTK end def console_text_width - @console_text_width ||= (GAME_WIDTH - 20).idiv(font_style.letter_size.x) + @console_text_width ||= ($gtk.logical_width - 20).idiv(font_style.letter_size.x) end def save_history @@ -285,12 +344,14 @@ module GTK @visible end + # @gtk def show reason = nil @shown_at = Kernel.global_tick_count @show_reason = reason toggle if hidden? end + # @gtk def hide if visible? toggle @@ -417,6 +478,7 @@ S Kernel.eval("$results = (#{cmd})") if $results.nil? puts "=> nil" + elsif $results == :console_silent_eval else puts "=> #{$results}" end @@ -696,6 +758,7 @@ S show show_reason end + # @gtk def set_command command, show_reason = nil set_command_silent command, show_reason show show_reason @@ -708,14 +771,14 @@ S private def w - GAME_WIDTH + $gtk.logical_width end def h - GAME_HEIGHT + $gtk.logical_height end - # def top; def left; def right + # methods top; left; right # Forward to grid %i[top left right].each do |method| define_method method do @@ -735,8 +798,15 @@ S [left, y, right, y, *color].line end + def include_row_marker? log_entry + log_entry[0] == "|" + end + def color_for_log_entry(log_entry) - if include_error_marker? log_entry + + if include_row_marker? log_entry + @text_color + elsif include_error_marker? log_entry @error_color elsif include_subdued_markers? log_entry @text_color.mult_alpha(0.5) @@ -748,7 +818,7 @@ S end def prompt - @prompt ||= Prompt.new(font_style: font_style, text_color: @text_color) + @prompt ||= Prompt.new(font_style: font_style, text_color: @text_color, console_text_width: console_text_width) end def current_input_str @@ -758,5 +828,12 @@ S def current_input_str=(str) prompt.current_input_str = str end + + def clear + @archived_log.clear + @log.clear + @prompt.clear + :console_silent_eval + end end end diff --git a/dragon/controller.rb b/dragon/controller.rb new file mode 100644 index 0000000..f67df63 --- /dev/null +++ b/dragon/controller.rb @@ -0,0 +1,122 @@ +# coding: utf-8 +# Copyright 2019 DragonRuby LLC +# MIT License +# controller.rb has been released under MIT (*only this file*). + +module GTK + # @gtk + class Controller + # Access to keys that have been pressed down. + # + # @return [Controller::Keys] + # @gtk + attr_reader :key_down + + # Access to keys that have been released up. + # + # @return [Controller::Keys] + # @gtk + attr_reader :key_up + + # Access to keys that have been held down. + # + # @return [Controller::Keys] + # @gtk + attr_reader :key_held + + # @gtk + attr_accessor :left_analog_x_raw, + :left_analog_y_raw, + :left_analog_x_perc, + :left_analog_y_perc, + :right_analog_x_raw, + :right_analog_y_raw, + :right_analog_x_perc, + :right_analog_y_perc + + + def initialize + @key_down = Controller::Keys.new + @key_up = Controller::Keys.new + @key_held = Controller::Keys.new + @left_analog_x_raw = 0 + @left_analog_y_raw = 0 + @left_analog_x_perc = 0 + @left_analog_y_perc = 0 + @right_analog_x_raw = 0 + @right_analog_y_raw = 0 + @right_analog_x_perc = 0 + @right_analog_y_perc = 0 + end + + def serialize + { + key_down: @key_down.serialize, + key_held: @key_held.serialize, + key_up: @key_up.serialize + } + end + + # Clear all current key presses. + # + # @return [void] + def clear + @key_down.clear + @key_up.clear + @key_held.clear + end + + def up + @key_up.up || @key_held.up + end + + def down + @key_up.down || @key_held.down + end + + def left + @key_up.left || @key_held.left + end + + def right + @key_up.right || @key_held.right + end + + # Activates a key into the down position. + # + # @param key [Symbol] The key to press down. + # + # @return [void] + def activate_down(key) + key_down.activate(key) + key_held.deactivate(key) + key_up.deactivate(key) + end + + # Activates a key into the held down position. + # + # @param key [Symbol] The key to hold down. + # + # @return [void] + def activate_held(key) + key_down.deactivate(key) + key_held.activate(key) unless key_held.send(key) + key_up.deactivate(key) + end + + + # Activates a key release into the up position. + # + # @param key [Symbol] The key release up. + # + # @return [void] + def activate_up(key) + key_down.deactivate(key) + key_held.deactivate(key) + key_up.activate(key) + end + + include DirectionalInputHelperMethods + end +end + diff --git a/dragon/directional_input_helper_methods.rb b/dragon/directional_input_helper_methods.rb index 8550e59..dcf5f07 100644 --- a/dragon/directional_input_helper_methods.rb +++ b/dragon/directional_input_helper_methods.rb @@ -4,7 +4,7 @@ # directional_input_helper_methods.rb has been released under MIT (*only this file*). module GTK - # normalization of behavior related to up|down|left|right on keyboards and controllers + # This is a module that contains normalization of behavior related to `up`|`down`|`left`|`right` on keyboards and controllers. module DirectionalInputHelperMethods def self.included klass key_state_methods = [:key_held, :key_down] @@ -29,18 +29,35 @@ S end end + # Returns a signal indicating left (`-1`), right (`1`), or neither ('0'). + # + # @return [Integer] def left_right return -1 if self.left return 1 if self.right return 0 end + # Returns a signal indicating up (`1`), down (`-1`), or neither ('0'). + # + # @return [Integer] def up_down return 1 if self.up return -1 if self.down return 0 end + # Returns a normal vector (in the form of an Array with two values). If no directionals are held/down, the function returns nil. + # + # The possible results are: + # + # - `nil` which denotes that no directional input exists. + # - `[ 0, 1]` which denotes that only up is being held/pressed. + # - `[ 0, -1]` which denotes that only down is being held/pressed. + # - `[ 1, 1]` which denotes that right and up are being pressed/held. + # - `[-1, -1]` which denotes that left and down are being pressed/held. + # + # @gtk def directional_vector lr, ud = [self.left_right, self.up_down] @@ -62,8 +79,8 @@ S end return send(m) - # see if the key is either held or down else + # see if the key is either held or down define_singleton_method(m) do self.key_down.send(m) || self.key_held.send(m) end diff --git a/dragon/geometry.rb b/dragon/geometry.rb index 777adcd..67e6a08 100644 --- a/dragon/geometry.rb +++ b/dragon/geometry.rb @@ -5,6 +5,7 @@ module GTK module Geometry + # Returns f(t) for a cubic Bezier curve. def self.cubic_bezier t, a, b, c, d s = 1 - t s0 = 1 @@ -23,10 +24,14 @@ module GTK 1 * s0 * t3 * d end + # Returns true if a primitive's rectangle is entirely inside another primitive's rectangle. + # @gtk def inside_rect? outer Geometry.inside_rect? self, outer end + # Returns true if a primitive's rectangle overlaps another primitive's rectangle. + # @gtk def intersect_rect? other, tolerance = 0.1 Geometry.intersect_rect? self, other, tolerance end @@ -47,22 +52,32 @@ module GTK anchor_y: anchor_y end + # Scales a primitive rect by a percentage. + # @gtk def scale_rect percentage, *anchors Geometry.scale_rect self, percentage, *anchors end + # Returns the angle from one primitive to another primitive. + # @gtk def angle_to other_point Geometry.angle_to self, other_point end + # Returns the angle to one primitive from another primitive. + # @gtk def angle_from other_point Geometry.angle_from self, other_point end + # Returns true if a primitive is within a circle specified by the circle's center and radius. + # @gtk def point_inside_circle? circle_center_point, radius Geometry.point_inside_circle? self, circle_center_point, radius end + # Returns a primitive that is anchored/repositioned based off its retangle. + # @gtk def anchor_rect anchor_x, anchor_y current_w = self.w current_h = self.h @@ -75,6 +90,7 @@ module GTK raise ":angle_given_point has been deprecated use :angle_from instead." end + # @gtk def self.shift_line line, x, y if line.is_a?(Array) || line.is_a?(Hash) new_line = line.dup @@ -102,21 +118,25 @@ rule for a long time (and why intersects_rect? has been deprecated). S end + # @gtk def self.line_y_intercept line line.y - line_slope(line) * line.x end + # @gtk def self.angle_between_lines line_one, line_two, replace_infinity: nil m_line_one = line_slope line_one, replace_infinity: replace_infinity m_line_two = line_slope line_two, replace_infinity: replace_infinity Math.atan((m_line_one - m_line_two) / (1 + m_line_two * m_line_one)).to_degrees end + # @gtk def self.line_slope line, replace_infinity: nil (line.y2 - line.y).fdiv(line.x2 - line.x) .replace_infinity(replace_infinity) end + # @gtk def self.ray_test point, line slope = (line.y2 - line.y).fdiv(line.x2 - line.x) @@ -138,6 +158,7 @@ S end end + # @gtk def self.line_rect line if line.x > line.x2 x = line.x2 @@ -157,6 +178,7 @@ S { x: x, y: y, w: w, h: h } end + # @gtk def self.line_intersect line_one, line_two m1 = line_slope(line_one) m2 = line_slope(line_two) @@ -167,6 +189,7 @@ S [x, y] end + # @gtk def self.intersect_rect? rect_one, rect_two, tolerance = 0.1 return false if rect_one.right - tolerance < rect_two.left + tolerance return false if rect_one.left + tolerance > rect_two.right - tolerance @@ -177,6 +200,7 @@ S raise e, ":intersect_rect? failed for rect_one: #{rect_one} rect_two: #{rect_two}." end + # @gtk def self.to_square size, x, y, anchor_x = 0.5, anchor_y = nil anchor_y ||= anchor_x x = x.shift_left(size * anchor_x) @@ -186,12 +210,14 @@ S raise e, ":to_square failed for size: #{size} x: #{x} y: #{y} anchor_x: #{anchor_x} anchor_y: #{anchor_y}." end + # @gtk def self.distance point_one, point_two Math.sqrt((point_two.x - point_one.x)**2 + (point_two.y - point_one.y)**2) rescue Exception => e raise e, ":distance failed for point_one: #{point_one} point_two #{point_two}." end + # @gtk def self.angle_from start_point, end_point d_y = end_point.y - start_point.y d_x = end_point.x - start_point.x @@ -200,18 +226,21 @@ S raise e, ":angle_from failed for start_point: #{start_point} end_point: #{end_point}." end + # @gtk def self.angle_to start_point, end_point angle_from end_point, start_point rescue Exception => e raise e, ":angle_to failed for start_point: #{start_point} end_point: #{end_point}." end + # @gtk def self.point_inside_circle? point, circle_center_point, radius (point.x - circle_center_point.x) ** 2 + (point.y - circle_center_point.y) ** 2 < radius ** 2 rescue Exception => e raise e, ":point_inside_circle? failed for point: #{point} circle_center_point: #{circle_center_point} radius: #{radius}" end + # @gtk def self.inside_rect? inner_rect, outer_rect inner_rect.x >= outer_rect.x && inner_rect.right <= outer_rect.right && @@ -221,6 +250,7 @@ S raise e, ":inside_rect? failed for inner_rect: #{inner_rect} outer_rect: #{outer_rect}." end + # @gtk def self.scale_rect_extended rect, percentage_x: percentage_x, percentage_y: percentage_y, @@ -255,6 +285,7 @@ S raise e, ":scale_rect_extended failed for rect: #{rect} percentage_x: #{percentage_x} percentage_y: #{percentage_y} anchors_x: #{anchor_x} anchor_y: #{anchor_y}." end + # @gtk def self.scale_rect rect, percentage, *anchors anchor_x, anchor_y = *anchors.flatten anchor_x ||= 0 diff --git a/dragon/grid.rb b/dragon/grid.rb index b794a7d..e5c21a3 100644 --- a/dragon/grid.rb +++ b/dragon/grid.rb @@ -7,28 +7,89 @@ module GTK class Grid include Serialize SCREEN_Y_DIRECTION = -1.0 - attr_accessor :bottom, :left, :right, :top, - :rect, :origin_x, :origin_y, :center_x, :center_y, - :name + + # The coordinate system currently in use. + # + # @return [Symbol] `:bottom_left` or `:center` + attr_accessor :name + + # Returns the "x" coordinate indicating the bottom of the screen. + # + # @return [Float] + attr_accessor :bottom + + # Returns the "x" coordinate indicating the top of the screen. + # + # @return [Float] + attr_accessor :top + + # Returns the "y" coordinate indicating the left of the screen. + # + # @return [Float] + attr_accessor :left + + # Returns the "y" coordinate indicating the right of the screen. + # + # @return [Float] + attr_accessor :right + + # Returns the "x" coordinate indicating the center of the screen. + # + # @return [Float] + attr_accessor :center_x + + # Returns the "y" coordinate indicating the center of the screen. + # + # @return [Float] + attr_accessor :center_y + + # Returns the bottom left and top right coordinates in a single list. + # + # @return [[Float, Float, Float, Float]] + attr_accessor :rect + + # Returns the "x" coordinate of the origin. + # + # @return [Float] + attr_accessor :origin_x + + # Returns the "y" coordinate of the origin. + # + # @return [Float] + attr_accessor :origin_y + + attr_accessor :left_margin, :bottom_margin def initialize ffi_draw @ffi_draw = ffi_draw origin_bottom_left! end + # Returns `x` plus the origin "x". + # + # @return [Float] def transform_x x @origin_x + x end + # Returns `x` minus the origin "x". + # + # @return [Float] def untransform_x x x - @origin_x end - def untransform_y y + # Returns `y` plus the origin "y". + # + # @return [Float] + def transform_y y @origin_y + y * SCREEN_Y_DIRECTION end - def transform_y y + # Returns `y` minus the origin "y". + # + # @return [Float] + def untransform_y y @origin_y + y * SCREEN_Y_DIRECTION end @@ -40,58 +101,86 @@ module GTK @ffi_draw = value end + # Sets the rendering coordinate system to have its origin in the bottom left. + # + # @return [void] + # @gtk def origin_bottom_left! return if @name == :bottom_left @name = :bottom_left @origin_x = 0.0 - @origin_y = GAME_HEIGHT + @origin_y = $gtk.logical_height @left = 0.0 - @right = GAME_WIDTH - @top = GAME_HEIGHT + @right = $gtk.logical_width + @top = $gtk.logical_height @bottom = 0.0 - @center_x = GAME_WIDTH.half - @center_y = GAME_HEIGHT.half - @rect = [@left, @bottom, GAME_WIDTH, GAME_HEIGHT].rect + @left_margin = 0.0 + @bottom_margin = 0.0 + @center_x = $gtk.logical_width.half + @center_y = $gtk.logical_height.half + @rect = [@left, @bottom, $gtk.logical_width, $gtk.logical_height].rect @center = [@center_x, @center_y].point @ffi_draw.set_gtk_grid @origin_x, @origin_y, SCREEN_Y_DIRECTION end + # Sets the rendering coordinate system to have its origin in the center. + # + # @return [void] + # @gtk def origin_center! return if @name == :center @name = :center - @origin_x = GAME_WIDTH.half - @origin_y = GAME_HEIGHT.half - @left = -GAME_WIDTH.half - @right = GAME_WIDTH.half - @top = GAME_HEIGHT.half - @bottom = -GAME_HEIGHT.half + @origin_x = $gtk.logical_width.half + @origin_y = $gtk.logical_height.half + @left = -$gtk.logical_width.half + @right = $gtk.logical_width.half + @top = $gtk.logical_height.half + @bottom = -$gtk.logical_height.half @center_x = 0.0 @center_y = 0.0 - @rect = [@left, @bottom, GAME_WIDTH, GAME_HEIGHT].rect + @rect = [@left, @bottom, $gtk.logical_width, $gtk.logical_height].rect @center = [@center_x, @center_y].point @ffi_draw.set_gtk_grid @origin_x, @origin_y, SCREEN_Y_DIRECTION end + # The logical width used for rendering. + # + # @return [Float] def w - GAME_WIDTH + $gtk.logical_width end + # Half the logical width used for rendering. + # + # @return [Float] def w_half w.half end + # The logical height used for rendering. + # + # @return [Float] def h - GAME_HEIGHT + $gtk.logical_height end + # Half the logical height used for rendering. + # + # @return [Float] def h_half h.half end + # Returns the coordinates indicating the center of the screen. + # + # @return [[Float, Float]] def center @center end + # Returns the coordinates indicating the bottom right of the screen. + # + # @return [[Float, Float]] def bottom_right [@right, @bottom].point end diff --git a/dragon/inputs.rb b/dragon/inputs.rb index 9b8720a..bbf5305 100644 --- a/dragon/inputs.rb +++ b/dragon/inputs.rb @@ -4,9 +4,12 @@ # inputs.rb has been released under MIT (*only this file*). module GTK + # Represents all the keys available on the keyboard. + # @gtk class KeyboardKeys include Serialize + # @gtk attr_accessor :exclamation_point, :zero, :one, :two, :three, :four, :five, :six, :seven, :eight, :nine, @@ -179,23 +182,27 @@ module GTK @scrubbed_ivars = nil end + # @gtk def left_right return -1 if self.left return 1 if self.right return 0 end + # @gtk def up_down return 1 if self.up return -1 if self.down return 0 end + # @gtk def truthy_keys get(all).find_all { |_, v| v } .map { |k, _| k.to_sym } end + # @gtk def all? keys values = get(keys.map { |k| k.without_ending_bang }) all_true = values.all? do |k, v| @@ -211,6 +218,7 @@ module GTK all_true end + # @gtk def any? keys values = get(keys.map { |k| k.without_ending_bang }) any_true = values.any? do |k, v| @@ -226,11 +234,13 @@ module GTK any_true end + # @gtk def clear_key key @scrubbed_ivars = nil self.instance_variable_set("@#{key.without_ending_bang}", false) end + # @gtk def all @scrubbed_ivars ||= self.instance_variables .reject { |i| i == :@all || i == :@scrubbed_ivars } @@ -239,6 +249,7 @@ module GTK get(@scrubbed_ivars).map { |k, _| k } end + # @gtk def get collection return [] if collection.length == 0 collection.map do |m| @@ -252,6 +263,7 @@ module GTK end end + # @gtk def set collection, value = true return if collection.length == 0 @scrubbed_ivars = nil @@ -305,8 +317,24 @@ S end module GTK + # @gtk class Keyboard - attr_accessor :key_up, :key_held, :key_down, :has_focus + + # @return [KeyboardKeys] + # @gtk + attr_accessor :key_up + + # @return [KeyboardKeys] + # @gtk + attr_accessor :key_held + + # @return [KeyboardKeys] + # @gtk + attr_accessor :key_down + + # @return [Boolean] + # @gtk + attr_accessor :has_focus def initialize @key_up = KeyboardKeys.new @@ -319,22 +347,37 @@ module GTK @key_down.p || @key_held.p end + # The left arrow or "a" was pressed. + # + # @return [Boolean] def left @key_up.left || @key_held.left || a end + # The right arrow or "d" was pressed. + # + # @return [Boolean] def right @key_up.right || @key_held.right || d end + # The up arrow or "w" was pressed. + # + # @return [Boolean] def up @key_up.up || @key_held.up || w end + # The down arrow or "s" was pressed. + # + # @return [Boolean] def down @key_up.down || @key_held.down || s end + # Clear all current key presses. + # + # @return [void] def clear @key_up.clear @key_held.clear @@ -343,25 +386,19 @@ module GTK def serialize { - key_up: @key_up.serialize, - key_held: @key_held.serialize, - key_down: @key_down.serialize, - has_focus: @has_focus + key_up: @key_up.serialize, + key_held: @key_held.serialize, + key_down: @key_down.serialize, + has_focus: @has_focus } end + alias_method :inspect, :serialize - def inspect - serialize - end - + # @return [String] def to_s serialize.to_s end - def keys - key - end - def key { down: @key_down.truthy_keys, @@ -370,128 +407,7 @@ module GTK up: @key_up.truthy_keys, } end - - include DirectionalInputHelperMethods - end -end - - -module GTK - class ControllerKeys - include Serialize - attr_accessor :up, :down, :left, :right, - :a, :b, :x, :y, - :l1, :r1, - :l2, :r2, - :l3, :r3, - :start, :select, - :directional_up, - :directional_down, - :directional_left, - :directional_right - def clear - @up = nil - @down = nil - @left = nil - @right = nil - @a = nil - @b = nil - @x = nil - @y = nil - @l1 = nil - @r1 = nil - @l2 = nil - @r2 = nil - @l3 = nil - @r3 = nil - @start = nil - @select = nil - @directional_up = nil - @directional_down = nil - @directional_left = nil - @directional_right = nil - end - - def truthy_keys - [ - :up, :down, :left, :right, - :a, :b, :x, :y, - :l1, :r1, :l2, :r2, :l3, :r3, - :start, :select, - :directional_up, :directional_down, :directional_left, :directional_right, - ].find_all { |attr| send(attr) }.to_a - end - end -end - -module GTK - class Controller - attr_accessor :key_down, :key_up, :key_held, :left_right, :up_down, - :left_analog_x_raw, - :left_analog_y_raw, - :left_analog_x_perc, - :left_analog_y_perc, - :right_analog_x_raw, - :right_analog_y_raw, - :right_analog_x_perc, - :right_analog_y_perc - - - def initialize - @key_down = ControllerKeys.new - @key_up = ControllerKeys.new - @key_held = ControllerKeys.new - @left_analog_x_raw = 0 - @left_analog_y_raw = 0 - @left_analog_x_perc = 0 - @left_analog_y_perc = 0 - @right_analog_x_raw = 0 - @right_analog_y_raw = 0 - @right_analog_x_perc = 0 - @right_analog_y_perc = 0 - end - - def left_right - return -1 if self.left - return 1 if self.right - return 0 - end - - def up_down - return 1 if self.up - return -1 if self.down - return 0 - end - - def serialize - { - key_down: @key_down.serialize, - key_held: @key_held.serialize, - key_up: @key_up.serialize - } - end - - def clear - @key_down.clear - @key_up.clear - @key_held.clear - end - - def up - @key_up.up || @key_held.up - end - - def down - @key_up.down || @key_held.down - end - - def left - @key_up.left || @key_held.left - end - - def right - @key_up.right || @key_held.right - end + alias_method :keys, :key include DirectionalInputHelperMethods end @@ -501,6 +417,7 @@ module GTK class MousePoint include GTK::Geometry + # @gtk attr_accessor :x, :y, :point, :created_at, :global_created_at def initialize x, y @@ -544,18 +461,45 @@ module GTK end end + # Provides access to the mouse. + # + # @gtk class Mouse - attr_accessor :click, - :previous_click, - :moved, + + # @gtk + attr_accessor :moved, :moved_at, :global_moved_at, - :x, :y, :up, :has_focus, + :up, :has_focus, :button_bits, :button_left, :button_middle, :button_right, :button_x1, :button_x2, :wheel + # The current click. + # + # @return [MousePoint] + # @gtk + attr_accessor :click + + # The previous click. + # + # @return [MousePoint] + # @gtk + attr_accessor :previous_click + + # The "x" coordinate. + # + # @return [Integer] + # @gtk + attr_accessor :x + + # The "y" coordinate. + # + # @return [Integer] + # @gtk + attr_accessor :y + def initialize @x = 0 @y = 0 @@ -569,9 +513,14 @@ module GTK clear end + # The "x" and "y" coordinate pair. + # + # @return [[Integer, Integer]] + # @gtk def point [@x, @y].point end + alias_method :position, :point def clear if @click @@ -586,18 +535,19 @@ module GTK @wheel = nil end + # @return [MousePoint] + # @gtk def up @up end + # @return [MousePoint] + # @gtk def down @click end - def position - [@x, @y] - end - + # @return [Hash] def serialize result = {} @@ -615,12 +565,35 @@ module GTK result end + + # @return [String] + def to_s + serialize.to_s + end + alias_method :inspect, :to_s end end module GTK + # @gtk class Inputs - attr_accessor :controllers, :keyboard, :mouse, :text, :history + + # A list of all controllers. + # + # @return [Controller[]] + # @gtk + attr_reader :controllers + + # @return [Keyboard] + # @gtk + attr_reader :keyboard + + # @return [Mouse] + # @gtk + attr_reader :mouse + + # @gtk + attr_accessor :text, :history def initialize @controllers = [Controller.new, Controller.new] @@ -654,44 +627,56 @@ module GTK (controller_one && controller_one.directional_vector) end + # Returns a signal indicating right (`1`), left (`-1`), or neither ('0'). + # + # @return [Integer] def left_right return -1 if self.left return 1 if self.right return 0 end + # Returns a signal indicating up (`1`), down (`-1`), or neither ('0'). + # + # @return [Integer] def up_down return 1 if self.up return -1 if self.down return 0 end + # Returns the coordinates of the last click. + # + # @return [Float, Float] def click return nil unless @mouse.click return @mouse.click.point end + # The first controller. + # + # @return [Controller] def controller_one - @controllers.value(0) + @controllers[0] end + # The second controller. + # + # @return [Controller] def controller_two - @controllers.value(1) + @controllers[1] end + # Clears all inputs. + # + # @return [void] def clear @mouse.clear - @keyboard.key_down.clear - @keyboard.key_up.clear - @keyboard.key_held.clear - @controllers[0].key_down.clear - @controllers[0].key_up.clear - @controllers[0].key_held.clear - @controllers[1].key_down.clear - @controllers[1].key_up.clear - @controllers[1].key_held.clear + @keyboard.clear + @controllers.each(&:clear) end + # @return [Hash] def serialize { controller_one: controller_one.serialize, diff --git a/dragon/keys.rb b/dragon/keys.rb new file mode 100644 index 0000000..9f41fa2 --- /dev/null +++ b/dragon/keys.rb @@ -0,0 +1,51 @@ +# coding: utf-8 +# Copyright 2019 DragonRuby LLC +# MIT License +# controller/keys.rb has been released under MIT (*only this file*). + +module GTK + class Controller + class Keys + include Serialize + + LABELS = [ + :up, :down, :left, :right, + :a, :b, :x, :y, + :l1, :r1, + :l2, :r2, + :l3, :r3, + :start, :select, + :directional_up, :directional_down, :directional_left, :directional_right + ].freeze + + LABELS.each do |label| + attr_reader label + end + + # Activate a key. + # + # @return [void] + def activate key + instance_variable_set("@#{key}", Kernel.tick_count + 1) + end + + # Deactivate a key. + # + # @return [void] + def deactivate key + instance_variable_set("@#{key}", nil) + end + + # Clear all key inputs. + # + # @return [void] + def clear + LABELS.each { |label| deactivate(label) } + end + + def truthy_keys + LABELS.select { |label| send(label) } + end + end + end +end |
