3007 lines
97 KiB
Plaintext
3007 lines
97 KiB
Plaintext
|
|
||
|
--[[
|
||
|
RavenAcsendant
|
||
|
25MAY2021
|
||
|
Anommaly Mod Configuration Menu (Anomaly MCM)
|
||
|
|
||
|
|
||
|
+Modified version of the Anomaly Options menu created by Tronex. It retains all the same features.
|
||
|
+Dedicated space for mod options (As of 1_4 this includes easy save game specific options storage)
|
||
|
+Dynamicly loads options from mod scripts at main menu load
|
||
|
simplifying the process and alowing it to be done before a save is loaded
|
||
|
without needing to edit a game script. There by reducing conflicts.
|
||
|
|
||
|
+Change History:
|
||
|
1_1 added options table passed by referance
|
||
|
1_2 Fixed a crash that is actualy still part of ui_options
|
||
|
1_3 Added overide controls for option tree/branch text see the tutorial section "Parameters of option Tree" at the end of this sctipt
|
||
|
1_4 Integrated dph-hcl's script for save game specific MCM options. See "Tutorial: Using : dph-hcl's script for save game specific MCM options"
|
||
|
1_5 Pass value to functors
|
||
|
1.6.0 Adopted https://semver.org/spec/v1.0.0.html versioning spec for reasons.
|
||
|
Far left list sorted alphabeticaly, except MCM will always be at the top to serve as a landing page.
|
||
|
Added logging utility see instruction in mcm_log.script
|
||
|
Now pass true to on_option_change callback to make it posible to identify when it is coming from mcm
|
||
|
MCM tracks a session ID that only increments when game exe is launched. ui_mcm.get_session_id()
|
||
|
Added assert to identify missing val fields as that tends to result in hard to diagnose errors.
|
||
|
Added assert to block calling get() while mcm is building the options tree.
|
||
|
Added keybind widget
|
||
|
includes a simple conflict list and all keybinds list under mcm's tree.
|
||
|
Added utility functions to support key binds.
|
||
|
Tracking of held status of shift controls and alt keys with single functon to read all three and a no modifier state
|
||
|
Functions to help identify single, double and long presses of a key.
|
||
|
Provided templated mcm option tables for selecting modifier keys and single/double and long presses that include translation strings.
|
||
|
Updated documentation at end of this sctipt for all keybind related features.
|
||
|
1.6.1 Fixed xrdebug 1040 crash.
|
||
|
1.6.2 Added a message for when io.open fails to create a file.
|
||
|
Made ui_mcm.script not fail if mcm_log.script is missing.
|
||
|
Made debug logging default to false as orginaly intended.
|
||
|
1.6.3 Fixed interferance between doubletap and single press when bonund to same key.
|
||
|
1.6.4 Missed one place in above fix.
|
||
|
1.6.5 Added support for unbound MCM keybinds
|
||
|
pressing ESC while binding a key will unbind the key, clicking will still cancel the bind process
|
||
|
-1 is the value for unbound
|
||
|
unbound keys will not be reported as conflicting
|
||
|
added mouse4-8 as valid keybinds.
|
||
|
updated mcm_log to 1.0.2 fixing a crash on quit.
|
||
|
chnaged the doubletap window to be 200ms. down from 500. this will make mcm keybinds feel more responsive. made it adjustable by increments of 5
|
||
|
special handeling for key wrappers in the keybinds list
|
||
|
fixed a bug in dph-hcl's script for save game specific MCM options (HarukaSai)
|
||
|
1.6.6 actually updated mcm_log to 1.0.2 fixing a crash on quit.
|
||
|
|
||
|
1.7.0 Update to Anomaly 1.5.3 earlier versions of anomaly will not be supported due to the stalker games EULA changes.
|
||
|
Included support for Catspaw's ui hooks
|
||
|
Adds new custom functors "ui_hook_functor" and "on_selection_functor", which respectively pass along UI element handlers and a trap for unsaved value changes.
|
||
|
These functors allow for dynamic customizations to MCM's UI elements at the element container level in response to user interactions.
|
||
|
See the tutorial section on "UI Functors" for more information.
|
||
|
Fixed a typo "dispaly_key" changed to "display_key", legacy name aliased for compatablity.
|
||
|
MCM get(id) will no longer read values from axr_options for settings that are not part of the curent options table
|
||
|
lacking a defined value type the data returned was always a string, orphaned settings values are also likely to be garbage
|
||
|
nil will be returned instead, you need to handel this.
|
||
|
Mostly this was an issue when addon A was reading addon B's settings and addon B had been uninstalled
|
||
|
these values will still exist in axr_options and can be read directly if needed.
|
||
|
Due to degree of code chnages steps have been taken to invalidate any monkey patches of the MCM UI.
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
Tronex
|
||
|
2019/10/12
|
||
|
Anomlous Options Menu
|
||
|
|
||
|
Features:
|
||
|
- %100 customizable, support for all kind of xml elements
|
||
|
- Highlight pending changes
|
||
|
- Option description support
|
||
|
- Option presets support
|
||
|
- Reset button to clear pending changes
|
||
|
- Script callback on option changes
|
||
|
- Functors capability to excute on apply or init
|
||
|
- Precoditions capability to hide/show/execute functors
|
||
|
|
||
|
To get an option value inside other scripts, use: ui_mcm.get(parameter)
|
||
|
Check the options table here to see the values used
|
||
|
|
||
|
See the tutorial at the bottom for adding or modifying options table
|
||
|
|
||
|
--]]
|
||
|
|
||
|
VERSION = "1.7.0"
|
||
|
MCM_DEBUG = axr_main.config:r_value("mcm", "mcm/mcm_log/debug_logging2", 1, false)
|
||
|
local enable_debug_prints = false
|
||
|
local enable_ui_functors = true -- feature killswitch
|
||
|
|
||
|
------------------------------------------------------------
|
||
|
-- Strings and LTX management
|
||
|
------------------------------------------------------------
|
||
|
local ini_pres = ini_file("presets\\includes.ltx")
|
||
|
local ini_loc = ini_file_ex("localization.ltx")
|
||
|
local ini_mcm_key = ini_file_ex("mcm_key_localization.ltx")
|
||
|
local _opt_ = "/" -- For axr_options.ltx, don't touch!
|
||
|
local opt_section = "mcm" -- For axr_options.ltx, don't touch!
|
||
|
local opt_str = "ui_mcm_" -- Option name: "ui_mcm_(option ID)" -- Option description: "ui_mcm_(option ID)_desc"
|
||
|
local opt_str_menu = "ui_mcm_menu_" -- Option menu: "ui_mcm_menu_(option menu ID)"
|
||
|
local opt_str_prst = "ui_mcm_prst_" -- Option preset: "ui_mcm_prst_(option preset ID)"
|
||
|
local opt_str_lst = "ui_mcm_lst_" -- List/Radio keys: "ui_mcm_lst_(key)"
|
||
|
function cc(path,opt) return (path .. _opt_ .. opt) end
|
||
|
|
||
|
------------------------------------------------------------
|
||
|
-- Utilities
|
||
|
------------------------------------------------------------
|
||
|
|
||
|
local session_id = nil
|
||
|
local m_floor, m_ceil, s_find, s_gsub = math.floor, math.ceil, string.find, string.gsub
|
||
|
local clr, clamp, round_idp, str_explode = utils_xml.get_color, clamp, round_idp, str_explode
|
||
|
local precision = 6 -- allowed number of zeros
|
||
|
local width_factor = utils_xml.is_widescreen() and 0.8 or 1
|
||
|
local clr_o = GetARGB(255, 250, 150, 75)
|
||
|
local clr_g1 = GetARGB(255, 170, 170, 170)
|
||
|
local clr_g2 = GetARGB(255, 200, 200, 200)
|
||
|
local clr_w = GetARGB(255, 255, 255, 255)
|
||
|
local clr_r = GetARGB(255, 225, 0, 0)
|
||
|
local clr_tree = {
|
||
|
[1] = GetARGB(255, 180, 180, 180),
|
||
|
[2] = GetARGB(255, 180, 180, 180),
|
||
|
[3] = GetARGB(255, 180, 180, 180),
|
||
|
}
|
||
|
|
||
|
local dbg = nil
|
||
|
function print_dbg(...)
|
||
|
if (not dbg) and MCM_DEBUG and mcm_log then
|
||
|
dbg = mcm_log.new("DBG")
|
||
|
dbg.enabled = MCM_DEBUG
|
||
|
dbg.continuous = true
|
||
|
end
|
||
|
if dbg then
|
||
|
dbg:log(...)
|
||
|
end
|
||
|
|
||
|
if enable_debug_prints then
|
||
|
printf(...)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function ui_functors_enabled()
|
||
|
-- for scripts to explicitly check for support
|
||
|
return enable_ui_functors
|
||
|
end
|
||
|
|
||
|
------------------------------------------------------------
|
||
|
-- Options
|
||
|
------------------------------------------------------------
|
||
|
options = {}
|
||
|
local opt_temp = {} -- stores pending changes
|
||
|
local opt_backup = {} -- do a backup of original changes for comparison with pendings
|
||
|
local opt_index = {} -- option index by path, so we can locate an option fast without iterating throw the whole options table
|
||
|
local opt_val = {} -- option value type by path, to execute proper functions on different type without iterating throw the whole options table
|
||
|
local allowed_type = { -- ignore tables from these types in temp tables
|
||
|
["check"] = true,
|
||
|
["list"] = true,
|
||
|
["input"] = true,
|
||
|
["radio_h"] = true,
|
||
|
["radio_v"] = true,
|
||
|
["track"] = true,
|
||
|
["key_bind"] = true,
|
||
|
|
||
|
|
||
|
}
|
||
|
local key_by_path = {}
|
||
|
local paths_by_key = {}
|
||
|
local gathering = false
|
||
|
|
||
|
|
||
|
AddScriptCallback("mcm_option_change")
|
||
|
AddScriptCallback("mcm_option_reset")
|
||
|
AddScriptCallback("mcm_option_restore_default")
|
||
|
AddScriptCallback("mcm_option_discard")
|
||
|
|
||
|
|
||
|
function init_opt_base()
|
||
|
print_dbg("MCM options reset.")
|
||
|
get_session_id()
|
||
|
options = {
|
||
|
{ id= "mcm" , gr={
|
||
|
{id = "mcm_about", sh=true , gr={
|
||
|
{ id= "slide_mcm" ,type= "slide" ,link= "AMCM_Banner.dds" ,text= "ui_mcm_menu_mcm_about" ,size= {512,50} },
|
||
|
{ id= "desc_mcm" ,type= "desc" ,text= "ui_mcm_desc_mcm" ,clr= {255,125,175,200} },
|
||
|
{ id= "desc_mcm2" ,type= "desc" ,text= "ui_mcm_desc_mcm2" ,clr= {255,125,175,200} },
|
||
|
--{ id="reset" ,type= "check" ,val= 1 ,def= false , functor = {reload } },
|
||
|
},},
|
||
|
{id = "mcm_kb", gr={
|
||
|
{id = "mcm_kb_main", sh=true ,gr={
|
||
|
{ id= "slide_kb" ,type= "slide" ,link= "AMCM_Banner.dds" ,text= "ui_mcm_menu_mcm_kb" ,size= {512,50} },
|
||
|
{ id= "desc_kb" ,type= "desc" ,text= "ui_mcm_mcm_desc_kb" ,clr= {255,125,175,200} },
|
||
|
{ id= "desc_kb2" ,type= "desc" ,text= "ui_mcm_mcm_desc_kb2" ,clr= {255,125,175,200} },
|
||
|
{id = "dtaptime2", type = "track", val = 2, def = 200, min = 100, max = 750, step = 5},
|
||
|
{id = "presstime", type = "track", val = 2, def = 1.5, min = 1.2, max = 2, step = 0.01, prec = 2},
|
||
|
|
||
|
},},
|
||
|
{id = "mcm_all_kb", sh=true , gr={
|
||
|
{ id= "slide_kb" ,type= "slide" ,link= "AMCM_Banner.dds" ,text= "ui_mcm_menu_mcm_all_kb" ,size= {512,50} },
|
||
|
{ id= "desc_kb" ,type= "desc" ,text= "ui_mcm_mcm_desc_kb_all" ,clr= {255,125,175,200} },
|
||
|
{ id= "desc_kb2" ,type= "desc" ,text= "ui_mcm_mcm_desc_kb_all2" ,clr= {255,125,175,200} },
|
||
|
},},
|
||
|
{id = "mcm_kb_conflicts", sh=true , gr={
|
||
|
{ id= "slide_kb" ,type= "slide" ,link= "AMCM_Banner.dds" ,text= "ui_mcm_menu_mcm_kb_conflicts" ,size= {512,50} },
|
||
|
{ id= "desc_kb" ,type= "desc" ,text= "ui_mcm_mcm_desc_kb_conflicts" ,clr= {255,125,175,200} },
|
||
|
{ id= "desc_kb2" ,type= "desc" ,text= "ui_mcm_mcm_desc_kb_all2" ,clr= {255,125,175,200} },
|
||
|
},},
|
||
|
|
||
|
|
||
|
},},
|
||
|
{id = "mcm_log", sh=true , gr={
|
||
|
{ id= "slide_log" ,type= "slide" ,link= "AMCM_Banner.dds" ,text= "ui_mcm_menu_mcm_log" ,size= {512,50} },
|
||
|
{ id= "desc_log" ,type= "desc" ,text= "ui_mcm_mcm_desc_log" ,clr= {255,125,175,200} },
|
||
|
{id = "enable", type = "check", val = 1, def = true},
|
||
|
{id = "numlogs", type = "track", val = 2, def = 2, min = 1, max = 10, step = 1},
|
||
|
{id = "continuous", type = "check", val = 1, def = false},
|
||
|
{id = "savefreq", type = "input", val = 2, def = 1000, min = 100, max = 1000000000000000},
|
||
|
{id = "timestamp", type = "input", val = 2, def = 1000, min = 1, max = 1000000000000000},
|
||
|
{id = "debug_logging", type = "line"},
|
||
|
{id = "debug_logging2", type = "check", val = 1, def = false},
|
||
|
},},
|
||
|
|
||
|
},
|
||
|
}
|
||
|
}
|
||
|
local kb_meta = {all = {}, conflict = {}}
|
||
|
gathering = true
|
||
|
gather_options()
|
||
|
gathering = false
|
||
|
table.sort(options, function(a,b)
|
||
|
if a.id == "mcm" then return true end
|
||
|
if b.id == "mcm" then return false end
|
||
|
return game.translate_string(a.text or opt_str_menu .. a.id) < game.translate_string(b.text or opt_str_menu .. b.id)
|
||
|
end)
|
||
|
|
||
|
init_opt_coder(kb_meta)
|
||
|
|
||
|
table.sort(kb_meta.conflict, function(a,b) return display_key(get(a.kb_path)) < display_key(get(b.kb_path)) end)
|
||
|
for i = 1, #kb_meta.all do
|
||
|
options[1].gr[2].gr[2].gr[3+i] = kb_meta.all[i]
|
||
|
options[1].gr[2].gr[3].gr[3+i] = kb_meta.conflict[i]
|
||
|
end
|
||
|
|
||
|
|
||
|
init_opt_coder()
|
||
|
MCM_DEBUG = get("mcm/mcm_log/debug_logging2")
|
||
|
end
|
||
|
|
||
|
|
||
|
function init_opt_coder(kb_meta)
|
||
|
local keybind_count = 0
|
||
|
------------------------------------------------------------------------
|
||
|
-- Coding options
|
||
|
local function code_option(gr, id, num)
|
||
|
local path
|
||
|
for i=1,#gr do
|
||
|
if allowed_type[gr[i].type] then
|
||
|
path = cc(id , gr[i].id)
|
||
|
opt_index[path] = cc(num , i)
|
||
|
opt_val[path] = assert(gr[i].val, "val is manditory! option path:"..path)
|
||
|
--printf("-[%s] | index: %s - type: %s", path, opt_index[path], opt_val[path])
|
||
|
if gr[i].type == "key_bind" and kb_meta then
|
||
|
keybind_count = keybind_count + 1
|
||
|
update_conflict(path, get(path))
|
||
|
local temp = dup_table(gr[i])
|
||
|
temp.id = temp.id .. "_"..keybind_count
|
||
|
temp.kb_path = path
|
||
|
temp.curr = {get,path}
|
||
|
temp.hint = temp.hint or id.."_"..gr[i].id
|
||
|
temp.functor = {function(p,v) set(p,v) update_conflict(p,v) end, path }
|
||
|
if path:find("mcm/key_wrapper/") then
|
||
|
temp.precondition = {function() return false end} --temp.precondition = {get, path:sub(1, -).."enable"}
|
||
|
end
|
||
|
kb_meta.all[#kb_meta.all+1] = dup_table(temp)
|
||
|
temp.precondition = {get_conflict, path}
|
||
|
kb_meta.conflict[#kb_meta.conflict+1] = dup_table(temp)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local id_1, id_2, id_3
|
||
|
-- Level 1
|
||
|
for i=1,#options do
|
||
|
id_1 = options[i].id
|
||
|
if options[i].sh then
|
||
|
code_option(options[i].gr, id_1, i)
|
||
|
else
|
||
|
-- Level 2
|
||
|
for ii=1,#options[i].gr do
|
||
|
id_2 = options[i].gr[ii].id
|
||
|
if options[i].gr[ii].sh then
|
||
|
code_option( (options[i].gr[ii].gr), (id_1 .._opt_.. id_2), (i .._opt_.. ii) )
|
||
|
else
|
||
|
-- Level 3
|
||
|
for iii=1,#options[i].gr[ii].gr do
|
||
|
id_3 = options[i].gr[ii].gr[iii].id
|
||
|
if options[i].gr[ii].gr[iii].sh then
|
||
|
code_option( (options[i].gr[ii].gr[iii].gr), (id_1 .._opt_.. id_2 .._opt_.. id_3), (i .._opt_.. ii .._opt_.. iii) )
|
||
|
else
|
||
|
----
|
||
|
end
|
||
|
end
|
||
|
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
end
|
||
|
|
||
|
function gather_options()
|
||
|
-- this is modified from how axr_main loads all the other scripts thanks to Igi for the idea.
|
||
|
local ignore = {
|
||
|
["ui_mcm.script"] = true,
|
||
|
|
||
|
}
|
||
|
|
||
|
local t = {}
|
||
|
local size_t = 0
|
||
|
local f = getFS()
|
||
|
local flist = f:file_list_open_ex("$game_scripts$",bit_or(FS.FS_ListFiles,FS.FS_RootOnly),"*mcm.script")
|
||
|
local f_cnt = flist:Size()
|
||
|
for it=0, f_cnt-1 do
|
||
|
local file = flist:GetAt(it)
|
||
|
local file_name = file:NameShort()
|
||
|
--printf("%s size=%s",file_name,file:Size())
|
||
|
if (file:Size() > 0 and ignore[file_name] ~= true) then
|
||
|
file_name = file_name:sub(0,file_name:len()-7)
|
||
|
if (_G[file_name] and _G[file_name].on_mcm_load) then
|
||
|
size_t = size_t + 1
|
||
|
t[size_t] = file_name -- load all scripts first
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
for i=1,#t do
|
||
|
--printf("%s.on_game_start()",t[i])
|
||
|
local temp = nil
|
||
|
temp, name = _G[ t[i] ].on_mcm_load(options) -- passing options by referance added in AMCM 1.1 if you use this, nil check, emphasise updating MCM in your mod desc or something
|
||
|
if temp and name then
|
||
|
local collect = false
|
||
|
for j=1, #options do
|
||
|
if options[j].id == name then
|
||
|
collect = j
|
||
|
end
|
||
|
end
|
||
|
if not collect then
|
||
|
collection = { id = name , gr = {}}
|
||
|
table.insert(collection.gr, temp)
|
||
|
table.insert(options, collection)
|
||
|
else
|
||
|
table.insert(options[collect].gr, temp)
|
||
|
end
|
||
|
elseif temp then
|
||
|
table.insert(options, temp)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
end
|
||
|
|
||
|
|
||
|
------------------------------------------------------------
|
||
|
-- Functors
|
||
|
------------------------------------------------------------
|
||
|
|
||
|
-- Special
|
||
|
local function empty_functor()
|
||
|
print_dbg("Empty functor called!")
|
||
|
end
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
function reload()
|
||
|
set("mcm/reset", false)
|
||
|
init_opt_base()
|
||
|
end
|
||
|
|
||
|
-- Preconditions
|
||
|
function level_present()
|
||
|
return level.present()
|
||
|
end
|
||
|
function debug_only()
|
||
|
return DEV_DEBUG
|
||
|
end
|
||
|
function for_renderer(...)
|
||
|
local rend = {...}
|
||
|
local curr_rend = get_console_cmd(0, "renderer")
|
||
|
local result = false
|
||
|
for i=1,#rend do
|
||
|
result = result or curr_rend == rend[i]
|
||
|
end
|
||
|
return result
|
||
|
end
|
||
|
|
||
|
--Key bind stuff
|
||
|
local function set_conflict(ck)
|
||
|
local conflict = nil
|
||
|
for k, v in pairs(paths_by_key[ck]) do
|
||
|
if k ~= "conflict" then
|
||
|
conflict = conflict or conflict == false or false --if it is true it stays true, if false it becomes true if nil it becomes false. 0 entries = nil, 1 entry = false, more than 1 entry = true
|
||
|
end
|
||
|
end
|
||
|
paths_by_key[ck].conflict = conflict
|
||
|
print_dbg("set conflict ck:%s con:%s",ck,conflict)
|
||
|
end
|
||
|
|
||
|
function update_conflict(path, key)
|
||
|
key = tonumber(key)
|
||
|
local old_key = key_by_path[path]
|
||
|
print_dbg("path:%s, key:%s, old:%s", path, key, old_key)
|
||
|
|
||
|
if key == old_key then return end
|
||
|
key_by_path[path] = key
|
||
|
if old_key then
|
||
|
paths_by_key[old_key][path] = nil
|
||
|
if paths_by_key[old_key].conflict then -- if this key used to have a conflict see if it can be clered
|
||
|
set_conflict(old_key)
|
||
|
end
|
||
|
end
|
||
|
if not paths_by_key[key] then
|
||
|
paths_by_key[key] = {}
|
||
|
end
|
||
|
paths_by_key[key][path] = true
|
||
|
if not paths_by_key[key].conflict then --if this key didn't have a conflict see if it does now
|
||
|
set_conflict(key)
|
||
|
end
|
||
|
|
||
|
end
|
||
|
|
||
|
function get_conflict(path, key)
|
||
|
local k = key or key_by_path[path]
|
||
|
print_dbg("path:%s, key:%s, k:%s", path, type(key), type(k))
|
||
|
assert(k, "No key found for path")
|
||
|
k = tonumber(k)
|
||
|
if k == -1 then return false end
|
||
|
if dik_to_bind(k) ~= dik_to_bind(1000) then --conflicts with engine keybind
|
||
|
print_dbg("bind:%s", dik_to_bind(k))
|
||
|
return true
|
||
|
end
|
||
|
|
||
|
if key_by_path[path] == k then
|
||
|
print_dbg("==")
|
||
|
return paths_by_key[k] and paths_by_key[k].conflict
|
||
|
end
|
||
|
print_dbg("=/= kbp:%s %s, k:%s %s",type(key_by_path[path]), key_by_path[path], k, type(k))
|
||
|
return paths_by_key[k] and (paths_by_key[k].conflict or paths_by_key[k].conflict == false) --limited support for pending changes conflicts.
|
||
|
end
|
||
|
|
||
|
function display_key(key)
|
||
|
local loc = ini_loc:r_value("string_table","language")
|
||
|
loc = ini_mcm_key:section_exist(loc) and loc or "eng"
|
||
|
return ini_mcm_key:r_value(loc, key,0,"<!!!>")
|
||
|
end
|
||
|
|
||
|
dispaly_key = display_key --old typo aliased to prevent breaking mods.
|
||
|
|
||
|
kb_mod_radio = "radio_h"
|
||
|
kb_mod_list = "list"
|
||
|
|
||
|
local tap = {}
|
||
|
function double_tap(id, key, multi_tap)
|
||
|
local tg = time_continual()
|
||
|
tap[key] = tap[key] or {}
|
||
|
if not tap[key][id] then --first press, set timer return false
|
||
|
tap[key][id] = tg
|
||
|
return false
|
||
|
end
|
||
|
local dtaptime = get("mcm/mcm_kb/mcm_kb_main/dtaptime2")
|
||
|
|
||
|
if (tg - tap[key][id] <= dtaptime) then --if inside the dtap window
|
||
|
tap[key][id] = multi_tap and tg -- if multi_tap set timer for next tap, else clear it.
|
||
|
return true
|
||
|
end
|
||
|
|
||
|
tap[key][id] = tg -- if we get here we are past the window for a double tap so this is a first press
|
||
|
return false
|
||
|
end
|
||
|
|
||
|
|
||
|
local hold = {}
|
||
|
function key_hold(id,key, cycle)
|
||
|
local dtaptime = get("mcm/mcm_kb/mcm_kb_main/dtaptime2")
|
||
|
local hold_time = get("mcm/mcm_kb/mcm_kb_main/presstime") * dtaptime
|
||
|
cycle = cycle or 10000
|
||
|
local tg = time_continual()
|
||
|
hold[key] = hold[key] or {}
|
||
|
if (not hold[key][id]) or (tg - hold[key][id] > hold_time*1.5) then
|
||
|
hold[key][id] = tg
|
||
|
elseif (tg - hold[key][id] > hold_time ) then
|
||
|
hold[key][id] = tg + cycle * 1000
|
||
|
return true
|
||
|
end
|
||
|
return false
|
||
|
end
|
||
|
|
||
|
local function execute_if_not_held(id,key, functor,...)
|
||
|
if not (hold[key] and hold[key]["mcm_single_"..id]) then
|
||
|
exec(functor,...)
|
||
|
end
|
||
|
return true
|
||
|
end
|
||
|
|
||
|
function simple_press(id, key, functor,...)
|
||
|
|
||
|
dtap = double_tap("mcm_single_"..id, key, true)
|
||
|
local tg = time_continual()
|
||
|
hold[key] = hold[key] or {}
|
||
|
hold[key]["mcm_single_"..id] = tg
|
||
|
local dtaptime = get("mcm/mcm_kb/mcm_kb_main/dtaptime2")
|
||
|
if dtap then
|
||
|
RemoveTimeEvent("mcm_single_press",id.."_mcm_single_"..key)
|
||
|
else
|
||
|
CreateTimeEvent("mcm_single_press",id.."_mcm_single_"..key,(dtaptime*1.1)/1000,execute_if_not_held,id, key, functor,...)
|
||
|
end
|
||
|
|
||
|
|
||
|
end
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
MOD_NONE = true
|
||
|
MOD_CTRL = false
|
||
|
MOD_SHIFT = false
|
||
|
MOD_ALT = false
|
||
|
|
||
|
function get_mod_key(val)
|
||
|
if val == 1 then
|
||
|
return MOD_SHIFT
|
||
|
elseif val == 2 then
|
||
|
return MOD_CTRL
|
||
|
elseif val == 3 then
|
||
|
return MOD_ALT
|
||
|
end
|
||
|
return MOD_NONE
|
||
|
end
|
||
|
|
||
|
function on_key_release(key)
|
||
|
hold[key] = nil
|
||
|
if key == DIK_keys.DIK_RCONTROL or key == DIK_keys.DIK_LCONTROL then
|
||
|
MOD_CTRL = false
|
||
|
elseif key == DIK_keys.DIK_RSHIFT or key == DIK_keys.DIK_LSHIFT then
|
||
|
MOD_SHIFT = false
|
||
|
elseif key == DIK_keys.DIK_RMENU or key == DIK_keys.DIK_LMENU then
|
||
|
MOD_ALT = false
|
||
|
elseif key == DIK_keys.DIK_ESCAPE then
|
||
|
MOD_CTRL = false
|
||
|
MOD_SHIFT = false
|
||
|
MOD_ALT = false
|
||
|
end
|
||
|
MOD_NONE = not(MOD_CTRL or MOD_SHIFT or MOD_ALT)
|
||
|
|
||
|
end
|
||
|
function on_key_press(key)
|
||
|
if key == DIK_keys.DIK_RCONTROL or key == DIK_keys.DIK_LCONTROL then
|
||
|
MOD_CTRL = true
|
||
|
elseif key == DIK_keys.DIK_RSHIFT or key == DIK_keys.DIK_LSHIFT then
|
||
|
MOD_SHIFT = true
|
||
|
elseif key == DIK_keys.DIK_RMENU or key == DIK_keys.DIK_LMENU then
|
||
|
MOD_ALT = true
|
||
|
|
||
|
end
|
||
|
MOD_NONE = not(MOD_CTRL or MOD_SHIFT or MOD_ALT)
|
||
|
|
||
|
end
|
||
|
|
||
|
|
||
|
|
||
|
-- Utilities
|
||
|
function is_int(num)
|
||
|
return (m_floor(num) == num)
|
||
|
end
|
||
|
function exec(func,...)
|
||
|
if (not func) then
|
||
|
return false
|
||
|
end
|
||
|
return func(...)
|
||
|
end
|
||
|
function str_opt_explode(id, by_num)
|
||
|
local nums = by_num and opt_index[id] or id
|
||
|
local t = nums and str_explode(nums, _opt_) or {}
|
||
|
if by_num then
|
||
|
for i=1,#t do
|
||
|
t[i] = tonumber(t[i])
|
||
|
end
|
||
|
end
|
||
|
return t
|
||
|
end
|
||
|
function get_opt_table(id)
|
||
|
local t = str_opt_explode(id, true)
|
||
|
if #t == 0 then
|
||
|
return {}
|
||
|
end
|
||
|
|
||
|
if #t == 1 then
|
||
|
return options[t[1]]
|
||
|
elseif #t == 2 then
|
||
|
return options[t[1]].gr[t[2]]
|
||
|
elseif #t == 3 then
|
||
|
return options[t[1]].gr[t[2]].gr[t[3]]
|
||
|
elseif #t == 4 then
|
||
|
return options[t[1]].gr[t[2]].gr[t[3]].gr[t[4]]
|
||
|
end
|
||
|
end
|
||
|
function check_opt_table(id)
|
||
|
local t = str_opt_explode(id, true)
|
||
|
return #t > 0
|
||
|
end
|
||
|
function CTimeTo_mSec(ct)
|
||
|
local Y, M, D, h, m, s, ms = 0, 0, 0, 0, 0, 0, 0
|
||
|
Y, M, D, h, m, s, ms = ct:get(Y, M, D, h, m, s, ms)
|
||
|
return ((D*24*60*60 + h*60*60 + m*60 + s)*1000 + ms)
|
||
|
end
|
||
|
|
||
|
local function create_session_id()
|
||
|
if not session_id then
|
||
|
session_id = axr_main.config:r_value(opt_section, "session_id", 2, 0)
|
||
|
|
||
|
if not level.present() then -- if there is a level present then this is an inprogress session, use ID in file. if not compare the stored session start to the curent time and time continual to identify new session
|
||
|
local session_start = axr_main.config:r_value(opt_section, "session_start", 2, 0)
|
||
|
local now = os.time() * 1000
|
||
|
if (now - session_start) > (time_continual() + 10000) then --ten seconds of slop
|
||
|
session_id = session_id + 1
|
||
|
axr_main.config:w_value(opt_section, "session_id", session_id)
|
||
|
axr_main.config:w_value(opt_section, "session_start", now)
|
||
|
axr_main.config:save()
|
||
|
printf("MCM Session ID:%s", session_id)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function get_session_id()
|
||
|
create_session_id()
|
||
|
return session_id
|
||
|
end
|
||
|
|
||
|
function is_gathering()
|
||
|
return gathering
|
||
|
end
|
||
|
--------------------
|
||
|
function get(id)
|
||
|
assert(not gathering, "ui_mcm.get() cannot be called during script load or on_mcm_load()!")
|
||
|
|
||
|
if (#options == 0) then
|
||
|
init_opt_base()
|
||
|
end
|
||
|
|
||
|
if not opt_val[id] then
|
||
|
printe("MCM given bad path:%s",id)
|
||
|
return
|
||
|
end
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
local value = axr_main.config:r_value(opt_section, id, opt_val[id])
|
||
|
if (value ~= nil) then
|
||
|
--print_dbg("/Got axr_option [%s] = %s", id, value)
|
||
|
return value
|
||
|
end
|
||
|
|
||
|
-- Write in axr_main if it doesn't exist
|
||
|
local v = get_opt_table(id)
|
||
|
if v.cmd then
|
||
|
if v.val == 0 then
|
||
|
value = get_console_cmd(0, v.cmd)
|
||
|
elseif v.val == 1 then
|
||
|
value = get_console_cmd(1, v.cmd)
|
||
|
elseif v.val == 2 then
|
||
|
value = get_console_cmd(0, v.cmd) --get_console_cmd(2, v.cmd)
|
||
|
value = tonumber(value)
|
||
|
if v.min and v.max then
|
||
|
value = clamp(value, v.min, v.max)
|
||
|
end
|
||
|
value = round_idp(value, v.prec or precision)
|
||
|
end
|
||
|
elseif (type(v.def) == "table") then
|
||
|
value = exec(unpack(v.def))
|
||
|
axr_main.config:w_value(opt_section, id, value)
|
||
|
axr_main.config:save()
|
||
|
else
|
||
|
value = v.def
|
||
|
axr_main.config:w_value(opt_section, id, value)
|
||
|
axr_main.config:save()
|
||
|
end
|
||
|
|
||
|
--print_dbg("/Got option [%s] = %s", id, value)
|
||
|
if (value == nil) then
|
||
|
printe("!Found nil option value [%s]", id)
|
||
|
end
|
||
|
|
||
|
return value
|
||
|
end
|
||
|
|
||
|
function set(id, value)
|
||
|
axr_main.config:w_value(opt_section, id, value)
|
||
|
axr_main.config:save()
|
||
|
end
|
||
|
--------------------
|
||
|
|
||
|
|
||
|
--===========================================================
|
||
|
--//////////////////////// OPTIONS //////////////////////////
|
||
|
--===========================================================
|
||
|
UIMCM = {} --old monkey patch black hole
|
||
|
|
||
|
class "UI_MCM" (CUIScriptWnd)
|
||
|
|
||
|
function UI_MCM:__init() super()
|
||
|
self.last_tree = {}
|
||
|
self.last_path = nil
|
||
|
self.last_curr_tree = nil
|
||
|
|
||
|
self._Cap = {}
|
||
|
self._Check = {}
|
||
|
self._List = {}
|
||
|
self._Input = {}
|
||
|
self._Track = {}
|
||
|
self._Radio = {}
|
||
|
self._Hint = {}
|
||
|
|
||
|
|
||
|
-- Prepare the options table
|
||
|
if (#options == 0) then
|
||
|
init_opt_base()
|
||
|
end
|
||
|
printf("MCM init")
|
||
|
self:InitControls()
|
||
|
self:InitCallBacks()
|
||
|
|
||
|
self:Reset()
|
||
|
end
|
||
|
|
||
|
function UI_MCM:__finalize()
|
||
|
end
|
||
|
|
||
|
function UI_MCM:InitControls()
|
||
|
self:SetWndRect (Frect():set(0,0,1024,768))
|
||
|
self:Enable (true)
|
||
|
|
||
|
self.xml = CScriptXmlInit()
|
||
|
local xml = self.xml
|
||
|
xml:ParseFile ("ui_mcm.xml")
|
||
|
|
||
|
self.background = xml:InitStatic("background", self)
|
||
|
self.dialog = xml:InitStatic("main", self)
|
||
|
|
||
|
xml:InitStatic("main:frame", self.dialog)
|
||
|
|
||
|
-- Buttons
|
||
|
self.btn_accept = xml:Init3tButton("main:btn_accept", self.dialog)
|
||
|
self:Register(self.btn_accept, "btn_accept")
|
||
|
|
||
|
self.btn_reset = xml:Init3tButton("main:btn_reset", self.dialog)
|
||
|
self:Register(self.btn_reset, "btn_reset")
|
||
|
|
||
|
self.btn_default = xml:Init3tButton("main:btn_default", self.dialog)
|
||
|
self:Register(self.btn_default, "btn_default")
|
||
|
|
||
|
self.btn_cancel = xml:Init3tButton("main:btn_cancel", self.dialog)
|
||
|
self:Register(self.btn_cancel, "btn_cancel")
|
||
|
|
||
|
-- Pending text
|
||
|
--xml:InitFrame("main:notify_frame", self.dialog)
|
||
|
self.pending = xml:InitTextWnd("main:notify", self.dialog)
|
||
|
|
||
|
-- Options lists
|
||
|
self.tree = {}
|
||
|
self.bl = {}
|
||
|
|
||
|
-- Options showcase
|
||
|
self.scroll_opt = xml:InitScrollView("main:scroll", self.dialog)
|
||
|
|
||
|
-- Presets
|
||
|
self.preset_cap = xml:InitStatic("main:cap_preset", self.dialog)
|
||
|
self.preset = xml:InitComboBox("main:preset",self.dialog)
|
||
|
self:Register(self.preset, "preset")
|
||
|
self.preset:Show(false)
|
||
|
self.preset_cap:Show(false)
|
||
|
|
||
|
-- Message box
|
||
|
self.message_box = CUIMessageBoxEx()
|
||
|
self:Register (self.message_box, "mb")
|
||
|
|
||
|
-- Hint Window
|
||
|
self.hint_wnd = utils_ui.UIHint(self)
|
||
|
|
||
|
--MCM Key Bind
|
||
|
self.k_binder = nil
|
||
|
self.k_timer = 0
|
||
|
self.key_input = nil
|
||
|
end
|
||
|
|
||
|
function UI_MCM:InitCallBacks()
|
||
|
self:AddCallback("btn_accept", ui_events.BUTTON_CLICKED, self.OnButton_Accept, self)
|
||
|
self:AddCallback("btn_reset", ui_events.BUTTON_CLICKED, self.OnButton_Reset, self)
|
||
|
self:AddCallback("btn_default", ui_events.BUTTON_CLICKED, self.OnButton_Default, self)
|
||
|
self:AddCallback("btn_cancel", ui_events.BUTTON_CLICKED, self.OnButton_Cancel, self)
|
||
|
|
||
|
self:AddCallback("preset", ui_events.LIST_ITEM_SELECT, self.Callback_Preset, self)
|
||
|
|
||
|
self:AddCallback("mb", ui_events.MESSAGE_BOX_YES_CLICKED, self.On_Discard, self)
|
||
|
--self:AddCallback("mb", ui_events.MESSAGE_BOX_NO_CLICKED, self.On_Discard,self)
|
||
|
end
|
||
|
|
||
|
function UI_MCM:Update()
|
||
|
CUIScriptWnd.Update(self)
|
||
|
|
||
|
-- Show hint on hover
|
||
|
for id,ctrl in pairs(self._Cap) do
|
||
|
if ctrl:IsCursorOverWindow() then
|
||
|
local str = opt_str .. (self._Hint[id] or id) .. "_desc"
|
||
|
local str_t = game.translate_string(str)
|
||
|
if (str ~= str_t) then
|
||
|
self.hint_wnd:Update(str_t)
|
||
|
end
|
||
|
return
|
||
|
end
|
||
|
end
|
||
|
|
||
|
self.hint_wnd:Update()
|
||
|
|
||
|
-- Hack to simulate tracing method for TrackBar value changes. TODO: add callback support for CUITrackBar in engine, this is just silly
|
||
|
for id,e in pairs(self._Track) do
|
||
|
if e.ctrl:IsCursorOverWindow() then
|
||
|
local v = self:GetOption(id)
|
||
|
local value = round_idp(e.ctrl:GetFValue(), v.prec or precision)
|
||
|
if (value ~= e.value) then
|
||
|
e.value = value
|
||
|
self:Callback_Track(e.txt, e.path, e.opt, v, value)
|
||
|
return
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function UI_MCM:Reset()
|
||
|
-- Clear all trees
|
||
|
for i=1,3 do
|
||
|
if self.tree[i] then
|
||
|
if type(self.tree[i]) == table then
|
||
|
for j=1,#self.tree[i] do
|
||
|
self.tree[i][j]:Clear()
|
||
|
end
|
||
|
else
|
||
|
self.tree[i]:Clear()
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
self.k_binder = nil
|
||
|
self.k_timer = 0
|
||
|
self.key_input = nil
|
||
|
|
||
|
self:Register_Tree(1, "", options, 1)
|
||
|
end
|
||
|
|
||
|
function UI_MCM:Reset_opt(curr_tree, path, flags)
|
||
|
flags = flags or {}
|
||
|
local xml = self.xml
|
||
|
self.scroll_opt:Clear()
|
||
|
|
||
|
-- If options tree has a precondition that must be met, don't show it if it returns false
|
||
|
if curr_tree.precondition and (not exec(unpack(curr_tree.precondition))) then
|
||
|
if curr_tree.output then
|
||
|
local _txt = xml:InitTextWnd("elements:block", nil)
|
||
|
_txt:SetText( game.translate_string(curr_tree.output) )
|
||
|
|
||
|
self.scroll_opt:AddWindow(_txt, true)
|
||
|
_txt:SetAutoDelete(false)
|
||
|
end
|
||
|
else
|
||
|
|
||
|
|
||
|
self.k_binder = nil
|
||
|
self.k_timer = 0
|
||
|
self.key_input = nil
|
||
|
|
||
|
|
||
|
-- Presets
|
||
|
self:Register_Preset(curr_tree)
|
||
|
|
||
|
if curr_tree.apply_to_all and curr_tree.id_gr then
|
||
|
flags.apply_to_all = true
|
||
|
flags.group = curr_tree.id_gr
|
||
|
else
|
||
|
flags.apply_to_all = nil
|
||
|
end
|
||
|
|
||
|
empty_table(self._Cap)
|
||
|
empty_table(self._Check)
|
||
|
empty_table(self._List)
|
||
|
empty_table(self._Input)
|
||
|
empty_table(self._Track)
|
||
|
empty_table(self._Radio)
|
||
|
empty_table(self._Hint)
|
||
|
|
||
|
for i=1,#curr_tree.gr do
|
||
|
-- Check preconditions
|
||
|
local to_hide = curr_tree.gr[i].precondition and (not exec(unpack(curr_tree.gr[i].precondition)))
|
||
|
for j=1,10 do -- support for 10 preconditions
|
||
|
if (not curr_tree.gr[i]["precondition_" .. j]) then
|
||
|
break
|
||
|
elseif (not exec(unpack(curr_tree.gr[i]["precondition_" .. j]))) then
|
||
|
to_hide = true
|
||
|
break
|
||
|
end
|
||
|
end
|
||
|
|
||
|
if (not to_hide) then
|
||
|
local opt = curr_tree.gr[i].id
|
||
|
local v = curr_tree.gr[i]
|
||
|
|
||
|
local _st = xml:InitStatic("main:st", nil)
|
||
|
local _h = 0
|
||
|
----------- Support
|
||
|
if (v.type == "line") then
|
||
|
_h = self:Register_Line(xml, _st, v)
|
||
|
|
||
|
elseif (v.type == "image") then
|
||
|
_h = self:Register_Image(xml, _st, v)
|
||
|
|
||
|
elseif (v.type == "slide") then
|
||
|
_h = self:Register_Slide(xml, _st, v)
|
||
|
|
||
|
elseif (v.type == "title") then
|
||
|
_h = self:Register_Title(xml, _st, v)
|
||
|
|
||
|
elseif (v.type == "desc") then
|
||
|
_h = self:Register_Desc(xml, _st, v)
|
||
|
|
||
|
----------- Option
|
||
|
elseif (v.type == "check") then
|
||
|
_h = self:Register_Check(xml, _st, path, opt, v, flags)
|
||
|
|
||
|
elseif (v.type == "button") then
|
||
|
_h = self:Register_Button(xml, _st, path, opt, v, flags)
|
||
|
|
||
|
elseif (v.type == "list") then
|
||
|
_h = self:Register_List(xml, _st, path, opt, v, flags)
|
||
|
|
||
|
elseif (v.type == "input") then
|
||
|
_h = self:Register_Input(xml, _st, path, opt, v, flags)
|
||
|
|
||
|
elseif (v.type == "track") then
|
||
|
_h = self:Register_Track(xml, _st, path, opt, v, flags)
|
||
|
|
||
|
elseif (v.type == "radio_h") then
|
||
|
_h = self:Register_Radio(xml, _st, path, opt, v, true, flags)
|
||
|
|
||
|
elseif (v.type == "radio_v") then
|
||
|
_h = self:Register_Radio(xml, _st, path, opt, v, false, flags)
|
||
|
|
||
|
elseif (v.type == "key_bind") then
|
||
|
_h = self:Register_Key_Bind(xml, _st, path, opt, v, flags)
|
||
|
|
||
|
end
|
||
|
|
||
|
_st:SetWndSize(vector2():set(_st:GetWidth(), _h + 10))
|
||
|
self.scroll_opt:AddWindow(_st, true)
|
||
|
_st:SetAutoDelete(true)
|
||
|
end
|
||
|
end
|
||
|
if self.Save_AXR then
|
||
|
self.Save_AXR = false
|
||
|
axr_main.config:save()
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function UI_MCM:Reset_last_opt()
|
||
|
Register_UI("UI_MCM")
|
||
|
|
||
|
if self.last_curr_tree and self.last_path then
|
||
|
if self.last_path == "mcm/mcm_kb/mcm_conflicts" then
|
||
|
self.last_curr_tree = options[1].gr[2].gr[3]
|
||
|
end
|
||
|
self:Reset_opt(self.last_curr_tree, self.last_path)
|
||
|
self:UpdatePending()
|
||
|
end
|
||
|
end
|
||
|
|
||
|
------------------------------------------------------------
|
||
|
-- Elements
|
||
|
------------------------------------------------------------
|
||
|
|
||
|
function UI_MCM:Init_Wrapper_Box(xml, anchor, w, h, posx, posy)
|
||
|
if not (xml and anchor) then return end
|
||
|
wrapbox = xml:InitStatic("elements:image", anchor)
|
||
|
if not wrapbox then return end
|
||
|
w = w or anchor:GetWidth()
|
||
|
h = h or anchor:GetHeight()
|
||
|
posx = posx and (type(posx) == "number") and posx or 0
|
||
|
posy = posy and (type(posy) == "number") and posy or 0
|
||
|
wrapbox:SetWndSize(vector2():set(w,h))
|
||
|
pos = wrapbox:GetWndPos()
|
||
|
wrapbox:SetWndPos(vector2():set(pos.x + posx, pos.y + posy))
|
||
|
return wrapbox
|
||
|
end
|
||
|
|
||
|
function UI_MCM:Register_Cap(xml, handler, id, hint)
|
||
|
id = s_gsub(id, _opt_, "_")
|
||
|
self._Cap[id] = xml:InitStatic("elements:cap",handler)
|
||
|
self._Cap[id]:TextControl():SetText( game.translate_string(opt_str .. (hint or id)) )
|
||
|
self._Hint[id] = hint
|
||
|
return self._Cap[id]:GetHeight()
|
||
|
end
|
||
|
|
||
|
function UI_MCM:Register_Line(xml, handler, v)
|
||
|
-- v parameter added to support ui_hook_functor
|
||
|
local line = xml:InitStatic("elements:line",handler)
|
||
|
if enable_ui_functors and v and v.ui_hook_functor then
|
||
|
-- Test for existence first since older versions of MCM won't be passing v to this function
|
||
|
local wrapbox = self:Init_Wrapper_Box(xml,handler,line:GetWidth(),line:GetHeight() + 10,-10)
|
||
|
local handlers = {
|
||
|
line = line, -- handler for the line element
|
||
|
}
|
||
|
local flags = {
|
||
|
etype = "line",
|
||
|
}
|
||
|
ui_mcm.exec(unpack(v.ui_hook_functor),wrapbox,handlers,v,flags)
|
||
|
end
|
||
|
|
||
|
return (line:GetHeight() + 10)
|
||
|
end
|
||
|
|
||
|
function UI_MCM:Register_Image(xml, handler, v)
|
||
|
local pic = xml:InitStatic("elements:image",handler)
|
||
|
if v.link then
|
||
|
if (v.pos) then
|
||
|
local pos = pic:GetWndPos()
|
||
|
pic:SetWndPos(vector2():set( pos.x + v.pos[1] , pos.y + v.pos[2] ))
|
||
|
end
|
||
|
if (v.size) then
|
||
|
pic:SetWndSize(vector2():set( v.size[1] , v.size[2] ))
|
||
|
end
|
||
|
pic:InitTexture(v.link)
|
||
|
pic:SetStretchTexture(v.stretch and true or false)
|
||
|
end
|
||
|
|
||
|
if enable_ui_functors and v.ui_hook_functor then
|
||
|
local wrapbox = self:Init_Wrapper_Box(xml,handler,pic:GetWidth(),pic:GetHeight(),-10)
|
||
|
local handlers = {
|
||
|
pic = pic, -- Handler for the image element
|
||
|
}
|
||
|
local flags = {
|
||
|
etype = "image",
|
||
|
}
|
||
|
ui_mcm.exec(unpack(v.ui_hook_functor),wrapbox,handlers,v,flags)
|
||
|
end
|
||
|
|
||
|
return pic:GetHeight()
|
||
|
end
|
||
|
|
||
|
function UI_MCM:Register_Slide(xml, handler, v)
|
||
|
local frame = xml:InitStatic("elements:slide", handler)
|
||
|
local _pos = frame:GetWndPos()
|
||
|
frame:SetWndPos(vector2():set( _pos.x , _pos.y + (v.spacing or 20) ))
|
||
|
|
||
|
local pic = xml:InitStatic("elements:slide:pic", frame)
|
||
|
if v.link then
|
||
|
pic:InitTexture(v.link)
|
||
|
pic:SetStretchTexture(true)
|
||
|
if (v.pos) then
|
||
|
local pos = pic:GetWndPos()
|
||
|
pic:SetWndPos(vector2():set( pos.x + v.pos[1] , pos.y + v.pos[2] ))
|
||
|
end
|
||
|
if (v.size) then
|
||
|
pic:SetWndSize(vector2():set( v.size[1] * width_factor , v.size[2] ))
|
||
|
end
|
||
|
pic:InitTexture(v.link)
|
||
|
end
|
||
|
|
||
|
local txt = xml:InitTextWnd("elements:slide:txt", frame)
|
||
|
if v.text then
|
||
|
txt:SetText( game.translate_string(v.text) )
|
||
|
end
|
||
|
|
||
|
if not v.borderless then
|
||
|
xml:InitStatic("elements:slide:line_1", frame)
|
||
|
xml:InitStatic("elements:slide:line_2", frame)
|
||
|
end
|
||
|
|
||
|
if enable_ui_functors and v.ui_hook_functor then
|
||
|
local wrapbox = self:Init_Wrapper_Box(xml,handler,pic:GetWidth(),pic:GetHeight() + 20,-10)
|
||
|
local handlers = {
|
||
|
pic = pic, -- handler for the image element
|
||
|
txt = txt, -- handler for the text element
|
||
|
}
|
||
|
local flags = {
|
||
|
etype = "slide",
|
||
|
}
|
||
|
ui_mcm.exec(unpack(v.ui_hook_functor),wrapbox,handlers,v,flags)
|
||
|
end
|
||
|
|
||
|
return (pic:GetHeight() + 20)
|
||
|
end
|
||
|
|
||
|
function UI_MCM:Register_Title(xml, handler, v)
|
||
|
local title = xml:InitTextWnd("elements:title_" .. (v.align or "l"), handler)
|
||
|
title:SetText( game.translate_string(v.text) )
|
||
|
title:AdjustHeightToText()
|
||
|
title:SetWndSize(vector2():set(title:GetWidth(), title:GetHeight() + 20))
|
||
|
if v.clr and v.clr[4] then
|
||
|
title:SetTextColor( GetARGB(v.clr[1], v.clr[2], v.clr[3], v.clr[4]) )
|
||
|
end
|
||
|
if enable_ui_functors and v.ui_hook_functor then
|
||
|
local wrapbox = self:Init_Wrapper_Box(xml,handler,title:GetWidth(),title:GetHeight(),-10)
|
||
|
local handlers = {
|
||
|
title = title, -- Handler for the title element
|
||
|
}
|
||
|
local flags = {
|
||
|
etype = "title",
|
||
|
}
|
||
|
ui_mcm.exec(unpack(v.ui_hook_functor),wrapbox,handlers,v,flags)
|
||
|
end
|
||
|
|
||
|
return title:GetHeight()
|
||
|
end
|
||
|
|
||
|
function UI_MCM:Register_Desc(xml, handler, v)
|
||
|
local desc = xml:InitTextWnd("elements:desc", handler)
|
||
|
desc:SetText( game.translate_string(v.text) )
|
||
|
desc:AdjustHeightToText()
|
||
|
desc:SetWndSize(vector2():set(desc:GetWidth(), desc:GetHeight() + 20))
|
||
|
if v.clr and v.clr[4] then
|
||
|
desc:SetTextColor( GetARGB(v.clr[1], v.clr[2], v.clr[3], v.clr[4]) )
|
||
|
end
|
||
|
|
||
|
if enable_ui_functors and v.ui_hook_functor then
|
||
|
local wrapbox = self:Init_Wrapper_Box(xml,handler,desc:GetWidth(),desc:GetHeight(),-10)
|
||
|
local handlers = {
|
||
|
desc = desc, -- Handler for the text description element
|
||
|
}
|
||
|
local flags = {
|
||
|
etype = "desc",
|
||
|
}
|
||
|
ui_mcm.exec(unpack(v.ui_hook_functor),wrapbox,handlers,v,flags)
|
||
|
end
|
||
|
|
||
|
return desc:GetHeight()
|
||
|
end
|
||
|
|
||
|
function UI_MCM:Register_Check(xml, handler, path, opt, v, flags)
|
||
|
local id = cc(path , opt)
|
||
|
|
||
|
-- Caption
|
||
|
local h = self:Register_Cap(xml, handler, id, v.hint)
|
||
|
|
||
|
-- Apply to all button
|
||
|
if flags.apply_to_all and flags.group then
|
||
|
self:Register_BtnAll(xml, handler, path, opt, v, flags)
|
||
|
end
|
||
|
|
||
|
-- Create control
|
||
|
local ctrl = xml:InitCheck("elements:check",handler)
|
||
|
if (ctrl:GetHeight() > h) then
|
||
|
h = ctrl:GetHeight()
|
||
|
end
|
||
|
|
||
|
-- Get values
|
||
|
local value = self:GetValue(path, opt, v, flags)
|
||
|
ctrl:SetCheck(value)
|
||
|
|
||
|
-- Register
|
||
|
local id_ctrl = self:Stacker(path, opt, v)
|
||
|
|
||
|
self:Register(ctrl, id_ctrl)
|
||
|
local _wrapper = function(handler) -- we need wrapper in order to pass ctrl to method
|
||
|
self:Callback_Check(ctrl, path, opt, v)
|
||
|
end
|
||
|
self:AddCallback(id_ctrl, ui_events.BUTTON_CLICKED, _wrapper, self)
|
||
|
if enable_ui_functors and v.ui_hook_functor then
|
||
|
local id_cap = s_gsub(id, _opt_, "_")
|
||
|
local cap = self._Cap[id_cap]
|
||
|
local wrapbox = self:Init_Wrapper_Box(xml,handler,handler:GetWidth(),h,-10)
|
||
|
local handlers = {
|
||
|
ctrl = ctrl, -- handler for the checkbox control element
|
||
|
cap = cap, -- handler for text caption element
|
||
|
}
|
||
|
flags.etype = "check"
|
||
|
flags.path = path -- MCM menu path
|
||
|
flags.opt = opt -- MCM option ID
|
||
|
ui_mcm.exec(unpack(v.ui_hook_functor),wrapbox,handlers,v,flags)
|
||
|
end
|
||
|
|
||
|
return h
|
||
|
end
|
||
|
function UI_MCM:Callback_Check(ctrl, path, opt, v)
|
||
|
local value = ctrl:GetCheck()
|
||
|
self:CacheValue(path, opt, value, v)
|
||
|
end
|
||
|
|
||
|
function UI_MCM:Register_Key_Bind(xml, handler, path, opt, v, flags)
|
||
|
local id = cc(path , opt)
|
||
|
|
||
|
self.k_binder = nil
|
||
|
self.k_timer = 0
|
||
|
self.key_input = nil
|
||
|
|
||
|
|
||
|
--[[ Apply to all button
|
||
|
if flags.apply_to_all and flags.group then
|
||
|
self:Register_BtnAll(xml, handler, path, opt, v, flags)
|
||
|
end
|
||
|
--]]
|
||
|
xml:InitFrame("elements:frame_key_button", handler)
|
||
|
local txt = xml:InitStatic("elements:txt_key_button", handler)
|
||
|
|
||
|
-- Create control
|
||
|
local ctrl = xml:Init3tButton("elements:btn_key_button", handler)
|
||
|
local h = ctrl:GetHeight()
|
||
|
|
||
|
-- Caption
|
||
|
local h = self:Register_Cap(xml, handler, id, v.hint)
|
||
|
|
||
|
-- Get values
|
||
|
local value = self:GetValue(path, opt, v, flags)
|
||
|
|
||
|
txt:TextControl():SetText(display_key(value))
|
||
|
|
||
|
if get_conflict(v.kb_path or id, value) then
|
||
|
txt:TextControl():SetTextColor(clr_r)
|
||
|
else
|
||
|
txt:TextControl():SetTextColor(clr_g1)
|
||
|
end
|
||
|
-- Register
|
||
|
local id_ctrl = self:Stacker(path, opt, v)
|
||
|
|
||
|
self:Register(ctrl, id_ctrl)
|
||
|
local _wrapper = function(handler) -- we need wrapper in order to pass ctrl to method
|
||
|
self:Callback_Key_Bind(ctrl, path, opt, v, txt)
|
||
|
end
|
||
|
|
||
|
self:AddCallback(id_ctrl, ui_events.BUTTON_CLICKED, _wrapper, self)
|
||
|
|
||
|
return h
|
||
|
end
|
||
|
|
||
|
function UI_MCM:Callback_Key_Bind(ctrl, path, opt, v, txt)
|
||
|
self.key_input = txt:TextControl()
|
||
|
self.key_input:SetText( "<?>")
|
||
|
txt:TextControl():SetTextColor(clr_w)
|
||
|
if self.k_binder then
|
||
|
self.k_binder() -- clear other bind inputs
|
||
|
end
|
||
|
self.k_timer = time_continual()
|
||
|
self.k_binder = function(key)
|
||
|
self:Key_Binder(ctrl, path, opt, v, key, txt)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function UI_MCM:Key_Binder(ctrl, path, opt, v, key, txt)
|
||
|
|
||
|
self.key_input = nil
|
||
|
self.k_binder = nil
|
||
|
local value
|
||
|
if key then
|
||
|
self:CacheValue(path, opt, key, v)
|
||
|
value = key
|
||
|
else
|
||
|
value = self:GetValue(path, opt, v, flags)
|
||
|
end
|
||
|
txt:TextControl():SetText(display_key(value))
|
||
|
if get_conflict(v.kb_path or cc(path , opt), value) then
|
||
|
txt:TextControl():SetTextColor(clr_r)
|
||
|
else
|
||
|
txt:TextControl():SetTextColor(clr_g1)
|
||
|
end
|
||
|
|
||
|
end
|
||
|
|
||
|
function UI_MCM:Register_Button(xml, handler, path, opt, v, flags)
|
||
|
local id = cc(path , opt)
|
||
|
|
||
|
|
||
|
|
||
|
--[[ Apply to all button
|
||
|
if flags.apply_to_all and flags.group then
|
||
|
self:Register_BtnAll(xml, handler, path, opt, v, flags)
|
||
|
end
|
||
|
--]]
|
||
|
|
||
|
xml:InitFrame("elements:frame_button", handler)
|
||
|
|
||
|
-- Create control
|
||
|
local ctrl = xml:Init3tButton("elements:btn_button", handler)
|
||
|
local h = ctrl:GetHeight()
|
||
|
|
||
|
-- Caption
|
||
|
local id_cap = s_gsub(id, _opt_, "_")
|
||
|
self._Cap[id_cap] = xml:InitStatic("elements:cap_button",handler)
|
||
|
self._Cap[id_cap]:TextControl():SetText( game.translate_string(opt_str .. (v.hint or id_cap)) )
|
||
|
if (self._Cap[id_cap]:GetHeight() > h) then
|
||
|
h = self._Cap[id_cap]:GetHeight()
|
||
|
end
|
||
|
|
||
|
-- Register
|
||
|
local id_ctrl = self:Stacker(path, opt, v)
|
||
|
|
||
|
self:Register(ctrl, id_ctrl)
|
||
|
local _wrapper = function(handler) -- we need wrapper in order to pass ctrl to method
|
||
|
self:Callback_Button(ctrl, path, opt, v)
|
||
|
end
|
||
|
self:AddCallback(id_ctrl, ui_events.BUTTON_CLICKED, _wrapper, self)
|
||
|
|
||
|
return h
|
||
|
end
|
||
|
|
||
|
|
||
|
|
||
|
function UI_MCM:Callback_Button(ctrl, path, opt, v)
|
||
|
if v.functor_ui then
|
||
|
local id = cc(path , opt)
|
||
|
print_dbg("- Executing functor_ui of [%s]",id)
|
||
|
exec(unpack(v.functor_ui),self)
|
||
|
end
|
||
|
if v.functor then
|
||
|
local id = cc(path , opt)
|
||
|
print_dbg("- Executing functor of [%s]",id)
|
||
|
exec(unpack(v.functor))
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function UI_MCM:Register_List(xml, handler, path, opt, v, flags)
|
||
|
local id = cc(path , opt)
|
||
|
|
||
|
-- Caption
|
||
|
local h = self:Register_Cap(xml, handler, id, v.hint)
|
||
|
|
||
|
-- Apply to all button
|
||
|
if flags.apply_to_all and flags.group then
|
||
|
self:Register_BtnAll(xml, handler, path, opt, v, flags)
|
||
|
end
|
||
|
|
||
|
-- Create control
|
||
|
local ctrl = xml:InitComboBox("elements:list",handler)
|
||
|
if (ctrl:GetHeight() > h) then
|
||
|
--h = ctrl:GetHeight()
|
||
|
end
|
||
|
|
||
|
-- Get values
|
||
|
local idx
|
||
|
local value = self:GetValue(path, opt, v, flags)
|
||
|
local content = self:GetContent(path, opt, v)
|
||
|
|
||
|
-- Setup
|
||
|
for i=1,#content do
|
||
|
local str_2 = content[i][2] or tostring(content[i][1])
|
||
|
local str = v.no_str and str_2 or game.translate_string(opt_str_lst .. str_2)
|
||
|
ctrl:AddItem( game.translate_string(str), i)
|
||
|
|
||
|
if content[i][1] == value then
|
||
|
idx = i
|
||
|
end
|
||
|
end
|
||
|
idx = idx or 1
|
||
|
--printf(path.." ".. idx)
|
||
|
local str_2 = content[idx][2] or tostring(content[idx][1])
|
||
|
local str = v.no_str and str_2 or game.translate_string(opt_str_lst .. str_2)
|
||
|
ctrl:enable_id( idx )
|
||
|
ctrl:SetText( game.translate_string(str) )
|
||
|
|
||
|
-- Register
|
||
|
local id_ctrl = self:Stacker(path, opt, v)
|
||
|
|
||
|
self:Register(ctrl, id_ctrl)
|
||
|
local _wrapper = function(handler) -- we need wrapper in order to pass ctrl to method
|
||
|
self:Callback_List(ctrl, path, opt, v)
|
||
|
end
|
||
|
self:AddCallback(id_ctrl, ui_events.LIST_ITEM_SELECT, _wrapper, self)
|
||
|
|
||
|
if enable_ui_functors and v.ui_hook_functor then
|
||
|
local id_cap = s_gsub(id, _opt_, "_")
|
||
|
local cap = self._Cap[id_cap]
|
||
|
local wrapbox = self:Init_Wrapper_Box(xml,handler,handler:GetWidth(),ctrl:GetHeight(),-10)
|
||
|
local handlers = {
|
||
|
ctrl = ctrl, -- handler for the list control element
|
||
|
cap = cap, -- handler for text caption element
|
||
|
}
|
||
|
flags.etype = "list"
|
||
|
flags.path = path -- MCM menu path
|
||
|
flags.opt = opt -- MCM option ID
|
||
|
ui_mcm.exec(unpack(v.ui_hook_functor),wrapbox,handlers,v,flags)
|
||
|
end
|
||
|
|
||
|
return h
|
||
|
end
|
||
|
function UI_MCM:Callback_List(ctrl, path, opt, v)
|
||
|
local i = ctrl:CurrentID()
|
||
|
local content = self:GetContent(path, opt, v)
|
||
|
self:CacheValue(path, opt, content[i][1], v)
|
||
|
end
|
||
|
|
||
|
function UI_MCM:Register_Input(xml, handler, path, opt, v, flags)
|
||
|
local id = cc(path , opt)
|
||
|
|
||
|
-- Caption
|
||
|
local h = self:Register_Cap(xml, handler, id, v.hint)
|
||
|
|
||
|
-- Apply to all button
|
||
|
if flags.apply_to_all and flags.group then
|
||
|
self:Register_BtnAll(xml, handler, path, opt, v, flags)
|
||
|
end
|
||
|
|
||
|
-- Create control
|
||
|
local ctrl = xml:InitEditBox("elements:input",handler)
|
||
|
if (ctrl:GetHeight() > h) then
|
||
|
h = ctrl:GetHeight()
|
||
|
end
|
||
|
|
||
|
-- Get values
|
||
|
local value = self:GetValue(path, opt, v, flags)
|
||
|
ctrl:SetText(value)
|
||
|
|
||
|
-- Register
|
||
|
local id_ctrl = self:Stacker(path, opt, v)
|
||
|
|
||
|
self:Register(ctrl, id_ctrl)
|
||
|
local _wrapper = function(handler) -- we need wrapper in order to pass ctrl to method
|
||
|
self:Callback_Input(ctrl, path, opt, v)
|
||
|
end
|
||
|
self:AddCallback(id_ctrl, ui_events.EDIT_TEXT_COMMIT, _wrapper, self)
|
||
|
|
||
|
if enable_ui_functors and v.ui_hook_functor then
|
||
|
local id_cap = s_gsub(id, _opt_, "_")
|
||
|
local cap = self._Cap[id_cap]
|
||
|
local wrapbox = self:Init_Wrapper_Box(xml,handler,handler:GetWidth(),h,-10)
|
||
|
local handlers = {
|
||
|
ctrl = ctrl, -- handler for the input control element
|
||
|
cap = cap, -- handler for text caption element
|
||
|
}
|
||
|
flags.etype = "input"
|
||
|
flags.path = path -- MCM menu path
|
||
|
flags.opt = opt -- MCM option ID
|
||
|
ui_mcm.exec(unpack(v.ui_hook_functor),wrapbox,handlers,v,flags)
|
||
|
end
|
||
|
|
||
|
return h
|
||
|
end
|
||
|
function UI_MCM:Callback_Input(ctrl, path, opt, v)
|
||
|
local value = ctrl:GetText()
|
||
|
if not (value and value ~= "") then
|
||
|
ctrl:SetText( self:GetCurrentValue(path, opt, v) or self:GetDefaultValue(path, opt, v) )
|
||
|
return
|
||
|
end
|
||
|
|
||
|
if (v.val == 2) then
|
||
|
value = tonumber(value)
|
||
|
if (not value) then
|
||
|
ctrl:SetText( self:GetCurrentValue(path, opt, v) or self:GetDefaultValue(path, opt, v) )
|
||
|
return
|
||
|
end
|
||
|
value = clamp(value, v.min, v.max)
|
||
|
end
|
||
|
|
||
|
self:CacheValue(path, opt, value, v)
|
||
|
ctrl:SetText(value)
|
||
|
end
|
||
|
|
||
|
|
||
|
function UI_MCM:Register_Track(xml, handler, path, opt, v, flags)
|
||
|
local id = cc(path , opt)
|
||
|
|
||
|
-- Caption
|
||
|
local h = self:Register_Cap(xml, handler, id, v.hint)
|
||
|
|
||
|
-- Apply to all button
|
||
|
if flags.apply_to_all and flags.group then
|
||
|
self:Register_BtnAll(xml, handler, path, opt, v, flags)
|
||
|
end
|
||
|
|
||
|
-- Create control
|
||
|
self._Track[id] = {}
|
||
|
self._Track[id].ctrl = xml:InitTrackBar("elements:track",handler)
|
||
|
self._Track[id].path = path
|
||
|
self._Track[id].opt = opt
|
||
|
if (self._Track[id].ctrl:GetHeight() > h) then
|
||
|
h = self._Track[id].ctrl:GetHeight()
|
||
|
end
|
||
|
|
||
|
self._Track[id].txt = xml:InitTextWnd("elements:track_value",handler)
|
||
|
|
||
|
-- Get values
|
||
|
local value = self:GetValue(path, opt, v, flags)
|
||
|
value = clamp(value, v.min, v.max)
|
||
|
value = round_idp(value, v.prec or precision)
|
||
|
|
||
|
local int = false --is_int(value) and is_int(v.step) and is_int(v.min) and is_int(v.max)
|
||
|
self._Track[id].value = value -- temp
|
||
|
self._Track[id].ctrl:SetInvert(v.invert and true or false)
|
||
|
self._Track[id].ctrl:SetStep(v.step)
|
||
|
if int then
|
||
|
self._Track[id].ctrl:SetOptIBounds(v.min, v.max)
|
||
|
self._Track[id].ctrl:SetIValue(value)
|
||
|
else
|
||
|
self._Track[id].ctrl:SetOptFBounds(v.min, v.max)
|
||
|
self._Track[id].ctrl:SetFValue(value)
|
||
|
end
|
||
|
if (not v.no_str) then
|
||
|
self._Track[id].txt:SetText(value)
|
||
|
end
|
||
|
|
||
|
if enable_ui_functors and v.ui_hook_functor then
|
||
|
local id_cap = s_gsub(id, _opt_, "_")
|
||
|
local cap = self._Cap[id_cap]
|
||
|
local wrapbox = self:Init_Wrapper_Box(xml,handler,10,10,-10)
|
||
|
local ctrl = self._Track[id].ctrl
|
||
|
local txt = self._Track[id].txt
|
||
|
local handlers = {
|
||
|
ctrl = ctrl, -- handler for input control
|
||
|
txt = txt, -- handler for current value display text
|
||
|
cap = cap, -- handler for text caption element
|
||
|
}
|
||
|
flags.etype = "track"
|
||
|
flags.path = path -- MCM menu path
|
||
|
flags.opt = opt -- MCM option ID
|
||
|
ui_mcm.exec(unpack(v.ui_hook_functor),wrapbox,handlers,v,flags)
|
||
|
end
|
||
|
|
||
|
return h
|
||
|
end
|
||
|
function UI_MCM:Callback_Track(ctrl, path, opt, v, value)
|
||
|
if (not v.no_str) then
|
||
|
ctrl:SetText(value)
|
||
|
end
|
||
|
self:CacheValue(path, opt, value, v)
|
||
|
end
|
||
|
|
||
|
function UI_MCM:Register_Radio(xml, handler, path, opt, v, typ, flags)
|
||
|
local id = cc(path , opt)
|
||
|
|
||
|
-- Caption
|
||
|
local h = self:Register_Cap(xml, handler, id, v.hint)
|
||
|
|
||
|
-- Apply to all button
|
||
|
if flags.apply_to_all and flags.group then
|
||
|
self:Register_BtnAll(xml, handler, path, opt, v, flags)
|
||
|
end
|
||
|
|
||
|
-- Determine type
|
||
|
local str = typ and "horz" or "vert"
|
||
|
local content = self:GetContent(path, opt, v)
|
||
|
local num = #content
|
||
|
if num > 8 and (not v.force_horz) then
|
||
|
typ = false
|
||
|
str = "vert"
|
||
|
end
|
||
|
|
||
|
-- Create control
|
||
|
local frame = xml:InitStatic("elements:radio_" .. str, handler)
|
||
|
local ctrl = {}
|
||
|
local txttbl = {}
|
||
|
local txt
|
||
|
local offset = typ and m_floor(frame:GetWidth()/num) or 30
|
||
|
local h_factor = typ and 1 or 0
|
||
|
local v_factor = typ and 0 or 1
|
||
|
local h1, h2 = 0, 0
|
||
|
--printf("offset: %s - h_factor: %s - v_factor: %s - num: %s", offset, h_factor, v_factor, num)
|
||
|
for i=1,num do
|
||
|
|
||
|
-- Buttons
|
||
|
ctrl[i] = xml:InitCheck("elements:radio_" .. str .. ":btn", frame)
|
||
|
local pos = ctrl[i]:GetWndPos()
|
||
|
h1 = (h1 * v_factor) + ctrl[i]:GetHeight()
|
||
|
ctrl[i]:SetWndPos(vector2():set( pos.x + ((i-1) * offset * h_factor) , pos.y + ((i-1) * offset * v_factor) ))
|
||
|
|
||
|
-- Text
|
||
|
txt = xml:InitTextWnd("elements:radio_" .. str .. ":txt", frame)
|
||
|
local pos2 = txt:GetWndPos()
|
||
|
h2 = h_factor * txt:GetHeight()
|
||
|
txt:SetWndPos(vector2():set( pos2.x + ((i-1) * offset * h_factor) , pos2.y - (v_factor * 30) + ((i-1) * offset * v_factor) ))
|
||
|
local str_2 = content[i][2] or tostring(content[i][1])
|
||
|
local str = v.no_str and game.translate_string(str_2) or game.translate_string(opt_str_lst .. str_2)
|
||
|
txt:SetText( str )
|
||
|
txttbl[i] = txt
|
||
|
if (h1 + h2 > h) then
|
||
|
h = h1 + h2
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Get values
|
||
|
local value = self:GetValue(path, opt, v, flags)
|
||
|
|
||
|
local id_ctrl = self:Stacker(path, opt, v)
|
||
|
for i=1,num do
|
||
|
if (content[i][1] == value) then
|
||
|
ctrl[i]:SetCheck(true)
|
||
|
else
|
||
|
ctrl[i]:SetCheck(false)
|
||
|
end
|
||
|
|
||
|
-- Register
|
||
|
self:Register(ctrl[i], id_ctrl .. i)
|
||
|
local _wrapper = function(handler) -- we need wrapper in order to pass ctrl to method
|
||
|
self:Callback_Radio(ctrl, path, opt, v, i)
|
||
|
end
|
||
|
self:AddCallback(id_ctrl .. i, ui_events.BUTTON_CLICKED, _wrapper, self)
|
||
|
end
|
||
|
|
||
|
|
||
|
if enable_ui_functors and v.ui_hook_functor then
|
||
|
local id_cap = s_gsub(id, _opt_, "_")
|
||
|
local cap = self._Cap[id_cap]
|
||
|
local wrapbox = self:Init_Wrapper_Box(xml,handler,handler:GetWidth(),h,-10)
|
||
|
local ctrltbl = ctrl
|
||
|
local handlers = {
|
||
|
ctrltbl = ctrltbl, -- TABLE of radio options from 1 to (flags.num_opts)
|
||
|
txttbl = txttbl, -- TABLE of handlers for the radio options
|
||
|
cap = cap, -- handler for text caption element
|
||
|
}
|
||
|
flags.etype = "radio"
|
||
|
flags.num_opts = num -- number of options
|
||
|
flags.hvtype = str -- "horz" or "vert"
|
||
|
flags.path = path -- MCM menu path
|
||
|
flags.opt = opt -- MCM option ID
|
||
|
ui_mcm.exec(unpack(v.ui_hook_functor),wrapbox,ctrltbl,v,flags)
|
||
|
end
|
||
|
|
||
|
return h
|
||
|
end
|
||
|
function UI_MCM:Callback_Radio(ctrl, path, opt, v, n)
|
||
|
local value = ctrl[n]:GetCheck()
|
||
|
--printf("n = %s", n)
|
||
|
if value then
|
||
|
for i=1,#ctrl do
|
||
|
if i ~= n then
|
||
|
ctrl[i]:SetCheck(false)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local content = self:GetContent(path, opt, v)
|
||
|
self:CacheValue(path, opt, content[n][1], v)
|
||
|
else
|
||
|
ctrl[n]:SetCheck(true)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function UI_MCM:Register_BtnAll(xml, handler, path, opt, v, flags)
|
||
|
local ctrl = xml:Init3tButton("elements:btn_all",handler)
|
||
|
xml:InitStatic("elements:cap_all",handler)
|
||
|
|
||
|
local id_ctrl = self:Stacker(path, opt, v)
|
||
|
|
||
|
self:Register(ctrl, id_ctrl)
|
||
|
local _wrapper = function(handler) -- we need wrapper in order to pass ctrl to method
|
||
|
self:Callback_BtnAll(ctrl, path, opt, v, flags)
|
||
|
end
|
||
|
self:AddCallback(id_ctrl, ui_events.BUTTON_CLICKED, _wrapper, self)
|
||
|
end
|
||
|
function UI_MCM:Callback_BtnAll(ctrl, path, opt, v, flags)
|
||
|
local id = cc(path , opt)
|
||
|
local group = flags.group
|
||
|
local value = self:GetValue(path, opt, v, flags)
|
||
|
|
||
|
-- Set same value for identical options of same group
|
||
|
local function set_in_group(p, group, path, opt, value)
|
||
|
print_dbg("~set_in_group | current path: %s - target opt: %s", path, opt)
|
||
|
for i=1,#p do
|
||
|
local path_ext = path and (path ~= "") and cc(path , p[i].id) or p[i].id
|
||
|
if p[i].sh then
|
||
|
print_dbg("~set_in_group | current path: %s - target opt: %s", path_ext, opt)
|
||
|
if (p[i].id_gr == group) then
|
||
|
local gr = p[i].gr
|
||
|
for j=1,#gr do
|
||
|
if gr[j].id == opt then
|
||
|
local id_ext = cc(path_ext , opt)
|
||
|
if check_opt_table(id_ext) then
|
||
|
print_dbg("-set_in_group | Found match: %s", id_ext)
|
||
|
self:CacheValue(path_ext, opt, value, gr[j])
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
else
|
||
|
set_in_group(p[i].gr, group, path_ext, opt, value)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
set_in_group(options, group, "", opt, value)
|
||
|
end
|
||
|
|
||
|
function UI_MCM:Register_Preset(ct)
|
||
|
if ct.presets then
|
||
|
self.preset:ClearList()
|
||
|
--
|
||
|
for i=1,#ct.presets do
|
||
|
self.preset:AddItem( game.translate_string(opt_str_prst .. ct.presets[i]), i)
|
||
|
end
|
||
|
if (ct.curr_preset) then
|
||
|
self.preset:SetText( game.translate_string(opt_str_prst .. ct.curr_preset) )
|
||
|
end
|
||
|
self.preset:Show(true)
|
||
|
self.preset_cap:Show(true)
|
||
|
else
|
||
|
self.preset:ClearList()
|
||
|
self.preset:Show(false)
|
||
|
self.preset_cap:Show(false)
|
||
|
end
|
||
|
end
|
||
|
function UI_MCM:Callback_Preset()
|
||
|
if not (self.last_curr_tree and self.last_path) then
|
||
|
return
|
||
|
end
|
||
|
|
||
|
local txt = self.preset:GetText()
|
||
|
if not (txt and txt ~= "") then
|
||
|
return
|
||
|
end
|
||
|
|
||
|
-- Retrieve the preset section
|
||
|
local pres
|
||
|
local presets = self.last_curr_tree.presets
|
||
|
for i=1,#presets do
|
||
|
if game.translate_string(opt_str_prst .. presets[i]) == txt then
|
||
|
pres = presets[i]
|
||
|
break
|
||
|
end
|
||
|
end
|
||
|
|
||
|
if pres and ini_pres:section_exist(pres) then
|
||
|
self.last_curr_tree.curr_preset = pres
|
||
|
--self:Reset_opt(self.last_curr_tree, self.last_path, { preset = pres })
|
||
|
|
||
|
local n = ini_pres:line_count(pres)
|
||
|
local result, id, value
|
||
|
for i=0,n-1 do
|
||
|
result, id, value = ini_pres:r_line_ex(pres,i,"","")
|
||
|
|
||
|
-- Validate option
|
||
|
local v = get_opt_table(id)
|
||
|
if v and v.type then
|
||
|
|
||
|
-- No need to modify options that can't be seen
|
||
|
local to_hide = v.precondition and (not exec(unpack(v.precondition)))
|
||
|
if (not to_hide) then
|
||
|
|
||
|
-- Get proper value
|
||
|
if v.val == 0 then
|
||
|
|
||
|
elseif v.val == 1 then
|
||
|
value = (value == "true") and true or false
|
||
|
elseif v.val == 2 then
|
||
|
value = tonumber(value)
|
||
|
end
|
||
|
|
||
|
-- Extract path and opt
|
||
|
local t = str_opt_explode(id)
|
||
|
local opt = t[#t]
|
||
|
local path = t[1]
|
||
|
for i=2,#t-1 do
|
||
|
path = cc(path , t[i])
|
||
|
end
|
||
|
|
||
|
-- Cache changes
|
||
|
self:CacheValue(path, opt, value, v)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Update XML elements
|
||
|
self:Reset_opt(self.last_curr_tree, self.last_path)
|
||
|
|
||
|
-- Update state
|
||
|
self:UpdatePending()
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function UI_MCM:Register_Tree(tr, path, group, idx)
|
||
|
print_dbg("-Register_Tree | tr: %s - path: %s", tr, path)
|
||
|
local xml = self.xml
|
||
|
self.key_input = nil
|
||
|
self.k_binder = nil
|
||
|
|
||
|
if (not self.tree[tr]) then
|
||
|
self.tree[tr] = {}
|
||
|
end
|
||
|
|
||
|
if (not self.tree[tr][path]) then
|
||
|
self.tree[tr][path] = xml:InitScrollView("main:tree_" .. tr, self.dialog)
|
||
|
|
||
|
--[[
|
||
|
local pos = self.tree[tr][path]:GetWndPos()
|
||
|
if tr == 3 then idx = 1 end
|
||
|
self.tree[tr][path]:SetWndPos(vector2():set( pos.x , pos.y + (25*(idx-1)) ))
|
||
|
--]]
|
||
|
|
||
|
if (not self.bl[tr]) then
|
||
|
self.bl[tr] = {}
|
||
|
end
|
||
|
|
||
|
self.bl[tr][path] = {}
|
||
|
|
||
|
-- Fill tree
|
||
|
for i=1,#group do
|
||
|
local _st = xml:InitStatic("main:st_tree", nil)
|
||
|
|
||
|
self.bl[tr][path][i] = xml:InitCheck("elements:btn_list", _st)
|
||
|
self.bl[tr][path][i]:SetCheck(false)
|
||
|
|
||
|
local txt = xml:InitTextWnd("elements:txt_list", _st)
|
||
|
txt:SetText( game.translate_string(group[i].text or opt_str_menu .. group[i].id) )
|
||
|
txt:SetTextColor( clr_tree[tr] )
|
||
|
|
||
|
self.tree[tr][path]:AddWindow(_st, true)
|
||
|
_st:SetAutoDelete(false)
|
||
|
end
|
||
|
|
||
|
-- Set Callback for tree buttons
|
||
|
for i=1,#self.bl[tr][path] do
|
||
|
local path_i = (path ~= "") and cc(path , group[i].id) or group[i].id
|
||
|
|
||
|
self:Register(self.bl[tr][path][i], ("tree_"..path_i))
|
||
|
local _wrapper = function(handler) -- we need wrapper in order to pass ctrl to method
|
||
|
self:Callback_Tree(tr, path_i, group[i], self.bl[tr][path], i)
|
||
|
end
|
||
|
self:AddCallback(("tree_"..path_i), ui_events.BUTTON_CLICKED, _wrapper, self)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
self.tree[tr][path]:Show(true)
|
||
|
|
||
|
self.bl[tr][path][1]:SetCheck(true)
|
||
|
local path_1 = (path ~= "") and cc(path , group[1].id) or group[1].id
|
||
|
self:Callback_Tree(tr, path_1, group[1], self.bl[tr][path], 1)
|
||
|
end
|
||
|
function UI_MCM:Callback_Tree(tr, path, group, ctrl, i)
|
||
|
print_dbg("-Callback_Tree | tr: %s - path: %s - index: %s", tr, path, i)
|
||
|
self.key_input = nil
|
||
|
self.k_binder = nil
|
||
|
|
||
|
-- Radio buttons behavior
|
||
|
if (ctrl[i]:GetCheck() == false) then
|
||
|
ctrl[i]:SetCheck(true)
|
||
|
return
|
||
|
end
|
||
|
for k=1,#ctrl do
|
||
|
if k ~= i then
|
||
|
ctrl[k]:SetCheck(false)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Hide all sub trees
|
||
|
for k=tr+1,#self.tree do
|
||
|
for _,v in pairs(self.tree[k]) do
|
||
|
v:Show(false)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- If its an option list, show it
|
||
|
if group.sh then
|
||
|
self:Reset_opt(group, path)
|
||
|
|
||
|
-- Caching current options
|
||
|
self.last_path = path
|
||
|
if (not self.last_curr_tree) then
|
||
|
self.last_curr_tree = {}
|
||
|
end
|
||
|
empty_table(self.last_curr_tree)
|
||
|
copy_table(self.last_curr_tree, group)
|
||
|
|
||
|
else
|
||
|
self:Register_Tree(tr+1, path, group.gr, i)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
|
||
|
------------------------------------------------------------
|
||
|
-- Utilities
|
||
|
------------------------------------------------------------
|
||
|
function UI_MCM:GetValue(path, opt, v, flags)
|
||
|
-- NOTE: make sure to check for nil values only, since false exists as legit value for commands and check boxes
|
||
|
local value
|
||
|
|
||
|
if flags and flags.def then
|
||
|
|
||
|
value = self:GetDefaultValue(path, opt, v)
|
||
|
|
||
|
elseif flags and flags.preset then
|
||
|
local pres = flags.preset
|
||
|
local id = cc(path , opt)
|
||
|
|
||
|
if v.val == 0 then
|
||
|
value = ini_pres:r_string_ex(pres, id)
|
||
|
elseif v.val == 1 then
|
||
|
value = ini_pres:r_bool_ex(pres, id)
|
||
|
elseif v.val == 2 then
|
||
|
value = ini_pres:r_float_ex(pres, id)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
if (value ~= nil) or (flags and flags.def) then
|
||
|
if (value ~= nil) and (v.type == "track") then
|
||
|
value = clamp(value, v.min, v.max)
|
||
|
end
|
||
|
self:CacheValue(path, opt, value, v)
|
||
|
end
|
||
|
if (value == nil) then
|
||
|
value = self:GetCurrentValue(path, opt, v)
|
||
|
end
|
||
|
if (value == nil) then
|
||
|
value = self:GetDefaultValue(path, opt, v)
|
||
|
end
|
||
|
|
||
|
if (value ~= nil) and (v.type == "track") then
|
||
|
value = clamp(value, v.min, v.max)
|
||
|
end
|
||
|
|
||
|
return value
|
||
|
end
|
||
|
|
||
|
function UI_MCM:GetDefaultValue(path, opt, v)
|
||
|
local id = cc(path , opt)
|
||
|
local value
|
||
|
|
||
|
if (type(v.def) == "table") then
|
||
|
value = exec(unpack(v.def))
|
||
|
else
|
||
|
value = v.def
|
||
|
end
|
||
|
|
||
|
-- We cache default values for the first time, so current values rely on them up later
|
||
|
-- because some default values are randomized and player might not touch them, thus causing randomized effects where they are used in-game
|
||
|
if (axr_main.config:r_value(opt_section, id, v.val) == nil) and (value ~= nil) then
|
||
|
axr_main.config:w_value(opt_section, id, value)
|
||
|
self.Save_AXR = true
|
||
|
end
|
||
|
|
||
|
return value
|
||
|
end
|
||
|
|
||
|
function UI_MCM:GetCurrentValue(path, opt, v)
|
||
|
local id = cc(path , opt)
|
||
|
|
||
|
if (opt_temp[id] ~= nil) then
|
||
|
local _id = s_gsub(id, _opt_, "_")
|
||
|
if self._Cap[_id] and self._Cap[_id]:IsShown() then
|
||
|
self._Cap[_id]:TextControl():SetTextColor( clr_o )
|
||
|
end
|
||
|
|
||
|
return opt_temp[id]
|
||
|
end
|
||
|
|
||
|
local value
|
||
|
if v.curr then
|
||
|
value = exec(unpack(v.curr))
|
||
|
elseif v.cmd then
|
||
|
if v.val == 0 then
|
||
|
value = get_console_cmd(0, v.cmd)
|
||
|
elseif v.val == 1 then
|
||
|
value = get_console_cmd(1, v.cmd)
|
||
|
elseif v.val == 2 then
|
||
|
value = get_console_cmd(0, v.cmd) --get_console_cmd(2, v.cmd) -- some commands are integers, using get_float will return 0. This is a walkaround
|
||
|
value = tonumber(value)
|
||
|
if v.min and v.max then
|
||
|
value = clamp(value, v.min, v.max)
|
||
|
end
|
||
|
value = round_idp(value, v.prec or precision)
|
||
|
end
|
||
|
|
||
|
else
|
||
|
value = axr_main.config:r_value(opt_section, id, v.val)
|
||
|
end
|
||
|
|
||
|
return value
|
||
|
end
|
||
|
|
||
|
function UI_MCM:GetContent(path, opt, v)
|
||
|
if v.cmd and (not v.content) then
|
||
|
local value
|
||
|
if v.val == 0 then
|
||
|
value = get_console_cmd(0, v.cmd)
|
||
|
elseif v.val == 1 then
|
||
|
value = get_console_cmd(1, v.cmd)
|
||
|
elseif v.val == 2 then
|
||
|
value = get_console_cmd(0, v.cmd) --get_console_cmd(2, v.cmd)
|
||
|
value = tonumber(value)
|
||
|
if v.min and v.max then
|
||
|
value = clamp(value, v.min, v.max)
|
||
|
end
|
||
|
value = round_idp(value, v.prec or precision)
|
||
|
end
|
||
|
return {{value,tostring(value)}}
|
||
|
|
||
|
elseif (type(v.content[1]) == "function") then
|
||
|
return exec(unpack(v.content))
|
||
|
else
|
||
|
return v.content
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function UI_MCM:GetOption(id)
|
||
|
|
||
|
local t = str_explode(id,_opt_ )
|
||
|
local v = options
|
||
|
for i=1,#t do
|
||
|
for j=1,#v do
|
||
|
if v[j].id == t[i] then
|
||
|
if i == #t then
|
||
|
v = v[j]
|
||
|
else
|
||
|
v = v[j].gr
|
||
|
end
|
||
|
break
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
return v
|
||
|
end
|
||
|
|
||
|
function UI_MCM:CacheValue(path, opt, value, v)
|
||
|
local id = cc(path , opt)
|
||
|
|
||
|
-- Do a backup of current values first
|
||
|
if (opt_backup[id] == nil) then
|
||
|
opt_backup[id] = self:GetValue(path, opt, v)
|
||
|
print_dbg("# Backup [%s] = %s", id, opt_backup[id])
|
||
|
end
|
||
|
|
||
|
-- Cache changed values
|
||
|
if (value ~= nil) and (value ~= opt_backup[id]) then
|
||
|
opt_temp[id] = value
|
||
|
print_dbg("/ Cached [%s] = %s", id, value)
|
||
|
else
|
||
|
opt_temp[id] = nil -- no need to cache current values
|
||
|
print_dbg("~ Cleared cache [%s]", id)
|
||
|
end
|
||
|
|
||
|
-- Change text color
|
||
|
local _id = s_gsub(id, _opt_, "_")
|
||
|
if self._Cap[_id] and self._Cap[_id]:IsShown() then
|
||
|
if (opt_temp[id] ~= nil) and (opt_temp[id] ~= opt_backup[id]) then
|
||
|
self._Cap[_id]:TextControl():SetTextColor( clr_o )
|
||
|
else
|
||
|
self._Cap[_id]:TextControl():SetTextColor( clr_g1 )
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Update state
|
||
|
self:UpdatePending()
|
||
|
if enable_ui_functors and v and v.on_selection_functor then
|
||
|
ui_mcm.exec(unpack(v.on_selection_functor),path,opt,value,v)
|
||
|
end
|
||
|
-- If the on_selection_functor attribute contains a functor, pass a copy of the same args to it
|
||
|
end
|
||
|
|
||
|
function UI_MCM:Stacker(path, opt, v)
|
||
|
-- This assure that each time a control is created, an unique callback id is given to it
|
||
|
-- Why? because in normal case, jumping between options removes the previous ones constantly, getting back to them will create new controls and assign them to the old ids
|
||
|
-- This is bad because callbacks are still attached to the old controls, any fresh controls that get assigned to those ids will be inactive as a result
|
||
|
-- My solution is this function to generate unique id each time a control is created
|
||
|
|
||
|
if (not v.stack) then v.stack = 0 end
|
||
|
v.stack = v.stack + 1
|
||
|
|
||
|
return cc( cc(path , opt) , v.stack)
|
||
|
end
|
||
|
|
||
|
function UI_MCM:UpdatePending()
|
||
|
local size = size_table(opt_temp)
|
||
|
if size > 0 then
|
||
|
self.pending:SetText( strformat( game.translate_string("ui_mm_warning_pending"), size) )
|
||
|
else
|
||
|
self.pending:SetText("")
|
||
|
end
|
||
|
end
|
||
|
|
||
|
|
||
|
------------------------------------------------------------
|
||
|
-- Callbacks
|
||
|
------------------------------------------------------------
|
||
|
function UI_MCM:OnButton_Accept()
|
||
|
--if self.Need_VidRestart then
|
||
|
-- self.message_box:InitMessageBox("message_box_yes_no")
|
||
|
-- self.message_box:SetText(string.format("%s %d% s", game.translate_string("ui_mm_confirm_changes"), 15, game.translate_string("mp_si_sec")))
|
||
|
-- self.message_box:ShowDialog(true)
|
||
|
--else
|
||
|
self:On_Accept()
|
||
|
--end
|
||
|
end
|
||
|
|
||
|
function UI_MCM:OnButton_Reset()
|
||
|
if self.last_path and self.last_curr_tree and is_not_empty(opt_temp) then
|
||
|
local to_reset
|
||
|
for id, val in pairs(opt_temp) do
|
||
|
if s_find(id,self.last_path) then
|
||
|
to_reset = true
|
||
|
opt_temp[id] = nil
|
||
|
|
||
|
local _id = s_gsub(id, _opt_, "_")
|
||
|
if self._Cap[_id] and self._Cap[_id]:IsShown() then
|
||
|
self._Cap[_id]:TextControl():SetTextColor( clr_g1 )
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
if (to_reset) then
|
||
|
self:UpdatePending()
|
||
|
self:Reset_opt(self.last_curr_tree, self.last_path)
|
||
|
print_dbg("% Sent callback (mcm_option_reset)")
|
||
|
SendScriptCallback(mcm_option_reset)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
|
||
|
end
|
||
|
|
||
|
function UI_MCM:OnButton_Default()
|
||
|
if self.last_path and self.last_curr_tree then
|
||
|
self:Reset_opt(self.last_curr_tree, self.last_path, { def = true })
|
||
|
print_dbg("% Sent callback (mcm_option_restore_default)")
|
||
|
SendScriptCallback(mcm_option_restore_default)
|
||
|
end
|
||
|
|
||
|
|
||
|
end
|
||
|
|
||
|
function UI_MCM:OnButton_Cancel()
|
||
|
if is_not_empty(opt_temp) then
|
||
|
self.message_box:InitMessageBox("message_box_yes_no")
|
||
|
self.message_box:SetText(game.translate_string("ui_mm_discard_changes"))
|
||
|
self.message_box:ShowDialog(true)
|
||
|
else
|
||
|
self:On_Cancel()
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function UI_MCM:On_Accept()
|
||
|
self.Changes ={}
|
||
|
for id, val in pairs(opt_temp) do
|
||
|
|
||
|
local v = self:GetOption(id)
|
||
|
|
||
|
-- Cache the changes
|
||
|
if (not v.curr) then
|
||
|
print_dbg("- Saved [%s] := %s", id, val)
|
||
|
axr_main.config:w_value(opt_section, id, val)
|
||
|
if key_by_path[id] then
|
||
|
update_conflict(id, val)
|
||
|
end
|
||
|
self.Save_AXR = true
|
||
|
end
|
||
|
|
||
|
self.Change_Done = true
|
||
|
self.Changes[id] = true
|
||
|
-- Execute functors if found
|
||
|
if v.functor then
|
||
|
if v.postcondition then
|
||
|
if exec(unpack(v.postcondition))then
|
||
|
print_dbg("- Executing postcondition functor of [%s]",id)
|
||
|
v.functor[#v.functor+1] = val
|
||
|
exec(unpack(v.functor))
|
||
|
v.functor[#v.functor] = nil
|
||
|
end
|
||
|
else
|
||
|
print_dbg("- Executing functor of [%s]",id)
|
||
|
v.functor[#v.functor+1] = val
|
||
|
exec(unpack(v.functor))
|
||
|
v.functor[#v.functor] = nil
|
||
|
end
|
||
|
end
|
||
|
|
||
|
if v.type == "key_bind" then
|
||
|
self.key_changed = true
|
||
|
end
|
||
|
|
||
|
|
||
|
-- See if it needs restart
|
||
|
if v.restart then
|
||
|
self.Need_Restart = true
|
||
|
end
|
||
|
if v.vid then
|
||
|
self.Need_VidRestart = true
|
||
|
end
|
||
|
|
||
|
-- Send callback and apply changes
|
||
|
if v.cmd then
|
||
|
local cmd_value = val
|
||
|
if type(cmd_value) == "boolean" then
|
||
|
if v.bool_to_num then
|
||
|
cmd_value = cmd_value and "1" or "0"
|
||
|
else
|
||
|
cmd_value = cmd_value and "on" or "off"
|
||
|
end
|
||
|
end
|
||
|
|
||
|
print_dbg("- Saved CMD [%s] := %s", id, cmd_value)
|
||
|
exec_console_cmd(v.cmd .. " " .. cmd_value)
|
||
|
self.Save_CFG = true
|
||
|
end
|
||
|
end
|
||
|
|
||
|
|
||
|
|
||
|
-- Save axr_options
|
||
|
if self.Save_AXR then
|
||
|
axr_main.config:save()
|
||
|
self.Save_AXR = false
|
||
|
end
|
||
|
|
||
|
|
||
|
|
||
|
-- appdata
|
||
|
if self.Save_CFG then
|
||
|
print_dbg("- Saved CFG")
|
||
|
exec_console_cmd("cfg_save")
|
||
|
--exec_console_cmd("cfg_save tmp")
|
||
|
end
|
||
|
|
||
|
if level.present() and self.Change_Done then
|
||
|
print_dbg("% Sent callback (on_option_change)")
|
||
|
SendScriptCallback("on_option_change", true)
|
||
|
end
|
||
|
if AddScriptCallback and self.Change_Done then
|
||
|
print_dbg("% Sent callback (mcm_option_change)")
|
||
|
SendScriptCallback("mcm_option_change", self.Changes )
|
||
|
end
|
||
|
|
||
|
-- Clear cache
|
||
|
empty_table(opt_temp)
|
||
|
empty_table(opt_backup)
|
||
|
|
||
|
if self.key_changed then --resort the conflict key list.
|
||
|
self.key_changed = false
|
||
|
table.sort(options[1].gr[2].gr[3].gr, function(a,b)
|
||
|
local t = {slide_kb = 1, desc_kb = 2, desc_kb2 = 3}
|
||
|
if t[a.id] and t[b.id] then
|
||
|
return t[a.id] < t[b.id]
|
||
|
end
|
||
|
if t[a.id] and not t[b.id] then
|
||
|
return true
|
||
|
end
|
||
|
if (not t[a.id]) and t[b.id] then
|
||
|
return false
|
||
|
end
|
||
|
return display_key(get(a.kb_path)) < display_key(get(b.kb_path))
|
||
|
end)
|
||
|
init_opt_coder()
|
||
|
end
|
||
|
|
||
|
|
||
|
-- Exit
|
||
|
self:On_Cancel()
|
||
|
end
|
||
|
|
||
|
function UI_MCM:On_Cancel()
|
||
|
|
||
|
self.owner:ShowDialog(true)
|
||
|
self:HideDialog()
|
||
|
self.owner:Show(true)
|
||
|
|
||
|
-- Restart vid
|
||
|
if self.Need_VidRestart then
|
||
|
exec_console_cmd("vid_restart")
|
||
|
end
|
||
|
|
||
|
if self.Need_Restart then
|
||
|
self.owner:SetMsg( game.translate_string("ui_mm_change_done_restart") , 7 )
|
||
|
self.message_box:InitMessageBox("message_box_restart_game")
|
||
|
self.message_box:ShowDialog(true)
|
||
|
|
||
|
elseif self.Change_Done then
|
||
|
self.owner:SetMsg( game.translate_string("ui_mm_change_done") , 5 )
|
||
|
end
|
||
|
|
||
|
self.Change_Done = false
|
||
|
self.Need_VidRestart = false
|
||
|
self.Need_Restart = false
|
||
|
self.Save_CFG = false
|
||
|
|
||
|
|
||
|
Unregister_UI("UI_MCM")
|
||
|
end
|
||
|
|
||
|
function UI_MCM:On_Discard()
|
||
|
empty_table(opt_temp)
|
||
|
if (self.last_path and self.last_curr_tree) then
|
||
|
self:UpdatePending()
|
||
|
self:Reset_opt(self.last_curr_tree, self.last_path)
|
||
|
print_dbg("% Sent callback (mcm_option_discard)")
|
||
|
SendScriptCallback(mcm_option_discard)
|
||
|
end
|
||
|
self:On_Cancel()
|
||
|
end
|
||
|
|
||
|
function UI_MCM:OnKeyboard(dik, keyboard_action)
|
||
|
local res = CUIScriptWnd.OnKeyboard(self,dik,keyboard_action)
|
||
|
if (res == false) then
|
||
|
local bind = dik_to_bind(dik)
|
||
|
if keyboard_action == ui_events.WINDOW_KEY_PRESSED then
|
||
|
|
||
|
if dik == DIK_keys.DIK_ESCAPE then
|
||
|
if self.k_binder then
|
||
|
self.k_binder(-1)
|
||
|
else
|
||
|
self:OnButton_Cancel()
|
||
|
end
|
||
|
elseif self.k_binder and display_key(dik) then
|
||
|
self.k_binder(dik)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
if self.k_binder and ((time_continual() - self.k_timer) > 500) then --clear k_binder on any other input after half a second.
|
||
|
self.k_binder()
|
||
|
end
|
||
|
return res
|
||
|
end
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
function trader_cond(x)
|
||
|
if x == 'get' then
|
||
|
-- printf('@@@ returning %s', alife_storage_manager.get_state().trader_buy_condition_override or "0 (DEFAULT)")
|
||
|
return alife_storage_manager.get_state().trader_buy_condition_override or 0
|
||
|
else
|
||
|
-- printf('@@@ setting %s', opt_temp["gameplay/economy_diff/condition_buy_override"] or '0 (DEFAULT)')
|
||
|
alife_storage_manager.get_state().trader_buy_condition_override = opt_temp["gameplay/economy_diff/condition_buy_override"] or 0
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function store_in_save()
|
||
|
printf("MCM Error: dph_mcm_save_storage.script not found")
|
||
|
end
|
||
|
|
||
|
if dph_mcm_save_storage and dph_mcm_save_storage.register_module then
|
||
|
store_in_save = dph_mcm_save_storage.register_module
|
||
|
end
|
||
|
|
||
|
function on_game_start()
|
||
|
RegisterScriptCallback("on_key_release",on_key_release)
|
||
|
RegisterScriptCallback("on_key_press",on_key_press)
|
||
|
end
|
||
|
|
||
|
------------------------------------------------------------
|
||
|
-- Tutorial:Table of Contents:
|
||
|
------------------------------------------------------------
|
||
|
--[[
|
||
|
Use the [-] in the margin in notpad++ to colapse sections for easier
|
||
|
navigation
|
||
|
|
||
|
1. How to read these options in your script (RavenAscendant)
|
||
|
2. How to add new options (Tronex orginal turotial from ui_options)
|
||
|
3. How to make your script talk to MCM
|
||
|
4. Examples
|
||
|
|
||
|
]]--
|
||
|
|
||
|
------------------------------------------------------------
|
||
|
-- Tutorial: How to read these options in your script:
|
||
|
------------------------------------------------------------
|
||
|
--[[
|
||
|
Feilds in [backets] are described in "How to add new options"
|
||
|
|
||
|
First a bit about setting up your options. Nine times out of ten you don't need any functors.
|
||
|
MCM will read the curent value of the setting from axr_options with out a [curr] functor and
|
||
|
will write values to axr_options if no [functor] is provided. For simple global settings this
|
||
|
will be more than adequate.
|
||
|
|
||
|
The easiest way to read your setting is call ui_mcm.get(path) where path is the id fields of
|
||
|
the nested tables down to the option in the table you returned in on_mcm_load() . Mostlikely
|
||
|
this will take the form of "modname/settingname" but you can break your settings into multiple
|
||
|
panels if you want resulting in a loinger path. see the options section of axr_configs for how
|
||
|
anomaly options menu translates into paths, same system is used here.
|
||
|
ui_mcm.get(path) is cached and fails back to the value you set in [def=]
|
||
|
|
||
|
Just like ui_options when MCM applies a settings change it sends an on_option_change callback
|
||
|
you can use this to do a one time read of your options into variables in your script.
|
||
|
you can either get the values with ui_mcm.get(path) or read them directly from axr_configs
|
||
|
like so:
|
||
|
axr_main.config:r_value("mcm", path, type,default) --see _g for how r_value functions.
|
||
|
|
||
|
|
||
|
Examples of when you might want to use functors:
|
||
|
Saving mod settings to the save game file instead of globaly to axr_configs
|
||
|
You are building your settings dynaicaly can't rely on the path being consistant.
|
||
|
You otherwise like to over complicate things.
|
||
|
|
||
|
]]--
|
||
|
|
||
|
|
||
|
------------------------------------------------------------
|
||
|
-- Tutorial: How to add new options:
|
||
|
------------------------------------------------------------
|
||
|
--[[
|
||
|
|
||
|
The only thing changed from this as compared to the version in ui_options is changing all ocurances of ui_mm_ to ui_mcm_
|
||
|
------------------------------------------------------------------------------------------------
|
||
|
Option name:
|
||
|
script will read option name (id) and show it automatically, naming should be like this: "ui_mcm_[tree_1]_[tree_2]_[tree_...]_[option]"
|
||
|
[tree_n] and [option] are detemined from option path inside the table
|
||
|
Example: options["video"]["general"]["renderer"] name will come from "ui_mcm_video_general_renderer" string
|
||
|
|
||
|
------------------------------------------------------------------------------------------------
|
||
|
Option description:
|
||
|
option description can show up in the hint window if its defined by its name followed by "_desc"
|
||
|
Example: option with a name of "ui_mcm_video_general_renderer" will show its hint if "ui_mcm_video_general_renderer_desc" exists
|
||
|
|
||
|
|
||
|
------------------------------------------------------------------------------------------------
|
||
|
Parameters of option Tree:
|
||
|
------------------------------------------------------------------------------------------------
|
||
|
|
||
|
- [id]
|
||
|
- Define: (string)
|
||
|
To give a tree its own identity
|
||
|
|
||
|
- [sh]
|
||
|
- Define: (boolean)
|
||
|
used to detemine that the sub-tree tables are actual list of options to set and show
|
||
|
|
||
|
- [text]
|
||
|
- Define: (string)
|
||
|
To over ride the display text for the tree in the tree select
|
||
|
|
||
|
- [precondition]
|
||
|
- Define: ( table {function, parameters} )
|
||
|
don't show tree options if its precondition return false
|
||
|
|
||
|
- [output]
|
||
|
- Define: (string)
|
||
|
Text to show when precondition fails
|
||
|
|
||
|
- [gr]
|
||
|
- Define: ( table { ... } )
|
||
|
Table of a sub-tree or options list
|
||
|
|
||
|
- [apply_to_all]
|
||
|
- Define: (boolean)
|
||
|
when you have options trees with similar options and group, you can use this to add "Apply to All" button to each option
|
||
|
clicking it will apply option changes to this option in all other trees from same group
|
||
|
you must give these a tree a group id
|
||
|
|
||
|
- [id_gr]
|
||
|
- Define: (string)
|
||
|
allows you to give options tree a group id, to connect them when you want to use "Apply to all" button for options
|
||
|
|
||
|
------------------------------------------------------------------------------------------------
|
||
|
Parameters of options:
|
||
|
------------------------------------------------------------------------------------------------
|
||
|
|
||
|
----------------------
|
||
|
Critical parameters:
|
||
|
--------------------
|
||
|
These parameters must be declared for elements
|
||
|
|
||
|
[id]
|
||
|
- Define: (string)
|
||
|
Option identity/name.
|
||
|
Option get stored in axr_main or called in other sripts by its path (IDs of sub trees and option):
|
||
|
Example: ( tree_1_id/tree_2_id/.../option_id )
|
||
|
The top id in the table you return to MCM (tree_1_id in the above example) should be as unique as
|
||
|
posible to prevent it from conflicting with another mod.
|
||
|
|
||
|
[type]
|
||
|
- Define: (string)
|
||
|
- Possible values:
|
||
|
- Option elements:
|
||
|
"check" : Option, check box, either ON or OFF
|
||
|
"list" : Option, list of strings, useful for options with too many selections
|
||
|
"input" : Option, input box, you can type a value of your choice
|
||
|
"radio_h" : Option, radio box, select one out of many choices. Can fit up to 8 selections (Horizental layout)
|
||
|
"radio_v" : Option, radio box, select one out of many choices. Can fit up any number of selections (Vertical layout)
|
||
|
"track" : Option, track bar, easy way to control numric options with min/max values (can be used only if [val] = 2)
|
||
|
"key_bind" : Option, button that registers a keypress after being clicked. (See suplimental instructions below)
|
||
|
- Support elements:
|
||
|
"line" : Support element, a simple line to separate things around
|
||
|
"image" : Support element, 563x50 px image box, full-area coverage
|
||
|
"slide" : Support element, image box on left, text on right
|
||
|
"title" : Support element, title (left/right/center alignment)
|
||
|
"desc" : Support element, description (left alignment)
|
||
|
|
||
|
|
||
|
----------------------
|
||
|
Dependable parameters:
|
||
|
----------------------
|
||
|
These parameters must be declared when other specific parameters are declared already. They work along with them
|
||
|
|
||
|
[val]
|
||
|
- Define: (number)
|
||
|
- Used by: option elements: ALL
|
||
|
Option's value type: 0. string | 1. boolean | 2. float
|
||
|
It tells the script what kind of value the option is storing / dealing with
|
||
|
|
||
|
[cmd]:
|
||
|
- Define: (string)
|
||
|
- Used by: option elements: ALL (needed if you want to control a console command)
|
||
|
Tie an option to a console command, so when the option value get changed, it get applied directly to the command
|
||
|
The option will show command's current value
|
||
|
NOTE:
|
||
|
cmd options don't get cached in axr_options, instead they get stored in appdata/user.ltx
|
||
|
[def] parameter is not needed here since we engine applies default values to commands if they don't exist in user.ltx automatically
|
||
|
|
||
|
[def]
|
||
|
- Define: (boolean) / (number) / (string) / ( table {function, parameters} )
|
||
|
- Used by: option elements: ALL (not needed if [cmd] is used)
|
||
|
Default value of an option
|
||
|
when no cached values are found in axr_options, the default value will be used
|
||
|
|
||
|
[min]
|
||
|
- Define: (number)
|
||
|
- Used by: option elements: "input" / "track": (only if [val] = 2)
|
||
|
Minimum viable value for an option, to make sure a value stays in range
|
||
|
|
||
|
[max]
|
||
|
- Define: (number)
|
||
|
- Used by: option elements: "input" / "track": (only if [val] = 2)
|
||
|
Maximum viable value for an option, to make sure a value stays in range
|
||
|
|
||
|
[step]
|
||
|
- Define: (number)
|
||
|
- Used by: option elements: "track": (only if [val] = 2)
|
||
|
How much a value can be increased/decreased in one step
|
||
|
|
||
|
[content]
|
||
|
- Define: ( table {double pairs} ) / ( table {function, parameters} )
|
||
|
- Used by: option elements: "list" / "radio_h" / "radio_v":
|
||
|
Delcares option's selection list
|
||
|
Pairs: { value of the selection, string to show on UI }
|
||
|
Example: content= { {0,"off"} , {1,"half"} , {2,"full"}}
|
||
|
So the list or radio option will show 3 selections (translated strings): (ui_mcm_lst_off) and (ui_mcm_lst_half) and (ui_mcm_lst_full)
|
||
|
When you select one and it get applied, the assosiated value will get applied
|
||
|
So picking the first one will pass ( 0 )
|
||
|
Because all lists and radio button elements share the same prefix, "ui_mcm_lst_" it is important that you not use common words like
|
||
|
the ones in the example above. Make your element names unique.
|
||
|
|
||
|
[link]
|
||
|
- Define: (string)
|
||
|
- Used by: support elements: "image" / "slide"
|
||
|
Link to texture you want to show
|
||
|
|
||
|
[text]
|
||
|
- Define: (string)
|
||
|
- Used by: support elements: "slide" / "title" / "desc"
|
||
|
String to show near the image, it will be translated
|
||
|
|
||
|
----------------------
|
||
|
Optional parameters:
|
||
|
----------------------
|
||
|
These parameters are completely optionals, and can be used for custom stuff
|
||
|
|
||
|
[force_horz]
|
||
|
- Define: (boolean)
|
||
|
- Used by: option elements: "radio_h"
|
||
|
Force the radio buttons into horizental layout, despite their number
|
||
|
|
||
|
[no_str]
|
||
|
- Define: (boolean)
|
||
|
- Used by: option elements: "list" / "radio_h" / "radio_v" / "track"
|
||
|
Usually, the 2nd key of pairs in content table are strings to show on the UI, by translating "opt_str_lst_(string)"
|
||
|
when we set [no_str] to true, it will show the string fromm table as it is without translations or "opt_str_lst_"
|
||
|
For TrackBars: no_str won't show value next to the slider
|
||
|
|
||
|
[prec]
|
||
|
- Define: (number)
|
||
|
- Used by: option elements: "track"
|
||
|
allowed number of zeros in a number
|
||
|
|
||
|
[precondition]
|
||
|
- Define: ( table {function, parameters} )
|
||
|
- Used by: option elements: ALL
|
||
|
Show the option on UI if the precondition function returns true
|
||
|
|
||
|
[functor]
|
||
|
- Define: ( table {function, parameters} )
|
||
|
- Used by: option elements: ALL
|
||
|
Execute a function when option's changes get applied
|
||
|
The value of the option is added to the end of the parameters list.
|
||
|
|
||
|
[postcondition]
|
||
|
- Define: ( table {function, parameters} )
|
||
|
- Used by: option elements: ALL, with the defined functor
|
||
|
Option won't execute its functor when changes are applied, unless if the postcondition function returns true
|
||
|
|
||
|
[ui_hook_functor]
|
||
|
- Define: ( table {function, parameters} )
|
||
|
- Used by: option elements: ALL except keybind, with the defined functor
|
||
|
- Used by: support elements: ALL
|
||
|
Passes handling elements and metadata back to the defined functor for UI customization
|
||
|
For ADVANCED scripting use only - see below
|
||
|
|
||
|
[on_selection_functor]
|
||
|
- Define: ( table {function, parameters} )
|
||
|
- Used by option elements: ALL, with the defined functor
|
||
|
Execute a defined functor upon any live selection of a new option value
|
||
|
For ADVANCED scripting use only - see below
|
||
|
|
||
|
[curr]
|
||
|
- Define: ( table {function, parameters} )
|
||
|
- Used by: option elements: ALL
|
||
|
get current value of an option by executing the declared function, instead of reading it from axr_options.ltx
|
||
|
|
||
|
[hint] (as of MCM 1.6.0 this will actualy show _desc strings)
|
||
|
- Define: (string)
|
||
|
- Used by: option elements: ALL
|
||
|
Override default name / desc rule to replace the translation of an option with a custom one, should be set without "ui_mcm_" and "_desc"
|
||
|
Example: { hint = "alife_warfare_capture"} will force the script to use "ui_mcm_alife_warfare_capture" and "ui_mcm_alife_warfare_capture_desc" for name and desc of the option
|
||
|
|
||
|
[clr]
|
||
|
- Define: ( table {a,r,b,g} )
|
||
|
- Used by: support elements: "title" / "desc"
|
||
|
determines the color of the text
|
||
|
|
||
|
[stretch]
|
||
|
- Define: (boolean)
|
||
|
- Used by: support elements: "slide"
|
||
|
force the texture to stretch or not
|
||
|
|
||
|
[pos]
|
||
|
- Define: ( table {x,y} )
|
||
|
- Used by: support elements: "slide"
|
||
|
custom pos for the texture
|
||
|
|
||
|
[size]
|
||
|
- Define: ( table {w,z} )
|
||
|
- Used by: support elements: "slide"
|
||
|
custom size for the texture
|
||
|
|
||
|
[align]
|
||
|
- Define: (string) "l" "r" "c"
|
||
|
- Used by: support elements: "title"
|
||
|
determines the alignment of the title
|
||
|
|
||
|
[spacing]
|
||
|
- Define: (number)
|
||
|
- Used by: support elements: "slide"
|
||
|
height offset to add extra space
|
||
|
|
||
|
[borderless]
|
||
|
- Define: (boolean)
|
||
|
- Used by: support elements: "slide"
|
||
|
disables the border lines above and below the slide
|
||
|
--]]
|
||
|
|
||
|
------------------------------------------------------------
|
||
|
-- Tutorial: How to make your script talk to MCM:
|
||
|
------------------------------------------------------------
|
||
|
|
||
|
--[[
|
||
|
MCM looks for scripts with names ending in mcm: *mcm.script you can use an _ to sperate it from the
|
||
|
rest of the name of your script but it isn't necessary.
|
||
|
In those scripts MCM will execute the function on_mcm_load()
|
||
|
In order for options to be added to MCM, on_mcm_load() must return a valid options tree
|
||
|
as described in the tutorial here, used in the ui_options script and shown in the examples below
|
||
|
An aditioanl retun value of a string naming a collection is optional. The string will be used to create a catagory to which the
|
||
|
the options menues of mods returning the same collection name will be added to. This is to allow for
|
||
|
modular mods to have the settings for each module be grooped under a common heading. Note the collection name becomes the root
|
||
|
name in your settings path and translation strings. As a root name care should be taken to ensure it will not conflict with another
|
||
|
mod.
|
||
|
|
||
|
|
||
|
]]--
|
||
|
|
||
|
---------------------------------------------------------------------------------------
|
||
|
-- Tutorial: Using dph-hcl's script for save game specific MCM options
|
||
|
---------------------------------------------------------------------------------------
|
||
|
|
||
|
--[[
|
||
|
dph-hcl's orginal script from https://www.moddb.com/mods/stalker-anomaly/addons/151-mcm-13-mcm-savefile-storage
|
||
|
is included un altered and can be used as described and documented in thier mod and script
|
||
|
|
||
|
Aditionaly for convinence the function has been aliased here as ui_mcm.store_in_save(path)
|
||
|
this function can be called safely as MCM will simply print an error if dph-hcl's script is missing
|
||
|
|
||
|
To make an option be stored in a save game instead of globaly call ui_mcm.store_in_save(path)
|
||
|
path can be a full option path like is used by ui_mcm.get(path) or a partial path
|
||
|
If a partial path is used all options that caontain that path will be stored in the savegame
|
||
|
partial paths must start with a valid root and cannot end with a /
|
||
|
|
||
|
In the second example below the second checkbox in the second options menu would be stored buy
|
||
|
ui_mcm.store_in_save("example_example/example_two/2check2")
|
||
|
In the same example storing all options (both checks) in the first option menu would be:
|
||
|
ui_mcm.store_in_save("example_example/example_one")
|
||
|
Lastly storing all of the options for your mod would look like:
|
||
|
ui_mcm.store_in_save("example_example")
|
||
|
|
||
|
ui_mcm.store_in_save(path) can be called at any time. The easyiest is probably in on_mcm_load()
|
||
|
however it could be done as late as on_game_start() if one wanted to have an MCM option for global vs save specific options storing
|
||
|
(calling ui_mcm.get(path) in on_mcm_load() is a bad idea don't do that )
|
||
|
]]--
|
||
|
|
||
|
|
||
|
|
||
|
---------------------------------------------------------------------------------------
|
||
|
-- Tutorial: Additional information on key_bind
|
||
|
---------------------------------------------------------------------------------------
|
||
|
|
||
|
--[[
|
||
|
Key binds are gathered into two meta lists for the users convienance. This means it is very important that your translation strings
|
||
|
clearly identify what the key does and ideally it should be clear what addon the keybiind is from.
|
||
|
|
||
|
The value stored by the key bind is the DIK_keys value of the key. Same number that will be given to the key related callbacks.
|
||
|
|
||
|
val must be set to 2 and is still manditory.
|
||
|
|
||
|
curr and functor are not curently supported. Post an issue on github describing the usecase you had for them, if it's cool enough they might get fixed.
|
||
|
|
||
|
Old (pre 1.6.0) versions of MCM will not display key_bind and calling ui_mcm.get for it will return nil, take that into acount if you want reverse compatablity.
|
||
|
|
||
|
]]--
|
||
|
|
||
|
|
||
|
---------------------------------------------------------------------------------------
|
||
|
-- Tutorial: Additional Key Bind utilities
|
||
|
---------------------------------------------------------------------------------------
|
||
|
|
||
|
--[[
|
||
|
MCM tracks the held status of the control and shift keys as well as a flag that is true when neither is pressed
|
||
|
ui_mcm.MOD_NONE ui_mcm.MOD_SHIFT and ui_mcm.MOD_CTRL
|
||
|
ui_mcm.get_mod_key(val) will return the above flags based on val: 0:MOD_NONE 1:MOD_SHIFT and 2:MOD_CTRL
|
||
|
If these somehow get latched they reset when Escape is pressed. Please report cases of latching.
|
||
|
|
||
|
MCM provides functions for detecting key double taps and keys that are held down, and single key presses that do not go on to be double or long presses.
|
||
|
ui_mcm.double_tap(id, key, [multi_tap]) should be called from on_key_press callback after you have filtered for your key
|
||
|
id: should be a unique identifier for your event, scriptname and a number work well:"ui_mcm01"
|
||
|
key: is of course they key passed into the on_key_press callback.
|
||
|
multi_tap: if true timer is updated instead of cleared allowing for the detection of triple/quad/ect taps
|
||
|
returns: true for a given id and key if less than X ms has elapsed since the last time it was called with that id and key (X is a user configurable value between 100ms and 1000 ms
|
||
|
returns false otherwise.
|
||
|
If multi_tap is false timer is reset when true is returned preventing the function from returning true twice in a row
|
||
|
If multi_tap is true the function will return true any time the gap between a call and the one before is within the window.
|
||
|
|
||
|
ui_mcm.key_hold(id, key, [repeat]) should be called from on_key_hold callback after you have filtered for your key
|
||
|
id: should be a unique identifer for your event, scriptname and a number work well:"ui_mcm01"
|
||
|
key: is the key passed into the on_key_hold callback.
|
||
|
repeat: Optional. time in seconds. If the key continues to be held down will return true again after this many seconds on a cycle.
|
||
|
|
||
|
when called from the on_key_hold callback it will return true after the key has been held down for Y ms (determined by applying a user defined multiplier to X above) and then again every repeat seconds if repeat is provided. sequence resets when key is released.
|
||
|
|
||
|
ui_mcm.simple_press(id, key, functor) should be called from on_key_press callback after you have filtered for your key
|
||
|
id: should be a unique identifier for your event, scrip name and a number work well:"ui_mcm01"
|
||
|
key: is the key passed into the on_key_press callback.
|
||
|
function: table {function, parameters}, to be executed when it is determined that the press is not long or double (or multi press in general)
|
||
|
|
||
|
Unlike the other two this does not return any thing but instead you give it a function to execute. Using this function you gain exclusivity, your event won't fire when the key is double(multi) taped or held (long press), at the cost of a small bit of input delay. This delay is dependent on the double tap window the used defines in the MCM Key Bind settings.
|
||
|
|
||
|
The following option entries have translation stings provided by MCM and are setup to be ignored by pre 1.6.0 versions of MCM
|
||
|
Note the keybind conflict identification in MCM does NOT look for these and reports conflict on the keybind value alone.
|
||
|
|
||
|
With shift and control, radio buton style
|
||
|
{id = "modifier", type = ui_mcm.kb_mod_radio, val = 2, def = 0, hint = "mcm_kb_modifier" , content= { {0,"mcm_kb_mod_none"} , {1,"mcm_kb_mod_shift"} , {2,"mcm_kb_mod_ctrl"},{3,"mcm_kb_mod_alt"}}},
|
||
|
With shift and control, list style
|
||
|
{id = "modifier", type = ui_mcm.kb_mod_list, val = 2, def = 0, hint = "mcm_kb_modifier" , content= { {0,"mcm_kb_mod_none"} , {1,"mcm_kb_mod_shift"} , {2,"mcm_kb_mod_ctrl"},{3,"mcm_kb_mod_alt"}}},
|
||
|
|
||
|
|
||
|
Single double or long press, , radio buton style
|
||
|
{id = "mode", type = ui_mcm.kb_mod_radio, val = 2, def = 0, hint = "mcm_kb_mode" , content= { {0,"mcm_kb_mode_press"} , {1,"mcm_kb_mode_dtap"} , {2,"mcm_kb_mode_hold"}}},
|
||
|
Single double or long press, , radio buton style
|
||
|
{id = "mode", type = ui_mcm.kb_mod_list, val = 2, def = 0, hint = "mcm_kb_mode" , content= { {0,"mcm_kb_mode_press"} , {1,"mcm_kb_mode_dtap"} , {2,"mcm_kb_mode_hold"}}},
|
||
|
|
||
|
An example script making use of all of these can be found at: https://github.com/RAX-Anomaly/MiniMapToggle/blob/main/gamedata/scripts/mini_map_toggle_mcm.script
|
||
|
]]--
|
||
|
|
||
|
------------------------------------------------------------
|
||
|
-- Tutorial: Examples:
|
||
|
------------------------------------------------------------
|
||
|
|
||
|
-- these examples can all be copied to a blank script example_mcm.script and ran.
|
||
|
|
||
|
-- A simple menu with a title slide and check boxes.
|
||
|
--[[
|
||
|
|
||
|
function on_mcm_load()
|
||
|
op = { id= "example_example" ,sh=true ,gr={
|
||
|
{ id= "slide_example_example" ,type= "slide" ,link= "AMCM_Banner.dds" ,text= "ui_mcm_title_example_example" ,size= {512,50} ,spacing= 20 },
|
||
|
{id = "check1", type = "check", val = 1, def = false},
|
||
|
{id = "check2", type = "check", val = 1, def = false},
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return op
|
||
|
end
|
||
|
]]--
|
||
|
|
||
|
-- A a tree with a root containing three menues with a title slide and check boxes.
|
||
|
--[[
|
||
|
|
||
|
function on_mcm_load()
|
||
|
op = { id= "example_example" , ,gr={
|
||
|
|
||
|
{ id= "example_one" ,sh=true ,gr={
|
||
|
{ id= "slide_example_example" ,type= "slide" ,link= "AMCM_Banner.dds" ,text= "ui_mcm_title_example_example" ,size= {512,50} ,spacing= 20 },
|
||
|
{id = "1check1", type = "check", val = 1, def = false},
|
||
|
{id = "1check2", type = "check", val = 1, def = false},
|
||
|
}
|
||
|
},
|
||
|
{ id= "example_two" ,sh=true ,gr={
|
||
|
{ id= "slide_example_example" ,type= "slide" ,link= "AMCM_Banner.dds" ,text= "ui_mcm_title_example_example" ,size= {512,50} ,spacing= 20 },
|
||
|
{id = "2check1", type = "check", val = 1, def = false},
|
||
|
{id = "2check2", type = "check", val = 1, def = false},
|
||
|
}
|
||
|
},
|
||
|
{ id= "example_three" ,sh=true ,gr={
|
||
|
{ id= "slide_example_example" ,type= "slide" ,link= "AMCM_Banner.dds" ,text= "ui_mcm_title_example_example" ,size= {512,50} ,spacing= 20 },
|
||
|
{id = "3check1", type = "check", val = 1, def = false},
|
||
|
{id = "3check2", type = "check", val = 1, def = false},
|
||
|
}
|
||
|
},
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
return op
|
||
|
end
|
||
|
]]--
|
||
|
|
||
|
-- Two scripts with a simple menu with a title slide and check boxes, that will be added to a collection named "collection_example"
|
||
|
--[[
|
||
|
-- example1_mcm.script
|
||
|
function on_mcm_load()
|
||
|
op = { id= "first_example" ,sh=true ,gr={
|
||
|
{ id= "slide_first_example" ,type= "slide" ,link= "AMCM_Banner.dds" ,text= "ui_mcm_title_first_example" ,size= {512,50} ,spacing= 20 },
|
||
|
{id = "check1", type = "check", val = 1, def = false},
|
||
|
{id = "check2", type = "check", val = 1, def = false},
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return op, "collection_example"
|
||
|
end
|
||
|
|
||
|
-- example2_mcm.script.
|
||
|
function on_mcm_load()
|
||
|
op = { id= "second_example" ,sh=true ,gr={
|
||
|
{ id= "slide_second_example" ,type= "slide" ,link= "AMCM_Banner.dds" ,text= "ui_mcm_title_second_example" ,size= {512,50} ,spacing= 20 },
|
||
|
{id = "check1", type = "check", val = 1, def = false},
|
||
|
{id = "check2", type = "check", val = 1, def = false},
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return op, "collection_example"
|
||
|
end
|
||
|
]]--
|
||
|
|
||
|
--[[=====================================================================
|
||
|
Tutorial: UI Functors
|
||
|
*** ADVANCED SCRIPTING USE ONLY ***
|
||
|
-- ======================================================================
|
||
|
|
||
|
The UI functors "ui_hook_functor" and "on_selection_functor", respectively, pass along UI element handlers and a trap for unsaved value changes. They are recommended only for advanced scripters who fully understand how to work with UI elements and want to customize their menu beyond what the template can achieve.
|
||
|
These functors allow for dynamic customizations to MCM's UI elements at the element container level in response to user interactions. They can also compeltely hose your addon's entire MCM menu with a single error.
|
||
|
|
||
|
This is not a power to be used lightly. If you use these, you are assumed to know what you're doing. Please don't bug us with how-to questions.
|
||
|
|
||
|
- [ui_hook_functor]
|
||
|
- Define: ( table {function, parameters} )
|
||
|
- Used by: option elements: ALL (except keybind), with the defined functor
|
||
|
- Used by: support elements: ALL, with the defined functor
|
||
|
|
||
|
- Parameters passed: anchor, handlers, attrs, flags
|
||
|
Execute a function upon the initial registration of a UI element that occurs during on_mcm_load.
|
||
|
anchor - empty static container to use as an anchor for other elements
|
||
|
handlers - table containing any necessary UI control handlers, varies by element type
|
||
|
attrs - table of MCM attributes for the menu option
|
||
|
flags - flags.etype is always a string with the element type, other metadata varies by type
|
||
|
As with other functor attributes, the value of the "parameters" option in the table is added to the end of the parameters list.
|
||
|
|
||
|
- [on_selection_functor]
|
||
|
- Define: ( table {function, parameters} )
|
||
|
- Used by option elements: ALL, with the defined functor
|
||
|
- Parameters passed: path, opt, value, attrs
|
||
|
Execute a function on any unsaved/uncommitted change to an option value. Allows realtime response to user selections.
|
||
|
path - MCM path to changed option
|
||
|
opt - ID of the changed option
|
||
|
value - value of the uncommitted change
|
||
|
attrs - table of MCM attributes for the menu option
|
||
|
As with other functor attributes, the value of the "parameters" option in the table is added to the end of the parameters list.
|
||
|
|
||
|
New supporting callbacks:
|
||
|
mcm_option_reset - sent from OnButton_Reset
|
||
|
mcm_option_restore_default - sent from OnButton_Default
|
||
|
mcm_option_discard - sent from On_Discard
|
||
|
|
||
|
These callbacks all fire on their respective events resulting in cancellation of pending MCM changes. This lets you clear your own table of changes or do any other necessary cleanup at the end of the MCM session.
|
||
|
|
||
|
Each menu element type has its own set of handlers and flags that are passed in these two tables. They are documented below.
|
||
|
|
||
|
In addition to those listed, all elements pass the flag "etype" containing the name of the element (in brackets below).
|
||
|
|
||
|
SUPPORT ELEMENTS
|
||
|
[line]
|
||
|
Handlers
|
||
|
- line: Static container for the line element and its texture
|
||
|
|
||
|
[image]
|
||
|
Handlers
|
||
|
- pic: Static container for the image element and its texture
|
||
|
|
||
|
[slide]
|
||
|
Handlers
|
||
|
- pic: Static container for the slide image element and its texture
|
||
|
- txt: TextWnd container for the slide label text
|
||
|
|
||
|
[title]
|
||
|
Handlers
|
||
|
- title: Static container for the title element text
|
||
|
|
||
|
[desc]
|
||
|
Handlers
|
||
|
- desc: Static container for the description element text
|
||
|
|
||
|
OPTION ELEMENTS
|
||
|
All Option elements pass the following Flags:
|
||
|
- path: MCM menu path to the option
|
||
|
- opt: MCM ID for the option
|
||
|
|
||
|
[check]
|
||
|
Handlers
|
||
|
- cap: Static container for the localized text caption
|
||
|
- ctrl: Static container for the checkbox input control
|
||
|
|
||
|
[list]
|
||
|
Handlers
|
||
|
- cap: Static container for the localized text caption
|
||
|
- ctrl: Static container for the dropdown list input control
|
||
|
|
||
|
[input]
|
||
|
Handlers
|
||
|
- cap: Static container for the localized text caption
|
||
|
- ctrl: Static container for the input box control
|
||
|
|
||
|
[track]
|
||
|
Handlers
|
||
|
- cap: Static container for the localized text caption
|
||
|
- ctrl: Static container for the track input control
|
||
|
|
||
|
[radio]
|
||
|
Handlers
|
||
|
- cap: Static container for the localized text caption
|
||
|
- txt: Table of TextWnd containers for the radio options
|
||
|
- ctrltbl: Table of radio button input controls
|
||
|
Flags
|
||
|
- num_opts: the number of radio button options in the above tables
|
||
|
- hvtype: string "horz" or "vert" denoting layout style
|
||
|
|
||
|
-- ======================================================================
|
||
|
Tutorial: UI Functors: Example:
|
||
|
-- ======================================================================
|
||
|
You should be very familiar with how to work with UI containers in Anomaly before going any further.
|
||
|
|
||
|
This is a very simple example of how to use the UI functors. Assume this script is saved as element_test_mcm.script.
|
||
|
|
||
|
1. MCM calls do_ui_hook_functor for each of the menu elements that have it defined: a slide and a checkbox
|
||
|
|
||
|
2. do_ui_hook_functor creates a new empty static anchored on the image element to contain the icon texture, stores the handlers for it and the slide text, and changes the caption text for the checkbox
|
||
|
|
||
|
3. once during init, and each time the user clicks the checkbox, the icon and slide text will change in response
|
||
|
|
||
|
-- ====================================================================]]
|
||
|
|
||
|
--[[
|
||
|
function on_mcm_load()
|
||
|
op = {id= "ui_functor_test", sh=true, gr={
|
||
|
{id = "img_container", type= "slide",
|
||
|
ui_hook_functor= {element_test_mcm.do_ui_hook_functor}},
|
||
|
|
||
|
{id = "checkbox", type= "check", val = 1, def = true, hint = "",
|
||
|
ui_hook_functor= {element_test_mcm.do_ui_hook_functor},
|
||
|
on_selection_functor= {element_test_mcm.do_on_selection_functor}},
|
||
|
}
|
||
|
}
|
||
|
return op
|
||
|
end
|
||
|
|
||
|
xml = CScriptXmlInit()
|
||
|
xml:ParseFile("ui_mcm.xml")
|
||
|
local wnd,txt
|
||
|
|
||
|
function do_on_selection_functor(path, opt, value, attrs)
|
||
|
-- This function is called every time the user makes a selection or change
|
||
|
-- We also call it manually below, once during init, to set the default texture
|
||
|
if not (wnd and txt) then return end
|
||
|
local primary = value and true or false
|
||
|
if primary then
|
||
|
wnd:InitTexture("ui_inGame2_PDA_icon_Primary_mission")
|
||
|
txt:SetText("Primary Mission icon")
|
||
|
else
|
||
|
wnd:InitTexture("ui_inGame2_PDA_icon_Secondary_mission")
|
||
|
txt:SetText("Secondary Mission icon")
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function do_ui_hook_functor(anchor, handlers, attrs, flags)
|
||
|
-- This function should do any first-time setup for each menu option
|
||
|
if not (anchor and handlers and flags) then return end
|
||
|
local etype = flags and flags.etype
|
||
|
if etype == "slide" then
|
||
|
wnd = xml:InitStatic("elements:image",anchor)
|
||
|
wnd:SetWndSize(vector2():set(24,28))
|
||
|
local pos = wnd:GetWndPos()
|
||
|
wnd:SetWndPos(vector2():set(pos.x, pos.y + 28))
|
||
|
txt = handlers.txt
|
||
|
local curr_state = ui_mcm.get("ui_functor_test/checkbox")
|
||
|
do_on_selection_functor(nil,nil,curr_state)
|
||
|
elseif etype == "check" then
|
||
|
local newtext = "Click the checkbox to toggle the icon type"
|
||
|
local cap = handlers.cap
|
||
|
cap:TextControl():SetText(newtext)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
--]]
|