Input Binding support
14
.gitignore
vendored
@ -25,3 +25,17 @@ install_manifest.txt
|
|||||||
compile_commands.json
|
compile_commands.json
|
||||||
CTestTestfile.cmake
|
CTestTestfile.cmake
|
||||||
_deps
|
_deps
|
||||||
|
|
||||||
|
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/settings.json
|
||||||
|
!.vscode/tasks.json
|
||||||
|
!.vscode/launch.json
|
||||||
|
!.vscode/extensions.json
|
||||||
|
!.vscode/*.code-snippets
|
||||||
|
|
||||||
|
# Local History for Visual Studio Code
|
||||||
|
.history/
|
||||||
|
|
||||||
|
# Built Visual Studio Code Extensions
|
||||||
|
*.vsix
|
BIN
assets/opensb/interface/opensb/bindings/bind.png
Normal file
After Width: | Height: | Size: 418 B |
160
assets/opensb/interface/opensb/bindings/bindings.config
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
{
|
||||||
|
"scripts" : ["/interface/opensb/bindings/bindings.lua"],
|
||||||
|
"scriptDelta" : 0,
|
||||||
|
"scriptWidgetCallbacks" : [
|
||||||
|
"selectCategory",
|
||||||
|
"applyBind",
|
||||||
|
"eraseBind",
|
||||||
|
"resetBind",
|
||||||
|
"snare"
|
||||||
|
],
|
||||||
|
|
||||||
|
"gui" : {
|
||||||
|
"panefeature" : {
|
||||||
|
"type" : "panefeature",
|
||||||
|
"positionLocked" : false
|
||||||
|
},
|
||||||
|
"background" : {
|
||||||
|
"type" : "background",
|
||||||
|
"fileHeader" : "/interface/opensb/bindings/header.png",
|
||||||
|
"fileBody" : "/interface/opensb/bindings/body.png",
|
||||||
|
"fileFooter" : "/interface/opensb/bindings/footer.png"
|
||||||
|
},
|
||||||
|
"banner" : {
|
||||||
|
"type" : "canvas",
|
||||||
|
"rect" : [146, 187, 398, 215]
|
||||||
|
},
|
||||||
|
"snare" : {
|
||||||
|
"type" : "textbox",
|
||||||
|
"position" : [2147483647, 2147483647],
|
||||||
|
"regex" : "(){0,0}",
|
||||||
|
"maxWidth" : 0,
|
||||||
|
"focus" : false,
|
||||||
|
"escapeKey": "snare",
|
||||||
|
"enterKey": "snare",
|
||||||
|
"callback": "snare"
|
||||||
|
},
|
||||||
|
"categories" : {
|
||||||
|
"type" : "scrollArea",
|
||||||
|
"rect" : [4, 16, 145, 214],
|
||||||
|
"children" : {
|
||||||
|
"list" : {
|
||||||
|
"type" : "list",
|
||||||
|
"schema" : {
|
||||||
|
"selectedBG" : "/interface/opensb/bindings/categoryback.png?multiply=0f0",
|
||||||
|
"unselectedBG" : "/interface/opensb/bindings/categoryback.png?multiply=222",
|
||||||
|
"spacing" : [0, 1],
|
||||||
|
"memberSize" : [130, 16],
|
||||||
|
"listTemplate" : {
|
||||||
|
"background" : {
|
||||||
|
"type" : "image",
|
||||||
|
"file" : "/interface/opensb/bindings/categoryback.png?multiply=222",
|
||||||
|
"position" : [0, 0],
|
||||||
|
"zlevel" : -1
|
||||||
|
},
|
||||||
|
"button" : {
|
||||||
|
"type" : "button",
|
||||||
|
"callback" : "selectCategory",
|
||||||
|
"caption" : "Unnamed",
|
||||||
|
"base" : "/interface/opensb/bindings/category.png?replace;fff=fff0;000=0007",
|
||||||
|
"hover" : "/interface/opensb/bindings/category.png?replace;fff=fff7;000=3337",
|
||||||
|
"press" : "/interface/opensb/bindings/category.png?replace;fff=000;000=7777",
|
||||||
|
"pressedOffset" : [0, 0],
|
||||||
|
"position" : [0, 0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"buttons" : {
|
||||||
|
"horizontal" : {
|
||||||
|
"forward" : { "base" : "", "hover" : "", "pressed" : "" },
|
||||||
|
"backward" : { "base" : "", "hover": "", "pressed" : "" }
|
||||||
|
},
|
||||||
|
"vertical" : {
|
||||||
|
"forward" : {
|
||||||
|
"base" : "/interface/scrollarea/varrow-forward.png?setcolor=fff",
|
||||||
|
"hover" : "/interface/scrollarea/varrow-forwardhover.png",
|
||||||
|
"pressed" : ""
|
||||||
|
},
|
||||||
|
"backward" : {
|
||||||
|
"base" : "/interface/scrollarea/varrow-backward.png?setcolor=fff",
|
||||||
|
"hover" : "/interface/scrollarea/varrow-backwardhover.png",
|
||||||
|
"pressed" : ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"thumbs" : {
|
||||||
|
"horizontal" : {
|
||||||
|
"base" : { "begin" : "", "end" : "", "inner" : "" },
|
||||||
|
"hover" : { "begin" : "", "end" : "", "inner" : "" },
|
||||||
|
"pressed" : { "begin" : "", "end" : "", "inner" : "" }
|
||||||
|
},
|
||||||
|
"vertical" : {
|
||||||
|
"base" : {
|
||||||
|
"begin" : "/interface/scrollarea/vthumb-begin.png",
|
||||||
|
"end" : "/interface/scrollarea/vthumb-end.png",
|
||||||
|
"inner" : "/interface/scrollarea/vthumb-inner.png"
|
||||||
|
},
|
||||||
|
"hover" : {
|
||||||
|
"begin" : "/interface/scrollarea/vthumb-beginhover.png",
|
||||||
|
"end" : "/interface/scrollarea/vthumb-endhover.png",
|
||||||
|
"inner" : "/interface/scrollarea/vthumb-innerhover.png"
|
||||||
|
},
|
||||||
|
"pressed" : {
|
||||||
|
"begin" : "/interface/scrollarea/vthumb-beginhover.png",
|
||||||
|
"end" : "/interface/scrollarea/vthumb-endhover.png",
|
||||||
|
"inner" : "/interface/scrollarea/vthumb-innerhover.png"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"binds" : {
|
||||||
|
"type" : "scrollArea",
|
||||||
|
"rect" : [147, 16, 398, 185],
|
||||||
|
"children" : {},
|
||||||
|
"buttons" : {
|
||||||
|
"horizontal" : {
|
||||||
|
"forward" : { "base" : "", "hover" : "", "pressed" : "" },
|
||||||
|
"backward" : { "base" : "", "hover": "", "pressed" : "" }
|
||||||
|
},
|
||||||
|
"vertical" : {
|
||||||
|
"forward" : {
|
||||||
|
"base" : "/interface/scrollarea/varrow-forward.png?setcolor=fff",
|
||||||
|
"hover" : "/interface/scrollarea/varrow-forwardhover.png",
|
||||||
|
"pressed" : ""
|
||||||
|
},
|
||||||
|
"backward" : {
|
||||||
|
"base" : "/interface/scrollarea/varrow-backward.png?setcolor=fff",
|
||||||
|
"hover" : "/interface/scrollarea/varrow-backwardhover.png",
|
||||||
|
"pressed" : ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"thumbs" : {
|
||||||
|
"horizontal" : {
|
||||||
|
"base" : { "begin" : "", "end" : "", "inner" : "" },
|
||||||
|
"hover" : { "begin" : "", "end" : "", "inner" : "" },
|
||||||
|
"pressed" : { "begin" : "", "end" : "", "inner" : "" }
|
||||||
|
},
|
||||||
|
"vertical" : {
|
||||||
|
"base" : {
|
||||||
|
"begin" : "/interface/scrollarea/vthumb-begin.png",
|
||||||
|
"end" : "/interface/scrollarea/vthumb-end.png",
|
||||||
|
"inner" : "/interface/scrollarea/vthumb-inner.png"
|
||||||
|
},
|
||||||
|
"hover" : {
|
||||||
|
"begin" : "/interface/scrollarea/vthumb-beginhover.png",
|
||||||
|
"end" : "/interface/scrollarea/vthumb-endhover.png",
|
||||||
|
"inner" : "/interface/scrollarea/vthumb-innerhover.png"
|
||||||
|
},
|
||||||
|
"pressed" : {
|
||||||
|
"begin" : "/interface/scrollarea/vthumb-beginhover.png",
|
||||||
|
"end" : "/interface/scrollarea/vthumb-endhover.png",
|
||||||
|
"inner" : "/interface/scrollarea/vthumb-innerhover.png"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
372
assets/opensb/interface/opensb/bindings/bindings.lua
Normal file
@ -0,0 +1,372 @@
|
|||||||
|
--constants
|
||||||
|
local PATH = "/interface/opensb/bindings/"
|
||||||
|
local CATEGORY_LIST_WIDGET = "categories.list"
|
||||||
|
local BINDS_WIDGET = "binds"
|
||||||
|
|
||||||
|
local fmt = string.format
|
||||||
|
local log = function() end
|
||||||
|
|
||||||
|
--SNARE
|
||||||
|
|
||||||
|
local snared = false
|
||||||
|
|
||||||
|
function snare(key) end
|
||||||
|
|
||||||
|
local mods = {}
|
||||||
|
for i, mod in ipairs{"LShift", "RShift", "LCtrl", "RCtrl", "LAlt", "RAlt", "LGui", "RGui", "AltGr", "Scroll"} do
|
||||||
|
local data = {name = mod, active = false}
|
||||||
|
mods[i], mods[mod] = data, data
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function getMods(key)
|
||||||
|
local bindMods = jarray()
|
||||||
|
for i, mod in ipairs(mods) do
|
||||||
|
if mod.active and mod.name ~= key then
|
||||||
|
bindMods[#bindMods + 1] = mod.name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if bindMods[1] then return bindMods end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function finishBind(type, value)
|
||||||
|
widget.blur("snare")
|
||||||
|
snared = false
|
||||||
|
snareFinished{ type = type, value = value, mods = getMods(value) }
|
||||||
|
for i, mod in ipairs(mods) do
|
||||||
|
mod.active = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function scanInputEvents()
|
||||||
|
local events = input.events()
|
||||||
|
for i, event in pairs(events) do
|
||||||
|
local type, data = event.type, event.data
|
||||||
|
if type == "KeyDown" then
|
||||||
|
local key = data.key
|
||||||
|
local mod = mods[key]
|
||||||
|
if mod then mod.active = true end
|
||||||
|
elseif type == "KeyUp" then
|
||||||
|
return finishBind("key", data.key)
|
||||||
|
elseif type == "MouseButtonDown" then
|
||||||
|
return finishBind("mouse", data.mouseButton)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function update()
|
||||||
|
if snared then
|
||||||
|
scanInputEvents()
|
||||||
|
if snared and not widget.hasFocus("snare") then
|
||||||
|
snared = false
|
||||||
|
snareFinished()
|
||||||
|
for i, mod in ipairs(mods) do
|
||||||
|
mod.active = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function beginSnare()
|
||||||
|
snared = true
|
||||||
|
widget.focus("snare")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- BINDING
|
||||||
|
|
||||||
|
|
||||||
|
local function alphabeticalNameSortGreater(a, b) return a.name > b.name end
|
||||||
|
local function alphabeticalNameSortLesser(a, b) return a.name < b.name end
|
||||||
|
local sortedCategories = {}
|
||||||
|
local categories = {}
|
||||||
|
|
||||||
|
local widgetsToCategories = {}
|
||||||
|
local allBinds = {}
|
||||||
|
|
||||||
|
local function addCategoryToList(data)
|
||||||
|
local name = widget.addListItem(CATEGORY_LIST_WIDGET)
|
||||||
|
widget.setText(fmt("%s.%s.button", CATEGORY_LIST_WIDGET, name), data.name)
|
||||||
|
log("Added category ^cyan;%s^reset; to list", data.name)
|
||||||
|
return name
|
||||||
|
end
|
||||||
|
|
||||||
|
local function parseBinds()
|
||||||
|
for i, path in pairs(root.assetsByExtension("binds")) do
|
||||||
|
local data = root.assetJson(path)
|
||||||
|
for categoryId, data in pairs(data) do
|
||||||
|
if not data.name then data.name = categoryId end
|
||||||
|
data.categoryId = categoryId
|
||||||
|
categories[categoryId] = data
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for categoryId, data in pairs(categories) do
|
||||||
|
sortedCategories[#sortedCategories + 1] = data
|
||||||
|
end
|
||||||
|
table.sort(sortedCategories, alphabeticalNameSortLesser)
|
||||||
|
for i = 1, #sortedCategories do
|
||||||
|
local data = sortedCategories[i]
|
||||||
|
data.index = i
|
||||||
|
local name = addCategoryToList(data)
|
||||||
|
data.widget = name
|
||||||
|
widgetsToCategories[name] = data
|
||||||
|
end
|
||||||
|
if sortedCategories[1] then
|
||||||
|
local first = sortedCategories[1].widget
|
||||||
|
widget.setListSelected(CATEGORY_LIST_WIDGET, first)
|
||||||
|
selectCategory(first)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function bindsToString(binds)
|
||||||
|
local t = {}
|
||||||
|
for i, bind in pairs(binds) do
|
||||||
|
local str = ""
|
||||||
|
if bind.mods then
|
||||||
|
for i, v in pairs(bind.mods) do
|
||||||
|
str = str .. v .. " + "
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if bind.type == "key" then
|
||||||
|
str = str .. bind.value
|
||||||
|
elseif bind.type == "mouse" then
|
||||||
|
str = str .. bind.value
|
||||||
|
end
|
||||||
|
local _i = (i - 1) * 2
|
||||||
|
if _i ~= 0 then
|
||||||
|
t[_i] = ", "
|
||||||
|
end
|
||||||
|
t[_i + 1] = str
|
||||||
|
end
|
||||||
|
return table.concat(t)
|
||||||
|
end
|
||||||
|
|
||||||
|
function setButtonsEnabled(state)
|
||||||
|
for i, bind in pairs(allBinds) do
|
||||||
|
widget.setButtonEnabled(fmt("%s.apply_%s", BINDS_WIDGET, bind.bindId), state)
|
||||||
|
widget.setButtonEnabled(fmt("%s.erase_%s", BINDS_WIDGET, bind.bindId), state)
|
||||||
|
widget.setButtonEnabled(fmt("%s.reset_%s", BINDS_WIDGET, bind.bindId), state)
|
||||||
|
end
|
||||||
|
for widgetId in pairs(widgetsToCategories) do
|
||||||
|
widget.setButtonEnabled(fmt("%s.%s.button", CATEGORY_LIST_WIDGET, widgetId), state)
|
||||||
|
end
|
||||||
|
widget.setButtonEnabled(CATEGORY_LIST_WIDGET, state)
|
||||||
|
end
|
||||||
|
|
||||||
|
local activeBind
|
||||||
|
local activeCategory
|
||||||
|
|
||||||
|
function snareFinished(newBind)
|
||||||
|
setButtonsEnabled(true)
|
||||||
|
if not newBind or not activeBind or not activeCategory then return end
|
||||||
|
local currentBinds = input.getBinds(activeCategory, activeBind)
|
||||||
|
local replace = false
|
||||||
|
for i, v in pairs(currentBinds) do
|
||||||
|
if sb.printJson(v) == sb.printJson(newBind) then
|
||||||
|
--equal, replace all binds
|
||||||
|
currentBinds = {newBind}
|
||||||
|
replace = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if replace then
|
||||||
|
currentBinds = {newBind}
|
||||||
|
log("Replaced %s with %s", activeBind, sb.printJson(newBind))
|
||||||
|
else
|
||||||
|
currentBinds[#currentBinds + 1] = newBind
|
||||||
|
log("Added %s to %s", sb.printJson(newBind), activeBind)
|
||||||
|
end
|
||||||
|
input.setBinds(activeCategory, activeBind, currentBinds)
|
||||||
|
widget.setText(fmt("%s.apply_%s", BINDS_WIDGET, activeBind), bindsToString(currentBinds))
|
||||||
|
end
|
||||||
|
|
||||||
|
function eraseBind(bind)
|
||||||
|
bind = bind:sub(7)
|
||||||
|
local binds = jarray()
|
||||||
|
binds[1] = nil
|
||||||
|
input.setBinds(activeCategory, bind, binds)
|
||||||
|
widget.setText(fmt("%s.apply_%s", BINDS_WIDGET, bind), "")
|
||||||
|
end
|
||||||
|
|
||||||
|
function resetBind(bind)
|
||||||
|
bind = bind:sub(7)
|
||||||
|
local defaultBinds = input.getDefaultBinds(activeCategory, bind)
|
||||||
|
if #defaultBinds == 0 then
|
||||||
|
defaultBinds = jarray()
|
||||||
|
defaultBinds[1] = nil
|
||||||
|
end
|
||||||
|
input.setBinds(activeCategory, bind, defaultBinds)
|
||||||
|
widget.setText(fmt("%s.apply_%s", BINDS_WIDGET, bind), bindsToString(defaultBinds))
|
||||||
|
end
|
||||||
|
|
||||||
|
function applyBind(bind)
|
||||||
|
bind = bind:sub(7)
|
||||||
|
log("Modifying bind %s", bind)
|
||||||
|
setButtonsEnabled(false)
|
||||||
|
activeBind = bind
|
||||||
|
beginSnare()
|
||||||
|
end
|
||||||
|
|
||||||
|
local function addBindGroup(data, i, added)
|
||||||
|
local y = (i - 1) * -14
|
||||||
|
local bg = {
|
||||||
|
type = "image",
|
||||||
|
file = PATH .. "groupname.png",
|
||||||
|
position = {-12, y}
|
||||||
|
}
|
||||||
|
local name = "group_" .. i
|
||||||
|
widget.addChild(BINDS_WIDGET, bg, name)
|
||||||
|
added[#added + 1] = name
|
||||||
|
local label = {
|
||||||
|
type = "label",
|
||||||
|
value = data.name,
|
||||||
|
wrapWidth = 120,
|
||||||
|
fontSize = 8,
|
||||||
|
hAnchor = "mid",
|
||||||
|
vAnchor = "mid",
|
||||||
|
position = {120, 6}
|
||||||
|
}
|
||||||
|
widget.addChild(fmt("%s.%s", BINDS_WIDGET, name), label, "text")
|
||||||
|
end
|
||||||
|
|
||||||
|
local function addBindSet(data, i, added)
|
||||||
|
local y = (i - 1) * -14
|
||||||
|
local bg = {
|
||||||
|
type = "image",
|
||||||
|
file = PATH .. "bindname.png",
|
||||||
|
position = {-12, y}
|
||||||
|
}
|
||||||
|
local name = "label_" .. i
|
||||||
|
widget.addChild(BINDS_WIDGET, bg, name)
|
||||||
|
added[#added + 1] = name
|
||||||
|
local label = {
|
||||||
|
type = "label",
|
||||||
|
value = data.name,
|
||||||
|
wrapWidth = 120,
|
||||||
|
fontSize = 8,
|
||||||
|
hAnchor = "mid",
|
||||||
|
vAnchor = "mid",
|
||||||
|
position = {62, 6}
|
||||||
|
}
|
||||||
|
widget.addChild(fmt("%s.%s", BINDS_WIDGET, name), label, "text")
|
||||||
|
local button = {
|
||||||
|
type = "button",
|
||||||
|
callback = "applyBind",
|
||||||
|
position = {112, y + 2},
|
||||||
|
pressedOffset = {0, 0},
|
||||||
|
base = PATH .. "bind.png",
|
||||||
|
hover = PATH .. "bind.png?fade=fff;0.025",
|
||||||
|
pressed = PATH .. "bind.png?fade=fff;0.05?multiply=0f0",
|
||||||
|
caption = bindsToString(input.getBinds(data.categoryId, data.bindId))
|
||||||
|
}
|
||||||
|
name = "apply_" .. data.bindId
|
||||||
|
added[#added + 1] = name
|
||||||
|
widget.addChild(BINDS_WIDGET, button, name)
|
||||||
|
local erase = {
|
||||||
|
type = "button",
|
||||||
|
callback = "eraseBind",
|
||||||
|
position = {209, y + 2},
|
||||||
|
pressedOffset = {0, -1},
|
||||||
|
base = PATH .. "garbage.png",
|
||||||
|
hover = PATH .. "garbage.png?multiply=faa",
|
||||||
|
pressed = PATH .. "garbage.png?multiply=f00",
|
||||||
|
}
|
||||||
|
name = "erase_" .. data.bindId
|
||||||
|
added[#added + 1] = name
|
||||||
|
widget.addChild(BINDS_WIDGET, erase, name)
|
||||||
|
local reset = {
|
||||||
|
type = "button",
|
||||||
|
callback = "resetBind",
|
||||||
|
position = {218, y + 2},
|
||||||
|
pressedOffset = {0, -1},
|
||||||
|
base = PATH .. "reset.png",
|
||||||
|
hover = PATH .. "reset.png?multiply=faa",
|
||||||
|
pressed = PATH .. "reset.png?multiply=f00",
|
||||||
|
}
|
||||||
|
name = "reset_" .. data.bindId
|
||||||
|
added[#added + 1] = name
|
||||||
|
widget.addChild(BINDS_WIDGET, reset, name)
|
||||||
|
end
|
||||||
|
|
||||||
|
function selectCategory()
|
||||||
|
local selected = widget.getListSelected(CATEGORY_LIST_WIDGET)
|
||||||
|
local category = widgetsToCategories[selected]
|
||||||
|
local dataFromPrev = widget.getData(BINDS_WIDGET)
|
||||||
|
if dataFromPrev then
|
||||||
|
for i, v in pairs(dataFromPrev) do
|
||||||
|
widget.removeChild(BINDS_WIDGET, v)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
widgetsToBinds = {}
|
||||||
|
|
||||||
|
local groups = category.groups or {}
|
||||||
|
if not groups.unsorted then
|
||||||
|
groups.unsorted = {name = "Unsorted"}
|
||||||
|
end
|
||||||
|
|
||||||
|
local sortedGroups = {}
|
||||||
|
for groupId, data in pairs(groups) do
|
||||||
|
data.name = tostring(data.name or groupId)
|
||||||
|
data.sortedBinds = {}
|
||||||
|
sortedGroups[#sortedGroups + 1] = data
|
||||||
|
end
|
||||||
|
|
||||||
|
allBinds = {}
|
||||||
|
for bindId, data in pairs(category.binds) do
|
||||||
|
if not data.name then data.name = bindId end
|
||||||
|
data.bindId = bindId
|
||||||
|
data.categoryId = category.categoryId
|
||||||
|
local group = groups[data.group or "unsorted"] or groups.unsorted
|
||||||
|
group.sortedBinds[#group.sortedBinds + 1] = data
|
||||||
|
allBinds[#allBinds + 1] = data
|
||||||
|
end
|
||||||
|
|
||||||
|
activeCategory = category.categoryId
|
||||||
|
table.sort(sortedGroups, alphabeticalNameSortLesser)
|
||||||
|
|
||||||
|
for groupId, data in pairs(groups) do
|
||||||
|
table.sort(data.sortedBinds, alphabeticalNameSortLesser)
|
||||||
|
end
|
||||||
|
|
||||||
|
local bannerBinds = widget.bindCanvas("banner")
|
||||||
|
bannerBinds:clear()
|
||||||
|
|
||||||
|
local bannerName = tostring(category.bannerName or category.name or category.categoryId)
|
||||||
|
bannerBinds:drawText(bannerName, {position = {127, 13}, horizontalAnchor = "mid", verticalAnchor = "mid"}, 16)
|
||||||
|
|
||||||
|
local onlyUnsorted = not sortedGroups[2] and sortedGroups[1] == groups.unsorted
|
||||||
|
|
||||||
|
local added = {}
|
||||||
|
local index = 0
|
||||||
|
for iA = 1, #sortedGroups do
|
||||||
|
local group = sortedGroups[iA]
|
||||||
|
local bindsCount = #group.sortedBinds
|
||||||
|
if bindsCount > 0 then
|
||||||
|
if not onlyUnsorted then
|
||||||
|
index = index + 1
|
||||||
|
addBindGroup(group, index, added)
|
||||||
|
end
|
||||||
|
for iB = 1, bindsCount do
|
||||||
|
index = index + 1
|
||||||
|
addBindSet(group.sortedBinds[iB], index, added)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
widget.setData(BINDS_WIDGET, added)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function initCallbacks()
|
||||||
|
widget.registerMemberCallback(CATEGORY_LIST_WIDGET, "selectCategory", selectCategory)
|
||||||
|
end
|
||||||
|
|
||||||
|
function init()
|
||||||
|
--log = sb.logInfo
|
||||||
|
|
||||||
|
widget.clearListItems(CATEGORY_LIST_WIDGET)
|
||||||
|
initCallbacks()
|
||||||
|
parseBinds()
|
||||||
|
|
||||||
|
script.setUpdateDelta(1)
|
||||||
|
end
|
BIN
assets/opensb/interface/opensb/bindings/bindname.png
Normal file
After Width: | Height: | Size: 249 B |
BIN
assets/opensb/interface/opensb/bindings/body.png
Normal file
After Width: | Height: | Size: 986 B |
BIN
assets/opensb/interface/opensb/bindings/category.png
Normal file
After Width: | Height: | Size: 181 B |
BIN
assets/opensb/interface/opensb/bindings/categoryback.png
Normal file
After Width: | Height: | Size: 318 B |
BIN
assets/opensb/interface/opensb/bindings/footer.png
Normal file
After Width: | Height: | Size: 260 B |
BIN
assets/opensb/interface/opensb/bindings/garbage.png
Normal file
After Width: | Height: | Size: 171 B |
BIN
assets/opensb/interface/opensb/bindings/groupname.png
Normal file
After Width: | Height: | Size: 475 B |
BIN
assets/opensb/interface/opensb/bindings/header.png
Normal file
After Width: | Height: | Size: 601 B |
BIN
assets/opensb/interface/opensb/bindings/reset.png
Normal file
After Width: | Height: | Size: 187 B |
BIN
assets/opensb/interface/optionsmenu/controlsbutton.png
Normal file
After Width: | Height: | Size: 213 B |
BIN
assets/opensb/interface/optionsmenu/controlsbuttonhover.png
Normal file
After Width: | Height: | Size: 213 B |
25
assets/opensb/interface/optionsmenu/optionsmenu.config.patch
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"paneLayout" : {
|
||||||
|
"showKeybindings" : {
|
||||||
|
"type" : "button",
|
||||||
|
"position" : [150, 95],
|
||||||
|
"caption" : "Game Binds",
|
||||||
|
"base" : "/interface/optionsmenu/controlsbutton.png",
|
||||||
|
"hover" : "/interface/optionsmenu/controlsbuttonhover.png"
|
||||||
|
},
|
||||||
|
"showModBindings" : {
|
||||||
|
"type" : "button",
|
||||||
|
"position" : [87, 95],
|
||||||
|
"caption" : "Mod Binds",
|
||||||
|
"base" : "/interface/optionsmenu/controlsbutton.png",
|
||||||
|
"hover" : "/interface/optionsmenu/controlsbuttonhover.png"
|
||||||
|
},
|
||||||
|
"showGraphics" : {
|
||||||
|
"type" : "button",
|
||||||
|
"position" : [24, 95],
|
||||||
|
"caption" : "Graphics",
|
||||||
|
"base" : "/interface/optionsmenu/controlsbutton.png",
|
||||||
|
"hover" : "/interface/optionsmenu/controlsbuttonhover.png"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -207,7 +207,7 @@ void ClientApplication::renderInit(RendererPtr renderer) {
|
|||||||
Application::renderInit(renderer);
|
Application::renderInit(renderer);
|
||||||
auto assets = m_root->assets();
|
auto assets = m_root->assets();
|
||||||
|
|
||||||
auto loadConfig = [&](String const& name) {
|
auto loadEffectConfig = [&](String const& name) {
|
||||||
String path = strf("/rendering/{}.config", name);
|
String path = strf("/rendering/{}.config", name);
|
||||||
if (assets->assetExists(path)) {
|
if (assets->assetExists(path)) {
|
||||||
StringMap<String> shaders;
|
StringMap<String> shaders;
|
||||||
@ -230,8 +230,8 @@ void ClientApplication::renderInit(RendererPtr renderer) {
|
|||||||
Logger::warn("No rendering config found for renderer with id '{}'", renderer->rendererId());
|
Logger::warn("No rendering config found for renderer with id '{}'", renderer->rendererId());
|
||||||
};
|
};
|
||||||
|
|
||||||
loadConfig("world");
|
loadEffectConfig("world");
|
||||||
loadConfig("default");
|
loadEffectConfig("default");
|
||||||
|
|
||||||
if (m_root->configuration()->get("limitTextureAtlasSize").optBool().value(false))
|
if (m_root->configuration()->get("limitTextureAtlasSize").optBool().value(false))
|
||||||
renderer->setSizeLimitEnabled(true);
|
renderer->setSizeLimitEnabled(true);
|
||||||
@ -301,17 +301,20 @@ void ClientApplication::processInput(InputEvent const& event) {
|
|||||||
if (!m_errorScreen->accepted() && m_errorScreen->handleInputEvent(event))
|
if (!m_errorScreen->accepted() && m_errorScreen->handleInputEvent(event))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (m_state == MainAppState::Splash) {
|
bool processed = false;
|
||||||
m_cinematicOverlay->handleInputEvent(event);
|
|
||||||
|
|
||||||
|
if (m_state == MainAppState::Splash) {
|
||||||
|
processed = m_cinematicOverlay->handleInputEvent(event);
|
||||||
} else if (m_state == MainAppState::Title) {
|
} else if (m_state == MainAppState::Title) {
|
||||||
if (!m_cinematicOverlay->handleInputEvent(event))
|
if (!(processed = m_cinematicOverlay->handleInputEvent(event)))
|
||||||
m_titleScreen->handleInputEvent(event);
|
processed = m_titleScreen->handleInputEvent(event);
|
||||||
|
|
||||||
} else if (m_state == MainAppState::SinglePlayer || m_state == MainAppState::MultiPlayer) {
|
} else if (m_state == MainAppState::SinglePlayer || m_state == MainAppState::MultiPlayer) {
|
||||||
if (!m_cinematicOverlay->handleInputEvent(event))
|
if (!(processed = m_cinematicOverlay->handleInputEvent(event)))
|
||||||
m_mainInterface->handleInputEvent(event);
|
processed = m_mainInterface->handleInputEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m_input->handleInput(event, processed);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClientApplication::update() {
|
void ClientApplication::update() {
|
||||||
@ -348,6 +351,7 @@ void ClientApplication::update() {
|
|||||||
|
|
||||||
m_guiContext->cleanup();
|
m_guiContext->cleanup();
|
||||||
m_edgeKeyEvents.clear();
|
m_edgeKeyEvents.clear();
|
||||||
|
m_input->reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClientApplication::render() {
|
void ClientApplication::render() {
|
||||||
@ -822,7 +826,9 @@ void ClientApplication::updateRunning() {
|
|||||||
m_mainInterface->update();
|
m_mainInterface->update();
|
||||||
m_mainMixer->update(m_cinematicOverlay->muteSfx(), m_cinematicOverlay->muteMusic());
|
m_mainMixer->update(m_cinematicOverlay->muteSfx(), m_cinematicOverlay->muteMusic());
|
||||||
|
|
||||||
appController()->setAcceptingTextInput(m_mainInterface->textInputActive());
|
bool inputActive = m_mainInterface->textInputActive();
|
||||||
|
appController()->setAcceptingTextInput(inputActive);
|
||||||
|
m_input->setTextInputActive(inputActive);
|
||||||
|
|
||||||
for (auto const& interactAction : m_player->pullInteractActions())
|
for (auto const& interactAction : m_player->pullInteractActions())
|
||||||
m_mainInterface->handleInteractAction(interactAction);
|
m_mainInterface->handleInteractAction(interactAction);
|
||||||
|
@ -13,6 +13,8 @@ INCLUDE_DIRECTORIES (
|
|||||||
SET (star_frontend_HEADERS
|
SET (star_frontend_HEADERS
|
||||||
StarActionBar.hpp
|
StarActionBar.hpp
|
||||||
StarAiInterface.hpp
|
StarAiInterface.hpp
|
||||||
|
StarBaseScriptPane.hpp
|
||||||
|
StarBindingsMenu.hpp
|
||||||
StarBookmarkInterface.hpp
|
StarBookmarkInterface.hpp
|
||||||
StarChat.hpp
|
StarChat.hpp
|
||||||
StarCharCreation.hpp
|
StarCharCreation.hpp
|
||||||
@ -59,6 +61,8 @@ SET (star_frontend_HEADERS
|
|||||||
SET (star_frontend_SOURCES
|
SET (star_frontend_SOURCES
|
||||||
StarActionBar.cpp
|
StarActionBar.cpp
|
||||||
StarAiInterface.cpp
|
StarAiInterface.cpp
|
||||||
|
StarBaseScriptPane.cpp
|
||||||
|
StarBindingsMenu.cpp
|
||||||
StarBookmarkInterface.cpp
|
StarBookmarkInterface.cpp
|
||||||
StarChat.cpp
|
StarChat.cpp
|
||||||
StarCharCreation.cpp
|
StarCharCreation.cpp
|
||||||
|
183
source/frontend/StarBaseScriptPane.cpp
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
#include "StarBaseScriptPane.hpp"
|
||||||
|
#include "StarRoot.hpp"
|
||||||
|
#include "StarAssets.hpp"
|
||||||
|
#include "StarGuiReader.hpp"
|
||||||
|
#include "StarJsonExtra.hpp"
|
||||||
|
#include "StarConfigLuaBindings.hpp"
|
||||||
|
#include "StarLuaGameConverters.hpp"
|
||||||
|
#include "StarWidgetLuaBindings.hpp"
|
||||||
|
#include "StarCanvasWidget.hpp"
|
||||||
|
#include "StarItemTooltip.hpp"
|
||||||
|
#include "StarItemGridWidget.hpp"
|
||||||
|
#include "StarSimpleTooltip.hpp"
|
||||||
|
#include "StarImageWidget.hpp"
|
||||||
|
|
||||||
|
namespace Star {
|
||||||
|
|
||||||
|
BaseScriptPane::BaseScriptPane(Json config) : Pane() {
|
||||||
|
auto& root = Root::singleton();
|
||||||
|
auto assets = root.assets();
|
||||||
|
|
||||||
|
if (config.type() == Json::Type::Object && config.contains("baseConfig")) {
|
||||||
|
auto baseConfig = assets->fetchJson(config.getString("baseConfig"));
|
||||||
|
m_config = jsonMerge(baseConfig, config);
|
||||||
|
} else {
|
||||||
|
m_config = assets->fetchJson(config);
|
||||||
|
}
|
||||||
|
m_reader.registerCallback("close", [this](Widget*) { dismiss(); });
|
||||||
|
|
||||||
|
for (auto const& callbackName : jsonToStringList(m_config.get("scriptWidgetCallbacks", JsonArray{}))) {
|
||||||
|
m_reader.registerCallback(callbackName, [this, callbackName](Widget* widget) {
|
||||||
|
m_script.invoke(callbackName, widget->name(), widget->data());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
m_reader.construct(assets->fetchJson(m_config.get("gui")), this);
|
||||||
|
|
||||||
|
for (auto pair : m_config.getObject("canvasClickCallbacks", {}))
|
||||||
|
m_canvasClickCallbacks.set(findChild<CanvasWidget>(pair.first), pair.second.toString());
|
||||||
|
for (auto pair : m_config.getObject("canvasKeyCallbacks", {}))
|
||||||
|
m_canvasKeyCallbacks.set(findChild<CanvasWidget>(pair.first), pair.second.toString());
|
||||||
|
|
||||||
|
m_script.setScripts(jsonToStringList(m_config.get("scripts", JsonArray())));
|
||||||
|
m_script.setUpdateDelta(m_config.getUInt("scriptDelta", 1));
|
||||||
|
|
||||||
|
m_callbacksAdded = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BaseScriptPane::show() {
|
||||||
|
Pane::show();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BaseScriptPane::displayed() {
|
||||||
|
Pane::displayed();
|
||||||
|
if (!m_callbacksAdded) {
|
||||||
|
m_script.addCallbacks("pane", makePaneCallbacks());
|
||||||
|
m_script.addCallbacks("widget", LuaBindings::makeWidgetCallbacks(this, &m_reader));
|
||||||
|
m_script.addCallbacks("config", LuaBindings::makeConfigCallbacks( [this](String const& name, Json const& def) {
|
||||||
|
return m_config.query(name, def);
|
||||||
|
}));
|
||||||
|
m_callbacksAdded = true;
|
||||||
|
}
|
||||||
|
m_script.init();
|
||||||
|
m_script.invoke("displayed");
|
||||||
|
}
|
||||||
|
|
||||||
|
void BaseScriptPane::dismissed() {
|
||||||
|
Pane::dismissed();
|
||||||
|
m_script.invoke("dismissed");
|
||||||
|
m_script.uninit();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BaseScriptPane::tick() {
|
||||||
|
Pane::tick();
|
||||||
|
|
||||||
|
for (auto p : m_canvasClickCallbacks) {
|
||||||
|
for (auto const& clickEvent : p.first->pullClickEvents())
|
||||||
|
m_script.invoke(p.second, jsonFromVec2I(clickEvent.position), (uint8_t)clickEvent.button, clickEvent.buttonDown);
|
||||||
|
}
|
||||||
|
for (auto p : m_canvasKeyCallbacks) {
|
||||||
|
for (auto const& keyEvent : p.first->pullKeyEvents())
|
||||||
|
m_script.invoke(p.second, (int)keyEvent.key, keyEvent.keyDown);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_playingSounds.filter([](pair<String, AudioInstancePtr> const& p) {
|
||||||
|
return p.second->finished() == false;
|
||||||
|
});
|
||||||
|
|
||||||
|
m_script.update(m_script.updateDt());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BaseScriptPane::sendEvent(InputEvent const& event) {
|
||||||
|
// Intercept GuiClose before the canvas child so GuiClose always closes
|
||||||
|
// BaseScriptPanes without having to support it in the script.
|
||||||
|
if (context()->actions(event).contains(InterfaceAction::GuiClose)) {
|
||||||
|
dismiss();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Pane::sendEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
PanePtr BaseScriptPane::createTooltip(Vec2I const& screenPosition) {
|
||||||
|
auto result = m_script.invoke<Json>("createTooltip", screenPosition);
|
||||||
|
if (result && !result.value().isNull()) {
|
||||||
|
if (result->type() == Json::Type::String) {
|
||||||
|
return SimpleTooltipBuilder::buildTooltip(result->toString());
|
||||||
|
} else {
|
||||||
|
PanePtr tooltip = make_shared<Pane>();
|
||||||
|
m_reader.construct(*result, tooltip.get());
|
||||||
|
return tooltip;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ItemPtr item;
|
||||||
|
if (auto child = getChildAt(screenPosition)) {
|
||||||
|
if (auto itemSlot = as<ItemSlotWidget>(child))
|
||||||
|
item = itemSlot->item();
|
||||||
|
if (auto itemGrid = as<ItemGridWidget>(child))
|
||||||
|
item = itemGrid->itemAt(screenPosition);
|
||||||
|
}
|
||||||
|
if (item)
|
||||||
|
return ItemTooltipBuilder::buildItemTooltip(item);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Maybe<String> BaseScriptPane::cursorOverride(Vec2I const& screenPosition) {
|
||||||
|
auto result = m_script.invoke<Maybe<String>>("cursorOverride", screenPosition);
|
||||||
|
if (result)
|
||||||
|
return *result;
|
||||||
|
else
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
LuaCallbacks BaseScriptPane::makePaneCallbacks() {
|
||||||
|
LuaCallbacks callbacks;
|
||||||
|
|
||||||
|
callbacks.registerCallback("dismiss", [this]() { dismiss(); });
|
||||||
|
|
||||||
|
callbacks.registerCallback("playSound",
|
||||||
|
[this](String const& audio, Maybe<int> loops, Maybe<float> volume) {
|
||||||
|
auto assets = Root::singleton().assets();
|
||||||
|
auto config = Root::singleton().configuration();
|
||||||
|
auto audioInstance = make_shared<AudioInstance>(*assets->audio(audio));
|
||||||
|
audioInstance->setVolume(volume.value(1.0));
|
||||||
|
audioInstance->setLoops(loops.value(0));
|
||||||
|
auto& guiContext = GuiContext::singleton();
|
||||||
|
guiContext.playAudio(audioInstance);
|
||||||
|
m_playingSounds.append({audio, move(audioInstance)});
|
||||||
|
});
|
||||||
|
|
||||||
|
callbacks.registerCallback("stopAllSounds", [this](String const& audio) {
|
||||||
|
m_playingSounds.filter([audio](pair<String, AudioInstancePtr> const& p) {
|
||||||
|
if (p.first == audio) {
|
||||||
|
p.second->stop();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
callbacks.registerCallback("setTitle", [this](String const& title, String const& subTitle) {
|
||||||
|
setTitleString(title, subTitle);
|
||||||
|
});
|
||||||
|
|
||||||
|
callbacks.registerCallback("setTitleIcon", [this](String const& image) {
|
||||||
|
if (auto icon = as<ImageWidget>(titleIcon()))
|
||||||
|
icon->setImage(image);
|
||||||
|
});
|
||||||
|
|
||||||
|
callbacks.registerCallback("addWidget", [this](Json const& newWidgetConfig, Maybe<String> const& newWidgetName) {
|
||||||
|
String name = newWidgetName.value(strf("{}", Random::randu64()));
|
||||||
|
WidgetPtr newWidget = m_reader.makeSingle(name, newWidgetConfig);
|
||||||
|
this->addChild(name, newWidget);
|
||||||
|
});
|
||||||
|
|
||||||
|
callbacks.registerCallback("removeWidget", [this](String const& widgetName) {
|
||||||
|
this->removeChild(widgetName);
|
||||||
|
});
|
||||||
|
|
||||||
|
return callbacks;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
45
source/frontend/StarBaseScriptPane.hpp
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
#ifndef STAR_BASE_SCRIPT_PANE_HPP
|
||||||
|
#define STAR_BASE_SCRIPT_PANE_HPP
|
||||||
|
|
||||||
|
#include "StarPane.hpp"
|
||||||
|
#include "StarLuaComponents.hpp"
|
||||||
|
#include "StarGuiReader.hpp"
|
||||||
|
|
||||||
|
namespace Star {
|
||||||
|
|
||||||
|
STAR_CLASS(CanvasWidget);
|
||||||
|
STAR_CLASS(BaseScriptPane);
|
||||||
|
|
||||||
|
// A more 'raw' script pane that doesn't depend on a world being present.
|
||||||
|
// Requires a derived class to provide a Lua root.
|
||||||
|
class BaseScriptPane : public Pane {
|
||||||
|
public:
|
||||||
|
BaseScriptPane(Json config);
|
||||||
|
|
||||||
|
virtual void show() override;
|
||||||
|
void displayed() override;
|
||||||
|
void dismissed() override;
|
||||||
|
|
||||||
|
void tick() override;
|
||||||
|
|
||||||
|
bool sendEvent(InputEvent const& event) override;
|
||||||
|
|
||||||
|
PanePtr createTooltip(Vec2I const& screenPosition) override;
|
||||||
|
Maybe<String> cursorOverride(Vec2I const& screenPosition) override;
|
||||||
|
protected:
|
||||||
|
virtual LuaCallbacks makePaneCallbacks();
|
||||||
|
Json m_config;
|
||||||
|
|
||||||
|
GuiReader m_reader;
|
||||||
|
|
||||||
|
Map<CanvasWidgetPtr, String> m_canvasClickCallbacks;
|
||||||
|
Map<CanvasWidgetPtr, String> m_canvasKeyCallbacks;
|
||||||
|
|
||||||
|
bool m_callbacksAdded;
|
||||||
|
LuaUpdatableComponent<LuaBaseComponent> m_script;
|
||||||
|
List<pair<String, AudioInstancePtr>> m_playingSounds;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
23
source/frontend/StarBindingsMenu.cpp
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
#include "StarBindingsMenu.hpp"
|
||||||
|
#include "StarInputLuaBindings.hpp"
|
||||||
|
|
||||||
|
namespace Star {
|
||||||
|
|
||||||
|
BindingsMenu::BindingsMenu(Json const& config) : BaseScriptPane(config) {
|
||||||
|
m_script.setLuaRoot(make_shared<LuaRoot>());
|
||||||
|
m_script.addCallbacks("input", LuaBindings::makeInputCallbacks());
|
||||||
|
}
|
||||||
|
|
||||||
|
void BindingsMenu::show() {
|
||||||
|
BaseScriptPane::show();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BindingsMenu::displayed() {
|
||||||
|
BaseScriptPane::displayed();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BindingsMenu::dismissed() {
|
||||||
|
BaseScriptPane::dismissed();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
24
source/frontend/StarBindingsMenu.hpp
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
#ifndef STAR_BINDINGS_MENU_HPP
|
||||||
|
#define STAR_BINDINGS_MENU_HPP
|
||||||
|
|
||||||
|
#include "StarBaseScriptPane.hpp"
|
||||||
|
|
||||||
|
namespace Star {
|
||||||
|
|
||||||
|
STAR_CLASS(BindingsMenu);
|
||||||
|
|
||||||
|
class BindingsMenu : public BaseScriptPane {
|
||||||
|
public:
|
||||||
|
BindingsMenu(Json const& config);
|
||||||
|
|
||||||
|
virtual void show() override;
|
||||||
|
void displayed() override;
|
||||||
|
void dismissed() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
@ -357,7 +357,7 @@ bool MainInterface::handleInputEvent(InputEvent const& event) {
|
|||||||
} else if (auto mouseDown = event.ptr<MouseButtonDownEvent>()) {
|
} else if (auto mouseDown = event.ptr<MouseButtonDownEvent>()) {
|
||||||
if (mouseDown->mouseButton == MouseButton::Left || mouseDown->mouseButton == MouseButton::Right
|
if (mouseDown->mouseButton == MouseButton::Left || mouseDown->mouseButton == MouseButton::Right
|
||||||
|| mouseDown->mouseButton == MouseButton::Middle)
|
|| mouseDown->mouseButton == MouseButton::Middle)
|
||||||
overlayClick(mouseDown->mousePosition, mouseDown->mouseButton);
|
return overlayClick(mouseDown->mousePosition, mouseDown->mouseButton);
|
||||||
|
|
||||||
} else if (auto mouseUp = event.ptr<MouseButtonUpEvent>()) {
|
} else if (auto mouseUp = event.ptr<MouseButtonUpEvent>()) {
|
||||||
if (mouseUp->mouseButton == MouseButton::Left)
|
if (mouseUp->mouseButton == MouseButton::Left)
|
||||||
@ -1428,7 +1428,7 @@ bool MainInterface::overButton(PolyI buttonPoly, Vec2I const& mousePos) const {
|
|||||||
return buttonPoly.contains(mousePos);
|
return buttonPoly.contains(mousePos);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainInterface::overlayClick(Vec2I const& mousePos, MouseButton mouseButton) {
|
bool MainInterface::overlayClick(Vec2I const& mousePos, MouseButton mouseButton) {
|
||||||
PolyI mainBarPoly = m_config->mainBarPoly;
|
PolyI mainBarPoly = m_config->mainBarPoly;
|
||||||
Vec2I barPos = mainBarPosition();
|
Vec2I barPos = mainBarPosition();
|
||||||
mainBarPoly.translate(barPos);
|
mainBarPoly.translate(barPos);
|
||||||
@ -1436,17 +1436,17 @@ void MainInterface::overlayClick(Vec2I const& mousePos, MouseButton mouseButton)
|
|||||||
|
|
||||||
if (overButton(m_config->mainBarInventoryButtonPoly, mousePos)) {
|
if (overButton(m_config->mainBarInventoryButtonPoly, mousePos)) {
|
||||||
m_paneManager.toggleRegisteredPane(MainInterfacePanes::Inventory);
|
m_paneManager.toggleRegisteredPane(MainInterfacePanes::Inventory);
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (overButton(m_config->mainBarCraftButtonPoly, mousePos)) {
|
if (overButton(m_config->mainBarCraftButtonPoly, mousePos)) {
|
||||||
togglePlainCraftingWindow();
|
togglePlainCraftingWindow();
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (overButton(m_config->mainBarCodexButtonPoly, mousePos)) {
|
if (overButton(m_config->mainBarCodexButtonPoly, mousePos)) {
|
||||||
m_paneManager.toggleRegisteredPane(MainInterfacePanes::Codex);
|
m_paneManager.toggleRegisteredPane(MainInterfacePanes::Codex);
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (overButton(m_config->mainBarDeployButtonPoly, mousePos)) {
|
if (overButton(m_config->mainBarDeployButtonPoly, mousePos)) {
|
||||||
@ -1454,29 +1454,29 @@ void MainInterface::overlayClick(Vec2I const& mousePos, MouseButton mouseButton)
|
|||||||
warpToOrbitedWorld(true);
|
warpToOrbitedWorld(true);
|
||||||
else if (m_client->canBeamUp())
|
else if (m_client->canBeamUp())
|
||||||
warpToOwnShip();
|
warpToOwnShip();
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (overButton(m_config->mainBarBeamButtonPoly, mousePos)) {
|
if (overButton(m_config->mainBarBeamButtonPoly, mousePos)) {
|
||||||
if (m_client->canBeamDown())
|
if (m_client->canBeamDown())
|
||||||
warpToOrbitedWorld();
|
warpToOrbitedWorld();
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (overButton(m_config->mainBarQuestLogButtonPoly, mousePos)) {
|
if (overButton(m_config->mainBarQuestLogButtonPoly, mousePos)) {
|
||||||
m_paneManager.toggleRegisteredPane(MainInterfacePanes::QuestLog);
|
m_paneManager.toggleRegisteredPane(MainInterfacePanes::QuestLog);
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (overButton(m_config->mainBarMmUpgradeButtonPoly, mousePos)) {
|
if (overButton(m_config->mainBarMmUpgradeButtonPoly, mousePos)) {
|
||||||
if (m_client->mainPlayer()->inventory()->essentialItem(EssentialItem::BeamAxe))
|
if (m_client->mainPlayer()->inventory()->essentialItem(EssentialItem::BeamAxe))
|
||||||
m_paneManager.toggleRegisteredPane(MainInterfacePanes::MmUpgrade);
|
m_paneManager.toggleRegisteredPane(MainInterfacePanes::MmUpgrade);
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (overButton(m_config->mainBarCollectionsButtonPoly, mousePos)) {
|
if (overButton(m_config->mainBarCollectionsButtonPoly, mousePos)) {
|
||||||
m_paneManager.toggleRegisteredPane(MainInterfacePanes::Collections);
|
m_paneManager.toggleRegisteredPane(MainInterfacePanes::Collections);
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mouseButton == MouseButton::Left)
|
if (mouseButton == MouseButton::Left)
|
||||||
@ -1485,6 +1485,8 @@ void MainInterface::overlayClick(Vec2I const& mousePos, MouseButton mouseButton)
|
|||||||
m_client->mainPlayer()->beginAltFire();
|
m_client->mainPlayer()->beginAltFire();
|
||||||
if (mouseButton == MouseButton::Middle)
|
if (mouseButton == MouseButton::Middle)
|
||||||
m_client->mainPlayer()->beginTrigger();
|
m_client->mainPlayer()->beginTrigger();
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -135,7 +135,7 @@ private:
|
|||||||
|
|
||||||
bool overButton(PolyI buttonPoly, Vec2I const& mousePos) const;
|
bool overButton(PolyI buttonPoly, Vec2I const& mousePos) const;
|
||||||
|
|
||||||
void overlayClick(Vec2I const& mousePos, MouseButton mouseButton);
|
bool overlayClick(Vec2I const& mousePos, MouseButton mouseButton);
|
||||||
|
|
||||||
GuiContext* m_guiContext;
|
GuiContext* m_guiContext;
|
||||||
MainInterfaceConfigConstPtr m_config;
|
MainInterfaceConfigConstPtr m_config;
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
#include "StarLabelWidget.hpp"
|
#include "StarLabelWidget.hpp"
|
||||||
#include "StarAssets.hpp"
|
#include "StarAssets.hpp"
|
||||||
#include "StarKeybindingsMenu.hpp"
|
#include "StarKeybindingsMenu.hpp"
|
||||||
|
#include "StarBindingsMenu.hpp"
|
||||||
#include "StarGraphicsMenu.hpp"
|
#include "StarGraphicsMenu.hpp"
|
||||||
|
|
||||||
namespace Star {
|
namespace Star {
|
||||||
@ -48,11 +49,16 @@ OptionsMenu::OptionsMenu(PaneManager* manager)
|
|||||||
reader.registerCallback("showKeybindings", [=](Widget*) {
|
reader.registerCallback("showKeybindings", [=](Widget*) {
|
||||||
displayControls();
|
displayControls();
|
||||||
});
|
});
|
||||||
|
reader.registerCallback("showModBindings", [=](Widget*) {
|
||||||
|
displayModBindings();
|
||||||
|
});
|
||||||
reader.registerCallback("showGraphics", [=](Widget*) {
|
reader.registerCallback("showGraphics", [=](Widget*) {
|
||||||
displayGraphics();
|
displayGraphics();
|
||||||
});
|
});
|
||||||
|
|
||||||
reader.construct(assets->json("/interface/optionsmenu/optionsmenu.config:paneLayout"), this);
|
Json config = assets->json("/interface/optionsmenu/optionsmenu.config");
|
||||||
|
|
||||||
|
reader.construct(config.get("paneLayout"), this);
|
||||||
|
|
||||||
m_sfxSlider = fetchChild<SliderBarWidget>("sfxSlider");
|
m_sfxSlider = fetchChild<SliderBarWidget>("sfxSlider");
|
||||||
m_musicSlider = fetchChild<SliderBarWidget>("musicSlider");
|
m_musicSlider = fetchChild<SliderBarWidget>("musicSlider");
|
||||||
@ -68,6 +74,7 @@ OptionsMenu::OptionsMenu(PaneManager* manager)
|
|||||||
m_sfxSlider->setRange(m_sfxRange, assets->json("/interface/optionsmenu/optionsmenu.config:sfxDelta").toInt());
|
m_sfxSlider->setRange(m_sfxRange, assets->json("/interface/optionsmenu/optionsmenu.config:sfxDelta").toInt());
|
||||||
m_musicSlider->setRange(m_musicRange, assets->json("/interface/optionsmenu/optionsmenu.config:musicDelta").toInt());
|
m_musicSlider->setRange(m_musicRange, assets->json("/interface/optionsmenu/optionsmenu.config:musicDelta").toInt());
|
||||||
|
|
||||||
|
m_modBindingsMenu = make_shared<BindingsMenu>(assets->json(config.getString("bindingsPanePath", "/interface/opensb/bindings/bindings.config")));
|
||||||
m_keybindingsMenu = make_shared<KeybindingsMenu>();
|
m_keybindingsMenu = make_shared<KeybindingsMenu>();
|
||||||
m_graphicsMenu = make_shared<GraphicsMenu>();
|
m_graphicsMenu = make_shared<GraphicsMenu>();
|
||||||
|
|
||||||
@ -162,6 +169,10 @@ void OptionsMenu::displayControls() {
|
|||||||
m_paneManager->displayPane(PaneLayer::ModalWindow, m_keybindingsMenu);
|
m_paneManager->displayPane(PaneLayer::ModalWindow, m_keybindingsMenu);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void OptionsMenu::displayModBindings() {
|
||||||
|
m_paneManager->displayPane(PaneLayer::ModalWindow, m_modBindingsMenu);
|
||||||
|
}
|
||||||
|
|
||||||
void OptionsMenu::displayGraphics() {
|
void OptionsMenu::displayGraphics() {
|
||||||
m_paneManager->displayPane(PaneLayer::ModalWindow, m_graphicsMenu);
|
m_paneManager->displayPane(PaneLayer::ModalWindow, m_graphicsMenu);
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ STAR_CLASS(ButtonWidget);
|
|||||||
STAR_CLASS(LabelWidget);
|
STAR_CLASS(LabelWidget);
|
||||||
STAR_CLASS(KeybindingsMenu);
|
STAR_CLASS(KeybindingsMenu);
|
||||||
STAR_CLASS(GraphicsMenu);
|
STAR_CLASS(GraphicsMenu);
|
||||||
|
STAR_CLASS(BindingsMenu);
|
||||||
STAR_CLASS(OptionsMenu);
|
STAR_CLASS(OptionsMenu);
|
||||||
|
|
||||||
class OptionsMenu : public Pane {
|
class OptionsMenu : public Pane {
|
||||||
@ -38,6 +38,7 @@ private:
|
|||||||
void syncGuiToConf();
|
void syncGuiToConf();
|
||||||
|
|
||||||
void displayControls();
|
void displayControls();
|
||||||
|
void displayModBindings();
|
||||||
void displayGraphics();
|
void displayGraphics();
|
||||||
|
|
||||||
SliderBarWidgetPtr m_sfxSlider;
|
SliderBarWidgetPtr m_sfxSlider;
|
||||||
@ -58,6 +59,7 @@ private:
|
|||||||
JsonObject m_origConfig;
|
JsonObject m_origConfig;
|
||||||
JsonObject m_localChanges;
|
JsonObject m_localChanges;
|
||||||
|
|
||||||
|
BindingsMenuPtr m_modBindingsMenu;
|
||||||
KeybindingsMenuPtr m_keybindingsMenu;
|
KeybindingsMenuPtr m_keybindingsMenu;
|
||||||
GraphicsMenuPtr m_graphicsMenu;
|
GraphicsMenuPtr m_graphicsMenu;
|
||||||
PaneManager* m_paneManager;
|
PaneManager* m_paneManager;
|
||||||
|
@ -20,64 +20,34 @@
|
|||||||
|
|
||||||
namespace Star {
|
namespace Star {
|
||||||
|
|
||||||
ScriptPane::ScriptPane(UniverseClientPtr client, Json config, EntityId sourceEntityId) {
|
ScriptPane::ScriptPane(UniverseClientPtr client, Json config, EntityId sourceEntityId) : BaseScriptPane(config) {
|
||||||
auto& root = Root::singleton();
|
auto& root = Root::singleton();
|
||||||
auto assets = root.assets();
|
auto assets = root.assets();
|
||||||
|
|
||||||
m_client = move(client);
|
m_client = move(client);
|
||||||
|
|
||||||
if (config.type() == Json::Type::Object && config.contains("baseConfig")) {
|
|
||||||
auto baseConfig = assets->fetchJson(config.getString("baseConfig"));
|
|
||||||
m_config = jsonMerge(baseConfig, config);
|
|
||||||
} else {
|
|
||||||
m_config = assets->fetchJson(config);
|
|
||||||
}
|
|
||||||
m_sourceEntityId = sourceEntityId;
|
m_sourceEntityId = sourceEntityId;
|
||||||
|
|
||||||
m_reader.registerCallback("close", [this](Widget*) { dismiss(); });
|
|
||||||
|
|
||||||
for (auto const& callbackName : jsonToStringList(m_config.get("scriptWidgetCallbacks", JsonArray{}))) {
|
|
||||||
m_reader.registerCallback(callbackName, [this, callbackName](Widget* widget) {
|
|
||||||
m_script.invoke(callbackName, widget->name(), widget->data());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
m_reader.construct(assets->fetchJson(m_config.get("gui")), this);
|
|
||||||
|
|
||||||
for (auto pair : m_config.getObject("canvasClickCallbacks", {}))
|
|
||||||
m_canvasClickCallbacks.set(findChild<CanvasWidget>(pair.first), pair.second.toString());
|
|
||||||
for (auto pair : m_config.getObject("canvasKeyCallbacks", {}))
|
|
||||||
m_canvasKeyCallbacks.set(findChild<CanvasWidget>(pair.first), pair.second.toString());
|
|
||||||
|
|
||||||
m_script.setScripts(jsonToStringList(m_config.get("scripts", JsonArray())));
|
|
||||||
m_script.addCallbacks("pane", makePaneCallbacks());
|
|
||||||
m_script.addCallbacks("widget", LuaBindings::makeWidgetCallbacks(this, &m_reader));
|
|
||||||
m_script.addCallbacks("config", LuaBindings::makeConfigCallbacks( [this](String const& name, Json const& def) {
|
|
||||||
return m_config.query(name, def);
|
|
||||||
}));
|
|
||||||
m_script.addCallbacks("player", LuaBindings::makePlayerCallbacks(m_client->mainPlayer().get()));
|
m_script.addCallbacks("player", LuaBindings::makePlayerCallbacks(m_client->mainPlayer().get()));
|
||||||
m_script.addCallbacks("status", LuaBindings::makeStatusControllerCallbacks(m_client->mainPlayer()->statusController()));
|
m_script.addCallbacks("status", LuaBindings::makeStatusControllerCallbacks(m_client->mainPlayer()->statusController()));
|
||||||
m_script.addCallbacks("celestial", LuaBindings::makeCelestialCallbacks(m_client.get()));
|
m_script.addCallbacks("celestial", LuaBindings::makeCelestialCallbacks(m_client.get()));
|
||||||
m_script.setUpdateDelta(m_config.getUInt("scriptDelta", 1));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScriptPane::displayed() {
|
void ScriptPane::displayed() {
|
||||||
Pane::displayed();
|
|
||||||
auto world = m_client->worldClient();
|
auto world = m_client->worldClient();
|
||||||
if (world && world->inWorld())
|
if (world && world->inWorld()) {
|
||||||
m_script.init(world.get());
|
m_script.setLuaRoot(world->luaRoot());
|
||||||
|
m_script.addCallbacks("world", LuaBindings::makeWorldCallbacks(world.get()));
|
||||||
m_script.invoke("displayed");
|
}
|
||||||
|
BaseScriptPane::displayed();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScriptPane::dismissed() {
|
void ScriptPane::dismissed() {
|
||||||
Pane::dismissed();
|
BaseScriptPane::dismissed();
|
||||||
m_script.invoke("dismissed");
|
m_script.removeCallbacks("world");
|
||||||
m_script.uninit();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScriptPane::tick() {
|
void ScriptPane::tick() {
|
||||||
Pane::tick();
|
BaseScriptPane::tick();
|
||||||
|
|
||||||
if (m_sourceEntityId != NullEntityId && !m_client->worldClient()->playerCanReachEntity(m_sourceEntityId))
|
if (m_sourceEntityId != NullEntityId && !m_client->worldClient()->playerCanReachEntity(m_sourceEntityId))
|
||||||
dismiss();
|
dismiss();
|
||||||
@ -98,17 +68,6 @@ void ScriptPane::tick() {
|
|||||||
m_script.update(m_script.updateDt());
|
m_script.update(m_script.updateDt());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ScriptPane::sendEvent(InputEvent const& event) {
|
|
||||||
// Intercept GuiClose before the canvas child so GuiClose always closes
|
|
||||||
// ScriptPanes without having to support it in the script.
|
|
||||||
if (context()->actions(event).contains(InterfaceAction::GuiClose)) {
|
|
||||||
dismiss();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Pane::sendEvent(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
PanePtr ScriptPane::createTooltip(Vec2I const& screenPosition) {
|
PanePtr ScriptPane::createTooltip(Vec2I const& screenPosition) {
|
||||||
auto result = m_script.invoke<Json>("createTooltip", screenPosition);
|
auto result = m_script.invoke<Json>("createTooltip", screenPosition);
|
||||||
if (result && !result.value().isNull()) {
|
if (result && !result.value().isNull()) {
|
||||||
@ -133,61 +92,9 @@ PanePtr ScriptPane::createTooltip(Vec2I const& screenPosition) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Maybe<String> ScriptPane::cursorOverride(Vec2I const& screenPosition) {
|
|
||||||
auto result = m_script.invoke<Maybe<String>>("cursorOverride", screenPosition);
|
|
||||||
if (result)
|
|
||||||
return *result;
|
|
||||||
else
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
LuaCallbacks ScriptPane::makePaneCallbacks() {
|
LuaCallbacks ScriptPane::makePaneCallbacks() {
|
||||||
LuaCallbacks callbacks;
|
LuaCallbacks callbacks = BaseScriptPane::makePaneCallbacks();
|
||||||
|
|
||||||
callbacks.registerCallback("sourceEntity", [this]() { return m_sourceEntityId; });
|
callbacks.registerCallback("sourceEntity", [this]() { return m_sourceEntityId; });
|
||||||
callbacks.registerCallback("dismiss", [this]() { dismiss(); });
|
|
||||||
|
|
||||||
callbacks.registerCallback("playSound",
|
|
||||||
[this](String const& audio, Maybe<int> loops, Maybe<float> volume) {
|
|
||||||
auto assets = Root::singleton().assets();
|
|
||||||
auto config = Root::singleton().configuration();
|
|
||||||
auto audioInstance = make_shared<AudioInstance>(*assets->audio(audio));
|
|
||||||
audioInstance->setVolume(volume.value(1.0));
|
|
||||||
audioInstance->setLoops(loops.value(0));
|
|
||||||
auto& guiContext = GuiContext::singleton();
|
|
||||||
guiContext.playAudio(audioInstance);
|
|
||||||
m_playingSounds.append({audio, move(audioInstance)});
|
|
||||||
});
|
|
||||||
|
|
||||||
callbacks.registerCallback("stopAllSounds", [this](String const& audio) {
|
|
||||||
m_playingSounds.filter([audio](pair<String, AudioInstancePtr> const& p) {
|
|
||||||
if (p.first == audio) {
|
|
||||||
p.second->stop();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
callbacks.registerCallback("setTitle", [this](String const& title, String const& subTitle) {
|
|
||||||
setTitleString(title, subTitle);
|
|
||||||
});
|
|
||||||
|
|
||||||
callbacks.registerCallback("setTitleIcon", [this](String const& image) {
|
|
||||||
if (auto icon = as<ImageWidget>(titleIcon()))
|
|
||||||
icon->setImage(image);
|
|
||||||
});
|
|
||||||
|
|
||||||
callbacks.registerCallback("addWidget", [this](Json const& newWidgetConfig, Maybe<String> const& newWidgetName) {
|
|
||||||
String name = newWidgetName.value(strf("{}", Random::randu64()));
|
|
||||||
WidgetPtr newWidget = m_reader.makeSingle(name, newWidgetConfig);
|
|
||||||
this->addChild(name, newWidget);
|
|
||||||
});
|
|
||||||
|
|
||||||
callbacks.registerCallback("removeWidget", [this](String const& widgetName) {
|
|
||||||
this->removeChild(widgetName);
|
|
||||||
});
|
|
||||||
|
|
||||||
return callbacks;
|
return callbacks;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
#ifndef STAR_SCRIPT_PANE_HPP
|
#ifndef STAR_SCRIPT_PANE_HPP
|
||||||
#define STAR_SCRIPT_PANE_HPP
|
#define STAR_SCRIPT_PANE_HPP
|
||||||
|
|
||||||
#include "StarPane.hpp"
|
#include "StarBaseScriptPane.hpp"
|
||||||
#include "StarLuaComponents.hpp"
|
|
||||||
#include "StarGuiReader.hpp"
|
|
||||||
|
|
||||||
namespace Star {
|
namespace Star {
|
||||||
|
|
||||||
@ -11,7 +9,7 @@ STAR_CLASS(CanvasWidget);
|
|||||||
STAR_CLASS(ScriptPane);
|
STAR_CLASS(ScriptPane);
|
||||||
STAR_CLASS(UniverseClient);
|
STAR_CLASS(UniverseClient);
|
||||||
|
|
||||||
class ScriptPane : public Pane {
|
class ScriptPane : public BaseScriptPane {
|
||||||
public:
|
public:
|
||||||
ScriptPane(UniverseClientPtr client, Json config, EntityId sourceEntityId = NullEntityId);
|
ScriptPane(UniverseClientPtr client, Json config, EntityId sourceEntityId = NullEntityId);
|
||||||
|
|
||||||
@ -20,28 +18,15 @@ public:
|
|||||||
|
|
||||||
void tick() override;
|
void tick() override;
|
||||||
|
|
||||||
bool sendEvent(InputEvent const& event) override;
|
|
||||||
|
|
||||||
PanePtr createTooltip(Vec2I const& screenPosition) override;
|
PanePtr createTooltip(Vec2I const& screenPosition) override;
|
||||||
Maybe<String> cursorOverride(Vec2I const& screenPosition) override;
|
|
||||||
|
|
||||||
bool openWithInventory() const;
|
bool openWithInventory() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
LuaCallbacks makePaneCallbacks();
|
LuaCallbacks makePaneCallbacks() override;
|
||||||
|
|
||||||
UniverseClientPtr m_client;
|
UniverseClientPtr m_client;
|
||||||
EntityId m_sourceEntityId;
|
EntityId m_sourceEntityId;
|
||||||
Json m_config;
|
|
||||||
|
|
||||||
GuiReader m_reader;
|
|
||||||
|
|
||||||
Map<CanvasWidgetPtr, String> m_canvasClickCallbacks;
|
|
||||||
Map<CanvasWidgetPtr, String> m_canvasKeyCallbacks;
|
|
||||||
|
|
||||||
LuaWorldComponent<LuaUpdatableComponent<LuaBaseComponent>> m_script;
|
|
||||||
|
|
||||||
List<pair<String, AudioInstancePtr>> m_playingSounds;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -234,6 +234,7 @@ SET (star_game_HEADERS
|
|||||||
scripting/StarConfigLuaBindings.hpp
|
scripting/StarConfigLuaBindings.hpp
|
||||||
scripting/StarEntityLuaBindings.hpp
|
scripting/StarEntityLuaBindings.hpp
|
||||||
scripting/StarFireableItemLuaBindings.hpp
|
scripting/StarFireableItemLuaBindings.hpp
|
||||||
|
scripting/StarInputLuaBindings.hpp
|
||||||
scripting/StarItemLuaBindings.hpp
|
scripting/StarItemLuaBindings.hpp
|
||||||
scripting/StarLuaActorMovementComponent.hpp
|
scripting/StarLuaActorMovementComponent.hpp
|
||||||
scripting/StarLuaAnimationComponent.hpp
|
scripting/StarLuaAnimationComponent.hpp
|
||||||
@ -472,6 +473,7 @@ SET (star_game_SOURCES
|
|||||||
scripting/StarConfigLuaBindings.cpp
|
scripting/StarConfigLuaBindings.cpp
|
||||||
scripting/StarEntityLuaBindings.cpp
|
scripting/StarEntityLuaBindings.cpp
|
||||||
scripting/StarFireableItemLuaBindings.cpp
|
scripting/StarFireableItemLuaBindings.cpp
|
||||||
|
scripting/StarInputLuaBindings.cpp
|
||||||
scripting/StarItemLuaBindings.cpp
|
scripting/StarItemLuaBindings.cpp
|
||||||
scripting/StarLuaComponents.cpp
|
scripting/StarLuaComponents.cpp
|
||||||
scripting/StarLuaGameConverters.cpp
|
scripting/StarLuaGameConverters.cpp
|
||||||
|
@ -5,6 +5,21 @@
|
|||||||
|
|
||||||
namespace Star {
|
namespace Star {
|
||||||
|
|
||||||
|
const char* InputBindingConfigRoot = "modBindings";
|
||||||
|
|
||||||
|
BiMap<Key, KeyMod> const KeysToMods{
|
||||||
|
{Key::LShift, KeyMod::LShift},
|
||||||
|
{Key::RShift, KeyMod::RShift},
|
||||||
|
{Key::LCtrl, KeyMod::LCtrl},
|
||||||
|
{Key::RCtrl, KeyMod::RCtrl},
|
||||||
|
{Key::LAlt, KeyMod::LAlt},
|
||||||
|
{Key::RAlt, KeyMod::RAlt},
|
||||||
|
{Key::LGui, KeyMod::LGui},
|
||||||
|
{Key::RGui, KeyMod::RGui},
|
||||||
|
{Key::AltGr, KeyMod::AltGr},
|
||||||
|
{Key::ScrollLock, KeyMod::Scroll}
|
||||||
|
};
|
||||||
|
|
||||||
const KeyMod KeyModOptional = KeyMod::Num | KeyMod::Caps | KeyMod::Scroll;
|
const KeyMod KeyModOptional = KeyMod::Num | KeyMod::Caps | KeyMod::Scroll;
|
||||||
|
|
||||||
inline bool compareKeyModLenient(KeyMod input, KeyMod test) {
|
inline bool compareKeyModLenient(KeyMod input, KeyMod test) {
|
||||||
@ -33,7 +48,7 @@ Json keyModsToJson(KeyMod mod) {
|
|||||||
if ((bool)(mod & KeyMod::AltGr )) array.emplace_back("AltGr" );
|
if ((bool)(mod & KeyMod::AltGr )) array.emplace_back("AltGr" );
|
||||||
if ((bool)(mod & KeyMod::Scroll)) array.emplace_back("Scroll");
|
if ((bool)(mod & KeyMod::Scroll)) array.emplace_back("Scroll");
|
||||||
|
|
||||||
return move(array);
|
return array.empty() ? Json() : move(array);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Optional pointer argument to output calculated priority
|
// Optional pointer argument to output calculated priority
|
||||||
@ -115,6 +130,8 @@ Json Input::inputEventToJson(InputEvent const& input) {
|
|||||||
|
|
||||||
Input::Bind Input::bindFromJson(Json const& json) {
|
Input::Bind Input::bindFromJson(Json const& json) {
|
||||||
Bind bind;
|
Bind bind;
|
||||||
|
if (json.isNull())
|
||||||
|
return bind;
|
||||||
|
|
||||||
String type = json.getString("type");
|
String type = json.getString("type");
|
||||||
Json value = json.get("value", {});
|
Json value = json.get("value", {});
|
||||||
@ -143,18 +160,22 @@ Input::Bind Input::bindFromJson(Json const& json) {
|
|||||||
|
|
||||||
Json Input::bindToJson(Bind const& bind) {
|
Json Input::bindToJson(Bind const& bind) {
|
||||||
if (auto keyBind = bind.ptr<KeyBind>()) {
|
if (auto keyBind = bind.ptr<KeyBind>()) {
|
||||||
return JsonObject{
|
auto obj = JsonObject{
|
||||||
{"type", "key"},
|
{"type", "key"},
|
||||||
{"value", KeyNames.getRight(keyBind->key)},
|
{"value", KeyNames.getRight(keyBind->key)}
|
||||||
{"mods", keyModsToJson(keyBind->mods)}
|
}; // don't want empty mods to exist as null entry
|
||||||
};
|
if (auto mods = keyModsToJson(keyBind->mods))
|
||||||
|
obj.emplace("mods", move(mods));
|
||||||
|
return move(obj);
|
||||||
}
|
}
|
||||||
else if (auto mouseBind = bind.ptr<MouseBind>()) {
|
else if (auto mouseBind = bind.ptr<MouseBind>()) {
|
||||||
return JsonObject{
|
auto obj = JsonObject{
|
||||||
{"type", "mouse"},
|
{"type", "mouse"},
|
||||||
{"value", MouseButtonNames.getRight(mouseBind->button)},
|
{"value", MouseButtonNames.getRight(mouseBind->button)}
|
||||||
{"mods", keyModsToJson(mouseBind->mods)}
|
|
||||||
};
|
};
|
||||||
|
if (auto mods = keyModsToJson(mouseBind->mods))
|
||||||
|
obj.emplace("mods", move(mods));
|
||||||
|
return move(obj);
|
||||||
}
|
}
|
||||||
else if (auto controllerBind = bind.ptr<ControllerBind>()) {
|
else if (auto controllerBind = bind.ptr<ControllerBind>()) {
|
||||||
return JsonObject{
|
return JsonObject{
|
||||||
@ -168,7 +189,7 @@ Json Input::bindToJson(Bind const& bind) {
|
|||||||
|
|
||||||
Input::BindEntry::BindEntry(String entryId, Json const& config, BindCategory const& parentCategory) {
|
Input::BindEntry::BindEntry(String entryId, Json const& config, BindCategory const& parentCategory) {
|
||||||
category = &parentCategory;
|
category = &parentCategory;
|
||||||
id = move(entryId);
|
id = entryId;
|
||||||
name = config.getString("name", id);
|
name = config.getString("name", id);
|
||||||
|
|
||||||
for (Json const& jBind : config.getArray("default", {})) {
|
for (Json const& jBind : config.getArray("default", {})) {
|
||||||
@ -179,13 +200,55 @@ Input::BindEntry::BindEntry(String entryId, Json const& config, BindCategory con
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Input::BindEntry::updated() {
|
||||||
|
auto config = Root::singleton().configuration();
|
||||||
|
|
||||||
|
JsonArray array;
|
||||||
|
for (auto const& bind : customBinds)
|
||||||
|
array.emplace_back(bindToJson(bind));
|
||||||
|
|
||||||
|
if (!config->get(InputBindingConfigRoot).isType(Json::Type::Object))
|
||||||
|
config->set(InputBindingConfigRoot, JsonObject());
|
||||||
|
|
||||||
|
String path = strf("{}.{}", InputBindingConfigRoot, category->id);
|
||||||
|
if (!config->getPath(path).isType(Json::Type::Object)) {
|
||||||
|
config->setPath(path, JsonObject{
|
||||||
|
{ id, move(array) }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
path = strf("{}.{}", path, id);
|
||||||
|
config->setPath(path, array);
|
||||||
|
}
|
||||||
|
|
||||||
|
Input::singleton().rebuildMappings();
|
||||||
|
}
|
||||||
|
|
||||||
|
Input::BindRef::BindRef(BindEntry& bindEntry, KeyBind& keyBind) {
|
||||||
|
entry = &bindEntry;
|
||||||
|
priority = keyBind.priority;
|
||||||
|
mods = keyBind.mods;
|
||||||
|
}
|
||||||
|
|
||||||
|
Input::BindRef::BindRef(BindEntry& bindEntry, MouseBind& mouseBind) {
|
||||||
|
entry = &bindEntry;
|
||||||
|
priority = mouseBind.priority;
|
||||||
|
mods = mouseBind.mods;
|
||||||
|
}
|
||||||
|
|
||||||
|
Input::BindRef::BindRef(BindEntry& bindEntry) {
|
||||||
|
entry = &bindEntry;
|
||||||
|
priority = 0;
|
||||||
|
mods = KeyMod::NoMod;
|
||||||
|
}
|
||||||
|
|
||||||
Input::BindCategory::BindCategory(String categoryId, Json const& categoryConfig) {
|
Input::BindCategory::BindCategory(String categoryId, Json const& categoryConfig) {
|
||||||
id = move(categoryId);
|
id = categoryId;
|
||||||
config = categoryConfig;
|
config = categoryConfig;
|
||||||
name = config.getString("name", id);
|
name = config.getString("name", id);
|
||||||
|
|
||||||
ConfigurationPtr userConfig = Root::singletonPtr()->configuration();
|
ConfigurationPtr userConfig = Root::singletonPtr()->configuration();
|
||||||
auto userBindings = userConfig->get("modBindings");
|
auto userBindings = userConfig->get(InputBindingConfigRoot);
|
||||||
|
|
||||||
for (auto& pair : config.getObject("binds", {})) {
|
for (auto& pair : config.getObject("binds", {})) {
|
||||||
String const& bindId = pair.first;
|
String const& bindId = pair.first;
|
||||||
@ -193,7 +256,7 @@ Input::BindCategory::BindCategory(String categoryId, Json const& categoryConfig)
|
|||||||
if (!bindConfig.isType(Json::Type::Object))
|
if (!bindConfig.isType(Json::Type::Object))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
BindEntry& entry = entries.insert(bindId, BindEntry(bindId, bindConfig, *this)).first->second;
|
BindEntry& entry = entries.try_emplace(bindId, bindId, bindConfig, *this).first->second;
|
||||||
|
|
||||||
if (userBindings.isType(Json::Type::Object)) {
|
if (userBindings.isType(Json::Type::Object)) {
|
||||||
for (auto& jBind : userBindings.queryArray(strf("{}.{}", id, bindId), {})) {
|
for (auto& jBind : userBindings.queryArray(strf("{}.{}", id, bindId), {})) {
|
||||||
@ -203,6 +266,9 @@ Input::BindCategory::BindCategory(String categoryId, Json const& categoryConfig)
|
|||||||
{ Logger::error("Binds: Error loading user bind in {}.{}: {}", id, bindId, e.what()); }
|
{ Logger::error("Binds: Error loading user bind in {}.{}: {}", id, bindId, e.what()); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (entry.customBinds.empty())
|
||||||
|
entry.customBinds = entry.defaultBinds;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -220,6 +286,34 @@ List<Input::BindEntry*> Input::filterBindEntries(List<Input::BindRef> const& bin
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Input::BindEntry* Input::bindEntryPtr(String const& categoryId, String const& bindId) {
|
||||||
|
if (auto category = m_bindCategories.ptr(categoryId)) {
|
||||||
|
if (auto entry = category->entries.ptr(bindId)) {
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
Input::BindEntry& Input::bindEntry(String const& categoryId, String const& bindId) {
|
||||||
|
if (auto ptr = bindEntryPtr(categoryId, bindId))
|
||||||
|
return *ptr;
|
||||||
|
else
|
||||||
|
throw InputException::format("Could not find bind entry {}.{}", categoryId, bindId);
|
||||||
|
}
|
||||||
|
|
||||||
|
Input::InputState* Input::bindStatePtr(String const& categoryId, String const& bindId) {
|
||||||
|
if (auto ptr = bindEntryPtr(categoryId, bindId))
|
||||||
|
return m_bindStates.ptr(ptr);
|
||||||
|
else
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
Input::InputState* Input::inputStatePtr(InputVariant key) {
|
||||||
|
return m_inputStates.ptr(key);
|
||||||
|
}
|
||||||
|
|
||||||
Input* Input::s_singleton;
|
Input* Input::s_singleton;
|
||||||
|
|
||||||
Input* Input::singletonPtr() {
|
Input* Input::singletonPtr() {
|
||||||
@ -239,6 +333,8 @@ Input::Input() {
|
|||||||
|
|
||||||
s_singleton = this;
|
s_singleton = this;
|
||||||
|
|
||||||
|
m_pressedMods = KeyMod::NoMod;
|
||||||
|
|
||||||
reload();
|
reload();
|
||||||
|
|
||||||
m_rootReloadListener = make_shared<CallbackListener>([&]() {
|
m_rootReloadListener = make_shared<CallbackListener>([&]() {
|
||||||
@ -256,10 +352,31 @@ List<std::pair<InputEvent, bool>> const& Input::inputEventsThisFrame() const {
|
|||||||
return m_inputEvents;
|
return m_inputEvents;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void Input::reset() {
|
void Input::reset() {
|
||||||
m_inputEvents.resize(0); // keeps reserved memory
|
m_inputEvents.resize(0); // keeps reserved memory
|
||||||
m_inputStates.clear();
|
{
|
||||||
m_bindStates.clear();
|
auto it = m_inputStates.begin();
|
||||||
|
while (it != m_inputStates.end()) {
|
||||||
|
if (it->second.held) {
|
||||||
|
it->second.reset();
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
else it = m_inputStates.erase(it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto it = m_bindStates.begin();
|
||||||
|
while (it != m_bindStates.end()) {
|
||||||
|
if (it->second.held) {
|
||||||
|
it->second.reset();
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
else it = m_bindStates.erase(it);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Input::update() {
|
void Input::update() {
|
||||||
@ -268,17 +385,83 @@ void Input::update() {
|
|||||||
|
|
||||||
bool Input::handleInput(InputEvent const& input, bool gameProcessed) {
|
bool Input::handleInput(InputEvent const& input, bool gameProcessed) {
|
||||||
m_inputEvents.emplace_back(input, gameProcessed);
|
m_inputEvents.emplace_back(input, gameProcessed);
|
||||||
|
if (auto keyDown = input.ptr<KeyDownEvent>()) {
|
||||||
|
auto keyToMod = KeysToMods.rightPtr(keyDown->key);
|
||||||
|
if (keyToMod)
|
||||||
|
m_pressedMods |= *keyToMod;
|
||||||
|
|
||||||
|
if (!gameProcessed && !m_textInputActive) {
|
||||||
|
m_inputStates[keyDown->key].press();
|
||||||
|
|
||||||
|
if (auto binds = m_bindMappings.ptr(keyDown->key)) {
|
||||||
|
for (auto bind : filterBindEntries(*binds, keyDown->mods))
|
||||||
|
m_bindStates[bind].press();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (auto keyUp = input.ptr<KeyUpEvent>()) {
|
||||||
|
auto keyToMod = KeysToMods.rightPtr(keyUp->key);
|
||||||
|
if (keyToMod)
|
||||||
|
m_pressedMods &= ~*keyToMod;
|
||||||
|
|
||||||
|
// We need to be able to release input even when gameProcessed is true, but only if it's already down.
|
||||||
|
if (auto state = m_inputStates.ptr(keyUp->key))
|
||||||
|
state->release();
|
||||||
|
|
||||||
|
if (auto binds = m_bindMappings.ptr(keyUp->key)) {
|
||||||
|
for (auto& bind : *binds) {
|
||||||
|
if (auto state = m_bindStates.ptr(bind.entry))
|
||||||
|
state->release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (auto mouseDown = input.ptr<MouseButtonDownEvent>()) {
|
||||||
|
if (!gameProcessed) {
|
||||||
|
m_inputStates[mouseDown->mouseButton].press();
|
||||||
|
|
||||||
|
if (auto binds = m_bindMappings.ptr(mouseDown->mouseButton)) {
|
||||||
|
for (auto bind : filterBindEntries(*binds, m_pressedMods))
|
||||||
|
m_bindStates[bind].press();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (auto mouseUp = input.ptr<MouseButtonUpEvent>()) {
|
||||||
|
if (auto state = m_inputStates.ptr(mouseUp->mouseButton))
|
||||||
|
state->release();
|
||||||
|
|
||||||
|
if (auto binds = m_bindMappings.ptr(mouseUp->mouseButton)) {
|
||||||
|
for (auto& bind : *binds) {
|
||||||
|
if (auto state = m_bindStates.ptr(bind.entry))
|
||||||
|
state->release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Input::rebuildMappings() {
|
void Input::rebuildMappings() {
|
||||||
|
reset();
|
||||||
m_bindMappings.clear();
|
m_bindMappings.clear();
|
||||||
|
|
||||||
|
for (auto& category : m_bindCategories) {
|
||||||
|
for (auto& pair : category.second.entries) {
|
||||||
|
auto& entry = pair.second;
|
||||||
|
for (auto& bind : entry.customBinds) {
|
||||||
|
if (auto keyBind = bind.ptr<KeyBind>())
|
||||||
|
m_bindMappings[keyBind->key].emplace_back(entry, *keyBind);
|
||||||
|
if (auto mouseBind = bind.ptr<MouseBind>())
|
||||||
|
m_bindMappings[mouseBind->button].emplace_back(entry, *mouseBind);
|
||||||
|
if (auto controllerBind = bind.ptr<ControllerBind>())
|
||||||
|
m_bindMappings[controllerBind->button].emplace_back(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& pair : m_bindMappings) {
|
||||||
|
pair.second.sort([](BindRef const& a, BindRef const& b)
|
||||||
|
{ return a.priority > b.priority; });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Input::reload() {
|
void Input::reload() {;
|
||||||
reset();
|
|
||||||
m_bindCategories.clear();
|
m_bindCategories.clear();
|
||||||
|
|
||||||
auto assets = Root::singleton().assets();
|
auto assets = Root::singleton().assets();
|
||||||
@ -290,7 +473,7 @@ void Input::reload() {
|
|||||||
if (!categoryConfig.isType(Json::Type::Object))
|
if (!categoryConfig.isType(Json::Type::Object))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
m_bindCategories.insert(categoryId, BindCategory(categoryId, categoryConfig));
|
m_bindCategories.try_emplace(categoryId, categoryId, categoryConfig);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -303,4 +486,65 @@ void Input::reload() {
|
|||||||
rebuildMappings();
|
rebuildMappings();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Input::setTextInputActive(bool active) {
|
||||||
|
m_textInputActive = active;
|
||||||
|
}
|
||||||
|
|
||||||
|
Maybe<unsigned> Input::bindDown(String const& categoryId, String const& bindId) {
|
||||||
|
if (auto state = bindStatePtr(categoryId, bindId))
|
||||||
|
if (state->presses)
|
||||||
|
return state->presses;
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Input::bindHeld(String const& categoryId, String const& bindId) {
|
||||||
|
if (auto state = bindStatePtr(categoryId, bindId))
|
||||||
|
return state->held;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Maybe<unsigned> Input::bindUp(String const& categoryId, String const& bindId) {
|
||||||
|
if (auto state = bindStatePtr(categoryId, bindId))
|
||||||
|
if (state->releases)
|
||||||
|
return state->releases;
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
void Input::resetBinds(String const& categoryId, String const& bindId) {
|
||||||
|
auto& entry = bindEntry(categoryId, bindId);
|
||||||
|
|
||||||
|
entry.customBinds = entry.defaultBinds;
|
||||||
|
entry.updated();
|
||||||
|
}
|
||||||
|
|
||||||
|
Json Input::getDefaultBinds(String const& categoryId, String const& bindId) {
|
||||||
|
JsonArray array;
|
||||||
|
for (Bind const& bind : bindEntry(categoryId, bindId).defaultBinds)
|
||||||
|
array.emplace_back(bindToJson(bind));
|
||||||
|
|
||||||
|
return move(array);
|
||||||
|
}
|
||||||
|
|
||||||
|
Json Input::getBinds(String const& categoryId, String const& bindId) {
|
||||||
|
JsonArray array;
|
||||||
|
for (Bind const& bind : bindEntry(categoryId, bindId).customBinds)
|
||||||
|
array.emplace_back(bindToJson(bind));
|
||||||
|
|
||||||
|
return move(array);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Input::setBinds(String const& categoryId, String const& bindId, Json const& jBinds) {
|
||||||
|
auto& entry = bindEntry(categoryId, bindId);
|
||||||
|
|
||||||
|
List<Bind> binds;
|
||||||
|
for (Json const& jBind : jBinds.toArray())
|
||||||
|
binds.emplace_back(bindFromJson(jBind));
|
||||||
|
|
||||||
|
entry.customBinds = move(binds);
|
||||||
|
entry.updated();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -69,20 +69,17 @@ namespace Star {
|
|||||||
List<Bind> customBinds;
|
List<Bind> customBinds;
|
||||||
|
|
||||||
BindEntry(String entryId, Json const& config, BindCategory const& parentCategory);
|
BindEntry(String entryId, Json const& config, BindCategory const& parentCategory);
|
||||||
|
void updated();
|
||||||
};
|
};
|
||||||
|
|
||||||
struct BindRef {
|
struct BindRef {
|
||||||
KeyMod mods;
|
KeyMod mods;
|
||||||
uint8_t priority;
|
uint8_t priority = 0;
|
||||||
|
BindEntry* entry = nullptr; // Invalidated on reload, careful!
|
||||||
|
|
||||||
// Invalidated on reload, careful!
|
struct BindRef(BindEntry& bindEntry, KeyBind& keyBind);
|
||||||
BindEntry* entry;
|
struct BindRef(BindEntry& bindEntry, MouseBind& mouseBind);
|
||||||
};
|
struct BindRef(BindEntry& bindEntry);
|
||||||
|
|
||||||
struct BindRefSorter {
|
|
||||||
inline bool operator() (BindRef const& a, BindRef const& b) {
|
|
||||||
return a.priority > b.priority;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct BindCategory {
|
struct BindCategory {
|
||||||
@ -90,14 +87,17 @@ namespace Star {
|
|||||||
String name;
|
String name;
|
||||||
Json config;
|
Json config;
|
||||||
|
|
||||||
StringMap<BindEntry> entries;
|
StableHashMap<String, BindEntry> entries;
|
||||||
|
|
||||||
BindCategory(String categoryId, Json const& categoryConfig);
|
BindCategory(String categoryId, Json const& categoryConfig);
|
||||||
};
|
};
|
||||||
|
|
||||||
struct InputState {
|
struct InputState {
|
||||||
size_t presses = 0;
|
unsigned presses = 0;
|
||||||
size_t releases = 0;
|
unsigned releases = 0;
|
||||||
|
bool pressed = false;
|
||||||
|
bool held = false;
|
||||||
|
bool released = false;
|
||||||
|
|
||||||
// Calls the passed functions for each press and release.
|
// Calls the passed functions for each press and release.
|
||||||
template <typename PressFunction, typename ReleaseFunction>
|
template <typename PressFunction, typename ReleaseFunction>
|
||||||
@ -108,9 +108,13 @@ namespace Star {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr bool held() {
|
inline void reset() {
|
||||||
return presses < releases;
|
presses = releases = 0;
|
||||||
|
pressed = released = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline void press() { pressed = ++presses; held = true; }
|
||||||
|
inline void release() { released = ++releases; held = false; }
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get pointer to the singleton root instance, if it exists. Otherwise,
|
// Get pointer to the singleton root instance, if it exists. Otherwise,
|
||||||
@ -141,13 +145,30 @@ namespace Star {
|
|||||||
|
|
||||||
// Loads input categories and their binds from Assets.
|
// Loads input categories and their binds from Assets.
|
||||||
void reload();
|
void reload();
|
||||||
|
|
||||||
|
void setTextInputActive(bool active);
|
||||||
|
|
||||||
|
Maybe<unsigned> bindDown(String const& categoryId, String const& bindId);
|
||||||
|
bool bindHeld(String const& categoryId, String const& bindId);
|
||||||
|
Maybe<unsigned> bindUp (String const& categoryId, String const& bindId);
|
||||||
|
|
||||||
|
void resetBinds(String const& categoryId, String const& bindId);
|
||||||
|
void setBinds(String const& categoryId, String const& bindId, Json const& binds);
|
||||||
|
Json getDefaultBinds(String const& categoryId, String const& bindId);
|
||||||
|
Json getBinds(String const& categoryId, String const& bindId);
|
||||||
private:
|
private:
|
||||||
List<BindEntry*> filterBindEntries(List<BindRef> const& binds, KeyMod mods) const;
|
List<BindEntry*> filterBindEntries(List<BindRef> const& binds, KeyMod mods) const;
|
||||||
|
|
||||||
|
BindEntry* bindEntryPtr(String const& categoryId, String const& bindId);
|
||||||
|
BindEntry& bindEntry(String const& categoryId, String const& bindId);
|
||||||
|
|
||||||
|
InputState* bindStatePtr(String const& categoryId, String const& bindId);
|
||||||
|
InputState* inputStatePtr(InputVariant key);
|
||||||
|
|
||||||
static Input* s_singleton;
|
static Input* s_singleton;
|
||||||
|
|
||||||
// Regenerated on reload.
|
// Regenerated on reload.
|
||||||
StringMap<BindCategory> m_bindCategories;
|
StableHashMap<String, BindCategory> m_bindCategories;
|
||||||
// Contains raw pointers to bind entries in categories, so also regenerated on reload.
|
// Contains raw pointers to bind entries in categories, so also regenerated on reload.
|
||||||
HashMap<InputVariant, List<BindRef>> m_bindMappings;
|
HashMap<InputVariant, List<BindRef>> m_bindMappings;
|
||||||
|
|
||||||
@ -160,7 +181,10 @@ namespace Star {
|
|||||||
//Input states
|
//Input states
|
||||||
HashMap<InputVariant, InputState> m_inputStates;
|
HashMap<InputVariant, InputState> m_inputStates;
|
||||||
//Bind states
|
//Bind states
|
||||||
HashMap<BindEntry*, InputState> m_bindStates;
|
HashMap<BindEntry const*, InputState> m_bindStates;
|
||||||
|
|
||||||
|
KeyMod m_pressedMods;
|
||||||
|
bool m_textInputActive;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
36
source/game/scripting/StarInputLuaBindings.cpp
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
#include "StarInputLuaBindings.hpp"
|
||||||
|
#include "StarLuaGameConverters.hpp"
|
||||||
|
#include "StarInput.hpp"
|
||||||
|
|
||||||
|
namespace Star {
|
||||||
|
|
||||||
|
LuaCallbacks LuaBindings::makeInputCallbacks() {
|
||||||
|
LuaCallbacks callbacks;
|
||||||
|
|
||||||
|
auto input = Input::singletonPtr();
|
||||||
|
|
||||||
|
callbacks.registerCallbackWithSignature<Maybe<unsigned>, String, String>("bindDown", bind(mem_fn(&Input::bindDown), input, _1, _2));
|
||||||
|
callbacks.registerCallbackWithSignature<bool, String, String>("bindHeld", bind(mem_fn(&Input::bindHeld), input, _1, _2));
|
||||||
|
callbacks.registerCallbackWithSignature<Maybe<unsigned>, String, String>("bindUp", bind(mem_fn(&Input::bindUp), input, _1, _2));
|
||||||
|
|
||||||
|
callbacks.registerCallbackWithSignature<void, String, String>("resetBinds", bind(mem_fn(&Input::resetBinds), input, _1, _2));
|
||||||
|
callbacks.registerCallbackWithSignature<void, String, String, Json>("setBinds", bind(mem_fn(&Input::setBinds), input, _1, _2, _3));
|
||||||
|
callbacks.registerCallbackWithSignature<Json, String, String>("getDefaultBinds", bind(mem_fn(&Input::getDefaultBinds), input, _1, _2));
|
||||||
|
callbacks.registerCallbackWithSignature<Json, String, String>("getBinds", bind(mem_fn(&Input::getBinds), input, _1, _2));
|
||||||
|
|
||||||
|
callbacks.registerCallback("events", [input]() -> Json {
|
||||||
|
JsonArray result;
|
||||||
|
|
||||||
|
for (auto& pair : input->inputEventsThisFrame()) {
|
||||||
|
if (auto jEvent = Input::inputEventToJson(pair.first))
|
||||||
|
result.emplace_back(jEvent.set("processed", pair.second));
|
||||||
|
}
|
||||||
|
|
||||||
|
return move(result);
|
||||||
|
});
|
||||||
|
|
||||||
|
return callbacks;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
17
source/game/scripting/StarInputLuaBindings.hpp
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
#ifndef STAR_INPUT_LUA_BINDINGS_HPP
|
||||||
|
#define STAR_INPUT_LUA_BINDINGS_HPP
|
||||||
|
|
||||||
|
#include "StarGameTypes.hpp"
|
||||||
|
#include "StarLua.hpp"
|
||||||
|
|
||||||
|
namespace Star {
|
||||||
|
|
||||||
|
STAR_CLASS(Input);
|
||||||
|
|
||||||
|
namespace LuaBindings {
|
||||||
|
LuaCallbacks makeInputCallbacks();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
@ -6,6 +6,7 @@
|
|||||||
#include "StarListener.hpp"
|
#include "StarListener.hpp"
|
||||||
#include "StarWorld.hpp"
|
#include "StarWorld.hpp"
|
||||||
#include "StarWorldLuaBindings.hpp"
|
#include "StarWorldLuaBindings.hpp"
|
||||||
|
#include "StarInputLuaBindings.hpp"
|
||||||
|
|
||||||
namespace Star {
|
namespace Star {
|
||||||
|
|
||||||
@ -282,6 +283,11 @@ void LuaWorldComponent<Base>::init(World* world) {
|
|||||||
|
|
||||||
Base::setLuaRoot(world->luaRoot());
|
Base::setLuaRoot(world->luaRoot());
|
||||||
Base::addCallbacks("world", LuaBindings::makeWorldCallbacks(world));
|
Base::addCallbacks("world", LuaBindings::makeWorldCallbacks(world));
|
||||||
|
|
||||||
|
if (world->isClient()) {
|
||||||
|
Base::addCallbacks("input", LuaBindings::makeInputCallbacks());
|
||||||
|
}
|
||||||
|
|
||||||
Base::init();
|
Base::init();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -289,6 +295,8 @@ template <typename Base>
|
|||||||
void LuaWorldComponent<Base>::uninit() {
|
void LuaWorldComponent<Base>::uninit() {
|
||||||
Base::uninit();
|
Base::uninit();
|
||||||
Base::removeCallbacks("world");
|
Base::removeCallbacks("world");
|
||||||
|
|
||||||
|
Base::removeCallbacks("input");
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Base>
|
template <typename Base>
|
||||||
|