#encoding:utf-8
#==============================================================================
# ■ 输入模块增强v1.1 By_QQEat
#
# 说明:处理按键输入操作等
#
#
# [先调用初始化, v1.1及以后的版本底部自动调用了, 无需手动在调用]
#
# Input.init
#
# [方法]
#
# Input.bind(key, action)
# Input.unbind(key)
# Input.unbindAll
#
# Input.pressed(tag)
# Input.released(tag)
# Input.down(tag, interval=0, delay=0)
# Input.sequence(*args)
#
# [范例]
#
# # 将 A 键绑定到一个执行方法
# Input.bind('A', proc { p '按下了 A 键' })
#
# # 将 S 键绑定到标签 'tag'
# Input.bind('S', 'tag')
#
# # 判断key是否有绑定执行方法
# Input.has_proc?(key)
#
# # 返回一个数组, 获取当前key绑定的所有标签
# Input.get_tags(key)
#
# # 判断 'tag' 标签绑定的键位是否被按下时(被按下时激活一次, 注意是pressed而不是press)
# Input.pressed('tag')
#
# # 判断 'tag' 标签绑定的键位是否被松开时(被松开时激活一次, 注意是released而不是release)
# Input.released('tag')
#
# # 处理 'tag' 标签绑定的键位按下后的操作
# # 按下后返回一次真,持续按住 60 帧后,每 30 帧返回一次真
# Input.down('tag', 30, 60)
#
# # 处理 'tag' 标签绑定的键位在规定帧数内连续按下后的操作
# # 按下tag,按下后20帧内按下tag2,按下后30帧内按下tag3,返回真
# Input.sequence('tag', 20, 'tag2', 30, 'tag3')
#
# # 删除 A 键绑定的所有标签和执行方法
# Input.unbind('a')
#
# # 删除所有绑定
# Input.unbindAll
#
# [v1.1更新内容_2019.08.22]
# 1)、修复错误:
# RuntimeError: can't add a new key into hash during iteration
# 2)、脚本底部自动调用 Input.init
# 3)、新增方法:
# Input.has_proc?(key) # 判断key是否有绑定执行方法
# Input.get_tags(key) # 返回一个数组, 获取当前key绑定的所有标签
#
#==============================================================================
module Input
module_function
#--------------------------------------------------------------------------
# ● APIS
#--------------------------------------------------------------------------
GetKeyState = Win32API.new("user32","GetAsyncKeyState",['I'],'I')
GetKeyboardState = Win32API.new("user32","GetKeyState",['I'],'I')
GetSetKeyState = Win32API.new("user32","SetKeyboardState",['I'],'I')
#--------------------------------------------------------------------------
# ● ALL_KEYS
#--------------------------------------------------------------------------
ALL_KEYS = {
# 鼠标
'MOUSE1' => 0x01, # 左键
'MOUSE2' => 0x02, # 右键
'MOUSE3' => 0x04, # 中键
'MOUSE4' => 0x05, # 侧键1
'MOUSE5' => 0x06, # 侧键2
# 键盘
'BACK' => 0x08, # BACKSPACE key
'TAB' => 0x09, # TAB key
'RETURN' => 0x0D, # ENTER key
'SHIFT' => 0x10, # SHIFT key
'CTLR' => 0x11, # CTLR key
'ALT' => 0x12, # ALT key
'PAUSE' => 0x13, # PAUSE key
'CAPITAL' => 0x14, # CAPS LOCK key
'ESCAPE' => 0x1B, # ESC key
'SPACE' => 0x20, # SPACEBAR
'PRIOR' => 0x21, # PAGE UP key
'NEXT' => 0x22, # PAGE DOWN key
'END' => 0x23, # END key
'HOME' => 0x24, # HOME key
'LEFT' => 0x25, # LEFT ARROW key
'UP' => 0x26, # UP ARROW key
'RIGHT' => 0x27, # RIGHT ARROW key
'DOWN' => 0x28, # DOWN ARROW key
'SELECT' => 0x29, # SELECT key
'PRINT' => 0x2A, # PRINT key
'SNAPSHOT' => 0x2C, # PRINT SCREEN key
'INSERT' => 0x2D, # INS key
'DELETE' => 0x2E, # DEL key
#--------------------------------------------------------------------------
'0' => 0x30, # 0 key
'1' => 0x31, # 1 key
'2' => 0x32, # 2 key
'3' => 0x33, # 3 key
'4' => 0x34, # 4 key
'5' => 0x35, # 5 key
'6' => 0x36, # 6 key
'7' => 0x37, # 7 key
'8' => 0x38, # 8 key
'9' => 0x39, # 9 key
#--------------------------------------------------------------------------
'A' => 0x41, # A key
'B' => 0x42, # B key
'C' => 0x43, # C key
'D' => 0x44, # D key
'E' => 0x45, # E key
'F' => 0x46, # F key
'G' => 0x47, # G key
'H' => 0x48, # H key
'I' => 0x49, # I key
'J' => 0x4A, # J key
'K' => 0x4B, # K key
'L' => 0x4C, # L key
'M' => 0x4D, # M key
'N' => 0x4E, # N key
'O' => 0x4F, # O key
'P' => 0x50, # P key
'Q' => 0x51, # Q key
'R' => 0x52, # R key
'S' => 0x53, # S key
'T' => 0x54, # T key
'U' => 0x55, # U key
'V' => 0x56, # V key
'W' => 0x57, # W key
'X' => 0x58, # X key
'Y' => 0x59, # Y key
'Z' => 0x5A, # Z key
#--------------------------------------------------------------------------
'LWIN' => 0x5B, # Left Windows key (Microsoft Natural keyboard)
'RWIN' => 0x5C, # Right Windows key (Natural keyboard)
'APPS' => 0x5D, # Applications key (Natural keyboard)
#--------------------------------------------------------------------------
'NUMPAD0' => 0x60, # Numeric keypad 0 key
'NUMPAD1' => 0x61, # Numeric keypad 1 key
'NUMPAD2' => 0x62, # Numeric keypad 2 key
'NUMPAD3' => 0x63, # Numeric keypad 3 key
'NUMPAD4' => 0x64, # Numeric keypad 4 key
'NUMPAD5' => 0x65, # Numeric keypad 5 key
'NUMPAD6' => 0x66, # Numeric keypad 6 key
'NUMPAD7' => 0x67, # Numeric keypad 7 key
'NUMPAD8' => 0x68, # Numeric keypad 8 key
'NUMPAD9' => 0x69, # Numeric keypad 9 key
'MULTIPLY' => 0x6A, # Multiply key (*)
'ADD' => 0x6B, # Add key (+)
'SEPARATOR' => 0x6C, # Separator key
'SUBTRACT' => 0x6D, # Subtract key (-)
'DECIMAL' => 0x6E, # Decimal key
'DIVIDE' => 0x6F, # Divide key (/)
#--------------------------------------------------------------------------
'F1' => 0x70, # F1 key
'F2' => 0x71, # F2 key
'F3' => 0x72, # F3 key
'F4' => 0x73, # F4 key
'F5' => 0x74, # F5 key
'F6' => 0x75, # F6 key
'F7' => 0x76, # F7 key
'F8' => 0x77, # F8 key
'F9' => 0x78, # F9 key
'F10' => 0x79, # F10 key
'F11' => 0x7A, # F11 key
'F12' => 0x7B, # F12 key
#--------------------------------------------------------------------------
'NUMLOCK' => 0x90, # NUM LOCK key
'SCROLL' => 0x91, # SCROLL LOCK key
#--------------------------------------------------------------------------
'LSHIFT' => 0xA0, # Left SHIFT key
'RSHIFT' => 0xA1, # Right SHIFT key
'LCONTROL' => 0xA2, # Left CONTROL key
'RCONTROL' => 0xA3, # Right CONTROL key
'L_ALT' => 0xA4, # Left ALT key
'R_ALT' => 0xA5, # Right ALT key
#--------------------------------------------------------------------------
'SEP' => 0xBC, # , key
'DASH' => 0xBD, # - key
'DOTT' => 0xBE, # . Key
}
#--------------------------------------------------------------------------
# ● init
#--------------------------------------------------------------------------
def init
@binds = Hash.new{|h,k| h[k] = [] }
@functions = {}
@prev_state, @state = {}, {}
@fun_state, @pr_state, @down_state, @repeat_state = {}, {}, {}, {}
@sequences = {}
end
#--------------------------------------------------------------------------
# ● bind
#--------------------------------------------------------------------------
def bind(key, action)
key = key.upcase # key convert to upcase
case action.class.to_s
when 'String'
@binds[action] << key unless @binds[action].include?(key)
when 'Proc','Method'
@functions[key] = action
end
end
#--------------------------------------------------------------------------
# ● has_proc?
#--------------------------------------------------------------------------
def has_proc?(key)
key = key.upcase # key convert to upcase
return @functions[key]
end
#--------------------------------------------------------------------------
# ● get_tags
#--------------------------------------------------------------------------
def get_tags(key)
key = key.upcase # key convert to upcase
arr = []
@binds.keys.each do |k|
arr << k if @binds[k].include?(key)
end
return arr
end
#--------------------------------------------------------------------------
# ● unbind
#--------------------------------------------------------------------------
def unbind(key)
key = key.upcase
@binds.values.each{|v| v.delete(key) }
@functions.delete(key)
end
#--------------------------------------------------------------------------
# ● unbindAll
#--------------------------------------------------------------------------
def unbindAll
[@binds, @functions, @fun_state, @prev_state, @state,
@pr_state, @down_state, @repeat_state, @sequences].each(&:clear)
end
#--------------------------------------------------------------------------
# ● pressed
#--------------------------------------------------------------------------
def pressed(tag)
return @binds[tag].find{|k|
key = "#{tag}_#{k}"
@pr_state[key] == true and @pr_state.delete(key)
} != nil
end
#--------------------------------------------------------------------------
# ● released
#--------------------------------------------------------------------------
def released(tag)
return @binds[tag].find{|k|
key = "#{tag}_#{k}"
@pr_state[key] == false and @pr_state.delete(key) == false
} != nil
end
#--------------------------------------------------------------------------
# ● down
#--------------------------------------------------------------------------
def down(tag, interval = 0, delay = 0)
key = ''
if @binds[tag].find{|k| key = "#{tag}_#{k}"; @down_state.include?(key) } != nil
if @repeat_state[key]
if @repeat_state[key][2] >= delay
if @repeat_state[key][1] >= interval
@repeat_state[key][1] -= interval
return true
else
@repeat_state[key][1] += 1
return false
end
else
@repeat_state[key][2] += 1
return false
end
else
@repeat_state[key] = [true, 0, 0]
return true
end
end
end
#--------------------------------------------------------------------------
# ● sequence
#--------------------------------------------------------------------------
def sequence(*args)
raise '参数传递错误!' if args.size <= 1 or args.size % 2 == 0
o = @sequences[args] ||= { index: 0, frame: 0, tag: {} }
if o[:index] == 0
tag = args[0]
if @binds[tag].find{|k| o[:tag].delete("#{tag}_#{k}") } != nil
o[:index], o[:frame] = 1, 0
return false
end
else
if o[:frame] <= args[o[:index]]
tag = args[o[:index]+1]
if @binds[tag].find{|k| o[:tag].delete("#{tag}_#{k}") } != nil
o[:index] += 2
o[:frame] = 0
if o[:index] >= args.size
@sequences.delete(args)
return true
end
end
o[:frame] += 1
return false
else
@sequences.delete(args)
return false
end
end
end
#--------------------------------------------------------------------------
# ● update
#--------------------------------------------------------------------------
InputUpdate = method :update unless $@
def update
InputUpdate.call
# call binding
@functions.keys.each do |k|
a = @functions[k]
press = GetKeyState.call(ALL_KEYS[k]) != 0
a.call if !@fun_state[k] and press
@fun_state[k] = press
end
@binds.keys.each do |tag|
pressState = press_(tag)
pressState.keys.each do |k|
s = pressState[k]
key = "#{tag}_#{k}"
@prev_state[key] = @state[key]
@state[key] = s
# pressed
if !@prev_state[key] and @state[key]
@pr_state[key] = true
@down_state[key] = true
@sequences.values.each{|s| s[:tag][key] = true }
end
# released
if @prev_state[key] and !@state[key]
@pr_state[key] = false
@down_state.delete(key)
@repeat_state.delete(key)
@sequences.values.each{|s| s[:tag].delete(key) }
end
end
end
end
#--------------------------------------------------------------------------
# ● press_
#--------------------------------------------------------------------------
def press_(tag)
state = {}
@binds[tag].map{|k| state[k] = GetKeyState.call(ALL_KEYS[k]) != 0 }
return state
end
private :press_
end
Input.init