Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refacto: rewrite VisibleIf Feat: manage LogicalOperator #11

Open
wants to merge 6 commits into
base: devel
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .vscode/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -495,7 +495,8 @@
">",
"<",
">=",
"<="
"<=",
"isVisible"
],
"description": "The operator to use for the comparison."
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,8 @@
-- - Sending messages to the server to update setting values
---@class IMGUILayer: MetaClass
---@field mods table<string, ModSettings>
---@field private profiles table<string, table>
---@field private visibilityTriggers table<string, table>
IMGUILayer = _Class:Create("IMGUILayer", nil, {
mods = {},
visibilityTriggers = {}
mods = {}
})

MCMClientState = IMGUILayer:New()
Expand All @@ -35,10 +32,10 @@ function IMGUILayer:SetClientStateValue(settingId, value, modGUID)
return
end

self:UpdateVisibility(modGUID, settingId, value)

mod.settingsValues[settingId] = value

IMGUIVisibilityManager:UpdateVisibility_SettingChanged(modGUID, settingId)

-- Check if the setting is of type 'text'; no need to update the UI value for text settings
-- Also, doing so creates issues with the text input field
local blueprint = MCMAPI:GetModBlueprint(modGUID)
Expand All @@ -51,94 +48,7 @@ function IMGUILayer:SetClientStateValue(settingId, value, modGUID)
IMGUIAPI:UpdateSettingUIValue(settingId, value, modGUID)
end

-- TODO: this should be refactored to use OOP or at least be more modular, however I've wasted too much time on this already with Lua's nonsense, so I'm stashing and leaving it as is
function IMGUILayer:UpdateVisibility(modGUID, settingId, value)
if not modGUID or not settingId or value == nil then
return
end

local visibilityTriggers = self.visibilityTriggers[modGUID]
if not visibilityTriggers then
return
end

local settingTriggers = visibilityTriggers[settingId]
if not settingTriggers then
return
end

self:ProcessTriggers(settingTriggers, value, modGUID)
end

function IMGUILayer:ProcessTriggers(settingTriggers, value, modGUID)
for group, operators in pairs(settingTriggers) do
if not group or not operators then
MCMWarn(0, "Invalid visibility trigger group for mod '" ..
Ext.Mod.GetMod(modGUID).Info.Name ..
"'. Please contact " ..
Ext.Mod.GetMod(modGUID).Info.Author .. " about this issue.")
goto continue
end

self:ProcessOperators(group, operators, value, modGUID)

::continue::
end
end

function IMGUILayer:ProcessOperators(group, operators, value, modGUID)
for operator, triggerValue in pairs(operators) do
if not operator or triggerValue == nil then
MCMWarn(0, "Invalid visibility trigger operator or value for mod '" ..
Ext.Mod.GetMod(modGUID).Info.Name ..
"'. Please contact " ..
Ext.Mod.GetMod(modGUID).Info.Author .. " about this issue.")
goto continue
end

group.Visible = self:EvaluateCondition(operator, value, triggerValue, modGUID)

::continue::
end
end

function IMGUILayer:EvaluateCondition(operator, value, triggerValue, modGUID)
if operator == nil or value == nil or triggerValue == nil then
MCMWarn(0,
"Invalid comparison operator or values passed by mod '" ..
Ext.Mod.GetMod(modGUID).Info.Name ..
"' for visibility condition. Please contact " ..
Ext.Mod.GetMod(modGUID).Info.Author .. " about this issue.")
return false
end

local strValue, strTrigger = tostring(value), tostring(triggerValue)
local numValue, numTrigger = tonumber(value), tonumber(triggerValue)

local operators = {
["=="] = function(a, b) return a == b end,
["!="] = function(a, b) return a ~= b end,
["<="] = function(a, b) return a <= b end,
[">="] = function(a, b) return a >= b end,
["<"] = function(a, b) return a < b end,
[">"] = function(a, b) return a > b end
}

