├── .gitignore ├── .prettierignore ├── LICENSE ├── README.md ├── enums ├── BadgeStyle.ts └── Font.ts ├── gulpfile.js ├── index.ts ├── items ├── UIMenuCheckboxItem.ts ├── UIMenuItem.ts ├── UIMenuListItem.ts └── UIMenuSliderItem.ts ├── modules ├── Container.ts ├── IElement.ts ├── ItemsCollection.ts ├── ListItem.ts ├── Rectangle.ts ├── ResRectangle.ts ├── ResText.ts ├── Sprite.ts ├── StringMeasurer.ts └── Text.ts ├── package.json ├── tsconfig.json ├── utils ├── Color.ts ├── Common.ts ├── LiteEvent.ts ├── Point.ts ├── Screen.ts ├── Size.ts └── UUIDV4.ts ├── webpack.config.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | index.js 2 | dist/ 3 | node_modules/ -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | StringMeasurer.ts -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RageMP-NativeUI 2 | NativeUI for RageMP written in Javascript 3 | -------------------------------------------------------------------------------- /enums/BadgeStyle.ts: -------------------------------------------------------------------------------- 1 | enum BadgeStyle { 2 | None, 3 | BronzeMedal, 4 | GoldMedal, 5 | SilverMedal, 6 | Alert, 7 | Crown, 8 | Ammo, 9 | Armour, 10 | Barber, 11 | Clothes, 12 | Franklin, 13 | Bike, 14 | Car, 15 | Gun, 16 | Heart, 17 | Makeup, 18 | Mask, 19 | Michael, 20 | Star, 21 | Tatoo, 22 | Trevor, 23 | Lock, 24 | Tick 25 | } 26 | 27 | export default BadgeStyle; 28 | -------------------------------------------------------------------------------- /enums/Font.ts: -------------------------------------------------------------------------------- 1 | enum Font { 2 | ChaletLondon = 0, 3 | HouseScript = 1, 4 | Monospace = 2, 5 | CharletComprimeColonge = 4, 6 | Pricedown = 7 7 | } 8 | 9 | export default Font; 10 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | const gulp = require("gulp"); 2 | 3 | const webpack = require("webpack-stream"); 4 | 5 | const gulpCopy = require("gulp-copy"); 6 | 7 | const path = require("path"); 8 | 9 | gulp.task("build", function() { 10 | return gulp 11 | .src("index.ts") 12 | .pipe(webpack(require("./webpack.config"))) 13 | .pipe(gulp.dest("dist")) 14 | .pipe( 15 | gulpCopy(path.resolve("../../server-files/client_packages/nativeui"), { 16 | prefix: 1 17 | }) 18 | ); 19 | }); 20 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | import BadgeStyle from "./enums/BadgeStyle"; 2 | import Font from "./enums/Font"; 3 | import UIMenuCheckboxItem from "./items/UIMenuCheckboxItem"; 4 | import UIMenuItem from "./items/UIMenuItem"; 5 | import UIMenuListItem from "./items/UIMenuListItem"; 6 | import UIMenuSliderItem from "./items/UIMenuSliderItem"; 7 | import Container from "./modules/Container"; 8 | import ItemsCollection from "./modules/ItemsCollection"; 9 | import ListItem from "./modules/ListItem"; 10 | import ResRectangle from "./modules/ResRectangle"; 11 | import ResText, { Alignment } from "./modules/ResText"; 12 | import Sprite from "./modules/Sprite"; 13 | import Color from "./utils/Color"; 14 | import Common from "./utils/Common"; 15 | import LiteEvent from "./utils/LiteEvent"; 16 | import Point from "./utils/Point"; 17 | import Size from "./utils/Size"; 18 | import StringMeasurer from "./modules/StringMeasurer"; 19 | import UUIDV4 from "./utils/UUIDV4"; 20 | import { Screen } from "./utils/Screen"; 21 | 22 | export default class NativeUI { 23 | public readonly Id: string = UUIDV4(); 24 | 25 | private title: string; 26 | private subtitle: string; 27 | private counterPretext: string = ""; 28 | private counterOverride: string = undefined; 29 | private spriteLibrary: string; 30 | private spriteName: string; 31 | private offset: Point; 32 | 33 | private lastUpDownNavigation = 0; 34 | private lastLeftRightNavigation = 0; 35 | 36 | private _activeItem: number = 1000; 37 | 38 | private extraOffset: number = 0; 39 | 40 | public ParentMenu: NativeUI; 41 | public ParentItem: UIMenuItem; 42 | 43 | public Children: Map; // (UUIDV4, NativeUI) 44 | 45 | public WidthOffset: number = 0; 46 | 47 | public Visible: boolean = true; 48 | 49 | public MouseControlsEnabled: boolean = false; 50 | 51 | private _justOpened: boolean = true; 52 | 53 | private safezoneOffset: Point = new Point(0, 0); 54 | 55 | private MaxItemsOnScreen: number = 9; 56 | private _minItem: number; 57 | private _maxItem: number = this.MaxItemsOnScreen; 58 | 59 | public AUDIO_LIBRARY: string = "HUD_FRONTEND_DEFAULT_SOUNDSET"; 60 | 61 | public AUDIO_UPDOWN: string = "NAV_UP_DOWN"; 62 | public AUDIO_LEFTRIGHT: string = "NAV_LEFT_RIGHT"; 63 | public AUDIO_SELECT: string = "SELECT"; 64 | public AUDIO_BACK: string = "BACK"; 65 | public AUDIO_ERROR: string = "ERROR"; 66 | 67 | public MenuItems: ( 68 | | UIMenuItem 69 | | UIMenuListItem 70 | | UIMenuSliderItem 71 | | UIMenuCheckboxItem)[] = []; 72 | 73 | get CurrentSelection() { 74 | return this._activeItem % this.MenuItems.length; 75 | } 76 | set CurrentSelection(v) { 77 | this.MenuItems[this._activeItem % this.MenuItems.length].Selected = false; 78 | this._activeItem = 1000 - (1000 % this.MenuItems.length) + v; 79 | if (this.CurrentSelection > this._maxItem) { 80 | this._maxItem = this.CurrentSelection; 81 | this._minItem = this.CurrentSelection - this.MaxItemsOnScreen; 82 | } else if (this.CurrentSelection < this._minItem) { 83 | this._maxItem = this.MaxItemsOnScreen + this.CurrentSelection; 84 | this._minItem = this.CurrentSelection; 85 | } 86 | } 87 | 88 | // Events 89 | public readonly IndexChange = new LiteEvent(); 90 | public readonly ListChange = new LiteEvent(); 91 | public readonly SliderChange = new LiteEvent(); 92 | public readonly SliderSelect = new LiteEvent(); 93 | public readonly CheckboxChange = new LiteEvent(); 94 | public readonly ItemSelect = new LiteEvent(); 95 | public readonly MenuOpen = new LiteEvent(); 96 | public readonly MenuClose = new LiteEvent(); 97 | public readonly MenuChange = new LiteEvent(); 98 | 99 | private MouseEdgeEnabled: boolean = true; 100 | 101 | private readonly _mainMenu: Container; 102 | private readonly _logo: Sprite; 103 | private readonly _upAndDownSprite: Sprite; 104 | private readonly _title: ResText; 105 | private readonly _subtitle: ResText; 106 | private readonly _extraRectangleUp: ResRectangle; 107 | private readonly _extraRectangleDown: ResRectangle; 108 | private readonly _descriptionBar: ResRectangle; 109 | private readonly _descriptionRectangle: Sprite; 110 | private readonly _descriptionText: ResText; 111 | private readonly _counterText: ResText; 112 | private readonly _background: Sprite; 113 | 114 | constructor(title, subtitle, offset, spriteLibrary, spriteName) { 115 | if (!(offset instanceof Point)) offset = Point.Parse(offset); 116 | 117 | this.title = title; 118 | this.subtitle = subtitle; 119 | this.spriteLibrary = spriteLibrary || "commonmenu"; 120 | this.spriteName = spriteName || "interaction_bgd"; 121 | this.offset = new Point(offset.X, offset.Y); 122 | this.Children = new Map(); 123 | 124 | // Create everything 125 | this._mainMenu = new Container( 126 | new Point(0, 0), 127 | new Size(700, 500), 128 | new Color(0, 0, 0, 0) 129 | ); 130 | this._logo = new Sprite( 131 | this.spriteLibrary, 132 | this.spriteName, 133 | new Point(0 + this.offset.X, 0 + this.offset.Y), 134 | new Size(431, 107) 135 | ); 136 | this._mainMenu.addItem( 137 | (this._title = new ResText( 138 | this.title, 139 | new Point(215 + this.offset.X, 20 + this.offset.Y), 140 | 1.15, 141 | new Color(255, 255, 255), 142 | 1, 143 | Alignment.Centered 144 | )) 145 | ); 146 | 147 | if (this.subtitle !== "") { 148 | this._mainMenu.addItem( 149 | new ResRectangle( 150 | new Point(0 + this.offset.X, 107 + this.offset.Y), 151 | new Size(431, 37), 152 | new Color(0, 0, 0, 255) 153 | ) 154 | ); 155 | this._mainMenu.addItem( 156 | (this._subtitle = new ResText( 157 | this.subtitle, 158 | new Point(8 + this.offset.X, 110 + this.offset.Y), 159 | 0.35, 160 | new Color(255, 255, 255), 161 | 0, 162 | Alignment.Left 163 | )) 164 | ); 165 | if (this.subtitle.startsWith("~")) { 166 | this.counterPretext = this.subtitle.substr(0, 3); 167 | } 168 | this._counterText = new ResText( 169 | "", 170 | new Point(425 + this.offset.X, 110 + this.offset.Y), 171 | 0.35, 172 | new Color(255, 255, 255), 173 | 0, 174 | Alignment.Right 175 | ); 176 | this.extraOffset += 37; 177 | } 178 | 179 | this._upAndDownSprite = new Sprite( 180 | "commonmenu", 181 | "shop_arrows_upanddown", 182 | new Point( 183 | 190 + this.offset.X, 184 | 147 + 185 | 37 * (this.MaxItemsOnScreen + 1) + 186 | this.offset.Y - 187 | 37 + 188 | this.extraOffset 189 | ), 190 | new Size(50, 50) 191 | ); 192 | 193 | this._extraRectangleUp = new ResRectangle( 194 | new Point( 195 | 0 + this.offset.X, 196 | 144 + 197 | 38 * (this.MaxItemsOnScreen + 1) + 198 | this.offset.Y - 199 | 37 + 200 | this.extraOffset 201 | ), 202 | new Size(431, 18), 203 | new Color(0, 0, 0, 200) 204 | ); 205 | 206 | this._extraRectangleDown = new ResRectangle( 207 | new Point( 208 | 0 + this.offset.X, 209 | 144 + 210 | 18 + 211 | 38 * (this.MaxItemsOnScreen + 1) + 212 | this.offset.Y - 213 | 37 + 214 | this.extraOffset 215 | ), 216 | new Size(431, 18), 217 | new Color(0, 0, 0, 200) 218 | ); 219 | 220 | this._descriptionBar = new ResRectangle( 221 | new Point(this.offset.X, 123), 222 | new Size(431, 4), 223 | Color.Black 224 | ); 225 | this._descriptionRectangle = new Sprite( 226 | "commonmenu", 227 | "gradient_bgd", 228 | new Point(this.offset.X, 127), 229 | new Size(431, 30) 230 | ); 231 | this._descriptionText = new ResText( 232 | "Description", 233 | new Point(this.offset.X + 5, 125), 234 | 0.35, 235 | new Color(255, 255, 255, 255), 236 | Font.ChaletLondon, 237 | Alignment.Left 238 | ); 239 | 240 | this._background = new Sprite( 241 | "commonmenu", 242 | "gradient_bgd", 243 | new Point(this.offset.X, 144 + this.offset.Y - 37 + this.extraOffset), 244 | new Size(290, 25) 245 | ); 246 | 247 | mp.events.add("render", this.render.bind(this)); 248 | console.log(`Created Native UI! ${this.title}`); 249 | } 250 | 251 | private RecalculateDescriptionPosition() { 252 | this._descriptionBar.pos = new Point( 253 | this.offset.X, 254 | 149 - 37 + this.extraOffset + this.offset.Y 255 | ); 256 | this._descriptionRectangle.pos = new Point( 257 | this.offset.X, 258 | 149 - 37 + this.extraOffset + this.offset.Y 259 | ); 260 | this._descriptionText.pos = new Point( 261 | this.offset.X + 8, 262 | 155 - 37 + this.extraOffset + this.offset.Y 263 | ); 264 | 265 | this._descriptionBar.size = new Size(431 + this.WidthOffset, 4); 266 | this._descriptionRectangle.size = new Size(431 + this.WidthOffset, 30); 267 | 268 | let count = this.MenuItems.length; 269 | if (count > this.MaxItemsOnScreen + 1) count = this.MaxItemsOnScreen + 2; 270 | 271 | this._descriptionBar.pos = new Point( 272 | this.offset.X, 273 | 38 * count + this._descriptionBar.pos.Y 274 | ); 275 | this._descriptionRectangle.pos = new Point( 276 | this.offset.X, 277 | 38 * count + this._descriptionRectangle.pos.Y 278 | ); 279 | this._descriptionText.pos = new Point( 280 | this.offset.X + 8, 281 | 38 * count + this._descriptionText.pos.Y 282 | ); 283 | } 284 | 285 | public SetMenuWidthOffset(widthOffset: number) { 286 | this.WidthOffset = widthOffset; 287 | if (this._logo != null) { 288 | this._logo.size = new Size(431 + this.WidthOffset, 107); 289 | } 290 | this._mainMenu.Items[0].pos = new Point( 291 | (this.WidthOffset + this.offset.X + 431) / 2, 292 | 20 + this.offset.Y 293 | ); 294 | if (this._counterText) { 295 | this._counterText.pos = new Point( 296 | 425 + this.offset.X + widthOffset, 297 | 110 + this.offset.Y 298 | ); 299 | } 300 | if (this._mainMenu.Items.length >= 2) { 301 | const tmp = this._mainMenu.Items[1]; 302 | tmp.size = new Size(431 + this.WidthOffset, 37); 303 | } 304 | } 305 | 306 | public AddItem(item: UIMenuItem) { 307 | if (this._justOpened) this._justOpened = false; 308 | item.Offset = this.offset; 309 | item.Parent = this; 310 | item.SetVerticalPosition( 311 | this.MenuItems.length * 25 - 37 + this.extraOffset 312 | ); 313 | this.MenuItems.push(item); 314 | 315 | item.Description = this.FormatDescription(item.Description); 316 | 317 | this.RefreshIndex(); 318 | this.RecalculateDescriptionPosition(); 319 | } 320 | 321 | public RefreshIndex() { 322 | if (this.MenuItems.length == 0) { 323 | this._activeItem = 1000; 324 | this._maxItem = this.MaxItemsOnScreen; 325 | this._minItem = 0; 326 | return; 327 | } 328 | 329 | for (let i = 0; i < this.MenuItems.length; i++) 330 | this.MenuItems[i].Selected = false; 331 | 332 | this._activeItem = 1000 - (1000 % this.MenuItems.length); 333 | this._maxItem = this.MaxItemsOnScreen; 334 | this._minItem = 0; 335 | } 336 | 337 | public Clear() { 338 | this.MenuItems = []; 339 | this.RecalculateDescriptionPosition(); 340 | } 341 | 342 | public Open() { 343 | Common.PlaySound(this.AUDIO_BACK, this.AUDIO_LIBRARY); 344 | this.Visible = true; 345 | this._justOpened = true; 346 | this.MenuOpen.emit(); 347 | } 348 | public Close() { 349 | Common.PlaySound(this.AUDIO_BACK, this.AUDIO_LIBRARY); 350 | this.Visible = false; 351 | this.RefreshIndex(); 352 | this.MenuClose.emit(); 353 | } 354 | 355 | set Subtitle(text: string) { 356 | this.subtitle = text; 357 | this._subtitle.caption = text; 358 | } 359 | 360 | public GoLeft() { 361 | if ( 362 | !(this.MenuItems[this.CurrentSelection] instanceof UIMenuListItem) && 363 | !(this.MenuItems[this.CurrentSelection] instanceof UIMenuSliderItem) 364 | ) 365 | return; 366 | if (this.MenuItems[this.CurrentSelection] instanceof UIMenuListItem) { 367 | const it = this.MenuItems[this.CurrentSelection]; 368 | if (it.Collection.length == 0) return; 369 | it.Index--; 370 | Common.PlaySound(this.AUDIO_LEFTRIGHT, this.AUDIO_LIBRARY); 371 | this.ListChange.emit(it, it.Index); 372 | } else if ( 373 | this.MenuItems[this.CurrentSelection] instanceof UIMenuSliderItem 374 | ) { 375 | const it = this.MenuItems[this.CurrentSelection]; 376 | it.Index = it.Index - 1; 377 | Common.PlaySound(this.AUDIO_LEFTRIGHT, this.AUDIO_LIBRARY); 378 | this.SliderChange.emit(it, it.Index, it.IndexToItem(it.Index)); 379 | // it.SliderChangedTrigger(it.Index); 380 | } 381 | } 382 | 383 | public GoRight() { 384 | if ( 385 | !(this.MenuItems[this.CurrentSelection] instanceof UIMenuListItem) && 386 | !(this.MenuItems[this.CurrentSelection] instanceof UIMenuSliderItem) 387 | ) 388 | return; 389 | if (this.MenuItems[this.CurrentSelection] instanceof UIMenuListItem) { 390 | const it = this.MenuItems[this.CurrentSelection]; 391 | if (it.Collection.length == 0) return; 392 | it.Index++; 393 | Common.PlaySound(this.AUDIO_LEFTRIGHT, this.AUDIO_LIBRARY); 394 | this.ListChange.emit(it, it.Index); 395 | } else if ( 396 | this.MenuItems[this.CurrentSelection] instanceof UIMenuSliderItem 397 | ) { 398 | const it = this.MenuItems[this.CurrentSelection]; 399 | it.Index++; 400 | Common.PlaySound(this.AUDIO_LEFTRIGHT, this.AUDIO_LIBRARY); 401 | this.SliderChange.emit(it, it.Index, it.IndexToItem(it.Index)); 402 | // it.SliderChangedTrigger(it.Index); 403 | } 404 | } 405 | 406 | public SelectItem() { 407 | if (!this.MenuItems[this.CurrentSelection].Enabled) { 408 | Common.PlaySound(this.AUDIO_ERROR, this.AUDIO_LIBRARY); 409 | return; 410 | } 411 | const it = this.MenuItems[this.CurrentSelection]; 412 | if (this.MenuItems[this.CurrentSelection] instanceof UIMenuCheckboxItem) { 413 | it.Checked = !it.Checked; 414 | Common.PlaySound(this.AUDIO_SELECT, this.AUDIO_LIBRARY); 415 | this.CheckboxChange.emit(it, it.Checked); 416 | } else { 417 | Common.PlaySound(this.AUDIO_SELECT, this.AUDIO_LIBRARY); 418 | this.ItemSelect.emit(it, this.CurrentSelection); 419 | if (this.Children.has(it.Id)) { 420 | const subMenu = this.Children.get(it.Id); 421 | this.Visible = false; 422 | subMenu.Visible = true; 423 | subMenu._justOpened = true; 424 | subMenu.MenuOpen.emit(); 425 | this.MenuChange.emit(subMenu, true); 426 | } 427 | } 428 | it.fireEvent(); 429 | } 430 | 431 | public getMousePosition(relative: boolean = false) { 432 | const screenw = Screen.width; 433 | const screenh = Screen.height; 434 | const cursor = mp.gui.cursor.position; 435 | let [mouseX, mouseY] = [cursor[0], cursor[1]]; 436 | if (relative) [mouseX, mouseY] = [cursor[0] / screenw, cursor[1] / screenh]; 437 | return [mouseX, mouseY]; 438 | } 439 | 440 | public GetScreenResolutionMantainRatio(): Size { 441 | const screenw = Screen.width; 442 | const screenh = Screen.height; 443 | const height = 1080.0; 444 | const ratio = screenw / screenh; 445 | var width = height * ratio; 446 | 447 | return new Size(width, height); 448 | } 449 | 450 | public IsMouseInBounds(topLeft: Point, boxSize: Size) { 451 | const res = this.GetScreenResolutionMantainRatio(); 452 | const [mouseX, mouseY] = this.getMousePosition(); 453 | return ( 454 | mouseX >= topLeft.X && 455 | mouseX <= topLeft.X + boxSize.Width && 456 | (mouseY > topLeft.Y && mouseY < topLeft.Y + boxSize.Height) 457 | ); 458 | } 459 | 460 | public IsMouseInListItemArrows( 461 | item, 462 | topLeft, 463 | safezone // TODO: Ability to scroll left and right 464 | ) { 465 | mp.game.invoke("0x54ce8ac98e120cab".toUpperCase(), "jamyfafi"); 466 | mp.game.ui.addTextComponentSubstringPlayerName(item.Text); 467 | var res = this.GetScreenResolutionMantainRatio(); 468 | var screenw = res.Width; 469 | var screenh = res.Height; 470 | const height = 1080.0; 471 | const ratio = screenw / screenh; 472 | var width = height * ratio; 473 | const labelSize = 474 | mp.game.invoke("0x85f061da64ed2f67".toUpperCase(), 0) * width * 0.35; 475 | 476 | const labelSizeX = 5 + labelSize + 10; 477 | const arrowSizeX = 431 - labelSizeX; 478 | return this.IsMouseInBounds(topLeft, new Size(labelSizeX, 38)) 479 | ? 1 480 | : this.IsMouseInBounds( 481 | new Point(topLeft.X + labelSizeX, topLeft.Y), 482 | new Size(arrowSizeX, 38) 483 | ) 484 | ? 2 485 | : 0; 486 | } 487 | 488 | public ProcessMouse() { 489 | if ( 490 | !this.Visible || 491 | this._justOpened || 492 | this.MenuItems.length == 0 || 493 | !this.MouseControlsEnabled 494 | ) { 495 | /*Common.EnableControl(0, GameControl.LookUpDown); 496 | Common.EnableControl(0, GameControl.LookLeftRight); 497 | Common.EnableControl(0, GameControl.Aim); 498 | Common.EnableControl(0, GameControl.Attack);*/ 499 | this.MenuItems.filter(i => i.Hovered).forEach(i => (i.Hovered = false)); 500 | return; 501 | } 502 | 503 | if (!mp.gui.cursor.visible) mp.gui.cursor.visible = true; 504 | let limit = this.MenuItems.length - 1; 505 | let counter = 0; 506 | if (this.MenuItems.length > this.MaxItemsOnScreen + 1) 507 | limit = this._maxItem; 508 | 509 | if ( 510 | this.IsMouseInBounds(new Point(0, 0), new Size(30, 1080)) && 511 | this.MouseEdgeEnabled 512 | ) { 513 | mp.game.cam.setGameplayCamRelativeHeading( 514 | mp.game.cam.getGameplayCamRelativeHeading() + 5.0 515 | ); 516 | mp.game.ui.setCursorSprite(6); 517 | } else if ( 518 | this.IsMouseInBounds( 519 | new Point(this.GetScreenResolutionMantainRatio().Width - 30.0, 0), 520 | new Size(30, 1080) 521 | ) && 522 | this.MouseEdgeEnabled 523 | ) { 524 | mp.game.cam.setGameplayCamRelativeHeading( 525 | mp.game.cam.getGameplayCamRelativeHeading() - 5.0 526 | ); 527 | mp.game.ui.setCursorSprite(7); 528 | } else if (this.MouseEdgeEnabled) { 529 | mp.game.ui.setCursorSprite(1); 530 | } 531 | 532 | for (let i = this._minItem; i <= limit; i++) { 533 | let xpos = this.offset.X; 534 | let ypos = this.offset.Y + 144 - 37 + this.extraOffset + counter * 38; 535 | let xsize = 431 + this.WidthOffset; 536 | const ysize = 38; 537 | const uiMenuItem = this.MenuItems[i]; 538 | if (this.IsMouseInBounds(new Point(xpos, ypos), new Size(xsize, ysize))) { 539 | uiMenuItem.Hovered = true; 540 | if ( 541 | mp.game.controls.isControlJustPressed(0, 24) || 542 | mp.game.controls.isDisabledControlJustPressed(0, 24) 543 | ) 544 | if (uiMenuItem.Selected && uiMenuItem.Enabled) { 545 | if ( 546 | this.MenuItems[i] instanceof UIMenuListItem && 547 | this.IsMouseInListItemArrows( 548 | this.MenuItems[i], 549 | new Point(xpos, ypos), 550 | 0 551 | ) > 0 552 | ) { 553 | const res = this.IsMouseInListItemArrows( 554 | this.MenuItems[i], 555 | new Point(xpos, ypos), 556 | 0 557 | ); 558 | switch (res) { 559 | case 1: 560 | Common.PlaySound(this.AUDIO_SELECT, this.AUDIO_LIBRARY); 561 | //this.MenuItems[i].ItemActivate(this); 562 | this.MenuItems[i].fireEvent(); 563 | this.ItemSelect.emit(this.MenuItems[i], i); 564 | break; 565 | case 2: 566 | var it = this.MenuItems[i]; 567 | if ( 568 | (it.Collection == null 569 | ? it.Items.Count 570 | : it.Collection.Count) > 0 571 | ) { 572 | it.Index++; 573 | Common.PlaySound(this.AUDIO_LEFTRIGHT, this.AUDIO_LIBRARY); 574 | this.ListChange.emit(it, it.Index); 575 | } 576 | break; 577 | } 578 | } else this.SelectItem(); 579 | } else if (!uiMenuItem.Selected) { 580 | this.CurrentSelection = i; 581 | Common.PlaySound(this.AUDIO_UPDOWN, this.AUDIO_LIBRARY); 582 | this.IndexChange.emit(this.CurrentSelection); 583 | this.SelectItem(); 584 | } else if (!uiMenuItem.Enabled && uiMenuItem.Selected) { 585 | Common.PlaySound(this.AUDIO_ERROR, this.AUDIO_LIBRARY); 586 | } 587 | } else uiMenuItem.Hovered = false; 588 | counter++; 589 | } 590 | const extraY = 591 | 144 + 592 | 38 * (this.MaxItemsOnScreen + 1) + 593 | this.offset.Y - 594 | 37 + 595 | this.extraOffset + 596 | this.safezoneOffset.Y; 597 | const extraX = this.safezoneOffset.X + this.offset.X; 598 | if (this.MenuItems.length <= this.MaxItemsOnScreen + 1) return; 599 | if ( 600 | this.IsMouseInBounds( 601 | new Point(extraX, extraY), 602 | new Size(431 + this.WidthOffset, 18) 603 | ) 604 | ) { 605 | this._extraRectangleUp.color = new Color(30, 30, 30, 255); 606 | if ( 607 | mp.game.controls.isControlJustPressed(0, 24) || 608 | mp.game.controls.isDisabledControlJustPressed(0, 24) 609 | ) { 610 | if (this.MenuItems.length > this.MaxItemsOnScreen + 1) 611 | this.GoUpOverflow(); 612 | else this.GoUp(); 613 | } 614 | } else this._extraRectangleUp.color = new Color(0, 0, 0, 200); 615 | 616 | if ( 617 | this.IsMouseInBounds( 618 | new Point(extraX, extraY + 18), 619 | new Size(431 + this.WidthOffset, 18) 620 | ) 621 | ) { 622 | this._extraRectangleDown.color = new Color(30, 30, 30, 255); 623 | if ( 624 | mp.game.controls.isControlJustPressed(0, 24) || 625 | mp.game.controls.isDisabledControlJustPressed(0, 24) 626 | ) { 627 | if (this.MenuItems.length > this.MaxItemsOnScreen + 1) 628 | this.GoDownOverflow(); 629 | else this.GoDown(); 630 | } 631 | } else this._extraRectangleDown.color = new Color(0, 0, 0, 200); 632 | } 633 | 634 | public ProcessControl() { 635 | if (!this.Visible) return; 636 | if (this._justOpened) { 637 | this._justOpened = false; 638 | return; 639 | } 640 | 641 | if (mp.game.controls.isControlJustReleased(0, 177)) { 642 | // Back 643 | this.GoBack(); 644 | } 645 | if (this.MenuItems.length == 0) return; 646 | if ( 647 | mp.game.controls.isControlPressed(0, 172) && 648 | this.lastUpDownNavigation + 120 < Date.now() 649 | ) { 650 | // isControlJustPressed 651 | // Up 652 | this.lastUpDownNavigation = Date.now(); 653 | if (this.MenuItems.length > this.MaxItemsOnScreen + 1) 654 | this.GoUpOverflow(); 655 | else this.GoUp(); 656 | } else if (mp.game.controls.isControlJustReleased(0, 172)) { 657 | this.lastUpDownNavigation = 0; 658 | } else if ( 659 | mp.game.controls.isControlPressed(0, 173) && 660 | this.lastUpDownNavigation + 120 < Date.now() 661 | ) { 662 | // isControlJustPressed 663 | // Down 664 | this.lastUpDownNavigation = Date.now(); 665 | if (this.MenuItems.length > this.MaxItemsOnScreen + 1) 666 | this.GoDownOverflow(); 667 | else this.GoDown(); 668 | } else if (mp.game.controls.isControlJustReleased(0, 173)) { 669 | this.lastUpDownNavigation = 0; 670 | } else if ( 671 | mp.game.controls.isControlPressed(0, 174) && 672 | this.lastLeftRightNavigation + 100 < Date.now() 673 | ) { 674 | // Left 675 | this.lastLeftRightNavigation = Date.now(); 676 | this.GoLeft(); 677 | } else if (mp.game.controls.isControlJustReleased(0, 174)) { 678 | this.lastLeftRightNavigation = 0; 679 | } else if ( 680 | mp.game.controls.isControlPressed(0, 175) && 681 | this.lastLeftRightNavigation + 100 < Date.now() 682 | ) { 683 | // Right 684 | this.lastLeftRightNavigation = Date.now(); 685 | this.GoRight(); 686 | } else if (mp.game.controls.isControlJustReleased(0, 175)) { 687 | this.lastLeftRightNavigation = 0; 688 | } else if (mp.game.controls.isControlJustPressed(0, 201)) { 689 | // Select 690 | this.SelectItem(); 691 | } 692 | } 693 | 694 | private FormatDescription(input: string) { 695 | if (input.length > 99) input = input.slice(0, 99); 696 | 697 | const maxPixelsPerLine = 425 + this.WidthOffset; 698 | let aggregatePixels = 0; 699 | let output = ""; 700 | const words = input.split(" "); 701 | for (const word of words) { 702 | const offset = StringMeasurer.MeasureString(word); 703 | aggregatePixels += offset; 704 | if (aggregatePixels > maxPixelsPerLine) { 705 | output += "\n" + word + " "; 706 | aggregatePixels = offset + StringMeasurer.MeasureString(" "); 707 | } else { 708 | output += word + " "; 709 | aggregatePixels += StringMeasurer.MeasureString(" "); 710 | } 711 | } 712 | return output; 713 | } 714 | 715 | public GoUpOverflow() { 716 | if (this.MenuItems.length <= this.MaxItemsOnScreen + 1) return; 717 | if (this._activeItem % this.MenuItems.length <= this._minItem) { 718 | if (this._activeItem % this.MenuItems.length == 0) { 719 | this._minItem = this.MenuItems.length - this.MaxItemsOnScreen - 1; 720 | this._maxItem = this.MenuItems.length - 1; 721 | this.MenuItems[ 722 | this._activeItem % this.MenuItems.length 723 | ].Selected = false; 724 | this._activeItem = 1000 - (1000 % this.MenuItems.length); 725 | this._activeItem += this.MenuItems.length - 1; 726 | this.MenuItems[ 727 | this._activeItem % this.MenuItems.length 728 | ].Selected = true; 729 | } else { 730 | this._minItem--; 731 | this._maxItem--; 732 | this.MenuItems[ 733 | this._activeItem % this.MenuItems.length 734 | ].Selected = false; 735 | this._activeItem--; 736 | this.MenuItems[ 737 | this._activeItem % this.MenuItems.length 738 | ].Selected = true; 739 | } 740 | } else { 741 | this.MenuItems[this._activeItem % this.MenuItems.length].Selected = false; 742 | this._activeItem--; 743 | this.MenuItems[this._activeItem % this.MenuItems.length].Selected = true; 744 | } 745 | Common.PlaySound(this.AUDIO_UPDOWN, this.AUDIO_LIBRARY); 746 | this.IndexChange.emit(this.CurrentSelection); 747 | } 748 | 749 | public GoUp() { 750 | if (this.MenuItems.length > this.MaxItemsOnScreen + 1) return; 751 | this.MenuItems[this._activeItem % this.MenuItems.length].Selected = false; 752 | this._activeItem--; 753 | this.MenuItems[this._activeItem % this.MenuItems.length].Selected = true; 754 | Common.PlaySound(this.AUDIO_UPDOWN, this.AUDIO_LIBRARY); 755 | this.IndexChange.emit(this.CurrentSelection); 756 | } 757 | 758 | public GoDownOverflow() { 759 | if (this.MenuItems.length <= this.MaxItemsOnScreen + 1) return; 760 | if (this._activeItem % this.MenuItems.length >= this._maxItem) { 761 | if ( 762 | this._activeItem % this.MenuItems.length == 763 | this.MenuItems.length - 1 764 | ) { 765 | this._minItem = 0; 766 | this._maxItem = this.MaxItemsOnScreen; 767 | this.MenuItems[ 768 | this._activeItem % this.MenuItems.length 769 | ].Selected = false; 770 | this._activeItem = 1000 - (1000 % this.MenuItems.length); 771 | this.MenuItems[ 772 | this._activeItem % this.MenuItems.length 773 | ].Selected = true; 774 | } else { 775 | this._minItem++; 776 | this._maxItem++; 777 | this.MenuItems[ 778 | this._activeItem % this.MenuItems.length 779 | ].Selected = false; 780 | this._activeItem++; 781 | this.MenuItems[ 782 | this._activeItem % this.MenuItems.length 783 | ].Selected = true; 784 | } 785 | } else { 786 | this.MenuItems[this._activeItem % this.MenuItems.length].Selected = false; 787 | this._activeItem++; 788 | this.MenuItems[this._activeItem % this.MenuItems.length].Selected = true; 789 | } 790 | Common.PlaySound(this.AUDIO_UPDOWN, this.AUDIO_LIBRARY); 791 | this.IndexChange.emit(this.CurrentSelection); 792 | } 793 | 794 | public GoDown() { 795 | if (this.MenuItems.length > this.MaxItemsOnScreen + 1) return; 796 | this.MenuItems[this._activeItem % this.MenuItems.length].Selected = false; 797 | this._activeItem++; 798 | this.MenuItems[this._activeItem % this.MenuItems.length].Selected = true; 799 | Common.PlaySound(this.AUDIO_UPDOWN, this.AUDIO_LIBRARY); 800 | this.IndexChange.emit(this.CurrentSelection); 801 | } 802 | 803 | public GoBack() { 804 | Common.PlaySound(this.AUDIO_BACK, this.AUDIO_LIBRARY); 805 | this.Visible = false; 806 | if (this.ParentMenu != null) { 807 | this.ParentMenu.Visible = true; 808 | this.ParentMenu._justOpened = true; 809 | this.ParentMenu.MenuOpen.emit(); 810 | this.MenuChange.emit(this.ParentMenu, false); 811 | } 812 | this.MenuClose.emit(); 813 | } 814 | 815 | public BindMenuToItem(menuToBind: NativeUI, itemToBindTo: UIMenuItem) { 816 | menuToBind.ParentMenu = this; 817 | menuToBind.ParentItem = itemToBindTo; 818 | this.Children.set(itemToBindTo.Id, menuToBind); 819 | } 820 | 821 | public ReleaseMenuFromItem(releaseFrom: UIMenuItem) { 822 | if (!this.Children.has(releaseFrom.Id)) return false; 823 | const menu: NativeUI = this.Children.get(releaseFrom.Id); 824 | menu.ParentItem = null; 825 | menu.ParentMenu = null; 826 | this.Children.delete(releaseFrom.Id); 827 | return true; 828 | } 829 | 830 | private render() { 831 | if (!this.Visible) return; 832 | 833 | if (this._justOpened) { 834 | if (this._logo != null && !this._logo.IsTextureDictionaryLoaded) 835 | this._logo.LoadTextureDictionary(); 836 | if (!this._background.IsTextureDictionaryLoaded) 837 | this._background.LoadTextureDictionary(); 838 | if (!this._descriptionRectangle.IsTextureDictionaryLoaded) 839 | this._descriptionRectangle.LoadTextureDictionary(); 840 | if (!this._upAndDownSprite.IsTextureDictionaryLoaded) 841 | this._upAndDownSprite.LoadTextureDictionary(); 842 | } 843 | this._mainMenu.Draw(); 844 | 845 | this.ProcessMouse(); 846 | this.ProcessControl(); 847 | 848 | this._background.size = 849 | this.MenuItems.length > this.MaxItemsOnScreen + 1 850 | ? new Size(431 + this.WidthOffset, 38 * (this.MaxItemsOnScreen + 1)) 851 | : new Size(431 + this.WidthOffset, 38 * this.MenuItems.length); 852 | this._background.Draw(); 853 | 854 | if (this.MenuItems.length > 0) { 855 | this.MenuItems[this._activeItem % this.MenuItems.length].Selected = true; 856 | if ( 857 | this.MenuItems[ 858 | this._activeItem % this.MenuItems.length 859 | ].Description.trim() !== "" 860 | ) { 861 | this.RecalculateDescriptionPosition(); 862 | let descCaption = this.MenuItems[ 863 | this._activeItem % this.MenuItems.length 864 | ].Description; 865 | // descCaption = this.FormatDescription(descCaption); 866 | this._descriptionText.caption = descCaption; 867 | const numLines = this._descriptionText.caption.split("\n").length; 868 | this._descriptionRectangle.size = new Size( 869 | 431 + this.WidthOffset, 870 | numLines * 25 + 15 871 | ); 872 | 873 | this._descriptionBar.Draw(); 874 | this._descriptionRectangle.Draw(); 875 | this._descriptionText.Draw(); 876 | } 877 | } 878 | 879 | if (this.MenuItems.length <= this.MaxItemsOnScreen + 1) { 880 | let count = 0; 881 | for (const item of this.MenuItems) { 882 | item.SetVerticalPosition(count * 38 - 37 + this.extraOffset); 883 | item.Draw(); 884 | count++; 885 | } 886 | if (this._counterText && this.counterOverride) { 887 | this._counterText.caption = this.counterPretext + this.counterOverride; 888 | this._counterText.Draw(); 889 | } 890 | } else { 891 | let count = 0; 892 | for (let index = this._minItem; index <= this._maxItem; index++) { 893 | var item = this.MenuItems[index]; 894 | item.SetVerticalPosition(count * 38 - 37 + this.extraOffset); 895 | item.Draw(); 896 | count++; 897 | } 898 | this._extraRectangleUp.size = new Size(431 + this.WidthOffset, 18); 899 | this._extraRectangleDown.size = new Size(431 + this.WidthOffset, 18); 900 | this._upAndDownSprite.pos = new Point( 901 | 190 + this.offset.X + this.WidthOffset / 2, 902 | 147 + 903 | 37 * (this.MaxItemsOnScreen + 1) + 904 | this.offset.Y - 905 | 37 + 906 | this.extraOffset 907 | ); 908 | 909 | this._extraRectangleUp.Draw(); 910 | this._extraRectangleDown.Draw(); 911 | this._upAndDownSprite.Draw(); 912 | if (this._counterText) { 913 | if (!this.counterOverride) { 914 | const cap = this.CurrentSelection + 1 + " / " + this.MenuItems.length; 915 | this._counterText.caption = this.counterPretext + cap; 916 | } else { 917 | this._counterText.caption = 918 | this.counterPretext + this.counterOverride; 919 | } 920 | this._counterText.Draw(); 921 | } 922 | } 923 | 924 | this._logo.Draw(); 925 | } 926 | } 927 | 928 | exports.Menu = NativeUI; 929 | exports.UIMenuItem = UIMenuItem; 930 | exports.UIMenuListItem = UIMenuListItem; 931 | exports.UIMenuCheckboxItem = UIMenuCheckboxItem; 932 | exports.UIMenuSliderItem = UIMenuSliderItem; 933 | exports.BadgeStyle = BadgeStyle; 934 | exports.Point = Point; 935 | exports.Size = Size; 936 | exports.Color = Color; 937 | exports.Font = Font; 938 | exports.ItemsCollection = ItemsCollection; 939 | exports.ListItem = ListItem; 940 | -------------------------------------------------------------------------------- /items/UIMenuCheckboxItem.ts: -------------------------------------------------------------------------------- 1 | import BadgeStyle from "../enums/BadgeStyle"; 2 | import Sprite from "../modules/Sprite"; 3 | import Color from "../utils/Color"; 4 | import LiteEvent from "../utils/LiteEvent"; 5 | import Point from "../utils/Point"; 6 | import Size from "../utils/Size"; 7 | import UIMenuItem from "./UIMenuItem"; 8 | 9 | export default class UIMenuCheckboxItem extends UIMenuItem { 10 | private readonly _checkedSprite: Sprite; 11 | 12 | private readonly OnCheckedChanged = new LiteEvent(); 13 | 14 | public get CheckedChanged() { 15 | return this.OnCheckedChanged.expose(); 16 | } 17 | 18 | public Checked: boolean = false; 19 | 20 | constructor(text: string, check: boolean = false, description: string = "") { 21 | super(text, description); 22 | const y = 0; 23 | this._checkedSprite = new Sprite( 24 | "commonmenu", 25 | "shop_box_blank", 26 | new Point(410, y + 95), 27 | new Size(50, 50) 28 | ); 29 | this.Checked = check; 30 | } 31 | 32 | public SetVerticalPosition(y: number) { 33 | super.SetVerticalPosition(y); 34 | this._checkedSprite.pos = new Point( 35 | 380 + this.Offset.X + this.Parent.WidthOffset, 36 | y + 138 + this.Offset.Y 37 | ); 38 | } 39 | 40 | public Draw() { 41 | super.Draw(); 42 | this._checkedSprite.pos = this._checkedSprite.pos = new Point( 43 | 380 + this.Offset.X + this.Parent.WidthOffset, 44 | this._checkedSprite.pos.Y 45 | ); 46 | const isDefaultHightlitedForeColor = 47 | this.HighlightedForeColor == UIMenuItem.DefaultHighlightedForeColor; 48 | if (this.Selected && isDefaultHightlitedForeColor) { 49 | this._checkedSprite.TextureName = this.Checked 50 | ? "shop_box_tickb" 51 | : "shop_box_blankb"; 52 | } else { 53 | this._checkedSprite.TextureName = this.Checked 54 | ? "shop_box_tick" 55 | : "shop_box_blank"; 56 | } 57 | this._checkedSprite.color = this.Enabled 58 | ? this.Selected && !isDefaultHightlitedForeColor 59 | ? this.HighlightedForeColor 60 | : this.ForeColor 61 | : new Color(163, 159, 148); 62 | this._checkedSprite.Draw(); 63 | } 64 | 65 | public SetRightBadge(badge: BadgeStyle) { 66 | return this; 67 | } 68 | 69 | public SetRightLabel(text: string) { 70 | return this; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /items/UIMenuItem.ts: -------------------------------------------------------------------------------- 1 | import BadgeStyle from "../enums/BadgeStyle"; 2 | import Font from "../enums/Font"; 3 | import NativeUI from "../index"; 4 | import ResRectangle from "../modules/ResRectangle"; 5 | import ResText, { Alignment } from "../modules/ResText"; 6 | import Sprite from "../modules/Sprite"; 7 | import Color from "../utils/Color"; 8 | import Point from "../utils/Point"; 9 | import Size from "../utils/Size"; 10 | import UUIDV4 from "../utils/UUIDV4"; 11 | 12 | export default class UIMenuItem { 13 | public readonly Id: string = UUIDV4(); 14 | 15 | public static readonly DefaultBackColor: Color = Color.Empty; 16 | public static readonly DefaultHighlightedBackColor: Color = Color.White; 17 | public static readonly DefaultForeColor: Color = Color.WhiteSmoke; 18 | public static readonly DefaultHighlightedForeColor: Color = Color.Black; 19 | 20 | private _event: { event: string; args: any[] }; 21 | 22 | protected _rectangle: ResRectangle; 23 | protected _text: ResText; 24 | protected _selectedSprite: Sprite; 25 | 26 | protected _badgeLeft: Sprite; 27 | protected _badgeRight: Sprite; 28 | 29 | protected _labelText: ResText; 30 | 31 | public BackColor: Color = UIMenuItem.DefaultBackColor; 32 | public HighlightedBackColor: Color = UIMenuItem.DefaultHighlightedBackColor; 33 | 34 | public ForeColor: Color = UIMenuItem.DefaultForeColor; 35 | public HighlightedForeColor: Color = UIMenuItem.DefaultHighlightedForeColor; 36 | 37 | public Enabled: boolean; 38 | public Selected: boolean; 39 | public Hovered: boolean; 40 | public Description: string; 41 | 42 | public Offset: Point; 43 | public Parent: NativeUI; 44 | 45 | get Text() { 46 | return this._text.caption; 47 | } 48 | set Text(v) { 49 | this._text.caption = v; 50 | } 51 | 52 | public RightLabel: string = ""; 53 | public LeftBadge: BadgeStyle = BadgeStyle.None; 54 | public RightBadge: BadgeStyle = BadgeStyle.None; 55 | 56 | constructor(text, description = "") { 57 | this.Enabled = true; 58 | 59 | this._rectangle = new ResRectangle( 60 | new Point(0, 0), 61 | new Size(431, 38), 62 | new Color(150, 0, 0, 0) 63 | ); 64 | this._text = new ResText( 65 | text, 66 | new Point(8, 0), 67 | 0.33, 68 | Color.WhiteSmoke, 69 | Font.ChaletLondon, 70 | Alignment.Left 71 | ); 72 | this.Description = description; 73 | this._selectedSprite = new Sprite( 74 | "commonmenu", 75 | "gradient_nav", 76 | new Point(0, 0), 77 | new Size(431, 38) 78 | ); 79 | 80 | this._badgeLeft = new Sprite( 81 | "commonmenu", 82 | "", 83 | new Point(0, 0), 84 | new Size(40, 40) 85 | ); 86 | this._badgeRight = new Sprite( 87 | "commonmenu", 88 | "", 89 | new Point(0, 0), 90 | new Size(40, 40) 91 | ); 92 | 93 | this._labelText = new ResText( 94 | "", 95 | new Point(0, 0), 96 | 0.35, 97 | Color.White, 98 | 0, 99 | Alignment.Right 100 | ); 101 | } 102 | 103 | public SetVerticalPosition(y: number) { 104 | this._rectangle.pos = new Point(this.Offset.X, y + 144 + this.Offset.Y); 105 | this._selectedSprite.pos = new Point( 106 | 0 + this.Offset.X, 107 | y + 144 + this.Offset.Y 108 | ); 109 | this._text.pos = new Point(8 + this.Offset.X, y + 147 + this.Offset.Y); 110 | 111 | this._badgeLeft.pos = new Point(0 + this.Offset.X, y + 142 + this.Offset.Y); 112 | this._badgeRight.pos = new Point( 113 | 385 + this.Offset.X, 114 | y + 142 + this.Offset.Y 115 | ); 116 | 117 | this._labelText.pos = new Point( 118 | 420 + this.Offset.X, 119 | y + 148 + this.Offset.Y 120 | ); 121 | } 122 | 123 | public addEvent(event: string, ...args: any[]) { 124 | this._event = { event: event, args: args }; 125 | } 126 | 127 | public fireEvent() { 128 | if (this._event) { 129 | mp.events.call(this._event.event, this, ...this._event.args); 130 | } 131 | } 132 | 133 | Draw() { 134 | this._rectangle.size = new Size(431 + this.Parent.WidthOffset, 38); 135 | this._selectedSprite.size = new Size(431 + this.Parent.WidthOffset, 38); 136 | 137 | if (this.Hovered && !this.Selected) { 138 | this._rectangle.color = new Color(255, 255, 255, 20); 139 | this._rectangle.Draw(); 140 | } 141 | 142 | this._selectedSprite.color = this.Selected 143 | ? this.HighlightedBackColor 144 | : this.BackColor; 145 | this._selectedSprite.Draw(); 146 | 147 | this._text.color = this.Enabled 148 | ? this.Selected 149 | ? this.HighlightedForeColor 150 | : this.ForeColor 151 | : new Color(163, 159, 148); 152 | 153 | if (this.LeftBadge != BadgeStyle.None) { 154 | this._text.pos = new Point(35 + this.Offset.X, this._text.pos.Y); 155 | this._badgeLeft.TextureDict = this.BadgeToSpriteLib(this.LeftBadge); 156 | this._badgeLeft.TextureName = this.BadgeToSpriteName( 157 | this.LeftBadge, 158 | this.Selected 159 | ); 160 | this._badgeLeft.color = this.IsBagdeWhiteSprite(this.LeftBadge) 161 | ? this.Enabled 162 | ? this.Selected 163 | ? this.HighlightedForeColor 164 | : this.ForeColor 165 | : new Color(163, 159, 148) 166 | : Color.White; 167 | this._badgeLeft.Draw(); 168 | } else { 169 | this._text.pos = new Point(8 + this.Offset.X, this._text.pos.Y); 170 | } 171 | 172 | if (this.RightBadge != BadgeStyle.None) { 173 | this._badgeRight.pos = new Point( 174 | 385 + this.Offset.X + this.Parent.WidthOffset, 175 | this._badgeRight.pos.Y 176 | ); 177 | this._badgeRight.TextureDict = this.BadgeToSpriteLib(this.RightBadge); 178 | this._badgeRight.TextureName = this.BadgeToSpriteName( 179 | this.RightBadge, 180 | this.Selected 181 | ); 182 | this._badgeRight.color = this.IsBagdeWhiteSprite(this.RightBadge) 183 | ? this.Enabled 184 | ? this.Selected 185 | ? this.HighlightedForeColor 186 | : this.ForeColor 187 | : new Color(163, 159, 148) 188 | : Color.White; 189 | this._badgeRight.Draw(); 190 | } 191 | 192 | if (this.RightLabel && this.RightLabel !== "") { 193 | this._labelText.pos = new Point( 194 | 420 + this.Offset.X + this.Parent.WidthOffset, 195 | this._labelText.pos.Y 196 | ); 197 | this._labelText.caption = this.RightLabel; 198 | this._labelText.color = this._text.color = this.Enabled 199 | ? this.Selected 200 | ? this.HighlightedForeColor 201 | : this.ForeColor 202 | : new Color(163, 159, 148); 203 | this._labelText.Draw(); 204 | } 205 | this._text.Draw(); 206 | } 207 | 208 | SetLeftBadge(badge: BadgeStyle) { 209 | this.LeftBadge = badge; 210 | } 211 | 212 | SetRightBadge(badge: BadgeStyle) { 213 | this.RightBadge = badge; 214 | } 215 | 216 | SetRightLabel(text: string) { 217 | this.RightLabel = text; 218 | } 219 | 220 | BadgeToSpriteLib(badge: BadgeStyle) { 221 | return "commonmenu"; 222 | } 223 | 224 | BadgeToSpriteName(badge: BadgeStyle, selected: boolean) { 225 | switch (badge) { 226 | case BadgeStyle.None: 227 | return ""; 228 | case BadgeStyle.BronzeMedal: 229 | return "mp_medal_bronze"; 230 | case BadgeStyle.GoldMedal: 231 | return "mp_medal_gold"; 232 | case BadgeStyle.SilverMedal: 233 | return "medal_silver"; 234 | case BadgeStyle.Alert: 235 | return "mp_alerttriangle"; 236 | case BadgeStyle.Crown: 237 | return "mp_hostcrown"; 238 | case BadgeStyle.Ammo: 239 | return selected ? "shop_ammo_icon_b" : "shop_ammo_icon_a"; 240 | case BadgeStyle.Armour: 241 | return selected ? "shop_armour_icon_b" : "shop_armour_icon_a"; 242 | case BadgeStyle.Barber: 243 | return selected ? "shop_barber_icon_b" : "shop_barber_icon_a"; 244 | case BadgeStyle.Clothes: 245 | return selected ? "shop_clothing_icon_b" : "shop_clothing_icon_a"; 246 | case BadgeStyle.Franklin: 247 | return selected ? "shop_franklin_icon_b" : "shop_franklin_icon_a"; 248 | case BadgeStyle.Bike: 249 | return selected ? "shop_garage_bike_icon_b" : "shop_garage_bike_icon_a"; 250 | case BadgeStyle.Car: 251 | return selected ? "shop_garage_icon_b" : "shop_garage_icon_a"; 252 | case BadgeStyle.Gun: 253 | return selected ? "shop_gunclub_icon_b" : "shop_gunclub_icon_a"; 254 | case BadgeStyle.Heart: 255 | return selected ? "shop_health_icon_b" : "shop_health_icon_a"; 256 | case BadgeStyle.Lock: 257 | return "shop_lock"; 258 | case BadgeStyle.Makeup: 259 | return selected ? "shop_makeup_icon_b" : "shop_makeup_icon_a"; 260 | case BadgeStyle.Mask: 261 | return selected ? "shop_mask_icon_b" : "shop_mask_icon_a"; 262 | case BadgeStyle.Michael: 263 | return selected ? "shop_michael_icon_b" : "shop_michael_icon_a"; 264 | case BadgeStyle.Star: 265 | return "shop_new_star"; 266 | case BadgeStyle.Tatoo: 267 | return selected ? "shop_tattoos_icon_b" : "shop_tattoos_icon_"; 268 | case BadgeStyle.Tick: 269 | return "shop_tick_icon"; 270 | case BadgeStyle.Trevor: 271 | return selected ? "shop_trevor_icon_b" : "shop_trevor_icon_a"; 272 | default: 273 | return ""; 274 | } 275 | } 276 | 277 | IsBagdeWhiteSprite(badge: BadgeStyle) { 278 | switch (badge) { 279 | case BadgeStyle.Lock: 280 | case BadgeStyle.Tick: 281 | case BadgeStyle.Crown: 282 | return true; 283 | default: 284 | return false; 285 | } 286 | } 287 | 288 | BadgeToColor(badge: BadgeStyle, selected: boolean): Color { 289 | switch (badge) { 290 | case BadgeStyle.Lock: 291 | case BadgeStyle.Tick: 292 | case BadgeStyle.Crown: 293 | return selected 294 | ? new Color(255, 0, 0, 0) 295 | : new Color(255, 255, 255, 255); 296 | default: 297 | return new Color(255, 255, 255, 255); 298 | } 299 | } 300 | } 301 | -------------------------------------------------------------------------------- /items/UIMenuListItem.ts: -------------------------------------------------------------------------------- 1 | import BadgeStyle from "../enums/BadgeStyle"; 2 | import Font from "../enums/Font"; 3 | import ItemsCollection from "../modules/ItemsCollection"; 4 | import ListItem from "../modules/ListItem"; 5 | import ResText, { Alignment } from "../modules/ResText"; 6 | import Sprite from "../modules/Sprite"; 7 | import Color from "../utils/Color"; 8 | import LiteEvent from "../utils/LiteEvent"; 9 | import Point from "../utils/Point"; 10 | import Size from "../utils/Size"; 11 | import StringMeasurer from "../modules/StringMeasurer"; 12 | import UIMenuItem from "./UIMenuItem"; 13 | 14 | export default class UIMenuListItem extends UIMenuItem { 15 | protected _itemText: ResText; 16 | 17 | protected _arrowLeft: Sprite; 18 | protected _arrowRight: Sprite; 19 | 20 | private _holdTime: number; 21 | 22 | private currOffset: number = 0; 23 | 24 | private collection: Array = []; 25 | 26 | get Collection() { 27 | return this.collection; 28 | } 29 | set Collection(v) { 30 | if (!v) throw new Error("The collection can't be null"); 31 | this.collection = v; 32 | } 33 | 34 | set SelectedItem(v: ListItem) { 35 | const idx = this.Collection.findIndex(li => li.Id === v.Id); 36 | if (idx > 0) this.Index = idx; 37 | else this.Index = 0; 38 | } 39 | 40 | get SelectedItem() { 41 | return this.Collection.length > 0 ? this.Collection[this.Index] : null; 42 | } 43 | 44 | get SelectedValue() { 45 | return this.SelectedItem == null 46 | ? null 47 | : this.SelectedItem.Data == null 48 | ? this.SelectedItem.DisplayText 49 | : this.SelectedItem.Data; 50 | } 51 | 52 | public ScrollingEnabled: boolean = true; 53 | 54 | public HoldTimeBeforeScroll: number = 200; 55 | 56 | private readonly OnListChanged = new LiteEvent(); 57 | 58 | public get ListChanged() { 59 | return this.OnListChanged.expose(); 60 | } 61 | 62 | protected _index: number = 0; 63 | 64 | get Index() { 65 | if (this.Collection == null) return -1; 66 | if (this.Collection != null && this.Collection.length == 0) return -1; 67 | 68 | return this._index % this.Collection.length; 69 | } 70 | set Index(value) { 71 | if (this.Collection == null) return; 72 | if (this.Collection != null && this.Collection.length == 0) return; 73 | 74 | this._index = 100000 - (100000 % this.Collection.length) + value; 75 | 76 | const caption = 77 | this.Collection.length >= this.Index 78 | ? this.Collection[this.Index].DisplayText 79 | : " "; 80 | this.currOffset = StringMeasurer.MeasureString(caption); 81 | } 82 | 83 | constructor( 84 | text: string, 85 | description: string = "", 86 | collection: ItemsCollection = new ItemsCollection([]), 87 | startIndex: number = 0 88 | ) { 89 | super(text, description); 90 | let y = 0; 91 | this.Collection = collection.getListItems(); 92 | this.Index = startIndex; 93 | this._arrowLeft = new Sprite( 94 | "commonmenu", 95 | "arrowleft", 96 | new Point(110, 105 + y), 97 | new Size(30, 30) 98 | ); 99 | this._arrowRight = new Sprite( 100 | "commonmenu", 101 | "arrowright", 102 | new Point(280, 105 + y), 103 | new Size(30, 30) 104 | ); 105 | this._itemText = new ResText( 106 | "", 107 | new Point(290, y + 104), 108 | 0.35, 109 | Color.White, 110 | Font.ChaletLondon, 111 | Alignment.Right 112 | ); 113 | } 114 | 115 | public setCollection(collection: ItemsCollection) { 116 | this.Collection = collection.getListItems(); 117 | } 118 | 119 | public setCollectionItem( 120 | index: number, 121 | item: ListItem | string, 122 | resetSelection: boolean = true 123 | ) { 124 | if (index > this.Collection.length) 125 | // Placeholder for formatting 126 | throw new Error("Index out of bounds"); 127 | if (typeof item === "string") 128 | // Placeholder for formatting 129 | item = new ListItem(item); 130 | 131 | this.Collection.splice(index, 1, item); 132 | 133 | if (resetSelection) 134 | // Placeholder for formatting 135 | this.Index = 0; 136 | } 137 | 138 | public SetVerticalPosition(y: number) { 139 | this._arrowLeft.pos = new Point( 140 | 300 + this.Offset.X + this.Parent.WidthOffset, 141 | 147 + y + this.Offset.Y 142 | ); 143 | this._arrowRight.pos = new Point( 144 | 400 + this.Offset.X + this.Parent.WidthOffset, 145 | 147 + y + this.Offset.Y 146 | ); 147 | this._itemText.pos = new Point( 148 | 300 + this.Offset.X + this.Parent.WidthOffset, 149 | y + 147 + this.Offset.Y 150 | ); 151 | super.SetVerticalPosition(y); 152 | } 153 | 154 | public SetRightLabel(text: string) { 155 | return this; 156 | } 157 | 158 | public SetRightBadge(badge: BadgeStyle) { 159 | return this; 160 | } 161 | 162 | public Draw() { 163 | super.Draw(); 164 | const caption = 165 | this.Collection.length >= this.Index 166 | ? this.Collection[this.Index].DisplayText 167 | : " "; 168 | const offset = this.currOffset; 169 | 170 | this._itemText.color = this.Enabled 171 | ? this.Selected 172 | ? this.HighlightedForeColor 173 | : this.ForeColor 174 | : new Color(163, 159, 148); 175 | 176 | this._itemText.caption = caption; 177 | 178 | this._arrowLeft.color = this.Enabled 179 | ? this.Selected 180 | ? this.HighlightedForeColor 181 | : this.ForeColor 182 | : new Color(163, 159, 148); 183 | this._arrowRight.color = this.Enabled 184 | ? this.Selected 185 | ? this.HighlightedForeColor 186 | : this.ForeColor 187 | : new Color(163, 159, 148); 188 | 189 | this._arrowLeft.pos = new Point( 190 | 375 - offset + this.Offset.X + this.Parent.WidthOffset, 191 | this._arrowLeft.pos.Y 192 | ); 193 | 194 | if (this.Selected) { 195 | this._arrowLeft.Draw(); 196 | this._arrowRight.Draw(); 197 | this._itemText.pos = new Point( 198 | 405 + this.Offset.X + this.Parent.WidthOffset, 199 | this._itemText.pos.Y 200 | ); 201 | } else { 202 | this._itemText.pos = new Point( 203 | 420 + this.Offset.X + this.Parent.WidthOffset, 204 | this._itemText.pos.Y 205 | ); 206 | } 207 | this._itemText.Draw(); 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /items/UIMenuSliderItem.ts: -------------------------------------------------------------------------------- 1 | import BadgeStyle from "../enums/BadgeStyle"; 2 | import ResRectangle from "../modules/ResRectangle"; 3 | import Sprite from "../modules/Sprite"; 4 | import Color from "../utils/Color"; 5 | import Point from "../utils/Point"; 6 | import Size from "../utils/Size"; 7 | import UIMenuItem from "./UIMenuItem"; 8 | 9 | export default class UIMenuSliderItem extends UIMenuItem { 10 | private _arrowLeft: Sprite; 11 | private _arrowRight: Sprite; 12 | 13 | private _rectangleBackground: ResRectangle; 14 | private _rectangleSlider: ResRectangle; 15 | private _rectangleDivider: ResRectangle; 16 | 17 | private _items: any[]; 18 | 19 | private _index: number; 20 | 21 | get Index() { 22 | return this._index % this._items.length; 23 | } 24 | set Index(value) { 25 | this._index = 100000000 - (100000000 % this._items.length) + value; 26 | } 27 | 28 | constructor( 29 | text: string, 30 | items: any[], 31 | index: number, 32 | description: string = "", 33 | divider: boolean = false 34 | ) { 35 | super(text, description); 36 | const y: number = 0; 37 | this._items = items; 38 | this._arrowLeft = new Sprite( 39 | "commonmenutu", 40 | "arrowleft", 41 | new Point(0, 105 + y), 42 | new Size(15, 15) 43 | ); 44 | this._arrowRight = new Sprite( 45 | "commonmenutu", 46 | "arrowright", 47 | new Point(0, 105 + y), 48 | new Size(15, 15) 49 | ); 50 | this._rectangleBackground = new ResRectangle( 51 | new Point(0, 0), 52 | new Size(150, 9), 53 | new Color(4, 32, 57, 255) 54 | ); 55 | this._rectangleSlider = new ResRectangle( 56 | new Point(0, 0), 57 | new Size(75, 9), 58 | new Color(57, 116, 200, 255) 59 | ); 60 | if (divider) { 61 | this._rectangleDivider = new ResRectangle( 62 | new Point(0, 0), 63 | new Size(2.5, 20), 64 | Color.WhiteSmoke 65 | ); 66 | } else { 67 | this._rectangleDivider = new ResRectangle( 68 | new Point(0, 0), 69 | new Size(2.5, 20), 70 | Color.Transparent 71 | ); 72 | } 73 | this.Index = index; 74 | } 75 | 76 | public SetVerticalPosition(y: number) { 77 | this._rectangleBackground.pos = new Point( 78 | 250 + this.Offset.X + this.Parent.WidthOffset, 79 | y + 158.5 + this.Offset.Y 80 | ); 81 | this._rectangleSlider.pos = new Point( 82 | 250 + this.Offset.X + this.Parent.WidthOffset, 83 | y + 158.5 + this.Offset.Y 84 | ); 85 | this._rectangleDivider.pos = new Point( 86 | 323.5 + this.Offset.X + this.Parent.WidthOffset, 87 | y + 153 + this.Offset.Y 88 | ); 89 | this._arrowLeft.pos = new Point( 90 | 235 + this.Offset.X + this.Parent.WidthOffset, 91 | 155.5 + y + this.Offset.Y 92 | ); 93 | this._arrowRight.pos = new Point( 94 | 400 + this.Offset.X + this.Parent.WidthOffset, 95 | 155.5 + y + this.Offset.Y 96 | ); 97 | 98 | super.SetVerticalPosition(y); 99 | } 100 | 101 | public IndexToItem(index: number) { 102 | return this._items[index]; 103 | } 104 | 105 | public Draw() { 106 | super.Draw(); 107 | this._arrowLeft.color = this.Enabled 108 | ? this.Selected 109 | ? Color.Black 110 | : Color.WhiteSmoke 111 | : new Color(163, 159, 148); 112 | this._arrowRight.color = this.Enabled 113 | ? this.Selected 114 | ? Color.Black 115 | : Color.WhiteSmoke 116 | : new Color(163, 159, 148); 117 | let offset = 118 | ((this._rectangleBackground.size.Width - 119 | this._rectangleSlider.size.Width) / 120 | (this._items.length - 1)) * 121 | this.Index; 122 | this._rectangleSlider.pos = new Point( 123 | 250 + this.Offset.X + offset + +this.Parent.WidthOffset, 124 | this._rectangleSlider.pos.Y 125 | ); 126 | if (this.Selected) { 127 | this._arrowLeft.Draw(); 128 | this._arrowRight.Draw(); 129 | } else { 130 | } 131 | this._rectangleBackground.Draw(); 132 | this._rectangleSlider.Draw(); 133 | this._rectangleDivider.Draw(); 134 | } 135 | 136 | public SetRightBadge(badge: BadgeStyle) {} 137 | 138 | public SetRightLabel(text: string) {} 139 | } 140 | -------------------------------------------------------------------------------- /modules/Container.ts: -------------------------------------------------------------------------------- 1 | import Size from "../utils/Size"; 2 | import Rectangle from "./Rectangle"; 3 | import { Screen } from "../utils/Screen"; 4 | export default class Container extends Rectangle { 5 | public Items: any[]; 6 | 7 | constructor(pos, size, color) { 8 | super(pos, size, color); 9 | this.Items = []; 10 | } 11 | 12 | addItem(item) { 13 | this.Items.push(item); 14 | } 15 | 16 | Draw(offset?) { 17 | if (!this.enabled) return; 18 | offset = offset || new Size(); 19 | const screenw = Screen.width; 20 | const screenh = Screen.height; 21 | const height = 1080.0; 22 | const ratio = screenw / screenh; 23 | const width = height * ratio; 24 | 25 | const w = this.size.Width / width; 26 | const h = this.size.Height / height; 27 | const x = (this.pos.X + offset.Width) / width + w * 0.5; 28 | const y = (this.pos.Y + offset.Height) / height + h * 0.5; 29 | 30 | mp.game.graphics.drawRect( 31 | x, 32 | y, 33 | w, 34 | h, 35 | this.color.R, 36 | this.color.G, 37 | this.color.B, 38 | this.color.A 39 | ); 40 | 41 | for (var item of this.Items) 42 | item.Draw( 43 | new Size(this.pos.X + offset.Width, this.pos.Y + offset.Height) 44 | ); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /modules/IElement.ts: -------------------------------------------------------------------------------- 1 | export default class IElement { 2 | public enabled: boolean; 3 | constructor() { 4 | this.enabled = true; 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /modules/ItemsCollection.ts: -------------------------------------------------------------------------------- 1 | import ListItem from "../modules/ListItem"; 2 | 3 | export default class ItemsCollection { 4 | private items: any[]; 5 | 6 | constructor(items: any[]) { 7 | if (items.length === 0) throw new Error("ItemsCollection cannot be empty"); 8 | this.items = items; 9 | } 10 | 11 | public length() { 12 | return this.items.length; 13 | } 14 | 15 | public getListItems() { 16 | const items = []; 17 | for (const item of this.items) { 18 | if (item instanceof ListItem) { 19 | items.push(item); 20 | } else if (typeof item == "string") { 21 | items.push(new ListItem(item.toString())); 22 | } 23 | } 24 | return items; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /modules/ListItem.ts: -------------------------------------------------------------------------------- 1 | import UUIDV4 from "../utils/UUIDV4"; 2 | 3 | export default class ListItem { 4 | public readonly Id: string = UUIDV4(); 5 | 6 | public DisplayText: string; 7 | public Data: any; 8 | 9 | constructor(text: string = "", data: any = null) { 10 | this.DisplayText = text; 11 | this.Data = data; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /modules/Rectangle.ts: -------------------------------------------------------------------------------- 1 | import Color from "../utils/Color"; 2 | import Point from "../utils/Point"; 3 | import Size from "../utils/Size"; 4 | import IElement from "./IElement"; 5 | 6 | export default class Rectangle extends IElement { 7 | public pos: Point; 8 | public size: Size; 9 | public color: Color; 10 | constructor(pos, size, color) { 11 | super(); 12 | this.enabled = true; 13 | this.pos = pos; 14 | this.size = size; 15 | this.color = color; 16 | } 17 | 18 | Draw(pos, size, color) { 19 | if (!pos) pos = new Size(0, 0); 20 | if (!size && !color) { 21 | pos = new Point(this.pos.X + pos.Width, this.pos.Y + pos.Height); 22 | size = this.size; 23 | color = this.color; 24 | } 25 | const w = size.Width / 1280.0; 26 | const h = size.Height / 720.0; 27 | const x = pos.X / 1280.0 + w * 0.5; 28 | const y = pos.Y / 720.0 + h * 0.5; 29 | 30 | mp.game.graphics.drawRect(x, y, w, h, color.R, color.G, color.B, color.A); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /modules/ResRectangle.ts: -------------------------------------------------------------------------------- 1 | import Point from "../utils/Point"; 2 | import Size from "../utils/Size"; 3 | import Rectangle from "./Rectangle"; 4 | import { Screen } from "../utils/Screen"; 5 | 6 | export default class ResRectangle extends Rectangle { 7 | constructor(pos, size, color) { 8 | super(pos, size, color); 9 | } 10 | 11 | public Draw(): void; 12 | public Draw(offset): void; 13 | public Draw(pos, size, color): void; 14 | 15 | Draw(pos?, size?, color?) { 16 | if (!pos) pos = new Size(); 17 | if (pos && !size && !color) { 18 | pos = new Point(this.pos.X + pos.Width, this.pos.Y + pos.Height); 19 | size = this.size; 20 | color = this.color; 21 | } 22 | 23 | const screenw = Screen.width; 24 | const screenh = Screen.height; 25 | const height = 1080.0; 26 | const ratio = screenw / screenh; 27 | const width = height * ratio; 28 | 29 | const w = size.Width / width; 30 | const h = size.Height / height; 31 | const x = pos.X / width + w * 0.5; 32 | const y = pos.Y / height + h * 0.5; 33 | 34 | mp.game.graphics.drawRect(x, y, w, h, color.R, color.G, color.B, color.A); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /modules/ResText.ts: -------------------------------------------------------------------------------- 1 | import Color from "../utils/Color"; 2 | import Point from "../utils/Point"; 3 | import Size from "../utils/Size"; 4 | import Text from "./Text"; 5 | import { Screen } from "../utils/Screen"; 6 | 7 | export enum Alignment { 8 | Left, 9 | Centered, 10 | Right 11 | } 12 | 13 | export default class ResText extends Text { 14 | public TextAlignment: Alignment = Alignment.Left; 15 | public DropShadow: boolean; 16 | public Outline: boolean; 17 | public WordWrap: Size; 18 | 19 | constructor(caption, pos, scale, color?, font?, justify?) { 20 | super( 21 | caption, 22 | pos, 23 | scale, 24 | color || new Color(255, 255, 255), 25 | font || 0, 26 | false 27 | ); 28 | if (justify) this.TextAlignment = justify; 29 | } 30 | 31 | public Draw(): void; 32 | public Draw(offset: Size): void; 33 | public Draw(caption, pos, scale, color, font, arg2): void; 34 | 35 | Draw( 36 | arg1?, 37 | pos?, 38 | scale?, 39 | color?, 40 | font?, 41 | arg2?, 42 | dropShadow?, 43 | outline?, 44 | wordWrap? 45 | ) { 46 | let caption = arg1; 47 | let centered = arg2; 48 | let textAlignment = arg2; 49 | if (!arg1) arg1 = new Size(0, 0); 50 | if (arg1 && !pos) { 51 | textAlignment = this.TextAlignment; 52 | caption = this.caption; 53 | pos = new Point(this.pos.X + arg1.Width, this.pos.Y + arg1.Height); 54 | scale = this.scale; 55 | color = this.color; 56 | font = this.font; 57 | if (centered == true || centered == false) { 58 | centered = this.centered; 59 | } else { 60 | centered = undefined; 61 | dropShadow = this.DropShadow; 62 | outline = this.Outline; 63 | wordWrap = this.WordWrap; 64 | } 65 | } 66 | 67 | const screenw = Screen.width; 68 | const screenh = Screen.height; 69 | 70 | const height = 1080.0; 71 | const ratio = screenw / screenh; 72 | const width = height * ratio; 73 | 74 | const x = this.pos.X / width; 75 | const y = this.pos.Y / height; 76 | 77 | mp.game.ui.setTextFont(parseInt(font)); 78 | mp.game.ui.setTextScale(1.0, scale); 79 | mp.game.ui.setTextColour(color.R, color.G, color.B, color.A); 80 | 81 | if (centered !== undefined) { 82 | mp.game.ui.setTextCentre(centered); 83 | } else { 84 | if (dropShadow) mp.game.ui.setTextDropshadow(2, 0, 0, 0, 0); 85 | 86 | if (outline) console.warn("not working!"); 87 | 88 | switch (textAlignment) { 89 | case Alignment.Centered: 90 | mp.game.ui.setTextCentre(true); 91 | break; 92 | case Alignment.Right: 93 | mp.game.ui.setTextRightJustify(true); 94 | mp.game.ui.setTextWrap(0.0, x); 95 | break; 96 | } 97 | 98 | if (wordWrap) { 99 | const xsize = (this.pos.X + wordWrap.Width) / width; 100 | mp.game.ui.setTextWrap(x, xsize); 101 | } 102 | } 103 | 104 | mp.game.ui.setTextEntry("STRING"); 105 | ResText.AddLongString(caption); 106 | mp.game.ui.drawText(x, y); 107 | } 108 | 109 | public static AddLongString(str: string) { 110 | const strLen = 99; 111 | for (var i = 0; i < str.length; i += strLen) { 112 | const substr = str.substr(i, Math.min(strLen, str.length - i)); 113 | mp.game.ui.addTextComponentSubstringPlayerName(substr); 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /modules/Sprite.ts: -------------------------------------------------------------------------------- 1 | import Color from "../utils/Color"; 2 | import Point from "../utils/Point"; 3 | import Size from "../utils/Size"; 4 | import { Screen } from "../utils/Screen"; 5 | 6 | export default class Sprite { 7 | public TextureName: string; 8 | public pos: Point; 9 | public size: Size; 10 | public heading: number; 11 | public color: Color; 12 | public visible: boolean; 13 | private _textureDict: string; 14 | 15 | constructor( 16 | textureDict, 17 | textureName, 18 | pos, 19 | size, 20 | heading = 0, 21 | color = new Color(255, 255, 255) 22 | ) { 23 | this.TextureDict = textureDict; 24 | this.TextureName = textureName; 25 | this.pos = pos; 26 | this.size = size; 27 | this.heading = heading; 28 | this.color = color; 29 | this.visible = true; 30 | } 31 | 32 | LoadTextureDictionary() { 33 | mp.game.graphics.requestStreamedTextureDict(this._textureDict, true); 34 | while (!this.IsTextureDictionaryLoaded) { 35 | //@ts-ignore 36 | mp.game.wait(0); 37 | } 38 | } 39 | 40 | set TextureDict(v) { 41 | this._textureDict = v; 42 | if (!this.IsTextureDictionaryLoaded) this.LoadTextureDictionary(); 43 | } 44 | get TextureDict(): string { 45 | return this._textureDict; 46 | } 47 | 48 | get IsTextureDictionaryLoaded() { 49 | return mp.game.graphics.hasStreamedTextureDictLoaded(this._textureDict); 50 | } 51 | 52 | Draw( 53 | textureDictionary?, 54 | textureName?, 55 | pos?, 56 | size?, 57 | heading?, 58 | color?, 59 | loadTexture? 60 | ) { 61 | textureDictionary = textureDictionary || this.TextureDict; 62 | textureName = textureName || this.TextureName; 63 | pos = pos || this.pos; 64 | size = size || this.size; 65 | heading = heading || this.heading; 66 | color = color || this.color; 67 | loadTexture = loadTexture || true; 68 | 69 | if (loadTexture) { 70 | if (!mp.game.graphics.hasStreamedTextureDictLoaded(textureDictionary)) 71 | mp.game.graphics.requestStreamedTextureDict(textureDictionary, true); 72 | } 73 | 74 | const screenw = Screen.width; 75 | const screenh = Screen.height; 76 | const height = 1080.0; 77 | const ratio = screenw / screenh; 78 | const width = height * ratio; 79 | 80 | const w = this.size.Width / width; 81 | const h = this.size.Height / height; 82 | const x = this.pos.X / width + w * 0.5; 83 | const y = this.pos.Y / height + h * 0.5; 84 | 85 | mp.game.graphics.drawSprite( 86 | textureDictionary, 87 | textureName, 88 | x, 89 | y, 90 | w, 91 | h, 92 | heading, 93 | color.R, 94 | color.G, 95 | color.B, 96 | color.A 97 | ); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /modules/StringMeasurer.ts: -------------------------------------------------------------------------------- 1 | import { Screen } from "../utils/Screen"; 2 | import ResText from "./ResText"; 3 | 4 | export default class StringMeasurer { 5 | public static MeasureStringWidthNoConvert(input: string) { 6 | mp.game.ui.setTextEntryForWidth("STRING"); 7 | ResText.AddLongString(input); 8 | mp.game.ui.setTextFont(0); 9 | mp.game.ui.setTextScale(0.35, 0.35); 10 | return mp.game.ui.getTextScreenWidth(false); 11 | } 12 | 13 | public static MeasureString(str: string) { 14 | const screenw = Screen.width; 15 | const screenh = Screen.height; 16 | const height = 1080.0; 17 | const ratio = screenw / screenh; 18 | const width = height * ratio; 19 | return this.MeasureStringWidthNoConvert(str) * width; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /modules/Text.ts: -------------------------------------------------------------------------------- 1 | import Color from "../utils/Color"; 2 | import Point from "../utils/Point"; 3 | import IElement from "./IElement"; 4 | import ResText from "./ResText"; 5 | 6 | export default class Text extends IElement { 7 | public caption: string; 8 | public pos: Point; 9 | public scale: number; 10 | public color: Color; 11 | public font: number; 12 | public centered: boolean; 13 | constructor(caption, pos, scale, color, font, centered) { 14 | super(); 15 | this.caption = caption; 16 | this.pos = pos; 17 | this.scale = scale; 18 | this.color = color || new Color(255, 255, 255, 255); 19 | this.font = font || 0; 20 | this.centered = centered || false; 21 | } 22 | 23 | Draw(caption, pos, scale, color, font, centered) { 24 | if (caption && !pos && !scale && !color && !font && !centered) { 25 | pos = new Point(this.pos.X + caption.Width, this.pos.Y + caption.Height); 26 | scale = this.scale; 27 | color = this.color; 28 | font = this.font; 29 | centered = this.centered; 30 | } 31 | const x = pos.X / 1280.0; 32 | const y = pos.Y / 720.0; 33 | 34 | mp.game.ui.setTextFont(parseInt(font)); 35 | mp.game.ui.setTextScale(scale, scale); 36 | mp.game.ui.setTextColour(color.R, color.G, color.B, color.A); 37 | mp.game.ui.setTextCentre(centered); 38 | mp.game.ui.setTextEntry("STRING"); 39 | ResText.AddLongString(caption); 40 | mp.game.ui.drawText(x, y); 41 | } 42 | } 43 | 44 | exports = Text; 45 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "NativeUI", 3 | "version": "1.0.0", 4 | "description": "RageMP NativeUI", 5 | "main": "index.js", 6 | "repository": "https://github.com/XMGamingMaster/RageMP-NativeUI.git", 7 | "author": "GamingMaster", 8 | "license": "Apache-2.0", 9 | "private": false, 10 | "dependencies": {}, 11 | "devDependencies": { 12 | "@types/node": "^10.12.12", 13 | "@types/ragemp-c": "github:CocaColaBear/types-ragemp-c#master", 14 | "gulp": "^4.0.0", 15 | "gulp-copy": "^4.0.1", 16 | "ts-loader": "^5.3.1", 17 | "typescript": "^3.2.1", 18 | "webpack-stream": "^5.2.1" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "es2015", 4 | "removeComments": true, 5 | "target": "es6" 6 | }, 7 | "include": ["**/*.ts"] 8 | } 9 | -------------------------------------------------------------------------------- /utils/Color.ts: -------------------------------------------------------------------------------- 1 | export default class Color { 2 | public static Empty = new Color(0, 0, 0, 0); 3 | public static Transparent = new Color(0, 0, 0, 0); 4 | public static Black = new Color(0, 0, 0, 255); 5 | public static White = new Color(255, 255, 255, 255); 6 | public static WhiteSmoke = new Color(245, 245, 245, 255); 7 | 8 | public R: number; 9 | public G: number; 10 | public B: number; 11 | public A: number; 12 | constructor(r, g, b, a = 255) { 13 | this.R = r; 14 | this.G = g; 15 | this.B = b; 16 | this.A = a; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /utils/Common.ts: -------------------------------------------------------------------------------- 1 | export default class Common { 2 | public static PlaySound(audioName: string, audioRef: string) { 3 | mp.game.audio.playSound(-1, audioName, audioRef, false, 0, true); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /utils/LiteEvent.ts: -------------------------------------------------------------------------------- 1 | interface ILiteEvent { 2 | on(handler: { (...args: any[]): void }): void; 3 | off(handler: { (...args: any[]): void }): void; 4 | } 5 | 6 | export default class LiteEvent implements ILiteEvent { 7 | private handlers: { (...args: any[]): void }[] = []; 8 | 9 | public on(handler: { (...args: any[]): void }): void { 10 | this.handlers.push(handler); 11 | } 12 | 13 | public off(handler: { (...args: any[]): void }): void { 14 | this.handlers = this.handlers.filter(h => h !== handler); 15 | } 16 | 17 | public emit(...args: any[]) { 18 | this.handlers.slice(0).forEach(h => h(...args)); 19 | } 20 | 21 | public expose(): ILiteEvent { 22 | return this; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /utils/Point.ts: -------------------------------------------------------------------------------- 1 | export default class Point { 2 | public X: number = 0; 3 | public Y: number = 0; 4 | constructor(x: number, y: number) { 5 | this.X = x; 6 | this.Y = y; 7 | } 8 | 9 | static Parse(point: number[]): Point; 10 | static Parse(point: { X: number; Y: number }): Point; 11 | static Parse(arg): Point { 12 | if (typeof arg === "object") { 13 | if (arg.length) { 14 | // Array 15 | return new Point(arg[0], arg[1]); 16 | } else if (arg.X && arg.Y) { 17 | // Object 18 | return new Point(arg.X, arg.Y); 19 | } 20 | } else if (typeof arg === "string") { 21 | if (arg.indexOf(",") !== -1) { 22 | const arr = arg.split(","); 23 | return new Point(parseFloat(arr[0]), parseFloat(arr[1])); 24 | } 25 | } 26 | return new Point(0, 0); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /utils/Screen.ts: -------------------------------------------------------------------------------- 1 | const gameScreen = mp.game.graphics.getScreenActiveResolution(0, 0); 2 | export const Screen = { 3 | width: gameScreen.x, 4 | height: gameScreen.y 5 | }; 6 | -------------------------------------------------------------------------------- /utils/Size.ts: -------------------------------------------------------------------------------- 1 | export default class Size { 2 | public Width: number; 3 | public Height: number; 4 | constructor(w: number = 0, h: number = 0) { 5 | this.Width = w; 6 | this.Height = h; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /utils/UUIDV4.ts: -------------------------------------------------------------------------------- 1 | export default function UUIDV4(): string { 2 | var uuid = "", 3 | ii; 4 | for (ii = 0; ii < 32; ii += 1) { 5 | switch (ii) { 6 | case 8: 7 | case 20: 8 | uuid += "-"; 9 | uuid += ((Math.random() * 16) | 0).toString(16); 10 | break; 11 | case 12: 12 | uuid += "-"; 13 | uuid += "4"; 14 | break; 15 | case 16: 16 | uuid += "-"; 17 | uuid += ((Math.random() * 4) | 8).toString(16); 18 | break; 19 | default: 20 | uuid += ((Math.random() * 16) | 0).toString(16); 21 | } 22 | } 23 | return uuid; 24 | } 25 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | 3 | module.exports = { 4 | mode: "production", 5 | module: { 6 | rules: [ 7 | { 8 | test: /\.tsx?$/, 9 | use: "ts-loader", 10 | exclude: /node_modules/ 11 | } 12 | ] 13 | }, 14 | resolve: { 15 | extensions: [".tsx", ".ts"] 16 | }, 17 | output: { 18 | filename: "index.js" 19 | } 20 | }; 21 | --------------------------------------------------------------------------------