From dfb6ea5572603338d4ba289516ef1cc0b1c83d01 Mon Sep 17 00:00:00 2001 From: MrPizza <5529325+sir-lord-pizza@users.noreply.github.com> Date: Wed, 26 Aug 2026 16:22:28 +0200 Subject: [PATCH 01/10] chore(usings): drop usings these files no longer need --- MenuAPI/HeaderGlare.cs | 3 --- MenuAPI/ticks/MenuTickHandle.cs | 2 -- 2 files changed, 5 deletions(-) diff --git a/MenuAPI/HeaderGlare.cs b/MenuAPI/HeaderGlare.cs index 078aadb..917bd73 100644 --- a/MenuAPI/HeaderGlare.cs +++ b/MenuAPI/HeaderGlare.cs @@ -1,6 +1,3 @@ -using CitizenFX.FiveM.Client; -using CitizenFX.FiveM.Shared.Data; - namespace MenuAPI; /// diff --git a/MenuAPI/ticks/MenuTickHandle.cs b/MenuAPI/ticks/MenuTickHandle.cs index c8bd865..bbe2f28 100644 --- a/MenuAPI/ticks/MenuTickHandle.cs +++ b/MenuAPI/ticks/MenuTickHandle.cs @@ -1,5 +1,3 @@ -using CitizenFX.FiveM.Client; - namespace MenuAPI; /// From 98d754d935960ae55ecc188189fcb983bb698624 Mon Sep 17 00:00:00 2001 From: MrPizza <5529325+sir-lord-pizza@users.noreply.github.com> Date: Wed, 26 Aug 2026 16:23:42 +0200 Subject: [PATCH 02/10] perf(ticks): cache the game state reads that every tick repeats --- MenuAPI/MenuController.cs | 34 +++--- MenuAPI/ticks/FrameState.cs | 128 ++++++++++++++++++++++ MenuAPI/ticks/InstructionalButtonIcons.cs | 4 +- MenuAPI/ticks/MenuTicks.cs | 4 +- 4 files changed, 149 insertions(+), 21 deletions(-) create mode 100644 MenuAPI/ticks/FrameState.cs diff --git a/MenuAPI/MenuController.cs b/MenuAPI/MenuController.cs index 198a8bf..a93a552 100644 --- a/MenuAPI/MenuController.cs +++ b/MenuAPI/MenuController.cs @@ -46,11 +46,11 @@ public class MenuController : IScript public static bool AreMenuButtonsEnabled => IsAnyMenuOpen() && - !Native.IsPauseMenuActive() && - Native.IsScreenFadedIn() && - !Native.IsPlayerSwitchInProgress() && + !FrameState.IsPauseMenuActive && + FrameState.IsScreenFadedIn && + !FrameState.IsPlayerSwitchInProgress && !DisableMenuButtons && - !API.Players.Local.IsDead && + !FrameState.IsDead && !IsF8ConsoleLikelyOpen; public static bool NavigateMenuUsingArrows { get; set; } = true; @@ -469,7 +469,7 @@ private static async Task ProcessMainButtons() bool selectPressed = MenuKeyBindings.ConsumeSelect(); bool backPressed = MenuKeyBindings.ConsumeBack(); - if (Native.IsPauseMenuActive()) + if (FrameState.IsPauseMenuActive) { return; } @@ -566,7 +566,7 @@ private static void HandlePreventExit() /// private static bool IsUsingWeaponWheel() { - if (API.Players.Local.Ped.IsPedInAnyVehicle()) + if (FrameState.IsInVehicle) { return false; } @@ -681,7 +681,7 @@ private static void ProcessToggleMenuButton() return; } - if (Native.IsPauseMenuActive() || Native.IsPauseMenuRestarting() || !Native.IsScreenFadedIn() || Native.IsPlayerSwitchInProgress() || API.Players.Local.IsDead || DisableMenuButtons) + if (FrameState.IsPauseMenuActive || Native.IsPauseMenuRestarting() || !FrameState.IsScreenFadedIn || FrameState.IsPlayerSwitchInProgress || FrameState.IsDead || DisableMenuButtons) { return; } @@ -714,7 +714,7 @@ private static async Task ProcessControllerToggle() return; } - if (Native.IsPauseMenuActive() || Native.IsPauseMenuRestarting() || !Native.IsScreenFadedIn() || Native.IsPlayerSwitchInProgress() || API.Players.Local.IsDead || DisableMenuButtons) + if (FrameState.IsPauseMenuActive || Native.IsPauseMenuRestarting() || !FrameState.IsScreenFadedIn || FrameState.IsPlayerSwitchInProgress || FrameState.IsDead || DisableMenuButtons) { return; } @@ -955,7 +955,7 @@ private static async Task HandleDownNavigation(Menu currentMenu) private static async Task HandleMenuToggleKeyForController() { int tmpTimer = Native.GetGameTimer(); - while ((Native.IsControlPressed(0, (int)Control.InteractionMenu) || Native.IsDisabledControlPressed(0, (int)Control.InteractionMenu)) && !Native.IsPauseMenuActive() && Native.IsScreenFadedIn() && !API.Players.Local.IsDead && !Native.IsPlayerSwitchInProgress() && !DontOpenAnyMenu) + while ((Native.IsControlPressed(0, (int)Control.InteractionMenu) || Native.IsDisabledControlPressed(0, (int)Control.InteractionMenu)) && !FrameState.IsPauseMenuActive && FrameState.IsScreenFadedIn && !FrameState.IsDead && !FrameState.IsPlayerSwitchInProgress && !DontOpenAnyMenu) { if (Native.GetGameTimer() - tmpTimer > 400) { @@ -1025,7 +1025,7 @@ private static async Task HandleUpNavigation(Menu currentMenu) private static async Task MenuButtonsDisableChecks() { - static bool isInputVisible() => Native.UpdateOnscreenKeyboard() == 0; + static bool isInputVisible() => FrameState.OnscreenKeyboard == 0; if (isInputVisible()) { bool buttonsState = DisableMenuButtons; @@ -1070,7 +1070,7 @@ private static void DisableControls() return; } - if (API.Players.Local.IsDead) + if (FrameState.IsDead) { // Close all menus when the player dies. CloseAllMenus(); @@ -1087,7 +1087,7 @@ private static void DisableControls() Native.DisableControlAction(0, (int)Control.InteractionMenu, false); // When in a vehicle - if (API.Players.Local.Ped.IsPedInAnyVehicle()) + if (FrameState.IsInVehicle) { Native.DisableControlAction(0, (int)Control.VehicleSelectNextWeapon, false); Native.DisableControlAction(0, (int)Control.VehicleSelectPrevWeapon, false); @@ -1106,7 +1106,7 @@ private static void DisableGenericControls(Menu currMenu) { Native.DisableControlAction(0, (int)Control.MultiplayerInfo, false); // when in a vehicle. - if (API.Players.Local.Ped.IsPedInAnyVehicle()) + if (FrameState.IsInVehicle) { Native.DisableControlAction(0, (int)Control.VehicleHeadlight, false); Native.DisableControlAction(0, (int)Control.VehicleDuck, false); @@ -1223,10 +1223,10 @@ private static async Task ProcessMenus() /// The game states that stop a menu being drawn, none of which announce a change. private static bool CanDraw() => - Native.IsScreenFadedIn() && - !Native.IsPauseMenuActive() && - !API.Players.Local.IsDead && - !Native.IsPlayerSwitchInProgress(); + FrameState.IsScreenFadedIn && + !FrameState.IsPauseMenuActive && + !FrameState.IsDead && + !FrameState.IsPlayerSwitchInProgress; private static async Task DrawMenus() { diff --git a/MenuAPI/ticks/FrameState.cs b/MenuAPI/ticks/FrameState.cs new file mode 100644 index 0000000..184c8b3 --- /dev/null +++ b/MenuAPI/ticks/FrameState.cs @@ -0,0 +1,128 @@ +namespace MenuAPI; + +internal static class FrameState +{ + private static int _ped; + private static bool _hasPed; + private static bool _isDead; + private static bool _hasIsDead; + private static bool _isInVehicle; + private static bool _hasIsInVehicle; + private static bool _isScreenFadedIn; + private static bool _hasIsScreenFadedIn; + private static bool _isPauseMenuActive; + private static bool _hasIsPauseMenuActive; + private static bool _isPlayerSwitchInProgress; + private static bool _hasIsPlayerSwitchInProgress; + private static int _onscreenKeyboard; + private static bool _hasOnscreenKeyboard; + + internal static void Invalidate() + { + _hasPed = false; + _hasIsDead = false; + _hasIsInVehicle = false; + _hasIsScreenFadedIn = false; + _hasIsPauseMenuActive = false; + _hasIsPlayerSwitchInProgress = false; + _hasOnscreenKeyboard = false; + } + + internal static int Ped + { + get + { + if (!_hasPed) + { + _ped = Native.PlayerPedId(); + _hasPed = true; + } + + return _ped; + } + } + + internal static bool IsDead + { + get + { + if (!_hasIsDead) + { + _isDead = Native.IsPlayerDead(Native.PlayerId()); + _hasIsDead = true; + } + + return _isDead; + } + } + + internal static bool IsInVehicle + { + get + { + if (!_hasIsInVehicle) + { + _isInVehicle = Native.IsPedInAnyVehicle(Ped, false); + _hasIsInVehicle = true; + } + + return _isInVehicle; + } + } + + internal static bool IsScreenFadedIn + { + get + { + if (!_hasIsScreenFadedIn) + { + _isScreenFadedIn = Native.IsScreenFadedIn(); + _hasIsScreenFadedIn = true; + } + + return _isScreenFadedIn; + } + } + + internal static bool IsPauseMenuActive + { + get + { + if (!_hasIsPauseMenuActive) + { + _isPauseMenuActive = Native.IsPauseMenuActive(); + _hasIsPauseMenuActive = true; + } + + return _isPauseMenuActive; + } + } + + internal static bool IsPlayerSwitchInProgress + { + get + { + if (!_hasIsPlayerSwitchInProgress) + { + _isPlayerSwitchInProgress = Native.IsPlayerSwitchInProgress(); + _hasIsPlayerSwitchInProgress = true; + } + + return _isPlayerSwitchInProgress; + } + } + + internal static int OnscreenKeyboard + { + get + { + if (!_hasOnscreenKeyboard) + { + _onscreenKeyboard = Native.UpdateOnscreenKeyboard(); + _hasOnscreenKeyboard = true; + } + + return _onscreenKeyboard; + } + } +} diff --git a/MenuAPI/ticks/InstructionalButtonIcons.cs b/MenuAPI/ticks/InstructionalButtonIcons.cs index d7b244d..a5cd412 100644 --- a/MenuAPI/ticks/InstructionalButtonIcons.cs +++ b/MenuAPI/ticks/InstructionalButtonIcons.cs @@ -1,5 +1,3 @@ -using CitizenFX.FiveM.Client; - namespace MenuAPI; /// @@ -33,7 +31,7 @@ internal static void Refresh() // The key mapping settings live in the pause menu, so a rebind is always followed by the pause // menu closing. Catching that edge is what keeps a rebound key's icon from staying wrong for as // long as the menu is open. - bool pauseMenuActive = Native.IsPauseMenuActive(); + bool pauseMenuActive = FrameState.IsPauseMenuActive; bool leftPauseMenu = pauseMenuWasActive && !pauseMenuActive; pauseMenuWasActive = pauseMenuActive; diff --git a/MenuAPI/ticks/MenuTicks.cs b/MenuAPI/ticks/MenuTicks.cs index 3a3ee11..74c0fde 100644 --- a/MenuAPI/ticks/MenuTicks.cs +++ b/MenuAPI/ticks/MenuTicks.cs @@ -1,4 +1,4 @@ -using CitizenFX.FiveM.Client; +using CitizenFX.FiveM.Client; using CitizenFX.FiveM.Shared; namespace MenuAPI; @@ -66,6 +66,8 @@ internal static void Initialize() private static void Flush() { + FrameState.Invalidate(); + if (!_reevaluatePending) { return; From c4e22872727b1e527ea8bb2df7c69723bd14e89e Mon Sep 17 00:00:00 2001 From: MrPizza <5529325+sir-lord-pizza@users.noreply.github.com> Date: Wed, 26 Aug 2026 16:24:21 +0200 Subject: [PATCH 03/10] perf(instructional-buttons): refill the slots on a slow tick instead of every frame --- MenuAPI/MenuController.cs | 75 +++++++++++++++++++++++++++------------ 1 file changed, 52 insertions(+), 23 deletions(-) diff --git a/MenuAPI/MenuController.cs b/MenuAPI/MenuController.cs index a93a552..578220e 100644 --- a/MenuAPI/MenuController.cs +++ b/MenuAPI/MenuController.cs @@ -1,6 +1,4 @@ -using CitizenFX.FiveM.Client; -using CitizenFX.FiveM.Client.Extensions; -using CitizenFX.FiveM.Shared.Data; +using CitizenFX.FiveM.Client; using CitizenFX.FiveM.Shared.Script; namespace MenuAPI; @@ -34,6 +32,8 @@ public class MenuController : IScript // to be often enough that the menu has settled by the time they look at it again. private const long LayoutRefreshIntervalMs = 500; + private const long InstructionalButtonsConfigureIntervalMs = 300; + private static float AspectRatio => Native.GetScreenAspectRatio(false); public static float ScreenWidth => 1080 * AspectRatio; public static float ScreenHeight => 1080; @@ -123,6 +123,8 @@ public static bool EnableMenuToggleKeyOnController internal static int _scale = Native.RequestScaleformMovie("INSTRUCTIONAL_BUTTONS"); + private static Menu? _instructionalButtonsMenu; + // Whether the mouse button was pressed down while a menu was open, see IsMouseButtonUsed. private static bool mouseSelectArmed = false; private static bool mouseBackArmed = false; @@ -195,6 +197,9 @@ public void Initialize() InstructionalButtonIcons.Clear(); }); + MenuTicks.Register("Menu.InstructionalButtonsData", ConfigureInstructionalButtons, + MenuTickRate.Every(InstructionalButtonsConfigureIntervalMs), IsAnyMenuOpen); + MenuTicks.Register("Menu.Select", ProcessMainButtons, MenuTickRate.PerFrame, IsAnyMenuOpen, // Nothing drains input while every menu is closed, so a menu has to open from a clean // slate rather than acting on presses that arrived when there was nothing to act on. @@ -1248,42 +1253,65 @@ private static async Task DrawMenus() } } - internal static async Task DrawInstructionalButtons() + internal static void DrawInstructionalButtons() { - // Whether a menu is open is the tick's own condition. What is left is volatile game state - // that changes every frame, so it stays inline. - if ( - Native.IsPlayerSwitchInProgress() || - API.Players.Local.IsDead || - !Native.IsScreenFadedIn() || - Native.IsWarningMessageActive() || - Native.UpdateOnscreenKeyboard() == 0 - ) + Menu? menu = GetCurrentMenu(); + + if (menu == null || !CanShowInstructionalButtons(menu) || !Native.HasScaleformMovieLoaded(_scale)) { - DisposeInstructionalButtonsScaleform(); return; } + + if (!ReferenceEquals(menu, _instructionalButtonsMenu)) + { + FillInstructionalButtonSlots(menu); + } + + Native.DrawScaleformMovieFullscreen(_scale, 255, 255, 255, 255, 0); + } + + // On a slow loop: what an icon looks like only changes when the player swaps between keyboard + // and controller or rebinds a key. Drawing the bar still has to happen every frame. + internal static async Task ConfigureInstructionalButtons() + { Menu? menu = GetCurrentMenu(); - if (menu == null || !menu.Visible || !menu.EnableInstructionalButtons) + + if (menu == null || !CanShowInstructionalButtons(menu)) { DisposeInstructionalButtonsScaleform(); + return; } + if (!Native.HasScaleformMovieLoaded(_scale)) { _scale = Native.RequestScaleformMovie("INSTRUCTIONAL_BUTTONS"); - } - while (!Native.HasScaleformMovieLoaded(_scale)) - { - await API.Delay(0); + + while (!Native.HasScaleformMovieLoaded(_scale)) + { + await API.Delay(0); + } } - Native.DrawScaleformMovieFullscreen(_scale, 255, 255, 255, 0, 0); + FillInstructionalButtonSlots(menu); + } + + private static bool CanShowInstructionalButtons(Menu menu) + { + return menu.Visible && + menu.EnableInstructionalButtons && + !FrameState.IsPlayerSwitchInProgress && + !FrameState.IsDead && + FrameState.IsScreenFadedIn && + !Native.IsWarningMessageActive() && + FrameState.OnscreenKeyboard != 0; + } + private static void FillInstructionalButtonSlots(Menu menu) + { Native.BeginScaleformMovieMethod(_scale, "CLEAR_ALL"); Native.EndScaleformMovieMethod(); - // Once here rather than at each icon below, and the only place that has to run every frame. InstructionalButtonIcons.Refresh(); int slot = 0; @@ -1305,7 +1333,6 @@ internal static async Task DrawInstructionalButtons() } // Enumerated rather than indexed: ElementAt on a dictionary walks it from the start every - // time, so indexing it in a loop re-walked the whole thing once per button, every frame. foreach (KeyValuePair button in menu.InstructionalButtons) { SetInstructionalButtonSlot(slot++, InstructionalButtonIcons.For((int)button.Key), button.Value); @@ -1321,7 +1348,7 @@ internal static async Task DrawInstructionalButtons() Native.ScaleformMovieMethodAddParamInt(0); Native.EndScaleformMovieMethod(); - Native.DrawScaleformMovieFullscreen(_scale, 255, 255, 255, 255, 0); + _instructionalButtonsMenu = menu; } private static void SetInstructionalButtonSlot(int slot, string buttonString, string text) @@ -1339,5 +1366,7 @@ private static void DisposeInstructionalButtonsScaleform() { Native.SetScaleformMovieAsNoLongerNeeded(ref _scale); } + + _instructionalButtonsMenu = null; } } \ No newline at end of file From 3694bd01b1f6144df92945625f7178bd18c1d5b1 Mon Sep 17 00:00:00 2001 From: MrPizza <5529325+sir-lord-pizza@users.noreply.github.com> Date: Wed, 26 Aug 2026 16:24:47 +0200 Subject: [PATCH 04/10] fix(menu): keep non finite stat values out of the stat panels --- MenuAPI/Menu.cs | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/MenuAPI/Menu.cs b/MenuAPI/Menu.cs index f717c1c..959dd76 100644 --- a/MenuAPI/Menu.cs +++ b/MenuAPI/Menu.cs @@ -1,5 +1,4 @@ -using CitizenFX.FiveM.Client; -using CitizenFX.FiveM.Client.Extensions; +using CitizenFX.FiveM.Client; namespace MenuAPI; @@ -1227,10 +1226,10 @@ public void SetWeaponStats(float damage, float fireRate, float accuracy, float r { WeaponStats = new float[4] { - Math.Clamp(damage, 0f, 1f), - Math.Clamp(fireRate, 0f, 1f), - Math.Clamp(accuracy, 0f, 1f), - Math.Clamp(range, 0f, 1f) + ClampStat(damage), + ClampStat(fireRate), + ClampStat(accuracy), + ClampStat(range) }; } @@ -1245,10 +1244,10 @@ public void SetWeaponComponentStats(float damage, float fireRate, float accuracy { WeaponComponentStats = new float[4] { - Math.Clamp(WeaponStats[0] + damage, 0f, 1f), - Math.Clamp(WeaponStats[1] + fireRate, 0f, 1f), - Math.Clamp(WeaponStats[2] + accuracy, 0f, 1f), - Math.Clamp(WeaponStats[3] + range, 0f, 1f) + ClampStat(WeaponStats[0] + damage), + ClampStat(WeaponStats[1] + fireRate), + ClampStat(WeaponStats[2] + accuracy), + ClampStat(WeaponStats[3] + range) }; } @@ -1263,10 +1262,10 @@ public void SetVehicleStats(float topSpeed, float acceleration, float braking, f { VehicleStats = new float[4] { - Math.Clamp(topSpeed, 0f, 1f), - Math.Clamp(acceleration, 0f, 1f), - Math.Clamp(braking, 0f, 1f), - Math.Clamp(traction, 0f, 1f) + ClampStat(topSpeed), + ClampStat(acceleration), + ClampStat(braking), + ClampStat(traction) }; } @@ -1284,12 +1283,14 @@ public void SetVehicleUpgradeStats(float topSpeed, float acceleration, float bra { VehicleUpgradeStats = new float[4] { - Math.Clamp(VehicleStats[0] + topSpeed, 0f, 1f), - Math.Clamp(VehicleStats[1] + acceleration, 0f, 1f), - Math.Clamp(VehicleStats[2] + braking, 0f, 1f), - Math.Clamp(VehicleStats[3] + traction, 0f, 1f) + ClampStat(VehicleStats[0] + topSpeed), + ClampStat(VehicleStats[1] + acceleration), + ClampStat(VehicleStats[2] + braking), + ClampStat(VehicleStats[3] + traction) }; } + + private static float ClampStat(float value) => float.IsFinite(value) ? Math.Clamp(value, 0f, 1f) : 0f; #endregion #region internal/private task functions From 5ed45b454fd8df91c16f43d4fe9defb98ddc8e4a Mon Sep 17 00:00:00 2001 From: MrPizza <5529325+sir-lord-pizza@users.noreply.github.com> Date: Wed, 26 Aug 2026 16:25:47 +0200 Subject: [PATCH 05/10] feat(nui): add a NUI render mode that draws menus as a web page --- MenuAPI/Menu.cs | 199 ++++++- MenuAPI/MenuAPI.csproj | 9 + MenuAPI/MenuController.cs | 123 ++++- MenuAPI/TextureDictionaries.cs | 5 +- MenuAPI/items/MenuCheckboxItem.cs | 34 +- MenuAPI/items/MenuDynamicListItem.cs | 23 +- MenuAPI/items/MenuItem.cs | 78 ++- MenuAPI/items/MenuItemList.cs | 185 +++++++ MenuAPI/items/MenuListItem.cs | 57 +- MenuAPI/items/MenuSliderItem.cs | 66 ++- MenuAPI/items/SeparatorMenuItem.cs | 10 +- MenuAPI/nui/MenuNui.cs | 512 ++++++++++++++++++ MenuAPI/nui/MenuRenderMode.cs | 7 + MenuAPI/nui/NuiJson.cs | 230 ++++++++ MenuAPI/nui/NuiTuning.cs | 70 +++ MenuAPI/ticks/MenuLayout.cs | 12 +- MenuAPI/ui/assets/arrow.svg | 3 + MenuAPI/ui/assets/glint.png | Bin 0 -> 3833 bytes MenuAPI/ui/colour-list.css | 196 +++++++ MenuAPI/ui/colour-list.js | 346 ++++++++++++ MenuAPI/ui/fonts/chalet-london.woff | Bin 0 -> 26760 bytes MenuAPI/ui/fonts/pricedown.woff | Bin 0 -> 12656 bytes MenuAPI/ui/glare.js | 341 ++++++++++++ MenuAPI/ui/menuapi.css | 478 ++++++++++++++++ MenuAPI/ui/menuapi.js | 500 +++++++++++++++++ TestMenu/ExampleMenu.cs | 172 +++++- TestMenu/TestMenu.csproj | 1 + TestMenu/fxmanifest.lua | 5 + TestMenu/index.html | 22 + docs/enhanced/src/content/docs/changelog.md | 28 + .../docs/reference/menuitems/menulistitem.md | 16 +- 31 files changed, 3610 insertions(+), 118 deletions(-) create mode 100644 MenuAPI/items/MenuItemList.cs create mode 100644 MenuAPI/nui/MenuNui.cs create mode 100644 MenuAPI/nui/MenuRenderMode.cs create mode 100644 MenuAPI/nui/NuiJson.cs create mode 100644 MenuAPI/nui/NuiTuning.cs create mode 100644 MenuAPI/ui/assets/arrow.svg create mode 100644 MenuAPI/ui/assets/glint.png create mode 100644 MenuAPI/ui/colour-list.css create mode 100644 MenuAPI/ui/colour-list.js create mode 100644 MenuAPI/ui/fonts/chalet-london.woff create mode 100644 MenuAPI/ui/fonts/pricedown.woff create mode 100644 MenuAPI/ui/glare.js create mode 100644 MenuAPI/ui/menuapi.css create mode 100644 MenuAPI/ui/menuapi.js create mode 100644 TestMenu/index.html diff --git a/MenuAPI/Menu.cs b/MenuAPI/Menu.cs index 959dd76..23c8853 100644 --- a/MenuAPI/Menu.cs +++ b/MenuAPI/Menu.cs @@ -345,11 +345,29 @@ internal virtual void PageChangeEvent(Menu menu, int oldPage, int newPage, bool private int index = 0; + private int viewIndexOffset = 0; + + private int pageIndex = 0; + private bool visible = false; - public int ViewIndexOffset { get; private set; } = 0; + public int ViewIndexOffset + { + get => viewIndexOffset; + private set + { + if (viewIndexOffset == value) + { + return; + } + + viewIndexOffset = value; + + MenuNui.Invalidate(); + } + } - private List VisibleMenuItems + internal List VisibleMenuItems { get { @@ -405,7 +423,12 @@ private List ActiveItems private bool pageItemsDirty = true; /// Anything that changes what belongs on the current page calls this. - private void InvalidatePage() => pageItemsDirty = true; + private void InvalidatePage() + { + pageItemsDirty = true; + + MenuNui.Invalidate(); + } // What is currently loaded into ColorPanelScaleform, so the 64 swatches are only re-sent when // they would actually differ. See DrawColorAndOpacityPanel. Static because the scaleform is. @@ -421,11 +444,29 @@ private List ActiveItems #endregion #region Public Variables - public string? MenuTitle { get; set; } + public string? MenuTitle + { + get => _menuTitle; + set => MenuNui.Change(ref _menuTitle, value); + } + + private string? _menuTitle; + + public string? MenuSubtitle + { + get => _menuSubtitle; + set => MenuNui.Change(ref _menuSubtitle, value); + } + + private string? _menuSubtitle; - public string? MenuSubtitle { get; set; } + public KeyValuePair HeaderTexture + { + get => _headerTexture; + set => MenuNui.Change(ref _headerTexture, value); + } - public KeyValuePair HeaderTexture { get; set; } = new KeyValuePair(); + private KeyValuePair _headerTexture = new KeyValuePair(); #region Menu title styling // Nullable, and falling back to the matching MenuController.Default*, so a resource can style @@ -437,10 +478,22 @@ private List ActiveItems // hangs off its own header helps nobody. /// The font the title is drawn in. See . - public int? MenuTitleFont { get; set; } + public int? MenuTitleFont + { + get => _menuTitleFont; + set => MenuNui.Change(ref _menuTitleFont, value); + } + + private int? _menuTitleFont; /// Where the title sits inside the header. - public TitleAlignmentOption? MenuTitleAlignment { get; set; } + public TitleAlignmentOption? MenuTitleAlignment + { + get => _menuTitleAlignment; + set => MenuNui.Change(ref _menuTitleAlignment, value); + } + + private TitleAlignmentOption? _menuTitleAlignment; public enum TitleAlignmentOption { @@ -450,18 +503,30 @@ public enum TitleAlignmentOption } /// Whether GTA Online's moving header glow is drawn over the banner. - public bool? ShowHeaderGlare { get; set; } + public bool? ShowHeaderGlare + { + get => _showHeaderGlare; + set => MenuNui.Change(ref _showHeaderGlare, value); + } + + private bool? _showHeaderGlare; - private int ResolvedTitleFont => MenuTitleFont ?? MenuController.DefaultTitleFont; + internal int ResolvedTitleFont => MenuTitleFont ?? MenuController.DefaultTitleFont; - private TitleAlignmentOption ResolvedTitleAlignment => MenuTitleAlignment ?? MenuController.DefaultTitleAlignment; + internal TitleAlignmentOption ResolvedTitleAlignment => MenuTitleAlignment ?? MenuController.DefaultTitleAlignment; - private bool ResolvedShowHeaderGlare => ShowHeaderGlare ?? MenuController.DefaultShowHeaderGlare; + internal bool ResolvedShowHeaderGlare => ShowHeaderGlare ?? MenuController.DefaultShowHeaderGlare; #endregion public bool IgnoreDontOpenMenus { get; set; } = false; - public int MaxItemsOnScreen { get; internal set; } = 10; + public int MaxItemsOnScreen + { + get => _maxItemsOnScreen; + internal set => MenuNui.Change(ref _maxItemsOnScreen, value); + } + + private int _maxItemsOnScreen = 10; public int Size => ActiveItems.Count; @@ -472,7 +537,21 @@ public enum TitleAlignmentOption public bool Paginated => PageSize > 0; /// The page currently being shown, counting from 0. - public int PageIndex { get; private set; } = 0; + public int PageIndex + { + get => pageIndex; + private set + { + if (pageIndex == value) + { + return; + } + + pageIndex = value; + + MenuNui.Invalidate(); + } + } /// Always at least 1, so an empty paginated menu still reads as "page 1 of 1". public int PageCount => Paginated ? Math.Max(1, (SourceItems.Count + PageSize - 1) / PageSize) : 1; @@ -498,6 +577,8 @@ public bool Visible } visible = value; + MenuNui.Invalidate(); + // The single funnel for OpenMenu, CloseMenu, GoBack and CloseAllMenus, so every one of // MenuAPI's ticks learns about a menu opening or closing from right here. Safe to call // from a setter: it only starts a loop, and a loop always waits a frame before its first @@ -513,33 +594,94 @@ public bool Visible public float MenuItemsYOffset { get; private set; } = 0f; - public string? CounterPreText { get; set; } + public string? CounterPreText + { + get => _counterPreText; + set => MenuNui.Change(ref _counterPreText, value); + } + + private string? _counterPreText; public Menu? ParentMenu { get; internal set; } = null; - public int CurrentIndex { get { return index; } internal set { index = Math.Clamp(value, 0, Math.Max(0, Size - 1)); } } + public int CurrentIndex + { + get => index; + internal set + { + var clamped = Math.Clamp(value, 0, Math.Max(0, Size - 1)); + + if (index == clamped) + { + return; + } + + index = clamped; + + MenuNui.Invalidate(); + } + } public bool EnableInstructionalButtons { get; set; } = true; /// /// Should contain 4 floats. /// - public float[] WeaponStats { get; private set; } = new float[4] { 0f, 0f, 0f, 0f }; + public float[] WeaponStats + { + get => _weaponStats; + private set => MenuNui.Change(ref _weaponStats, value); + } + + private float[] _weaponStats = new float[4] { 0f, 0f, 0f, 0f }; /// /// Should contain 4 floats. /// - public float[] WeaponComponentStats { get; private set; } = new float[4] { 0f, 0f, 0f, 0f }; + public float[] WeaponComponentStats + { + get => _weaponComponentStats; + private set => MenuNui.Change(ref _weaponComponentStats, value); + } + + private float[] _weaponComponentStats = new float[4] { 0f, 0f, 0f, 0f }; /// /// Should contain 4 floats. /// - public float[] VehicleStats { get; private set; } = new float[4] { 0f, 0f, 0f, 0f }; + public float[] VehicleStats + { + get => _vehicleStats; + private set => MenuNui.Change(ref _vehicleStats, value); + } + + private float[] _vehicleStats = new float[4] { 0f, 0f, 0f, 0f }; /// /// Should contain 4 floats. /// - public float[] VehicleUpgradeStats { get; private set; } = new float[4] { 0f, 0f, 0f, 0f }; + public float[] VehicleUpgradeStats + { + get => _vehicleUpgradeStats; + private set => MenuNui.Change(ref _vehicleUpgradeStats, value); + } + + private float[] _vehicleUpgradeStats = new float[4] { 0f, 0f, 0f, 0f }; + + public bool ShowWeaponStatsPanel + { + get => _showWeaponStatsPanel; + set => MenuNui.Change(ref _showWeaponStatsPanel, value); + } - public bool ShowWeaponStatsPanel { get; set; } = false; - public bool ShowVehicleStatsPanel { get; set; } = false; + public bool ShowVehicleStatsPanel + { + get => _showVehicleStatsPanel; + set => MenuNui.Change(ref _showVehicleStatsPanel, value); + } + + private bool _showWeaponStatsPanel = false; + private bool _showVehicleStatsPanel = false; + + internal string StatLabelKey(int index) => + ShowWeaponStatsPanel ? weaponStatNames[index] : vehicleStatNames[index]; private readonly string[] weaponStatNames = new string[4] { "PM_DAMAGE", "PM_FIRERATE", "PM_ACCURACY", "PM_RANGE" }; private readonly string[] vehicleStatNames = new string[4] { "FMMC_VEHST_0", "FMMC_VEHST_1", "FMMC_VEHST_2", "FMMC_VEHST_3" }; @@ -988,6 +1130,8 @@ public void SelectItem(MenuItem item) { Native.PlaySoundFrontend(-1, "SELECT", "HUD_FRONTEND_DEFAULT_SOUNDSET", false); item.Select(); + + MenuNui.Invalidate(); if (MenuController.MenuButtons.TryGetValue(item, out var value)) { // Read once. The old code asked for the current menu twice, so the second call could @@ -1130,6 +1274,8 @@ public void GoLeft() if (item != null) { item.GoLeft(); + + MenuNui.Invalidate(); } // If the item is not any of the above, return to parent menu. else if (MenuController.NavigateMenuUsingArrows && !MenuController.DisableBackButton && !(MenuController.PreventExitingMenu && ParentMenu == null)) @@ -1155,7 +1301,12 @@ public void GoRight() return; } - item?.GoRight(); + if (item != null) + { + item.GoRight(); + + MenuNui.Invalidate(); + } } /// @@ -1297,7 +1448,7 @@ public void SetVehicleUpgradeStats(float topSpeed, float acceleration, float bra /// /// Processes any custom button press handlers for this menu. /// - private void ProcessButtonPressHandlers() + internal void ProcessButtonPressHandlers() { if (ButtonPressHandlers.Count != 0) { diff --git a/MenuAPI/MenuAPI.csproj b/MenuAPI/MenuAPI.csproj index 0b193fb..a15bae3 100644 --- a/MenuAPI/MenuAPI.csproj +++ b/MenuAPI/MenuAPI.csproj @@ -36,6 +36,15 @@ + + + + diff --git a/MenuAPI/MenuController.cs b/MenuAPI/MenuController.cs index 578220e..2f45f62 100644 --- a/MenuAPI/MenuController.cs +++ b/MenuAPI/MenuController.cs @@ -32,6 +32,8 @@ public class MenuController : IScript // to be often enough that the menu has settled by the time they look at it again. private const long LayoutRefreshIntervalMs = 500; + private const long TextureRefreshIntervalMs = 250; + private const long InstructionalButtonsConfigureIntervalMs = 300; private static float AspectRatio => Native.GetScreenAspectRatio(false); @@ -64,15 +66,51 @@ public class MenuController : IScript // drawn with, so leaving them alone changes nothing. /// The font menu titles are drawn in. See . - public static int DefaultTitleFont { get; set; } = MenuFont.HouseScript; + public static int DefaultTitleFont + { + get => _defaultTitleFont; + set => MenuNui.Change(ref _defaultTitleFont, value); + } + + private static int _defaultTitleFont = MenuFont.HouseScript; /// Where menu titles sit inside the header. - public static Menu.TitleAlignmentOption DefaultTitleAlignment { get; set; } = Menu.TitleAlignmentOption.Center; + public static Menu.TitleAlignmentOption DefaultTitleAlignment + { + get => _defaultTitleAlignment; + set => MenuNui.Change(ref _defaultTitleAlignment, value); + } + + private static Menu.TitleAlignmentOption _defaultTitleAlignment = Menu.TitleAlignmentOption.Center; /// Whether GTA Online's moving header glow is drawn over menu banners. - public static bool DefaultShowHeaderGlare { get; set; } = false; + public static bool DefaultShowHeaderGlare + { + get => _defaultShowHeaderGlare; + set => MenuNui.Change(ref _defaultShowHeaderGlare, value); + } + + private static bool _defaultShowHeaderGlare = false; #endregion + private static MenuRenderMode _renderMode = MenuRenderMode.Native; + + public static MenuRenderMode RenderMode + { + get => _renderMode; + set + { + if (_renderMode == value) + { + return; + } + + _renderMode = value; + + MenuTicks.Reevaluate(); + } + } + private static bool _dontOpenAnyMenu = false; // Backed by a field rather than an auto property because the controller toggle tick is gated on @@ -141,14 +179,14 @@ public static MenuAlignmentOption MenuAlignment if (AspectRatio < 1.888888888888889f) { // alignment can be whatever the resource wants it to be because this aspect ratio is supported. - _alignment = value; + MenuNui.Change(ref _alignment, value); } // right aligned menus are not supported for aspect ratios 17:9 or 21:9. else { // no matter what the new value would've been, the aspect ratio does not support right aligned menus, // so (re)set it to be left aligned. - _alignment = MenuAlignmentOption.Left; + MenuNui.Change(ref _alignment, MenuAlignmentOption.Left); // In case the value was being changed to be right aligned, notify the user properly. if (value == MenuAlignmentOption.Right) @@ -183,12 +221,17 @@ public void Initialize() MenuTicks.Register("Menu.Layout", MenuLayout.Refresh, MenuTickRate.Every(LayoutRefreshIntervalMs), IsAnyMenuOpen, onStarted: MenuLayout.Refresh); - MenuTicks.Register("Menu.Draw", ProcessMenus, MenuTickRate.PerFrame, IsAnyMenuOpen, - onStopped: () => - { - UnloadAssets(); - HeaderGlare.Dispose(); - }); + MenuTicks.Register("Menu.Textures", RefreshTextures, + MenuTickRate.Every(TextureRefreshIntervalMs), IsAnyMenuOpen, + onStopped: UnloadAssets); + + MenuTicks.Register("Menu.Draw", ProcessMenus, MenuTickRate.PerFrame, + () => IsAnyMenuOpen() && RenderMode == MenuRenderMode.Native, + onStopped: HeaderGlare.Dispose); + + MenuTicks.Register("Menu.DrawNui", ProcessMenusNui, MenuTickRate.PerFrame, + () => IsAnyMenuOpen() && RenderMode == MenuRenderMode.Nui, + onStopped: MenuNui.Hide); MenuTicks.Register("Menu.InstructionalButtons", DrawInstructionalButtons, MenuTickRate.PerFrame, IsAnyMenuOpen, onStopped: () => @@ -461,7 +504,6 @@ private static void UnloadAssets() /// public static bool IsAnyMenuOpen() => VisibleMenus.Count != 0; - #region Process Menu Buttons /// /// Process the select & go back/cancel buttons. @@ -1226,6 +1268,63 @@ private static async Task ProcessMenus() await DrawMenus(); } + private static void ProcessMenusNui() + { + MenuLayout.EnsureComputed(); + + if (!CanDraw()) + { + MenuNui.Hide(); + + return; + } + + DisableControls(); + + Menu? menu = GetCurrentMenu(); + + if (menu == null) + { + MenuNui.Hide(); + + return; + } + + if (DontOpenAnyMenu) + { + if (menu.Visible && !menu.IgnoreDontOpenMenus) + { + menu.CloseMenu(); + } + + MenuNui.Hide(); + + return; + } + + if (!menu.Visible) + { + MenuNui.Hide(); + + return; + } + + menu.ProcessButtonPressHandlers(); + + MenuNui.SendChanges(menu); + } + + private static void RefreshTextures() + { + TextureDictionaries.RequestAll(menuTextureAssets); + + MenuNui.RequestPendingTextures(); + } + + // For the one thing that cannot say so itself: something the description is built from that + // MenuAPI does not own, such as a label you resolve yourself. + public static void RefreshNui() => MenuNui.Invalidate(); + /// The game states that stop a menu being drawn, none of which announce a change. private static bool CanDraw() => FrameState.IsScreenFadedIn && diff --git a/MenuAPI/TextureDictionaries.cs b/MenuAPI/TextureDictionaries.cs index a910e58..4931b6e 100644 --- a/MenuAPI/TextureDictionaries.cs +++ b/MenuAPI/TextureDictionaries.cs @@ -1,5 +1,3 @@ -using CitizenFX.FiveM.Client; - namespace MenuAPI; /// @@ -29,6 +27,9 @@ internal static bool IsLoaded(string dict) } Loaded.Add(dict); + + MenuNui.Invalidate(); + return true; } diff --git a/MenuAPI/items/MenuCheckboxItem.cs b/MenuAPI/items/MenuCheckboxItem.cs index 649c091..d15634e 100644 --- a/MenuAPI/items/MenuCheckboxItem.cs +++ b/MenuAPI/items/MenuCheckboxItem.cs @@ -1,6 +1,4 @@ -using CitizenFX.FiveM.Client; - -namespace MenuAPI; +namespace MenuAPI; /// /// Creates a new with all parameters set. @@ -10,8 +8,20 @@ namespace MenuAPI; /// public class MenuCheckboxItem(string text, string? description, bool _checked) : MenuItem(text, description) { - public bool Checked { get; set; } = _checked; - public CheckboxStyle Style { get; set; } = CheckboxStyle.Tick; + public bool Checked + { + get => _isChecked; + set => MenuNui.Change(ref _isChecked, value); + } + + public CheckboxStyle Style + { + get => _style; + set => MenuNui.Change(ref _style, value); + } + + private bool _isChecked = _checked; + private CheckboxStyle _style = CheckboxStyle.Tick; public enum CheckboxStyle { Cross, @@ -41,7 +51,9 @@ private int GetSpriteColour() return Enabled ? 255 : 109; } - private string GetSpriteName(bool selected) + internal const float SpriteSizePx = 45f; + + internal string GetSpriteName(bool selected) { if (Checked) { @@ -100,10 +112,14 @@ private static float GetSpriteX(Menu parent) } } - internal override void Draw(int offset) + internal override void PrepareForDisplay() { RightIcon = Icon.NONE; Label = null; + } + + internal override void Draw(int offset) + { base.Draw(offset); if (ParentMenu is not Menu parent) @@ -122,8 +138,8 @@ internal override void Draw(int offset) float spriteY = (((index - offset) * RowHeight) + 20f + yOffset) / MenuLayout.ScreenHeight; float spriteX = GetSpriteX(parent); - float spriteHeight = 45f / MenuLayout.ScreenHeight; - float spriteWidth = 45f / MenuLayout.ScreenWidth; + float spriteHeight = SpriteSizePx / MenuLayout.ScreenHeight; + float spriteWidth = SpriteSizePx / MenuLayout.ScreenWidth; int color = GetSpriteColour(); Native.DrawSprite("commonmenu", name, spriteX, spriteY, spriteWidth, spriteHeight, 0f, color, color, color, 255, false, false); Native.ResetScriptGfxAlign(); diff --git a/MenuAPI/items/MenuDynamicListItem.cs b/MenuAPI/items/MenuDynamicListItem.cs index bcf5119..9668b2b 100644 --- a/MenuAPI/items/MenuDynamicListItem.cs +++ b/MenuAPI/items/MenuDynamicListItem.cs @@ -1,11 +1,21 @@ -using CitizenFX.FiveM.Client; - -namespace MenuAPI; +namespace MenuAPI; public class MenuDynamicListItem(string text, string? initialValue, MenuDynamicListItem.ChangeItemCallback callback, string? description) : MenuItem(text, description) { - public bool HideArrowsWhenNotSelected { get; set; } = false; - public string? CurrentItem { get; set; } = initialValue; + public bool HideArrowsWhenNotSelected + { + get => _hideArrowsWhenNotSelected; + set => MenuNui.Change(ref _hideArrowsWhenNotSelected, value); + } + + public string? CurrentItem + { + get => _currentItem; + set => MenuNui.Change(ref _currentItem, value); + } + + private bool _hideArrowsWhenNotSelected = false; + private string? _currentItem = initialValue; public delegate string ChangeItemCallback(MenuDynamicListItem item, bool left); @@ -13,7 +23,7 @@ public class MenuDynamicListItem(string text, string? initialValue, MenuDynamicL public MenuDynamicListItem(string text, string? initialValue, ChangeItemCallback callback) : this(text, initialValue, callback, null) { } - internal override void Draw(int indexOffset) + internal override void PrepareForDisplay() { if (HideArrowsWhenNotSelected && !Selected) { @@ -23,7 +33,6 @@ internal override void Draw(int indexOffset) { Label = $"~s~← {CurrentItem ?? "~r~N/A~s~"} ~s~→"; } - base.Draw(indexOffset); } internal override void GoRight() diff --git a/MenuAPI/items/MenuItem.cs b/MenuAPI/items/MenuItem.cs index 3c6d710..822e229 100644 --- a/MenuAPI/items/MenuItem.cs +++ b/MenuAPI/items/MenuItem.cs @@ -1,6 +1,4 @@ -using CitizenFX.FiveM.Client; - -namespace MenuAPI; +namespace MenuAPI; public class MenuItem { @@ -187,22 +185,47 @@ public enum Icon BRAND_ZIRCONIUM, INFO } - public string Text { get; set; } - public string? Label { get; set; } - public Icon LeftIcon { get; set; } - public Icon RightIcon { get; set; } - public bool Enabled { get; set; } = true; + public string Text + { + get => _text; + set => MenuNui.Change(ref _text, value); + } + + public string? Label + { + get => _label; + set => MenuNui.Change(ref _label, value); + } + + public Icon LeftIcon + { + get => _leftIcon; + set => MenuNui.Change(ref _leftIcon, value); + } + + public Icon RightIcon + { + get => _rightIcon; + set => MenuNui.Change(ref _rightIcon, value); + } + + public bool Enabled + { + get => _enabled; + set => MenuNui.Change(ref _enabled, value); + } + public string? Description { - get - { - return _description; - } - set - { - _description = value; - } + get => _description; + set => MenuNui.Change(ref _description, value); } + + private string _text = ""; + private string? _label; + private Icon _leftIcon; + private Icon _rightIcon; + private bool _enabled = true; private string? _description; public int Index => ParentMenu?.IndexOf(this) ?? -1; public bool Selected { get { if (ParentMenu != null) { return ParentMenu.CurrentIndex == Index; } return false; } } @@ -236,7 +259,7 @@ public MenuItem(string text, string? description) /// /// /// - protected string GetSpriteDictionary(Icon icon) + protected internal string GetSpriteDictionary(Icon icon) { return icon switch { @@ -259,7 +282,7 @@ protected string GetSpriteDictionary(Icon icon) /// /// /// - protected string GetSpriteName(Icon icon, bool selected) + protected internal string GetSpriteName(Icon icon, bool selected) { switch (icon) { @@ -455,14 +478,17 @@ protected string GetSpriteName(Icon icon, bool selected) /// /// /// - protected float GetSpriteSize(Icon icon, bool width) + protected float GetSpriteSize(Icon icon, bool width) => + GetSpriteSizePx(icon) / (width ? MenuLayout.ScreenWidth : MenuLayout.ScreenHeight); + + protected internal static float GetSpriteSizePx(Icon icon) { return icon switch { - Icon.CASH or Icon.COKE or Icon.CROWN or Icon.HEROIN or Icon.METH or Icon.WEED or Icon.ADVERSARY or Icon.BASE_JUMPING or Icon.BRIEFCASE or Icon.MISSION_STAR or Icon.DEATHMATCH or Icon.CASTLE or Icon.TROPHY or Icon.RACE_FLAG or Icon.RACE_FLAG_PLANE or Icon.RACE_FLAG_BICYCLE or Icon.RACE_FLAG_PERSON or Icon.RACE_FLAG_CAR or Icon.RACE_FLAG_BOAT_ANCHOR or Icon.ROCKSTAR or Icon.STUNT or Icon.STUNT_PREMIUM or Icon.RACE_FLAG_STUNT_JUMP or Icon.SHIELD or Icon.TEAM_DEATHMATCH or Icon.VEHICLE_DEATHMATCH or Icon.AUDIO_MUTE or Icon.AUDIO_INACTIVE or Icon.AUDIO_VOL1 or Icon.AUDIO_VOL2 or Icon.AUDIO_VOL3 or Icon.BRAND_ALBANY or Icon.BRAND_ANNIS or Icon.BRAND_BANSHEE or Icon.BRAND_BENEFACTOR or Icon.BRAND_BF or Icon.BRAND_BOLLOKAN or Icon.BRAND_BRAVADO or Icon.BRAND_BRUTE or Icon.BRAND_BUCKINGHAM or Icon.BRAND_CANIS or Icon.BRAND_CHARIOT or Icon.BRAND_CHEVAL or Icon.BRAND_CLASSIQUE or Icon.BRAND_COIL or Icon.BRAND_DECLASSE or Icon.BRAND_DEWBAUCHEE or Icon.BRAND_DILETTANTE or Icon.BRAND_DINKA or Icon.BRAND_DUNDREARY or Icon.BRAND_EMPORER or Icon.BRAND_ENUS or Icon.BRAND_FATHOM or Icon.BRAND_GALIVANTER or Icon.BRAND_GROTTI or Icon.BRAND_HIJAK or Icon.BRAND_HVY or Icon.BRAND_IMPONTE or Icon.BRAND_INVETERO or Icon.BRAND_JACKSHEEPE or Icon.BRAND_JOBUILT or Icon.BRAND_KARIN or Icon.BRAND_LAMPADATI or Icon.BRAND_MAIBATSU or Icon.BRAND_MAMMOTH or Icon.BRAND_MTL or Icon.BRAND_NAGASAKI or Icon.BRAND_OBEY or Icon.BRAND_OCELOT or Icon.BRAND_OVERFLOD or Icon.BRAND_PED or Icon.BRAND_PEGASSI or Icon.BRAND_PFISTER or Icon.BRAND_PRINCIPE or Icon.BRAND_PROGEN or Icon.BRAND_SCHYSTER or Icon.BRAND_SHITZU or Icon.BRAND_SPEEDOPHILE or Icon.BRAND_STANLEY or Icon.BRAND_TRUFFADE or Icon.BRAND_UBERMACHT or Icon.BRAND_VAPID or Icon.BRAND_VULCAR or Icon.BRAND_WEENY or Icon.BRAND_WESTERN or Icon.BRAND_WESTERNMOTORCYCLE or Icon.BRAND_WILLARD or Icon.BRAND_ZIRCONIUM or Icon.BRAND_GROTTI2 or Icon.BRAND_LCC or Icon.BRAND_PROGEN2 or Icon.BRAND_RUNE or Icon.COUNTRY_USA or Icon.COUNTRY_UK or Icon.COUNTRY_SWEDEN or Icon.COUNTRY_KOREA or Icon.COUNTRY_JAPAN or Icon.COUNTRY_ITALY or Icon.COUNTRY_GERMANY or Icon.COUNTRY_FRANCE => 30f / (width ? MenuLayout.ScreenWidth : MenuLayout.ScreenHeight), - Icon.STAR or Icon.LOCK_ARENA => 52f / (width ? MenuLayout.ScreenWidth : MenuLayout.ScreenHeight), - Icon.MEDAL_SILVER or Icon.MP_AMMO_PICKUP or Icon.MP_AMMO or Icon.MP_CASH or Icon.MP_RP or Icon.GLOBE_WHITE or Icon.GLOBE_BLUE or Icon.GLOBE_GREEN or Icon.GLOBE_ORANGE or Icon.GLOBE_RED or Icon.GLOBE_YELLOW or Icon.INV_ARM_WRESTLING or Icon.INV_BASEJUMP or Icon.INV_MISSION or Icon.INV_DARTS or Icon.INV_DEATHMATCH or Icon.INV_DRUG or Icon.INV_CASTLE or Icon.INV_GOLF or Icon.INV_BIKE or Icon.INV_BOAT or Icon.INV_ANCHOR or Icon.INV_CAR or Icon.INV_DOLLAR or Icon.INV_COKE or Icon.INV_KEY or Icon.INV_DATA or Icon.INV_HELI or Icon.INV_HEORIN or Icon.INV_KEYCARD or Icon.INV_METH or Icon.INV_BRIEFCASE or Icon.INV_LINK or Icon.INV_PERSON or Icon.INV_PLANE or Icon.INV_PLANE2 or Icon.INV_QUESTIONMARK or Icon.INV_REMOTE or Icon.INV_SAFE or Icon.INV_STEER_WHEEL or Icon.INV_WEAPON or Icon.INV_WEED or Icon.INV_RACE_FLAG_PLANE or Icon.INV_RACE_FLAG_BICYCLE or Icon.INV_RACE_FLAG_BOAT_ANCHOR or Icon.INV_RACE_FLAG_PERSON or Icon.INV_RACE_FLAG_CAR or Icon.INV_RACE_FLAG_HELMET or Icon.INV_SHOOTING_RANGE or Icon.INV_SURVIVAL or Icon.INV_TEAM_DEATHMATCH or Icon.INV_TENNIS or Icon.INV_VEHICLE_DEATHMATCH => 22f / (width ? MenuLayout.ScreenWidth : MenuLayout.ScreenHeight), - _ => 38f / (width ? MenuLayout.ScreenWidth : MenuLayout.ScreenHeight), + Icon.CASH or Icon.COKE or Icon.CROWN or Icon.HEROIN or Icon.METH or Icon.WEED or Icon.ADVERSARY or Icon.BASE_JUMPING or Icon.BRIEFCASE or Icon.MISSION_STAR or Icon.DEATHMATCH or Icon.CASTLE or Icon.TROPHY or Icon.RACE_FLAG or Icon.RACE_FLAG_PLANE or Icon.RACE_FLAG_BICYCLE or Icon.RACE_FLAG_PERSON or Icon.RACE_FLAG_CAR or Icon.RACE_FLAG_BOAT_ANCHOR or Icon.ROCKSTAR or Icon.STUNT or Icon.STUNT_PREMIUM or Icon.RACE_FLAG_STUNT_JUMP or Icon.SHIELD or Icon.TEAM_DEATHMATCH or Icon.VEHICLE_DEATHMATCH or Icon.AUDIO_MUTE or Icon.AUDIO_INACTIVE or Icon.AUDIO_VOL1 or Icon.AUDIO_VOL2 or Icon.AUDIO_VOL3 or Icon.BRAND_ALBANY or Icon.BRAND_ANNIS or Icon.BRAND_BANSHEE or Icon.BRAND_BENEFACTOR or Icon.BRAND_BF or Icon.BRAND_BOLLOKAN or Icon.BRAND_BRAVADO or Icon.BRAND_BRUTE or Icon.BRAND_BUCKINGHAM or Icon.BRAND_CANIS or Icon.BRAND_CHARIOT or Icon.BRAND_CHEVAL or Icon.BRAND_CLASSIQUE or Icon.BRAND_COIL or Icon.BRAND_DECLASSE or Icon.BRAND_DEWBAUCHEE or Icon.BRAND_DILETTANTE or Icon.BRAND_DINKA or Icon.BRAND_DUNDREARY or Icon.BRAND_EMPORER or Icon.BRAND_ENUS or Icon.BRAND_FATHOM or Icon.BRAND_GALIVANTER or Icon.BRAND_GROTTI or Icon.BRAND_HIJAK or Icon.BRAND_HVY or Icon.BRAND_IMPONTE or Icon.BRAND_INVETERO or Icon.BRAND_JACKSHEEPE or Icon.BRAND_JOBUILT or Icon.BRAND_KARIN or Icon.BRAND_LAMPADATI or Icon.BRAND_MAIBATSU or Icon.BRAND_MAMMOTH or Icon.BRAND_MTL or Icon.BRAND_NAGASAKI or Icon.BRAND_OBEY or Icon.BRAND_OCELOT or Icon.BRAND_OVERFLOD or Icon.BRAND_PED or Icon.BRAND_PEGASSI or Icon.BRAND_PFISTER or Icon.BRAND_PRINCIPE or Icon.BRAND_PROGEN or Icon.BRAND_SCHYSTER or Icon.BRAND_SHITZU or Icon.BRAND_SPEEDOPHILE or Icon.BRAND_STANLEY or Icon.BRAND_TRUFFADE or Icon.BRAND_UBERMACHT or Icon.BRAND_VAPID or Icon.BRAND_VULCAR or Icon.BRAND_WEENY or Icon.BRAND_WESTERN or Icon.BRAND_WESTERNMOTORCYCLE or Icon.BRAND_WILLARD or Icon.BRAND_ZIRCONIUM or Icon.BRAND_GROTTI2 or Icon.BRAND_LCC or Icon.BRAND_PROGEN2 or Icon.BRAND_RUNE or Icon.COUNTRY_USA or Icon.COUNTRY_UK or Icon.COUNTRY_SWEDEN or Icon.COUNTRY_KOREA or Icon.COUNTRY_JAPAN or Icon.COUNTRY_ITALY or Icon.COUNTRY_GERMANY or Icon.COUNTRY_FRANCE => 30f, + Icon.STAR or Icon.LOCK_ARENA => 52f, + Icon.MEDAL_SILVER or Icon.MP_AMMO_PICKUP or Icon.MP_AMMO or Icon.MP_CASH or Icon.MP_RP or Icon.GLOBE_WHITE or Icon.GLOBE_BLUE or Icon.GLOBE_GREEN or Icon.GLOBE_ORANGE or Icon.GLOBE_RED or Icon.GLOBE_YELLOW or Icon.INV_ARM_WRESTLING or Icon.INV_BASEJUMP or Icon.INV_MISSION or Icon.INV_DARTS or Icon.INV_DEATHMATCH or Icon.INV_DRUG or Icon.INV_CASTLE or Icon.INV_GOLF or Icon.INV_BIKE or Icon.INV_BOAT or Icon.INV_ANCHOR or Icon.INV_CAR or Icon.INV_DOLLAR or Icon.INV_COKE or Icon.INV_KEY or Icon.INV_DATA or Icon.INV_HELI or Icon.INV_HEORIN or Icon.INV_KEYCARD or Icon.INV_METH or Icon.INV_BRIEFCASE or Icon.INV_LINK or Icon.INV_PERSON or Icon.INV_PLANE or Icon.INV_PLANE2 or Icon.INV_QUESTIONMARK or Icon.INV_REMOTE or Icon.INV_SAFE or Icon.INV_STEER_WHEEL or Icon.INV_WEAPON or Icon.INV_WEED or Icon.INV_RACE_FLAG_PLANE or Icon.INV_RACE_FLAG_BICYCLE or Icon.INV_RACE_FLAG_BOAT_ANCHOR or Icon.INV_RACE_FLAG_PERSON or Icon.INV_RACE_FLAG_CAR or Icon.INV_RACE_FLAG_HELMET or Icon.INV_SHOOTING_RANGE or Icon.INV_SURVIVAL or Icon.INV_TEAM_DEATHMATCH or Icon.INV_TENNIS or Icon.INV_VEHICLE_DEATHMATCH => 22f, + _ => 38f, }; } @@ -472,7 +498,7 @@ protected float GetSpriteSize(Icon icon, bool width) /// /// /// - protected (int R, int G, int B) GetSpriteColour(Icon icon, bool selected) + protected internal (int R, int G, int B) GetSpriteColour(Icon icon, bool selected) { return icon switch { @@ -553,6 +579,8 @@ internal virtual void Select() ParentMenu?.ItemSelectedEvent(this, Index); } + internal virtual void PrepareForDisplay() { } + /// How far down the menu the first row starts. Identical for every row in a menu. internal static float RowYOffset(Menu parent) => parent.MenuItemsYOffset + 1f - (RowHeight * Math.Clamp(parent.Size, 0, parent.MaxItemsOnScreen)); @@ -562,6 +590,8 @@ internal static float RowYOffset(Menu parent) => /// internal virtual void Draw(int indexOffset) { + PrepareForDisplay(); + if (ParentMenu is not Menu parent) { return; diff --git a/MenuAPI/items/MenuItemList.cs b/MenuAPI/items/MenuItemList.cs new file mode 100644 index 0000000..bc0c1a7 --- /dev/null +++ b/MenuAPI/items/MenuItemList.cs @@ -0,0 +1,185 @@ +using System.Collections; + +namespace MenuAPI; + +public sealed class MenuItemList : IList +{ + private readonly List _items; + + public MenuItemList() => _items = new List(); + + public MenuItemList(IEnumerable items) => _items = new List(items); + + public static implicit operator MenuItemList(List items) => new(items); + + public int Count => _items.Count; + public bool IsReadOnly => false; + + public string this[int index] + { + get => _items[index]; + set + { + if (_items[index] == value) + { + return; + } + + _items[index] = value; + + MenuNui.Invalidate(); + } + } + + public void Add(string item) + { + _items.Add(item); + + MenuNui.Invalidate(); + } + + public void AddRange(IEnumerable items) + { + _items.AddRange(items); + + MenuNui.Invalidate(); + } + + public void Insert(int index, string item) + { + _items.Insert(index, item); + + MenuNui.Invalidate(); + } + + public bool Remove(string item) + { + if (!_items.Remove(item)) + { + return false; + } + + MenuNui.Invalidate(); + + return true; + } + + public void RemoveAt(int index) + { + _items.RemoveAt(index); + + MenuNui.Invalidate(); + } + + public void Clear() + { + if (_items.Count == 0) + { + return; + } + + _items.Clear(); + + MenuNui.Invalidate(); + } + + public void Sort() => Sort(null); + + public void Sort(Comparison? compare) + { + if (compare is null) + { + _items.Sort(); + } + else + { + _items.Sort(compare); + } + + MenuNui.Invalidate(); + } + + public void InsertRange(int index, IEnumerable items) + { + var before = _items.Count; + + _items.InsertRange(index, items); + + if (_items.Count != before) + { + MenuNui.Invalidate(); + } + } + + public int RemoveAll(Predicate match) + { + var removed = _items.RemoveAll(match); + + if (removed > 0) + { + MenuNui.Invalidate(); + } + + return removed; + } + + public void RemoveRange(int index, int count) + { + if (count <= 0) + { + return; + } + + _items.RemoveRange(index, count); + + MenuNui.Invalidate(); + } + + public void Reverse() + { + if (_items.Count < 2) + { + return; + } + + _items.Reverse(); + + MenuNui.Invalidate(); + } + + public bool Contains(string item) => _items.Contains(item); + + public int IndexOf(string item) => _items.IndexOf(item); + + public int LastIndexOf(string item) => _items.LastIndexOf(item); + + public bool Exists(Predicate match) => _items.Exists(match); + + public string? Find(Predicate match) => _items.Find(match); + + public string? FindLast(Predicate match) => _items.FindLast(match); + + public int FindIndex(Predicate match) => _items.FindIndex(match); + + public int FindLastIndex(Predicate match) => _items.FindLastIndex(match); + + public List FindAll(Predicate match) => _items.FindAll(match); + + public bool TrueForAll(Predicate match) => _items.TrueForAll(match); + + public List ConvertAll(Converter converter) => _items.ConvertAll(converter); + + public List GetRange(int index, int count) => _items.GetRange(index, count); + + public void ForEach(Action action) => _items.ForEach(action); + + public void CopyTo(string[] array, int arrayIndex) => _items.CopyTo(array, arrayIndex); + + public string[] ToArray() => _items.ToArray(); + + public List ToList() => new(_items); + + public IEnumerator GetEnumerator() => _items.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => _items.GetEnumerator(); +} diff --git a/MenuAPI/items/MenuListItem.cs b/MenuAPI/items/MenuListItem.cs index 4e244bd..fef707f 100644 --- a/MenuAPI/items/MenuListItem.cs +++ b/MenuAPI/items/MenuListItem.cs @@ -1,15 +1,50 @@ -using CitizenFX.FiveM.Client; - -namespace MenuAPI; +namespace MenuAPI; public class MenuListItem(string text, List items, int index, string? description) : MenuItem(text, description) { - public int ListIndex { get; set; } = index; - public List ListItems { get; set; } = items; - public bool HideArrowsWhenNotSelected { get; set; } = false; - public bool ShowOpacityPanel { get; set; } = false; - public bool ShowColorPanel { get; set; } = false; - public ColorPanelType ColorPanelColorType = ColorPanelType.Hair; + public int ListIndex + { + get => _listIndex; + set => MenuNui.Change(ref _listIndex, value); + } + + public MenuItemList ListItems + { + get => _listItems; + set => MenuNui.Change(ref _listItems, value ?? new MenuItemList()); + } + + public bool HideArrowsWhenNotSelected + { + get => _hideArrowsWhenNotSelected; + set => MenuNui.Change(ref _hideArrowsWhenNotSelected, value); + } + + public bool ShowOpacityPanel + { + get => _showOpacityPanel; + set => MenuNui.Change(ref _showOpacityPanel, value); + } + + public bool ShowColorPanel + { + get => _showColorPanel; + set => MenuNui.Change(ref _showColorPanel, value); + } + + public ColorPanelType ColorPanelColorType + { + get => _colorPanelColorType; + set => MenuNui.Change(ref _colorPanelColorType, value); + } + + private int _listIndex = index; + private MenuItemList _listItems = new(items); + private bool _hideArrowsWhenNotSelected = false; + private bool _showOpacityPanel = false; + private bool _showColorPanel = false; + private ColorPanelType _colorPanelColorType = ColorPanelType.Hair; + public enum ColorPanelType { Hair, @@ -28,7 +63,7 @@ public enum ColorPanelType public MenuListItem(string text, List items, int index) : this(text, items, index, null) { } - internal override void Draw(int indexOffset) + internal override void PrepareForDisplay() { if (ItemsCount < 1) { @@ -54,8 +89,6 @@ internal override void Draw(int indexOffset) { Label = $"~s~← {GetCurrentSelection() ?? "~r~N/A~s~"} ~s~→"; } - - base.Draw(indexOffset); } internal override void GoRight() diff --git a/MenuAPI/items/MenuSliderItem.cs b/MenuAPI/items/MenuSliderItem.cs index d85768e..532daaf 100644 --- a/MenuAPI/items/MenuSliderItem.cs +++ b/MenuAPI/items/MenuSliderItem.cs @@ -1,19 +1,52 @@ -using CitizenFX.FiveM.Client; - -namespace MenuAPI; +namespace MenuAPI; public class MenuSliderItem(string name, string? description, int min, int max, int startPosition, bool showDivider) : MenuItem(name, description) { public int Min { get; private set; } = min; public int Max { get; private set; } = max; - public bool ShowDivider { get; set; } = showDivider; - public int Position { get; set; } = startPosition; - public Icon SliderLeftIcon { get; set; } = Icon.NONE; - public Icon SliderRightIcon { get; set; } = Icon.NONE; + public bool ShowDivider + { + get => _showDivider; + set => MenuNui.Change(ref _showDivider, value); + } + + public int Position + { + get => _position; + set => MenuNui.Change(ref _position, value); + } + + public Icon SliderLeftIcon + { + get => _sliderLeftIcon; + set => MenuNui.Change(ref _sliderLeftIcon, value); + } + + public Icon SliderRightIcon + { + get => _sliderRightIcon; + set => MenuNui.Change(ref _sliderRightIcon, value); + } + + public System.Drawing.Color BackgroundColor + { + get => _backgroundColor; + set => MenuNui.Change(ref _backgroundColor, value); + } + + public System.Drawing.Color BarColor + { + get => _barColor; + set => MenuNui.Change(ref _barColor, value); + } - public System.Drawing.Color BackgroundColor { get; set; } = System.Drawing.Color.FromArgb(255, 24, 93, 151); - public System.Drawing.Color BarColor { get; set; } = System.Drawing.Color.FromArgb(255, 53, 165, 223); + private bool _showDivider = showDivider; + private int _position = startPosition; + private Icon _sliderLeftIcon = Icon.NONE; + private Icon _sliderRightIcon = Icon.NONE; + private System.Drawing.Color _backgroundColor = System.Drawing.Color.FromArgb(255, 24, 93, 151); + private System.Drawing.Color _barColor = System.Drawing.Color.FromArgb(255, 53, 165, 223); public MenuSliderItem(string name, int min, int max, int startPosition) : this(name, min, max, startPosition, false) { } public MenuSliderItem(string name, int min, int max, int startPosition, bool showDivider) : this(name, null, min, max, startPosition, showDivider) { } @@ -33,11 +66,19 @@ private static float Map(float val, float in_min, float in_max, float out_min, f return (val - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; } - internal override void Draw(int indexOffset) + internal override void PrepareForDisplay() { RightIcon = SliderRightIcon; Label = null; + if (Position > Max || Position < Min) + { + Position = (Max - Min) / 2; + } + } + + internal override void Draw(int indexOffset) + { base.Draw(indexOffset); if (ParentMenu is not Menu parent) @@ -45,11 +86,6 @@ internal override void Draw(int indexOffset) return; } - if (Position > Max || Position < Min) - { - Position = (Max - Min) / 2; - } - int index = Index; bool selected = parent.CurrentIndex == index; diff --git a/MenuAPI/items/SeparatorMenuItem.cs b/MenuAPI/items/SeparatorMenuItem.cs index 9f590f8..6fa6c81 100644 --- a/MenuAPI/items/SeparatorMenuItem.cs +++ b/MenuAPI/items/SeparatorMenuItem.cs @@ -1,5 +1,3 @@ -using CitizenFX.FiveM.Client; - namespace MenuAPI; /// @@ -17,7 +15,13 @@ public class SeparatorMenuItem : MenuItem private const int TextOnHighlight = 0; /// Draws the text as ↓ Text ↓. On by default. - public bool ShowArrows { get; set; } = true; + public bool ShowArrows + { + get => _showArrows; + set => MenuNui.Change(ref _showArrows, value); + } + + private bool _showArrows = true; /// /// Creates a with the down arrows around its text. diff --git a/MenuAPI/nui/MenuNui.cs b/MenuAPI/nui/MenuNui.cs new file mode 100644 index 0000000..063db0e --- /dev/null +++ b/MenuAPI/nui/MenuNui.cs @@ -0,0 +1,512 @@ +using System.Globalization; + +namespace MenuAPI; + +internal static class MenuNui +{ + private const string MessageType = "menuapi"; + + private const string HideMessage = "{\"type\":\"menuapi\",\"visible\":false}"; + + private const float RotationTolerance = 0.5f; + private const int FreemodeHudColour = 116; + private const int PauseBackgroundHudColour = 117; + private const int WhiteHudColour = 1; + private const int PaletteSize = 64; + + private static string? _sent; + private static readonly NuiJson _snapshot = new(); + private static readonly NuiJson _glare = new(); + private static bool _dirty = true; + private static readonly List _pendingTextures = new(); + private static float _heading = float.NaN; + private static string? _freemode; + private static string? _theme; + private static string? _accent; + private static MenuListItem.ColorPanelType? _palette; + + internal static void Invalidate() => _dirty = true; + + internal static void Change(ref T field, T value) + { + if (EqualityComparer.Default.Equals(field, value)) + { + return; + } + + field = value; + + _dirty = true; + } + + internal static void RequestPendingTextures() + { + for (var i = _pendingTextures.Count - 1; i >= 0; i--) + { + if (TextureDictionaries.Request(_pendingTextures[i])) + { + _pendingTextures.RemoveAt(i); + } + } + } + + internal static void SendChanges(Menu menu) + { + if (_dirty) + { + SendSnapshot(menu); + } + + if (menu.ResolvedShowHeaderGlare) + { + SendHeading(); + } + } + + internal static void SendSnapshot(Menu menu) + { + _dirty = false; + + Build(menu); + + if (_snapshot.Matches(_sent)) + { + return; + } + + _sent = _snapshot.ToString(); + + Native.SendNuiMessage(_sent); + } + + internal static void Hide() + { + if (_sent == HideMessage) + { + return; + } + + _sent = HideMessage; + + _dirty = true; + + _heading = float.NaN; + + _palette = null; + + _pendingTextures.Clear(); + + Native.SendNuiMessage(HideMessage); + } + + private static void SendHeading() + { + var heading = Wrap(Native.GetFinalRenderedCamRot(2).Z); + + if (!float.IsNaN(_heading) && Math.Abs(_heading - heading) <= RotationTolerance) + { + return; + } + + _heading = heading; + + var json = _glare + .Reset() + .Object() + .Prop("type", "menuapi:glare") + .Prop("heading", heading) + .EndObject() + .ToString(); + + Native.SendNuiMessage(json); + } + + private static void Build(Menu menu) + { + _pendingTextures.Clear(); + + var json = _snapshot + .Reset() + .Object() + .Prop("type", MessageType) + .Prop("visible", true) + .Prop("align", menu.LeftAligned ? "left" : "right"); + + WriteOrigin(json, menu); + + json.Prop("panelBackground", PanelBackground()) + .Prop("panelAccent", PanelAccent()); + + json.Object("text") + .Prop("size", NuiTuning.TextSize) + .Prop("brightness", NuiTuning.TextBrightness) + .Prop("weight", NuiTuning.WeightName) + .EndObject(); + + WriteHeader(json, menu); + WriteSubtitle(json, menu); + WriteRows(json, menu); + + json.Prop("overflow", menu.Size > menu.MaxItemsOnScreen); + + json.Prop("description", menu.GetCurrentMenuItem()?.Description); + + WritePanel(json, menu); + WriteStats(json, menu); + + json.EndObject(); + } + + private static void WriteOrigin(NuiJson json, Menu menu) + { + var inset = (1f - MenuLayout.SafeZone) / 2f; + + var x = menu.LeftAligned + ? inset + : 1f - inset - (Menu.Width / MenuLayout.ScreenWidth); + + json.Object("origin") + .Prop("x", x) + .Prop("y", inset) + .EndObject(); + } + + private static void WriteHeader(NuiJson json, Menu menu) + { + if (string.IsNullOrEmpty(menu.MenuTitle)) + { + json.Null("header"); + + return; + } + + json.Object("header") + .Prop("title", menu.MenuTitle) + .Prop("font", menu.ResolvedTitleFont) + .Prop("titleAlign", menu.ResolvedTitleAlignment switch + { + Menu.TitleAlignmentOption.Left => "left", + Menu.TitleAlignmentOption.Right => "right", + _ => "center", + }) + .Prop("glare", menu.ResolvedShowHeaderGlare); + + var custom = !string.IsNullOrEmpty(menu.HeaderTexture.Key) && !string.IsNullOrEmpty(menu.HeaderTexture.Value); + var dictionary = custom ? menu.HeaderTexture.Key : MenuController._texture_dict; + + if (TextureReady(dictionary)) + { + json.Object("texture") + .Prop("dict", dictionary) + .Prop("name", custom ? menu.HeaderTexture.Value : MenuController._header_texture) + .EndObject(); + } + else + { + json.Null("texture"); + } + + json.EndObject(); + } + + private static void WriteSubtitle(NuiJson json, Menu menu) + { + var counter = !string.IsNullOrEmpty(menu.CounterPreText) || menu.MaxItemsOnScreen < menu.Size + ? (menu.CounterPreText ?? "") + (menu.CurrentIndex + 1) + " / " + menu.Size + : null; + + json.Object("subtitle") + .Prop("text", menu.MenuSubtitle) + .Prop("counter", counter) + .Prop("colour", Freemode()) + .Prop("freemode", !(menu.MenuSubtitle ?? "").Contains('~') + && !(menu.CounterPreText ?? "").Contains('~') + && !string.IsNullOrEmpty(menu.MenuTitle)) + .EndObject(); + } + + private static void WriteRows(NuiJson json, Menu menu) + { + json.Array("rows"); + + var visible = menu.VisibleMenuItems; + + for (var i = 0; i < visible.Count; i++) + { + var item = visible[i]; + + item.PrepareForDisplay(); + + var selected = menu.CurrentIndex == menu.ViewIndexOffset + i; + + json.Object() + .Prop("kind", item switch + { + MenuCheckboxItem => "checkbox", + MenuSliderItem => "slider", + SeparatorMenuItem => "separator", + _ => "item", + }) + .Prop("text", item.Text) + .Prop("label", item.Label) + .Prop("enabled", item.Enabled) + .Prop("selected", selected); + + WriteIcon(json, "leftIcon", item, item.LeftIcon, selected); + WriteIcon(json, "rightIcon", item, item.RightIcon, selected); + + switch (item) + { + case MenuCheckboxItem checkbox when TextureReady(MenuController._texture_dict): + json.Object("checkbox") + .Prop("dict", MenuController._texture_dict) + .Prop("name", checkbox.GetSpriteName(selected)) + .Prop("size", MenuCheckboxItem.SpriteSizePx) + .Prop("shade", checkbox.Enabled ? 255 : 109) + .EndObject(); + + break; + + case MenuSliderItem slider: + json.Object("slider") + .Prop("min", slider.Min) + .Prop("max", slider.Max) + .Prop("position", slider.Position) + .Prop("divider", slider.ShowDivider) + .Prop("background", Hex(slider.BackgroundColor)) + .Prop("bar", Hex(slider.BarColor)); + + WriteIcon(json, "sliderLeftIcon", slider, slider.SliderLeftIcon, selected); + + json.EndObject(); + + break; + + case SeparatorMenuItem separator: + json.Prop("arrows", separator.ShowArrows); + + break; + } + + json.EndObject(); + } + + json.EndArray(); + } + + private static bool TextureReady(string dict) + { + if (TextureDictionaries.Request(dict)) + { + return true; + } + + if (!_pendingTextures.Contains(dict)) + { + _pendingTextures.Add(dict); + } + + return false; + } + + private static void WriteIcon(NuiJson json, string name, MenuItem item, MenuItem.Icon icon, bool selected) + { + if (icon == MenuItem.Icon.NONE) + { + json.Null(name); + + return; + } + + var dictionary = item.GetSpriteDictionary(icon); + + if (!TextureReady(dictionary)) + { + json.Null(name); + + return; + } + + var colour = item.GetSpriteColour(icon, selected); + + json.Object(name) + .Prop("dict", dictionary) + .Prop("name", item.GetSpriteName(icon, selected)) + .Prop("size", MenuItem.GetSpriteSizePx(icon)) + .Prop("r", colour.R) + .Prop("g", colour.G) + .Prop("b", colour.B) + .EndObject(); + } + + private static void WritePanel(NuiJson json, Menu menu) + { + if (menu.GetCurrentMenuItem() is not MenuListItem item + || (!item.ShowColorPanel && !item.ShowOpacityPanel)) + { + json.Null("panel"); + + return; + } + + json.Object("panel") + .Prop("colours", item.ShowColorPanel) + .Prop("index", item.ListIndex) + .Prop("title", "Opacity"); + + if (item.ShowOpacityPanel) + { + json.Prop("opacity", item.ResolvedOpacityPercent); + } + else + { + json.Null("opacity"); + } + + json.Prop("name", item.ShowColorPanel ? ColourName(item.ListIndex + 1, item.ItemsCount) : null); + + json.EndObject(); + + if (item.ShowColorPanel) + { + SendPalette(item.ColorPanelColorType); + } + } + + private static void SendPalette(MenuListItem.ColorPanelType type) + { + if (_palette == type) + { + return; + } + + _palette = type; + + var json = new NuiJson() + .Object() + .Prop("type", "menuapi:palette") + .Array("colours"); + + for (var i = 0; i < PaletteSize; i++) + { + int r; + int g; + int b; + + if (type == MenuListItem.ColorPanelType.Hair) + { + Native.GetHairRgbColor(i, out r, out g, out b); + } + else + { + Native.GetMakeupRgbColor(i, out r, out g, out b); + } + + json.Array().Value(r).Value(g).Value(b).EndArray(); + } + + Native.SendNuiMessage(json.EndArray().EndObject().ToString()); + } + + private static string ColourName(int position, int count) + { + var template = Native.GetLabelText("FACE_COLOUR"); + + if (string.IsNullOrEmpty(template) || template == "NULL") + { + return position + " / " + count; + } + + return Substitute(Substitute(template, position), count); + } + + private static string Substitute(string text, int value) + { + var at = text.IndexOf("~1~", StringComparison.Ordinal); + + return at < 0 ? text : text[..at] + value + text[(at + 3)..]; + } + + private static void WriteStats(NuiJson json, Menu menu) + { + if (menu.GetCurrentMenuItem() is MenuListItem { ShowColorPanel: true } or MenuListItem { ShowOpacityPanel: true }) + { + json.Null("stats"); + + return; + } + + if (!menu.ShowWeaponStatsPanel && !menu.ShowVehicleStatsPanel) + { + json.Null("stats"); + + return; + } + + var weapon = menu.ShowWeaponStatsPanel; + var values = weapon ? menu.WeaponStats : menu.VehicleStats; + var upgrades = weapon ? menu.WeaponComponentStats : menu.VehicleUpgradeStats; + + json.Array("stats"); + + for (var i = 0; i < 4; i++) + { + json.Object() + .Prop("label", Native.GetLabelText(menu.StatLabelKey(i))) + .Prop("value", values[i]) + .Prop("upgrade", upgrades[i]) + .EndObject(); + } + + json.EndArray(); + } + + private static string Freemode() + { + if (_freemode is null) + { + Native.GetHudColour(FreemodeHudColour, out var r, out var g, out var b, out _); + + _freemode = $"rgb({r} {g} {b})"; + } + + return _freemode; + } + + private static string PanelBackground() + { + if (_theme is null) + { + Native.GetHudColour(PauseBackgroundHudColour, out var r, out var g, out var b, out var a); + + var alpha = (a / 255f).ToString("0.###", CultureInfo.InvariantCulture); + + _theme = "rgb(" + r + " " + g + " " + b + " / " + alpha + ")"; + } + + return _theme; + } + + private static string PanelAccent() + { + if (_accent is null) + { + Native.GetHudColour(WhiteHudColour, out var r, out var g, out var b, out _); + + // Comma separated: it goes into rgba(var(--accent), 0.3). + _accent = r + ", " + g + ", " + b; + } + + return _accent; + } + + private static string Hex(System.Drawing.Color colour) => + "#" + colour.R.ToString("x2") + colour.G.ToString("x2") + colour.B.ToString("x2"); + + private static float Wrap(float degrees) + { + var wrapped = degrees % 360f; + + return wrapped < 0f ? wrapped + 360f : wrapped; + } +} diff --git a/MenuAPI/nui/MenuRenderMode.cs b/MenuAPI/nui/MenuRenderMode.cs new file mode 100644 index 0000000..180bf11 --- /dev/null +++ b/MenuAPI/nui/MenuRenderMode.cs @@ -0,0 +1,7 @@ +namespace MenuAPI; + +public enum MenuRenderMode +{ + Native, + Nui, +} diff --git a/MenuAPI/nui/NuiJson.cs b/MenuAPI/nui/NuiJson.cs new file mode 100644 index 0000000..f4d38b8 --- /dev/null +++ b/MenuAPI/nui/NuiJson.cs @@ -0,0 +1,230 @@ +using System.Globalization; +using System.Text; + +namespace MenuAPI; + +internal sealed class NuiJson +{ + private readonly StringBuilder _builder = new(); + private bool _needsComma; + + internal NuiJson Reset() + { + _builder.Clear(); + _needsComma = false; + + return this; + } + + internal bool Matches(string? value) => value is not null && _builder.Equals(value.AsSpan()); + + internal NuiJson Object() + { + Separate(); + _builder.Append('{'); + _needsComma = false; + + return this; + } + + internal NuiJson EndObject() + { + _builder.Append('}'); + _needsComma = true; + + return this; + } + + internal NuiJson Array() + { + Separate(); + _builder.Append('['); + _needsComma = false; + + return this; + } + + internal NuiJson EndArray() + { + _builder.Append(']'); + _needsComma = true; + + return this; + } + + internal NuiJson Object(string name) + { + Key(name); + _builder.Append('{'); + _needsComma = false; + + return this; + } + + internal NuiJson Array(string name) + { + Key(name); + _builder.Append('['); + _needsComma = false; + + return this; + } + + internal NuiJson Prop(string name, string? value) + { + Key(name); + + if (value is null) + { + _builder.Append("null"); + } + else + { + Escape(value); + } + + _needsComma = true; + + return this; + } + + internal NuiJson Prop(string name, bool value) + { + Key(name); + _builder.Append(value ? "true" : "false"); + _needsComma = true; + + return this; + } + + internal NuiJson Prop(string name, int value) + { + Key(name); + _builder.Append(value); + _needsComma = true; + + return this; + } + + internal NuiJson Prop(string name, float value) + { + Key(name); + _builder.Append(value.ToString("0.####", CultureInfo.InvariantCulture)); + _needsComma = true; + + return this; + } + + internal NuiJson Null(string name) + { + Key(name); + _builder.Append("null"); + _needsComma = true; + + return this; + } + + internal NuiJson Value(int value) + { + Separate(); + _builder.Append(value); + _needsComma = true; + + return this; + } + + internal NuiJson Value(string value) + { + Separate(); + Escape(value); + _needsComma = true; + + return this; + } + + public override string ToString() => _builder.ToString(); + + private void Key(string name) + { + Separate(); + Escape(name); + _builder.Append(':'); + } + + private void Separate() + { + if (_needsComma) + { + _builder.Append(','); + } + } + + private void Escape(string value) + { + _builder.Append('"'); + + if (!NeedsEscaping(value)) + { + _builder.Append(value).Append('"'); + + return; + } + + for (var i = 0; i < value.Length; i++) + { + var character = value[i]; + + switch (character) + { + case '"': + _builder.Append("\\\""); + break; + + case '\\': + _builder.Append("\\\\"); + break; + + case '\n': + _builder.Append("\\n"); + break; + + case '\r': + _builder.Append("\\r"); + break; + + case '\t': + _builder.Append("\\t"); + break; + + default: + if (character < ' ') + { + _builder.Append("\\u").Append(((int)character).ToString("x4")); + } + else + { + _builder.Append(character); + } + + break; + } + } + + _builder.Append('"'); + } + + private static bool NeedsEscaping(string value) + { + for (var i = 0; i < value.Length; i++) + { + var character = value[i]; + + if (character == '"' || character == '\\' || character < ' ') + { + return true; + } + } + + return false; + } +} diff --git a/MenuAPI/nui/NuiTuning.cs b/MenuAPI/nui/NuiTuning.cs new file mode 100644 index 0000000..b05eaa7 --- /dev/null +++ b/MenuAPI/nui/NuiTuning.cs @@ -0,0 +1,70 @@ +namespace MenuAPI; + +public static class NuiTuning +{ + private static float _textSize = DefaultTextSize; + private static int _textBrightness = DefaultTextBrightness; + private static TextWeightMode _textWeight = TextWeightMode.Default; + + public const float DefaultTextSize = 21f; + public const int DefaultTextBrightness = 225; + + public enum TextWeightMode + { + Default, + GeometricPrecision, + Supersampled, + } + + public static float TextSize + { + get => _textSize; + set + { + _textSize = Math.Clamp(value, 8f, 48f); + + MenuNui.Invalidate(); + } + } + + public static int TextBrightness + { + get => _textBrightness; + set + { + _textBrightness = Math.Clamp(value, 0, 255); + + MenuNui.Invalidate(); + } + } + + public static TextWeightMode TextWeight + { + get => _textWeight; + set + { + _textWeight = value; + + MenuNui.Invalidate(); + } + } + + public static void Reset() + { + _textSize = DefaultTextSize; + _textBrightness = DefaultTextBrightness; + _textWeight = TextWeightMode.Default; + + MenuNui.Invalidate(); + } + + public static string Describe() => + $"size {_textSize:0.##}px, brightness {_textBrightness}, weight {_textWeight}"; + + internal static string WeightName => _textWeight switch + { + TextWeightMode.GeometricPrecision => "geometric", + TextWeightMode.Supersampled => "supersampled", + _ => "default", + }; +} diff --git a/MenuAPI/ticks/MenuLayout.cs b/MenuAPI/ticks/MenuLayout.cs index f112f04..744707d 100644 --- a/MenuAPI/ticks/MenuLayout.cs +++ b/MenuAPI/ticks/MenuLayout.cs @@ -1,6 +1,4 @@ -using CitizenFX.FiveM.Client; - -namespace MenuAPI; +namespace MenuAPI; /// /// Everything about the screen that the drawing code needs, worked out once instead of per draw call. @@ -61,11 +59,19 @@ internal static class MenuLayout /// Reads the screen values back from the game and works the rest out from them. internal static void Refresh() { + var previousWidth = ScreenWidth; + var previousSafeZone = SafeZone; + AspectRatio = Native.GetScreenAspectRatio(false); ScreenWidth = 1080f * AspectRatio; ScreenHeight = 1080f; SafeZone = Native.GetSafeZoneSize(); + if (ScreenWidth != previousWidth || SafeZone != previousSafeZone) + { + MenuNui.Invalidate(); + } + MenuWidthN = Menu.Width / ScreenWidth; HeaderHeightN = HeaderHeight / ScreenHeight; RowHeightN = RowHeight / ScreenHeight; diff --git a/MenuAPI/ui/assets/arrow.svg b/MenuAPI/ui/assets/arrow.svg new file mode 100644 index 0000000..6b28474 --- /dev/null +++ b/MenuAPI/ui/assets/arrow.svg @@ -0,0 +1,3 @@ + + + diff --git a/MenuAPI/ui/assets/glint.png b/MenuAPI/ui/assets/glint.png new file mode 100644 index 0000000000000000000000000000000000000000..664da57b496d411d618f3ef80088a77a05d7e9a5 GIT binary patch literal 3833 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 literal 0 HcmV?d00001 diff --git a/MenuAPI/ui/colour-list.css b/MenuAPI/ui/colour-list.css new file mode 100644 index 0000000..7faed59 --- /dev/null +++ b/MenuAPI/ui/colour-list.css @@ -0,0 +1,196 @@ +/* Everything is in the scaleform's own units. --u is a plain number that scales the whole component. */ + +.gcl { + --u: 1; + + --bg: rgba(0, 0, 0, 0.73); + --accent: 240, 240, 240; + --text: #f0f0f0; + + --palette-offset: 0; + + position: relative; + width: calc(288px * var(--u)); + font-family: "Chalet London 1960", "Helvetica Neue", Arial, sans-serif; + color: var(--text); + user-select: none; +} + +.gcl__title { + position: relative; + height: calc(50px * var(--u)); + background: var(--bg); +} + +.gcl__title[hidden] { + display: none; +} + +.gcl__label, +.gcl__min, +.gcl__max { + position: absolute; + font-size: calc(13px * var(--u)); + line-height: calc(20px * var(--u)); + white-space: nowrap; +} + +.gcl__label { + left: calc(7px * var(--u)); + top: calc(6px * var(--u)); +} + +.gcl__min, +.gcl__max { + top: calc(36px * var(--u)); + font-size: calc(9px * var(--u)); + line-height: calc(13px * var(--u)); + opacity: 0.7; +} + +.gcl__min { + left: calc(7px * var(--u)); +} + +.gcl__max { + right: calc(7px * var(--u)); +} + +.gcl__bar { + position: absolute; + left: calc(7px * var(--u)); + top: calc(30px * var(--u)); + width: calc(274px * var(--u)); + height: calc(6px * var(--u)); +} + +.gcl__bar-black, +.gcl__bar-alpha, +.gcl__bar-fill { + position: absolute; + left: 0; + top: 0; + height: 100%; +} + +.gcl__bar-black { + width: 100%; + background: rgba(0, 0, 0, 0.5); +} + +.gcl__bar-alpha { + width: 100%; + background: rgba(var(--accent), 0.3); +} + +.gcl__bar-fill { + width: 0; + background: rgb(var(--accent)); +} + +.gcl__palette { + position: relative; + height: calc(74px * var(--u)); + background: var(--bg); + top: calc(1px * var(--palette-offset) * var(--u)); +} + +.gcl__palette[hidden] { + display: none; +} + +.gcl__strip { + position: absolute; + left: calc(9px * var(--u)); + top: 0; + width: calc(270px * var(--u)); + height: calc(35px * var(--u)); +} + +.gcl__swatch { + --sw: 30; + position: absolute; + top: 0; + width: calc(1px * var(--sw) * var(--u)); + height: calc(35px * var(--u)); +} + +.gcl__swatch[hidden] { + display: none; +} + +.gcl__highlight { + position: absolute; + left: 0; + top: 0; + width: 100%; + height: calc(5px * var(--u)); + background: #fff; + clip-path: polygon( + 0 0, + calc(50% - 5px * var(--u)) 0, + 50% 100%, + calc(50% + 5px * var(--u)) 0, + 100% 0, + 100% 100%, + 0 100% + ); +} + +.gcl__highlight[hidden] { + display: none; +} + +.gcl__colour { + position: absolute; + left: 0; + top: calc(5px * var(--u)); + width: 100%; + height: calc(30px * var(--u)); + background: #888; +} + +.gcl--pc .gcl__colour { + cursor: pointer; +} + +.gcl__name { + position: absolute; + left: calc(9px * var(--u)); + top: calc(41px * var(--u)); + font-size: calc(12px * var(--u)); + line-height: calc(18px * var(--u)); + white-space: nowrap; +} + +.gcl__arrow { + position: absolute; + top: calc(14.6px * var(--u)); + width: calc(15.5px * var(--u)); + height: calc(13.75px * var(--u)); + padding: 0; + border: 0; + background: none; + cursor: pointer; + background-image: url("assets/arrow.svg"); + background-repeat: no-repeat; + background-size: 100% 100%; +} + +.gcl__arrow[hidden] { + display: none; +} + +.gcl__arrow--left { + left: calc(7.35px * var(--u)); +} + +.gcl__arrow--right { + left: calc(282px * var(--u)); + transform: scaleX(-1); + transform-origin: left center; +} + +.gcl__arrow:hover { + filter: brightness(1.4); +} diff --git a/MenuAPI/ui/colour-list.js b/MenuAPI/ui/colour-list.js new file mode 100644 index 0000000..61f71ed --- /dev/null +++ b/MenuAPI/ui/colour-list.js @@ -0,0 +1,346 @@ +// Port of Rockstar's COLOUR_SWITCHER scaleform to HTML. Method names, numbers and units are the +// scaleform's own, so what the C# side sends maps across one for one. + + +const COMPONENT_W = 288; +const TITLE_H = 50; +const PALETTE_H = 74; + +const SWATCH_W = 30; +const HIGHLIGHT_H = 5; +const SWATCH_COLOUR_H = 30; + +const VISIBLE_ITEMS = 9; +const STRIP_W = VISIBLE_ITEMS * SWATCH_W; + +const BAR_X = 7; +const BAR_Y = 30; +const BAR_W = 274; +const BAR_H = 6; + +const ARROW_LEFT_X = 7.35; +const ARROW_RIGHT_X = 282; +const ARROW_Y = 14.6; + +const HIGHLIGHT_DROP = 10; +const HIGHLIGHT_DURATION = 0.3; + +const BAR_TWEEN_DURATION = 0.175; + +function circEaseOut(t, b, c, d) { + t = t / d - 1; + return c * Math.sqrt(1 - t * t) + b; +} + +function quadEaseOut(t, b, c, d) { + t /= d; + return -c * t * (t - 2) + b; +} + +function el(tag, cls, parent) { + const node = document.createElement(tag); + if (cls) node.className = cls; + if (parent) parent.appendChild(node); + return node; +} + +function clamp(v, lo, hi) { + return Math.max(lo, Math.min(v, hi)); +} + +class GtaColourList { + constructor(root, options) { + options = options || {}; + this.root = root; + this.arrowsAllowed = options.arrows !== false; + this.visibleItems = options.visibleItems || VISIBLE_ITEMS; + this.onSelect = options.onSelect || function () {}; + this.onScroll = options.onScroll || function () {}; + + this.colourData = []; + this.swatches = []; + this.highlightIndex = 0; + this.highlightPosIndex = 0; + this.topEdge = 0; + this.pcActive = false; + + this.tweens = []; + this.raf = 0; + this._tick = this._tick.bind(this); + + this._build(); + } + + _build() { + const r = this.root; + r.classList.add('gcl'); + r.innerHTML = ''; + + this.titleEl = el('div', 'gcl__title', r); + this.titleEl.hidden = true; + this.titleLabel = el('div', 'gcl__label', this.titleEl); + this.minLabel = el('div', 'gcl__min', this.titleEl); + this.maxLabel = el('div', 'gcl__max', this.titleEl); + + this.barEl = el('div', 'gcl__bar', this.titleEl); + this.barBlack = el('div', 'gcl__bar-black', this.barEl); + this.barAlpha = el('div', 'gcl__bar-alpha', this.barEl); + this.barFill = el('div', 'gcl__bar-fill', this.barEl); + this.barWidth = BAR_W; + + this.paletteEl = el('div', 'gcl__palette', r); + this.paletteEl.hidden = true; + this.stripEl = el('div', 'gcl__strip', this.paletteEl); + this.nameLabel = el('div', 'gcl__name', this.paletteEl); + + this.leftArrow = el('button', 'gcl__arrow gcl__arrow--left', this.paletteEl); + this.rightArrow = el('button', 'gcl__arrow gcl__arrow--right', this.paletteEl); + this.leftArrow.type = this.rightArrow.type = 'button'; + this.leftArrow.hidden = this.rightArrow.hidden = true; + this.leftArrow.addEventListener('click', () => this.onScroll(-1)); + this.rightArrow.addEventListener('click', () => this.onScroll(1)); + + this.stripEl.addEventListener('mouseleave', () => { + if (this.pcActive) this.onSelect(-1); + }); + } + + SET_IS_PC(isPc) { + this.pcActive = !!isPc && this.arrowsAllowed; + this.leftArrow.hidden = this.rightArrow.hidden = !this.pcActive; + this.root.classList.toggle('gcl--pc', this.pcActive); + return this; + } + + SET_TITLE(title, paletteLabel, percent, showArrows) { + if (paletteLabel !== undefined) this.nameLabel.textContent = paletteLabel; + + if (percent === undefined || percent === -1 || isNaN(percent)) { + this.titleEl.hidden = true; + this.root.classList.add('gcl--no-title'); + } else { + if (title !== undefined) this.titleLabel.textContent = title; + this.minLabel.textContent = '0%'; + this.maxLabel.textContent = '100%'; + this.percent(percent); + this.titleEl.hidden = false; + this.root.classList.remove('gcl--no-title'); + } + + if (showArrows) this.SET_IS_PC(true); + return this; + } + + SHOW_OPACITY(show, opacityPosTop) { + this.titleEl.hidden = !show; + this.root.classList.toggle('gcl--no-title', !show); + this.root.style.setProperty( + '--palette-offset', opacityPosTop ? 0 : PALETTE_H + ); + return this; + } + + percent(p, tween) { + const clamped = clamp(p, 0, 100); + const target = Math.round(this.barWidth * (clamped / 100)); + if (tween) { + this._tween(this.barFill, this.barFillWidth || 0, target, + BAR_TWEEN_DURATION, quadEaseOut, (node, v) => { + node.style.width = 'calc(' + v + 'px * var(--u))'; + }); + } else { + this.barFill.style.width = 'calc(' + target + 'px * var(--u))'; + } + this.barFillWidth = target; + return this; + } + + SET_DATA_SLOT(index, r, g, b) { + this.colourData[index] = [index, r, g, b]; + return this; + } + + SET_DATA_SLOT_EMPTY() { + this.tweens.length = 0; + this.stripEl.innerHTML = ''; + this.colourData = []; + this.swatches = []; + this.paletteEl.hidden = true; + return this; + } + + DISPLAY_VIEW() { + const count = Math.min(this.colourData.length, this.visibleItems); + this.stripEl.innerHTML = ''; + this.swatches = []; + + for (let i = 0; i < count; i++) { + const swatch = el('div', 'gcl__swatch', this.stripEl); + const highlight = el('div', 'gcl__highlight', swatch); + const colour = el('div', 'gcl__colour', swatch); + + swatch.dataset.index = String(i); + highlight.hidden = true; + + colour.addEventListener('mouseenter', () => { + if (this.pcActive) this.onSelect(this._dataIndexAt(i)); + }); + colour.addEventListener('click', () => { + this.onSelect(this._dataIndexAt(i)); + }); + + this.swatches.push({ root: swatch, highlight: highlight, colour: colour }); + this.itemSetData(i, this.swatches[i], this.colourData[i]); + } + + this.repositionPalettes(); + this.paletteEl.hidden = false; + this._startLoop(); + return this; + } + + UPDATE_SLOT(index, r, g, b) { + this.SET_DATA_SLOT(index, r, g, b); + const local = index - this.topEdge; + if (this.swatches[local]) { + this.itemSetData(local, this.swatches[local], this.colourData[index]); + } + return this; + } + + CLEAR_HIGHLIGHT() { + this.highlightIndex = 0; + this.highlightPosIndex = 0; + this.topEdge = 0; + return this; + } + + SET_HIGHLIGHT(index) { + const total = this.colourData.length; + if (!total) return this; + + index = clamp(index, 0, total - 1); + let local = index; + let firstVisible = this.topEdge; + + if (total > this.visibleItems) { + if (local > this.topEdge + this.visibleItems - 1) { + firstVisible = local - (this.visibleItems - 1); + this.topEdge = firstVisible; + local = this.visibleItems - 1; + } else if (local < this.topEdge) { + firstVisible = local; + this.topEdge = firstVisible; + local = 0; + } else { + firstVisible = this.topEdge; + local -= this.topEdge; + } + for (let i = 0; i < this.swatches.length; i++) { + this.itemSetData(i, this.swatches[i], this.colourData[firstVisible + i]); + } + } + + for (let i = 0; i < this.swatches.length; i++) { + const on = i === local; + const highlight = this.swatches[i].highlight; + highlight.hidden = !on; + if (on) { + if (this.highlightPosIndex !== local) { + this._tween(highlight, HIGHLIGHT_DROP, 0, + HIGHLIGHT_DURATION, circEaseOut, (node, v) => { + node.style.transform = 'translateY(calc(' + v + 'px * var(--u)))'; + }); + } else { + this.tweens = this.tweens.filter((t) => t.node !== highlight); + highlight.style.transform = 'translateY(0)'; + } + } + } + + this.highlightIndex = index; + this.highlightPosIndex = local; + return this; + } + + itemSetData(i, swatch, data) { + if (!swatch) return; + if (!data) { + swatch.root.hidden = true; + return; + } + swatch.root.hidden = false; + const r = data[1], g = data[2], b = data[3]; + if (r !== undefined) { + swatch.colour.style.background = 'rgb(' + r + ',' + g + ',' + b + ')'; + } + } + + repositionPalettes() { + const count = this.swatches.length; + if (!count) return this; + const w = count <= this.visibleItems + ? (this.visibleItems * SWATCH_W) / count + : SWATCH_W; + + for (let i = 0; i < count; i++) { + const s = this.swatches[i]; + s.root.style.setProperty('--sw', w); + s.root.style.left = 'calc(' + (i * w) + 'px * var(--u))'; + } + return this; + } + + _dataIndexAt(local) { + return this.topEdge + local; + } + + _tween(node, from, to, duration, ease, apply) { + this.tweens = this.tweens.filter((t) => t.node !== node); + this.tweens.push({ + node: node, from: from, to: to, + duration: duration * 1000, start: performance.now(), + ease: ease, apply: apply, + }); + apply(node, from); + this._startLoop(); + } + + _startLoop() { + if (!this.raf) this.raf = requestAnimationFrame(this._tick); + } + + _tick(now) { + this.raf = 0; + for (let i = this.tweens.length - 1; i >= 0; i--) { + const t = this.tweens[i]; + const elapsed = now - t.start; + if (elapsed >= t.duration) { + t.apply(t.node, t.to); + this.tweens.splice(i, 1); + } else { + t.apply(t.node, t.ease(elapsed / 1000, t.from, t.to - t.from, t.duration / 1000)); + } + } + if (this.tweens.length) this._startLoop(); + } + + destroy() { + if (this.raf) cancelAnimationFrame(this.raf); + this.raf = 0; + this.tweens.length = 0; + return this; + } +} + +Object.assign(GtaColourList.prototype, { + setIsPc: GtaColourList.prototype.SET_IS_PC, + setTitle: GtaColourList.prototype.SET_TITLE, + showOpacity: GtaColourList.prototype.SHOW_OPACITY, + setDataSlot: GtaColourList.prototype.SET_DATA_SLOT, + clearData: GtaColourList.prototype.SET_DATA_SLOT_EMPTY, + displayView: GtaColourList.prototype.DISPLAY_VIEW, + updateSlot: GtaColourList.prototype.UPDATE_SLOT, + clearHighlight: GtaColourList.prototype.CLEAR_HIGHLIGHT, + setHighlight: GtaColourList.prototype.SET_HIGHLIGHT, +}); diff --git a/MenuAPI/ui/fonts/chalet-london.woff b/MenuAPI/ui/fonts/chalet-london.woff new file mode 100644 index 0000000000000000000000000000000000000000..e55e897a764a55b814d24e920d16a14bba7d1c31 GIT binary patch literal 26760 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 L2LK?f0K)$V6ZVK0 literal 0 HcmV?d00001 diff --git a/MenuAPI/ui/glare.js b/MenuAPI/ui/glare.js new file mode 100644 index 0000000..7cbcc4b --- /dev/null +++ b/MenuAPI/ui/glare.js @@ -0,0 +1,341 @@ +// Port of Rockstar's mp_menu_glare scaleform to canvas 2D. Every number came out of the +// decompiled SWF, and everything is laid out in the scaleform's own 288 by 75 unit space. + +const DESIGN_W = 288; +const DESIGN_H = 65; + +const DESIGN_BLEED = 10; +const GLINT_CLIP_H = DESIGN_H + DESIGN_BLEED; + +const GLOBE_X = 196; +const GLOBE_Y = -9; + +const GLOBE_ALPHA = 38 / 256; + +const GLOBE_D = + 'M31.4 41.95 L30.75 47.3 30.35 51.45 Q30.0 55.55 29.9 59.9 L68.5 64.0 ' + + 'Q69.75 57.2 70.3 51.55 L71.1 40.35 32.3 36.25 31.4 41.95 M9.45 33.8 ' + + 'Q8.1 39.1 7.5 44.85 6.8 50.9 7.2 57.5 L23.0 59.15 23.4 51.45 23.85 46.6 ' + + '24.4 41.95 25.4 35.5 9.45 33.8 M32.85 9.25 Q26.5 12.05 21.25 16.6 ' + + '16.0 21.2 12.45 27.15 L12.5 27.15 26.8 28.7 Q29.15 18.25 32.8 9.45 ' + + 'L32.85 9.25 M92.0 35.65 L92.0 42.6 78.0 41.1 77.2 52.25 Q76.5 58.8 ' + + '75.35 64.75 L91.2 66.4 92.0 63.3 92.0 74.0 87.85 74.0 88.4 73.1 ' + + '73.9 71.55 73.3 74.0 66.2 74.0 67.0 70.8 29.9 66.85 30.3 74.0 23.4 74.0 ' + + '23.0 66.15 8.65 64.6 8.6 64.6 Q10.35 69.55 13.2 74.0 L6.1 74.0 ' + + 'Q-1.35 60.7 0.35 44.65 2.55 23.95 18.75 10.85 L21.2 9.0 40.55 9.0 ' + + 'Q36.4 17.65 33.7 29.4 L71.2 33.4 Q71.0 19.65 68.1 9.5 L68.0 9.2 67.55 9.0 ' + + 'L79.7 9.0 Q85.05 12.9 89.5 18.4 L92.0 21.75 92.0 34.3 Q89.8 28.5 ' + + '86.05 23.45 81.9 17.85 76.2 13.8 L76.3 14.2 Q77.95 23.35 78.1 34.15 ' + + 'L92.0 35.65 Z'; + +const GLARE_D = + 'M-53.4 15.75 Q-69.15 -45.9 -97.8 -99.6 L20.2 59.35 -35.9 124.5 ' + + 'Q-40.5 66.5 -53.4 15.75 Z'; + +const GLARE_GRAD_FROM = [11.66, 45.87]; +const GLARE_GRAD_TO = [-62.55, 99.02]; + +const GLARE_RAMP = [ + 1.0, 0.9882, 0.9686, 0.9451, 0.9216, 0.8941, 0.8627, 0.8314, 0.8, 0.7686, + 0.7333, 0.6941, 0.6588, 0.6196, 0.5804, 0.5412, 0.5059, 0.4667, 0.4275, + 0.3882, 0.349, 0.3137, 0.2745, 0.2392, 0.2078, 0.1765, 0.1451, 0.1137, + 0.0863, 0.0627, 0.0392, 0.0196, 0.0078, +]; + +const GX1 = 230, GR1 = -55, GA1 = 20; +const GX2 = 290, GR2 = -30, GA2 = 20; +const GLARE_Y = -25; + +const FPS = 30; +const FRAME_MS = 1000 / FPS; + +const GLINT_TOTAL_FRAMES = 270; +const GLINT_LOOP_FRAME = 42; +const GLINT_MIN_DELAY = 60; + +const GLINT_FRAMES = [ + {ga:0, m:null, s:[-15.0, 0.25, 0.25]}, + {ga:0.0, m:[-0.44732, -1.6911, 14.89977, -3.94117, -153.55, 225.75], s:[4.75, 0.2901, 0.31943]}, + {ga:0.0312, m:[-0.44732, -1.6911, 14.89977, -3.94117, -148.41667, 226.28333], s:[23.4, 0.3287, 0.38625]}, + {ga:0.0703, m:[-0.44732, -1.6911, 14.89977, -3.94117, -143.28333, 226.81667], s:[40.95, 0.3658, 0.45049]}, + {ga:0.1094, m:[-0.44732, -1.6911, 14.89977, -3.94117, -138.15, 227.35], s:[57.35, 0.40146, 0.51219]}, + {ga:0.1406, m:[-0.44732, -1.6911, 14.89977, -3.94117, -133.01667, 227.88333], s:[72.8, 0.4357, 0.57147]}, + {ga:0.1797, m:[-0.44732, -1.6911, 14.89977, -3.94117, -127.88333, 228.41667], s:[87.25, 0.46857, 0.62837]}, + {ga:0.2188, m:[-0.44732, -1.6911, 14.89977, -3.94117, -122.75, 228.95], s:[100.8, 0.50011, 0.68297]}, + {ga:0.25, m:[-0.44732, -1.6911, 14.89977, -3.94117, -117.61667, 229.48333], s:[113.5, 0.53035, 0.73531]}, + {ga:0.2891, m:[-0.44732, -1.6911, 14.89977, -3.94117, -112.48216, 230.01679], s:[125.45, 0.55934, 0.78552]}, + {ga:0.3281, m:[-0.44732, -1.6911, 14.89977, -3.94117, -107.34883, 230.55012], s:[136.6, 0.58713, 0.8336]}, + {ga:0.3711, m:[-0.44732, -1.6911, 14.89977, -3.94117, -102.21549, 231.08346], s:[147.1, 0.61374, 0.87967]}, + {ga:0.3984, m:[-0.44732, -1.6911, 14.89977, -3.94117, -97.08216, 231.61679], s:[156.9, 0.63922, 0.9238]}, + {ga:0.4414, m:[-0.44732, -1.6911, 14.89977, -3.94117, -91.94883, 232.15012], s:[166.1, 0.66362, 0.96603]}, + {ga:0.4805, m:[-0.44732, -1.6911, 14.89977, -3.94117, -86.81549, 232.68346], s:[174.8, 0.68697, 1.00644]}, + {ga:0.5117, m:[-0.44732, -1.6911, 14.89977, -3.94117, -81.68216, 233.21679], s:[182.95, 0.68407, 1.00815]}, + {ga:0.5508, m:[-0.44732, -1.6911, 14.89977, -3.94117, -76.55, 233.75], s:[190.6, 0.64749, 0.96025]}, + {ga:0.5898, m:[-0.39647, -1.68586, 14.85357, -3.49318, -75.26857, 229.95365], s:[197.85, 0.61253, 0.91447]}, + {ga:0.6211, m:[-0.34564, -1.68062, 14.80738, -3.04535, -73.98762, 226.15868], s:[204.65, 0.57907, 0.87067]}, + {ga:0.6602, m:[-0.2948, -1.67537, 14.76117, -2.59735, -72.70619, 222.36233], s:[211.15, 0.54704, 0.82875]}, + {ga:0.6992, m:[-0.24395, -1.67013, 14.71496, -2.14936, -71.42477, 218.56597], s:[217.3, 0.51642, 0.78865]}, + {ga:0.7383, m:[-0.19312, -1.66488, 14.66877, -1.70153, -70.14381, 214.77101], s:[223.1, 0.48712, 0.75029]}, + {ga:0.7695, m:[-0.14227, -1.65964, 14.62256, -1.25353, -68.86238, 210.97465], s:[228.7, 0.45905, 0.71355]}, + {ga:0.8086, m:[-0.09143, -1.6544, 14.57635, -0.80554, -67.58096, 207.1783], s:[234.0, 0.43214, 0.6783]}, + {ga:0.8516, m:[-0.0406, -1.64915, 14.53016, -0.35771, -66.3, 203.38333], s:[239.15, 0.40634, 0.64453]}, + {ga:0.8789, m:[0.01025, -1.64391, 14.48395, 0.09028, -65.01857, 199.58698], s:[244.05, 0.38159, 0.61214]}, + {ga:0.9219, m:[0.06109, -1.63866, 14.43775, 0.53828, -63.73715, 195.79062], s:[248.85, 0.3578, 0.58098]}, + {ga:0.9609, m:[0.11192, -1.63342, 14.39155, 0.98611, -62.45619, 191.99566], s:[253.45, 0.34384, 0.55101]}, + {ga:1.0, m:[0.16277, -1.62818, 14.34535, 1.4341, -61.17477, 188.1993], s:[257.95, 0.33482, 0.52209]}, + {ga:0.9102, m:[0.21361, -1.62293, 14.29914, 1.8821, -59.89334, 184.40295], s:[262.4, 0.32613, 0.49419]}, + {ga:0.8281, m:[0.26444, -1.61769, 14.25295, 2.32993, -58.61238, 180.60799], s:[266.7, 0.3177, 0.46719]}, + {ga:0.75, m:[0.31529, -1.61244, 14.20674, 2.77792, -57.33096, 176.81163], s:[270.95, 0.30956, 0.44102]}, + {ga:0.6602, m:[0.36614, -1.6072, 14.16053, 3.22591, -56.04953, 173.01528], s:[275.15, 0.30161, 0.41556]}, + {ga:0.5781, m:[0.41696, -1.60196, 14.11434, 3.67374, -54.76857, 169.22031], s:[279.35, 0.29388, 0.39076]}, + {ga:0.5, m:[0.46781, -1.59671, 14.06813, 4.12174, -53.48715, 165.42396], s:[283.45, 0.2863, 0.36647]}, + {ga:0.4102, m:[0.51866, -1.59147, 14.02193, 4.56973, -52.20572, 161.6276], s:[287.6, 0.27888, 0.34265]}, + {ga:0.3281, m:[0.56948, -1.58623, 13.97573, 5.01756, -50.92477, 157.83264], s:[291.7, 0.27155, 0.31914]}, + {ga:0.25, m:[0.62033, -1.58098, 13.92953, 5.46556, -49.64334, 154.03628], s:[295.8, 0.26433, 0.29596]}, + {ga:0.1602, m:[0.67118, -1.57574, 13.88332, 5.91355, -48.36191, 150.23993], s:[299.85, 0.25716, 0.27295]}, + {ga:0.0781, m:[0.72201, -1.57049, 13.83713, 6.36138, -47.08096, 146.44496], s:[304.0, 0.25, 0.25]}, +]; + +function quadEaseOut(t, b, c, d) { + t /= d; + return -c * t * (t - 2) + b; +} + +function quadEaseInOut(t, b, c, d) { + t /= d / 2; + if (t < 1) return (c / 2) * t * t + b; + t -= 1; + return (-c / 2) * (t * (t - 2) - 1) + b; +} + +function loadImage(src) { + return new Promise(function (resolve, reject) { + const img = new Image(); + img.onload = function () { resolve(img); }; + img.onerror = function () { reject(new Error('could not load ' + src)); }; + img.src = src; + }); +} + +class GtaMenuGlare { + constructor(canvas, options) { + options = options || {}; + this.canvas = canvas; + this.ctx = canvas.getContext('2d'); + + this.glintTextureSrc = options.glintTexture || 'menuapi/assets/glint.png'; + this.autoGlint = options.autoGlint !== false; + this.fadeIn = options.fadeIn !== false; + this.bleed = options.bleed === undefined ? DESIGN_BLEED : options.bleed; + + this.globePath = new Path2D(GLOBE_D); + this.glarePath = new Path2D(GLARE_D); + + this.targetAngle = 0; + this.position = 0; + this.easedPosition = 0; + this.contentAlpha = this.fadeIn ? 0 : 1; + this.easeInCur = this.fadeIn ? 0 : 30; + this.easeInDuration = 30; + + this.glintFrame = GLINT_LOOP_FRAME; + this.glintPattern = null; + + this.running = false; + this.accumulator = 0; + this.lastTime = 0; + this.raf = 0; + + this._tick = this._tick.bind(this); + } + + load() { + const self = this; + return loadImage(this.glintTextureSrc).then(function (img) { + self.glintPattern = self.ctx.createPattern(img, 'repeat'); + return self; + }); + } + + start() { + if (this.running) return this; + this.running = true; + this.lastTime = performance.now(); + this.accumulator = 0; + this.raf = requestAnimationFrame(this._tick); + return this; + } + + stop() { + this.running = false; + if (this.raf) cancelAnimationFrame(this.raf); + return this; + } + + open() { + this.easeInCur = 0; + this.contentAlpha = 0; + return this; + } + + setHeading(angle, triggerGlint) { + if (triggerGlint) this.triggerGlint(); + this.targetAngle = angle % 360; + return this; + } + + triggerGlint() { + this.glintFrame = 2; + return this; + } + + toDataURL() { + return this.canvas.toDataURL('image/png'); + } + + _resize() { + const dpr = window.devicePixelRatio || 1; + const rect = this.canvas.getBoundingClientRect(); + const w = Math.max(1, Math.round(rect.width * dpr)); + const h = Math.max(1, Math.round(rect.height * dpr)); + if (this.canvas.width !== w || this.canvas.height !== h) { + this.canvas.width = w; + this.canvas.height = h; + } + } + + _step() { + if (this.easeInCur < this.easeInDuration) { + this.contentAlpha = + quadEaseOut(this.easeInCur++, 0, 100, this.easeInDuration) / 100; + } else { + this.contentAlpha = 1; + } + + let targetPosition = (this.targetAngle % 180) / 180; + if (this.targetAngle > 180) targetPosition = 1 - targetPosition; + + this.position += (targetPosition - this.position) / 16; + this.easedPosition = quadEaseInOut(this.position, 0, 1, 1); + + this.glintFrame += 1; + if (this.glintFrame === GLINT_LOOP_FRAME && this.autoGlint) { + const span = GLINT_TOTAL_FRAMES - GLINT_LOOP_FRAME - GLINT_MIN_DELAY; + this.glintFrame = Math.round(Math.random() * span) + GLINT_LOOP_FRAME; + } + if (this.glintFrame > GLINT_TOTAL_FRAMES) this.glintFrame = 1; + } + + _tick(now) { + if (!this.running) return; + this.raf = requestAnimationFrame(this._tick); + + this.accumulator += now - this.lastTime; + this.lastTime = now; + if (this.accumulator > FRAME_MS * 5) this.accumulator = FRAME_MS * 5; + + let stepped = false; + while (this.accumulator >= FRAME_MS) { + this.accumulator -= FRAME_MS; + this._step(); + stepped = true; + } + if (stepped) this.draw(); + } + + draw() { + this._resize(); + const ctx = this.ctx; + const w = this.canvas.width; + const h = this.canvas.height; + + ctx.setTransform(1, 0, 0, 1, 0, 0); + ctx.clearRect(0, 0, w, h); + if (this.contentAlpha <= 0) return; + + ctx.save(); + ctx.scale(w / DESIGN_W, h / (DESIGN_H + this.bleed)); + ctx.imageSmoothingEnabled = true; + ctx.imageSmoothingQuality = 'high'; + + this._drawGlobe(ctx); + this._drawGlint(ctx); + this._drawGlare(ctx); + + ctx.restore(); + } + + _drawGlobe(ctx) { + ctx.save(); + ctx.globalAlpha = GLOBE_ALPHA * this.contentAlpha; + ctx.fillStyle = '#000'; + ctx.translate(GLOBE_X, GLOBE_Y); + ctx.fill(this.globePath, 'evenodd'); + ctx.restore(); + } + + _drawGlint(ctx) { + const frame = GLINT_FRAMES[this.glintFrame - 1]; + if (!frame || !this.glintPattern) return; + + ctx.save(); + ctx.beginPath(); + ctx.rect(0, 0, DESIGN_W, GLINT_CLIP_H); + ctx.clip(); + + if (frame.m && frame.ga > 0) { + ctx.save(); + ctx.globalAlpha = frame.ga * this.contentAlpha; + ctx.translate(GLOBE_X, GLOBE_Y); + this.glintPattern.setTransform(new DOMMatrix(frame.m)); + ctx.fillStyle = this.glintPattern; + ctx.fill(this.globePath, 'evenodd'); + ctx.restore(); + } + + if (frame.s) { + const x = frame.s[0], sx = frame.s[1], sy = frame.s[2]; + ctx.save(); + ctx.globalCompositeOperation = 'lighter'; + ctx.globalAlpha = this.contentAlpha; + ctx.translate(x, 65); + ctx.scale(sx, sy); + this.glintPattern.setTransform(new DOMMatrix([1, 0, 0, 1, -64, -8])); + ctx.fillStyle = this.glintPattern; + ctx.fillRect(-64, -8, 128, 16); + ctx.restore(); + } + + ctx.restore(); + } + + _drawGlare(ctx) { + const p = this.easedPosition; + const x = GX1 + (GX2 - GX1) * p; + const rotation = GR1 + (GR2 - GR1) * p; + const alpha = (GA1 + (GA2 - GA1) * p) / 100; + + ctx.save(); + ctx.beginPath(); + ctx.rect(0, 0, DESIGN_W, DESIGN_H); + ctx.clip(); + + ctx.globalAlpha = alpha * this.contentAlpha; + ctx.translate(x, GLARE_Y); + ctx.rotate((rotation * Math.PI) / 180); + + const grad = ctx.createLinearGradient( + GLARE_GRAD_FROM[0], GLARE_GRAD_FROM[1], + GLARE_GRAD_TO[0], GLARE_GRAD_TO[1] + ); + for (let i = 0; i < GLARE_RAMP.length; i++) { + grad.addColorStop( + i / (GLARE_RAMP.length - 1), + 'rgba(255,255,255,' + GLARE_RAMP[i] + ')' + ); + } + ctx.fillStyle = grad; + ctx.fill(this.glarePath); + ctx.restore(); + } +} diff --git a/MenuAPI/ui/menuapi.css b/MenuAPI/ui/menuapi.css new file mode 100644 index 0000000..df17ebd --- /dev/null +++ b/MenuAPI/ui/menuapi.css @@ -0,0 +1,478 @@ +@font-face { + font-family: "GTA Chalet"; + src: url("fonts/chalet-london.woff") format("woff"); + font-display: block; +} + +@font-face { + font-family: "GTA House Script"; + src: url("fonts/house-script.woff") format("woff"); + font-display: block; +} + +@font-face { + font-family: "GTA Rockstar Tag"; + src: url("fonts/rockstar-tag.woff") format("woff"); + font-display: block; +} + +@font-face { + font-family: "GTA Chalet Comprime"; + src: url("fonts/chalet-comprime.woff") format("woff"); + font-display: block; +} + +@font-face { + font-family: "GTA Pricedown"; + src: url("fonts/pricedown.woff") format("woff"); + font-display: block; +} + +/* :where() so this carries no specificity and cannot outrank the class rules below. */ +:where(#menuapi, #menuapi *, #menuapi *::before, #menuapi *::after) { + box-sizing: content-box; + margin: 0; + padding: 0; + border: 0; +} + +#menuapi { + --menuapi-origin-x: 0.0125; + --menuapi-origin-y: 0.0185; + + --menuapi-scale: 1; + --menuapi-aspect: 1.7777778; + + --menuapi-freemode: rgb(255 255 255); + + /* The game insets text from where it is told to draw it and CSS does not. Both measured. */ + --menuapi-text-inset: 2.5px; + + --menuapi-title-inset: 1px; + + /* 225, not 255: measured off the game's own white menu text. */ + --menuapi-text: rgb(var(--menuapi-text-rgb, 225 225 225)); + + font-size: calc(var(--menuapi-text-size, 21) * 1px); + + position: fixed; + top: calc(var(--menuapi-origin-y) * 100vh); + left: calc(var(--menuapi-origin-x) * 100vw); + width: 500px; + + transform-origin: top left; + transform: scale(var(--menuapi-scale)); + + pointer-events: none; + + font-family: "GTA Chalet", "Helvetica Neue", Arial, sans-serif; + + line-height: 1; + color: var(--menuapi-text); +} + +#menuapi[data-text-weight="geometric"] { + text-rendering: geometricPrecision; +} + +#menuapi[data-text-weight="supersampled"] { + zoom: 2; + transform: scale(calc(var(--menuapi-scale) / 2)); + will-change: transform; +} + +#menuapi[hidden] { + display: none; +} + +/* A class display rule outranks the browser's own hidden rule, so put it back. */ +#menuapi [hidden] { + display: none; +} + +.menuapi-header { + position: relative; + width: 500px; + height: 110px; +} + +.menuapi-header__bg { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + object-fit: fill; +} + +/* Taller than the header on purpose: half the light bar's glow belongs below the lower edge. */ +.menuapi-header__glare { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 115.385%; +} + +.menuapi-header__title { + position: absolute; + inset: 0; + display: flex; + align-items: center; + padding: 0 calc(10px + var(--menuapi-text-inset)) 0 calc(10px + var(--menuapi-title-inset)); + color: var(--menuapi-text); + transform: translateY(var(--menuapi-title-nudge, 0px)); +} + +/* Sizes and nudges are measured per font against the game. They differ by up to 5px, do not merge them. */ +.menuapi-header__title[data-font="0"] { + font-family: "GTA Chalet", Arial, sans-serif; + font-size: 52.4px; + --menuapi-title-nudge: 2.5px; +} + +.menuapi-header__title[data-font="1"] { + font-family: "GTA House Script", cursive; + font-size: 62.7px; + --menuapi-title-nudge: 3.9px; +} + +/* MenuFont.Monospace is font 2, which is the crew tag face rather than a Courier. */ +.menuapi-header__title[data-font="2"] { + font-family: "GTA Rockstar Tag", monospace; + font-size: 64.4px; + --menuapi-title-nudge: -1.6px; +} + +.menuapi-header__title[data-font="4"] { + font-family: "GTA Chalet Comprime", Arial, sans-serif; + font-size: 58.4px; + --menuapi-title-nudge: 2.2px; +} + +.menuapi-header__title[data-font="7"] { + font-family: "GTA Pricedown", Impact, sans-serif; + font-size: 62.9px; + --menuapi-title-nudge: 2.8px; +} + +.menuapi-header__title[data-align="center"] { + justify-content: center; +} + +.menuapi-header__title[data-align="right"] { + justify-content: flex-end; +} + +.menuapi-subtitle { + display: flex; + align-items: center; + justify-content: space-between; + height: 38px; + padding: 0 calc(10px + var(--menuapi-text-inset)); + background: rgb(0 0 0 / 98%); + + text-transform: uppercase; +} + +.menuapi-subtitle--freemode { + color: var(--menuapi-freemode); +} + +.menuapi-rows { + background: rgb(0 0 0 / 70.5%); +} + +.menuapi-row { + position: relative; + height: 38px; + color: var(--menuapi-text); +} + +.menuapi-row--selected { + background: rgb(255 255 255 / 88%); + color: #000; +} + +.menuapi-row--disabled { + color: rgb(109 109 109); +} + +.menuapi-row--selected.menuapi-row--disabled { + color: rgb(50 50 50); +} + +.menuapi-row--separator { + line-height: 38px; + text-align: center; +} + +.menuapi-row--separator.menuapi-row--disabled { + color: var(--menuapi-text); +} + +.menuapi-row--separator.menuapi-row--selected { + color: #000; +} + +.menuapi-row__text { + position: absolute; + top: 0; + left: 10px; + right: 10px; + padding-left: var(--menuapi-text-inset); + height: 100%; + line-height: 38px; + overflow: hidden; + white-space: nowrap; +} + +.menuapi-row--icon-left .menuapi-row__text { + left: 35px; +} + +.menuapi-row__label { + position: absolute; + top: 0; + right: 10px; + padding-right: var(--menuapi-text-inset); + height: 100%; + line-height: 38px; + white-space: nowrap; +} + +.menuapi-row--icon-right .menuapi-row__label { + right: 35px; +} + +.menuapi-icon { + position: absolute; + top: 50%; + background-image: var(--sprite); + background-position: center; + background-repeat: no-repeat; + background-size: contain; + + isolation: isolate; +} + +/* Tint multiplied over the texture and masked to its alpha, so a shaded sprite keeps its detail. */ +.menuapi-icon::after { + content: ""; + position: absolute; + inset: 0; + background-color: var(--tint); + mix-blend-mode: multiply; + mask-image: var(--sprite); + mask-position: center; + mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-image: var(--sprite); + -webkit-mask-position: center; + -webkit-mask-repeat: no-repeat; + -webkit-mask-size: contain; +} + +.menuapi-icon--left { + left: 20px; + transform: translate(-50%, -50%); +} + +.menuapi-icon--right, +.menuapi-icon--checkbox { + left: 480px; + transform: translate(-50%, -50%); +} + +.menuapi-slider { + position: absolute; + top: 50%; + right: 8px; + width: 150px; + height: 10px; + transform: translateY(-50%); +} + +.menuapi-row--slider-icons .menuapi-slider { + right: 48px; +} + +/* Always half the track, slid end to end rather than grown from nothing. */ +.menuapi-slider__bar { + position: absolute; + top: 0; + width: 50%; + height: 100%; +} + +.menuapi-slider__divider { + position: absolute; + top: 50%; + left: 77px; + width: 4px; + height: 19px; + background: #fff; + transform: translate(-50%, -50%); +} + +.menuapi-icon--slider { + right: 202px; + transform: translateY(-50%); +} + +.menuapi-overflow { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: calc(60px / var(--menuapi-aspect)); + margin-top: 4px; + background: rgb(0 0 0 / 70.5%); + color: var(--menuapi-text); +} + +.menuapi-overflow > span { + line-height: 11px; +} + +.menuapi-desc { + position: relative; + padding: 8px calc(10px + var(--menuapi-text-inset)) 2.5px; + margin-top: 8px; + background: rgb(0 0 0 / 70.5%); + + white-space: pre-wrap; + overflow-wrap: anywhere; + line-height: 26.5px; +} + +#menuapi.menuapi--overflow .menuapi-desc { + margin-top: 4px; +} + +.menuapi-desc::before { + content: ""; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 4px; + background: rgb(0 0 0 / 78%); +} + +.menuapi-panel { + --u: calc(500 / 288); + + margin-top: 4px; + + --bg: var(--menuapi-panel-bg, rgb(0 0 0 / 73%)); + --accent: var(--menuapi-panel-accent, 240, 240, 240); + + --text: var(--menuapi-text); + + font-family: inherit; +} + +#menuapi .gcl__label, +#menuapi .gcl__min, +#menuapi .gcl__max { + top: calc(6px * var(--u)); + font-size: calc(11px * var(--u)); + line-height: calc(16px * var(--u)); + opacity: 1; +} + +#menuapi .gcl__name { + top: calc(6px * var(--u)); + font-size: calc(14.5px * var(--u)); + line-height: calc(21px * var(--u)); + opacity: 1; +} + +#menuapi .gcl__label, +#menuapi .gcl__name { + left: 0; + right: 0; + text-align: center; +} + +#menuapi .gcl__min { + left: calc(7px * var(--u)); +} + +#menuapi .gcl__max { + right: calc(7px * var(--u)); +} + +#menuapi .gcl__strip { + top: calc(29px * var(--u)); +} + +#menuapi .gcl__title { + height: calc(47px * var(--u)); +} + +#menuapi .menuapi-panel:not(.menuapi-panel--no-colours) .gcl__title { + height: calc(42px * var(--u)); +} + +.menuapi-panel--no-colours .gcl__palette { + display: none; +} + +.menuapi-stats { + height: 130px; + padding-top: 10px; + margin-top: 4px; + background: rgb(0 0 0 / 70.5%); +} + +#menuapi.menuapi--overflow .menuapi-stats { + margin-top: calc((60px / var(--menuapi-aspect)) - 26px); +} + +#menuapi.menuapi--no-desc:not(.menuapi--overflow) .menuapi-stats { + margin-top: 8px; +} + +.menuapi-stat { + position: relative; + height: 30px; +} + +.menuapi-stat__label { + position: absolute; + top: 2px; + left: 10px; + padding-left: var(--menuapi-text-inset); + line-height: 26.5px; + white-space: nowrap; +} + +.menuapi-stat__track { + position: absolute; + top: 10px; + right: 10px; + width: 250px; + height: 10px; + background: rgb(100 100 100 / 70.5%); +} + +.menuapi-stat__upgrade, +.menuapi-stat__value { + position: absolute; + top: 0; + left: 0; + height: 100%; +} + +.menuapi-stat__upgrade { + background: rgb(93 182 229); +} + +.menuapi-stat__upgrade--reduced { + background: rgb(224 50 50); +} + +.menuapi-stat__value { + background: #fff; +} diff --git a/MenuAPI/ui/menuapi.js b/MenuAPI/ui/menuapi.js new file mode 100644 index 0000000..c8160f6 --- /dev/null +++ b/MenuAPI/ui/menuapi.js @@ -0,0 +1,500 @@ +"use strict"; + +(() => { + const root = document.getElementById("menuapi"); + + if (!root) { + return; + } + + root.hidden = true; + + function spriteUrl(dict, name) { + return typeof window.MENUAPI_SPRITE_URL === "function" + ? window.MENUAPI_SPRITE_URL(dict, name) + : `https://nui-img/${dict}/${name}`; + } + + // Probed through an Image first: a CSS background that failed is never retried, and the url + // does not change when the dictionary finally streams in. + const sprites = new Map(); + const SPRITE_TRIES = 5; + const SPRITE_RETRY_MS = 200; + + function spriteReady(url) { + const state = sprites.get(url); + + if (state) { + return state.ok; + } + + const next = { ok: false, tries: 0 }; + + sprites.set(url, next); + loadSprite(url, next); + + return false; + } + + function loadSprite(url, state) { + const probe = new Image(); + + probe.onload = () => { + state.ok = true; + showSprite(url); + }; + + probe.onerror = () => { + state.tries += 1; + + if (state.tries >= SPRITE_TRIES) { + console.warn(`[menuapi] sprite never arrived: ${url}`); + + return; + } + + setTimeout(() => loadSprite(url, state), state.tries * SPRITE_RETRY_MS); + }; + + probe.src = url; + } + + function showSprite(url) { + for (const node of root.querySelectorAll(".menuapi-icon")) { + if (node.dataset.sprite === url) { + node.style.setProperty("--sprite", `url("${url}")`); + } + } + + if (bannerUrl === url) { + headerBg.src = url; + headerBg.hidden = false; + } + } + + function rescale() { + root.style.setProperty("--menuapi-scale", window.innerHeight / 1080); + root.style.setProperty("--menuapi-aspect", window.innerWidth / window.innerHeight); + } + + rescale(); + window.addEventListener("resize", rescale); + + let glare = null; + let glareRunning = false; + + function setGlare(wanted) { + if (wanted && !glare && typeof GtaMenuGlare !== "undefined") { + glare = new GtaMenuGlare(headerGlare, {}); + glare.load().then(() => { + if (glareRunning) { + glare.start(); + } + }).catch(() => { + glare = null; + }); + } + + if (!glare) { + headerGlare.hidden = !wanted; + + return; + } + + headerGlare.hidden = !wanted; + + if (wanted === glareRunning) { + return; + } + + glareRunning = wanted; + + if (wanted) { + glare.open(); + glare.start(); + } else { + glare.stop(); + } + } + + const header = element("div", "menuapi-header"); + const headerBg = element("img", "menuapi-header__bg"); + const headerGlare = element("canvas", "menuapi-header__glare"); + const headerTitle = element("div", "menuapi-header__title"); + const subtitle = element("div", "menuapi-subtitle"); + const subtitleText = element("span", "menuapi-subtitle__text"); + const subtitleCounter = element("span", "menuapi-subtitle__counter"); + const rows = element("div", "menuapi-rows"); + const overflow = element("div", "menuapi-overflow"); + const description = element("div", "menuapi-desc"); + const stats = element("div", "menuapi-stats"); + const panel = element("div", "menuapi-panel"); + + headerBg.alt = ""; + header.append(headerBg, headerGlare, headerTitle); + + let bannerUrl = null; + subtitle.append(subtitleText, subtitleCounter); + overflow.append(text("↑"), text("↓")); + root.append(header, subtitle, rows, overflow, description, panel, stats); + + function element(tag, className) { + const node = document.createElement(tag); + + if (className) { + node.className = className; + } + + return node; + } + + function text(value) { + const node = document.createElement("span"); + + node.textContent = value; + + return node; + } + + const TOKENS = { + r: "#e03232", + g: "#42a05b", + b: "#3f9dd4", + y: "#f0c419", + o: "#e8910e", + p: "#9b59b6", + w: "#ffffff", + h: null, + n: null, + s: null + }; + + function markup(value) { + const fragment = document.createDocumentFragment(); + + if (!value) { + return fragment; + } + + let colour = null; + + for (const part of String(value).split("~")) { + if (part === "") { + continue; + } + + if (part.length <= 24 && !part.includes(" ") && Object.hasOwn(TOKENS, part.toLowerCase())) { + colour = TOKENS[part.toLowerCase()]; + + continue; + } + + if (part.startsWith("HUD_COLOUR_")) { + colour = null; + + continue; + } + + const node = document.createElement("span"); + + node.textContent = part; + + if (colour) { + node.style.color = colour; + } + + fragment.append(node); + } + + return fragment; + } + + function icon(spec, className) { + const node = element("span", className ? "menuapi-icon " + className : "menuapi-icon"); + + const url = spriteUrl(spec.dict, spec.name); + + node.style.width = `${spec.size}px`; + node.style.height = `${spec.size}px`; + node.style.setProperty("--tint", `rgb(${spec.r} ${spec.g} ${spec.b})`); + node.dataset.sprite = url; + + if (spriteReady(url)) { + node.style.setProperty("--sprite", `url("${url}")`); + } + + return node; + } + + function renderHeader(data) { + if (!data) { + header.hidden = true; + setGlare(false); + + return; + } + + header.hidden = false; + + bannerUrl = data.texture ? spriteUrl(data.texture.dict, data.texture.name) : null; + headerBg.hidden = !bannerUrl || !spriteReady(bannerUrl); + + if (!headerBg.hidden) { + headerBg.src = bannerUrl; + } + + headerTitle.dataset.align = data.titleAlign; + headerTitle.dataset.font = data.font; + headerTitle.replaceChildren(markup(data.title)); + + setGlare(!!data.glare); + } + + function renderSubtitle(data) { + subtitle.classList.toggle("menuapi-subtitle--freemode", !!data.freemode); + + if (data.colour) { + root.style.setProperty("--menuapi-freemode", data.colour); + } + subtitleText.replaceChildren(markup(data.text)); + subtitleCounter.replaceChildren(markup(data.counter)); + } + + function renderRow(data) { + const node = element("div", "menuapi-row"); + + node.classList.toggle("menuapi-row--selected", data.selected); + node.classList.toggle("menuapi-row--disabled", !data.enabled); + + node.classList.toggle("menuapi-row--icon-left", !!data.leftIcon); + node.classList.toggle("menuapi-row--icon-right", !!data.rightIcon || !!data.checkbox); + + node.classList.toggle("menuapi-row--slider-icons", !!(data.slider?.sliderLeftIcon && data.rightIcon)); + + if (data.kind === "separator") { + node.classList.add("menuapi-row--separator"); + node.append(text(data.arrows ? `↓ ${data.text ?? ""} ↓` : data.text ?? "")); + + return node; + } + + if (data.leftIcon) { + node.append(icon(data.leftIcon, "menuapi-icon--left")); + } + + const label = element("span", "menuapi-row__text"); + + label.append(markup(data.text)); + node.append(label); + + if (data.label) { + const value = element("span", "menuapi-row__label"); + + value.append(markup(data.label)); + node.append(value); + } + + if (data.slider) { + if (data.slider.sliderLeftIcon) { + node.append(icon(data.slider.sliderLeftIcon, "menuapi-icon--slider")); + } + + node.append(renderSlider(data.slider)); + } + + if (data.checkbox) { + node.append(icon({ + dict: data.checkbox.dict, + name: data.checkbox.name, + size: data.checkbox.size, + r: data.checkbox.shade, + g: data.checkbox.shade, + b: data.checkbox.shade + }, "menuapi-icon--checkbox")); + } + + if (data.rightIcon) { + node.append(icon(data.rightIcon, "menuapi-icon--right")); + } + + return node; + } + + function renderSlider(data) { + const node = element("span", "menuapi-slider"); + const bar = element("span", "menuapi-slider__bar"); + const span = Math.max(1, data.max - data.min); + + node.style.backgroundColor = data.background; + bar.style.backgroundColor = data.bar; + + bar.style.left = `${((data.position - data.min) / span) * 50}%`; + + node.append(bar); + + if (data.divider) { + node.append(element("span", "menuapi-slider__divider")); + } + + return node; + } + + let colours = null; + + let palette = null; + + let paletteIndex = 0; + + function renderPanel(data) { + if (!data) { + panel.hidden = true; + + return; + } + + if (!colours && typeof GtaColourList !== "undefined") { + colours = new GtaColourList(panel, { arrows: false }); + applyPalette(); + } + + if (!colours) { + panel.hidden = true; + + return; + } + + panel.hidden = false; + + colours.SET_TITLE(data.title, data.name ?? "", data.opacity ?? -1); + colours.SHOW_OPACITY(data.opacity !== null && data.opacity !== undefined, true); + + panel.classList.toggle("menuapi-panel--no-colours", !data.colours); + + if (data.colours) { + paletteIndex = data.index; + colours.SET_HIGHLIGHT(paletteIndex); + } + } + + function applyPalette() { + if (!colours || !palette) { + return; + } + + colours.SET_DATA_SLOT_EMPTY(); + + palette.forEach((rgb, i) => colours.SET_DATA_SLOT(i, rgb[0], rgb[1], rgb[2])); + + colours.DISPLAY_VIEW(); + + colours.SET_HIGHLIGHT(paletteIndex); + } + + function applyText(data) { + if (!data) { + return; + } + + root.style.setProperty("--menuapi-text-size", data.size); + root.style.setProperty("--menuapi-text-rgb", `${data.brightness} ${data.brightness} ${data.brightness}`); + root.dataset.textWeight = data.weight; + } + + function renderStats(data) { + if (!data) { + stats.hidden = true; + + return; + } + + stats.hidden = false; + stats.replaceChildren(...data.map(entry => { + const node = element("div", "menuapi-stat"); + const label = element("span", "menuapi-stat__label"); + const track = element("div", "menuapi-stat__track"); + const upgrade = element("div", "menuapi-stat__upgrade"); + const value = element("div", "menuapi-stat__value"); + + const reduced = entry.upgrade < entry.value; + + upgrade.classList.toggle("menuapi-stat__upgrade--reduced", reduced); + upgrade.style.width = `${Math.max(entry.upgrade, entry.value) * 100}%`; + value.style.width = `${Math.min(entry.upgrade, entry.value) * 100}%`; + + label.textContent = entry.label; + + track.append(upgrade, value); + node.append(label, track); + + return node; + })); + } + + function render(data) { + if (!data.visible) { + root.hidden = true; + setGlare(false); + + for (const [url, state] of sprites) { + if (!state.ok) { + sprites.delete(url); + } + } + + return; + } + + root.hidden = false; + root.dataset.align = data.align; + + root.style.setProperty("--menuapi-origin-x", data.origin.x); + root.style.setProperty("--menuapi-origin-y", data.origin.y); + + renderHeader(data.header); + renderSubtitle(data.subtitle); + + rows.replaceChildren(...data.rows.map(renderRow)); + + overflow.hidden = !data.overflow; + + root.classList.toggle("menuapi--overflow", !!data.overflow); + + description.hidden = !data.description; + + root.classList.toggle("menuapi--no-desc", !data.description); + description.replaceChildren(markup(data.description)); + + renderPanel(data.panel); + renderStats(data.stats); + + root.style.setProperty("--menuapi-panel-bg", data.panelBackground); + root.style.setProperty("--menuapi-panel-accent", data.panelAccent); + + applyText(data.text); + } + + window.addEventListener("message", event => { + let data = event.data; + + if (typeof data === "string") { + try { + data = JSON.parse(data); + } catch { + return; + } + } + + if (!data || typeof data !== "object") { + return; + } + + if (data.type === "menuapi") { + render(data); + } else if (data.type === "menuapi:glare" && glare) { + glare.setHeading(data.heading || 0, false); + } else if (data.type === "menuapi:palette") { + palette = data.colours; + applyPalette(); + } + }); +})(); diff --git a/TestMenu/ExampleMenu.cs b/TestMenu/ExampleMenu.cs index cc50cf2..23986e6 100644 --- a/TestMenu/ExampleMenu.cs +++ b/TestMenu/ExampleMenu.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; - -using CitizenFX.FiveM.Client; +using CitizenFX.FiveM.Client; using CitizenFX.FiveM.Shared.Script; using MenuAPI; @@ -12,8 +9,12 @@ namespace TestMenu; public class ExampleMenu : IScript { + private static MenuCheckboxItem? renderModeBox; + public ExampleMenu() { + RegisterRenderModeToggle(); + // Setting the menu alignment to be right aligned. This can be changed at any time and it'll update instantly. // To test this, checkout one of the checkbox items in this example menu. Clicking it will toggle the menu alignment. MenuController.MenuAlignment = MenuController.MenuAlignmentOption.Right; @@ -673,6 +674,146 @@ void SortProduce(Comparison compareProduce) }; menu.AddMenuItem(separators); MenuController.BindMenuItem(menu, menu10, separators); + Menu menu11 = new Menu("Render Mode", "Native vs NUI"); + + renderModeBox = new MenuCheckboxItem( + "Draw with NUI", + "Off draws the menu with the game's own natives, on draws it as a web page. F7 does the same thing without moving your selection, which is what you want when comparing the two.", + MenuController.RenderMode == MenuRenderMode.Nui + ); + menu11.AddMenuItem(renderModeBox); + + menu11.AddMenuItem(new MenuItem("A normal row", "Something to look at while you flick between the two.")); + menu11.AddMenuItem(new MenuCheckboxItem("A checkbox", "Ticked, so the box sprite is on screen too.", true)); + menu11.AddMenuItem(new MenuItem("A row with an icon", "So an icon's tint can be compared as well.") { LeftIcon = MenuItem.Icon.STAR }); + menu11.AddMenuItem(new MenuItem("A disabled row", "Greyed out, so the disabled colour is on screen too.") { Enabled = false }); + + menu11.OnCheckboxChange += (_menu, _item, _index, _checked) => + { + if (_item == renderModeBox) + { + MenuController.RenderMode = _checked ? MenuRenderMode.Nui : MenuRenderMode.Native; + } + }; + + MenuController.AddSubmenu(menu, menu11); + MenuItem renderMode = new MenuItem("Render mode", "Switch between drawing the menu with game natives and drawing it as a web page.") + { + Label = "→→→" + }; + menu.AddMenuItem(renderMode); + MenuController.BindMenuItem(menu, menu11, renderMode); + + Menu menu12 = new Menu("NUI Text", "Weight & size"); + + List weightNames = new List() { "Default", "Geometric precision", "Supersampled" }; + + MenuListItem weightMode = new MenuListItem( + "Stem treatment", + weightNames, + 0, + "How the page is asked for a thinner stem. Flip to native with F7 after each one and see which matches." + ); + menu12.AddMenuItem(weightMode); + + MenuDynamicListItem textSize = new MenuDynamicListItem( + "Text size", + NuiTuning.TextSize.ToString("0.##"), + (item, left) => + { + NuiTuning.TextSize += left ? -0.5f : 0.5f; + + return NuiTuning.TextSize.ToString("0.##"); + }, + "Pixels at 1080p. The rows measured the same height as the game's at the value it starts on." + ); + menu12.AddMenuItem(textSize); + + MenuDynamicListItem textBrightness = new MenuDynamicListItem( + "Text brightness", + NuiTuning.TextBrightness.ToString(), + (item, left) => + { + NuiTuning.TextBrightness += left ? -5 : 5; + + return NuiTuning.TextBrightness.ToString(); + }, + "0 to 255. The game's own white menu text peaks at the value this starts on, not at 255." + ); + menu12.AddMenuItem(textBrightness); + + menu12.AddMenuItem(new MenuItem("Print the values", "Writes what everything is set to into the console, so it can be pasted back.")); + menu12.AddMenuItem(new MenuItem("Reset", "Puts every knob back to what the renderer ships with.")); + + menu12.AddMenuItem(new SeparatorMenuItem("Something to look at")); + menu12.AddMenuItem(new MenuItem("Handgun ammunition", "A row with descenders and round letters in it.")); + menu12.AddMenuItem(new MenuItem("Vehicles Menu", "The same words the weight was measured against.")); + menu12.AddMenuItem(new MenuItem("A disabled row") { Enabled = false }); + + menu12.OnListIndexChange += (_menu, _item, _oldIndex, _newIndex, _itemIndex) => + { + if (_item == weightMode) + { + NuiTuning.TextWeight = (NuiTuning.TextWeightMode)_newIndex; + } + }; + + menu12.OnItemSelect += (_menu, _item, _index) => + { + if (_item.Text == "Print the values") + { + API.Log.Info($"[TestMenu] NUI text: {NuiTuning.Describe()}"); + } + else if (_item.Text == "Reset") + { + NuiTuning.Reset(); + + weightMode.ListIndex = 0; + textSize.CurrentItem = NuiTuning.TextSize.ToString("0.##"); + textBrightness.CurrentItem = NuiTuning.TextBrightness.ToString(); + } + }; + + MenuController.AddSubmenu(menu, menu12); + MenuItem nuiText = new MenuItem("NUI text", "Nudge the size, the brightness and the stem treatment of the NUI renderer's text, live.") + { + Label = "\u2192\u2192\u2192" + }; + menu.AddMenuItem(nuiText); + MenuController.BindMenuItem(menu, menu12, nuiText); + + Menu menu13 = new Menu("NUI Sprites", "Icons & tints") + { + HeaderTexture = new KeyValuePair("shopui_title_barber", "shopui_title_barber") + }; + + menu13.AddMenuItem(new SeparatorMenuItem("White with an alpha channel")); + menu13.AddMenuItem(new MenuItem("Lock", "Scroll onto me. Selected turns the tint black, so a lost alpha channel shows up as a filled square instead of a lock.") { LeftIcon = MenuItem.Icon.LOCK }); + menu13.AddMenuItem(new MenuItem("Tick", "The same again at a different size.") { LeftIcon = MenuItem.Icon.TICK }); + + menu13.AddMenuItem(new SeparatorMenuItem("Carries its own colours")); + menu13.AddMenuItem(new MenuItem("Star", "Gold. If it comes out flat white then the texture's own colours are being lost.") { LeftIcon = MenuItem.Icon.STAR }); + menu13.AddMenuItem(new MenuItem("Gold medal", "Another one with colour in the texture rather than in the tint.") { LeftIcon = MenuItem.Icon.MEDAL_GOLD }); + menu13.AddMenuItem(new MenuItem("Pegassi badge", "Fine detail, so it shows any loss of sharpness.") { LeftIcon = MenuItem.Icon.BRAND_PEGASSI }); + + menu13.AddMenuItem(new SeparatorMenuItem("Tinted by the menu")); + menu13.AddMenuItem(new MenuItem("Blue globe", "White texture, blue tint. Wrong alpha turns this into a blue block.") { LeftIcon = MenuItem.Icon.GLOBE_BLUE }); + menu13.AddMenuItem(new MenuItem("Green globe") { LeftIcon = MenuItem.Icon.GLOBE_GREEN }); + menu13.AddMenuItem(new MenuItem("Disabled, greyed out", "The tint drops to 109 grey.") { LeftIcon = MenuItem.Icon.STAR, Enabled = false }); + + menu13.AddMenuItem(new SeparatorMenuItem("Other dictionaries")); + menu13.AddMenuItem(new MenuItem("Male", "From mpleaderboard.") { LeftIcon = MenuItem.Icon.MALE }); + menu13.AddMenuItem(new MenuItem("Info", "From shared.") { LeftIcon = MenuItem.Icon.INFO }); + menu13.AddMenuItem(new MenuCheckboxItem("A ticked checkbox", "Its box has the tick drawn into the texture rather than the alpha, so it is the one most likely to break.", true)); + + MenuController.AddSubmenu(menu, menu13); + MenuItem nuiSprites = new MenuItem("NUI sprites", "Every icon type in one place, plus a banner from a dictionary MenuAPI does not otherwise touch.") + { + Label = "\u2192\u2192\u2192" + }; + menu.AddMenuItem(nuiSprites); + MenuController.BindMenuItem(menu, menu13, nuiSprites); + /*-------------- Event handlers --------------*/ @@ -755,4 +896,25 @@ Event handlers API.Log.Info($"OnDynamicListItemSelect: [{_menu}, {_dynamicListItem}, {_currentItem}]"); }; } -} \ No newline at end of file + + private static void RegisterRenderModeToggle() + { + const string command = "menuapi_testmenu_rendermode"; + + CitizenFX.FiveM.Shared.SharedAPI.Commands.RegisterCommand(command, false, new Action(() => + { + MenuController.RenderMode = MenuController.RenderMode == MenuRenderMode.Native + ? MenuRenderMode.Nui + : MenuRenderMode.Native; + + if (renderModeBox != null) + { + renderModeBox.Checked = MenuController.RenderMode == MenuRenderMode.Nui; + } + + API.Log.Info($"[TestMenu] render mode is now {MenuController.RenderMode}."); + })); + + RegisterKeyMapping(command, "Toggle NUI menu rendering", "keyboard", "F7"); + } +} diff --git a/TestMenu/TestMenu.csproj b/TestMenu/TestMenu.csproj index 9ae2021..f5b3d0c 100644 --- a/TestMenu/TestMenu.csproj +++ b/TestMenu/TestMenu.csproj @@ -27,6 +27,7 @@ + + + + +

