CCMenuAccount.m 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794
  1. //
  2. // CCMenuAccount.h
  3. // Nextcloud iOS
  4. //
  5. // Created by Marino Faggiana on 07/04/16.
  6. // Copyright (c) 2017 Marino Faggiana. All rights reserved.
  7. //
  8. // Author Marino Faggiana <marino.faggiana@nextcloud.com>
  9. //
  10. // This program is free software: you can redistribute it and/or modify
  11. // it under the terms of the GNU General Public License as published by
  12. // the Free Software Foundation, either version 3 of the License, or
  13. // (at your option) any later version.
  14. //
  15. // This program is distributed in the hope that it will be useful,
  16. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. // GNU General Public License for more details.
  19. //
  20. // You should have received a copy of the GNU General Public License
  21. // along with this program. If not, see <http://www.gnu.org/licenses/>.
  22. //
  23. #import <Foundation/Foundation.h>
  24. #import <QuartzCore/QuartzCore.h>
  25. #import "CCMenuAccount.h"
  26. ////////////////////////////////////////////////////////////////////////////////
  27. ////////////////////////////////////////////////////////////////////////////////
  28. @interface CCMenuOverlay : UIView
  29. @end
  30. @implementation CCMenuOverlay
  31. - (id)initWithFrame:(CGRect)frame maskSetting:(Boolean)mask
  32. {
  33. self = [super initWithFrame:frame];
  34. if (self) {
  35. if (mask) {
  36. self.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.17];
  37. } else {
  38. self.backgroundColor = [UIColor clearColor];
  39. }
  40. UITapGestureRecognizer *gestureRecognizer;
  41. gestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self
  42. action:@selector(singleTap:)];
  43. [self addGestureRecognizer:gestureRecognizer];
  44. }
  45. return self;
  46. }
  47. // thank horaceho https://github.com/horaceho
  48. // for his solution described in https://github.com/kolyvan/kxmenu/issues/9
  49. - (void)singleTap:(UITapGestureRecognizer *)recognizer
  50. {
  51. for (UIView *v in self.subviews) {
  52. #pragma clang diagnostic push
  53. #pragma clang diagnostic ignored "-Wundeclared-selector"
  54. if ([v isKindOfClass:[CCMenuView class]] && [v respondsToSelector:@selector(dismissMenu:)]) {
  55. [v performSelector:@selector(dismissMenu:) withObject:@(YES)];
  56. }
  57. #pragma clang diagnostic pop
  58. }
  59. }
  60. @end
  61. ////////////////////////////////////////////////////////////////////////////////
  62. ////////////////////////////////////////////////////////////////////////////////
  63. @implementation CCMenuItem
  64. + (instancetype) menuItem:(NSString *)title argument:(NSString*)argument image:(UIImage *)image target:(id)target action:(SEL)action
  65. {
  66. return [[CCMenuItem alloc] init:title argument:argument image:image target:target action:action];
  67. }
  68. - (id)init:(NSString *)title argument:(NSString*)argument image:(UIImage *)image target:(id)target action:(SEL)action
  69. {
  70. NSParameterAssert(title.length || image);
  71. self = [super init];
  72. if (self) {
  73. _title = title;
  74. _argument = argument;
  75. _image = image;
  76. _target = target;
  77. _action = action;
  78. }
  79. return self;
  80. }
  81. - (BOOL)enabled
  82. {
  83. return _target != nil && _action != NULL;
  84. }
  85. - (void)performAction
  86. {
  87. __strong id target = self.target;
  88. if (target && [target respondsToSelector:_action]) {
  89. [target performSelectorOnMainThread:_action withObject:self waitUntilDone:YES];
  90. }
  91. }
  92. - (NSString *)description
  93. {
  94. return [NSString stringWithFormat:@"<%@ #%p %@>", [self class], self, _title];
  95. }
  96. @end
  97. ////////////////////////////////////////////////////////////////////////////////
  98. ////////////////////////////////////////////////////////////////////////////////
  99. typedef enum {
  100. CCMenuViewArrowDirectionNone,
  101. CCMenuViewArrowDirectionUp,
  102. CCMenuViewArrowDirectionDown,
  103. CCMenuViewArrowDirectionLeft,
  104. CCMenuViewArrowDirectionRight,
  105. } CCMenuViewArrowDirection;
  106. @implementation CCMenuView {
  107. CCMenuViewArrowDirection _arrowDirection;
  108. CGFloat _arrowPosition;
  109. UIView *_contentView;
  110. NSArray *_menuItems;
  111. }
  112. - (id)init
  113. {
  114. self = [super initWithFrame:CGRectZero];
  115. if(self) {
  116. self.backgroundColor = [UIColor clearColor];
  117. self.opaque = YES;
  118. self.alpha = 0;
  119. }
  120. return self;
  121. }
  122. - (void)setupFrameInView:(UIView *)view fromRect:(CGRect)fromRect
  123. {
  124. const CGSize contentSize = _contentView.frame.size;
  125. const CGFloat outerWidth = view.bounds.size.width;
  126. const CGFloat rectXM = fromRect.origin.x + fromRect.size.width * 0.5f;
  127. const CGFloat widthHalf = contentSize.width * 0.5f;
  128. const CGFloat kMargin = 5.f;
  129. const CGFloat rectY = fromRect.origin.y;
  130. if (self.CCMenuViewOptions.shadowOfMenu) {
  131. self.layer.shadowOpacity = 0.5;
  132. self.layer.shadowOffset = CGSizeMake(2, 2);
  133. self.layer.shadowRadius = 2;
  134. self.layer.shadowColor = [[UIColor blackColor] CGColor];
  135. }
  136. _arrowDirection = CCMenuViewArrowDirectionUp;
  137. CGPoint point = (CGPoint){
  138. rectXM - widthHalf,
  139. rectY - 4
  140. };
  141. if (point.x < kMargin)
  142. point.x = kMargin;
  143. if ((point.x + contentSize.width + kMargin) > outerWidth)
  144. point.x = outerWidth - contentSize.width - kMargin;
  145. _arrowPosition = rectXM - point.x;
  146. _contentView.frame = (CGRect){CGPointZero, contentSize};
  147. self.frame = (CGRect) {
  148. point,
  149. contentSize.width,
  150. contentSize.height - self.CCMenuViewOptions.arrowSize
  151. };
  152. }
  153. - (void)showMenuInView:(UIView *)view fromRect:(CGRect)rect menuItems:(NSArray *)menuItems withOptions:(OptionalConfiguration)options
  154. {
  155. self.CCMenuViewOptions = options;
  156. _menuItems = menuItems;
  157. _contentView = [self mkContentView];
  158. [self addSubview:_contentView];
  159. [self setupFrameInView:view fromRect:rect];
  160. CCMenuOverlay *overlay = [[CCMenuOverlay alloc] initWithFrame:view.bounds maskSetting:self.CCMenuViewOptions.maskToBackground];
  161. [overlay addSubview:self];
  162. [view addSubview:overlay];
  163. _contentView.hidden = YES;
  164. const CGRect toFrame = self.frame;
  165. self.frame = (CGRect){self.arrowPoint, 1, 1};
  166. [UIView animateWithDuration:0.2
  167. animations:^(void) {
  168. self.alpha = 1.0f;
  169. self.frame = toFrame;
  170. } completion:^(BOOL completed) {
  171. _contentView.hidden = NO;
  172. }];
  173. }
  174. - (void)dismissMenu:(BOOL) noAnimated
  175. {
  176. if (self.superview) {
  177. if (!noAnimated) {
  178. const CGRect toFrame = (CGRect){self.arrowPoint, 1, 1};
  179. _contentView.hidden = YES;
  180. [UIView animateWithDuration:0.1
  181. animations:^(void) {
  182. self.alpha = 0;
  183. self.frame = toFrame;
  184. } completion:^(BOOL finished) {
  185. if ([self.superview isKindOfClass:[CCMenuOverlay class]])
  186. [self.superview removeFromSuperview];
  187. [self removeFromSuperview];
  188. }];
  189. } else {
  190. if ([self.superview isKindOfClass:[CCMenuOverlay class]])
  191. [self.superview removeFromSuperview];
  192. [self removeFromSuperview];
  193. }
  194. }
  195. }
  196. - (void)performAction:(id)sender
  197. {
  198. [self dismissMenu:YES];
  199. UIButton *button = (UIButton *)sender;
  200. CCMenuItem *menuItem = _menuItems[button.tag];
  201. [menuItem performAction];
  202. }
  203. - (UIView *)mkContentView
  204. {
  205. for (UIView *v in self.subviews) {
  206. [v removeFromSuperview];
  207. }
  208. if (!_menuItems.count)
  209. return nil;
  210. const CGFloat kMinMenuItemHeight = 32.f;
  211. const CGFloat kMinMenuItemWidth = 32.f;
  212. const CGFloat kMarginX = self.CCMenuViewOptions.marginXSpacing;
  213. const CGFloat kMarginY = self.CCMenuViewOptions.marginYSpacing;
  214. UIFont *titleFont = [CCMenuAccount titleFont];
  215. if (!titleFont) titleFont = [UIFont boldSystemFontOfSize:16];
  216. CGFloat maxImageWidth = 0;
  217. CGFloat maxItemHeight = 0;
  218. CGFloat maxItemWidth = 0;
  219. for (CCMenuItem *menuItem in _menuItems) {
  220. const CGSize imageSize = menuItem.image.size;
  221. if (imageSize.width > maxImageWidth)
  222. maxImageWidth = imageSize.width;
  223. }
  224. if (maxImageWidth) {
  225. maxImageWidth += kMarginX;
  226. }
  227. for (CCMenuItem *menuItem in _menuItems) {
  228. #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000
  229. const CGSize titleSize = [menuItem.title sizeWithAttributes:@{NSFontAttributeName: titleFont}];
  230. #else
  231. const CGSize titleSize = [menuItem.title sizeWithFont:titleFont];
  232. #endif
  233. const CGSize imageSize = menuItem.image.size;
  234. const CGFloat itemHeight = MAX(titleSize.height, imageSize.height) + kMarginY * 2;
  235. const CGFloat itemWidth = ((!menuItem.enabled && !menuItem.image) ? titleSize.width : maxImageWidth + titleSize.width) + kMarginX * 2 + self.CCMenuViewOptions.intervalSpacing;
  236. if (itemHeight > maxItemHeight)
  237. maxItemHeight = itemHeight;
  238. if (itemWidth > maxItemWidth)
  239. maxItemWidth = itemWidth;
  240. }
  241. maxItemWidth = MAX(maxItemWidth, kMinMenuItemWidth);
  242. maxItemHeight = MAX(maxItemHeight, kMinMenuItemHeight);
  243. const CGFloat titleX = maxImageWidth + self.CCMenuViewOptions.intervalSpacing;
  244. const CGFloat titleWidth = maxItemWidth - titleX - kMarginX *2;
  245. UIImage *selectedImage = [CCMenuView selectedImage:(CGSize){maxItemWidth, maxItemHeight + 2}];
  246. int insets = 0;
  247. if (self.CCMenuViewOptions.seperatorLineHasInsets) {
  248. insets = 4;
  249. }
  250. UIImage *gradientLine = [CCMenuView gradientLine: (CGSize){maxItemWidth- kMarginX * insets, 0.4}];
  251. UIView *contentView = [[UIView alloc] initWithFrame:CGRectZero];
  252. contentView.autoresizingMask = UIViewAutoresizingNone;
  253. contentView.backgroundColor = [UIColor clearColor];
  254. contentView.opaque = NO;
  255. CGFloat itemY = kMarginY * 2;
  256. NSUInteger itemNum = 0;
  257. for (CCMenuItem *menuItem in _menuItems) {
  258. const CGRect itemFrame = (CGRect){0, itemY-kMarginY * 2 + self.CCMenuViewOptions.menuCornerRadius, maxItemWidth, maxItemHeight};
  259. UIView *itemView = [[UIView alloc] initWithFrame:itemFrame];
  260. itemView.autoresizingMask = UIViewAutoresizingNone;
  261. itemView.opaque = NO;
  262. [contentView addSubview:itemView];
  263. if (menuItem.enabled) {
  264. UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
  265. button.tag = itemNum;
  266. button.frame = itemView.bounds;
  267. button.enabled = menuItem.enabled;
  268. button.backgroundColor = [UIColor clearColor];
  269. button.opaque = NO;
  270. button.autoresizingMask = UIViewAutoresizingNone;
  271. [button addTarget:self
  272. action:@selector(performAction:)
  273. forControlEvents:UIControlEventTouchUpInside];
  274. [button setBackgroundImage:selectedImage forState:UIControlStateHighlighted];
  275. [itemView addSubview:button];
  276. }
  277. if (menuItem.title.length) {
  278. CGRect titleFrame;
  279. if (!menuItem.enabled && !menuItem.image) {
  280. titleFrame = (CGRect){
  281. kMarginX * 2,
  282. kMarginY,
  283. maxItemWidth - kMarginX * 4,
  284. maxItemHeight - kMarginY * 2
  285. };
  286. } else {
  287. titleFrame = (CGRect){
  288. titleX,
  289. kMarginY,
  290. titleWidth,
  291. maxItemHeight - kMarginY * 2
  292. };
  293. }
  294. UILabel *titleLabel = [[UILabel alloc] initWithFrame:titleFrame];
  295. titleLabel.text = menuItem.title;
  296. titleLabel.font = titleFont;
  297. titleLabel.textAlignment = menuItem.alignment;
  298. //titleLabel.textColor = menuItem.foreColor ? menuItem.foreColor : [UIColor blackColor];
  299. titleLabel.textColor = [UIColor colorWithRed:self.CCMenuViewOptions.textColor.R green:self.CCMenuViewOptions.textColor.G blue:self.CCMenuViewOptions.textColor.B alpha:1];
  300. titleLabel.backgroundColor = [UIColor clearColor];
  301. titleLabel.autoresizingMask = UIViewAutoresizingNone;
  302. [itemView addSubview:titleLabel];
  303. }
  304. if (menuItem.image) {
  305. const CGRect imageFrame = {kMarginX * 2, kMarginY, maxImageWidth, maxItemHeight - kMarginY * 2};
  306. UIImageView *imageView = [[UIImageView alloc] initWithFrame:imageFrame];
  307. imageView.image = menuItem.image;
  308. imageView.clipsToBounds = YES;
  309. imageView.contentMode = UIViewContentModeCenter;
  310. imageView.autoresizingMask = UIViewAutoresizingNone;
  311. [itemView addSubview:imageView];
  312. }
  313. if (itemNum < _menuItems.count - 1) {
  314. UIImageView *gradientView = [[UIImageView alloc] initWithImage:gradientLine];
  315. if (self.CCMenuViewOptions.seperatorLineHasInsets) {
  316. gradientView.frame = (CGRect){kMarginX * 2, maxItemHeight + 1, gradientLine.size};
  317. } else {
  318. gradientView.frame = (CGRect){0, maxItemHeight + 1 , gradientLine.size};
  319. }
  320. gradientView.contentMode = UIViewContentModeLeft;
  321. if (self.CCMenuViewOptions.hasSeperatorLine) {
  322. [itemView addSubview:gradientView];
  323. itemY += 2;
  324. }
  325. itemY += maxItemHeight;
  326. }
  327. ++itemNum;
  328. }
  329. itemY += self.CCMenuViewOptions.menuCornerRadius;
  330. contentView.frame = (CGRect){0, 0, maxItemWidth, itemY + kMarginY * 2 + 5.5 + self.CCMenuViewOptions.menuCornerRadius};
  331. return contentView;
  332. }
  333. - (CGPoint)arrowPoint
  334. {
  335. CGPoint point;
  336. if (_arrowDirection == CCMenuViewArrowDirectionUp) {
  337. point = (CGPoint){ CGRectGetMinX(self.frame) + _arrowPosition, CGRectGetMinY(self.frame) };
  338. } else if (_arrowDirection == CCMenuViewArrowDirectionDown) {
  339. point = (CGPoint){ CGRectGetMinX(self.frame) + _arrowPosition, CGRectGetMaxY(self.frame) };
  340. } else if (_arrowDirection == CCMenuViewArrowDirectionLeft) {
  341. point = (CGPoint){ CGRectGetMinX(self.frame), CGRectGetMinY(self.frame) + _arrowPosition };
  342. } else if (_arrowDirection == CCMenuViewArrowDirectionRight) {
  343. point = (CGPoint){ CGRectGetMaxX(self.frame), CGRectGetMinY(self.frame) + _arrowPosition };
  344. } else {
  345. point = self.center;
  346. }
  347. return point;
  348. }
  349. + (UIImage *)selectedImage:(CGSize)size
  350. {
  351. const CGFloat locations[] = {0,1};
  352. const CGFloat components[] = {
  353. 0.890,0.890,0.890,1,
  354. 0.890,0.890,0.890,1
  355. };
  356. return [self gradientImageWithSize:size locations:locations components:components count:2];
  357. }
  358. + (UIImage *)gradientLine:(CGSize)size
  359. {
  360. const CGFloat locations[5] = {0,0.2,0.5,0.8,1};
  361. const CGFloat R = 0.890f, G = 0.890f, B = 0.890f;
  362. const CGFloat components[20] = {
  363. R,G,B,1,
  364. R,G,B,1,
  365. R,G,B,1,
  366. R,G,B,1,
  367. R,G,B,1
  368. };
  369. return [self gradientImageWithSize:size locations:locations components:components count:5];
  370. }
  371. + (UIImage *)gradientImageWithSize:(CGSize)size locations:(const CGFloat [])locations components:(const CGFloat [])components count:(NSUInteger)count
  372. {
  373. UIGraphicsBeginImageContextWithOptions(size, NO, 0);
  374. CGContextRef context = UIGraphicsGetCurrentContext();
  375. CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
  376. CGGradientRef colorGradient = CGGradientCreateWithColorComponents(colorSpace, components, locations, 2);
  377. CGColorSpaceRelease(colorSpace);
  378. CGContextDrawLinearGradient(context, colorGradient, (CGPoint){0, 0}, (CGPoint){size.width, 0}, 0);
  379. CGGradientRelease(colorGradient);
  380. UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
  381. UIGraphicsEndImageContext();
  382. return image;
  383. }
  384. - (void)drawRect:(CGRect)rect
  385. {
  386. [self drawBackground:self.bounds inContext:UIGraphicsGetCurrentContext()];
  387. }
  388. - (void)drawBackground:(CGRect)frame inContext:(CGContextRef)context
  389. {
  390. CGFloat R0 = self.CCMenuViewOptions.menuBackgroundColor.R, G0 = self.CCMenuViewOptions.menuBackgroundColor.G, B0 = self.CCMenuViewOptions.menuBackgroundColor.B;
  391. CGFloat R1 = R0, G1 = G0, B1 = B0;
  392. UIColor *tintColor = [CCMenuAccount tintColor];
  393. if (tintColor) {
  394. CGFloat a;
  395. [tintColor getRed:&R0 green:&G0 blue:&B0 alpha:&a];
  396. }
  397. CGFloat X0 = frame.origin.x;
  398. CGFloat X1 = frame.origin.x + frame.size.width;
  399. CGFloat Y0 = frame.origin.y;
  400. CGFloat Y1 = frame.origin.y + frame.size.height;
  401. // render arrow
  402. UIBezierPath *arrowPath = [UIBezierPath bezierPath];
  403. // fix the issue with gap of arrow's base if on the edge
  404. const CGFloat kEmbedFix = 3.f;
  405. if (_arrowDirection == CCMenuViewArrowDirectionUp) {
  406. const CGFloat arrowXM = _arrowPosition;
  407. const CGFloat arrowX0 = arrowXM - self.CCMenuViewOptions.arrowSize;
  408. const CGFloat arrowX1 = arrowXM + self.CCMenuViewOptions.arrowSize;
  409. const CGFloat arrowY0 = Y0;
  410. const CGFloat arrowY1 = Y0 + self.CCMenuViewOptions.arrowSize + kEmbedFix;
  411. [arrowPath moveToPoint: (CGPoint){arrowXM, arrowY0}];
  412. [arrowPath addLineToPoint: (CGPoint){arrowX1, arrowY1}];
  413. [arrowPath addLineToPoint: (CGPoint){arrowX0, arrowY1}];
  414. [arrowPath addLineToPoint: (CGPoint){arrowXM, arrowY0}];
  415. [[UIColor colorWithRed:R0 green:G0 blue:B0 alpha:1] set];
  416. Y0 += self.CCMenuViewOptions.arrowSize;
  417. } else if (_arrowDirection == CCMenuViewArrowDirectionDown) {
  418. const CGFloat arrowXM = _arrowPosition;
  419. const CGFloat arrowX0 = arrowXM - self.CCMenuViewOptions.arrowSize;
  420. const CGFloat arrowX1 = arrowXM + self.CCMenuViewOptions.arrowSize;
  421. const CGFloat arrowY0 = Y1 - self.CCMenuViewOptions.arrowSize - kEmbedFix;
  422. const CGFloat arrowY1 = Y1;
  423. [arrowPath moveToPoint: (CGPoint){arrowXM, arrowY1}];
  424. [arrowPath addLineToPoint: (CGPoint){arrowX1, arrowY0}];
  425. [arrowPath addLineToPoint: (CGPoint){arrowX0, arrowY0}];
  426. [arrowPath addLineToPoint: (CGPoint){arrowXM, arrowY1}];
  427. [[UIColor colorWithRed:R1 green:G1 blue:B1 alpha:1] set];
  428. Y1 -= self.CCMenuViewOptions.arrowSize;
  429. } else if (_arrowDirection == CCMenuViewArrowDirectionLeft) {
  430. const CGFloat arrowYM = _arrowPosition;
  431. const CGFloat arrowX0 = X0;
  432. const CGFloat arrowX1 = X0 + self.CCMenuViewOptions.arrowSize + kEmbedFix;
  433. const CGFloat arrowY0 = arrowYM - self.CCMenuViewOptions.arrowSize;;
  434. const CGFloat arrowY1 = arrowYM + self.CCMenuViewOptions.arrowSize;
  435. [arrowPath moveToPoint: (CGPoint){arrowX0, arrowYM}];
  436. [arrowPath addLineToPoint: (CGPoint){arrowX1, arrowY0}];
  437. [arrowPath addLineToPoint: (CGPoint){arrowX1, arrowY1}];
  438. [arrowPath addLineToPoint: (CGPoint){arrowX0, arrowYM}];
  439. [[UIColor colorWithRed:R0 green:G0 blue:B0 alpha:1] set];
  440. X0 += self.CCMenuViewOptions.arrowSize;
  441. } else if (_arrowDirection == CCMenuViewArrowDirectionRight) {
  442. const CGFloat arrowYM = _arrowPosition;
  443. const CGFloat arrowX0 = X1;
  444. const CGFloat arrowX1 = X1 - self.CCMenuViewOptions.arrowSize - kEmbedFix;
  445. const CGFloat arrowY0 = arrowYM - self.CCMenuViewOptions.arrowSize;;
  446. const CGFloat arrowY1 = arrowYM + self.CCMenuViewOptions.arrowSize;
  447. [arrowPath moveToPoint: (CGPoint){arrowX0, arrowYM}];
  448. [arrowPath addLineToPoint: (CGPoint){arrowX1, arrowY0}];
  449. [arrowPath addLineToPoint: (CGPoint){arrowX1, arrowY1}];
  450. [arrowPath addLineToPoint: (CGPoint){arrowX0, arrowYM}];
  451. [[UIColor colorWithRed:R1 green:G1 blue:B1 alpha:1] set];
  452. X1 -= self.CCMenuViewOptions.arrowSize;
  453. }
  454. [arrowPath fill];
  455. // render body
  456. const CGRect bodyFrame = {X0, Y0, X1 - X0, Y1 - Y0};
  457. UIBezierPath *borderPath = [UIBezierPath bezierPathWithRoundedRect:bodyFrame cornerRadius:self.CCMenuViewOptions.menuCornerRadius];
  458. const CGFloat locations[] = {0, 1};
  459. const CGFloat components[] = {
  460. R0, G0, B0, 1,
  461. R1, G1, B1, 1,
  462. };
  463. CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
  464. CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, components, locations, sizeof(locations)/sizeof(locations[0]));
  465. CGColorSpaceRelease(colorSpace);
  466. [borderPath addClip];
  467. CGPoint start, end;
  468. if (_arrowDirection == CCMenuViewArrowDirectionLeft ||
  469. _arrowDirection == CCMenuViewArrowDirectionRight) {
  470. start = (CGPoint){X0, Y0};
  471. end = (CGPoint){X1, Y0};
  472. } else {
  473. start = (CGPoint){X0, Y0};
  474. end = (CGPoint){X0, Y1};
  475. }
  476. CGContextDrawLinearGradient(context, gradient, start, end, 0);
  477. CGGradientRelease(gradient);
  478. }
  479. @end
  480. ////////////////////////////////////////////////////////////////////////////////
  481. ////////////////////////////////////////////////////////////////////////////////
  482. static CCMenuAccount *gMenu;
  483. static UIColor *gTintColor;
  484. static UIFont *gTitleFont;
  485. @implementation CCMenuAccount {
  486. CCMenuView *_menuView;
  487. BOOL _observing;
  488. }
  489. + (instancetype)sharedMenu
  490. {
  491. static dispatch_once_t onceToken;
  492. dispatch_once(&onceToken, ^{
  493. gMenu = [[CCMenuAccount alloc] init];
  494. });
  495. return gMenu;
  496. }
  497. - (id)init
  498. {
  499. NSAssert(!gMenu, @"singleton object");
  500. self = [super init];
  501. if (self) {
  502. }
  503. return self;
  504. }
  505. - (void)dealloc
  506. {
  507. if (_observing) {
  508. [[NSNotificationCenter defaultCenter] removeObserver:self];
  509. }
  510. }
  511. - (void)showMenuInView:(UIView *)view fromRect:(CGRect)rect menuItems:(NSArray *)menuItems withOptions:(OptionalConfiguration) options
  512. {
  513. NSParameterAssert(view);
  514. NSParameterAssert(menuItems.count);
  515. if (_menuView) {
  516. [_menuView dismissMenu:NO];
  517. _menuView = nil;
  518. }
  519. if (!_observing) {
  520. _observing = YES;
  521. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(orientationWillChange:) name:UIApplicationWillChangeStatusBarOrientationNotification object:nil];
  522. }
  523. _menuView = [[CCMenuView alloc] init];
  524. [_menuView showMenuInView:view fromRect:rect menuItems:menuItems withOptions:options];
  525. }
  526. - (void)dismissMenu
  527. {
  528. if (_menuView) {
  529. [_menuView dismissMenu:NO];
  530. _menuView = nil;
  531. }
  532. if (_observing) {
  533. _observing = NO;
  534. [[NSNotificationCenter defaultCenter] removeObserver:self];
  535. }
  536. }
  537. - (void)orientationWillChange: (NSNotification *) n
  538. {
  539. [self dismissMenu];
  540. }
  541. + (void)showMenuInView:(UIView *)view fromRect:(CGRect)rect menuItems:(NSArray *)menuItems withOptions:(OptionalConfiguration)options
  542. {
  543. [[self sharedMenu] showMenuInView:view fromRect:rect menuItems:menuItems withOptions:options];
  544. }
  545. + (void)dismissMenu
  546. {
  547. [[self sharedMenu] dismissMenu];
  548. }
  549. + (UIColor *)tintColor
  550. {
  551. return gTintColor;
  552. }
  553. + (void)setTintColor:(UIColor *)tintColor
  554. {
  555. if (tintColor != gTintColor) {
  556. gTintColor = tintColor;
  557. }
  558. }
  559. + (UIFont *)titleFont
  560. {
  561. return gTitleFont;
  562. }
  563. + (void)setTitleFont:(UIFont *)titleFont
  564. {
  565. if (titleFont != gTitleFont) {
  566. gTitleFont = titleFont;
  567. }
  568. }
  569. @end