├── .gitignore ├── InvokeItem.h ├── InvokeItem.mm ├── LICENSE ├── Makefile ├── Menu.h ├── Menu.mm ├── MenuItem.h ├── MenuItem.mm ├── Page.h ├── Page.mm ├── PageItem.h ├── PageItem.mm ├── README.md ├── SliderItem.h ├── SliderItem.mm ├── TextfieldItem.h ├── TextfieldItem.mm ├── ToggleItem.h ├── ToggleItem.mm ├── Tweak.xm ├── Utils.h ├── githubImg.jpg ├── layout ├── DEBIAN │ └── control └── var │ └── mobile │ └── Documents │ └── com.xelahot.xelahotmodmenubase │ └── images │ ├── back.png │ ├── close.png │ ├── in.png │ ├── menuIcon.png │ └── sliderIcon.png └── xelahotmodmenubase.plist /.gitignore: -------------------------------------------------------------------------------- 1 | .theos/ 2 | packages/ 3 | -------------------------------------------------------------------------------- /InvokeItem.h: -------------------------------------------------------------------------------- 1 | #import "MenuItem.h" 2 | 3 | @interface InvokeItem : MenuItem 4 | 5 | @property (nonatomic) NSString *Description; 6 | @property (nonatomic) void (*FunctionPtr)(); 7 | 8 | - (id)initWithTitle:(NSString *)title_ 9 | description:(NSString *)description_ functionPtr:(void (*)())functionPtr_; 10 | 11 | @end -------------------------------------------------------------------------------- /InvokeItem.mm: -------------------------------------------------------------------------------- 1 | #import "InvokeItem.h" 2 | 3 | @implementation InvokeItem 4 | 5 | @synthesize FunctionPtr = _FunctionPtr; 6 | @synthesize Description = _Description; 7 | 8 | - (void) setFunctionPtr:(void (*)())pFunctionPtr { 9 | _FunctionPtr = pFunctionPtr; 10 | } 11 | 12 | - (void (*)()) FunctionPtr { 13 | return _FunctionPtr; 14 | } 15 | 16 | - (void) setDescription:(NSString *)pDescription { 17 | _Description = pDescription; 18 | } 19 | 20 | - (NSString*) Description { 21 | return _Description; 22 | } 23 | 24 | - (id)initWithTitle:(NSString *)title_ 25 | description:(NSString *)description_ functionPtr:(void (*)())functionPtr_ { 26 | //Set object propreties 27 | self.Title = title_; 28 | 29 | self.Description = description_; 30 | self.FunctionPtr = functionPtr_; 31 | 32 | return self; 33 | } 34 | 35 | @end -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 12 | HEREUNDER. 13 | 14 | Statement of Purpose 15 | 16 | The laws of most jurisdictions throughout the world automatically confer 17 | exclusive Copyright and Related Rights (defined below) upon the creator 18 | and subsequent owner(s) (each and all, an "owner") of an original work of 19 | authorship and/or a database (each, a "Work"). 20 | 21 | Certain owners wish to permanently relinquish those rights to a Work for 22 | the purpose of contributing to a commons of creative, cultural and 23 | scientific works ("Commons") that the public can reliably and without fear 24 | of later claims of infringement build upon, modify, incorporate in other 25 | works, reuse and redistribute as freely as possible in any form whatsoever 26 | and for any purposes, including without limitation commercial purposes. 27 | These owners may contribute to the Commons to promote the ideal of a free 28 | culture and the further production of creative, cultural and scientific 29 | works, or to gain reputation or greater distribution for their Work in 30 | part through the use and efforts of others. 31 | 32 | For these and/or other purposes and motivations, and without any 33 | expectation of additional consideration or compensation, the person 34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 35 | is an owner of Copyright and Related Rights in the Work, voluntarily 36 | elects to apply CC0 to the Work and publicly distribute the Work under its 37 | terms, with knowledge of his or her Copyright and Related Rights in the 38 | Work and the meaning and intended legal effect of CC0 on those rights. 39 | 40 | 1. Copyright and Related Rights. A Work made available under CC0 may be 41 | protected by copyright and related or neighboring rights ("Copyright and 42 | Related Rights"). Copyright and Related Rights include, but are not 43 | limited to, the following: 44 | 45 | i. the right to reproduce, adapt, distribute, perform, display, 46 | communicate, and translate a Work; 47 | ii. moral rights retained by the original author(s) and/or performer(s); 48 | iii. publicity and privacy rights pertaining to a person's image or 49 | likeness depicted in a Work; 50 | iv. rights protecting against unfair competition in regards to a Work, 51 | subject to the limitations in paragraph 4(a), below; 52 | v. rights protecting the extraction, dissemination, use and reuse of data 53 | in a Work; 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal 56 | protection of databases, and under any national implementation 57 | thereof, including any amended or successor version of such 58 | directive); and 59 | vii. other similar, equivalent or corresponding rights throughout the 60 | world based on applicable law or treaty, and any national 61 | implementations thereof. 62 | 63 | 2. Waiver. To the greatest extent permitted by, but not in contravention 64 | of, applicable law, Affirmer hereby overtly, fully, permanently, 65 | irrevocably and unconditionally waives, abandons, and surrenders all of 66 | Affirmer's Copyright and Related Rights and associated claims and causes 67 | of action, whether now known or unknown (including existing as well as 68 | future claims and causes of action), in the Work (i) in all territories 69 | worldwide, (ii) for the maximum duration provided by applicable law or 70 | treaty (including future time extensions), (iii) in any current or future 71 | medium and for any number of copies, and (iv) for any purpose whatsoever, 72 | including without limitation commercial, advertising or promotional 73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 74 | member of the public at large and to the detriment of Affirmer's heirs and 75 | successors, fully intending that such Waiver shall not be subject to 76 | revocation, rescission, cancellation, termination, or any other legal or 77 | equitable action to disrupt the quiet enjoyment of the Work by the public 78 | as contemplated by Affirmer's express Statement of Purpose. 79 | 80 | 3. Public License Fallback. Should any part of the Waiver for any reason 81 | be judged legally invalid or ineffective under applicable law, then the 82 | Waiver shall be preserved to the maximum extent permitted taking into 83 | account Affirmer's express Statement of Purpose. In addition, to the 84 | extent the Waiver is so judged Affirmer hereby grants to each affected 85 | person a royalty-free, non transferable, non sublicensable, non exclusive, 86 | irrevocable and unconditional license to exercise Affirmer's Copyright and 87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 88 | maximum duration provided by applicable law or treaty (including future 89 | time extensions), (iii) in any current or future medium and for any number 90 | of copies, and (iv) for any purpose whatsoever, including without 91 | limitation commercial, advertising or promotional purposes (the 92 | "License"). The License shall be deemed effective as of the date CC0 was 93 | applied by Affirmer to the Work. Should any part of the License for any 94 | reason be judged legally invalid or ineffective under applicable law, such 95 | partial invalidity or ineffectiveness shall not invalidate the remainder 96 | of the License, and in such case Affirmer hereby affirms that he or she 97 | will not (i) exercise any of his or her remaining Copyright and Related 98 | Rights in the Work or (ii) assert any associated claims and causes of 99 | action with respect to the Work, in either case contrary to Affirmer's 100 | express Statement of Purpose. 101 | 102 | 4. Limitations and Disclaimers. 103 | 104 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 105 | surrendered, licensed or otherwise affected by this document. 106 | b. Affirmer offers the Work as-is and makes no representations or 107 | warranties of any kind concerning the Work, express, implied, 108 | statutory or otherwise, including without limitation warranties of 109 | title, merchantability, fitness for a particular purpose, non 110 | infringement, or the absence of latent or other defects, accuracy, or 111 | the present or absence of errors, whether or not discoverable, all to 112 | the greatest extent permissible under applicable law. 113 | c. Affirmer disclaims responsibility for clearing rights of other persons 114 | that may apply to the Work or any use thereof, including without 115 | limitation any person's Copyright and Related Rights in the Work. 116 | Further, Affirmer disclaims responsibility for obtaining any necessary 117 | consents, permissions or other rights required for any use of the 118 | Work. 119 | d. Affirmer understands and acknowledges that Creative Commons is not a 120 | party to this document and has no duty or obligation with respect to 121 | this CC0 or use of the Work. 122 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PACKAGE_VERSION = $(THEOS_PACKAGE_BASE_VERSION) 2 | ARCHS = arm64 arm64e 3 | 4 | DEBUG = 0 5 | FINALPACKAGE = 1 6 | FOR_RELEASE = 1 7 | 8 | include $(THEOS)/makefiles/common.mk 9 | 10 | TWEAK_NAME = xelahotmodmenubase 11 | 12 | xelahotmodmenubase_CCFLAGS = -std=c++11 -fno-rtti -fno-exceptions -DNDEBUG 13 | xelahotmodmenubase_CFLAGS = -fobjc-arc #-w #-Wno-deprecated -Wno-deprecated-declarations 14 | xelahotmodmenubase_FILES = Tweak.xm Page.mm Menu.mm MenuItem.mm ToggleItem.mm PageItem.mm SliderItem.mm TextfieldItem.mm InvokeItem.mm 15 | xelahotmodmenubase_FRAMEWORKS = UIKit 16 | # GO_EASY_ON_ME = 1 17 | 18 | include $(THEOS_MAKE_PATH)/tweak.mk 19 | include $(THEOS_MAKE_PATH)/aggregate.mk 20 | -------------------------------------------------------------------------------- /Menu.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "UIKit/UIKit.h" 3 | #import "Page.h" 4 | 5 | @interface Menu : UIView 6 | 7 | @property (nonatomic, strong) NSMutableArray *Pages; 8 | @property (nonatomic, assign) int CurrentPage; 9 | @property (nonatomic, strong) NSMutableDictionary *MenuItems; 10 | @property (nonatomic) UIScrollView *ScrollViewRef; 11 | 12 | - (void)addToggleItem:(NSString *)title_ 13 | description:(NSString *)description_ isOn:(BOOL)isOn_; 14 | - (void)addPageItem:(NSString *)title_ targetPage:(NSUInteger)targetPage_; 15 | - (void)addSliderItem:(NSString *)title_ 16 | description:(NSString *)description_ isOn:(BOOL)isOn_ isFloating:(BOOL)isFloating_ defaultValue:(float)defaultValue_ minValue:(float)minValue_ maxValue:(float)maxValue_; 17 | - (BOOL)isItemOn:(NSString *)itemName; 18 | - (float)getSliderValue:(NSString *)itemName; 19 | - (NSString *)getTextfieldValue:(NSString *)itemName; 20 | - (void)addPage:(Page *)page; 21 | - (void)loadPage:(int)pageNumber; 22 | - (void)backPage; 23 | - (id)initMenu; 24 | - (id)itemWithName:(NSString *)itemName; 25 | - (void)setUserDefaultsAndDict; 26 | - (UIButton *)getMenuButtRef ; 27 | @end -------------------------------------------------------------------------------- /Menu.mm: -------------------------------------------------------------------------------- 1 | #import 2 | #import "Menu.h" 3 | #import "Page.h" 4 | #import "ToggleItem.h" 5 | #import "PageItem.h" 6 | #import "SliderItem.h" 7 | #import "TextfieldItem.h" 8 | #import "InvokeItem.h" 9 | #import "Utils.h" 10 | 11 | //Create a subclass of UITapGestureRecognizer to be able to pass an argument when trying to change page 12 | @interface InheritGesture : UITapGestureRecognizer 13 | 14 | @property (nonatomic) int number; 15 | @property (nonatomic) NSString *text; 16 | @property (nonatomic) void (*ptr)(); 17 | 18 | @end 19 | 20 | @implementation InheritGesture: UITapGestureRecognizer 21 | @end 22 | 23 | //Create a subclass of UISlider to be able to pass info when the value is changed 24 | @interface MyUISlider : UISlider 25 | 26 | @property (nonatomic) int number; 27 | @property (nonatomic) NSString *customText; 28 | 29 | @end 30 | 31 | @implementation MyUISlider: UISlider 32 | @end 33 | 34 | @implementation Menu { 35 | UIWindow *mainWindow; 36 | NSUserDefaults *userDefaults; 37 | UIButton *menuButton; 38 | CGPoint latestMenuButtonPosition; 39 | CGPoint latestMenuPosition; 40 | UIButton *menuHeader; 41 | UIView *menuHeaderBar; 42 | CAShapeLayer *menuHeaderBarLayer; 43 | UILabel *menuTitle; 44 | UIButton *menuBackButton; 45 | UIButton *menuCloseButton; 46 | UIScrollView *menuScrollView; 47 | CGFloat menuScrollViewHeight; 48 | UIView *menuTopBorder; 49 | UIView *menuBottomBorder; 50 | UIView *menuLeftBorder; 51 | UIView *menuRightBorder; 52 | UIView *menuHeaderBottomBorder; 53 | } 54 | 55 | @synthesize Pages = _Pages; 56 | @synthesize CurrentPage = _CurrentPage; 57 | @synthesize MenuItems = _MenuItems; //That's a dictionnary that stores a key/value pair of all MenuItems. That way I can find a specific item by name without looping all of them. 58 | @synthesize ScrollViewRef = _ScrollViewRef; //That's a property of the menu that will have a getter to access the menu's scrollView at anytime so I can retrieve UI elements easily from outside this class. 59 | 60 | - (void) setPages:(NSMutableArray *)pPages { 61 | _Pages = pPages; 62 | } 63 | 64 | - (NSMutableArray *) Pages { 65 | return _Pages; 66 | } 67 | 68 | - (void) setCurrentPage:(int)pCurrentPage { 69 | _CurrentPage = pCurrentPage; 70 | } 71 | 72 | - (int) CurrentPage { 73 | return _CurrentPage; 74 | } 75 | 76 | - (void) setMenuItems:(NSMutableDictionary *)pMenuItems { 77 | _MenuItems = pMenuItems; 78 | } 79 | 80 | - (NSMutableDictionary *) MenuItems { 81 | return _MenuItems; 82 | } 83 | 84 | - (UIScrollView *) ScrollViewRef { 85 | return _ScrollViewRef; 86 | } 87 | 88 | - (void) showDescription:(InheritGesture *)tap 89 | { 90 | showPopup(@"Description :", tap.text); 91 | } 92 | 93 | - (id)itemWithName:(NSString *)itemName 94 | { 95 | for (Page *currentPage in self.Pages) 96 | { 97 | for (MenuItem *currentItem in currentPage.Items) 98 | { 99 | if([currentItem.Title isEqualToString:itemName]) 100 | return currentItem; 101 | } 102 | } 103 | 104 | return NULL; 105 | } 106 | 107 | - (BOOL)isItemOn:(NSString *)itemName 108 | { 109 | //Lagging? 110 | /*for (Page *currentPage in self.Pages) 111 | { 112 | int count = [currentPage.Items count]; 113 | for (int i=0; i 30 && p.x < itemViewRef.bounds.size.width / 3 * 2) 240 | return; 241 | 242 | SliderItem *mySliderItem = menuItem; 243 | 244 | //Set/replace the userDefaults on/off value for that item 245 | NSString *keyIO = [itemTitle stringByAppendingString:@"_IsOn"]; //.Title + "_IsOn" 246 | [userDefaults setObject:[NSNumber numberWithBool:!mySliderItem.IsOn] forKey:keyIO]; 247 | 248 | //Change the background color 249 | if(! mySliderItem.IsOn){ 250 | [UIView animateWithDuration:0.25 animations:^ { 251 | itemViewRef.backgroundColor = [UIColor colorWithRed:0.35 green:0.35 blue:0.35 alpha:0.75]; 252 | }]; 253 | } 254 | else{ 255 | [UIView animateWithDuration:0.25 animations:^ { 256 | itemViewRef.backgroundColor = [UIColor clearColor]; 257 | }]; 258 | } 259 | 260 | //Change the actual bool 261 | mySliderItem.IsOn = ! mySliderItem.IsOn; 262 | } 263 | else if([menuItem isKindOfClass:[TextfieldItem class]]) 264 | { 265 | TextfieldItem *myTextfieldItem = menuItem; 266 | 267 | //Set/replace the userDefaults on/off value for that item 268 | NSString *keyIO = [itemTitle stringByAppendingString:@"_IsOn"]; //.Title + "_IsOn" 269 | [userDefaults setObject:[NSNumber numberWithBool:!myTextfieldItem.IsOn] forKey:keyIO]; 270 | 271 | //Change the background color 272 | if(!myTextfieldItem.IsOn){ 273 | [UIView animateWithDuration:0.25 animations:^ { 274 | itemViewRef.backgroundColor = [UIColor colorWithRed:0.35 green:0.35 blue:0.35 alpha:0.75]; 275 | }]; 276 | } 277 | else{ 278 | [UIView animateWithDuration:0.25 animations:^ { 279 | itemViewRef.backgroundColor = [UIColor clearColor]; 280 | }]; 281 | } 282 | 283 | //Change the actual bool 284 | myTextfieldItem.IsOn = ! myTextfieldItem.IsOn; 285 | } 286 | } 287 | 288 | - (void)addToggleItem:(NSString *)title_ 289 | description:(NSString *)description_ isOn:(BOOL)isOn_ 290 | { 291 | float toggleItemHeight = 40; 292 | 293 | //Create item UI 294 | UIButton *myItem = [[UIButton alloc] initWithFrame:CGRectMake(0, menuScrollViewHeight, 200, toggleItemHeight)]; 295 | if(!isOn_) 296 | myItem.backgroundColor = [UIColor clearColor]; 297 | else 298 | myItem.backgroundColor = [UIColor colorWithRed:0.35 green:0.35 blue:0.35 alpha:0.75]; 299 | myItem.layer.borderWidth = 0.5f; 300 | myItem.layer.borderColor = [UIColor whiteColor].CGColor; 301 | 302 | UILabel *myLabel = [[UILabel alloc]initWithFrame:CGRectMake(10, 0, myItem.bounds.size.width - 35, toggleItemHeight)]; 303 | myLabel.text = title_; 304 | myLabel.textColor = [UIColor whiteColor]; 305 | myLabel.font = [UIFont fontWithName:@"AppleSDGothicNeo-Light" size:15]; 306 | myLabel.textAlignment = NSTextAlignmentLeft; 307 | //[myLabel sizeToFit]; //make container the same size as the resulting text 308 | //myLabel.center = CGPointMake(CGRectGetMidX(myItem.bounds), CGRectGetMidY(myItem.bounds)); 309 | //myLabel.adjustsFontSizeToFitWidth = true; 310 | [myItem addSubview: myLabel]; 311 | 312 | //Add description button 313 | UIButton *descriptionButton = [UIButton buttonWithType:UIButtonTypeInfoDark]; 314 | descriptionButton.frame = CGRectMake(myItem.bounds.size.width - 30, 12.5, 15, 15); 315 | descriptionButton.tintColor = [UIColor whiteColor]; 316 | [myItem addSubview: descriptionButton]; 317 | 318 | //Add description touch event listener 319 | InheritGesture *tapGestureRecognizer = [[InheritGesture alloc]initWithTarget:self action:@selector(showDescription:)]; 320 | tapGestureRecognizer.text = description_; 321 | [descriptionButton addGestureRecognizer: tapGestureRecognizer]; 322 | 323 | [menuScrollView addSubview: myItem]; 324 | 325 | menuScrollViewHeight += toggleItemHeight; 326 | menuScrollView.contentSize = CGSizeMake(200, menuScrollViewHeight); 327 | 328 | //Add touch event listener 329 | InheritGesture *tapGestureRecognizer2 = [[InheritGesture alloc]initWithTarget:self action:@selector(toggleItemOnOff:)]; 330 | tapGestureRecognizer2.text = title_; 331 | tapGestureRecognizer2.number = [menuScrollView.subviews indexOfObject: myItem]; 332 | [myItem addGestureRecognizer: tapGestureRecognizer2]; 333 | } 334 | 335 | - (void)InvokeFunction:(InheritGesture *)tap 336 | { 337 | //Do an animation on the item 338 | int itemIndexInScrollView = tap.number; 339 | UIButton *itemViewRef = [menuScrollView.subviews objectAtIndex: itemIndexInScrollView]; 340 | 341 | [UIView animateWithDuration:0.125 animations:^ { 342 | itemViewRef.backgroundColor = [UIColor colorWithRed:0.35 green:0.35 blue:0.35 alpha:0.75]; 343 | }]; 344 | [UIView animateWithDuration:0.125 animations:^ { 345 | itemViewRef.backgroundColor = [UIColor clearColor]; 346 | }]; 347 | 348 | //Invoke the function 349 | tap.ptr(); 350 | } 351 | 352 | - (void)addInvokeItem:(NSString *)title_ 353 | description:(NSString *)description_ functionPtr:(void (*)())functionPtr_ 354 | { 355 | float invokeItemHeight = 40; 356 | 357 | //Create item UI 358 | UIButton *myItem = [[UIButton alloc] initWithFrame:CGRectMake(0, menuScrollViewHeight, 200, invokeItemHeight)]; 359 | myItem.backgroundColor = [UIColor clearColor]; 360 | myItem.layer.borderWidth = 0.5f; 361 | myItem.layer.borderColor = [UIColor whiteColor].CGColor; 362 | 363 | UILabel *myLabel = [[UILabel alloc]initWithFrame:CGRectMake(10, 0, myItem.bounds.size.width - 35, invokeItemHeight)]; 364 | myLabel.text = title_; 365 | myLabel.textColor = [UIColor whiteColor]; 366 | myLabel.font = [UIFont fontWithName:@"AppleSDGothicNeo-Light" size:15]; 367 | myLabel.textAlignment = NSTextAlignmentLeft; 368 | //[myLabel sizeToFit]; //make container the same size as the resulting text 369 | //myLabel.center = CGPointMake(CGRectGetMidX(myItem.bounds), CGRectGetMidY(myItem.bounds)); 370 | //myLabel.adjustsFontSizeToFitWidth = true; 371 | [myItem addSubview: myLabel]; 372 | 373 | //Add description button 374 | UIButton *descriptionButton = [UIButton buttonWithType:UIButtonTypeInfoDark]; 375 | descriptionButton.frame = CGRectMake(myItem.bounds.size.width - 30, 12.5, 15, 15); 376 | descriptionButton.tintColor = [UIColor whiteColor]; 377 | [myItem addSubview: descriptionButton]; 378 | 379 | //Add description touch event listener 380 | InheritGesture *tapGestureRecognizer = [[InheritGesture alloc]initWithTarget:self action:@selector(showDescription:)]; 381 | tapGestureRecognizer.text = description_; 382 | [descriptionButton addGestureRecognizer: tapGestureRecognizer]; 383 | 384 | [menuScrollView addSubview: myItem]; 385 | 386 | menuScrollViewHeight += invokeItemHeight; 387 | menuScrollView.contentSize = CGSizeMake(200, menuScrollViewHeight); 388 | 389 | //Add touch event listener 390 | InheritGesture 391 | *tapGestureRecognizer2 = [[InheritGesture alloc]initWithTarget:self action:@selector(InvokeFunction:)]; 392 | tapGestureRecognizer2.ptr = functionPtr_; 393 | tapGestureRecognizer2.number = [menuScrollView.subviews indexOfObject: myItem]; 394 | [myItem addGestureRecognizer: tapGestureRecognizer2]; 395 | } 396 | 397 | - (void)pageClicked:(InheritGesture *)tap 398 | { 399 | [self loadPage: tap.number]; 400 | } 401 | 402 | - (void)addPageItem:(NSString *)title_ targetPage:(NSUInteger)targetPage_ 403 | { 404 | float pageItemHeight = 40; 405 | 406 | //Create item UI 407 | UIButton *myItem = [[UIButton alloc] initWithFrame:CGRectMake(0, menuScrollViewHeight, 200, pageItemHeight)]; 408 | myItem.backgroundColor = [UIColor clearColor]; 409 | myItem.layer.borderWidth = 0.5f; 410 | myItem.layer.borderColor = [UIColor whiteColor].CGColor; 411 | 412 | //Title 413 | UILabel *myLabel = [[UILabel alloc]initWithFrame:CGRectMake(10, 0, myItem.bounds.size.width - 35, pageItemHeight)]; 414 | myLabel.text = title_; 415 | myLabel.textColor = [UIColor whiteColor]; 416 | myLabel.font = [UIFont fontWithName:@"AppleSDGothicNeo-Light" size:15]; 417 | myLabel.textAlignment = NSTextAlignmentLeft; 418 | //[myLabel sizeToFit]; //make container the same size as the resulting text 419 | //myLabel.center = CGPointMake(CGRectGetMidX(myItem.bounds), CGRectGetMidY(myItem.bounds)); 420 | //myLabel.adjustsFontSizeToFitWidth = true; 421 | [myItem addSubview: myLabel]; 422 | 423 | //Arrow image 424 | NSString *desiredImgPath = @"/var/mobile/Documents/com.xelahot.xelahotagario/images/in.png"; 425 | UIImage* pageImage = [UIImage imageWithContentsOfFile:desiredImgPath]; 426 | //UIImage* pageImage = [UIImage imageWithContentsOfFile:@"/var/mobile/Documents/com.xelahot.libxelahot/images/in.png"]; 427 | UIImageView *imageView = [[UIImageView alloc] initWithImage:pageImage]; 428 | imageView.frame = CGRectMake(myItem.bounds.size.width - 30, 10, 20, 20); 429 | imageView.backgroundColor = [UIColor clearColor]; 430 | 431 | [myItem addSubview: imageView]; 432 | 433 | [menuScrollView addSubview: myItem]; 434 | 435 | menuScrollViewHeight += pageItemHeight; 436 | menuScrollView.contentSize = CGSizeMake(200, menuScrollViewHeight); 437 | 438 | //Add touch event listener 439 | InheritGesture *tapGestureRecognizer = [[InheritGesture alloc]initWithTarget:self action:@selector(pageClicked:)]; 440 | tapGestureRecognizer.number = targetPage_; 441 | [myItem addGestureRecognizer: tapGestureRecognizer]; 442 | } 443 | 444 | -(void)menuSliderValueChanged:(MyUISlider *)slider_ 445 | { 446 | NSString *title = slider_.customText; 447 | int itemViewIndex = slider_.number; 448 | 449 | //Get the menu item views references 450 | UIButton *itemViewRef = [menuScrollView.subviews objectAtIndex: itemViewIndex]; 451 | MyUISlider *sliderViewRef = [itemViewRef.subviews objectAtIndex: 2]; 452 | UILabel *sliderLabel = [itemViewRef.subviews objectAtIndex: 3]; 453 | 454 | //Get the SliderItem ref. from title 455 | SliderItem *sliderItem = [self itemWithName:title]; 456 | 457 | //Assign the new UI value on the instance property 458 | sliderItem.DefaultValue = sliderViewRef.value; 459 | 460 | //Set/replace the userDefaults DefaultValue for that item 461 | NSString *keyDF = [title stringByAppendingString:@"_DefaultValue"]; //.Title + "_DefaultValue" 462 | [userDefaults setObject:[NSNumber numberWithFloat:sliderItem.DefaultValue] forKey:keyDF]; 463 | 464 | //Update the value on the UI elements 465 | dispatch_async(dispatch_get_main_queue(), ^{ 466 | //Update the label text with that new value depending on the .IsFloating proprety 467 | if(sliderItem.IsFloating) 468 | sliderLabel.text = [NSString stringWithFormat:@"%.2f", sliderViewRef.value]; 469 | else{ 470 | sliderLabel.text = [NSString stringWithFormat:@"%.0f", sliderViewRef.value]; 471 | } 472 | 473 | //Update the actual slider value 474 | sliderViewRef.value = sliderItem.DefaultValue; 475 | }); 476 | } 477 | 478 | - (UIImage *)imageWithImage:(UIImage *)image convertToSize:(CGSize)size { 479 | UIGraphicsBeginImageContext(size); 480 | [image drawInRect:CGRectMake(0, 0, size.width, size.height)]; 481 | UIImage *destImage = UIGraphicsGetImageFromCurrentImageContext(); 482 | UIGraphicsEndImageContext(); 483 | return destImage; 484 | } 485 | 486 | - (void)addSliderItem:(NSString *)title_ 487 | description:(NSString *)description_ isOn:(BOOL)isOn_ isFloating:(BOOL)isFloating_ defaultValue:(float)defaultValue_ minValue:(float)minValue_ maxValue:(float)maxValue_ 488 | { 489 | float sliderItemHeight = 60; 490 | 491 | //Create item UI 492 | UIButton *myItem = [[UIButton alloc] initWithFrame:CGRectMake(0, menuScrollViewHeight, 200, sliderItemHeight)]; 493 | if(!isOn_) 494 | myItem.backgroundColor = [UIColor clearColor]; 495 | else 496 | myItem.backgroundColor = [UIColor colorWithRed:0.35 green:0.35 blue:0.35 alpha:0.75]; 497 | myItem.layer.borderWidth = 0.5f; 498 | myItem.layer.borderColor = [UIColor whiteColor].CGColor; 499 | 500 | UILabel *myLabel = [[UILabel alloc]initWithFrame:CGRectMake(10, 0, myItem.bounds.size.width - 35, 35)]; 501 | myLabel.text = title_; 502 | myLabel.textColor = [UIColor whiteColor]; 503 | myLabel.font = [UIFont fontWithName:@"AppleSDGothicNeo-Light" size:15]; 504 | myLabel.textAlignment = NSTextAlignmentLeft; 505 | //[myLabel sizeToFit]; //make container the same size as the resulting text 506 | //myLabel.center = CGPointMake(CGRectGetMidX(myItem.bounds), CGRectGetMidY(myItem.bounds)); 507 | //myLabel.adjustsFontSizeToFitWidth = true; 508 | [myItem addSubview: myLabel]; 509 | 510 | //Add description button 511 | UIButton *descriptionButton = [UIButton buttonWithType:UIButtonTypeInfoDark]; 512 | descriptionButton.frame = CGRectMake(myItem.bounds.size.width - 30, 7.5, 15, 15); 513 | descriptionButton.tintColor = [UIColor whiteColor]; 514 | [myItem addSubview: descriptionButton]; 515 | 516 | //Add description touch event listener 517 | InheritGesture *tapGestureRecognizer = [[InheritGesture alloc]initWithTarget:self action:@selector(showDescription:)]; 518 | tapGestureRecognizer.text = description_; 519 | [descriptionButton addGestureRecognizer: tapGestureRecognizer]; 520 | 521 | MyUISlider *menuSlider = [[MyUISlider alloc]initWithFrame:CGRectMake(10, 30, self.bounds.size.width / 2 + 10, 20)]; 522 | menuSlider.minimumTrackTintColor = [UIColor whiteColor]; 523 | menuSlider.maximumTrackTintColor = [UIColor whiteColor]; 524 | 525 | NSString *desiredImgPath = @"/var/mobile/Documents/com.xelahot.xelahotagario/images/sliderIcon.png"; 526 | UIImage* sliderImage = [UIImage imageWithContentsOfFile:desiredImgPath]; 527 | //UIImage* sliderImage = [UIImage imageWithContentsOfFile:@"/var/mobile/Documents/com.xelahot.libxelahot/images/sliderIcon.png"]; 528 | UIImage* sliderImageResized = [self imageWithImage:sliderImage convertToSize:CGSizeMake(20, 20)]; 529 | [menuSlider setThumbImage: sliderImageResized forState:UIControlStateNormal]; 530 | [menuSlider setThumbImage: sliderImageResized forState: UIControlStateSelected]; 531 | [menuSlider setThumbImage: sliderImageResized forState: UIControlStateHighlighted]; 532 | 533 | dispatch_async(dispatch_get_main_queue(), ^{ 534 | menuSlider.value = defaultValue_; 535 | }); 536 | menuSlider.minimumValue = minValue_; 537 | menuSlider.maximumValue = maxValue_; 538 | menuSlider.continuous = true; 539 | 540 | [myItem addSubview: menuSlider]; 541 | 542 | //Slider text value 543 | UILabel *menuSliderValue = [[UILabel alloc]initWithFrame:CGRectMake(10 + menuSlider.bounds.size.width + 10, 30, self.bounds.size.width - menuSlider.bounds.size.width - 20, 20)]; 544 | 545 | dispatch_async(dispatch_get_main_queue(), ^{ 546 | if(isFloating_) 547 | menuSliderValue.text = [NSString stringWithFormat:@"%.2f", menuSlider.value]; 548 | else{ 549 | menuSliderValue.text = [NSString stringWithFormat:@"%.0f", menuSlider.value]; 550 | } 551 | }); 552 | 553 | menuSliderValue.textColor = [UIColor whiteColor]; 554 | menuSliderValue.font = [UIFont fontWithName:@"AppleSDGothicNeo-Light" size:15]; 555 | menuSliderValue.textAlignment = NSTextAlignmentLeft; 556 | 557 | [myItem addSubview: menuSliderValue]; 558 | 559 | [menuScrollView addSubview: myItem]; 560 | 561 | menuScrollViewHeight += sliderItemHeight; 562 | menuScrollView.contentSize = CGSizeMake(200, menuScrollViewHeight); 563 | 564 | //Add on/off event listener 565 | InheritGesture *tapGestureRecognizer2 = [[InheritGesture alloc]initWithTarget:self action:@selector(toggleItemOnOff:)]; 566 | tapGestureRecognizer2.text = title_; 567 | tapGestureRecognizer2.number = [menuScrollView.subviews indexOfObject: myItem]; 568 | [myItem addGestureRecognizer: tapGestureRecognizer2]; 569 | 570 | //Add slider value changed event 571 | menuSlider.number = [menuScrollView.subviews indexOfObject: myItem]; 572 | menuSlider.customText = title_; 573 | [menuSlider addTarget:self action:@selector(menuSliderValueChanged:) forControlEvents:UIControlEventValueChanged]; 574 | } 575 | 576 | - (void)addTextfieldItem:(NSString *)title_ 577 | description:(NSString *)description_ isOn:(BOOL)isOn_ defaultValue:(NSString *)defaultValue_ 578 | { 579 | float textfieldItemHeight = 60; 580 | 581 | //Create item UI 582 | UIButton *myItem = [[UIButton alloc] initWithFrame:CGRectMake(0, menuScrollViewHeight, 200, textfieldItemHeight)]; 583 | if(!isOn_) 584 | myItem.backgroundColor = [UIColor clearColor]; 585 | else 586 | myItem.backgroundColor = [UIColor colorWithRed:0.35 green:0.35 blue:0.35 alpha:0.75]; 587 | myItem.layer.borderWidth = 0.5f; 588 | myItem.layer.borderColor = [UIColor whiteColor].CGColor; 589 | 590 | UILabel *myLabel = [[UILabel alloc]initWithFrame:CGRectMake(10, 0, myItem.bounds.size.width - 35, 35)]; 591 | myLabel.text = title_; 592 | myLabel.textColor = [UIColor whiteColor]; 593 | myLabel.font = [UIFont fontWithName:@"AppleSDGothicNeo-Light" size:15]; 594 | myLabel.textAlignment = NSTextAlignmentLeft; 595 | //[myLabel sizeToFit]; //make container the same size as the resulting text 596 | //myLabel.center = CGPointMake(CGRectGetMidX(myItem.bounds), CGRectGetMidY(myItem.bounds)); 597 | //myLabel.adjustsFontSizeToFitWidth = true; 598 | [myItem addSubview: myLabel]; 599 | 600 | //Add description button 601 | UIButton *descriptionButton = [UIButton buttonWithType:UIButtonTypeInfoDark]; 602 | descriptionButton.frame = CGRectMake(myItem.bounds.size.width - 30, 7.5, 15, 15); 603 | descriptionButton.tintColor = [UIColor whiteColor]; 604 | [myItem addSubview: descriptionButton]; 605 | 606 | //Add description touch event listener 607 | InheritGesture *tapGestureRecognizer = [[InheritGesture alloc]initWithTarget:self action:@selector(showDescription:)]; 608 | tapGestureRecognizer.text = description_; 609 | [descriptionButton addGestureRecognizer: tapGestureRecognizer]; 610 | 611 | //Container to add padding to the textfield 612 | UIView *textfieldContainer = [[UIView alloc] initWithFrame:CGRectMake(10, 30, self.bounds.size.width - 50, 20)]; 613 | //textfieldContainer.layer.borderWidth = 0.5f; 614 | //textfieldContainer.layer.borderColor = [UIColor whiteColor].CGColor; 615 | //textfieldContainer.layer.cornerRadius = 2.0f; 616 | textfieldContainer.backgroundColor = [UIColor clearColor]; 617 | [myItem addSubview: textfieldContainer]; 618 | 619 | //Add borders to the container 620 | UIView *containerBotBorder = [[UIView alloc] initWithFrame:CGRectMake(10, 50, self.bounds.size.width - 50, 0.5)]; 621 | containerBotBorder.backgroundColor = [UIColor whiteColor]; 622 | [myItem addSubview: containerBotBorder]; 623 | UIView *containerLeftBorder = [[UIView alloc] initWithFrame:CGRectMake(9.5, 30, 0.5, 20)]; 624 | containerLeftBorder.backgroundColor = [UIColor whiteColor]; 625 | [myItem addSubview: containerLeftBorder]; 626 | 627 | //The actual textfield 628 | UITextField * myTextfield = [[UITextField alloc]initWithFrame:CGRectMake(5, 5, self.bounds.size.width - 50 - 5, 20 - 5)]; 629 | myTextfield.delegate = self; //needed to close the keyboard 630 | //myTextfield.layer.borderWidth = 0.5f; 631 | //myTextfield.layer.borderColor = [UIColor whiteColor].CGColor; 632 | //myTextfield.layer.cornerRadius = 2.0f; 633 | myTextfield.textColor = [UIColor whiteColor]; 634 | myTextfield.textAlignment = NSTextAlignmentLeft; 635 | myTextfield.font = [UIFont fontWithName:@"AppleSDGothicNeo-Light" size:14]; 636 | myTextfield.backgroundColor = [UIColor clearColor]; 637 | myTextfield.text = defaultValue_; 638 | 639 | //Add padding to the textfield text 640 | /*UIView *paddingView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 5, 30)]; 641 | myTextfield.leftView = paddingView; 642 | myTextfield.leftViewMode = UITextFieldViewModeAlways;*/ 643 | 644 | [textfieldContainer addSubview: myTextfield]; 645 | 646 | [menuScrollView addSubview: myItem]; 647 | myTextfield.tag = [menuScrollView.subviews indexOfObject: myItem]; 648 | 649 | menuScrollViewHeight += textfieldItemHeight; 650 | menuScrollView.contentSize = CGSizeMake(200, menuScrollViewHeight); 651 | 652 | //Add on/off event listener 653 | InheritGesture *tapGestureRecognizer2 = [[InheritGesture alloc]initWithTarget:self action:@selector(toggleItemOnOff:)]; 654 | tapGestureRecognizer2.text = title_; 655 | tapGestureRecognizer2.number = [menuScrollView.subviews indexOfObject: myItem]; 656 | [myItem addGestureRecognizer: tapGestureRecognizer2]; 657 | } 658 | 659 | // Native method that happens when we clicked the "return" key on the keyboard and the keyboard goes away. ***The arg1 must end with a '_' for the keyboard to close with resignFirstResponder 660 | -(BOOL)textFieldShouldReturn:(UITextField*)textfieldRef_ { 661 | int itemIndexInScrollView = textfieldRef_.tag; //Get the item index in the scrollView (might be problematic if it does that on every keyboard in the game because there won't be any TextfieldItem or .tag) 662 | 663 | //Get the item title 664 | UIButton *itemViewRef = [menuScrollView.subviews objectAtIndex: itemIndexInScrollView]; 665 | UILabel *itemLabelRef = [itemViewRef.subviews objectAtIndex: 0]; 666 | NSString *itemTitle = itemLabelRef.text; 667 | 668 | //Get the TextfieldItem ref. to set the .DefaultValue 669 | TextfieldItem *textfieldItem = [self itemWithName:itemTitle]; 670 | textfieldItem.DefaultValue = textfieldRef_.text; 671 | 672 | //Set/replace the userDefaults DefaultValue for that item 673 | NSString *keyDF = [itemTitle stringByAppendingString:@"_DefaultValue"]; //.Title + "_DefaultValue" 674 | [userDefaults setObject:textfieldItem.DefaultValue forKey:keyDF]; 675 | 676 | //Returns true by default in the default implementation 677 | //[textfieldRef_ resignFirstResponder]; 678 | [[UIApplication sharedApplication] sendAction:@selector(resignFirstResponder) to:nil from:nil forEvent:nil]; 679 | 680 | return true; 681 | } 682 | 683 | - (void)addPage:(Page *)page { 684 | [self.Pages addObject:page]; 685 | } 686 | 687 | - (void)backPage 688 | { 689 | Page *currentPage = [Page pageWithNum:_CurrentPage menuRef:(Menu *)self]; 690 | [self loadPage: currentPage.ParentPage]; 691 | } 692 | 693 | - (void)closeMenu:(UITapGestureRecognizer *)tap { 694 | if(tap.state == UIGestureRecognizerStateEnded) { 695 | [UIView animateWithDuration:0.5 animations:^ { 696 | self.alpha = 0.0f; 697 | }]; 698 | } 699 | } 700 | 701 | - (void)openMenu:(UITapGestureRecognizer *)tap { 702 | if(tap.state == UIGestureRecognizerStateEnded) { 703 | [UIView animateWithDuration:0.5 animations:^ { 704 | self.alpha = 1.0f; 705 | }]; 706 | } 707 | } 708 | 709 | - (void)loadPage:(int)pageNumber 710 | { 711 | if(_CurrentPage == 1){ 712 | //Show the back button 713 | [UIView animateWithDuration:0.5 animations:^ { 714 | menuBackButton.alpha = 1.0f; 715 | }]; 716 | } 717 | 718 | _CurrentPage = pageNumber; 719 | 720 | if(pageNumber == 1){ 721 | //Hide the back button 722 | menuBackButton.alpha = 0.0f; 723 | } 724 | 725 | Page * myPage = [Page pageWithNum:pageNumber menuRef:(Menu *)self]; 726 | //Page *myPage = [self.Pages objectAtIndex:pageNumber - 1]; //This way is bad because it relies on the order addPage was used in Tweak.xm 727 | NSMutableArray *pageItems = myPage.Items; 728 | 729 | //Hide the scrollView 730 | [UIView animateWithDuration:0.25 animations:^ { 731 | menuScrollView.alpha = 0.0f; 732 | }]; 733 | 734 | //Before loading the page, clear the scrollView items 735 | [menuScrollView.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)]; 736 | menuScrollView.contentSize = CGSizeMake(200, 0); 737 | menuScrollViewHeight = 0; 738 | 739 | for (id currentItem in pageItems) 740 | { 741 | if([currentItem isKindOfClass:[ToggleItem class]]) 742 | { 743 | ToggleItem *myToggleItem = currentItem; 744 | [self addToggleItem:(NSString *) myToggleItem.Title 745 | description:(NSString *) myToggleItem.Description isOn:(BOOL) myToggleItem.IsOn]; 746 | } 747 | else if([currentItem isKindOfClass:[PageItem class]]) 748 | { 749 | PageItem *myPageItem = currentItem; 750 | [self addPageItem:(NSString *) myPageItem.Title 751 | targetPage:(NSUInteger) myPageItem.TargetPage]; 752 | } 753 | else if([currentItem isKindOfClass:[SliderItem class]]) 754 | { 755 | SliderItem *mySliderItem = currentItem; 756 | [self addSliderItem:(NSString *) mySliderItem.Title description:(NSString *) mySliderItem.Description isOn:(BOOL) mySliderItem.IsOn isFloating:(BOOL)mySliderItem.IsFloating defaultValue:(float)mySliderItem.DefaultValue minValue:(float)mySliderItem.MinValue maxValue:(float)mySliderItem.MaxValue]; 757 | } 758 | else if([currentItem isKindOfClass:[TextfieldItem class]]) 759 | { 760 | TextfieldItem *myTextfieldItem = currentItem; 761 | [self addTextfieldItem:(NSString *) myTextfieldItem.Title description:(NSString *) myTextfieldItem.Description isOn:(BOOL) myTextfieldItem.IsOn defaultValue:(NSString *) myTextfieldItem.DefaultValue]; 762 | } 763 | if([currentItem isKindOfClass:[InvokeItem class]]) 764 | { 765 | InvokeItem *myInvokeItem = currentItem; 766 | [self addInvokeItem:(NSString *) myInvokeItem.Title 767 | description:(NSString *) myInvokeItem.Description functionPtr:(void (*)()) myInvokeItem.FunctionPtr]; 768 | } 769 | } 770 | 771 | //Show the scrollView again 772 | [UIView animateWithDuration:0.25 animations:^ { 773 | menuScrollView.alpha = 1.0f; 774 | }]; 775 | } 776 | 777 | - (void)moveMenu:(UIPanGestureRecognizer *)gesture { 778 | CGPoint newPosition = [gesture translationInView:self.superview]; 779 | 780 | self.frame = CGRectMake(latestMenuPosition.x + newPosition.x, latestMenuPosition.y + newPosition.y, self.frame.size.width, self.frame.size.height); 781 | 782 | if(gesture.state == UIGestureRecognizerStateEnded) 783 | { 784 | //All fingers are lifted. Save position 785 | latestMenuPosition.x = latestMenuPosition.x + newPosition.x; 786 | latestMenuPosition.y = latestMenuPosition.y + newPosition.y; 787 | } 788 | } 789 | 790 | - (void)moveMenuButton:(UIPanGestureRecognizer *)gesture { 791 | CGPoint newPosition = [gesture translationInView:menuButton.superview]; 792 | 793 | menuButton.frame = CGRectMake(latestMenuButtonPosition.x + newPosition.x, latestMenuButtonPosition.y + newPosition.y, menuButton.frame.size.width, menuButton.frame.size.height); 794 | 795 | if(gesture.state == UIGestureRecognizerStateEnded) 796 | { 797 | //All fingers are lifted. Save position 798 | latestMenuButtonPosition.x = latestMenuButtonPosition.x + newPosition.x; 799 | latestMenuButtonPosition.y = latestMenuButtonPosition.y + newPosition.y; 800 | } 801 | } 802 | 803 | //This is a native method that gets called whenever I touch a UIView 804 | - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { 805 | latestMenuPosition = CGPointMake(self.frame.origin.x, self.frame.origin.y); 806 | 807 | latestMenuButtonPosition = CGPointMake(menuButton.frame.origin.x, menuButton.frame.origin.y); 808 | 809 | //Invoke the original implementation 810 | [super touchesBegan:touches withEvent:event]; 811 | } 812 | 813 | - (void)setUserDefaultsAndDict 814 | { 815 | for (Page *currentPage in self.Pages) 816 | { 817 | int count = [currentPage.Items count]; 818 | 819 | for (int i=0; i