if operators[operator] then
if operator == "==" or operator == "!=" then
return operators[operator](strValue, strTrigger)
elseif numValue ~= nil and numTrigger ~= nil then
return operators[operator](numValue, numTrigger)
end
return false
end

MCMWarn(0, "Unknown comparison operator: " .. operator .. " for mod '" ..
Ext.Mod.GetMod(modGUID).Info.Name ..
"'. Please contact " ..
Ext.Mod.GetMod(modGUID).Info.Author .. " about this issue.")
return false
end

function IMGUILayer:GetClientStateValue(settingId, modGUID)
modGUID = modGUID or ModuleUUID
Expand Down Expand Up @@ -307,24 +217,17 @@ end
---@return nil
function IMGUILayer:CreateMainTable()
FrameManager:AddMenuSection(Ext.Loca.GetTranslatedString("h47d091e82e1a475b86bbe31555121a22eca7"))

local sortedModKeys = MCMUtils.SortModsByName(self.mods)
for _, modGUID in ipairs(sortedModKeys) do
self.visibilityTriggers[modGUID] = {}

local modName = self:GetModName(modGUID)
local modDescription = MCMUtils.AddNewlinesAfterPeriods(Ext.Mod.GetMod(modGUID).Info.Description)
FrameManager:addButtonAndGetModTabBar(modName, modDescription, modGUID)
self.mods[modGUID].widgets = {}

self:CreateModMenuFrame(modGUID)

local modSettings = self.mods[modGUID].settingsValues
for settingId, group in pairs(self.visibilityTriggers[modGUID]) do
self:UpdateVisibility(modGUID, settingId, modSettings[settingId])
end
end
FrameManager:setVisibleFrame(ModuleUUID)
IMGUIVisibilityManager:UpdateAllVisibility()
end

--- Get the mod name, considering custom blueprint names
Expand Down Expand Up @@ -395,7 +298,7 @@ function IMGUILayer:CreateModMenuSubTab(modTabs, tabInfo, modSettings, modGUID)
local tabSections = tabInfo:GetSections()
local tabSettings = tabInfo:GetSettings()

self:manageVisibleIf(modGUID, tabInfo, tab)
IMGUIVisibilityManager:manageVisibleIf(modGUID, tabInfo, tab)

if #tabSections > 0 then
for sectionIndex, section in ipairs(tabInfo:GetSections()) do
Expand All @@ -408,22 +311,6 @@ function IMGUILayer:CreateModMenuSubTab(modTabs, tabInfo, modSettings, modGUID)
end
end

function IMGUILayer:manageVisibleIf(modGUID, elementInfo, uiElement)
if elementInfo.VisibleIf and elementInfo.VisibleIf.Conditions then
for _, condition in ipairs(elementInfo.VisibleIf.Conditions) do
local settingIdTriggering = condition.SettingId
local operator = condition.Operator
local value = condition.ExpectedValue
self.visibilityTriggers[modGUID] = self.visibilityTriggers[modGUID] or {}
self.visibilityTriggers[modGUID][settingIdTriggering] = self.visibilityTriggers[modGUID]
[settingIdTriggering] or {}
self.visibilityTriggers[modGUID][settingIdTriggering][uiElement] = self.visibilityTriggers[modGUID]
[settingIdTriggering][uiElement] or {}
self.visibilityTriggers[modGUID][settingIdTriggering][uiElement][operator] = value
end
end
end

