AtilioA / BG3-MCM

🔧 Baldur's Gate 3 Mod Configuration Menu provides an in-game UI that enables players to intuitively manage mod settings.
https://wiki.bg3.community/Tutorials/Mod-Frameworks/mod-configuration-menu
GNU Affero General Public License v3.0
10 stars 1 forks source link

conditionalRendering #4

Closed mqueb closed 3 months ago

AtilioA commented 3 months ago

Thank you for your contribution again. There are a few areas that need a more refined approach:

In the end, the ideal approach would likely require a refactor of UI updates, unfortunately, as it's the most problematic part of the code, but it would behave like this:

AtilioA commented 3 months ago

Also, please avoid force-pushing changes, as it can make it harder to review incremental updates and track the history of your work.

mqueb commented 3 months ago

Also, please avoid force-pushing changes, as it can make it harder to review incremental updates and track the history of your work.

i i think u dont start review, i doing it. will avoid now

mqueb commented 3 months ago
  • The schema for VisibleIf should be an object rather than a string relying on matching. I'll be pushing a schema update soon for reference, don't worry about changing anything for now.

always doing like that for string matching. curious abous how u doing

  • The schema for VisibleIf should be an object rather than a string relying on matching. I'll be pushing a schema update soon for reference, don't worry about changing anything for now.

there is now a modGUID lvl

  • Currently, this implementation only works on initialization and does not update visibility while the user changes settings' values.

no, it's work on update: https://github.com/mqueb/BG3-MCM/blob/conditionnalRendering/Mod%20Configuration%20Menu/Mods/BG3MCM/ScriptExtender/Lua/Client/IMGUILayer.lua#L37 job done here

https://i.ibb.co/ryDM7gz/Cond-Rendering.gif

mqueb commented 3 months ago

In the end, the ideal approach would likely require a refactor of UI updates, unfortunately, as it's the most problematic part of the code, but it would behave like this:

  • When processing user input (e.g. starting on IMGUILayer:SetClientStateValue), iterate BlueprintSetting instances;
  • Check VisibleIf array for each;
  • Toggle the corresponding widget's Visibility property according to whether conditions are met or not.

When processing user input (e.g. starting on IMGUILayer:SetClientStateValue), > what i do

  • iterate BlueprintSetting instances;
  • Check VisibleIf array for each;
  • Toggle the corresponding widget's Visibility property according to whether conditions are met or not.

I dont do that to avait parse all each time. so i build a reverse listing that is simple to use to trigger update visibility

mqueb commented 3 months ago

all work since last push, you can test in on your own. my MCM_blueprints for test was.

i havent test on checkbox ... but it should work