+ + + + + + + diff --git a/docs/enhanced/src/content/docs/changelog.md b/docs/enhanced/src/content/docs/changelog.md index f9e864c..1f8a081 100644 --- a/docs/enhanced/src/content/docs/changelog.md +++ b/docs/enhanced/src/content/docs/changelog.md @@ -10,6 +10,34 @@ title: "Changelog" These are the changes in MenuAPI for FiveM Enhanced. If you are moving a resource over from the older (v3, non Enhanced) MenuAPI, this is the list of things you will have to deal with along the way. ::: +### The menu is drawn without describing it sixty times a second + +The NUI renderer used to build a full description of the open menu on every single frame, purely to +find out whether anything about it had changed. Nothing usually had, so almost all of that work, and +the few kilobytes of garbage that came with it, was thrown straight away again. + +Now every property that is on screen says so when you write to it, and the description is only built +when something actually said it changed. A menu nobody is touching costs nothing at all beyond the +draw itself. + +**Things you have to change in your own code:** + +- `MenuListItem.ListItems` is now a `MenuItemList` instead of a `List`. It is used the same + way, and a `List` can still be assigned straight to it, so most code needs no change. The + two things that do: a variable typed `List values = item.ListItems;` has to become + `MenuItemList` (or call `.ToList()`), and the list you pass to the constructor is now **copied**, + so change the values through `item.ListItems` rather than through your own copy of the list. See + the [Migration guide](migration/). + +**Things that just behave differently now:** + +- If you build part of a row's text from something MenuAPI does not own, such as a label you resolve + yourself, call `MenuController.RefreshNui()` when it changes. Everything MenuAPI owns already says + so on its own. +- The instructional buttons bar fills its slots a few times a second instead of every frame. Swapping + between keyboard and controller still changes the icons, it just no longer costs a dozen scaleform + calls per frame to notice. + ### Menus can be split into groups with a heading There is a new item type, [SeparatorMenuItem](reference/menuitems/separatormenuitem/). It is a heading that labels the rows underneath it, so a long menu can be broken into readable groups without pushing anything into a submenu. diff --git a/docs/enhanced/src/content/docs/reference/menuitems/menulistitem.md b/docs/enhanced/src/content/docs/reference/menuitems/menulistitem.md index 029e61c..24a5012 100644 --- a/docs/enhanced/src/content/docs/reference/menuitems/menulistitem.md +++ b/docs/enhanced/src/content/docs/reference/menuitems/menulistitem.md @@ -63,13 +63,25 @@ The list wraps around: pressing right on the last value goes back to the first o #### Changing the values later -`ListItems` is a normal `List`, so you can change it at any time. Remember to keep `ListIndex` in range when you do. +`ListItems` is a `MenuItemList`, which is used exactly like a `List`: add to it, index into +it, loop over it, ask it for its `Count`. The difference is that it tells the menu when it changes, +so the row redraws as soon as you touch it. Remember to keep `ListIndex` in range when you do. ```cs +item.ListItems.Add("Option 4"); +item.ListItems.RemoveAt(0); + +// Or replace the lot. A List can be assigned straight to it. item.ListItems = newValues; item.ListIndex = 0; ``` +:::caution +The list you hand to the constructor is **copied**, so changing your own copy afterwards does not +reach the item. Change it through `item.ListItems` instead. See the [Migration guide](/migration/) +if you are moving a resource over from the older MenuAPI. +::: + :::caution Never leave a list item with an empty `ListItems` list. If the list is empty, MenuAPI inserts a single `"N/A"` value to keep the menu from freezing, and the item's value will read `N/A`. ::: @@ -119,7 +131,7 @@ The MenuItem properties **RightIcon** and **Label** are not available for MenuLi |Property|Type|Default value|Description|Optional| |---|---|---|---|---| |ListIndex|int|0|The currently selected list index.|**No**| -|ListItems|List<string>|-|A list holding a collection of strings which can be selected through this menu list item. At least one string must be present in this list.|**No**| +|ListItems|MenuItemList|-|A list holding a collection of strings which can be selected through this menu list item. At least one string must be present in this list. Used exactly like a `List`, and one can be assigned straight to it.|**No**| |HideArrowsWhenNotSelected|boolean|false|Hides the left & right arrows when the menu list item is not currently highlighted.|Yes| |ShowOpacityPanel|boolean|false|Shows the Opacity Panel.|Yes| |ShowColorPanel|boolean|false|Shows the Color Panel.|Yes| From 9e2d8649c302ffc8eb86d6594f1a31b83a8a1bd0 Mon Sep 17 00:00:00 2001 From: MrPizza <5529325+sir-lord-pizza@users.noreply.github.com> Date: Wed, 26 Aug 2026 16:25:56 +0200 Subject: [PATCH 06/10] feat(listitem): give the opacity panel a value of its own --- MenuAPI/Menu.cs | 2 +- MenuAPI/items/MenuListItem.cs | 10 ++++++++++ TestMenu/ExampleMenu.cs | 21 +++++++++++++++++++++ 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/MenuAPI/Menu.cs b/MenuAPI/Menu.cs index 23c8853..41edc36 100644 --- a/MenuAPI/Menu.cs +++ b/MenuAPI/Menu.cs @@ -2096,7 +2096,7 @@ private void DrawColorAndOpacityPanel(float descriptionYOffset) Native.BeginScaleformMovieMethod(OpacityPanelScaleform, "SET_TITLE"); Native.PushScaleformMovieMethodParameterString("Opacity"); Native.PushScaleformMovieMethodParameterString(""); - Native.ScaleformMovieMethodAddParamInt(listItem.ListIndex * 10); // opacity percent + Native.ScaleformMovieMethodAddParamInt(listItem.ResolvedOpacityPercent); Native.EndScaleformMovieMethod(); float width = Width / MenuLayout.ScreenWidth; diff --git a/MenuAPI/items/MenuListItem.cs b/MenuAPI/items/MenuListItem.cs index fef707f..b923a65 100644 --- a/MenuAPI/items/MenuListItem.cs +++ b/MenuAPI/items/MenuListItem.cs @@ -45,6 +45,16 @@ public ColorPanelType ColorPanelColorType private bool _showColorPanel = false; private ColorPanelType _colorPanelColorType = ColorPanelType.Hair; + private int opacityPercent; + + public int OpacityPercent + { + get => opacityPercent; + set => MenuNui.Change(ref opacityPercent, Math.Clamp(value, 0, 100)); + } + + internal int ResolvedOpacityPercent => + ShowColorPanel ? OpacityPercent : Math.Clamp(ListIndex * 10, 0, 100); public enum ColorPanelType { Hair, diff --git a/TestMenu/ExampleMenu.cs b/TestMenu/ExampleMenu.cs index 23986e6..4c11851 100644 --- a/TestMenu/ExampleMenu.cs +++ b/TestMenu/ExampleMenu.cs @@ -146,9 +146,30 @@ string ChangeCallback(MenuDynamicListItem item, bool left) ShowOpacityPanel = true }; + List combinedList = new List(); + for (var i = 0; i < 64; i++) + { + combinedList.Add($"Color #{i}"); + } + MenuListItem combined = new MenuListItem("Color + Opacity", combinedList, 0, "Left and right move the colour. The bar above it is its own value, driven by the Opacity Panel row.") + { + ShowColorPanel = true, + ShowOpacityPanel = true, + OpacityPercent = 0 + }; + + menu.OnListIndexChange += (_menu, _item, _oldIndex, _newIndex, _itemIndex) => + { + if (_item == opacity) + { + combined.OpacityPercent = _newIndex * 10; + } + }; + menu.AddMenuItem(hairColors); menu.AddMenuItem(makeupColors); menu.AddMenuItem(opacity); + menu.AddMenuItem(combined); // Normal List normalList = new List() { "Item #1", "Item #2", "Item #3" }; MenuListItem normalListItem = new MenuListItem( From 0e9ff4b74654a15b59724dcf241c6b5fa00a9218 Mon Sep 17 00:00:00 2001 From: MrPizza <5529325+sir-lord-pizza@users.noreply.github.com> Date: Wed, 26 Aug 2026 16:26:02 +0200 Subject: [PATCH 07/10] chore(tools): add the dds converter and the NUI preview harness --- tools/dds-to-png.mjs | 281 ++++++++++++++++++++++++++++++++++++++ tools/preview/index.html | 59 ++++++++ tools/preview/serve.mjs | 56 ++++++++ tools/preview/snapshot.js | 96 +++++++++++++ 4 files changed, 492 insertions(+) create mode 100644 tools/dds-to-png.mjs create mode 100644 tools/preview/index.html create mode 100644 tools/preview/serve.mjs create mode 100644 tools/preview/snapshot.js diff --git a/tools/dds-to-png.mjs b/tools/dds-to-png.mjs new file mode 100644 index 0000000..0557902 --- /dev/null +++ b/tools/dds-to-png.mjs @@ -0,0 +1,281 @@ +import fs from "node:fs"; +import path from "node:path"; +import zlib from "node:zlib"; + +const DDS_MAGIC = 0x20534444; +const HEADER_SIZE = 128; + +const PF_FLAGS = 80; +const PF_FOURCC = 84; +const PF_RGB_BIT_COUNT = 88; +const PF_FLAG_FOURCC = 0x4; + +function decode(buffer) { + if (buffer.readUInt32LE(0) !== DDS_MAGIC) { + throw new Error("not a DDS file"); + } + + const height = buffer.readUInt32LE(12); + const width = buffer.readUInt32LE(16); + const compressed = (buffer.readUInt32LE(PF_FLAGS) & PF_FLAG_FOURCC) !== 0; + const fourCC = buffer.toString("ascii", PF_FOURCC, PF_FOURCC + 4); + const pixels = Buffer.alloc(width * height * 4); + + if (!compressed) { + const bits = buffer.readUInt32LE(PF_RGB_BIT_COUNT); + + if (bits !== 32) { + throw new Error(`unsupported uncompressed depth ${bits}`); + } + + for (let i = 0; i < width * height; i++) { + const at = HEADER_SIZE + (i * 4); + + pixels[(i * 4) + 0] = buffer[at + 2]; + pixels[(i * 4) + 1] = buffer[at + 1]; + pixels[(i * 4) + 2] = buffer[at + 0]; + pixels[(i * 4) + 3] = buffer[at + 3]; + } + + return { width, height, pixels }; + } + + if (fourCC !== "DXT1" && fourCC !== "DXT3" && fourCC !== "DXT5") { + throw new Error(`unsupported format ${fourCC}`); + } + + const blockBytes = fourCC === "DXT1" ? 8 : 16; + const blocksWide = Math.max(1, Math.ceil(width / 4)); + const blocksHigh = Math.max(1, Math.ceil(height / 4)); + + let at = HEADER_SIZE; + + for (let by = 0; by < blocksHigh; by++) { + for (let bx = 0; bx < blocksWide; bx++) { + const block = buffer.subarray(at, at + blockBytes); + + at += blockBytes; + + const colour = fourCC === "DXT1" ? block : block.subarray(8); + const alpha = decodeAlpha(fourCC, block); + + writeColourBlock(pixels, width, height, bx, by, colour, alpha, fourCC === "DXT1"); + } + } + + return { width, height, pixels }; +} + +function decodeAlpha(fourCC, block) { + if (fourCC === "DXT1") { + return null; + } + + const out = new Uint8Array(16); + + if (fourCC === "DXT3") { + for (let i = 0; i < 16; i++) { + const nibble = (block[i >> 1] >> ((i & 1) * 4)) & 0xf; + + out[i] = (nibble * 255) / 15; + } + + return out; + } + + const a0 = block[0]; + const a1 = block[1]; + const ramp = new Uint8Array(8); + + ramp[0] = a0; + ramp[1] = a1; + + if (a0 > a1) { + for (let i = 2; i < 8; i++) { + ramp[i] = ((8 - i) * a0 + (i - 1) * a1) / 7; + } + } else { + for (let i = 2; i < 6; i++) { + ramp[i] = ((6 - i) * a0 + (i - 1) * a1) / 5; + } + + ramp[6] = 0; + ramp[7] = 255; + } + + for (let half = 0; half < 2; half++) { + const base = 2 + (half * 3); + const bits = block[base] | (block[base + 1] << 8) | (block[base + 2] << 16); + + for (let i = 0; i < 8; i++) { + out[(half * 8) + i] = ramp[(bits >> (i * 3)) & 0x7]; + } + } + + return out; +} + +function writeColourBlock(pixels, width, height, bx, by, colour, alpha, punchThrough) { + const c0 = colour.readUInt16LE(0); + const c1 = colour.readUInt16LE(2); + const bits = colour.readUInt32LE(4); + + const r = new Uint8Array(4); + const g = new Uint8Array(4); + const b = new Uint8Array(4); + const a = new Uint8Array(4).fill(255); + + unpack565(c0, r, g, b, 0); + unpack565(c1, r, g, b, 1); + + const opaque = !punchThrough || c0 > c1; + + if (opaque) { + for (const channel of [r, g, b]) { + channel[2] = ((2 * channel[0]) + channel[1]) / 3; + channel[3] = (channel[0] + (2 * channel[1])) / 3; + } + } else { + for (const channel of [r, g, b]) { + channel[2] = (channel[0] + channel[1]) / 2; + channel[3] = 0; + } + + a[3] = 0; + } + + for (let y = 0; y < 4; y++) { + for (let x = 0; x < 4; x++) { + const px = (bx * 4) + x; + const py = (by * 4) + y; + + if (px >= width || py >= height) { + continue; + } + + const i = (y * 4) + x; + const code = (bits >> (i * 2)) & 0x3; + const at = ((py * width) + px) * 4; + + pixels[at + 0] = r[code]; + pixels[at + 1] = g[code]; + pixels[at + 2] = b[code]; + pixels[at + 3] = alpha ? alpha[i] : a[code]; + } + } +} + +function unpack565(value, r, g, b, index) { + const r5 = (value >> 11) & 0x1f; + const g6 = (value >> 5) & 0x3f; + const b5 = value & 0x1f; + + r[index] = (r5 << 3) | (r5 >> 2); + g[index] = (g6 << 2) | (g6 >> 4); + b[index] = (b5 << 3) | (b5 >> 2); +} + +function encodePng({ width, height, pixels }) { + const raw = Buffer.alloc((width * 4 + 1) * height); + + for (let y = 0; y < height; y++) { + raw[y * (width * 4 + 1)] = 0; + pixels.copy(raw, (y * (width * 4 + 1)) + 1, y * width * 4, (y + 1) * width * 4); + } + + const ihdr = Buffer.alloc(13); + + ihdr.writeUInt32BE(width, 0); + ihdr.writeUInt32BE(height, 4); + ihdr[8] = 8; + ihdr[9] = 6; + ihdr[10] = 0; + ihdr[11] = 0; + ihdr[12] = 0; + + return Buffer.concat([ + Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]), + chunk("IHDR", ihdr), + chunk("IDAT", zlib.deflateSync(raw, { level: 9 })), + chunk("IEND", Buffer.alloc(0)), + ]); +} + +function chunk(type, data) { + const out = Buffer.alloc(data.length + 12); + + out.writeUInt32BE(data.length, 0); + out.write(type, 4, "ascii"); + data.copy(out, 8); + out.writeUInt32BE(crc32(out.subarray(4, out.length - 4)), out.length - 4); + + return out; +} + +const CRC_TABLE = (() => { + const table = new Uint32Array(256); + + for (let i = 0; i < 256; i++) { + let value = i; + + for (let bit = 0; bit < 8; bit++) { + value = value & 1 ? 0xedb88320 ^ (value >>> 1) : value >>> 1; + } + + table[i] = value >>> 0; + } + + return table; +})(); + +function crc32(buffer) { + let crc = 0xffffffff; + + for (const byte of buffer) { + crc = CRC_TABLE[(crc ^ byte) & 0xff] ^ (crc >>> 8); + } + + return (crc ^ 0xffffffff) >>> 0; +} + +const source = process.argv[2]; +const target = process.argv[3] ?? "MenuAPI/ui/sprites"; + +if (!source) { + console.error('usage: node tools/dds-to-png.mjs "" [output folder]'); + process.exit(1); +} + +let converted = 0; +let failed = 0; + +for (const entry of fs.readdirSync(source, { withFileTypes: true })) { + if (!entry.isDirectory()) { + continue; + } + + const from = path.join(source, entry.name); + const to = path.join(target, entry.name); + + fs.mkdirSync(to, { recursive: true }); + + for (const file of fs.readdirSync(from)) { + if (!file.toLowerCase().endsWith(".dds")) { + continue; + } + + const out = path.join(to, `${path.basename(file, path.extname(file))}.png`); + + try { + fs.writeFileSync(out, encodePng(decode(fs.readFileSync(path.join(from, file))))); + converted++; + } catch (error) { + console.error(` ${entry.name}/${file}: ${error.message}`); + failed++; + } + } + + console.log(`${entry.name}`); +} + +console.log(`\n${converted} converted, ${failed} failed, into ${target}`); diff --git a/tools/preview/index.html b/tools/preview/index.html new file mode 100644 index 0000000..f846289 --- /dev/null +++ b/tools/preview/index.html @@ -0,0 +1,59 @@ + + + + + + MenuAPI NUI preview + + + + + + + + + + + + + + + + + + + + diff --git a/tools/preview/serve.mjs b/tools/preview/serve.mjs new file mode 100644 index 0000000..67f0a37 --- /dev/null +++ b/tools/preview/serve.mjs @@ -0,0 +1,56 @@ +import fs from "node:fs"; +import http from "node:http"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const here = path.dirname(fileURLToPath(import.meta.url)); +const ui = path.resolve(here, "../../MenuAPI/ui"); +const port = Number(process.argv[2] ?? 8730); + +const TYPES = { + ".css": "text/css", + ".html": "text/html; charset=utf-8", + ".js": "text/javascript", + ".png": "image/png", + ".woff": "font/woff", + ".woff2": "font/woff2", +}; + +if (!fs.existsSync(ui)) { + console.error(`cannot find ${ui}`); + process.exit(1); +} + +function resolve(url) { + const clean = decodeURIComponent(url.split("?")[0]).replace(/^\/+/, "") || "index.html"; + + const target = clean.startsWith("menuapi/") + ? path.join(ui, clean.slice("menuapi/".length)) + : path.join(here, clean); + + const resolved = path.resolve(target); + + return resolved.startsWith(path.resolve(ui)) || resolved.startsWith(path.resolve(here)) + ? resolved + : null; +} + +http.createServer((request, response) => { + const file = resolve(request.url ?? "/"); + + if (!file || !fs.existsSync(file) || !fs.statSync(file).isFile()) { + response.writeHead(404).end("not found"); + + return; + } + + response.writeHead(200, { + "content-type": TYPES[path.extname(file).toLowerCase()] ?? "application/octet-stream", + "cache-control": "no-store", + }); + + response.end(fs.readFileSync(file)); +}).listen(port, () => { + console.log(`MenuAPI NUI preview on http://localhost:${port}`); + console.log(`serving menuapi/ from ${ui}`); +}); diff --git a/tools/preview/snapshot.js b/tools/preview/snapshot.js new file mode 100644 index 0000000..196739f --- /dev/null +++ b/tools/preview/snapshot.js @@ -0,0 +1,96 @@ +function sprite(dict, name, size, r = 255, g = 255, b = 255) { + return { dict, name, size, r, g, b }; +} + +function row(data) { + return { kind: "item", label: null, enabled: true, selected: false, leftIcon: null, rightIcon: null, ...data }; +} + +const SNAPSHOT = { + type: "menuapi", + visible: true, + align: "left", + + origin: { x: 0.0125, y: 0.0185 }, + + header: { + title: "Main Menu", + font: 1, + titleAlign: "center", + glare: true, + texture: { dict: "commonmenu", name: "interaction_bgd" } + }, + + subtitle: { + text: "Subtitle", + counter: "5 / 32", + freemode: true, + colour: "rgb(64 148 214)" + }, + + rows: [ + row({ + text: "Normal Button", + enabled: false, + leftIcon: sprite("commonmenu", "shop_tick_icon", 38, 109, 109, 109) + }), + row({ + kind: "slider", + text: "Slider", + slider: { min: 0, max: 10, position: 5, divider: false, background: "#185d97", bar: "#35a5df", sliderLeftIcon: null } + }), + row({ + kind: "slider", + text: "Slider + Bar", + slider: { min: 0, max: 10, position: 5, divider: true, background: "#1e7a3c", bar: "#49e96f", sliderLeftIcon: null } + }), + row({ + kind: "slider", + text: "Slider + Bar + Icons", + rightIcon: sprite("mpleaderboard", "leaderboard_female_icon", 38), + slider: { + min: 0, max: 10, position: 5, divider: true, + background: "#6b1414", bar: "#e03232", + sliderLeftIcon: sprite("mpleaderboard", "leaderboard_male_icon", 38) + } + }), + row({ + kind: "checkbox", + text: "Checkbox - Style 1 (click me!)", + selected: true, + checkbox: { dict: "commonmenu", name: "shop_box_blankb", size: 45, shade: 255 } + }), + row({ + kind: "checkbox", + text: "Checkbox - Style 2", + checkbox: { dict: "commonmenu", name: "shop_box_tick", size: 45, shade: 255 } + }), + row({ + kind: "checkbox", + text: "Checkbox (unchecked + locked)", + enabled: false, + leftIcon: sprite("commonmenu", "shop_lock", 38, 109, 109, 109), + checkbox: { dict: "commonmenu", name: "shop_box_blank", size: 45, shade: 109 } + }), + row({ text: "Dynamic list item.", label: "~s~← -7 ~s~→" }), + row({ text: "Hair Color", label: "~s~← Color #55 ~s~→" }), + row({ text: "Makeup Color", label: "~s~← Color #59 ~s~→" }) + ], + + panelBackground: "rgb(0 0 0 / 73%)", + panelAccent: "240, 240, 240", + + text: { size: 21, brightness: 225, weight: "default" }, + + panel: { + colours: true, + opacity: 40, + index: 12, + title: "Opacity", + name: "Colour 13 of 64" + }, + + overflow: true, + description: "This checkbox can toggle the menu position! Try it out.", + stats: null +}; From 96c30550a7f7901c1a61407375893b1f901dae0d Mon Sep 17 00:00:00 2001 From: MrPizza <5529325+sir-lord-pizza@users.noreply.github.com> Date: Wed, 26 Aug 2026 16:26:02 +0200 Subject: [PATCH 08/10] docs(enhanced): add a migration guide --- docs/enhanced/astro.config.mjs | 1 + docs/enhanced/src/content/docs/migration.md | 105 ++++++++++++++++++++ 2 files changed, 106 insertions(+) create mode 100644 docs/enhanced/src/content/docs/migration.md diff --git a/docs/enhanced/astro.config.mjs b/docs/enhanced/astro.config.mjs index b23f44c..d9482fd 100644 --- a/docs/enhanced/astro.config.mjs +++ b/docs/enhanced/astro.config.mjs @@ -47,6 +47,7 @@ export default defineConfig({ sidebar: [ { label: 'Basic Info', link: '/' }, { label: 'Setup', link: '/setup/' }, + { label: 'Migration guide', link: '/migration/' }, { label: 'API Reference', items: [ diff --git a/docs/enhanced/src/content/docs/migration.md b/docs/enhanced/src/content/docs/migration.md new file mode 100644 index 0000000..81f01d3 --- /dev/null +++ b/docs/enhanced/src/content/docs/migration.md @@ -0,0 +1,105 @@ +--- +title: "Migration guide" +--- + +## Migration guide + +Moving a resource from the older (v3, non Enhanced) MenuAPI to the Enhanced one. This page is just +the things that stop compiling or stop behaving the way they used to, with a before and after for +each. The [Changelog](changelog/) explains why each of them changed. + +Everything not listed here still works the way it always did. + +---- + +### The menu toggle key is the player's choice now + +`MenuController.MenuToggleKey` and `MenuController.MenuToggleKeyIsValid` are gone. You can still pick +the key players start with, but they can rebind it in **Settings, Key Bindings**. + +```cs +// Before +MenuController.MenuToggleKey = Control.SelectCharacterMichael; + +// After (a key name, and only for players who have never rebound it) +MenuController.MenuToggleKeyDefault = "M"; +``` + +---- + +### Select and back instructional buttons moved + +`Menu.InstructionalButtons` used to come with a select and a back entry already in it. It now starts +empty, and those two live on the menu itself so they can follow the player's own key binding. + +```cs +// Before +menu.InstructionalButtons[Control.FrontendAccept] = "Choose"; +menu.InstructionalButtons.Remove(Control.FrontendCancel); + +// After +menu.SelectButtonText = "Choose"; +menu.ShowBackInstructionalButton = false; +``` + +Your own extra buttons still go in `Menu.InstructionalButtons` exactly as before. + +---- + +### List item values are a MenuItemList + +`MenuListItem.ListItems` used to be a plain `List` that you handed over and kept a reference +to. It is now a [MenuItemList](reference/menuitems/menulistitem/#changing-the-values-later), which +works the same way but tells the menu when it changes, so the row redraws the moment you add or +remove a value. + +You still build one from a `List`, and the list you pass in is **copied**, so changing your +own copy afterwards no longer reaches the item. Change it through the item instead. + +```cs +// Before +List values = new List { "A", "B" }; +MenuListItem item = new MenuListItem("Item", values, 0); + +values.Add("C"); // the item picked this up +List current = item.ListItems; + +// After +MenuListItem item = new MenuListItem("Item", new List { "A", "B" }, 0); + +item.ListItems.Add("C"); // change it through the item +MenuItemList current = item.ListItems; +``` + +Everything you would call on a `List` is there and works the same: `Add`, `AddRange`, +`Insert`, `InsertRange`, `Remove`, `RemoveAt`, `RemoveAll`, `RemoveRange`, `Clear`, `Sort`, +`Reverse`, `Contains`, `IndexOf`, `Find`, `FindAll`, `FindIndex`, `Exists`, `TrueForAll`, +`ConvertAll`, `GetRange`, `ForEach`, `ToArray`, `Count`, indexing, `foreach` and all of LINQ. If you +genuinely need a `List` back, call `item.ListItems.ToList()`. + +---- + +### Manual garbage collection is gone + +`MenuController.EnableManualGCs` no longer exists. Delete the line, .NET handles this itself. + +```cs +// Before +MenuController.EnableManualGCs = false; + +// After +// (nothing) +``` + +---- + +### Nullable reference types + +MenuAPI is built with nullable reference types on, so things that can be null now say so: +`MenuController.GetCurrentMenu()`, `MenuController.MainMenu`, `Menu.ParentMenu`, +`Menu.GetCurrentMenuItem()`, `Menu.MenuTitle`, `Menu.MenuSubtitle`, `Menu.CounterPreText`, +`MenuItem.Label`, `MenuItem.Description` and `MenuDynamicListItem.CurrentItem`. + +Nothing breaks. If your resource has nullable switched on too you may get new warnings, and each one +is pointing at a crash that could already happen today, so they are worth fixing rather than +silencing. From cab5d388cc739ddccaae81f75695dc163c84b99e Mon Sep 17 00:00:00 2001 From: MrPizza <5529325+sir-lord-pizza@users.noreply.github.com> Date: Wed, 26 Aug 2026 16:31:28 +0200 Subject: [PATCH 09/10] fix(build): put back the usings the cleanup removed by accident --- MenuAPI/HeaderGlare.cs | 2 ++ MenuAPI/TextureDictionaries.cs | 2 ++ MenuAPI/items/MenuCheckboxItem.cs | 4 +++- MenuAPI/items/MenuDynamicListItem.cs | 4 +++- MenuAPI/items/MenuItem.cs | 4 +++- MenuAPI/items/MenuListItem.cs | 4 +++- MenuAPI/items/MenuSliderItem.cs | 4 +++- MenuAPI/items/SeparatorMenuItem.cs | 2 ++ MenuAPI/nui/MenuNui.cs | 2 ++ MenuAPI/ticks/FrameState.cs | 2 ++ MenuAPI/ticks/InstructionalButtonIcons.cs | 2 ++ MenuAPI/ticks/MenuLayout.cs | 4 +++- MenuAPI/ticks/MenuTickHandle.cs | 2 ++ 13 files changed, 32 insertions(+), 6 deletions(-) diff --git a/MenuAPI/HeaderGlare.cs b/MenuAPI/HeaderGlare.cs index 917bd73..dbbb73a 100644 --- a/MenuAPI/HeaderGlare.cs +++ b/MenuAPI/HeaderGlare.cs @@ -1,3 +1,5 @@ +using CitizenFX.FiveM.Client; + namespace MenuAPI; /// diff --git a/MenuAPI/TextureDictionaries.cs b/MenuAPI/TextureDictionaries.cs index 4931b6e..d221712 100644 --- a/MenuAPI/TextureDictionaries.cs +++ b/MenuAPI/TextureDictionaries.cs @@ -1,3 +1,5 @@ +using CitizenFX.FiveM.Client; + namespace MenuAPI; /// diff --git a/MenuAPI/items/MenuCheckboxItem.cs b/MenuAPI/items/MenuCheckboxItem.cs index d15634e..0311bb3 100644 --- a/MenuAPI/items/MenuCheckboxItem.cs +++ b/MenuAPI/items/MenuCheckboxItem.cs @@ -1,4 +1,6 @@ -namespace MenuAPI; +using CitizenFX.FiveM.Client; + +namespace MenuAPI; /// /// Creates a new with all parameters set. diff --git a/MenuAPI/items/MenuDynamicListItem.cs b/MenuAPI/items/MenuDynamicListItem.cs index 9668b2b..80fff50 100644 --- a/MenuAPI/items/MenuDynamicListItem.cs +++ b/MenuAPI/items/MenuDynamicListItem.cs @@ -1,4 +1,6 @@ -namespace MenuAPI; +using CitizenFX.FiveM.Client; + +namespace MenuAPI; public class MenuDynamicListItem(string text, string? initialValue, MenuDynamicListItem.ChangeItemCallback callback, string? description) : MenuItem(text, description) { diff --git a/MenuAPI/items/MenuItem.cs b/MenuAPI/items/MenuItem.cs index 822e229..b3fc625 100644 --- a/MenuAPI/items/MenuItem.cs +++ b/MenuAPI/items/MenuItem.cs @@ -1,4 +1,6 @@ -namespace MenuAPI; +using CitizenFX.FiveM.Client; + +namespace MenuAPI; public class MenuItem { diff --git a/MenuAPI/items/MenuListItem.cs b/MenuAPI/items/MenuListItem.cs index b923a65..faa29cb 100644 --- a/MenuAPI/items/MenuListItem.cs +++ b/MenuAPI/items/MenuListItem.cs @@ -1,4 +1,6 @@ -namespace MenuAPI; +using CitizenFX.FiveM.Client; + +namespace MenuAPI; public class MenuListItem(string text, List items, int index, string? description) : MenuItem(text, description) { diff --git a/MenuAPI/items/MenuSliderItem.cs b/MenuAPI/items/MenuSliderItem.cs index 532daaf..7c96869 100644 --- a/MenuAPI/items/MenuSliderItem.cs +++ b/MenuAPI/items/MenuSliderItem.cs @@ -1,4 +1,6 @@ -namespace MenuAPI; +using CitizenFX.FiveM.Client; + +namespace MenuAPI; public class MenuSliderItem(string name, string? description, int min, int max, int startPosition, bool showDivider) : MenuItem(name, description) { diff --git a/MenuAPI/items/SeparatorMenuItem.cs b/MenuAPI/items/SeparatorMenuItem.cs index 6fa6c81..6398809 100644 --- a/MenuAPI/items/SeparatorMenuItem.cs +++ b/MenuAPI/items/SeparatorMenuItem.cs @@ -1,3 +1,5 @@ +using CitizenFX.FiveM.Client; + namespace MenuAPI; /// diff --git a/MenuAPI/nui/MenuNui.cs b/MenuAPI/nui/MenuNui.cs index 063db0e..1073a93 100644 --- a/MenuAPI/nui/MenuNui.cs +++ b/MenuAPI/nui/MenuNui.cs @@ -1,5 +1,7 @@ using System.Globalization; +using CitizenFX.FiveM.Client; + namespace MenuAPI; internal static class MenuNui diff --git a/MenuAPI/ticks/FrameState.cs b/MenuAPI/ticks/FrameState.cs index 184c8b3..55087f5 100644 --- a/MenuAPI/ticks/FrameState.cs +++ b/MenuAPI/ticks/FrameState.cs @@ -1,3 +1,5 @@ +using CitizenFX.FiveM.Client; + namespace MenuAPI; internal static class FrameState diff --git a/MenuAPI/ticks/InstructionalButtonIcons.cs b/MenuAPI/ticks/InstructionalButtonIcons.cs index a5cd412..2abf9e9 100644 --- a/MenuAPI/ticks/InstructionalButtonIcons.cs +++ b/MenuAPI/ticks/InstructionalButtonIcons.cs @@ -1,3 +1,5 @@ +using CitizenFX.FiveM.Client; + namespace MenuAPI; /// diff --git a/MenuAPI/ticks/MenuLayout.cs b/MenuAPI/ticks/MenuLayout.cs index 744707d..c3b24b9 100644 --- a/MenuAPI/ticks/MenuLayout.cs +++ b/MenuAPI/ticks/MenuLayout.cs @@ -1,4 +1,6 @@ -namespace MenuAPI; +using CitizenFX.FiveM.Client; + +namespace MenuAPI; /// /// Everything about the screen that the drawing code needs, worked out once instead of per draw call. diff --git a/MenuAPI/ticks/MenuTickHandle.cs b/MenuAPI/ticks/MenuTickHandle.cs index bbe2f28..c8bd865 100644 --- a/MenuAPI/ticks/MenuTickHandle.cs +++ b/MenuAPI/ticks/MenuTickHandle.cs @@ -1,3 +1,5 @@ +using CitizenFX.FiveM.Client; + namespace MenuAPI; /// From 06c0982bedaab270aeb0decd1287e181bcf14035 Mon Sep 17 00:00:00 2001 From: MrPizza <5529325+sir-lord-pizza@users.noreply.github.com> Date: Wed, 26 Aug 2026 21:37:37 +0200 Subject: [PATCH 10/10] chore(fonts): ship OFL faces and match the title sizes to the game --- MenuAPI/ui/fonts/OFL.txt | 96 ++++++++++++++++++ MenuAPI/ui/fonts/antonio-latin-ext.woff2 | Bin 0 -> 16288 bytes MenuAPI/ui/fonts/antonio-latin.woff2 | Bin 0 -> 26592 bytes MenuAPI/ui/fonts/bebas-neue-latin-ext.woff2 | Bin 0 -> 8928 bytes MenuAPI/ui/fonts/bebas-neue-latin.woff2 | Bin 0 -> 13768 bytes .../ui/fonts/dancing-script-latin-ext.woff2 | Bin 0 -> 22652 bytes MenuAPI/ui/fonts/dancing-script-latin.woff2 | Bin 0 -> 42708 bytes .../ui/fonts/dancing-script-vietnamese.woff2 | Bin 0 -> 7712 bytes MenuAPI/ui/menuapi.css | 84 +++++++++++---- .../src/content/docs/reference/menu.md | 9 ++ 10 files changed, 172 insertions(+), 17 deletions(-) create mode 100644 MenuAPI/ui/fonts/OFL.txt create mode 100644 MenuAPI/ui/fonts/antonio-latin-ext.woff2 create mode 100644 MenuAPI/ui/fonts/antonio-latin.woff2 create mode 100644 MenuAPI/ui/fonts/bebas-neue-latin-ext.woff2 create mode 100644 MenuAPI/ui/fonts/bebas-neue-latin.woff2 create mode 100644 MenuAPI/ui/fonts/dancing-script-latin-ext.woff2 create mode 100644 MenuAPI/ui/fonts/dancing-script-latin.woff2 create mode 100644 MenuAPI/ui/fonts/dancing-script-vietnamese.woff2 diff --git a/MenuAPI/ui/fonts/OFL.txt b/MenuAPI/ui/fonts/OFL.txt new file mode 100644 index 0000000..3d85383 --- /dev/null +++ b/MenuAPI/ui/fonts/OFL.txt @@ -0,0 +1,96 @@ +Copyright 2013 The Antonio Project Authors (https://github.com/googlefonts/antonioFont) +Copyright © 2010 by Dharma Type (Bebas Neue) +Copyright 2016 The Dancing Script Project Authors (https://github.com/googlefonts/DancingScript), with Reserved Font Name 'Dancing Script'. + + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/MenuAPI/ui/fonts/antonio-latin-ext.woff2 b/MenuAPI/ui/fonts/antonio-latin-ext.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..282553595781c80dc690e0e88e64dd9cbb82c843 GIT binary patch literal 16288 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