--- Create a new section for a mod in the MCM
---@param sectionIndex number The index of the section
---@param modGroup any The IMGUI group for the mod
Expand All @@ -442,7 +329,7 @@ function IMGUILayer:CreateModMenuSection(sectionIndex, modGroup, section, modSet
local sectionOptions = section:GetOptions()
local sectionGroup = modGroup:AddGroup(sectionId)

self:manageVisibleIf(modGUID, section, sectionGroup)
IMGUIVisibilityManager:manageVisibleIf(modGUID, section, sectionGroup)

local sectionContentElement = sectionGroup
if sectionOptions.IsCollapsible then
Expand Down Expand Up @@ -479,7 +366,7 @@ function IMGUILayer:CreateModMenuSetting(modGroup, setting, modSettings, modGUID
local widgetGroup = modGroup:AddGroup(setting:GetId())
local widget = createWidget(widgetGroup, setting, settingValue, modGUID)

self:manageVisibleIf(modGUID, setting, widgetGroup)
IMGUIVisibilityManager:manageVisibleIf(modGUID, setting, widgetGroup)

self.mods[modGUID].widgets[setting:GetId()] = widget
end
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
---@class IMGUIVisibilityManager: MetaClass
IMGUIVisibilityManager = _Class:Create("IMGUIVisibilityManager", nil, {
visibilityTriggers = {},
uiElementByName = {},
operators = {
["=="] = function(a, b) return a == b end,
["!="] = function(a, b) return a ~= b end,
["<="] = function(a, b) return a <= b end,
[">="] = function(a, b) return a >= b end,
["<"] = function(a, b) return a < b end,
[">"] = function(a, b) return a > b end,
["isVisible"] = function(a, b) return a == b end
}
})


---
function IMGUIVisibilityManager:manageVisibleIf(modGUID, elementInfo, uiElement)
if elementInfo.VisibleIf and elementInfo.VisibleIf.Conditions then
if self:EvaluateVisibleIf(modGUID, elementInfo.VisibleIf, elementInfo.Name or elementInfo.SectionName or elementInfo.TabName ) then -- change for generic Name ...
self.visibilityTriggers[modGUID] = self.visibilityTriggers[modGUID] or {}
self.visibilityTriggers[modGUID][uiElement] = elementInfo.VisibleIf
end
end

self.uiElementByName[modGUID] = self.uiElementByName[modGUID] or {}
self.uiElementByName[modGUID][elementInfo.TabId or elementInfo.SectionId or elementInfo.Id] = uiElement -- change for generic Id ...
end


function IMGUIVisibilityManager:EvaluateVisibleIf(modGUID, visibleIf, elementName)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can be local function in manageVisibleIf

local valid = true
-- control LogicalOperator is valid if present
if visibleIf.LogicalOperator ~= nil and visibleIf.LogicalOperator ~= "or" and visibleIf.LogicalOperator ~= "and" then
MCMWarn(0,
"Invalid LogicalOperator passed by mod '" ..
Ext.Mod.GetMod(modGUID).Info.Name ..
"' for visibility condition of '"..elementName.."'. Please contact " ..
Ext.Mod.GetMod(modGUID).Info.Author .. " about this issue.")
valid = false
end

-- Control Conditions
for _, condition in ipairs(visibleIf.Conditions) do
if condition.SettingId == nil then
MCMWarn(0,
"Invalid condition (no settingId) passed by mod '" ..
Ext.Mod.GetMod(modGUID).Info.Name ..
"' for visibility condition of '"..elementName.."'. Please contact " ..
Ext.Mod.GetMod(modGUID).Info.Author .. " about this issue.")
valid = false
end
if condition.Operator == nil then
MCMWarn(0,
"Invalid condition (no operator) passed by mod '" ..
Ext.Mod.GetMod(modGUID).Info.Name ..
"' for visibility condition of '"..elementName.."'. Please contact " ..
Ext.Mod.GetMod(modGUID).Info.Author .. " about this issue.")
valid = false
else
if self.operators[condition.Operator] == nil then
MCMWarn(0,
"Invalid condition (invalid operator: "..condition.Operator..") passed by mod '" ..
Ext.Mod.GetMod(modGUID).Info.Name ..
"' for visibility condition of '"..elementName.."'. Please contact " ..
Ext.Mod.GetMod(modGUID).Info.Author .. " about this issue.")
valid = false
end
end
if condition.ExpectedValue == nil then
MCMWarn(0,
"Invalid condition (no triggerValue) passed by mod '" ..
Ext.Mod.GetMod(modGUID).Info.Name ..
"' for visibility condition of '"..elementName.."'. Please contact " ..
Ext.Mod.GetMod(modGUID).Info.Author .. " about this issue.")
valid = false
end
end

return valid
end

function IMGUIVisibilityManager:UpdateAllVisibility()
for _, modGUID in ipairs(self.visibilityTriggers) do
for _, uiElement in pairs(self.visibilityTriggers[modGUID]) do
IMGUIVisibilityManager:UpdateVisibilityOfUiElement(modGUID, uiElement, false)
end
end
end


-- TODO: this should be refactored to use OOP or at least be more modular, however I've wasted too much time on this already with Lua's nonsense, so I'm stashing and leaving it as is
function IMGUIVisibilityManager:UpdateVisibility_SettingChanged(modGUID, settingId)
if not modGUID or not settingId then
return
end

local visibilityTriggers = self.visibilityTriggers[modGUID] or {}
for uiElement, visibleIf in pairs(visibilityTriggers) do
for _, condition in ipairs(visibleIf.Conditions) do
if condition.SettingId == settingId then
self:UpdateVisibilityOfUiElement(modGUID, uiElement, true)
break
end
end
end
end

function IMGUIVisibilityManager:UpdateVisibility_GroupVisibilityChanged(modGUID, uiElementChanged)
local visibilityTriggers = self.visibilityTriggers[modGUID] or {}
for uiElement, visibleIf in pairs(visibilityTriggers) do
for _, condition in ipairs(visibleIf.Conditions) do
if condition.Operator == "isVisible" and self.uiElementByName[modGUID][condition.SettingId] == uiElementChanged then
self:UpdateVisibilityOfUiElement(modGUID, uiElement, true)
break
end
end
end
end


function IMGUIVisibilityManager:UpdateVisibilityOfUiElement(modGUID, uiElement, transmitVisibilityChanged)
local visibilityTriggers = self.visibilityTriggers[modGUID] or {}
local visibleIf = visibilityTriggers[uiElement]
local logicalOperator = visibleIf.LogicalOperator or "and"
local visible = true
if logicalOperator == "or" then
visible = false
end

for _, condition in ipairs(visibleIf.Conditions) do
local settingIdTriggering = condition.SettingId
local operator = condition.Operator
local triggerValue = condition.ExpectedValue

local value = nil
if operator == "isVisible" then
value = self.uiElementByName[modGUID][settingIdTriggering].Visible
else
value = MCMClientState:GetClientStateValue(settingIdTriggering, modGUID)
end

local strValue, strTrigger = tostring(value), tostring(triggerValue)
local numValue, numTrigger = tonumber(value), tonumber(triggerValue)

local v = nil
if operator == "==" or operator == "!=" or operator == "isVisible" then
v = self.operators[operator](strValue, strTrigger)
elseif numValue ~= nil and numTrigger ~= nil then
v = self.operators[operator](numValue, numTrigger)
else
MCMWarn(0,
"Something go wrong, contact MCM support.") -- should never happen (trigegr is not empty at that point, value from widget too)
v = false
end

if v and (logicalOperator == "or") then
visible = true
break
elseif (not v) and (logicalOperator == "and") then
visible = false
break
end
end
local visibilityChanged = (uiElement.Visible ~= visible)
if transmitVisibilityChanged and visibilityChanged then
uiElement.Visible = visible
self:UpdateVisibility_GroupVisibilityChanged(modGUID, uiElement)
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ RequireFiles("Client/", {
"Components/_Init",
"Helpers/_Init",
"IMGUILayer",
"IMGUIVisibilityManager",
"IMGUIAPI",
"SubscribedEvents",
})