diff options
| author | Amir Rajan <[email protected]> | 2019-08-29 18:19:05 -0500 |
|---|---|---|
| committer | Amir Rajan <[email protected]> | 2019-08-29 18:20:33 -0500 |
| commit | 9c2ce126dba8a4c3ada82e5f4bd6637b1fa92ab0 (patch) | |
| tree | 241ed4b1dc46b1bd53f185f80e238f31f4c12403 | |
| parent | 62343afae7e3776b8f700ffeac33c0ff3f651c61 (diff) | |
| download | dragonruby-game-toolkit-contrib-9c2ce126dba8a4c3ada82e5f4bd6637b1fa92ab0.tar.gz dragonruby-game-toolkit-contrib-9c2ce126dba8a4c3ada82e5f4bd6637b1fa92ab0.zip | |
Synced from GTK source.
| -rw-r--r-- | dragon/console.rb | 191 | ||||
| -rw-r--r-- | dragon/controller_config.rb | 390 |
2 files changed, 531 insertions, 50 deletions
diff --git a/dragon/console.rb b/dragon/console.rb index fb74da1..3bd3e0b 100644 --- a/dragon/console.rb +++ b/dragon/console.rb @@ -1,5 +1,5 @@ # Copyright 2019 DragonRuby LLC - +# MIT License # console.rb has been released under MIT (*only this file*). module GTK @@ -8,7 +8,8 @@ module GTK :text_color, :cursor_color, :font, :animation_duration, :max_log_lines, :max_history, :current_input_str, :log, :last_command_errored, :last_command, :error_color, :shown_at, - :header_color + :header_color, :archived_log, :last_log_lines, :last_log_lines_count, + :suppress_left_arrow_behavior def initialize @disabled = false @@ -16,9 +17,10 @@ module GTK @log_offset = 0 @visible = false @toast_ids = [] + @archived_log = [] @log = [ 'Console ready.' ] - @max_log_lines = 100 # I guess...? - @max_history = 100 # I guess...? + @max_log_lines = 1000 # I guess...? + @max_history = 1000 # I guess...? @command_history = [] @command_history_index = -1 @nonhistory_input = '' @@ -73,13 +75,36 @@ module GTK end def addtext obj + @last_log_lines_count ||= 1 + str = obj.to_s + + log_lines = [] + str.each_line { |s| - @log.shift if @log.length > 1000 s.wrapped_lines(console_text_width).each do |l| - @log << l + log_lines << l end } + + if log_lines == @last_log_lines + @last_log_lines_count += 1 + new_log_line_with_count = @last_log_lines.last + " (#{@last_log_lines_count})" + if log_lines.length > 1 + @log = @log[0..-(@log.length - log_lines.length)] + log_lines[0..-2] + [new_log_line_with_count] + else + @log = @log[0..-(@log.length - log_lines.length)] + [new_log_line_with_count] + end + return + end + + log_lines.each do |l| + @log.shift if @log.length > @max_log_lines + @log << l + end + + @last_log_lines_count = 1 + @last_log_lines = log_lines end def ready? @@ -103,6 +128,11 @@ module GTK def hide if visible? toggle + @archived_log += @log + if @archived_log.length > @max_log_lines + @archived_log = @archived_log.drop(@archived_log.length - @max_log_lines) + end + @log.clear @show_reason = nil clear_toast end @@ -112,10 +142,6 @@ module GTK hide end - def clear - @log.clear - end - def clear_toast @toasted_at = nil @toast_duration = 0 @@ -193,6 +219,39 @@ S args.inputs.keyboard.key_down.ordinal_indicator! end + def eval_the_set_command + cmd = @current_input_str.strip + if cmd.length != 0 + @log_offset = 0 + @current_input_str = '' + + @command_history.pop while @command_history.length >= @max_history + @command_history.unshift cmd + @command_history_index = -1 + @nonhistory_input = '' + + if cmd == 'quit' || cmd == ':wq' || cmd == ':q!' || cmd == ':q' || cmd == ':wqa' + $gtk.request_quit + else + puts "-> #{cmd}" + begin + @last_command = cmd + $gtk.ffi_mrb.eval("$results = (#{cmd})") + if $results.nil? + puts "=> nil" + else + puts "=> #{$results}" + end + @last_command_errored = false + rescue Exception => e + @last_command_errored = true + puts "#{e}" + log "#{e}" + end + end + end + end + def process_inputs args if console_toggle_key_down? args args.inputs.text.clear @@ -201,40 +260,38 @@ S return unless visible? + if !@suppress_left_arrow_behavior && args.inputs.keyboard.key_down.left && (@current_input_str || '').strip.length > 0 + log_info "Use repl.rb!", <<-S +The Console is nice for quick commands, but for more complex edits, use repl.rb. + +I've written the current command at the top of a file called ./repl.rb (right next to dragonruby(.exe)). Please open the the file and apply additional edits there. +S + if @last_command_written_to_repl_rb != @current_input_str + @last_command_written_to_repl_rb = @current_input_str + contents = $gtk.read_file 'repl.rb' + contents ||= '' + contents = <<-S + contents + +# Remove the x from xrepl to run the command. +xrepl do + #{@last_command_written_to_repl_rb} +end + +S + $gtk.suppress_hotload = true + $gtk.write_file 'repl.rb', contents + $gtk.reload_if_needed 'repl.rb', true + $gtk.suppress_hotload = false + end + + return + end + args.inputs.text.each { |str| @current_input_str << str } args.inputs.text.clear if args.inputs.keyboard.key_down.enter - cmd = @current_input_str.strip - if cmd.length != 0 - @log_offset = 0 - @current_input_str = '' - - @command_history.pop while @command_history.length >= @max_history - @command_history.unshift cmd - @command_history_index = -1 - @nonhistory_input = '' - - if cmd == 'quit' || cmd == ':wq' || cmd == ':q!' || cmd == ':q' || cmd == ':wqa' - $gtk.request_quit - else - puts "-> #{cmd}" - begin - @last_command = cmd - $gtk.ffi_mrb.eval("$results = (#{cmd})") - if $results.nil? - puts "=> nil" - else - puts "=> #{$results}" - end - @last_command_errored = false - rescue Exception => e - @last_command_errored = true - puts "#{e}" - log "#{e}" - end - end - end + eval_the_set_command elsif args.inputs.keyboard.key_down.v if args.inputs.keyboard.key_down.control || args.inputs.keyboard.key_down.meta @current_input_str << $gtk.ffi_misc.getclipboard @@ -279,6 +336,19 @@ S args.inputs.keyboard.key_held.clear end + def write_line args, left, y, str, errorinfo, headerinfo, txtinfo + str ||= '' + if include_error_marker? str + args.outputs.reserved << [left + 10, y, str, 1, 0, *errorinfo].label + elsif include_subdued_markers? str + args.outputs.reserved << [left + 10, y, str, 1, 0, [txtinfo[0..2], txtinfo[3].half]].label + elsif str.start_with?("====") || str.include?("app") + args.outputs.reserved << [left + 10, y, str, 1, 0, *headerinfo].label + else + args.outputs.reserved << [left + 10, y, str, 1, 0, *txtinfo].label + end + end + def render args return if !@toggled_at @@ -315,25 +385,46 @@ S y += h # !!! FIXME: remove this when we fix coordinate origin on labels. ((@log.size - @log_offset) - 1).downto(0) do |idx| - str = @log[idx] || "" - if include_error_marker? str - args.outputs.reserved << [left + 10, y, str, 1, 0, *errorinfo].label - elsif str.start_with? "====" - args.outputs.reserved << [left + 10, y, str, 1, 0, *headerinfo].label - else - args.outputs.reserved << [left + 10, y, str, 1, 0, *txtinfo].label - end + write_line args, left, y, @log[idx], errorinfo, headerinfo, txtinfo + y += h + break if y > top + end + + # past log seperator + args.outputs.reserved << [0, y - h.half, 1280, y - h.half, [txtinfo[0..2], txtinfo[3].idiv(4)]].line + + y += h + + txtinfo = [ @text_color[0], @text_color[1], @text_color[2], (@text_color[3].to_f * percent.half).to_i, @font ] + errorinfo = [ @error_color[0], @error_color[1], @error_color[2], (@error_color[3].to_f * percent.half).to_i, @font ] + cursorinfo = [ @cursor_color[0], @cursor_color[1], @cursor_color[2], (@cursor_color[3].to_f * percent.half).to_i, @font ] + headerinfo = [ @header_color[0], @header_color[1], @header_color[2], (@header_color[3].to_f * percent.half).to_i, @font ] + + ((@archived_log.size - @log_offset) - 1).downto(0) do |idx| + write_line args, left, y, @archived_log[idx], errorinfo, headerinfo, txtinfo y += h break if y > top end end def include_error_marker? text - error_markers.any? { |e| text.downcase.include? e } + include_any_words? text, error_markers end def error_markers - ["exception:", "error:", "undefined method", "failed", "syntax"] + ["exception", "error", "undefined method", "failed", "syntax", "deprecated"] + end + + def include_subdued_markers? text + include_any_words? text, subdued_markers + end + + def include_any_words? text, words + words.any? { |w| text.downcase.include? w } + end + + def subdued_markers + ["reloaded", "exported the"] end def calc args diff --git a/dragon/controller_config.rb b/dragon/controller_config.rb new file mode 100644 index 0000000..18bc92d --- /dev/null +++ b/dragon/controller_config.rb @@ -0,0 +1,390 @@ +# 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 ControllerConfig + 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.ease_using_global_tick_count(@animation_duration, :flip, :quint, :flip) + if percent >= 1.0 + percent = 1.0 + @fading = 0 + end + else # fading out + percent = @toggled_at.ease_using_global_tick_count(@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, 1280, 720, 255, 255, 255, fade].solid + args.outputs.primitives << [0, 0, 1280, 720, 'dragonruby-controller.png', 0, fade, 255, 255, 255].sprite + args.outputs.primitives << [1280 / 2, 700, joystickname, 2, 1, 0, 0, 0, fade].label + args.outputs.primitives << [1280 / 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 << [1280 / 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 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 << [1280 / 2, 575, "Please press the #{part[2]}.", 0, 1, 0, 0, 0, 255].label + render_part_highlight args, part, @part_alpha + args.outputs.primitives << [1280 / 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 |
