|
| 1 | +-- Category / Group / Section |
| 2 | + |
| 3 | +-- OptionType |
| 4 | +local optionType : OptionType = { |
| 5 | + InputBox = 0, |
| 6 | + Checkbox = 1, |
| 7 | + Slider = 2, |
| 8 | + ComboBox = 3, |
| 9 | +} |
| 10 | +OptionType = optionType; |
| 11 | + |
| 12 | +-- OptionCategory |
| 13 | +local optionCategoryType : OptionCategoryType = { |
| 14 | + Gameplay = "Gameplay", |
| 15 | + Keybindings = "Keybindings", |
| 16 | + Accessibility = "Accessibility", |
| 17 | + System = "System", |
| 18 | +} |
| 19 | +OptionCategoryType = optionCategoryType; |
| 20 | + |
| 21 | +-- Option |
| 22 | +type Option = |
| 23 | +{ |
| 24 | + name : string, |
| 25 | + type : OptionType, |
| 26 | + |
| 27 | + defaultValue : any, |
| 28 | + value : any, |
| 29 | +} |
| 30 | + |
| 31 | +-- OptionsSection |
| 32 | +type OptionsSection = |
| 33 | +{ |
| 34 | + name : string, |
| 35 | + options : LookupTable<Option>, |
| 36 | +} |
| 37 | + |
| 38 | +-- OptionsGroup |
| 39 | +type OptionsGroup = |
| 40 | +{ |
| 41 | + name : string, |
| 42 | + sections : LookupTable<OptionsSection>, |
| 43 | +} |
| 44 | + |
| 45 | +-- OptionsCategory |
| 46 | +type OptionsCategory = |
| 47 | +{ |
| 48 | + name : string, |
| 49 | + groups : LookupTable<OptionsGroup>, |
| 50 | +} |
| 51 | + |
| 52 | +-- Context |
| 53 | +type OptionsContext = |
| 54 | +{ |
| 55 | + categories : LookupTable<OptionsCategory>, |
| 56 | +} |
| 57 | + |
| 58 | +-- LookupTable |
| 59 | +type LookupTable<T> = { |
| 60 | + nameToIndex : { [string]: number }, |
| 61 | + list : { T }, |
| 62 | +} |
| 63 | + |
| 64 | +-- Metatables |
| 65 | +local OptionMetatable = { |
| 66 | + Get = function(self : Option) : any |
| 67 | + return self.value; |
| 68 | + end, |
| 69 | + GetDefault = function(self : Option) : any |
| 70 | + return self.defaultValue; |
| 71 | + end, |
| 72 | + Set = function(self : Option, value : any) : () |
| 73 | + self.value = value; |
| 74 | + end, |
| 75 | + Reset = function(self : Option) : () |
| 76 | + self.value = self.defaultValue; |
| 77 | + end, |
| 78 | +} |
| 79 | +OptionMetatable.__index = OptionMetatable |
| 80 | + |
| 81 | +local LookupTableMetatable = { |
| 82 | + Get = function(self : LookupTable<any>, name : string) : number |
| 83 | + local index = self.nameToIndex[name]; |
| 84 | + if (index == nil) then |
| 85 | + -- Create a new entry |
| 86 | + local value = { name = name }; |
| 87 | + table.insert(self.list, value); |
| 88 | + index = #self.list; |
| 89 | + self.nameToIndex[name] = index; |
| 90 | + end |
| 91 | + |
| 92 | + return index; |
| 93 | + end, |
| 94 | + Has = function(self : LookupTable<any>, name : string) : boolean |
| 95 | + return self.nameToIndex[name] ~= nil; |
| 96 | + end, |
| 97 | +} |
| 98 | +LookupTableMetatable.__index = LookupTableMetatable; |
| 99 | + |
| 100 | +local SectionMetatable = { |
| 101 | + GetOption = function(self : OptionsSection, name : string) : Option |
| 102 | + if (not self.options:Has(name)) then |
| 103 | + error(`Section {self.name} does not have option {name}`, 2); |
| 104 | + end |
| 105 | + |
| 106 | + -- Return existing object |
| 107 | + local index = self.options:Get(name); |
| 108 | + return self.options.list[index]; |
| 109 | + end, |
| 110 | + RegisterOption = function(self : OptionsSection, name : string, defaultValue : any, type : OptionType) : Option |
| 111 | + if (self.options:Has(name)) then |
| 112 | + error(`Section {self.name} already has option {name}`, 2); |
| 113 | + end |
| 114 | + |
| 115 | + local index = self.options:Get(name); |
| 116 | + local object = self.options.list[index]; |
| 117 | + object.name = name; |
| 118 | + object.type = type; |
| 119 | + object.defaultValue = defaultValue; |
| 120 | + object.value = defaultValue; |
| 121 | + setmetatable(object, OptionMetatable); |
| 122 | + |
| 123 | + return object; |
| 124 | + end, |
| 125 | +} |
| 126 | +SectionMetatable.__index = SectionMetatable; |
| 127 | +setmetatable(SectionMetatable, LookupTableMetatable); |
| 128 | + |
| 129 | +local GroupMetatable = { |
| 130 | + Get = function(self : OptionsGroup, name : string) : OptionsSection |
| 131 | + if (not self.sections:Has(name)) then |
| 132 | + local index = self.sections:Get(name); |
| 133 | + local object = self.sections.list[index]; |
| 134 | + object.options = { |
| 135 | + nameToIndex = {}, |
| 136 | + list = {}, |
| 137 | + }; |
| 138 | + |
| 139 | + setmetatable(object, SectionMetatable); |
| 140 | + setmetatable(object.options, LookupTableMetatable); |
| 141 | + return object; |
| 142 | + end |
| 143 | + |
| 144 | + -- Return existing object |
| 145 | + local index = self.sections:Get(name); |
| 146 | + return self.sections.list[index]; |
| 147 | + end, |
| 148 | +} |
| 149 | +GroupMetatable.__index = GroupMetatable; |
| 150 | +setmetatable(GroupMetatable, LookupTableMetatable); |
| 151 | + |
| 152 | +local CategoryMetatable = { |
| 153 | + Get = function(self : OptionsCategory, name : string) : OptionsGroup |
| 154 | + if (not self.groups:Has(name)) then |
| 155 | + local index = self.groups:Get(name); |
| 156 | + local object = self.groups.list[index]; |
| 157 | + object.sections = { |
| 158 | + nameToIndex = {}, |
| 159 | + list = {}, |
| 160 | + }; |
| 161 | + |
| 162 | + setmetatable(object, GroupMetatable); |
| 163 | + setmetatable(object.sections, LookupTableMetatable); |
| 164 | + return object; |
| 165 | + end |
| 166 | + |
| 167 | + -- Return existing object |
| 168 | + local index = self.groups:Get(name); |
| 169 | + return self.groups.list[index]; |
| 170 | + end, |
| 171 | +} |
| 172 | +CategoryMetatable.__index = CategoryMetatable; |
| 173 | +setmetatable(CategoryMetatable, LookupTableMetatable); |
| 174 | + |
| 175 | +local ContextMetatable = { |
| 176 | + GetCategory = function(self : OptionsContext, optionCategory : OptionCategoryType) : OptionsCategory |
| 177 | + if (not self:Has(optionCategory)) then |
| 178 | + local index = LookupTableMetatable.Get(self.categories, optionCategory); |
| 179 | + local object = self.categories.list[index]; |
| 180 | + object.groups = { |
| 181 | + nameToIndex = {}, |
| 182 | + list = {}, |
| 183 | + }; |
| 184 | + |
| 185 | + setmetatable(object, CategoryMetatable); |
| 186 | + setmetatable(object.groups, LookupTableMetatable); |
| 187 | + return object; |
| 188 | + end |
| 189 | + |
| 190 | + -- Return existing object |
| 191 | + local index = LookupTableMetatable.Get(self.categories, optionCategory); |
| 192 | + return self.categories.list[index]; |
| 193 | + end, |
| 194 | + GetCategories = function(self : OptionsContext) : { OptionsCategory } |
| 195 | + return self.categories.list; |
| 196 | + end, |
| 197 | + Has = function(self : OptionsContext, name : string) : boolean |
| 198 | + return self.categories.nameToIndex[name] ~= nil; |
| 199 | + end, |
| 200 | +} |
| 201 | +ContextMetatable.__index = ContextMetatable; |
| 202 | + |
| 203 | +local optionsContext : OptionsContext = { |
| 204 | + categories = { |
| 205 | + nameToIndex = {}, |
| 206 | + list = {}, |
| 207 | + } |
| 208 | +}; |
| 209 | + |
| 210 | +-- This might look ugly but it's needed to make the context global and not as a copy |
| 211 | +__global_optionsContext = optionsContext; |
| 212 | +setmetatable(__global_optionsContext, ContextMetatable); |
| 213 | + |
| 214 | +__global_optionsContext:GetCategory(OptionCategoryType.Gameplay); |
| 215 | +__global_optionsContext:GetCategory(OptionCategoryType.Keybindings); |
| 216 | +__global_optionsContext:GetCategory(OptionCategoryType.Accessibility); |
| 217 | +__global_optionsContext:GetCategory(OptionCategoryType.System); |
| 218 | + |
| 219 | +return __global_optionsContext; |
| 220 | + |
| 221 | +-- Usage: |
| 222 | +--local optionCategory = OptionsContext:GetCategory(OptionCategoryType.Gameplay); |
| 223 | +--local optionGroup = optionCategory:Get("Nameplates"); |
| 224 | +--local optionSection = optionGroup:Get("General"); |
| 225 | +--local enabledOption = optionSection:Get("Enabled"); -- ERROR |
| 226 | +--local enabledOption = optionSection.RegisterOption("Enabled", true, OptionType.Checkbox);); |
| 227 | +--local isEnabled = enabledOption:Get(); |
0 commit comments