KxMenu.m 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888
  1. //
  2. // KxMenu.m
  3. // kxmenu project
  4. // https://github.com/kolyvan/kxmenu/
  5. //
  6. // Created by Kolyvan on 17.05.13.
  7. //
  8. /*
  9. Copyright (c) 2013 Konstantin Bukreev. All rights reserved.
  10. Redistribution and use in source and binary forms, with or without
  11. modification, are permitted provided that the following conditions are met:
  12. - Redistributions of source code must retain the above copyright notice, this
  13. list of conditions and the following disclaimer.
  14. - Redistributions in binary form must reproduce the above copyright notice,
  15. this list of conditions and the following disclaimer in the documentation
  16. and/or other materials provided with the distribution.
  17. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  18. AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  19. IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  20. DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
  21. FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  22. DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  23. SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  24. CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  25. OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  26. OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  27. */
  28. /*
  29. Some ideas was taken from QBPopupMenu project by Katsuma Tanaka.
  30. https://github.com/questbeat/QBPopupMenu
  31. */
  32. #import "KxMenu.h"
  33. #import <QuartzCore/QuartzCore.h>
  34. const CGFloat kArrowSize = 12.f;
  35. ////////////////////////////////////////////////////////////////////////////////
  36. ////////////////////////////////////////////////////////////////////////////////
  37. @interface KxMenuView : UIView
  38. @end
  39. @interface KxMenuOverlay : UIView
  40. @end
  41. @implementation KxMenuOverlay
  42. // - (void) dealloc { NSLog(@"dealloc %@", self); }
  43. - (id)initWithFrame:(CGRect)frame
  44. {
  45. self = [super initWithFrame:frame];
  46. if (self) {
  47. self.backgroundColor = [UIColor clearColor];
  48. self.opaque = NO;
  49. UITapGestureRecognizer *gestureRecognizer;
  50. gestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self
  51. action:@selector(singleTap:)];
  52. [self addGestureRecognizer:gestureRecognizer];
  53. }
  54. return self;
  55. }
  56. // thank horaceho https://github.com/horaceho
  57. // for his solution described in https://github.com/kolyvan/kxmenu/issues/9
  58. - (void)singleTap:(UITapGestureRecognizer *)recognizer
  59. {
  60. for (UIView *v in self.subviews) {
  61. if ([v isKindOfClass:[KxMenuView class]] && [v respondsToSelector:@selector(dismissMenu:)]) {
  62. [v performSelector:@selector(dismissMenu:) withObject:@(YES)];
  63. }
  64. }
  65. }
  66. @end
  67. ////////////////////////////////////////////////////////////////////////////////
  68. ////////////////////////////////////////////////////////////////////////////////
  69. @implementation KxMenuItem
  70. + (instancetype) menuItem:(NSString *) title
  71. image:(UIImage *) image
  72. target:(id)target
  73. action:(SEL) action
  74. {
  75. return [[KxMenuItem alloc] init:title
  76. image:image
  77. target:target
  78. action:action];
  79. }
  80. - (id) init:(NSString *) title
  81. image:(UIImage *) image
  82. target:(id)target
  83. action:(SEL) action
  84. {
  85. NSParameterAssert(title.length || image);
  86. self = [super init];
  87. if (self) {
  88. _title = title;
  89. _image = image;
  90. _target = target;
  91. _action = action;
  92. }
  93. return self;
  94. }
  95. - (BOOL) enabled
  96. {
  97. return _target != nil && _action != NULL;
  98. }
  99. - (void) performAction
  100. {
  101. __strong id target = self.target;
  102. if (target && [target respondsToSelector:_action]) {
  103. [target performSelectorOnMainThread:_action withObject:self waitUntilDone:YES];
  104. }
  105. }
  106. - (NSString *) description
  107. {
  108. return [NSString stringWithFormat:@"<%@ #%p %@>", [self class], self, _title];
  109. }
  110. @end
  111. ////////////////////////////////////////////////////////////////////////////////
  112. ////////////////////////////////////////////////////////////////////////////////
  113. typedef enum {
  114. KxMenuViewArrowDirectionNone,
  115. KxMenuViewArrowDirectionUp,
  116. KxMenuViewArrowDirectionDown,
  117. KxMenuViewArrowDirectionLeft,
  118. KxMenuViewArrowDirectionRight,
  119. } KxMenuViewArrowDirection;
  120. @implementation KxMenuView {
  121. KxMenuViewArrowDirection _arrowDirection;
  122. CGFloat _arrowPosition;
  123. UIView *_contentView;
  124. NSArray *_menuItems;
  125. }
  126. - (id)init
  127. {
  128. self = [super initWithFrame:CGRectZero];
  129. if(self) {
  130. self.backgroundColor = [UIColor clearColor];
  131. self.opaque = YES;
  132. self.alpha = 0;
  133. self.layer.shadowOpacity = 0.5;
  134. self.layer.shadowOffset = CGSizeMake(2, 2);
  135. self.layer.shadowRadius = 2;
  136. }
  137. return self;
  138. }
  139. // - (void) dealloc { NSLog(@"dealloc %@", self); }
  140. - (void) setupFrameInView:(UIView *)view
  141. fromRect:(CGRect)fromRect
  142. {
  143. const CGSize contentSize = _contentView.frame.size;
  144. const CGFloat outerWidth = view.bounds.size.width;
  145. const CGFloat outerHeight = view.bounds.size.height;
  146. const CGFloat rectX0 = fromRect.origin.x;
  147. const CGFloat rectX1 = fromRect.origin.x + fromRect.size.width;
  148. const CGFloat rectXM = fromRect.origin.x + fromRect.size.width * 0.5f;
  149. const CGFloat rectY0 = fromRect.origin.y;
  150. const CGFloat rectY1 = fromRect.origin.y + fromRect.size.height;
  151. const CGFloat rectYM = fromRect.origin.y + fromRect.size.height * 0.5f;;
  152. const CGFloat widthPlusArrow = contentSize.width + kArrowSize;
  153. const CGFloat heightPlusArrow = contentSize.height + kArrowSize;
  154. const CGFloat widthHalf = contentSize.width * 0.5f;
  155. const CGFloat heightHalf = contentSize.height * 0.5f;
  156. const CGFloat kMargin = 5.f;
  157. if (heightPlusArrow < (outerHeight - rectY1)) {
  158. _arrowDirection = KxMenuViewArrowDirectionUp;
  159. CGPoint point = (CGPoint){
  160. rectXM - widthHalf,
  161. rectY1
  162. };
  163. if (point.x < kMargin)
  164. point.x = kMargin;
  165. if ((point.x + contentSize.width + kMargin) > outerWidth)
  166. point.x = outerWidth - contentSize.width - kMargin;
  167. _arrowPosition = rectXM - point.x;
  168. //_arrowPosition = MAX(16, MIN(_arrowPosition, contentSize.width - 16));
  169. _contentView.frame = (CGRect){0, kArrowSize, contentSize};
  170. self.frame = (CGRect) {
  171. point,
  172. contentSize.width,
  173. contentSize.height + kArrowSize
  174. };
  175. } else if (heightPlusArrow < rectY0) {
  176. _arrowDirection = KxMenuViewArrowDirectionDown;
  177. CGPoint point = (CGPoint){
  178. rectXM - widthHalf,
  179. rectY0 - heightPlusArrow
  180. };
  181. if (point.x < kMargin)
  182. point.x = kMargin;
  183. if ((point.x + contentSize.width + kMargin) > outerWidth)
  184. point.x = outerWidth - contentSize.width - kMargin;
  185. _arrowPosition = rectXM - point.x;
  186. _contentView.frame = (CGRect){CGPointZero, contentSize};
  187. self.frame = (CGRect) {
  188. point,
  189. contentSize.width,
  190. contentSize.height + kArrowSize
  191. };
  192. } else if (widthPlusArrow < (outerWidth - rectX1)) {
  193. _arrowDirection = KxMenuViewArrowDirectionLeft;
  194. CGPoint point = (CGPoint){
  195. rectX1,
  196. rectYM - heightHalf
  197. };
  198. if (point.y < kMargin)
  199. point.y = kMargin;
  200. if ((point.y + contentSize.height + kMargin) > outerHeight)
  201. point.y = outerHeight - contentSize.height - kMargin;
  202. _arrowPosition = rectYM - point.y;
  203. _contentView.frame = (CGRect){kArrowSize, 0, contentSize};
  204. self.frame = (CGRect) {
  205. point,
  206. contentSize.width + kArrowSize,
  207. contentSize.height
  208. };
  209. } else if (widthPlusArrow < rectX0) {
  210. _arrowDirection = KxMenuViewArrowDirectionRight;
  211. CGPoint point = (CGPoint){
  212. rectX0 - widthPlusArrow,
  213. rectYM - heightHalf
  214. };
  215. if (point.y < kMargin)
  216. point.y = kMargin;
  217. if ((point.y + contentSize.height + 5) > outerHeight)
  218. point.y = outerHeight - contentSize.height - kMargin;
  219. _arrowPosition = rectYM - point.y;
  220. _contentView.frame = (CGRect){CGPointZero, contentSize};
  221. self.frame = (CGRect) {
  222. point,
  223. contentSize.width + kArrowSize,
  224. contentSize.height
  225. };
  226. } else {
  227. _arrowDirection = KxMenuViewArrowDirectionNone;
  228. self.frame = (CGRect) {
  229. (outerWidth - contentSize.width) * 0.5f,
  230. (outerHeight - contentSize.height) * 0.5f,
  231. contentSize,
  232. };
  233. }
  234. }
  235. - (void)showMenuInView:(UIView *)view
  236. fromRect:(CGRect)rect
  237. menuItems:(NSArray *)menuItems
  238. {
  239. _menuItems = menuItems;
  240. _contentView = [self mkContentView];
  241. [self addSubview:_contentView];
  242. [self setupFrameInView:view fromRect:rect];
  243. KxMenuOverlay *overlay = [[KxMenuOverlay alloc] initWithFrame:view.bounds];
  244. [overlay addSubview:self];
  245. [view addSubview:overlay];
  246. _contentView.hidden = YES;
  247. const CGRect toFrame = self.frame;
  248. self.frame = (CGRect){self.arrowPoint, 1, 1};
  249. [UIView animateWithDuration:0.2
  250. animations:^(void) {
  251. self.alpha = 1.0f;
  252. self.frame = toFrame;
  253. } completion:^(BOOL completed) {
  254. _contentView.hidden = NO;
  255. }];
  256. }
  257. - (void)dismissMenu:(BOOL) animated
  258. {
  259. if (self.superview) {
  260. if (animated) {
  261. _contentView.hidden = YES;
  262. const CGRect toFrame = (CGRect){self.arrowPoint, 1, 1};
  263. [UIView animateWithDuration:0.2
  264. animations:^(void) {
  265. self.alpha = 0;
  266. self.frame = toFrame;
  267. } completion:^(BOOL finished) {
  268. if ([self.superview isKindOfClass:[KxMenuOverlay class]])
  269. [self.superview removeFromSuperview];
  270. [self removeFromSuperview];
  271. }];
  272. } else {
  273. if ([self.superview isKindOfClass:[KxMenuOverlay class]])
  274. [self.superview removeFromSuperview];
  275. [self removeFromSuperview];
  276. }
  277. }
  278. }
  279. - (void)performAction:(id)sender
  280. {
  281. [self dismissMenu:YES];
  282. UIButton *button = (UIButton *)sender;
  283. KxMenuItem *menuItem = _menuItems[button.tag];
  284. [menuItem performAction];
  285. }
  286. - (UIView *) mkContentView
  287. {
  288. for (UIView *v in self.subviews) {
  289. [v removeFromSuperview];
  290. }
  291. if (!_menuItems.count)
  292. return nil;
  293. const CGFloat kMinMenuItemHeight = 32.f;
  294. const CGFloat kMinMenuItemWidth = 32.f;
  295. const CGFloat kMarginX = 10.f;
  296. const CGFloat kMarginY = 5.f;
  297. UIFont *titleFont = [KxMenu titleFont];
  298. if (!titleFont) titleFont = [UIFont boldSystemFontOfSize:16];
  299. CGFloat maxImageWidth = 0;
  300. CGFloat maxItemHeight = 0;
  301. CGFloat maxItemWidth = 0;
  302. for (KxMenuItem *menuItem in _menuItems) {
  303. const CGSize imageSize = menuItem.image.size;
  304. if (imageSize.width > maxImageWidth)
  305. maxImageWidth = imageSize.width;
  306. }
  307. if (maxImageWidth) {
  308. maxImageWidth += kMarginX;
  309. }
  310. for (KxMenuItem *menuItem in _menuItems) {
  311. const CGSize titleSize = [menuItem.title sizeWithFont:titleFont];
  312. const CGSize imageSize = menuItem.image.size;
  313. const CGFloat itemHeight = MAX(titleSize.height, imageSize.height) + kMarginY * 2;
  314. const CGFloat itemWidth = ((!menuItem.enabled && !menuItem.image) ? titleSize.width : maxImageWidth + titleSize.width) + kMarginX * 4;
  315. if (itemHeight > maxItemHeight)
  316. maxItemHeight = itemHeight;
  317. if (itemWidth > maxItemWidth)
  318. maxItemWidth = itemWidth;
  319. }
  320. maxItemWidth = MAX(maxItemWidth, kMinMenuItemWidth);
  321. maxItemHeight = MAX(maxItemHeight, kMinMenuItemHeight);
  322. const CGFloat titleX = kMarginX * 2 + maxImageWidth;
  323. const CGFloat titleWidth = maxItemWidth - titleX - kMarginX * 2;
  324. UIImage *selectedImage = [KxMenuView selectedImage:(CGSize){maxItemWidth, maxItemHeight + 2}];
  325. UIImage *gradientLine = [KxMenuView gradientLine: (CGSize){maxItemWidth - kMarginX * 4, 1}];
  326. UIView *contentView = [[UIView alloc] initWithFrame:CGRectZero];
  327. contentView.autoresizingMask = UIViewAutoresizingNone;
  328. contentView.backgroundColor = [UIColor clearColor];
  329. contentView.opaque = NO;
  330. CGFloat itemY = kMarginY * 2;
  331. NSUInteger itemNum = 0;
  332. for (KxMenuItem *menuItem in _menuItems) {
  333. const CGRect itemFrame = (CGRect){0, itemY, maxItemWidth, maxItemHeight};
  334. UIView *itemView = [[UIView alloc] initWithFrame:itemFrame];
  335. itemView.autoresizingMask = UIViewAutoresizingNone;
  336. itemView.backgroundColor = [UIColor clearColor];
  337. itemView.opaque = NO;
  338. [contentView addSubview:itemView];
  339. if (menuItem.enabled) {
  340. UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
  341. button.tag = itemNum;
  342. button.frame = itemView.bounds;
  343. button.enabled = menuItem.enabled;
  344. button.backgroundColor = [UIColor clearColor];
  345. button.opaque = NO;
  346. button.autoresizingMask = UIViewAutoresizingNone;
  347. [button addTarget:self
  348. action:@selector(performAction:)
  349. forControlEvents:UIControlEventTouchUpInside];
  350. [button setBackgroundImage:selectedImage forState:UIControlStateHighlighted];
  351. [itemView addSubview:button];
  352. }
  353. if (menuItem.title.length) {
  354. CGRect titleFrame;
  355. if (!menuItem.enabled && !menuItem.image) {
  356. titleFrame = (CGRect){
  357. kMarginX * 2,
  358. kMarginY,
  359. maxItemWidth - kMarginX * 4,
  360. maxItemHeight - kMarginY * 2
  361. };
  362. } else {
  363. titleFrame = (CGRect){
  364. titleX,
  365. kMarginY,
  366. titleWidth,
  367. maxItemHeight - kMarginY * 2
  368. };
  369. }
  370. UILabel *titleLabel = [[UILabel alloc] initWithFrame:titleFrame];
  371. titleLabel.text = menuItem.title;
  372. titleLabel.font = titleFont;
  373. titleLabel.textAlignment = menuItem.alignment;
  374. titleLabel.textColor = menuItem.foreColor ? menuItem.foreColor : [UIColor whiteColor];
  375. titleLabel.backgroundColor = [UIColor clearColor];
  376. titleLabel.autoresizingMask = UIViewAutoresizingNone;
  377. //titleLabel.backgroundColor = [UIColor greenColor];
  378. [itemView addSubview:titleLabel];
  379. }
  380. if (menuItem.image) {
  381. const CGRect imageFrame = {kMarginX * 2, kMarginY, maxImageWidth, maxItemHeight - kMarginY * 2};
  382. UIImageView *imageView = [[UIImageView alloc] initWithFrame:imageFrame];
  383. imageView.image = menuItem.image;
  384. imageView.clipsToBounds = YES;
  385. imageView.contentMode = UIViewContentModeCenter;
  386. imageView.autoresizingMask = UIViewAutoresizingNone;
  387. [itemView addSubview:imageView];
  388. }
  389. if (itemNum < _menuItems.count - 1) {
  390. UIImageView *gradientView = [[UIImageView alloc] initWithImage:gradientLine];
  391. gradientView.frame = (CGRect){kMarginX * 2, maxItemHeight + 1, gradientLine.size};
  392. gradientView.contentMode = UIViewContentModeLeft;
  393. [itemView addSubview:gradientView];
  394. itemY += 2;
  395. }
  396. itemY += maxItemHeight;
  397. ++itemNum;
  398. }
  399. contentView.frame = (CGRect){0, 0, maxItemWidth, itemY + kMarginY * 2};
  400. return contentView;
  401. }
  402. - (CGPoint) arrowPoint
  403. {
  404. CGPoint point;
  405. if (_arrowDirection == KxMenuViewArrowDirectionUp) {
  406. point = (CGPoint){ CGRectGetMinX(self.frame) + _arrowPosition, CGRectGetMinY(self.frame) };
  407. } else if (_arrowDirection == KxMenuViewArrowDirectionDown) {
  408. point = (CGPoint){ CGRectGetMinX(self.frame) + _arrowPosition, CGRectGetMaxY(self.frame) };
  409. } else if (_arrowDirection == KxMenuViewArrowDirectionLeft) {
  410. point = (CGPoint){ CGRectGetMinX(self.frame), CGRectGetMinY(self.frame) + _arrowPosition };
  411. } else if (_arrowDirection == KxMenuViewArrowDirectionRight) {
  412. point = (CGPoint){ CGRectGetMaxX(self.frame), CGRectGetMinY(self.frame) + _arrowPosition };
  413. } else {
  414. point = self.center;
  415. }
  416. return point;
  417. }
  418. + (UIImage *) selectedImage: (CGSize) size
  419. {
  420. const CGFloat locations[] = {0,1};
  421. const CGFloat components[] = {
  422. 0.216, 0.471, 0.871, 1,
  423. 0.059, 0.353, 0.839, 1,
  424. };
  425. return [self gradientImageWithSize:size locations:locations components:components count:2];
  426. }
  427. + (UIImage *) gradientLine: (CGSize) size
  428. {
  429. const CGFloat locations[5] = {0,0.2,0.5,0.8,1};
  430. const CGFloat R = 0.44f, G = 0.44f, B = 0.44f;
  431. const CGFloat components[20] = {
  432. R,G,B,0.1,
  433. R,G,B,0.4,
  434. R,G,B,0.7,
  435. R,G,B,0.4,
  436. R,G,B,0.1
  437. };
  438. return [self gradientImageWithSize:size locations:locations components:components count:5];
  439. }
  440. + (UIImage *) gradientImageWithSize:(CGSize) size
  441. locations:(const CGFloat []) locations
  442. components:(const CGFloat []) components
  443. count:(NSUInteger)count
  444. {
  445. UIGraphicsBeginImageContextWithOptions(size, NO, 0);
  446. CGContextRef context = UIGraphicsGetCurrentContext();
  447. CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
  448. CGGradientRef colorGradient = CGGradientCreateWithColorComponents(colorSpace, components, locations, 2);
  449. CGColorSpaceRelease(colorSpace);
  450. CGContextDrawLinearGradient(context, colorGradient, (CGPoint){0, 0}, (CGPoint){size.width, 0}, 0);
  451. CGGradientRelease(colorGradient);
  452. UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
  453. UIGraphicsEndImageContext();
  454. return image;
  455. }
  456. - (void) drawRect:(CGRect)rect
  457. {
  458. [self drawBackground:self.bounds
  459. inContext:UIGraphicsGetCurrentContext()];
  460. }
  461. - (void)drawBackground:(CGRect)frame
  462. inContext:(CGContextRef) context
  463. {
  464. CGFloat R0 = 0.267, G0 = 0.303, B0 = 0.335;
  465. CGFloat R1 = 0.040, G1 = 0.040, B1 = 0.040;
  466. UIColor *tintColor = [KxMenu tintColor];
  467. if (tintColor) {
  468. CGFloat a;
  469. [tintColor getRed:&R0 green:&G0 blue:&B0 alpha:&a];
  470. }
  471. CGFloat X0 = frame.origin.x;
  472. CGFloat X1 = frame.origin.x + frame.size.width;
  473. CGFloat Y0 = frame.origin.y;
  474. CGFloat Y1 = frame.origin.y + frame.size.height;
  475. // render arrow
  476. UIBezierPath *arrowPath = [UIBezierPath bezierPath];
  477. // fix the issue with gap of arrow's base if on the edge
  478. const CGFloat kEmbedFix = 3.f;
  479. if (_arrowDirection == KxMenuViewArrowDirectionUp) {
  480. const CGFloat arrowXM = _arrowPosition;
  481. const CGFloat arrowX0 = arrowXM - kArrowSize;
  482. const CGFloat arrowX1 = arrowXM + kArrowSize;
  483. const CGFloat arrowY0 = Y0;
  484. const CGFloat arrowY1 = Y0 + kArrowSize + kEmbedFix;
  485. [arrowPath moveToPoint: (CGPoint){arrowXM, arrowY0}];
  486. [arrowPath addLineToPoint: (CGPoint){arrowX1, arrowY1}];
  487. [arrowPath addLineToPoint: (CGPoint){arrowX0, arrowY1}];
  488. [arrowPath addLineToPoint: (CGPoint){arrowXM, arrowY0}];
  489. [[UIColor colorWithRed:R0 green:G0 blue:B0 alpha:1] set];
  490. Y0 += kArrowSize;
  491. } else if (_arrowDirection == KxMenuViewArrowDirectionDown) {
  492. const CGFloat arrowXM = _arrowPosition;
  493. const CGFloat arrowX0 = arrowXM - kArrowSize;
  494. const CGFloat arrowX1 = arrowXM + kArrowSize;
  495. const CGFloat arrowY0 = Y1 - kArrowSize - kEmbedFix;
  496. const CGFloat arrowY1 = Y1;
  497. [arrowPath moveToPoint: (CGPoint){arrowXM, arrowY1}];
  498. [arrowPath addLineToPoint: (CGPoint){arrowX1, arrowY0}];
  499. [arrowPath addLineToPoint: (CGPoint){arrowX0, arrowY0}];
  500. [arrowPath addLineToPoint: (CGPoint){arrowXM, arrowY1}];
  501. [[UIColor colorWithRed:R1 green:G1 blue:B1 alpha:1] set];
  502. Y1 -= kArrowSize;
  503. } else if (_arrowDirection == KxMenuViewArrowDirectionLeft) {
  504. const CGFloat arrowYM = _arrowPosition;
  505. const CGFloat arrowX0 = X0;
  506. const CGFloat arrowX1 = X0 + kArrowSize + kEmbedFix;
  507. const CGFloat arrowY0 = arrowYM - kArrowSize;;
  508. const CGFloat arrowY1 = arrowYM + kArrowSize;
  509. [arrowPath moveToPoint: (CGPoint){arrowX0, arrowYM}];
  510. [arrowPath addLineToPoint: (CGPoint){arrowX1, arrowY0}];
  511. [arrowPath addLineToPoint: (CGPoint){arrowX1, arrowY1}];
  512. [arrowPath addLineToPoint: (CGPoint){arrowX0, arrowYM}];
  513. [[UIColor colorWithRed:R0 green:G0 blue:B0 alpha:1] set];
  514. X0 += kArrowSize;
  515. } else if (_arrowDirection == KxMenuViewArrowDirectionRight) {
  516. const CGFloat arrowYM = _arrowPosition;
  517. const CGFloat arrowX0 = X1;
  518. const CGFloat arrowX1 = X1 - kArrowSize - kEmbedFix;
  519. const CGFloat arrowY0 = arrowYM - kArrowSize;;
  520. const CGFloat arrowY1 = arrowYM + kArrowSize;
  521. [arrowPath moveToPoint: (CGPoint){arrowX0, arrowYM}];
  522. [arrowPath addLineToPoint: (CGPoint){arrowX1, arrowY0}];
  523. [arrowPath addLineToPoint: (CGPoint){arrowX1, arrowY1}];
  524. [arrowPath addLineToPoint: (CGPoint){arrowX0, arrowYM}];
  525. [[UIColor colorWithRed:R1 green:G1 blue:B1 alpha:1] set];
  526. X1 -= kArrowSize;
  527. }
  528. [arrowPath fill];
  529. // render body
  530. const CGRect bodyFrame = {X0, Y0, X1 - X0, Y1 - Y0};
  531. UIBezierPath *borderPath = [UIBezierPath bezierPathWithRoundedRect:bodyFrame
  532. cornerRadius:8];
  533. const CGFloat locations[] = {0, 1};
  534. const CGFloat components[] = {
  535. R0, G0, B0, 1,
  536. R1, G1, B1, 1,
  537. };
  538. CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
  539. CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace,
  540. components,
  541. locations,
  542. sizeof(locations)/sizeof(locations[0]));
  543. CGColorSpaceRelease(colorSpace);
  544. [borderPath addClip];
  545. CGPoint start, end;
  546. if (_arrowDirection == KxMenuViewArrowDirectionLeft ||
  547. _arrowDirection == KxMenuViewArrowDirectionRight) {
  548. start = (CGPoint){X0, Y0};
  549. end = (CGPoint){X1, Y0};
  550. } else {
  551. start = (CGPoint){X0, Y0};
  552. end = (CGPoint){X0, Y1};
  553. }
  554. CGContextDrawLinearGradient(context, gradient, start, end, 0);
  555. CGGradientRelease(gradient);
  556. }
  557. @end
  558. ////////////////////////////////////////////////////////////////////////////////
  559. ////////////////////////////////////////////////////////////////////////////////
  560. static KxMenu *gMenu;
  561. static UIColor *gTintColor;
  562. static UIFont *gTitleFont;
  563. @implementation KxMenu {
  564. KxMenuView *_menuView;
  565. BOOL _observing;
  566. }
  567. + (instancetype) sharedMenu
  568. {
  569. static dispatch_once_t onceToken;
  570. dispatch_once(&onceToken, ^{
  571. gMenu = [[KxMenu alloc] init];
  572. });
  573. return gMenu;
  574. }
  575. - (id) init
  576. {
  577. NSAssert(!gMenu, @"singleton object");
  578. self = [super init];
  579. if (self) {
  580. }
  581. return self;
  582. }
  583. - (void) dealloc
  584. {
  585. if (_observing) {
  586. [[NSNotificationCenter defaultCenter] removeObserver:self];
  587. }
  588. }
  589. - (void) showMenuInView:(UIView *)view
  590. fromRect:(CGRect)rect
  591. menuItems:(NSArray *)menuItems
  592. {
  593. NSParameterAssert(view);
  594. NSParameterAssert(menuItems.count);
  595. if (_menuView) {
  596. [_menuView dismissMenu:NO];
  597. _menuView = nil;
  598. }
  599. if (!_observing) {
  600. _observing = YES;
  601. [[NSNotificationCenter defaultCenter] addObserver:self
  602. selector:@selector(orientationWillChange:)
  603. name:UIApplicationWillChangeStatusBarOrientationNotification
  604. object:nil];
  605. }
  606. _menuView = [[KxMenuView alloc] init];
  607. [_menuView showMenuInView:view fromRect:rect menuItems:menuItems];
  608. }
  609. - (void) dismissMenu
  610. {
  611. if (_menuView) {
  612. [_menuView dismissMenu:NO];
  613. _menuView = nil;
  614. }
  615. if (_observing) {
  616. _observing = NO;
  617. [[NSNotificationCenter defaultCenter] removeObserver:self];
  618. }
  619. }
  620. - (void) orientationWillChange: (NSNotification *) n
  621. {
  622. [self dismissMenu];
  623. }
  624. + (void) showMenuInView:(UIView *)view
  625. fromRect:(CGRect)rect
  626. menuItems:(NSArray *)menuItems
  627. {
  628. [[self sharedMenu] showMenuInView:view fromRect:rect menuItems:menuItems];
  629. }
  630. + (void) dismissMenu
  631. {
  632. [[self sharedMenu] dismissMenu];
  633. }
  634. + (UIColor *) tintColor
  635. {
  636. return gTintColor;
  637. }
  638. + (void) setTintColor: (UIColor *) tintColor
  639. {
  640. if (tintColor != gTintColor) {
  641. gTintColor = tintColor;
  642. }
  643. }
  644. + (UIFont *) titleFont
  645. {
  646. return gTitleFont;
  647. }
  648. + (void) setTitleFont: (UIFont *) titleFont
  649. {
  650. if (titleFont != gTitleFont) {
  651. gTitleFont = titleFont;
  652. }
  653. }
  654. @end