*!K5cU_36)g z-jrc^*5d|IG{uX}Wb*A3Zou{@XSB94d z`||7SaD02^&9x}HwMHMAv-JEmZxgKj?OS*Dy>s`#d-o1~a34R|KO~Qm$MEF;DSehc zXH-V#Ou?3%d-V%E7Y?G3q^uOJ8hAb-^tmvvMA#NZ1p*F7Aa@MOs&F7n)pozY0323! zBmlW^!-AB5ZdRmG?Q^q12K#2Vyumnz!vJ;w`5)kJ!2D`p{SdJE8Zf^JKto0+@52~5 zlnuIXpdbm8c|zbxVL{0a9g}oHe-`F{Q`RNFf<3)P@v=VHG}8&*rZG(ki57#&okQ^b$Y08*lQCi{t0DMK;H^+ zzV}DiHvc9@4yC{7=A_)lqfigLPK1-O&}gc`O6wGF>eEbo7YEf0#1BHpq%30m^y+$t!m~56^FW^VHg%%!1VNv6g1;TY#w$z8F zl&Fki6=w1X!nZe+&n1Er{bKX=>xlxhyu3@dBAFK2Bmip?o zeyg4J;-k!Hg#!9;5aia)0WFn#9+)y)1hin0X6qH88J~umo4e1&wWqhIlB<2D~bt1Q= zVm8l#7`7YotHLK(`Q(pL<)1RviedoBUc~>rJ zfcq3~&L;2GZo-)Y!RI}*2YUg4iDS1jtUtZ=e#DdWD?zgX@6qs^oX(Hp`|t{fV+6Lt zrFoQUjOn+Wf&V#w^?M!GyL1h8^B;JbZy7FQ{%}rb*mrlNwl}Yp!;>0bE!8>a|HEKe z^LcMyCV8lda>8XOD%NlCgszl4$Y7@V#2L9#HoIBxdb#0w`fswT)`5$u^$Z4!GTv!1 z?=}_Re8RuDB|WVfHV=lT1Mj97zVokd*^T}20)YvIsPW@MH@uS7v!R}|qAa}VdzN~Q zR^*Q@-s|MHc62dZdX*ss3#9zkP;RfmQpLjKi`U&x#4$Q>b%!e>Plbh*mJ(B|{6{id zJ(mY^JMtU(%ntZwspvsvK3?X=^>kGkW(zsy39K+!T%VFr^?It`>senhcm>w|-XeSP zGSG4dJE(LxC-B(>&@_4ijBYBX-IvQ^dvnoksX$Y20~n);fM~msv{Xve6B4?D1OmU2EdN#BThlnd_JpwV?-?vPM!DQ1STs99xjdM;2ug$w z@pEveKQmZb8#~9zJW!s7s??@?aB<=^_jUU;-I?HODJwrm!VId@y;lA2#)4kAphJ$} zBt49^>>FyHLC+^2Vsg+b#1KVvrE0mm`6#$22 z6G>4R361UHxM?O9rwzLffwkcd7F2pvyOXP4cXoKxQpvAy>545`!$?qI-KX ziB*T{lW{dkUUV80Qxn~{mR>}@%xtdXf*PTypvhvV$Fq(0v81-xHz~NJb_3YGjXB2r zBTV&K=MAF$aYz7M%8#eS;v3x9;C{AX-TV)PvQ$CLGd{;}Z06(~uH!PL_ zq2H4jFtbnzjF4&4AjoS8ip1Ll=t*mzbkcgmz3CiWZR87u#~|nfT6@ZqBz9wdLP^8; z^rG^3QV_?>mQA&Z43Yu`(A?etW*fXbMdN9!$>m=VjCn>t^SKh3N71g?5%xdDHbRz5xm#i48k0^=O!yE#3}b#&i!H0;|PBXOz@Cku{n4I`|A zn-0Z_Yd@TwP|ijN2GPSe@HLW#1Nu2u1wgoS?V33_p%qGR`2Kk&4OB5~bzsNajbE!9Ja|bVu{- zsQAv|PP`I7H9W*y=^9_ze~60SzKtj!M6m|J;o9R|2c}NpT@6+&E_&{v z)+6*11(B-IvP3_?{rH~RS*8o@1c0{&p6Pcvuh|PW1+`m1l;yfHaDddVfo+w1LAb zKs^)SdI4E&aAJ=$$XJ1)enDo@07MSUZ&Ae3co&S+OYK_6^+xA-Q3P8?3BtHKb{ftC zJVb6*h&cgztIF%$58X4n?|{LtGyWo<5 zw3y)c{njO9l)s9GCK1h@Sgw?)foJP9-c8ED z0m{I2Y78f8j3sSYJvjC|ySZc*jK~9;6p!C19aPGJrfcX;Vam*87LZm)*%20Ib}NEv zbjHW*sk+G1GXXGfP}R)c#exQPp^c$xvtlf2Qzeg6ZM-oHMq0J9!-N*1ehIQ#pZpg) z*2l}1J$H47{Q?jDQk8^|J=RZwTBCEk(X(QCPF$@_ z@)cdX!9brjJPM=B%5?In&G6RQZ^b$ImB8^rx0e831xceBwugb;U6aK|SNTY6rO*Q$ z^fu*1sLmU=+3%u90`RRnQoFfR)>_|Ly=nK85l@Nhm(%X5o?(X7!jhGSj;*=(sq?lE zTEH#4Au9>JGsT8(-y-M{0x>U-4t2m4vBs1w+a6$w)~tl6btYTVV(eH)(l%P3*8EUm z!FGJR#$ry@d&X@wH^dol+b@H$s;87Y-moxZpqCz9H_sQ*E%p9&+xq1S-2Sp2;t_c7 z>C|m)G%{~HQc$98_FZ1h$)oj+b~s*;td6$)>l_=xj328)uQz5+PpV()3Mbu{+7al$ zjn%sbT=B(z(RuGNTYhT0JNWGH^P`_Sz6=j8Ir;p37XbD_yWP6m4<$Ow{s->TCh01Y z(PJMau={QK(NyI?yRw&BB5Z}-SFc?YwNc)Wj0$Jj-@esK%CZW_mY9Ux+1jG-*|E{r z_4dlA<$Vf_lb-^O+$H*g;RNJA%l^1?x26$aF)jgh=_-^kdc-mFcCA5l%&*t4yNz~y z4HGiqn0>Erw0lPh(td=DxOoQLfenEhiVf~Nfp()%`$iNdA{zbQOM0Pm_Nt=Wpv!C4 z?$rZzr>I{#Exgx6vw^drkhAPJHWpoF@~?1jAv%~l|H&?|i!On4+Jy;*Oe6(+yq=Ar z{#e^)ziJKYp1R01cGfGoZ&})6VJ4q-XGL)+0I1R>H8Xib*|t{FEpb@Kr4Cs!W&-)2 z5S;w9B~imMEv3?Ugp47Xrb!*sLh7L2)>d+f>er~X%cwO~Oiy*um13=PwZ@?yI)2Qc zxvss#+P-C%Hplr_lBq5 zFw`@7^^mP5e~ona#xI(?P9rq)OMS6omMK{*1`Eipza3xH%y2FK`6YGP5=vuSRMVx0 zf|UE=QXp?#|1<#9YgldfoRmbMSENU^D3+~W$edk!{*7x+k>IShGQ}5((OT;g}C$kr|j}iWduI zauyi7ioQqPEu0b+ys~8F1b)ns9IME>V$21KQD_!M6lzn5LI!$}nC;d*(yZKL9Atg8 z{nnOsS8<;OZS^)y^vbg4KwU?aZJ7_^f}<_}uXScDF(jnTt@TQM*w&)wZOV7)-l06f zblYXi?g^k<4O~Iz5lB@nq+57E-Mb{XMP~oU!U6(8Kpp8nKphQSsI^47#M*q9pa_$a zg4EuW^R_gIvdqf4I5l18>uxD)tKKxYSv1$iY~d}b<+bY8-fp%>?c3H2%}|mh zm;`d^MLT;r%}vzJTk`5JV!f-mKr;{e+VB3!U%!0!En;!Y1`18_h_!aQ-?QFUHz*;c zQx#XM5)xHeYs=f#(Jm)sS4dwS2o6C;SRz~y0f;a}A|e+dMzkUZ5R-_rh}(#d5C$Sf zv>+)Y0ojb~LyjZQpwv;Bs1j5Isuy(}^&6@eEuc%#4d`C5VXN5xI00vXqvKj|S8?}o3NC~9!SnDj z_$>T2{1*NP{5t|eC?rS-U4&u6Ea4L2N5Vcaj_5}e5aWmC4T>qnhmuYipsZ0|Qr_vRbiH*Wbt`mF>)zB| zTLN>?s0Pr$P6a>#3WbD&fGH%1gdkFq{9DGPW&1A;@a%Vo(0G%4d9uSFemr+MhTetw z4yrDBi`O~Ym!O3e;MUU%*RjE2XuT&1?~M!6)(XNGKh)9tQg3w5Wl zOi*)OPZ8a0BBo?&Mb{O`3_~13VzlDIGD6CGqi#E(lA26Y#EF`Di1^9Q;sG6)AS02% z&ghl{)J(F+?YKRqyguxl&O+RFt`sOw1)aQGH)TP!vuzCi>8eJ+(+#1R5f=Rb5Xf1_ zwFgSw;m;GH&!ofwQ>5|gvuAUrBw)F>K!i-l0y#&Zf-?nSULKKM!KqMj3NK69G*lm3 zSSwvaMFJ_8^F`^sn|*adR2+-vW}5v*cE^mle5VB#na05XG&pP;^X_Z(5y1){U344B zx5X}9M;ynpA~LN~E2QSmoPEFqGi+zO9Hz%R5CsDkUMH0rGqtw8<+PR+9ke{JKqM&@ zgcRXHEF}LZlFx zKPk;$yn-y*sN?6|!swB;x7@nhGpG6AGjd%b51oOFv+br`q$LVC=hqT&JLiyzEF}3n$^fv zp52Ek-^pM(fHHa0^S15`5+e|34?`sNij6*@JhaV0Ge2&6OCa7Ub*573TSCB>OC?h)EWsv3bg6eX*fDxu3(7mO*4D@N|JaW@$HVP>h6Uk(OcyyB%;vsgi znaPY1x>gzGqoa$(r5X5tuRYjSp%J%gg?Oqlf=l_ftD@kQ;Elw-TZcBV5*h1}S1n_$ zM_ANS1m+=spDaPtExGP)$K1=-2I*E`w>T`DHYnGH^^amK1Yfx$2tm5wIbuStT-$!?rdDR-S0V zV0#Zn%rsff^k}(U|#vPZX>!&s>5F{|CbPEB|Nfz`*wUpgoOE#L@})Pp@&%-lDjJ zB+x__A5)k zVH}QLd!6BarscZu9ddl78|NEbwlR*eWr#PyDp8fB+R2gB8HCT{(rSp|GCg~?;i50KbBeDy9${qcM*c_B@NbO{ zY%t!0RYOu$NZ7kg6NPEoWE5+U=QfrorQ0t@b~L`8xNWi>Q( z%t%&i3AujFb~!|q1$QDT>);ZqsG>@dKrvWQdxnntV70`?V|Xl63*aYCi9DyFIZ@(S zIWNjXW0ELRz2tDqGMgBp{7Q+g_5c$iIf=-A7&-@J9If9sb;fn~yMNP7$57vX=-A2k zMvIjzeZ*udJ7Mz5l+4Vp4Z_=hZJhe*mJ>SM{LvC@rdUgbl1r}f~ zSjvUMNEkN4a;KPPDY9TRF)SzrL9<0FPdM#bF_i%unHcs;jpso$YU84$oHIM_@EUQm1YHyqwq4F_3I^sY)85KYUNQm~=}2XZ@lLGD zNe5%wEaQFe`1Z_;&^45+N<+IPNFgDM>{pu32A;GlM?LAASH`zLhJYN$1joXhA6FM^ z?epl1srSukbMA%~00q1c?;m|Md>(zv!5!ghY+2QRk!z}bf)pxJNPR^KRfRVyPZ}IT@~u|{mRaV4 z1_ceOfd6Q8U@7fFQezUnLj`bBgMoD#2iG(_uuA$02|vTn(zUIsCK9hoyj3u24n{Cc zhO7%ewwc?Oe}iGf54^^py{gL!y=d^1T7-tfnLNzxf^N;2f-jAOyZ^S)Dz}%|?>P!l z+8rZQ!MZXSh0JR8)iF6q?STz`;9-T&aIy_=WgwXlS+%#`f@BKCbLAY{CakLQ;=yui zqbM#vE%phTWERgf4Bo35PpMiyA1`ri%;}9vGvO$@T9!3dd%i?roGvpF^EV9(yvjJk znAm3N<9o{|D%W8-Zp4k2%IvhPC_~~oi+ghK@x41K_>}i@O%kIOdWJH51MUhV*M?!id}T7L=IY*%$KasG5lM-fWdzw8x&$3K zill_z6a;a~0jZf3XU6aLr3K|z(m%-^#{Ft{^FIKm5UK_^W;NA zX61|kr^O)zJ?;dpCH6c*^m5xl6)d1et01Ew_A<23Fxe#rmI((RhA6ISZ@4~!V5(T6 z5I!d=_IKy%(8Wa&2YhidUSd%U0=-;~M+;g_N7RO<>Ddt^F{P#HZc)L0VZbluMonVi zTRH$8lk?o}X^O@ybbTQn#DiJZ)tzcUOg3y-ror606nP8E+%WXee&-ctr=b=iOBYxc zE{HfpMcOJfuu_ZH%Vh`2w2sZmZNiMnN?ZUF_@yzaGZ$m!;W0ewhu6ZBlY(du))Q<& z6R3O^HnuOX7Hvx-8Lp&zo?(c%(DzsYM`)=(X|FX5CrO;Vf5^^ zQop7I{9MBTFEd85>MpTWA*rCU&HTXk@;6szVTe_%W{~b|n5N8Oku%FM+A&H&FM4(a z4No)DC|UIzvxq(ll56d^bq z%3LpurL)-$(&g0xPovpjPSmP(FDmq@Cj!wHBvp|$1I?!}1Z-5NjEi!BMuVtaMi)c8 zS+&sjnWty*8iT&HJ5iMt6%zQR$~I#Xt5!%?HBN>KjuzTU+^Ibk=UB`$=oPG9#Ye?K z#4{lZEs?6}E-yo7T9w&q$JyXCV`>>hg)Bu$UYNr(3PE5oPO)j|uA!mF76qf;J4Yr@ zT-IG<$MAf>@Mv#=8Y@xN#~OIEVOc8{h;#yO(NmaZyG}f<)zcVm%#PRQB^p)`LlL$n zNY+h#Cl5i7?%B+MIOns3DtuH>amhV@Ae6=xHAX4Dlbm%A5V36={Tnm|{p5k*8&cF< zF3+WC6!C#Ej0i6%*w&y-oJb_meh>Zbc1Qd|k5A_SbP>*1J%G8$Ak&mAp3`lAKK9_> z5{F0Vc!XRK=mA5wld?7)dG%da#1(@j`~D!9g4$zLB&RU`Da)>O2_!QgYaM?*@uo8ed8WqC}`539(rxEL^k2qCSKf`qP&B^KuR2u3C^U1p8jaqQ&y@BVUM zewE$bB^-81QDvOAp}p!wa1=4bzPnhFjLGxrD-&D-6X7BR$#ztYTAe5Qs8MhQL~s?l zw_(7Qu*Myf-Pwy5v875Z^_-{9T~091Iq3Og=;6AB(C`E|CC7lW3IQ+Cs5r7$6&a1{ zrR<9B#a3rKZF|k}tVX*(4D2pR+Di~P-CM?nI5)-@9T}`8PdvouVysr4+l1ZqE|Ze2 zY>%JMs$PZ$m@$C=SbE|tgL(#iDYIJ+9073-d2?)BQQWDS41IH5oM{hPA*FIOU3nhV zTJ0i!s%ZAuEG5?S_x_#G-fr3goR}E{_i!t2%~GyzmO6=~)=AotA6}C_7VUIgsY-ge zw8}CbI1O%zSV7I?-@8GnK4go2b4*ci;W(Qpz62vF6`|l|C|Jj2?--Q`O-O}NhSSQ;cN0OVC0rGuy?%F`-nCMd@;EWMDB%PpTu~?pm)egprSvQN5{``L#v| zHX5(Osv$WflAXp1xH1!vio#k7IIC*)C?sk=5ZwGf)--O zX@YZ6(rHB~mHX~sQKR3(Jkq%q7YEq+a3ktSD_=|o0Rkb0ET=#0U@&1D^KlAe2rN>n#2B^;-M<5_ zPw&RfPAyF5O1;~o$%#R@!m;@%7abXejCp&HfWr}{O}X&n*jczAKkR8@E4iQl+pB~g7fV~%M;C~n2# zc~6$3iy~+gcptL#-%m;g?PIfcd|`@H4DpujpD%2r?i+{S>uK~Op3**e)lSkC=sR7YUD&ykBn$l#j~>CS z9pE~fq3?H#HeusV%8~o_WoCn=aTTlX^zuof?zf{K291Nay_#U55jv**O z;6$lw?~_o0P(}Akz-_{5soc=0HXhynzzNLro65 zm$kZQB3GT&ZZ)rI9F4ck0qiGVgf<+*G0^%V_jk{8H$W0u#QdPw4D1ha1gqAGq?#oc zigonR`%6H?xl}xjQY4lw3{iWa#1v8OIWO;@{bXGM@)%8JKHO+D}D05!p2o4#6DVb{q%10G$ z>}Ucb4+v995~T+MTW21w1ZN3z3D9@N5W*L9`rS8Z^?$$@VSZJ+S9ME0#t;)q!Xcc1 z*a2kstfT?^sh1TROg}Yv$aTH)flB}KN5{#;`9>Bb|Ebp<&(U+6wC8*15o2L^%07+r zvTBr1!TgaioN>)iG0rYl2JNrrC zZ^1LH(r6t*Cy+^kVEB(lx1fjd)_5nGoEyJEG(QBzf9+dPGp~UYN5Awso*5rQ9kv-4 znq!NDfD3MqNl!aQJwA^El{)|iNL9zxL+zJJX*k zpr4|ar>I_E0fl7ZLy7c~kz6tuNQesZT3<{?@YcHN?g>@}>Db8!k#P`&N#@&4ASMG} zqy&S7aFrODC9UcnOc^M;5IohQj`om))GQPg>XQCG zee3QUs|s320ve_SMOr6F5C#}g&RnXqF^hT;pDxj!zlPr~X8_MsD-zK`XLal%%$G48 zQRJPr-OW1bx@3_XvM@BxQ5Hrgi{q%l-j2puGQXQ{RD@Jf(p0-)I8y5)FxJMfAd^xQ z!)$zdqaS;^2VVjyo`07l(gwMF1uSvXi$#-c%BcNpDyTMZcX3hEy=av=%zW7 z{rtNG{xF>LSAVYh3r91XRVJgp|5MW>;Jx$y1`%^%Z2uXJda5jMK>M}DUAI}N)#`4+ zY25uwuhGRLiR&4q5n1lf5X4DmLxKMF?^!);e~$do_+0$SUpFkT|J*Q=7n3C91If<* z@4o=zVC8`sGCRmmVid=zG&0X*p6YU*13@HQ^$l+SG@&29=4JDhaC>b3_@1Se$|9Qyv>n$Z~y>Jk~l zxoDqGX~`rwe?|BmkFn?tCw{a!a69?&kkKyryXJ7k-H72GhEHEf|I~Yp*aQKgeK)s_ zuI~uHzw_!m0KWG>9$kxQ0W&v%ZigjG2rP&Xo2E1l5su9YJEC<&2ds}ucyzZZ zc{%Hg6kSLH3kz0GY4)Q8_qe+8dy+WtSRXJYSMH^v(J~nEJUxL@F1Gy9FHjiRr zhJn0WEUiiS+pGFK)r^p1(9wQ`8T&~Z7us5-z{I=lYb)I*R22K3;hy#roO>)miYZEa|539+9#qTI@4JsPopqNq6!LS`&q7FCRW!q|A@A zf4;tdZ5>`1Y(C{g7M=N;c<_-isd%kee^M(~ZI3cJx`K1log>9TSe^3cXGI3g37g~~=_ zu()pJ# z#jqSN5=#n~%8HOHl&ZcW?4Y356sgteJ5;p6Xe!3sH~z6$i?tPJN21UeEDlc~lE@S) zjm}`Q01$%N94?P95Q<80NTjloJc^QieV0FV$67I)RHN1D4MvmMVzt>FPM6!`_4)Mz z_4g8vU^yiwl2?|)DX$=JdjFcQ&i|UwX~Rpn4jO-G^B(dEZm2J{u7|5(5LdJ2k(EYp z%90f%A~4Y(mZ{hmF<;TCKRv{IESs_a@~+!MT~2h>{VnQhRwU5uO|~lzgK5=hSei`Q zj$3^U60XtcxE&p+!Fc-B_BdVj=s}eaV!tn3vIV z!f@$J0b1@pE+q?U$C@%0(K?-3f3L3-2LICG+AEV(;-lHlqwV{(kBN3;jh*7n3tSrQ zF@mJj#Z7Re6-IHcPz6QU=eFw>&p^%R<9fJ%KxP8_WvX5CPBq=v&Fv5Q7m3n zK~;9(A%{x_vK*k|Re6Dy2@R&TY}|X64_x2|2*C&z?H~9CgkT=(_uoo6ECe70hDry93=9Vwpsp2i^E83(K*+18CHE+DI|4;2wrFG^*f;=L%-7BSKOs38 zqL6NosHS%Pp-8YvaH?u(t~n>>u9EsjZxjq+rg8S;-~0BlwVtf9Fj{1Y)*z?*eMz-v zif#z|%rX*k`s>{XpQBjPTb)m z{IOW!(05oLNc#EGKPwQO`H{syZhojxb6%p-7X4?nG}7J!WD5vB0)$VDhE3oBkcPl* z9VlqH5OJ}@g=2z(6Gdc*V)1s5J75fKm0Ie{TIMa*?`7Ru#rdvA{^86uf{@R9xWs|Bh$B?>RGlM84<+mKAs$ zX$Y;yf1qbIKj`JZLQduAg!mljz=9s;?tk2&sTh{8xraLKWW{n3AT)KIWCu$?4&oEy zTIhc*Hh&j{Yl3LVW<$CX%%;4KNFui{%iUAvi{^tyW&vk~uz{Sj>)jdVj&`%6_3wRzwNo#sCW*d@kg=-6qEPW^;< z#{se}U@&3gp*71{$Oh;jPsar$pPbi!(a}@KF1;-4oMIhf4b61lzxt>|MYHte?~0H#0eBi%c|(td;ard4RoKntX|*tnpTujpdrZ|y7_+> z{En2b^VS4w%NG$smaxM7%{EbA`)vJ6Y%7#P&tj1ys=xR)to`4Ys$SoNR?Z?7fZIpv zt`kiDx_-uo3r9%rx7v;$pvnRuSXk}-?(Q1kE7V-3erHg)-i)v=Bl#w)= zCtHq>t-y)nB|CZSC5RoMoQfU;f6__xT*{){bSp9JO9TEk)$xM#AUJc=_8O zEUdujh0OSSu?S}kIr4=98iooLD3f|4>i(eARo(=Gr{WmBZYy(hjzZjyLOd(0c;O|Y zZ|oZ(Ab_0-LV-r!3egB^PFW?aQfCA)8zx^`L9l2+t|9j$qPNYjmKGU1o zN89mkfA&DqVCrDzV4j2{NtX#_S>BkrMaGw>Dl(K@6;~alPEaRmIK$DyTy2!@&TG<@ z-NED*U9>J{Bo;-`C^Sj`v4J;48xbSd7&96%8aaxL(dX|Ni<}6Xh)m4%la!`=sg@P9 zY9Ys6!iKVBN}Kw{vk&@piLEE`L#FF{f~RZ!47`sY1F|X z$&CPHwouCYe@PRJCubzMP){ZKuB_0=j#* z?nYf3?`zWyZHm@os#{t^TWkZZnW{^hxFxp87Jz#0&AJ&iD9%~ag_gopit0t(sVVp? ztFB5*T!|G~0k|pdqn;3*0x!OzUUCtBt214{rmlH#kY5~(JWdl0& zYA3>a{ZG3D2wRm31IE#!YZ{^pW6}vA)!B#E=}$58ok7%fB-dxkh^4yBRG*Oc`6}je zUd$8rIEq>T8f(v%x0T3NBoxr(P;|Zu-M3Jzrd4yq4Bzr`SYj5&7|n~KcW4s@Nde8t z4Vq@aWbQ2=C$b#b*|P;4dwiFYMK6mSzzA>zD*&|_x8{!;(27xMuO@~a&7mR*2F10l zv$9%NR?F(tGQ(P3&jedMkUmZ^Ou;{a9*kpwjk+Vy>ga8nuky~!vTFz>$E1d0mRM7k zQ;^7V2MWx#k&o6AI_IZ$Z$$AFDHgrbU6vs?R0?@c9rJri)H^QgkZ9K)gxFJIU^;4K zI%Q1jT0wtDf<9@FMKk9)^C;C9=ytGd_a^3)ThquMN0%ClQ^RD%X=Ji&Myvul<(}P} z1kR2}2UK&mqDG2~QQ*ulyHfqkR5E{Q$b48NG9N*RQBc6xbTUScBiV}5J7ntvh4s)P zp(h}aIRyobb{=A+Bi@P}GnZ0*!k|}5!!Dc@!nXIm6mLU3f+gY7XBaH#FVJR25c zhAV}PEax!52fTOnPZUZWbO*p*pxYfRU_No|Ktq}CTa#Pu=VZ2>z6+g=lJ}(zsJ?Wy z))rLz@I9(A-Q^uRQ0TR_;%qGO8jrWtoG2$XD;>ie>7@!%!^%s0C+vAc2o z*4FkWaa5U|9UHEs=z>ts_onS5^!&%J35P(7IU7iJ>=|m3& z%11F7U!}_V>C=y8$w?e%ofQmdF9u4s!(2>966T-*>SF|?x=gRcqXhNP5~YyovUw!~ z(!78H)GHPUAlam-rUz8f3BW{+=}ZoDU|vAdps7s2g~DA~hdEnN&fiWG8I=keH)`C? z_tWLU(*fT6sbYT~145!uxNu<{ZVGXf1s5V$YxfYHS_)QsHD@9d?5l6}Fo&gYMUtE~@jPO^b&JcmKQ1M#aQf1is z)B)orO*^cKUyQF>a}w@6we?&_9Ta-7dDEGTTwqB4tM7yhrtcsj=|A>PpI}5wK`(>n zp?Rc^tM36>8xez|3aTOu;i!h{PsN=I?x0fgr{m8FCFt$80xkr2{D21VG!jnBj!^GL zYCSmEv^*Sun&~opDYR;0Y8(SXb-IWMtggP(cZ7WOy92;ipFS%9?>;-YTXMMEy|ZLc zav+#n2=fAB`Zb%R292HX3g=y0ZCFNi|82|Vj5^jEXfcf;}qnuwhPGb0f*rEJIoJ- zgWFQQAxl}ri+8!S&Z9Yz8|m}gk|Q~mfwbnPR=AW08_FlMmT}+mg=}SeF8jvDv`3JQ zC!NoXuE3;F+8O%y<;ggn=xsrh#$?6Kjd8+1DpRtUlE7x-7cQ$&M+x{94 zr#;G?4|+WJav_a5lT!hZpVp>o%uc%7?ZcWSQADOro$IL~x*hdaJ98)Ef_Ol5zo`v* zUR#>!sLq3Mtp~-7P4g+!vjJ(%qXO2%T;;QsT_rSw*_Yl^^vl|ubJaFF&45*UFSpYS zvV%aTDGLoY2t1Y36tf8eL-Y4 zPwKE%(nnm!OlCQ=l}FL@p~n-CixylKJr=!A#H}*cY}V)2-PXO&dYq$g1fF-_^t?>J z;&s*SD)XA(bliG^!xq~oFBM6a6MITPG9Ii z)*yAyGm;ExyfjIgCtW0K$O`2t@(g*If}_k*7HMjpKR;ZoEz-rHA$s%tba9Un8U1SX zIkW51-IpI+ZVg}kcXopKsMG7dp~1BGnB`@f7DdBoTa8c2t;d+{@rwr+el^@2?#2J! zr9f}Xq67oB^ZGX6O|Ma>J%Y=K4{cTS|9gSq=cv^a0K6Jt z`|%hCoZw~=4YgfR`+1K6eb%Gaj*0+aKNutwOriZtK_J7=PTU*Zato$IJF9|+dm8l> z^4vbq3MY2lhv`-yY6Ap<1}!?!^cgUOgGWGQ%A6esj+{6n zqjBTTgO^1!PI&C7uXtl&j|0bujO1L6S4IWrUDi%-f95}sD~2C@edG0R@z z!un)?N%1%S*7+{r|G)v)037iMpgkL?I|FE-%WEqt+e%VHBAs+lo{-8Zh+9sklIq~{ zDjrawUv6S6MHmU;s$*;cEPJc66#A&js!hc*WszzR077i3N43R@u%EVirAMO@TdIl$ zt>XJ|Eyz;VQdn+KwOg&^+kahvCOkx9&nQ%LK3clVaLO=|<{%9UqB7*!ko1M%~jAc^=DW|SPdMYxXcYzh8d1&p_7?yu?hF9F zE4Ybj4}-(w-4(#lZmboe{)$!h>4ZB6sUQWsY|D`J{S^C>pJSsm6u(0@A&ZwJTo|!K zpA=edbqAS*59xX{Xj*gU+~<&x8xZ-hCv;D7pI zAcSHtO@4GWf;(C8zi%ai&+ufGt6ge+&N6b=D=~^==hODP=wAA&_tmOI)&( zx*%YbTrRmcS{j@Czza3@ehkfBS&^U0LkKe|$qf{DF8lShhG7FeOa>}tg%803GNjure0~q#s@T36o zwk%ST_5g?}`CA+}Qm8JrNO&P|ny3(D{ay4hA2>*de4#Z^Z4pho^sD-RpIBuh0i{ER zK|M@_Flf$I5OVB?^$4tN)lGsr zhhNZBz4fmq0s*!7#YMQG^m5T&eI@NMtl%FpZL-m9-mIUjFxG1`xoe!DxLu#?tmpY z(ksF7#loV~RTKc*57(FolN#YA!|nftrNF z%}F6`yt&~I+G;ppskV}KA5vq@!&&hUgU4$n1RX~46hs(Ekr_-4gcj%ku{L^Oz~&O> zZ_CI~)`SuY>#=AlQd^6m>((8&g`U~(ZRFa3Vh^@FOvu>cY#1Wesj8UMa$2GrTeZPHxFx9&{xxQ(`9Fs zETy7de8>O)*PkW*`Sh)3N;W?I2vN@ zj-#mW7*T1&>dO@ArluxLSZ6>N#XGR5Cgd}Wtx0pTwDBwy`WarF0~ z?K*L$ne@um$>`zd;;or=}(e~OAhNOce5nk_TSE8EUFmxk-j8SSWZby7penBa0$_Xc;#} zaBs`wQb5_I+>AvYF8bQFVZGf)r{AOgQ`X>EXE-O{3#Jwi^{Le^`@s%oo+-A^`rZX79rspjIiW)Kh0ADC4leywDg8~odBJrUfS6Z>$mJG+=&cJ$aTi^esO*4T&>sN z#!3bKuuGTV;ADv{<@SL>Avpt|$}pjd!EUH-Q118sgj$Bb9Nf_soRg^t;5K8<7F@e4 zyx=$!8RI!O`A=Qwgg4hC%H`mH{A@i079ULyA@Ad;d*gekvL{?+ToYbA? zY|&I}I~URwCq>Ukd(k)EanZ5ffAQycIqC$<-g((M7Mw*6AO6?$3mNf3SldCKG_O9; zzfJ#=to7E{)s)KcXus}rU)?D@5ijsw+Wva?RLEE^wgt;859QE*--LO8|I7OGF`VC0 zbgRKA`sm2LZG%W&g~)gEcXM;}LzIRBn9qFxqrD#8$6D+HyjZ5f{tB>(Fr-zJAoz*!15j+nz z4iz)LTTvhKdNW+rDQ)5e)ML*Ssjv8aH&EQL>fy!EA5<7jkO%oO-UAbc_4)&J1~{>u zX64XoBsIr!)3$-1>w{U0{S~z#&&R_Bot29=@kUEVb&iXn3uy(gs)iUZ#RR$@-EqU* z2}sMTXR{lO&dIQ=v|qZ)n|e>C)X?cHgUL3t<1RCC4Pb^L-y!r$>$$8ZixzN-6glPH%F%(wo(qUB_dzqR$seZj@#tyIi<#lzpry zoMRf#3PG3EB&U`CKC{pLVRr#|A>gi87uk#J0PYH2hw6vKeh;Sr40j*E3{$jPytpj3 z2UoK>1;C7U01P+f`*1)ELQ#VKt$*L9cc}`xGTvSbIK2-D+MD+;9T2fKo7nT3yKJ>T z*qd7L{cZY|p5}jgz(J>JR>$9aeStT?+tBfE(YY3PYAt0v1^9ibXa3)|SVUO4b3gFf ze_sOjNrQDo;39FtV0|Ec5fo~+mVE{`deZ&tt749F(ye4djZ!rlm8(-GbiHRMG@SLX zsApx1gFrys65n2fAidV2{8ZBEtiqT$cmj^r^pwi@=|8kVXsm{%u=aRbR?s-)-G@2&F^PF)~NiM>gCSWQN#cT z&5Ar9lc1Vy0RL@!mrg+dy`Z?-_F0Qxi}lky`H8teVBb5H$k&oPv#Acxh-GEMA<7`Q z%c_>xo zL@+(%W|3#fh1}a{IdV*61XirqHtqIqWYfTEGJSZyq%X#W;Ynv23@Hv47*{eJ!InQ~ zyj`!szrw3);D~+eU@zNsk=Q(Ua}ag06}K6u3%E#csSD6P+4%dVk2NY^`|*&?1-LXT6PnG~4I#fDk< zE23h`s*cS~ZO=vfF=-(zR@~Ii(QrD)ofnuCuEZX3T8~|yolsb<30Vl|a{K+wNkQJ3 z^4n6cW&dG-_CxFXeZ#k zv-qG#7r=ksUW4b!eCav4H5ShgnsnKIRoPVOnaHUrZC>C3r=IbYD6MKThi3QGw5&X{ zpVG@f)L}b)Q1r;TIbn|kt!Xh3IA9Xx%%R$SON@y6$^YjGz6HFB~5fwZ*XSt^{0@)HpZ! z(!Dv*BppmDJw+=!Ln4&EmLDIv0+ayOmSEK4A6IWLZ5s9SA8X%=#7jAh$I*JiIID4a zNP+%}e`S+zq2!oYWMU>vgB1!RnvSLMn0)(qA10sY!;A;8O)#h~Om#37@#ETt+K}7G znV}gj$(5}Uco-=3{0sfqJ!P}ooiX${|3Hp7taS&#_I8fyvkS!lq1C^)p%xWoou+1% z4sVD&ueH7hzH$7qcSH3ze*?mS1QWG?n6yMwwSVlbsp{c0)e*a68qs?f56A{ISOb8d z(**ne$;0v2?XNf9zw-R|{QQ3d7|8r*bb&oXJ@1k#D@G^!=uP-FO}l;+sUL2x*`m|` zw??>mYM^Z(2pC4s_K;U$o#<0jp3C6YFxV3_{UNhWIFqJ9_7t4<*KOuElb+*Vo?Ret z8)q&=`iG#4+ZUBeMe&s*?y1DuJW`Ec`1XWK>lh6dY*$Eaa3i515 zS5%l`qy5v7tw@U)nn@5lC(}FzOj#AmMAqlz#|mv%W#asTLXp*K(XmR)bA_3Otq&}# zC4Y*{D;X?{O3|O0mepVxtyGklz8d2VS!^pLot@PZ!uAlaMC%N-wt|_vgs-VY)D^}x z8x+4ZH$x!B&GAKu+K~y+24Huq(wwt#mb2Uj*P;ZhUk`xqgQYYP@kr=N|FUU9CRTt< zfF(NC>Q%qBvGp`;Z2tPr>P;*7&eq`)x!mzZSB!qYQ=1ic(!?#@vszygARh&bdh6}q z{S$jFqEz=_rH7{*6V+4NgdYj?W0Q|K`3E@@TI$LP6(csbg&29E?G3kYI$o}?4}J6X zzR1s#&HV6T_2#RY|BZOS`&XyBjY?E@-*sQ;4qpA`Lm#*jENO~&8%n%-t7)EX0>gvM zkI@p@X8BNj;)Sa^SvDIvqdEdha!h^-_4oaxDeYg~4_8HiyP=#$HoMViv*N3ZuJ1bk zZpc#zJhqNRou#(wDN1P`jy(0T>g_&(iA!ZwsqGIdp5vsS8XJ37Ju8rP*$oQqVJg$p z{6i*~&(?1m2lxHI1RqPya~zgm8!8<%f4Ztm!h%bulr zSLp54K~hVX;+Kvh^)Ev$z7>P{X{XLe&ta4`zw3sm{@|k_^KK%}CPT)z;;{b9{u_LL zS!F`Zb#3x)^(?aumsH{VnDOWYUqd6IK}yP8UuZk-nk2J;5#i;Sfs!-Dl=+YWelKf6 zCka~!Ry^yoX6I;Kv~tr#A0LvqtidxY2w#YMcLIqOgd0cP!xQTCqC$)h;YOun^|eJ4 z!)dgG3`2iBbtZFOHV=YvrEk}8&2Pc0^oRXT^pWuc1iKJ}vljBhNKB??#_J7;pOxXP zBY(}e9oy{}hmQ9(G)yr_PC0xiq!oKfB+BE*iiqRU(PlkP6DDhf{80TI@JP&*KD#mG z9_UNs1f7%YF}RNGUUkr+bkyN)#SqE-oK)xgEog4mS!v zkHZU5k- z)v9Xi9q1PTE{db;0zZCXDQr`lt@-W!@KsKAEBXi3_2`jKGFsXB zo#TGtaL8Ll(J&y%#7(v_pZTyDYO;atb#cyhd^xQY6v_^$^1iKFRwT^zy#rb@o_(Eq z-%-DnF9X)J>=o|xEA9Jk!)`OiyO|BPCZBxrPxF5H+S9%YJRTaD7k{JG&`3aNG~2vb zda?8AuQhwYD6192__I_kre%cRubeYdSFOqWb4Vg0H9+ z#YbfpR)+{0vx1%+j=^3JTs~Ov5;5W5nrxC`XK9y-+8I$0KE&T#DmirNi(B7F-oO7d z`1{2i$iFe~QBENNj+H^0@#ZzAp%vzv74xhsK&Dr_vb?ScdQiT`jHFLQBDlvVR+}>` zBWYv#smybk3mzfka#R1PPAXf_?GY$AViqyPJsm#(XK-|;lf$abBqiqj@yUa})uaX$ zf7L9X@&o2Qo?$^7sVO!Bmi_PUzBn>v3mnOx)SVgcPH-=&WNu+QodA4)tqNNm`gjKO zdcA8WPbJ?>Om!-Y_wkD6cw-`b^0uabPpC`eg;oWbu;~25VR1nXN}jm=*qP;otAd({ zL?`P3yq}eozMn7e>pRyNRG(C&z>Vz@801P#jr3meYh9kDZT>Nu(7}{oK(Z{==V~8ul9bw>IQr-q&F75|LVx1;&L;Y(acPoB%>f< zTgWeyi^PJe((3HM9U?K&_jA{T(RJ6gU(uDk`;#l6o!~#p^~}gY(=ovu9VXl`+$FFs z*!amGloCcMDhbTx zdX)y`*Y_0ia@k%b0RipT?i@DA{=+EGNKJArI8P56`qU)(g|+n196yv^q&IFLnm{Kn zo$lzfzK)DS6?xv7U75*ij-sDv?ON*fM`lPoZO;?ql3ldFMa4Qkb?(qm>I#(xMFuNT zRV>r>SrFJPvsku}&B-9k5ZQ9`UZN({e*b4!u>Yao{-xl{e}LD=w}iJ~xgopND0R(D$E9xuyPkd`1fm$`+x<|h9 z$)E!(XdqOf{(;eF|4Q)HKVjT{)qa5V_w5U@%q=bowkp7snWkcldPwT$ZLZphJR`Xj z8@p_5fw=^0P1pu;r$v9Cd4dD52DXmgC@d9j&;Qfxlm%6jf)Cr1ouI>>v%g?>WB+Sb zaZ7~f`HA!~kJBxovJ&IDFqS?s<13=#DV|Go?z?PCc;L;BkiN?;obaN5id&D~ePeRP zl9Fa~a>jXfMN`ost?Y>x=*&q{#RWluiAqfq6$A!(Qn_xl06Tly z+qhpkmCCiZqXqzzZzT39)``}($kgA0pXWL2`O}z81Cou{BGJNFwRiAiCAQHxJ;3XD zwclEoY8RN3c&w(Sq*+-oELo`$dsf%j8iz7%(-bT^E31kDR7kF}KV)4M(9=Kw)5`=z zH7b8)vV4!c{29yg7uL&PyXw6Wfg0nVhX?>_jeoIBg-moR7ra#{-@`9|wlMF7PBbik zDY$%ZJt=>-z8ZV6Ocp>wcYNvq7*ma?MDv|5lWxoR43$6QSN_67?~PnTE`T{U8OLoh za%?g_F&@p}uf`j5;Q*bury(3L=LB*aFG$K?yFL2m-pKYo_ulXdr_3lENS_lt&rS*) zu0iDc4bLBj(#t!0_P!EyedBlH(HDQi*pr?=Ct%|H@A|DlNU{c1HYu(iEh40C2nWo$ zf!xNc?C6^Znmku85E0BYD4X4C=iv)e+IREHl4R`90HCjXy1sGr)+{FJN&6d)Hc|DB z%dm+K)cWG zjkgnQxeyB)ZwUN=|eC+HpCnrjn1Ny2cT1sp9H| z2Snzu9z>Gl$0f4%$kMGd*?bDTt5p2XDs}{L$yxR1hn57^RVokXKv7o*Xy@2V7aj{? zkd>}L@;ghO7)+3UwY8=iahh6Zy0laPSAajqofz(k{XiZz&D10Obj+C+p!P^Ev<3n7 z5N(y>8234DDrC$0t)ExULA#=cECFG@cROV0%+h__RIrv-x%E8JSu-9j zIHCpm(Le8o?r$wuZ+m*am+RZqC>N6YXrv`g3WD=OW{3AZc6AQjrw-5H2yJ}W($^1i z5%^id4M2YT3ljLpf4ZL|M1m}3n)I5s?2A-oB8&Md=3?NGvMyS&DxuESXQivz@-+q? zX`wBr)wGUwroCvRy`*z{bWiVv{dSjbye(;)-2NHgj#tAx<0g6f&XAcf%jU@3oUi9( zlC4?t9hKYV5qU{Ikd15G*59Yp8#m=1s6K7;^3(P{`;2|dZm{j0x78kZt~#|&>(=hA zV_WMT)4dpawC>Bg-|HImOtt7eUH@(U-}Tk*E_eF6y#FO{cI%49ulm0WPD4H&{4rP! z#CGx_gBs|3^V`ke4tE9Vf@XK&=;fB3Axm1n5YL2GwSA#7AI47JY(ExGsXpGZFCvg$ z$zEgaNEc(J@qZXCqy@8$O~PhjORx>t3G7Mi4)zuHZ=4z@8Yjlda3i=KybYd?55*_q zrTB7u3%(z}OVB6O623Em@Qh6;CUGWpCPz)Kn!F;a^6iKWVjQuG*lucLx?=j=tiv2{ z-f4c{Lf69F!o%XYCDzi{@*Igwl8_onoup;bEz(QUZ&sQ~Hda|yRaU)LcdZkx^R4Tx zmDV%XC#`p^|FE&LNw&$gQQAz{9Je`bbJga)%}bkaZQk4b=gIjKsHARy0@w-wqJU7a zA&C)22xEkZb*nb>h{6!iKbmPo<9axcYJ0MSX0jYke1R zc5CA)M8;1}pxKy4wc^6C<(L|WJVs_9q$XMMB~>aRNrehA>8zql%2Z5A#}s2LD>`WJ zB`#SX`cQ9gUte--GaW z<*c)OjUoc`+{osm`+0};Z5sNOXx5O}KR}09AG({nq9qz@_$!9GhYFJW=BMzfX~u;dX|G zH~+oKb}Hpi=J5{Xm(8oONe6}w(xmXYW0=I*N@F$Nh=&qBm+~QWQG}3)q{&DFDmKe( zV@U3^BJ^gT09(B&GQpv6u-K1dh{^ zrWXT`n>?dqkfCCoX*{KJXvszStLIC$Ytp0x8s_4_$z!bpq7=2w!`A>yG?8Ix30K%X%M{tDkTa+g|RFcjr+qifsR8&K( zj|NfDcNa+W{bOY{e8r-Hd_^0$Xf#0~C%e#!XO7H9B`399cP_^yI)rZW3ggSw zyrV+c0wc0XZ{@Z%>T+ies~fEtcV;4trZ^p)mz2(nvE4eS_`P{(ier)$kg7ybmLws! zR|5`IaJn&;?chiT!3x(eRH+l%h8Au1uD0CVnvc)YM(If5la3sGcN1P*+HY@z1IU)^ zq4b>6iq};sI&0M(@>to%gbC`9uq3-)f39#s&5awm!udl!~N%N@h}@LT?p? zE;`u6Y;P~%Q)(wb$c75Pl=$F+kq{N&=U=dFnHW<$F?Lh0Q#ngo1sO3vY86PLv01>qTeb3^-=1F@13^0ipgIKUjuk%?Ox?E~ELR>Z zT)B*B0H!dhi>s3o5u2p5y78_c26yLc*ja!Q_=^p|lU1@n32JD5Q%e!@9|QV#C*1BJ zE5~IC7Zjkj;lmx;D^VZbGJJSYD}I@-%|a8KwgrMe-O~;Nu+lcHXlDONQvTAP5RpUt z1SS%4$vqTetQmM7Xc45zz-FVR#*?<<1B#aKOuG21VdtL9X}QEma9|Y@ zwD39%ELTp)XPr>9WmLm<4A-3??~6EEs;pczL5vVd)LkS!gbJF4U6y)nHKuuecfW}* zbH2a!YYiIAo~Tx!cv_Kg7MW%FbbQ)!@76MM-NQrMPF0!~oSLOdvlzjIJ_HHD*{Eb3 zX%{whRA0rHztmk}aWtof$GY;?5>gPRr$y1LDiwNSF5l*{I9mpxq%0g&T`k&?&S8V1 z-ajzJ+v*Pd7Ms{Yw<<0|9x>Vi0byiACo|(SImhJ~dQnAtC=vchS*c)Kzmy|5e42ws z`F6e5o`rZCUO-^2n2Sp1GLZA*P2`T2WsMV5Xq>7_#?Z-o?1Hx+2qyXxCJ4mAC5Yz& zj>WUQjoC88pb;S=j!~qt>F8#S84F@!`&UNn$L3mD@^#ye216sgz^TRaiXoP(i$M|X zl!Eavti2h5RB$FG7Y6WuZd&ztCq|tO=JEtVb+(I^*_w@f;Lj=5*-&yB8Qt9S&K@$h zGCApd_&BD!?FjkLKAMcxH3*s^ah9anElUc)d9E^JUF%Ap(??URoUe=(O7&qiGKQZ1 zz9lubM|S-F<5F5BCwKo~e%g*)F9aIvFPC{ulP+9hKi?nBU|=ZUV1~9R|D?R-Frkbr zfpb|9GD!N9L|FFaAfHaCBX15PYsxMED zWktZ(u*lWEH|a(I?2Ly4Uv6A0aRM-C|j3Uf3(oOfnKkhQcpcqQZUh$6=3qeRJ)l}dx&QP!s z!W0pNC`Q=shT(R_(3!9JP}xKXPOaf$0T2O|CKH>$!&G4KrL2UzXNZTG*uP*^Iw$-UfNZO=vMr>=|_qo$ee^-RArgPz1OvTy81-N0f1N7k>75 zd2?^vVdD89#NZ3~qC2-jM*;8?3y|pbj#}wo{lB`&9m6##Sb)cZ7g%^`Z-4V}1K|Gd`d#b&v=hvPunG#CSVxrackZabKN6-C7^YcxRKBB<1o z&hD#pfEWw5MSb9k5Gc|Lq^UY)7$J$j^4=eAUb(vT;v1m2T(g$GNW(%hEnk=(cBgLm z9cUh`p^499w$u3OYB%Cq8mhsiDN9gg@W$MW4D5>MpN(60!0qgm79z_LM1U4jFdp*T zG2s2xd8J`$+ytP1!7wRDG4T{8hs3P7eLN$J+y*XWmCWa4w1M{sR-A~yiARFr!6gRx z8HKm{H+UM@4kzbUPdVHd!yuI*ID`cvrlE0W=g(PSkdsY(e;N>MCH6BJxhs5o0O@W;3&%Kzr~!GnYS z8WmJrOC5})dgg0%Lwc@lb0LcogEbCzTt`def)nc4 z^NK?@b}t!E2wJDX9%rqoLD6fJo<-8@oQKs<~yoY+)0{okTI=jAYWRvW#k2UZZ)1 zRuHf(=WEm~)6gFuGZmu;X(7*fRd>8d^@=g4V%0md2q4BZ)6xvK5H|e-mOye*E+s)5 z_)9IhORdlOx=GgnlfYHDo2|ezF%ECxVUyAUv4G{Ne|K;O&nn?!)|}vT z1hML$QqaQ7XBO6Z3q@o0{s$i)nD{!2X^iG4G5pW;Jp#i+c|s6^ z#i=rGW^m!$g`BUA8_+bE4Q)=9y5ZT*d33vsrl&7FLn!1(wB1!it9uPJuZ9qU<>|g9 zktkeaItdKQNeRZ8+)Vs-lLvx&Sbu#sk)T5eye3RqmtTmlT<^`;#dzR~>EU4%0p+3U z;*Ee*2?6a%2h_YdQ=~}Ke~VNkktEqE@jzj57Ng*X91f#^v;H6^E)mi+>`{M4rld}F z#gH#?&fuwx7K*$=5-6|mZO^+u)F3@j%M2W^D5m$83#y!~|wiyz;`%5v{$`mD}PM47@qrA+CNfTD_2wEw3Mlp=|*(Dfr}b-^3h zg4}qMHcfe)RcL{4E=byOoMN1nNw=0r912(o%#dI?2^0 z&bk8QOdC@V7-X_wc?Fm*+se;W4NiTQ#KCW>n>lw^KVCX|?|g&J6o~t}WTxOlm|RiY zM_W3C8Lo9r!7iV){o94@AoWtRUzG7~3A0caDIABp@*Iu}zL>RTJUOV8))k+tB6c?z z-gImR>)*r3h-@~xxItz{Qu8?fNK(Av4FU6-Eio7ZH)$-PdWZu@Lb8#5qnicE#^Z96;Xh(E+2IVQB8v@^z#*oFf*F0AW0PpJt;{UZ54-gbaBys zB*lXeOpL4`Oe0AZ&pZiX5Q-A@k%Kv6gz<5qSjwkU_adLn$09?3MB}Aj;<&0Qo_X>G z)O0H0b?MxMuw@Z7b?j!(nd!97hR>o@RKN7qyplpz=4Qy+QUT5gjyAHg{Qv5`r0qs| z8Mh^hkV;T%VxcwqS65U`lFpc2g4=%W=+rny>|_-0o&tjTg_g?om79_kfCbaG<1|`d zjc;W@b)_4~fWwp&oMFPWLIk&s8cDbapA^9r^%}msd#cCZWFE4^x*}o?oov$Uy>7(Ru@gAcs-o#h^`Qrf3Dn_j z#Pm`TW{7cX+G5hD4-?xN%tNIXGmIXlbuCwjS69+OBRCXef^>W|rF&W3n~+=JY2S_w;;~q+k5Bitjb3j=%0jW& zosdIpwlIb|!w^&bkcX_xoXRU|1U?p`WW!LGc!SCiJp4r zV{b+ULW)iI^1y4Oj!lAdm2&VkUWGhq#gFWe%R0~C^%`0K$7#SvVoJ3`I3;-&D1o3( z0mvPU0aK3gC)DgDKbX+gw$R9$8!}g7Msdf&G)ma?|m||0NGFsAL z@&%HyR_6q}bJglyRgpvU>OKLddvYBVlM**d^9_r&>qHngVMl@s8rk*54M4wY{Q(4= zd9GE(jwfw_CXkK&y&Z9F%0bjDzGIAl?~c86|FY3Ahu zql2Si%+@bnb!g(?s`>CT!47$j=!EDplP1P)A>+ton9b*cZE;T9Enx{USoQj7XJPu{ zAtIOJb0+v=sd-xc=EGZY!cMn;eQGrzr18$kbKu$eW<^i4)9@`>#Eaf)v1o+>F$?$4 zI99Daxccw{Ajy<-S6Q)P3DTZ-bhV?U!ECV?Tp=M$+}a|%Hm92iQL{W7t({uF(o%)O zQjNW<;W#{(NWLs4ZHj@ENodGpSnv{{rWQ?&$tLb(RK+2cYF3HYN9{?|8ql%T&XR%_ zuEYCWryljM2+E1@?oL=1IlO>DM~q~;W9Xm^uwmJ$<4EF!MTfP+5hM55?6RzLkn9UY&(o_1VN0)(>6ld2j_17VA3*oVhp?qPlw7BMKw*?X8{k)o7bsU3+E@?`l zfl?|6aWP=|#`y{+=0sj@=EJ-#;|d8Bib9wF)^&PA7vIxlNrs3c<-!Ls34#$C&K$X( zScAUf{lk;jBBj_wyROvHh25->!Cc|fBU~qeQf`islzWW_6E*c-((tFNDH2IPTR$=0 z0}~y6(?W6avcc-Zs-{)K**=DQveGl(Kdu1AJJOcD1qztIWucAUY8j2U7gDTl~O zA?c1qkFaD#RlBg?LO<_{{TXPED*@nag@jufnaEyEQ4BN;&6J+hlz!+1F*)$=V?49dWt0MB07Q<>P(U&}+4=LwezMPXDKU@3}<4 zO@R%1fC-g#uuk!aXWSi5k2-Dek$?u7V4K{8AyS&7(gI|MI75@j{)uK<4!@#FmZL#R z2(ue+>O2UO_K;X0tcH|N!CH5!>(E2Fz=Y|CG(}VC8)?SxnFF5EJa!d020l%go~KV= z9kmdWVvG&jo&pW7ZRe3E(R0AQj4Evd&O#Ib6E3BYJ1Bgc?SBDeFN3#9z!zzR(DmKv z-sxoh9dxzYmt55t4e444QXFNRSGnm3HU(P(XIU0H3@@H};caA>l&XfFc;;qZsFuzMaG1(p+SWF7XCuhi#V0UhF9xj7 zz3ljHj(O(KZfNJAfQD(1yjZ0BH$b1WBOk~`2?!|=I+y*8xz~m2SuxN2DFiR3kW5A* zB5*VaQXG)|3*>#aQ)uE;058=vHNclJbB{=lOg@Cakmkxh^d-yrf2h?6w#SBLIW#xU zNi3r=@x2pv9M~ftM`6XZ@2q1v(MTjZ!A4XnY^I0e@_`?G?`ihH>sd?MX-b5Xb!kCG z(u{0xK43z2IjKDo<*_f@ZM3!k2k4Vbv}?7w8F<695F}8PWQ?4dHVMLb&$DqcBGakT zNEX}u{s;}$h_u}xqwWhljtN2&Tl(-?UPsJ0i++=(fd^lx_O!L}3tWlFo%cItjd<#U z0b%ZN{k7HIWz+8Ee)9l5j$yV71wPC4$*PXp}c#V?>ShmR`5pAthk;` z_pk?JOCD+a9I~WK20OZzx19jy<=3ZCHxfiMe3oWd=Ohq*qNa_FbmWwkp}|cV45HuN zGCAGu_OA=Z1-Dm|xZn*ZPZ^s7I`N`Zh0t{$-aI-!38rVQ27YU2)9Epqdx^_KGBg}7 zx=Pg<%#+o4*_$3_XDN6I86ngVxJ2R$*{xJ?W+ky)-5P7&xVyZ})N9~&1G)4#;0tCL zL(ct98ZB5%ZUOB%hA6xCAE!I27QiSTWGWCsw<-K8O{Jw6f|slDia+Ahn^#gNw~XMx z)Eth6&*M5zO1*vFyb~7Y!|EN6H*-86OB7HwKI{&++MeNo!I)Y{uZc!Mal79VMqG7L zx9!$U1V=Y}?Xi0^WN!L|3DzX?h;33Bg>rc?@RP~G8@J~?EV6w+q;pJ`EfPQeEmS6! zPD$G9oMC)f(WVXkA~PGeGW!qZqDP!UYugkVxQ;r5SZX=%979~4aXfEo$2*=MdHXTp zhc75%m_~P+d)nUhvj@{HD{9n*5;MA$=Isw6Maj5{EM1c4z=$S85Bw%*nxB<-VWi6< zgqcMNJ5eLjpIxH1ThT&Q%ylc-m zV0NPGw-~NgYdFCdFvpHA?Hu@?V(ql!DK=X=S?0u(2DP102%^lAD}d{>|ri8$30p(ejngo z0q#Axoq4ya+c%b|60jR$>_O%Vg1e)B{+FS`b%WY@J3S}I7ttGER!f4wuU zp&1;Vr)AF|DP0N_jYl$10!=EKFiJB*c0v{=unZNuEOIM+OW&y<75y!T@E2t?5o+Xk4u+*-e9tIjUTt zMcK83DCi6W4!w=ctt%xSi^CEwjLYj+E;>dfIp%qlD_eVWb^%4bT5|Pf>n{fk+c}&# z)4zZ16;{zsj;BRrqq041+y6PP@x0vTw9Y`6TS~}G4x-X35ww*xWZ6D@a2C*y@9a; zuj_--^y0KXR<$v$S%P9E&7-i>&lE)5GC*JCqokFFi--~k5{qq^PV&RP0I$p%J$Gsv zNxXSBUK3F?uWYZk&yfv7DYPg?J1&tXm{43AH-a41W(#^Eoyz3y?eh#Z?s&mK>w0dP zr^=2Oc4r%M*1=a^Z%oI%rCmnRQm(MRf)p*+^UIxK!8VUS9&qtTdR8^&;jJYJO_s%D z%a-JA;xsEUE8>}Ns8;7Y>k!_P6M0tjq#$m<$|sb!KqCwjojjtoE)zx8*ZIaVoxER| zh>s4fIve`x=9oaH^t*+KPggZ1mI)imGfcC=j(~!9*Y5xA_r{M>K^*CN04U#WS8tvI z4;ds1$r7wfj8^m($=PMa8(zuNvk1o|xBPqaie${H8sIIptcz+iYlo^rJ;Z#58fF5{ zuoTvAoRv&~>jxaoOC82ElyoK+i|2O%MI@LI;Q$&R2I<2#_!aW;c!D6yT&N*D?no2N zy9hz$wr(i{xWm5ob~MY@g`kJ?TEkjxv&M|J7kU+jrU(Rf6*JFTa7zS^W=l#FRas(L zRlrNn`NLn0rl91zyi&BHnjn@*t+;NO51o+baTX|ziyPC^b~j&#;YgfR!#JYSve{-z z@UJY}Nmh&kTTQrrDbHOO&T-BIydwG>QT7}hS)Ds#!|6blc`}QxQ$-9V*qs{|_xHnq zPl4Ehz?@WoHDUr8Fd50YP>x4g6yZ|9tpow)!=mdNgQNwV^payELCP=*u?J~bL(ZO= zh8|CQO{pXlNA`D2jav!4iV}6%<)O8Ml>#giQt$%Dgi%ydM7uJO^gdO~XPp*NWnGaJ z;UbqzN<0$;bTqH1vz%qmtY`+4>`pAkY1c3o(%ivFxOI*-%Vo)!M2$r!!sPxG)#0h> zZPa)Q`CXUC*H9Lq;Tk5V+X$LrOiJZ-(JiKVRY{jAfhKWC!5IdDW?7i^<2)bRHDTh^ zDg`b*PZxxY9{kjOWlX zfZEi&HB>byet+NcTRL7i%05Rk{+2s3KJ`$v2#(7EAi8PX+%@(fgLN-LTjKnoSV()8 zte4%*_?2$`>Fk}KoP8QiXgWTT4So&}dgO7LHJV92AV!2&Qy8)Y>5vu`hC_>*x0~Yx zgB?zAX=?)O5z&yh=Dj&Oncb)CajLsYftEb2o}8#x4vpAu)vB-smYFGVZ*d_bT#YtY;sax_y=O9GJ7_q5!4%&qujuh#u` z;qJivi)04KEwm_qe@{M5;9s2L(`0ukKcHNf#e`ADb9vKG*Q z3vgx?>b-PrcXkB%!ok6F^Dp~QnVmEZ!wV(#^bYG{BkswIfw$O{Q9LhV<+f>qP@eh4wXT5ZhnZYW z{p_6&u!Ft;2Acw=N!Rh#y|wMjoIVOqfvTzyE-MPsX+Yslh{f*2?YsmA*f4)fCbubQ zc-kw>d=aDrbgKY$Y>ItgSM&04=|Dheqw~*htiRsA(lc*ubVOCgSWH$$@V>!q&VlK8 zv_MfD7dl$MH~SbhRS_OTBvt4{tt-7(+M*74-`jI#o=lBC^vU6!_1)c#&4KY*K_$(V zK^#h-=$ToPbOS-iZg%}rIWKh}O-0W0u%G$LYa{1}@*6NhxpXHCcPeZfK?ykivXWm)KvPqH1q0|sn@|gjlzNGD70o@=%3&^dQ|H5C6q)BZGR*V<&Rl+^^b?4TF0iQW~=(Nra1<5`f{vO5uA zrfMA3)#ZiR*(j722?DXTb*5ONvSWfmqM{m%-R%uv9z6@uz~0>hyc8&@w4nUAzU~GF zSaTbjk00$;oIy1UD8VEc2ZLa+>JWXmNIwoUeJ<j$ql#l{?KU{GCMULb7%%*2#A zw&(7A;p{HVnBJ%KdqF3vjp*4kXUi8JeB0BIQw5(^fG=Tz_CC}>Lo@Q_en2siMhKb? z&gzo`fk5o+%ys<2TqKw6@4?GsfDV-^iBQM@?F}|bL1eSAEG$!}rNw)J9F$~tO-?E& zrevff(w27v2Iy2+q}%+{(yeetcs!dO$o2ZcdHI z0Q_)^+S%Kct@J+XS>;rbninw;i`!PK*__hvZ%T-<1z*Ad?J7c{q>2GLjPiIM{%md} z6aT4yi=7<=LcpgOybcY)1Hn*H$45bi+1*OcO48-675u5YHEZB-+uZIg1^hopp=*JM2YLOUG z>I%hQNZPQgqdnl*9DXuRh}5Jcki{i#Waurhgn^yQnlihf1yX2+Y!O#wi z=I1O5>t=)ZGF3!p(A{mUmUm*;QF?+MZcmRP)QU zm)+~=5d6Fp>N5xdPx+i)isJb>9AngNTex5yKD?F>SIWb;YaF8mVVU{5yH8R5HR>bB zSy!bNhSz&1YcX9qlx0@L%Zbc6!&B}5^l!n%7fvaTB+DMhQas{pp`Uvp`vSn)B1mH_ zlb5Wl!cM&ioMRnNy`(Ii#Oh+v8Zw6WSK&gN{~w$NSVZkDEEL0eFK2=ms->DyWer8j z>SP{59bQSw(!S-)tp?o=66IOr&d0Zu<4q6+REet6*Bz?*o>Y~eok@3z_h+=M=oT8; zmU648*7`hyTPdK``xp8VWF3s=ntw?jf zEUBMqOUo;}BJBw5{%3P;R(2udzcD^CyJt4vTiNzky_V_#Dicb@hx;3@%fJ1vSD7u7 z3Edah{%Ci8O9lZ_V>o*GtKVP# z*Nx@oMsoXxTRyU9O+0QUING!%zM01IwS$AAW!iyA0WIm8rYcrQ+eXm!wa3_-AAJBE zTZfvyN+xSE5lm7tkJU;6xInq6$bvQRd4%;$OQ@1XKh>Mc+9w|{qJ}uVYLejjb*<~puy?^7V#Nhi*u&k)MPkDw)tHmRe=Ib;wzF?DZf*1puI}u;!jvdr zG|vwN;$Zi%&hB`EqE2=%h^JzGCsfNPtm(RP5-O(@()eIvIt^~pCP`(@95tU{`+&IO zEG6wmc9syaHWmXRkjS7Rjq}khl?jz5-4MeyJ&dl>Z?d6DM0=J?nIa#qis8}X*&KnR zYDLpc+n=L+(yozpB_`q_*)+Xr!6PA}jB(@T5h6l{zC(^h$K9$mYR;eoa7Cw<8-8+P z&BMd`HB5JWE7y>rr&KsSVWNs=4q{i{7)?)(FxoGcA1M?C52Yl-@wd*z^fQ# zvSmyZm5w4U4~ll_9!4n!4t4j?Iurg7*KGf@xYuBxFq8cahWGw?K0Mfv=l$3e(8RFVY=7+eR)TnG8f9pWyafoaOFm`Rb7^W6dH-s*FBJC zYAuN_=t#6ff*!v*^4;I#1GB1- ze{^l_v7anP`_Y*|fA8Q(8p77V^C4;q@<@@lsT%d>_#l=@BKmNazw{r;p5Ce%>zse1!0KOcljWpO;?Wkx*1pZd9hXk{wtVJI8b z8kVzaIM9WLBnWXR-nag_RJJ7N)~Xlmf5Q;&s8Ops)&?nt(YDG}HBZ`Cf~H-2`NVvi z*+1QjO&Q_f+u;Pt8!}aVz{qNA^&Ze8u|0U+E${BtAIfOBYeOebl>J%l-%s|zFwZPd zM5}zf97Yx$+ir4?;`w8JVx~+&NXW9kdbv+T^VZWt)fv>8*pcJCA5`sCJi#?ZB%i3j zC!N_;Mw!LCdoTbK`+jM(9YCn!(7`hQ^-hSZX^XBJvyp9@xNveAS0>!yA;LN<;h& zL70vQ6i|(n%EX2E;sjxGv`JGBZf_s|)NB@5dT|=@yr7PcY5W|()Y1gNY^WU6n?rR? zwPHG(&cl8?@WM%UeiD>5^Z02Up_1%Ou0t+K(GilMqOUl-&oz-t|LtGZ5Wm{rHr>Gs zGu?>R>gyqcuWJi04h*MLow^m)P5rTzrj;D-`#j<4vx2i0KBhe*TMxM4P~tRx4IzoV z`}~)a!PL(0!SlF=@0c~K57$iigV=f*?pZhS$>c2|OFDSoTkbpm%d9K= zXb*7H^b({;E0~V6wN=p@un~7&)6jGdh7EY!tCc_sKb}y2o$YX~d6(q;uyY-^6#}ZHn#8G?k|0Qe5TbZW1ey^IW-86K z)eGlW&t}B_Cn4nV7$Qa0Q`|l`HX7yf;-=i$|NMa(;Oi(87Fx0(BpJrzQs~sj=lZm& zQ>}q#tC6M^MtK2`2yv(cgciEVMOWemuv|T+C`__SC6BsI$@ALc1Yx@=D^{yF>*=|= z7|SvcM4gLDTJpNiLpJlGiax$}&AL8(_+Y(NUL)!HT8d!U#cy;;1nCe#34> zIZVc>ow~NoP9FK$C8s^VRu0oIMNgh&XNWy3@I}&|JhxZ%jD;n%#A)d}piW ztdUsl!C+BMFaYu#&qWv&v@PPU+&Mi-j?YKV-uuGf%twGrjb(hos z`d{!DCjjaSWFQ4S+G%H=sD}TJTyf`yjW{y|7bYDwrtJ_wBX$ux*v#1C?{Bgn08E9v ze`B~1bc82_M_tn@n2DUxC<<|6rzCl)m*LJjcjp|`M`pewxa%biCLVeq+1xHGjk)y@ z=d7`;8Dkc+Yexwr{`+z9p(1q5Jucm{_}JFqKxI!Blmyup)hm zGS6N;TUyTN72a4}#i<3ORC0&J)lWjuw%*yn?pu(5sXuWnAvBl`)6n1*a!BJ3Sn zU+Y%L7yJt1cR$R#EBZ*;V|J}>s@&u1dfmtTzWRyTcqq8AW>TPA( zpw#M}t0kSY)g8lhDa@2rB69&Aoo-_qGIs;+WOL^7Z;#pAqvGWzpq!rhRd%nnnqqT% z7Uqyf+Hu(hjZnjVS-T^-U;Huj7n~>>nWG&5^BWK?RxM~ z_Fk~hmN-t=iyVq4UZ65~7;i6Wz;z0sI~ zGlhfj+M+0IaiE-#7$bgu-DdiXg;h0(Ok~|FgcoSDDl$|ph(<0{ zkDDV$zLU`zaZL3U@^=qNq0Wb?5K|#|anDtK1X4OmhZ^@_jvtb&kUlw{S?nOWR$^I5 zjwz`qypi zXvJ};7ahFXx`*9ng}X_P=evW|bWwlv$bk#aEs_>u`^fUK$h6~iT3K)Fp1<@1>ci_h zOs|er)PYaFb-eSZ-c(>Md%&UIdg31PTm~umvvz3hT(33fkx#wnR>JSUF6SLoG4B6%?ECs0tdUpjT{d;C#Uz2RL zp9R97lox2(7m%}JQ9nMvD_;uU`H^#+H5)xL-O}~o$S50Q&sS3Wm$?N8lZ)y^!YGYC zwT(o+a~G40lc$FB*7S@k*uRo@6MI=ptEHH|fxll%zJP$t_L9Wd@_bF%O}=CATP>=4 zo$f>1y79ZhI@P>$LoipJoX_y{Ld-tHNRO>pN%DJAY6r!Rq3^2RTz^g$z`3N>@$S=~ zPp!w_GrItZI=_4oKmfmyK}{&tZ9FYr)S&|fDj=N-fRD{cNI+;ufY8Zn6DnLMFrkVt z_6Zowpb~HdzduNutIKPF0PN#b0s?i52^>I}>A=@XBS8R2JqaREcQ8Q$Mv4+-fDzk4 zshLKC3W(ejG+>07paV3`KK_?8e%w0{$&jT)F1B(qMN$Qy0~J=2_>L*xJ|9QWr{DuOh?JQL>fiuBAv@{ z&o3{SDTj!dDtuxwC#kgzhy4g_Kuv|;_|=@^c5vXPG$*86qGX9AgO%@C6S?J%yKLC9 zWAC1AJMQ1FpxUTh{`_JT4?L9Vk#5UYxcG@H>U|FCeQcM+lmAN%yt+T2W7+X+Ii!oZ zrmz+Fa(ypAK}Ti_@$S!$dHR2%EtCiu)%z(_8`$MvOSwoDs>oHER&7<3pYcT#XcHq= zjXDao;>1gk=tmop1`f%P0;!M&#!b+>=S2R!4*K5(3pW~&0h!=}-~HhOm9T&=9e9M% z0jdr&Gvk^TTHsK8AqQ#ANlY3vB@^<&aNU+u)}407yyG@q0)t@gfL6`g9n_&ginj^W z=pr>u259@mJ7*D@GSMs5EWbffPjJxDk3kG$6ysom4KDZ)ViICZV;1vR#4@De_t1mo zljYLjhwmO$lss`Ar4Y8#oEN$!;s>pi?Q6O9%j#Ci5|wy*iCJgb0UIA7Mk!R@`{5wY z*wP0v6pFXa|s?>)aA6*8#!L4om0#udkFUOer>2ii(0t?I literal 0 HcmV?d00001 diff --git a/MenuAPI/ui/fonts/bebas-neue-latin-ext.woff2 b/MenuAPI/ui/fonts/bebas-neue-latin-ext.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..23b344f3d403eadc81e5fe6e9893aef20a5859d4 GIT binary patch literal 8928 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 z zadmU|@bvQb@%2McGKJa`8lAypu{m5GUmz5TB|Yn};WD{GsZwjS2#VnZNzn|;@%{eA zUP+EBlq$7GtJ52dCbPwAvlse$&~2=E#?FEL-sw-oS{&|UC)OE`JuSqF9KJVEWKZqG zO8my3(<%Sk!mSlgjyHtAHz@+)$7Lr*;OCbqln`a3k$@U1k;#YicaW;Xw1rSV0|E zkC@jc+5^^V4ZBuVWpF*}+gAQwS{pHAy$)7>#=ted96OPYJEtk^d!_eghE;9>KIdlZ z^^C)J?-2(vepenb!wGC%X{_VWSh1(ZKUT^M93US~hB%ximM1c$zhnTpPF8OI9WGv6 z`nSV(StD!Wm=q<}#4)pG*1RYu zCLPPkEjq5%6kflG$-ZiIM6VN!Z*T1KGyf_WE{~*x()fy+HU0Vi-yvO**rI!KcP~SV zHaA;TmkLD3pu`9S;=L7RL51Yo<51Ww!A)EYm{v;$U)JbZ)#NCMdI!Q1Dx ukL#ecfRlJ-kq&?#b}$}`QJAEW8&IatGyH$@tLW_45Le=tXq^s{F8}~`(B88E literal 0 HcmV?d00001 diff --git a/MenuAPI/ui/fonts/bebas-neue-latin.woff2 b/MenuAPI/ui/fonts/bebas-neue-latin.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..92d6a0f7297276c67ade85119be8996eae8b3d57 GIT binary patch literal 13768 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≠ueS2(Z(=p06JW_4vixfA?-eT;=$)( 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 literal 0 HcmV?d00001 diff --git a/MenuAPI/ui/fonts/dancing-script-latin-ext.woff2 b/MenuAPI/ui/fonts/dancing-script-latin-ext.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..853834d25c490d946edb05a83263e1086a8047cd GIT binary patch literal 22652 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 literal 0 HcmV?d00001 diff --git a/MenuAPI/ui/fonts/dancing-script-latin.woff2 b/MenuAPI/ui/fonts/dancing-script-latin.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..5353684edd50058cb7e2a92ccfe43aba99ef01b4 GIT binary patch literal 42708 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

4J5Zn7C3Yxun;m^}&kJ+tX6b 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 at1u(_@YvtiZ