{
    "SchemaVersion": 1,
    "ModName": "Mod Configuration Menu",
    "Tabs": [
        {
            "TabId": "settings",
            "TabName": "Settings",
            "Handles": {
                "NameHandle": "h91b72aadb42a441881a1a7c5de2f82e27agga"
            },
            "Sections": [
                {
                    "SectionId": "general_settings",
                    "SectionName": "General settings",
                    "Settings": [
                        {
                            "Id": "toggle_mcm_keybinding",
                            "Name": "Toggle MCM window",
                            "Type": "keybinding",
                            "Default": {
                                "ScanCode": "INSERT",
                                "Modifier": ""
                            },
                            "Description": "Set the keybinding for toggling MCM's window.",
                            "Handles": {
                                "NameHandle": "hb2789893dcaf46fca8c40add9358e1f2ca93",
                                "DescriptionHandle": "h7728653cb8de46388719316b95f3a4110e7c"
                            }
                        },
                        {
                            "Id": "host-only_mode",
                            "Name": "Host-only mode",
                            "Type": "checkbox",
                            "Default": false,
                            "Description": "Only the host can change MCM settings in multiplayer games.",
                            "Handles": {
                                "DescriptionHandle": "h48d5ce6ebd8144649e72da14b64214a9b57f",
                                "NameHandle": "h119ee473f3f6444b887ed4e2a2fdb97b9fea"
                            }
                        },
                        {
                            "Id": "open_on_start",
                            "Name": "Open MCM window on start",
                            "Type": "checkbox",
                            "Default": true,
                            "Description": "Open the MCM menu when the game starts.",
                            "Handles": {
                                "NameHandle": "h76a89ee330464e30b1b9a01de85cbd0042a6",
                                "DescriptionHandle": "h0d8762f0ceb645608a64b684e581f7a72d67"
                            }
                        },
                        {
                            "Id": "auto_resize_window",
                            "Name": "Automatically resize window",
                            "Type": "checkbox",
                            "Default": true,
                            "Description": "Dynamically resize the MCM window to fit settings.",
                            "Handles": {
                                "NameHandle": "hc4abe0365af046e6abac1b4432abc3f7g4g3",
                                "DescriptionHandle": "h2c525b9161344f509bb4dcad8e49010bb9ce"
                            }
                        },
                        {
                            "Id": "dynamic_opacity",
                            "Name": "Dynamic opacity",
                            "Type": "checkbox",
                            "Default": true,
                            "Description": "Make the MCM window more transparent when interacting with game elements.",
                            "Handles": {
                                "NameHandle": "h9abda7ddcd564ba69433278a82884c535566",
                                "DescriptionHandle": "h204a4196566b44eb8070bc8b5d36edde1c1d"
                            }
                        }
                    ],
                    "Handles": {
                        "NameHandle": "h6db631a023d0425ca0a62566ab1bec41797f"
                    }
                },
                {
                    "SectionId": "debug_settings",
                    "SectionName": "Debug settings",
                    "Settings": [
                        {
                            "Id": "debug_level",
                            "Name": "Debug level",
                            "Type": "slider_int",
                            "Default": 0,
                            "Description": "Used mainly to determine which messages to print.\nUseful for developers or general troubleshooting.",
                            "Options": {
                                "Min": 0,
                                "Max": 3
                            },
                            "Handles": {
                                "NameHandle": "hffc964abc9a74a928cb047607a7aeb124594",
                                "DescriptionHandle": "h1143226dbdd44d4aab3ce3010ffc589a073a"
                            }
                        },
                        {
                            "Id": "debug_levelfffdfd",
                            "Name": " test",
                            "Type": "slider_int",
                            "VisibleIf": "debug_level=3",
                            "Default": 0,
                            "Description": "Used mainly to determine which messages to print.\nUseful for developers or general troubleshooting.",
                            "Options": {
                                "Min": 0,
                                "Max": 3
                            },
                            "Handles": {
                                "NameHandle": "hffc964abc9a74a928cb047607a7aeb124594",
                                "DescriptionHandle": "h1143226dbdd44d4aab3ce3010ffc589a073a"
                            }
                        }
                    ],
                    "Handles": {
                        "NameHandle": "h67357ed078184370901b1d572062b7ba26a5"
                    }
                },
                {
                    "SectionId": "debug_settingsg",
                    "SectionName": "teffst",
                    "VisibleIf": "debug_level=2",
                    "Settings": [
                        {
                            "Id": "debug_levfel3",
                            "Name": "Debug level",
                            "Type": "slider_int",
                            "Default": 0,
                            "Description": "Used mainly to determine which messages to print.\nUseful for developers or general troubleshooting.",
                            "Options": {
                                "Min": 0,
                                "Max": 3
                            },
                            "Handles": {
                                "NameHandle": "hffc964abc9a74a928cb047607a7aeb124594",
                                "DescriptionHandle": "h1143226dbdd44d4aab3ce3010ffc589a073a"
                            }
                        }
                    ],
                    "Handles": {
                        "NameHandle": "h67357ed078184370901b1d572062b7ba26a5"
                    }
                }
            ]
        }
    ]
}
AtilioA commented 3 months ago

I missed that change on SetClientStateValue in the diff, sorry. Busy day here. I'll only be able to push the new schema in a few hours. It would use something like this:

    // ... definitions ...
    "VisibilityCondition": {
        "type": "object",
        "properties": {
            "Expression": {
                "type": "object",
                "properties": {
                    "LogicalOperator": {
                        "type": "string",
                        "enum": ["and", "or"],
                        "description": "The logical operator to use between the conditions."
                    },
                    "Conditions": {
                        "type": "array",
                        "minItems": 1,
                        "items": {
                            "type": "object",
                            "properties": {
                                "SettingId": {
                                    "type": "string",
                                    "description": "The ID of the setting that determines the visibility of this element."
                                },
                                "Operator": {
                                    "type": "string",
                                    "enum": ["==", "!=", ">", "<", ">=", "<="],
                                    "description": "The operator to use for the comparison."
                                },
                                "ExpectedValue": {
                                    "type": "string",
                                    "description": "The expected value of the setting specified by SettingId for this element to be visible."
                                }
                            },
                            "required": [
                                "SettingId",
                                "Operator",
                                "ExpectedValue"
                            ]
                        }
                    }
                },
                "required": [
                    "LogicalOperator",
                    "Conditions"
                ]
            }
        },
        "required": [
            "Expression"
        ],
        "description": "A boolean expression that determines the visibility of this element."
    }

This wouldn't require string matching and would also allow more complex conditions. Sections would have something like:

"VisibleIf": {
    "$ref": "#/definitions/VisibilityCondition",
    "description": "Conditions that determine the visibility of this section."
}
mqueb commented 3 months ago

it will requie to have a level for operation . May be in a next step. equal cover lot of usage i guess

