├── fxmanifest.lua ├── LICENSE ├── warmenu_demo.lua ├── README.md └── warmenu.lua /fxmanifest.lua: -------------------------------------------------------------------------------- 1 | fx_version 'cerulean' 2 | game 'gta5' 3 | lua54 'yes' 4 | 5 | author 'Warxander (https://github.com/warxander)' 6 | description 'FiveM Lua Menu framework' 7 | version '2.0.0' 8 | 9 | files { 10 | 'warmenu.lua', 11 | 'warmenu_demo.lua' 12 | } 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 - 2024, Warxander, https://github.com/warxander 2 | Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, 3 | provided that the above copyright notice and this permission notice appear in all copies. 4 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. 5 | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, 6 | WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 7 | -------------------------------------------------------------------------------- /warmenu_demo.lua: -------------------------------------------------------------------------------- 1 | local wasInitialized = false 2 | 3 | local items = { 'F', 'I', 'V', 'E', 'M' } 4 | local state = {} 5 | 6 | local function uiThread() 7 | while true do 8 | if WarMenu.Begin('warmenuDemo') then 9 | WarMenu.MenuButton('Controls', 'warmenuDemo_controls') 10 | WarMenu.MenuButton('~r~Exit', 'warmenuDemo_exit') 11 | 12 | WarMenu.End() 13 | elseif WarMenu.Begin('warmenuDemo_controls') then 14 | WarMenu.Button('Button', 'Subtext') 15 | if WarMenu.IsItemHovered() then 16 | WarMenu.ToolTip('Tooltip example.') 17 | end 18 | 19 | local isPressed, inputText = WarMenu.InputButton('InputButton', nil, state.inputText) 20 | if isPressed and inputText then 21 | state.inputText = inputText 22 | end 23 | 24 | if WarMenu.SpriteButton('SpriteButton', 'commonmenu', state.useAltSprite and 'shop_gunclub_icon_b' or 'shop_garage_icon_b') then 25 | state.useAltSprite = not state.useAltSprite 26 | end 27 | 28 | if WarMenu.CheckBox('CheckBox', state.isChecked) then 29 | state.isChecked = not state.isChecked 30 | end 31 | 32 | local _, currentIndex = WarMenu.ComboBox('ComboBox', items, state.currentIndex) 33 | state.currentIndex = currentIndex 34 | 35 | WarMenu.End() 36 | elseif WarMenu.Begin('warmenuDemo_exit') then 37 | WarMenu.MenuButton('No', 'warmenuDemo') 38 | 39 | if WarMenu.Button('~r~Yes') then 40 | WarMenu.CloseMenu() 41 | end 42 | 43 | WarMenu.End() 44 | end 45 | 46 | Wait(0) 47 | end 48 | end 49 | 50 | RegisterCommand('warmenuDemo', function() 51 | if WarMenu.IsAnyMenuOpened() then 52 | return 53 | end 54 | 55 | if not wasInitialized then 56 | WarMenu.CreateMenu('warmenuDemo', 'WarMenu Demo', 'Created by Warxander') 57 | 58 | WarMenu.CreateSubMenu('warmenuDemo_controls', 'warmenuDemo', 'Controls') 59 | WarMenu.CreateSubMenu('warmenuDemo_exit', 'warmenuDemo', 'Are you sure?') 60 | 61 | Citizen.CreateThread(uiThread) 62 | 63 | wasInitialized = true 64 | end 65 | 66 | state = { 67 | useAltSprite = false, 68 | isChecked = false, 69 | currentIndex = 1 70 | } 71 | 72 | WarMenu.OpenMenu('warmenuDemo') 73 | end) 74 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WarMenu 2 | FiveM Lua Menu framework 3 | 4 | WarMenu is an immediate mode GUI framework based on GTA V GUI style 5 | 6 | ## Installation Guide 7 | 1. Download and put into `resources/` directory 8 | 2. Add `ensure warmenu` to your `server.cfg` 9 | 3. Add `client_script '@warmenu/warmenu.lua'` to your `fxmanifest.lua` 10 | 11 | ## Demo 12 | Add `client_script @warmenu/warmenu_demo.lua` to your `fxmanifest.lua` and use `warmenuDemo` chat command to open WarMenu demo 13 | 14 | ## API 15 | ```lua 16 | --! @param id: string 17 | --! @param title: string 18 | --! @param subTitle: string 19 | --! @param style: table 20 | WarMenu.CreateMenu(id, title, [subTitle, style]) 21 | 22 | --! @param id: string 23 | --! @param parentId: string 24 | --! @param subTitle: string 25 | --! @param style: table 26 | WarMenu.CreateSubMenu(id, parentId, [subTitle, style]) 27 | 28 | --! @return id: string 29 | local id = WarMenu.CurrentMenu() 30 | 31 | --! @return optionIndex: number 32 | local optionIndex = WarMenu.OptionIndex() 33 | 34 | --! @param id: string 35 | WarMenu.OpenMenu(id) 36 | 37 | --! @param id: string 38 | --! @return isOpened: boolean 39 | local isOpened = WarMenu.Begin(id) 40 | 41 | WarMenu.End() 42 | 43 | --! @param text: string 44 | --! @return isPressed: boolean 45 | local isPressed = WarMenu.Button(text [, subText]) 46 | 47 | --! @param text: string 48 | --! @param windowTitleEntry: string 49 | --! @param defaultText: string 50 | --! @param maxLength: number 51 | --! @param subText: string 52 | --! @return isPressed: boolean 53 | --! @return inputText: string 54 | local isPressed, inputText = WarMenu.InputButton(text [, windowTitleEntry, defaultText, maxLength, subText]) 55 | 56 | --! @param text: string 57 | --! @param dict: string 58 | --! @param name: string 59 | --! @param r: number 60 | --! @param g: number 61 | --! @param b: number 62 | --! @param a: number 63 | --! @return isPressed: boolean 64 | local isPressed = WarMenu.SpriteButton(text, dict, name [, r, g, b, a]) 65 | 66 | --! @param text: string 67 | --! @param id: string 68 | --! @param subText: string 69 | --! @return isPressed: boolean 70 | local isPressed = WarMenu.MenuButton(text, id [, subText]) 71 | 72 | --! @param text: string 73 | --! @param checked: boolean 74 | --! @return isPressed: boolean 75 | local isPressed = WarMenu.CheckBox(text, checked) 76 | 77 | --! @param text: string 78 | --! @param items: table 79 | --! @param currentIndex: number 80 | --! @return isPressed: boolean 81 | --! @return currentIndex: number 82 | local isPressed, currentIndex = WarMenu.ComboBox(text, items, currentIndex) 83 | 84 | --! @param text: string 85 | --! @param width: number 86 | --! @param flipHorizontal: boolean 87 | WarMenu.ToolTip(text [, width, flipHorizontal]) 88 | 89 | --! @return isHovered: boolean 90 | local isHovered = WarMenu.IsItemHovered() 91 | 92 | --! @return isSelected: boolean 93 | local isSelected = WarMenu.IsItemSelected() 94 | 95 | --! @param id: string 96 | --! @param title: string 97 | --! @comment Uppercased automatically 98 | WarMenu.SetMenuTitle(id, title) 99 | 100 | --! @param id: string 101 | --! @param subTitle: string 102 | --! @comment Uppercased automatically 103 | WarMenu.SetMenuSubTitle(id, subTitle) 104 | 105 | --! @param id: string 106 | --! @param style: table 107 | --! @comment Use style methods to figure out style values 108 | WarMenu.SetMenuStyle(id, style) 109 | 110 | --! @param id: string 111 | --! @param x: number [0.0..1.0] left-right direction 112 | WarMenu.SetMenuX(id, x) 113 | 114 | --! @param id: string 115 | --! @param y: number [0.0..1.0] top-bottom direction 116 | WarMenu.SetMenuY(id, y) 117 | 118 | --! @param id: string 119 | --! @param width: number [0.0..1.0] 120 | WarMenu.SetMenuWidth(id, width) 121 | 122 | --! @param id: string 123 | --! @param optionCount: number 124 | WarMenu.SetMenuMaxOptionCountOnScreen(id, optionCount) 125 | 126 | --! @param id: string 127 | --! @param visible: boolean 128 | WarMenu.SetMenuTitleVisible(id, visible) 129 | 130 | --! @param id: string 131 | --! @param r: number 132 | --! @param g: number 133 | --! @param b: number 134 | --! @param a: number 135 | WarMenu.SetMenuTitleColor(id, r, g, b [, a]) 136 | 137 | --! @param id: string 138 | --! @param r: number 139 | --! @param g: number 140 | --! @param b: number 141 | --! @param a: number 142 | WarMenu.SetMenuSubTitleColor(id, r, g, b [, a]) 143 | 144 | --! @param id: string 145 | --! @param r: number 146 | --! @param g: number 147 | --! @param b: number 148 | --! @param a: number 149 | WarMenu.SetMenuSubTitleBackgroundColor(id, r, g, b [, a]) 150 | 151 | --! @param id: string 152 | --! @param r: number 153 | --! @param g: number 154 | --! @param b: number 155 | --! @param a: number 156 | WarMenu.SetMenuTitleBackgroundColor(id, r, g, b [, a]) 157 | 158 | --! @param id: string 159 | --! @param dict: string 160 | --! @param name: string 161 | WarMenu.SetMenuTitleBackgroundSprite(id, dict, name) 162 | 163 | --! @param id: string 164 | --! @param r: number 165 | --! @param g: number 166 | --! @param b: number 167 | --! @param a: number 168 | WarMenu.SetMenuBackgroundColor(id, r, g, b [, a]) 169 | 170 | --! @param id: string 171 | --! @param r: number 172 | --! @param g: number 173 | --! @param b: number 174 | --! @param a: number 175 | WarMenu.SetMenuTextColor(id, r, g, b [, a]) 176 | 177 | --! @param id: string 178 | --! @param r: number 179 | --! @param g: number 180 | --! @param b: number 181 | --! @param a: number 182 | WarMenu.SetMenuSubTextColor(id, r, g, b [, a]) 183 | 184 | --! @param id: string 185 | --! @param r: number 186 | --! @param g: number 187 | --! @param b: number 188 | --! @param a: number 189 | WarMenu.SetMenuFocusColor(id, r, g, b [, a]) 190 | 191 | --! @param id: string 192 | --! @param r: number 193 | --! @param g: number 194 | --! @param b: number 195 | --! @param a: number 196 | WarMenu.SetMenuFocusTextColor(id, r, g, b [, a]) 197 | 198 | --! @param id: string 199 | --! @param name: string 200 | --! @param set: string 201 | --! @comment List of sounds from decompiled scripts: https://pastebin.com/0neZdsZ5 202 | WarMenu.SetMenuButtonPressedSound(id, name, set) 203 | ``` 204 | -------------------------------------------------------------------------------- /warmenu.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2017 - 2024, Warxander, https://github.com/warxander 2 | -- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, 3 | -- provided that the above copyright notice and this permission notice appear in all copies. 4 | -- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. 5 | -- IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, 6 | -- WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 7 | 8 | 9 | WarMenu = {} 10 | WarMenu.__index = WarMenu 11 | 12 | --! @deprecated 13 | function WarMenu.SetDebugEnabled() 14 | end 15 | 16 | --! @deprecated 17 | function WarMenu.IsDebugEnabled() 18 | return false 19 | end 20 | 21 | --! @deprecated 22 | function WarMenu.IsMenuAboutToBeClosed() 23 | return false 24 | end 25 | 26 | local keys = { down = 187, scrollDown = 242, up = 188, scrollUp = 241, left = 189, right = 190, select = 191, accept = 237, back = 194, cancel = 238 } 27 | 28 | local toolTipWidth = 0.153 29 | 30 | local buttonSpriteWidth = 0.027 31 | 32 | local titleHeight = 0.101 33 | local titleYOffset = 0.021 34 | local titleFont = 1 35 | local titleScale = 1.0 36 | 37 | local buttonHeight = 0.038 38 | local buttonFont = 0 39 | local buttonScale = 0.365 40 | local buttonTextXOffset = 0.005 41 | local buttonTextYOffset = 0.005 42 | local buttonSpriteXOffset = 0.002 43 | local buttonSpriteYOffset = 0.005 44 | 45 | local defaultStyle = { 46 | x = 0.0175, 47 | y = 0.025, 48 | width = 0.23, 49 | maxOptionCountOnScreen = 10, 50 | titleVisible = true, 51 | titleColor = { 0, 0, 0, 255 }, 52 | titleBackgroundColor = { 245, 127, 23, 255 }, 53 | titleBackgroundSprite = nil, 54 | subTitleColor = { 245, 127, 23, 255 }, 55 | textColor = { 254, 254, 254, 255 }, 56 | subTextColor = { 189, 189, 189, 255 }, 57 | focusTextColor = { 0, 0, 0, 255 }, 58 | focusColor = { 245, 245, 245, 255 }, 59 | backgroundColor = { 0, 0, 0, 160 }, 60 | subTitleBackgroundColor = { 0, 0, 0, 255 }, 61 | buttonPressedSound = { name = 'SELECT', set = 'HUD_FRONTEND_DEFAULT_SOUNDSET' }, 62 | } 63 | 64 | local menus = {} 65 | 66 | local skipInputNextFrame = true 67 | 68 | local currentMenu = nil 69 | local currentKey = nil 70 | local currentOptionCount = 0 71 | 72 | local function isNavigatedDown() 73 | return IsControlJustReleased(2, keys.down) or IsControlJustReleased(2, keys.scrollDown) 74 | end 75 | 76 | local function isNavigatedUp() 77 | return IsControlJustReleased(2, keys.up) or IsControlJustReleased(2, keys.scrollUp) 78 | end 79 | 80 | local function isSelectedPressed() 81 | return IsControlJustReleased(2, keys.select) or IsControlJustReleased(2, keys.accept) 82 | end 83 | 84 | local function isBackPressed() 85 | return IsControlJustReleased(2, keys.back) or IsControlJustReleased(2, keys.cancel) 86 | end 87 | 88 | local function setMenuProperty(id, property, value) 89 | if not id then 90 | return 91 | end 92 | 93 | local menu = menus[id] 94 | if menu then 95 | menu[property] = value 96 | end 97 | end 98 | 99 | local function setStyleProperty(id, property, value) 100 | if not id then 101 | return 102 | end 103 | 104 | local menu = menus[id] 105 | 106 | if menu then 107 | if not menu.overrideStyle then 108 | menu.overrideStyle = {} 109 | end 110 | 111 | menu.overrideStyle[property] = value 112 | end 113 | end 114 | 115 | local function getStyleProperty(property, menu) 116 | local usedMenu = menu or currentMenu 117 | 118 | if usedMenu.overrideStyle then 119 | local value = usedMenu.overrideStyle[property] 120 | if value ~= nil then 121 | return value 122 | end 123 | end 124 | 125 | return usedMenu.style and usedMenu.style[property] or defaultStyle[property] 126 | end 127 | 128 | local function getTitleHeight() 129 | return getStyleProperty('titleVisible') and titleHeight or 0 130 | end 131 | 132 | local function copyTable(t) 133 | if type(t) ~= 'table' then 134 | return t 135 | end 136 | 137 | local result = {} 138 | for k, v in pairs(t) do 139 | result[k] = copyTable(v) 140 | end 141 | 142 | return result 143 | end 144 | 145 | local function setMenuVisible(id, visible, holdOptionIndex) 146 | if currentMenu then 147 | if visible then 148 | if currentMenu.id == id then 149 | return 150 | end 151 | else 152 | if currentMenu.id ~= id then 153 | return 154 | end 155 | end 156 | end 157 | 158 | if visible then 159 | local menu = menus[id] 160 | 161 | if not currentMenu then 162 | menu.optionIndex = 1 163 | else 164 | if not holdOptionIndex then 165 | menus[currentMenu.id].optionIndex = 1 166 | end 167 | end 168 | 169 | currentMenu = menu 170 | skipInputNextFrame = true 171 | 172 | SetUserRadioControlEnabled(false) 173 | HudWeaponWheelIgnoreControlInput(true) 174 | else 175 | HudWeaponWheelIgnoreControlInput(false) 176 | SetUserRadioControlEnabled(true) 177 | 178 | currentMenu = nil 179 | end 180 | end 181 | 182 | local function setTextParams(font, color, scale, center, shadow, alignRight, wrapFrom, wrapTo) 183 | SetTextFont(font) 184 | SetTextColour(color[1], color[2], color[3], color[4] or 255) 185 | SetTextScale(scale, scale) 186 | 187 | if shadow then 188 | SetTextDropShadow() 189 | end 190 | 191 | if center then 192 | SetTextCentre(true) 193 | elseif alignRight then 194 | SetTextRightJustify(true) 195 | end 196 | 197 | SetTextWrap(wrapFrom or getStyleProperty('x'), 198 | wrapTo or (getStyleProperty('x') + getStyleProperty('width') - buttonTextXOffset)) 199 | end 200 | 201 | local function getLinesCount(text, x, y) 202 | BeginTextCommandLineCount('TWOSTRINGS') 203 | AddTextComponentString(tostring(text)) 204 | return EndTextCommandGetLineCount(x, y) 205 | end 206 | 207 | local function drawText(text, x, y) 208 | BeginTextCommandDisplayText('TWOSTRINGS') 209 | AddTextComponentString(tostring(text)) 210 | EndTextCommandDisplayText(x, y) 211 | end 212 | 213 | local function drawRect(x, y, width, height, color) 214 | DrawRect(x, y, width, height, color[1], color[2], color[3], color[4] or 255) 215 | end 216 | 217 | local function getCurrentOptionIndex() 218 | if not currentMenu then error('getCurrentOptionIndex() failed: No current menu') end 219 | 220 | local maxOptionCount = getStyleProperty('maxOptionCountOnScreen') 221 | if currentMenu.optionIndex <= maxOptionCount and currentOptionCount <= maxOptionCount then 222 | return currentOptionCount 223 | elseif currentOptionCount > currentMenu.optionIndex - maxOptionCount and currentOptionCount <= currentMenu.optionIndex then 224 | return currentOptionCount - (currentMenu.optionIndex - maxOptionCount) 225 | end 226 | 227 | return nil 228 | end 229 | 230 | local function drawTitle() 231 | if not currentMenu then error('drawTitle() failed: No current menu') end 232 | 233 | if not getStyleProperty('titleVisible') then 234 | return 235 | end 236 | 237 | local width = getStyleProperty('width') 238 | local x = getStyleProperty('x') + width / 2 239 | local y = getStyleProperty('y') + titleHeight / 2 240 | 241 | local backgroundSprite = getStyleProperty('titleBackgroundSprite') 242 | if backgroundSprite then 243 | DrawSprite(backgroundSprite.dict, backgroundSprite.name, x, y, 244 | width, titleHeight, 0., 255, 255, 255, 255) 245 | else 246 | drawRect(x, y, width, titleHeight, getStyleProperty('titleBackgroundColor')) 247 | end 248 | 249 | if currentMenu.title then 250 | setTextParams(titleFont, getStyleProperty('titleColor'), titleScale, true) 251 | drawText(currentMenu.title, x, y - titleHeight / 2 + titleYOffset) 252 | end 253 | end 254 | 255 | local function drawSubTitle() 256 | if not currentMenu then error('drawSubTitle() failed: No current menu') end 257 | 258 | local width = getStyleProperty('width') 259 | local styleX = getStyleProperty('x') 260 | local x = styleX + width / 2 261 | local y = getStyleProperty('y') + getTitleHeight() + buttonHeight / 2 262 | local subTitleColor = getStyleProperty('subTitleColor') 263 | 264 | drawRect(x, y, width, buttonHeight, getStyleProperty('subTitleBackgroundColor')) 265 | 266 | setTextParams(buttonFont, subTitleColor, buttonScale, false) 267 | drawText(currentMenu.subTitle, styleX + buttonTextXOffset, y - buttonHeight / 2 + buttonTextYOffset) 268 | 269 | if currentOptionCount > getStyleProperty('maxOptionCountOnScreen') then 270 | setTextParams(buttonFont, subTitleColor, buttonScale, false, false, true) 271 | drawText(tostring(currentMenu.optionIndex) .. ' / ' .. tostring(currentOptionCount), 272 | styleX + width, y - buttonHeight / 2 + buttonTextYOffset) 273 | end 274 | end 275 | 276 | local function drawButton(text, subText) 277 | if not currentMenu then error('drawButton() failed: No current menu') end 278 | 279 | local optionIndex = getCurrentOptionIndex() 280 | if not optionIndex then 281 | return 282 | end 283 | 284 | local backgroundColor = nil 285 | local textColor = nil 286 | local subTextColor = nil 287 | local shadow = false 288 | 289 | if currentMenu.optionIndex == currentOptionCount then 290 | backgroundColor = getStyleProperty('focusColor') 291 | textColor = getStyleProperty('focusTextColor') 292 | subTextColor = getStyleProperty('focusTextColor') 293 | else 294 | backgroundColor = getStyleProperty('backgroundColor') 295 | textColor = getStyleProperty('textColor') 296 | subTextColor = getStyleProperty('subTextColor') 297 | shadow = true 298 | end 299 | 300 | local width = getStyleProperty('width') 301 | local styleX = getStyleProperty('x') 302 | local halfButtonHeight = buttonHeight / 2 303 | local x = styleX + width / 2 304 | local y = getStyleProperty('y') + getTitleHeight() + buttonHeight + (buttonHeight * optionIndex) - halfButtonHeight 305 | 306 | drawRect(x, y, width, buttonHeight, backgroundColor) 307 | 308 | setTextParams(buttonFont, textColor, buttonScale, false, shadow) 309 | drawText(text, styleX + buttonTextXOffset, y - halfButtonHeight + buttonTextYOffset) 310 | 311 | if subText then 312 | setTextParams(buttonFont, subTextColor, buttonScale, false, shadow, true) 313 | drawText(subText, styleX + buttonTextXOffset, y - halfButtonHeight + buttonTextYOffset) 314 | end 315 | end 316 | 317 | function WarMenu.CreateMenu(id, title, subTitle, style) 318 | local menu = {} 319 | 320 | menu.id = id 321 | menu.parentId = nil 322 | menu.optionIndex = 1 323 | menu.title = title 324 | menu.subTitle = subTitle and string.upper(subTitle) or 'INTERACTION MENU' 325 | 326 | if style then 327 | menu.style = style 328 | end 329 | 330 | menus[id] = menu 331 | end 332 | 333 | function WarMenu.CreateSubMenu(id, parentId, subTitle, style) 334 | local parentMenu = menus[parentId] 335 | if not parentMenu then 336 | return 337 | end 338 | 339 | WarMenu.CreateMenu(id, parentMenu.title, subTitle and string.upper(subTitle) or parentMenu.subTitle) 340 | 341 | local menu = menus[id] 342 | 343 | menu.parentId = parentId 344 | 345 | if parentMenu.overrideStyle then 346 | menu.overrideStyle = copyTable(parentMenu.overrideStyle) 347 | end 348 | 349 | if style then 350 | menu.style = style 351 | elseif parentMenu.style then 352 | menu.style = copyTable(parentMenu.style) 353 | end 354 | end 355 | 356 | function WarMenu.CurrentMenu() 357 | return currentMenu and currentMenu.id or nil 358 | end 359 | 360 | function WarMenu.OpenMenu(id) 361 | if id and menus[id] then 362 | PlaySoundFrontend(-1, 'SELECT', 'HUD_FRONTEND_DEFAULT_SOUNDSET', true) 363 | setMenuVisible(id, true, true) 364 | end 365 | end 366 | 367 | function WarMenu.IsMenuOpened(id) 368 | return currentMenu and currentMenu.id == id 369 | end 370 | 371 | WarMenu.Begin = WarMenu.IsMenuOpened 372 | 373 | function WarMenu.IsAnyMenuOpened() 374 | return currentMenu ~= nil 375 | end 376 | 377 | function WarMenu.CloseMenu() 378 | if not currentMenu then return end 379 | 380 | setMenuVisible(currentMenu.id, false) 381 | currentOptionCount = 0 382 | currentKey = nil 383 | PlaySoundFrontend(-1, 'QUIT', 'HUD_FRONTEND_DEFAULT_SOUNDSET', true) 384 | end 385 | 386 | function WarMenu.ToolTip(text, width, flipHorizontal) 387 | if not currentMenu then 388 | return 389 | end 390 | 391 | local optionIndex = getCurrentOptionIndex() 392 | if not optionIndex then 393 | return 394 | end 395 | 396 | local tipWidth = width or toolTipWidth 397 | local halfTipWidth = tipWidth / 2 398 | local x = nil 399 | local y = getStyleProperty('y') 400 | 401 | if not flipHorizontal then 402 | x = getStyleProperty('x') + getStyleProperty('width') + halfTipWidth + buttonTextXOffset 403 | else 404 | x = getStyleProperty('x') - halfTipWidth - buttonTextXOffset 405 | end 406 | 407 | local textX = x - halfTipWidth + buttonTextXOffset 408 | setTextParams(buttonFont, getStyleProperty('textColor'), buttonScale, false, true, false, textX, 409 | textX + tipWidth - (buttonTextYOffset * 2)) 410 | local linesCount = getLinesCount(text, textX, y) 411 | 412 | local height = GetTextScaleHeight(buttonScale, buttonFont) * (linesCount + 1) + buttonTextYOffset 413 | local halfHeight = height / 2 414 | y = y + getTitleHeight() + (buttonHeight * optionIndex) + halfHeight 415 | 416 | drawRect(x, y, tipWidth, height, getStyleProperty('backgroundColor')) 417 | 418 | y = y - halfHeight + buttonTextYOffset 419 | drawText(text, textX, y) 420 | end 421 | 422 | function WarMenu.Button(text, subText) 423 | if not currentMenu then 424 | return 425 | end 426 | 427 | currentOptionCount = currentOptionCount + 1 428 | 429 | drawButton(text, subText) 430 | 431 | local pressed = false 432 | 433 | if currentMenu.optionIndex == currentOptionCount then 434 | if currentKey == keys.select then 435 | local buttonPressedSound = getStyleProperty('buttonPressedSound') 436 | if buttonPressedSound then 437 | PlaySoundFrontend(-1, buttonPressedSound.name, buttonPressedSound.set, true) 438 | end 439 | 440 | pressed = true 441 | elseif currentKey == keys.left or currentKey == keys.right then 442 | PlaySoundFrontend(-1, 'NAV_UP_DOWN', 'HUD_FRONTEND_DEFAULT_SOUNDSET', true) 443 | end 444 | end 445 | 446 | return pressed 447 | end 448 | 449 | function WarMenu.SpriteButton(text, dict, name, r, g, b, a) 450 | if not currentMenu then 451 | return 452 | end 453 | 454 | local pressed = WarMenu.Button(text) 455 | 456 | local optionIndex = getCurrentOptionIndex() 457 | if not optionIndex then 458 | return 459 | end 460 | 461 | if not HasStreamedTextureDictLoaded(dict) then 462 | RequestStreamedTextureDict(dict) 463 | end 464 | 465 | local buttonSpriteHeight = buttonSpriteWidth * GetAspectRatio() 466 | DrawSprite(dict, name, 467 | getStyleProperty('x') + getStyleProperty('width') - buttonSpriteWidth / 2 - buttonSpriteXOffset, 468 | getStyleProperty('y') + getTitleHeight() + buttonHeight + (buttonHeight * optionIndex) - buttonSpriteHeight / 2 + 469 | buttonSpriteYOffset, buttonSpriteWidth, buttonSpriteHeight, 0., r or 255, g or 255, b or 255, a or 255) 470 | 471 | return pressed 472 | end 473 | 474 | function WarMenu.InputButton(text, windowTitleEntry, defaultText, maxLength, subText) 475 | if not currentMenu then 476 | return 477 | end 478 | 479 | local pressed = WarMenu.Button(text, subText) 480 | local inputText = nil 481 | 482 | if pressed then 483 | DisplayOnscreenKeyboard(1, windowTitleEntry or 'FMMC_MPM_NA', '', defaultText or '', '', '', '', maxLength or 255) 484 | 485 | while true do 486 | local status = UpdateOnscreenKeyboard() 487 | if status == 2 then 488 | break 489 | elseif status == 1 then 490 | inputText = GetOnscreenKeyboardResult() 491 | break 492 | end 493 | 494 | Citizen.Wait(0) 495 | end 496 | end 497 | 498 | return pressed, inputText 499 | end 500 | 501 | function WarMenu.MenuButton(text, id, subText) 502 | if not currentMenu then 503 | return 504 | end 505 | 506 | local pressed = WarMenu.Button(text, subText) 507 | 508 | if pressed then 509 | currentMenu.optionIndex = currentOptionCount 510 | setMenuVisible(currentMenu.id, false) 511 | setMenuVisible(id, true, true) 512 | end 513 | 514 | return pressed 515 | end 516 | 517 | function WarMenu.CheckBox(text, checked) 518 | if not currentMenu then 519 | return 520 | end 521 | 522 | local name = nil 523 | if currentMenu.optionIndex == currentOptionCount + 1 then 524 | name = checked and 'shop_box_tickb' or 'shop_box_blankb' 525 | else 526 | name = checked and 'shop_box_tick' or 'shop_box_blank' 527 | end 528 | 529 | return WarMenu.SpriteButton(text, 'commonmenu', name) 530 | end 531 | 532 | function WarMenu.ComboBox(text, items, currentIndex) 533 | if not currentMenu then 534 | return 535 | end 536 | 537 | local itemsCount = #items 538 | local selectedItem = items[currentIndex] 539 | local isCurrent = currentMenu.optionIndex == currentOptionCount + 1 540 | 541 | if itemsCount > 1 and isCurrent then 542 | selectedItem = '← ' .. tostring(selectedItem) .. ' →' 543 | end 544 | 545 | local pressed = WarMenu.Button(text, selectedItem) 546 | 547 | if not pressed and isCurrent then 548 | if currentKey == keys.left then 549 | if currentIndex > 1 then currentIndex = currentIndex - 1 else currentIndex = itemsCount end 550 | elseif currentKey == keys.right then 551 | if currentIndex < itemsCount then currentIndex = currentIndex + 1 else currentIndex = 1 end 552 | end 553 | end 554 | 555 | return pressed, currentIndex 556 | end 557 | 558 | function WarMenu.Display() 559 | if not currentMenu then 560 | return 561 | end 562 | 563 | if not IsPauseMenuActive() then 564 | ClearAllHelpMessages() 565 | HudWeaponWheelIgnoreSelection() 566 | DisablePlayerFiring(PlayerId(), true) 567 | DisableControlAction(0, 25, true) 568 | 569 | drawTitle() 570 | drawSubTitle() 571 | 572 | currentKey = nil 573 | 574 | if skipInputNextFrame then 575 | skipInputNextFrame = false 576 | else 577 | if isNavigatedDown() then 578 | PlaySoundFrontend(-1, 'NAV_UP_DOWN', 'HUD_FRONTEND_DEFAULT_SOUNDSET', true) 579 | 580 | if currentMenu.optionIndex < currentOptionCount then 581 | currentMenu.optionIndex = currentMenu.optionIndex + 1 582 | else 583 | currentMenu.optionIndex = 1 584 | end 585 | elseif isNavigatedUp() then 586 | PlaySoundFrontend(-1, 'NAV_UP_DOWN', 'HUD_FRONTEND_DEFAULT_SOUNDSET', true) 587 | 588 | if currentMenu.optionIndex > 1 then 589 | currentMenu.optionIndex = currentMenu.optionIndex - 1 590 | else 591 | currentMenu.optionIndex = currentOptionCount 592 | end 593 | elseif IsControlJustReleased(2, keys.left) then 594 | currentKey = keys.left 595 | elseif IsControlJustReleased(2, keys.right) then 596 | currentKey = keys.right 597 | elseif isSelectedPressed() then 598 | currentKey = keys.select 599 | elseif isBackPressed() then 600 | if menus[currentMenu.parentId] then 601 | setMenuVisible(currentMenu.parentId, true) 602 | PlaySoundFrontend(-1, 'BACK', 'HUD_FRONTEND_DEFAULT_SOUNDSET', true) 603 | else 604 | WarMenu.CloseMenu() 605 | end 606 | end 607 | end 608 | end 609 | 610 | currentOptionCount = 0 611 | end 612 | 613 | WarMenu.End = WarMenu.Display 614 | 615 | function WarMenu.CurrentOption() 616 | if currentMenu and currentOptionCount ~= 0 then 617 | return currentMenu.optionIndex 618 | end 619 | 620 | return nil 621 | end 622 | 623 | WarMenu.OptionIndex = WarMenu.CurrentOption 624 | 625 | function WarMenu.IsItemHovered() 626 | if not currentMenu or currentOptionCount == 0 then 627 | return false 628 | end 629 | 630 | return currentMenu.optionIndex == currentOptionCount 631 | end 632 | 633 | function WarMenu.IsItemSelected() 634 | return currentKey == keys.select and WarMenu.IsItemHovered() 635 | end 636 | 637 | function WarMenu.SetTitle(id, title) 638 | setMenuProperty(id, 'title', title) 639 | end 640 | 641 | WarMenu.SetMenuTitle = WarMenu.SetTitle 642 | 643 | function WarMenu.SetSubTitle(id, subTitle) 644 | setMenuProperty(id, 'subTitle', string.upper(subTitle)) 645 | end 646 | 647 | WarMenu.SetMenuSubTitle = WarMenu.SetSubTitle 648 | 649 | function WarMenu.SetMenuStyle(id, style) 650 | setMenuProperty(id, 'style', style) 651 | end 652 | 653 | function WarMenu.SetMenuTitleVisible(id, visible) 654 | setStyleProperty(id, 'titleVisible', visible) 655 | end 656 | 657 | function WarMenu.SetMenuX(id, x) 658 | setStyleProperty(id, 'x', x) 659 | end 660 | 661 | function WarMenu.SetMenuY(id, y) 662 | setStyleProperty(id, 'y', y) 663 | end 664 | 665 | function WarMenu.SetMenuWidth(id, width) 666 | setStyleProperty(id, 'width', width) 667 | end 668 | 669 | function WarMenu.SetMenuMaxOptionCountOnScreen(id, optionCount) 670 | setStyleProperty(id, 'maxOptionCountOnScreen', optionCount) 671 | end 672 | 673 | function WarMenu.SetTitleColor(id, r, g, b, a) 674 | setStyleProperty(id, 'titleColor', { r, g, b, a }) 675 | end 676 | 677 | WarMenu.SetMenuTitleColor = WarMenu.SetTitleColor 678 | 679 | function WarMenu.SetMenuSubTitleColor(id, r, g, b, a) 680 | setStyleProperty(id, 'subTitleColor', { r, g, b, a }) 681 | end 682 | 683 | function WarMenu.SetMenuSubTitleBackgroundColor(id, r, g, b, a) 684 | setStyleProperty(id, 'subTitleBackgroundColor', { r, g, b, a }) 685 | end 686 | 687 | function WarMenu.SetTitleBackgroundColor(id, r, g, b, a) 688 | setStyleProperty(id, 'titleBackgroundColor', { r, g, b, a }) 689 | end 690 | 691 | WarMenu.SetMenuTitleBackgroundColor = WarMenu.SetTitleBackgroundColor 692 | 693 | function WarMenu.SetTitleBackgroundSprite(id, dict, name) 694 | RequestStreamedTextureDict(dict) 695 | setStyleProperty(id, 'titleBackgroundSprite', { dict = dict, name = name }) 696 | end 697 | 698 | WarMenu.SetMenuTitleBackgroundSprite = WarMenu.SetTitleBackgroundSprite 699 | 700 | function WarMenu.SetMenuBackgroundColor(id, r, g, b, a) 701 | setStyleProperty(id, 'backgroundColor', { r, g, b, a }) 702 | end 703 | 704 | function WarMenu.SetMenuTextColor(id, r, g, b, a) 705 | setStyleProperty(id, 'textColor', { r, g, b, a }) 706 | end 707 | 708 | function WarMenu.SetMenuSubTextColor(id, r, g, b, a) 709 | setStyleProperty(id, 'subTextColor', { r, g, b, a }) 710 | end 711 | 712 | function WarMenu.SetMenuFocusColor(id, r, g, b, a) 713 | setStyleProperty(id, 'focusColor', { r, g, b, a }) 714 | end 715 | 716 | function WarMenu.SetMenuFocusTextColor(id, r, g, b, a) 717 | setStyleProperty(id, 'focusTextColor', { r, g, b, a }) 718 | end 719 | 720 | function WarMenu.SetMenuButtonPressedSound(id, name, set) 721 | setStyleProperty(id, 'buttonPressedSound', { name = name, set = set }) 722 | end 723 | --------------------------------------------------------------------------------