CCMenuAccount.m 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794
  1. //
  2. // CCMenuAccount.h
  3. // Nextcloud
  4. //
  5. // Created by Marino Faggiana on 07/04/16.
  6. // Copyright (c) 2016 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} color:self.CCMenuViewOptions.separatorColor];
  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 = self.CCMenuViewOptions.textColor;
  299. titleLabel.backgroundColor = [UIColor clearColor];
  300. titleLabel.autoresizingMask = UIViewAutoresizingNone;
  301. [itemView addSubview:titleLabel];
  302. }
  303. if (menuItem.image) {
  304. const CGRect imageFrame = {kMarginX * 2, kMarginY, maxImageWidth, maxItemHeight - kMarginY * 2};
  305. UIImageView *imageView = [[UIImageView alloc] initWithFrame:imageFrame];
  306. imageView.image = menuItem.image;
  307. imageView.clipsToBounds = YES;
  308. imageView.contentMode = UIViewContentModeCenter;
  309. imageView.autoresizingMask = UIViewAutoresizingNone;
  310. [itemView addSubview:imageView];
  311. }
  312. if (itemNum < _menuItems.count - 1) {
  313. UIImageView *gradientView = [[UIImageView alloc] initWithImage:gradientLine];
  314. if (self.CCMenuViewOptions.seperatorLineHasInsets) {
  315. gradientView.frame = (CGRect){kMarginX * 2, maxItemHeight + 1, gradientLine.size};
  316. } else {
  317. gradientView.frame = (CGRect){0, maxItemHeight + 1 , gradientLine.size};
  318. }
  319. gradientView.contentMode = UIViewContentModeLeft;
  320. if (self.CCMenuViewOptions.hasSeperatorLine) {
  321. [itemView addSubview:gradientView];
  322. itemY += 2;
  323. }
  324. itemY += maxItemHeight;
  325. }
  326. ++itemNum;
  327. }
  328. itemY += self.CCMenuViewOptions.menuCornerRadius;
  329. contentView.frame = (CGRect){0, 0, maxItemWidth, itemY + kMarginY * 2 + 5.5 + self.CCMenuViewOptions.menuCornerRadius};
  330. return contentView;
  331. }
  332. - (CGPoint)arrowPoint
  333. {
  334. CGPoint point;
  335. if (_arrowDirection == CCMenuViewArrowDirectionUp) {
  336. point = (CGPoint){ CGRectGetMinX(self.frame) + _arrowPosition, CGRectGetMinY(self.frame) };
  337. } else if (_arrowDirection == CCMenuViewArrowDirectionDown) {
  338. point = (CGPoint){ CGRectGetMinX(self.frame) + _arrowPosition, CGRectGetMaxY(self.frame) };
  339. } else if (_arrowDirection == CCMenuViewArrowDirectionLeft) {
  340. point = (CGPoint){ CGRectGetMinX(self.frame), CGRectGetMinY(self.frame) + _arrowPosition };
  341. } else if (_arrowDirection == CCMenuViewArrowDirectionRight) {
  342. point = (CGPoint){ CGRectGetMaxX(self.frame), CGRectGetMinY(self.frame) + _arrowPosition };
  343. } else {
  344. point = self.center;
  345. }
  346. return point;
  347. }
  348. + (UIImage *)selectedImage:(CGSize)size
  349. {
  350. const CGFloat locations[] = {0,1};
  351. const CGFloat components[] = {
  352. 0.890,0.890,0.890,1,
  353. 0.890,0.890,0.890,1
  354. };
  355. return [self gradientImageWithSize:size locations:locations components:components count:2];
  356. }
  357. + (UIImage *)gradientLine:(CGSize)size color:(UIColor *)color
  358. {
  359. if (color == nil) color = UIColor.lightGrayColor;
  360. const CGFloat locations[5] = {0,0.2,0.5,0.8,1};
  361. const CGFloat *componentsColor = CGColorGetComponents(color.CGColor);
  362. const CGFloat R = componentsColor[0], G = componentsColor[1], B = componentsColor[2];
  363. const CGFloat components[20] = {
  364. R,G,B,1,
  365. R,G,B,1,
  366. R,G,B,1,
  367. R,G,B,1,
  368. R,G,B,1
  369. };
  370. return [self gradientImageWithSize:size locations:locations components:components count:5];
  371. }
  372. + (UIImage *)gradientImageWithSize:(CGSize)size locations:(const CGFloat [])locations components:(const CGFloat [])components count:(NSUInteger)count
  373. {
  374. UIGraphicsBeginImageContextWithOptions(size, NO, 0);
  375. CGContextRef context = UIGraphicsGetCurrentContext();
  376. CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
  377. CGGradientRef colorGradient = CGGradientCreateWithColorComponents(colorSpace, components, locations, 2);
  378. CGColorSpaceRelease(colorSpace);
  379. CGContextDrawLinearGradient(context, colorGradient, (CGPoint){0, 0}, (CGPoint){size.width, 0}, 0);
  380. CGGradientRelease(colorGradient);
  381. UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
  382. UIGraphicsEndImageContext();
  383. return image;
  384. }
  385. - (void)drawRect:(CGRect)rect
  386. {
  387. [self drawBackground:self.bounds inContext:UIGraphicsGetCurrentContext()];
  388. }
  389. - (void)drawBackground:(CGRect)frame inContext:(CGContextRef)context
  390. {
  391. CGFloat R0 = self.CCMenuViewOptions.menuBackgroundColor.R, G0 = self.CCMenuViewOptions.menuBackgroundColor.G, B0 = self.CCMenuViewOptions.menuBackgroundColor.B;
  392. CGFloat R1 = R0, G1 = G0, B1 = B0;
  393. UIColor *tintColor = [CCMenuAccount tintColor];
  394. if (tintColor) {
  395. CGFloat a;
  396. [tintColor getRed:&R0 green:&G0 blue:&B0 alpha:&a];
  397. }
  398. CGFloat X0 = frame.origin.x;
  399. CGFloat X1 = frame.origin.x + frame.size.width;
  400. CGFloat Y0 = frame.origin.y;
  401. CGFloat Y1 = frame.origin.y + frame.size.height;
  402. // render arrow
  403. UIBezierPath *arrowPath = [UIBezierPath bezierPath];
  404. // fix the issue with gap of arrow's base if on the edge
  405. const CGFloat kEmbedFix = 3.f;
  406. if (_arrowDirection == CCMenuViewArrowDirectionUp) {
  407. const CGFloat arrowXM = _arrowPosition;
  408. const CGFloat arrowX0 = arrowXM - self.CCMenuViewOptions.arrowSize;
  409. const CGFloat arrowX1 = arrowXM + self.CCMenuViewOptions.arrowSize;
  410. const CGFloat arrowY0 = Y0;
  411. const CGFloat arrowY1 = Y0 + self.CCMenuViewOptions.arrowSize + kEmbedFix;
  412. [arrowPath moveToPoint: (CGPoint){arrowXM, arrowY0}];
  413. [arrowPath addLineToPoint: (CGPoint){arrowX1, arrowY1}];
  414. [arrowPath addLineToPoint: (CGPoint){arrowX0, arrowY1}];
  415. [arrowPath addLineToPoint: (CGPoint){arrowXM, arrowY0}];
  416. [[UIColor colorWithRed:R0 green:G0 blue:B0 alpha:1] set];
  417. Y0 += self.CCMenuViewOptions.arrowSize;
  418. } else if (_arrowDirection == CCMenuViewArrowDirectionDown) {
  419. const CGFloat arrowXM = _arrowPosition;
  420. const CGFloat arrowX0 = arrowXM - self.CCMenuViewOptions.arrowSize;
  421. const CGFloat arrowX1 = arrowXM + self.CCMenuViewOptions.arrowSize;
  422. const CGFloat arrowY0 = Y1 - self.CCMenuViewOptions.arrowSize - kEmbedFix;
  423. const CGFloat arrowY1 = Y1;
  424. [arrowPath moveToPoint: (CGPoint){arrowXM, arrowY1}];
  425. [arrowPath addLineToPoint: (CGPoint){arrowX1, arrowY0}];
  426. [arrowPath addLineToPoint: (CGPoint){arrowX0, arrowY0}];
  427. [arrowPath addLineToPoint: (CGPoint){arrowXM, arrowY1}];
  428. [[UIColor colorWithRed:R1 green:G1 blue:B1 alpha:1] set];
  429. Y1 -= self.CCMenuViewOptions.arrowSize;
  430. } else if (_arrowDirection == CCMenuViewArrowDirectionLeft) {
  431. const CGFloat arrowYM = _arrowPosition;
  432. const CGFloat arrowX0 = X0;
  433. const CGFloat arrowX1 = X0 + self.CCMenuViewOptions.arrowSize + kEmbedFix;
  434. const CGFloat arrowY0 = arrowYM - self.CCMenuViewOptions.arrowSize;;
  435. const CGFloat arrowY1 = arrowYM + self.CCMenuViewOptions.arrowSize;
  436. [arrowPath moveToPoint: (CGPoint){arrowX0, arrowYM}];
  437. [arrowPath addLineToPoint: (CGPoint){arrowX1, arrowY0}];
  438. [arrowPath addLineToPoint: (CGPoint){arrowX1, arrowY1}];
  439. [arrowPath addLineToPoint: (CGPoint){arrowX0, arrowYM}];
  440. [[UIColor colorWithRed:R0 green:G0 blue:B0 alpha:1] set];
  441. X0 += self.CCMenuViewOptions.arrowSize;
  442. } else if (_arrowDirection == CCMenuViewArrowDirectionRight) {
  443. const CGFloat arrowYM = _arrowPosition;
  444. const CGFloat arrowX0 = X1;
  445. const CGFloat arrowX1 = X1 - self.CCMenuViewOptions.arrowSize - kEmbedFix;
  446. const CGFloat arrowY0 = arrowYM - self.CCMenuViewOptions.arrowSize;;
  447. const CGFloat arrowY1 = arrowYM + self.CCMenuViewOptions.arrowSize;
  448. [arrowPath moveToPoint: (CGPoint){arrowX0, arrowYM}];
  449. [arrowPath addLineToPoint: (CGPoint){arrowX1, arrowY0}];
  450. [arrowPath addLineToPoint: (CGPoint){arrowX1, arrowY1}];
  451. [arrowPath addLineToPoint: (CGPoint){arrowX0, arrowYM}];
  452. [[UIColor colorWithRed:R1 green:G1 blue:B1 alpha:1] set];
  453. X1 -= self.CCMenuViewOptions.arrowSize;
  454. }
  455. [arrowPath fill];
  456. // render body
  457. const CGRect bodyFrame = {X0, Y0, X1 - X0, Y1 - Y0};
  458. UIBezierPath *borderPath = [UIBezierPath bezierPathWithRoundedRect:bodyFrame cornerRadius:self.CCMenuViewOptions.menuCornerRadius];
  459. const CGFloat locations[] = {0, 1};
  460. const CGFloat components[] = {
  461. R0, G0, B0, 1,
  462. R1, G1, B1, 1,
  463. };
  464. CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
  465. CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, components, locations, sizeof(locations)/sizeof(locations[0]));
  466. CGColorSpaceRelease(colorSpace);
  467. [borderPath addClip];
  468. CGPoint start, end;
  469. if (_arrowDirection == CCMenuViewArrowDirectionLeft ||
  470. _arrowDirection == CCMenuViewArrowDirectionRight) {
  471. start = (CGPoint){X0, Y0};
  472. end = (CGPoint){X1, Y0};
  473. } else {
  474. start = (CGPoint){X0, Y0};
  475. end = (CGPoint){X0, Y1};
  476. }
  477. CGContextDrawLinearGradient(context, gradient, start, end, 0);
  478. CGGradientRelease(gradient);
  479. }
  480. @end
  481. ////////////////////////////////////////////////////////////////////////////////
  482. ////////////////////////////////////////////////////////////////////////////////
  483. static CCMenuAccount *gMenu;
  484. static UIColor *gTintColor;
  485. static UIFont *gTitleFont;
  486. @implementation CCMenuAccount {
  487. CCMenuView *_menuView;
  488. BOOL _observing;
  489. }
  490. + (instancetype)sharedMenu
  491. {
  492. static dispatch_once_t onceToken;
  493. dispatch_once(&onceToken, ^{
  494. gMenu = [[CCMenuAccount alloc] init];
  495. });
  496. return gMenu;
  497. }
  498. - (id)init
  499. {
  500. NSAssert(!gMenu, @"singleton object");
  501. self = [super init];
  502. if (self) {
  503. }
  504. return self;
  505. }
  506. - (void)dealloc
  507. {
  508. if (_observing) {
  509. [[NSNotificationCenter defaultCenter] removeObserver:self];
  510. }
  511. }
  512. - (void)showMenuInView:(UIView *)view fromRect:(CGRect)rect menuItems:(NSArray *)menuItems withOptions:(OptionalConfiguration) options
  513. {
  514. NSParameterAssert(view);
  515. NSParameterAssert(menuItems.count);
  516. if (_menuView) {
  517. [_menuView dismissMenu:NO];
  518. _menuView = nil;
  519. }
  520. if (!_observing) {
  521. _observing = YES;
  522. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(orientationWillChange:) name:UIApplicationWillChangeStatusBarOrientationNotification object:nil];
  523. }
  524. _menuView = [[CCMenuView alloc] init];
  525. [_menuView showMenuInView:view fromRect:rect menuItems:menuItems withOptions:options];
  526. }
  527. - (void)dismissMenu
  528. {
  529. if (_menuView) {
  530. [_menuView dismissMenu:NO];
  531. _menuView = nil;
  532. }
  533. if (_observing) {
  534. _observing = NO;
  535. [[NSNotificationCenter defaultCenter] removeObserver:self];
  536. }
  537. }
  538. - (void)orientationWillChange: (NSNotification *) n
  539. {
  540. [self dismissMenu];
  541. }
  542. + (void)showMenuInView:(UIView *)view fromRect:(CGRect)rect menuItems:(NSArray *)menuItems withOptions:(OptionalConfiguration)options
  543. {
  544. [[self sharedMenu] showMenuInView:view fromRect:rect menuItems:menuItems withOptions:options];
  545. }
  546. + (void)dismissMenu
  547. {
  548. [[self sharedMenu] dismissMenu];
  549. }
  550. + (UIColor *)tintColor
  551. {
  552. return gTintColor;
  553. }
  554. + (void)setTintColor:(UIColor *)tintColor
  555. {
  556. if (tintColor != gTintColor) {
  557. gTintColor = tintColor;
  558. }
  559. }
  560. + (UIFont *)titleFont
  561. {
  562. return gTitleFont;
  563. }
  564. + (void)setTitleFont:(UIFont *)titleFont
  565. {
  566. if (titleFont != gTitleFont) {
  567. gTitleFont = titleFont;
  568. }
  569. }
  570. @end