i'm not sure such a lv lis required for MCM, and with your model it's still lack (c1 OP1 C2 ) OP2 (c3 OP1 c4)

but i think MCM to have a single condition is enought.

the fun fact is that i wrote a rule motor at my job. that condition usage of param inside a json schema.

mqueb commented 3 months ago

make necessary to have == != <= >= < > working No mangament for multiple condition

mqueb commented 3 months ago

json schema you may want is that:

"definitions": {
        "Conditions": {
            "type": "object",
            "description": "A single tab in the MCM menu.",
            "properties": {
                "Or": {
                    "type": "array",
                    "minItems": 1,
                    "items": {
                        "$ref": "#/definitions/Conditions"
                    }
                },
                "And": {
                    "type": "array",
                    "minItems": 1,
                    "items": {
                        "$ref": "#/definitions/Conditions"
                    }
                },
                "SettingId": {
                    "type": "string",
                    "description": "The ID of the setting that determines the visibility of this element."
                },
                "Operator": {
                    "type": "string",
                    "enum": ["==", "!=", ">", "<", ">=", "<="],
                    "description": "The operator to use for the comparison."
                },
                "ExpectedValue": {
                    "type": "string",
                    "description": "The expected value of the setting specified by SettingId for this element to be visible."
                }
            },
            "additionalProperties": false,
            "allOf": [
                {
                    "if": {
                        "not": {
                            "properties": {
                                "And": {
                                    "const": "undefined"
                                }
                            }
                        }
                    },
                    "then": {
                        "propertyNames": {
                            "pattern": "And"
                        }
                    }

                },{
                    "if": {
                        "not": {
                            "properties": {
                                "Or": {
                                    "const": "undefined"
                                }
                            }
                        }
                    },
                    "then": {
                        "propertyNames": {
                            "pattern": "Or"
                        }
                    }

                },{
                    "if": {
                        "allOf": [
                            {
                                "properties": {
                                    "Or": {
                                        "const": "undefined"
                                    }
                                }
                            },
                            {
                                "properties": {
                                    "Or": {
                                        "const": "undefined"
                                    }
                                }
                            }
                        ]
                    },
                    "then": {
                        "required": ["SettingId", "Operator", "ExpectedValue"]
                    }

                }
            ]
        },
"VisibleIf": {
                    "$ref": "#/definitions/Conditions"
                },

so you can write:

"VisibleIf": {
                                "Or": [
                                    {
                                        "And": [
                                            {
                                                "ExpectedValue": "v1",
                                                "Operator": "!=",
                                                "SettingId": "set1"
                                            },
                                            {
                                                "ExpectedValue": "3",
                                                "Operator": ">",
                                                "SettingId": "set2"
                                            }
                                        ]
                                    },
                                    {
                                        "And": [
                                            {
                                                "ExpectedValue": "ff",
                                                "Operator": "!=",
                                                "SettingId": "ff"
                                            },
                                            {
                                                "ExpectedValue": "ff",
                                                "Operator": "!=",
                                                "SettingId": "ff"
                                            }
                                        ]
                                    },
                                    {
                                        "ExpectedValue": "ff",
                                        "Operator": "!=",
                                        "SettingId": "ff"
                                    }
                                ]
                            }

But is it required for MCM ...

i think, a simple way is to manage both over time, the simple string is already done and easy to use for simple condition the "complex" object. could be done later if required. schema can manage both case and code also detect string or object and process as needed

mqueb commented 3 months ago

Thinking about it. Will do it soon. Its vert few modification I'm busy today but will bé done next day i think

If u really want 3 param over simple string, i Can Do it. But i think its boilderplate. Thé only usefull thing is ans/or

Let me know before i start

AtilioA commented 3 months ago

Please don't do anything yet, let me at least push the schema. The schema I proposed leaves out complex conditions by design, especially since authors can create complex patterns already by simply depending on elements that already have conditions. Wanting to have more than simple conditions is probably bad design, so we don't need to include that, but having most operators is something that can be useful.

AtilioA commented 3 months ago

I'll refactor some stuff and merge later on

mqueb commented 3 months ago

What i dont like in your schéma IS that a simple expression require A boilderplate condition level.

I Can understand 3 param vs a string. Ecen if i prefer a single string as end user usage. its simple to build object as that are parsed

Sry for all error in text. Writing from my french phone...

AtilioA commented 3 months ago

I see your point, but I think this provides the right amount of flexibility. This is just one extra level than what a minimal solution with objects would be. Writing an entry for this in the blueprint JSON with the schema is still very easy as it will point out the structure for you (in VSCode for example) I can read French just fine, you can use it if you want.

mqueb commented 3 months ago

any ETA of merge ?

AtilioA commented 3 months ago

I had some problems with subsequent changes but I plan to get it sorted out today or tomorrow