FLEXArgumentInputColorView.m 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  1. //
  2. // FLEXArgumentInputColorView.m
  3. // Flipboard
  4. //
  5. // Created by Ryan Olson on 6/30/14.
  6. // Copyright (c) 2020 FLEX Team. All rights reserved.
  7. //
  8. #import "FLEXArgumentInputColorView.h"
  9. #import "FLEXUtility.h"
  10. #import "FLEXRuntimeUtility.h"
  11. #import "fakes.h"
  12. @protocol FLEXColorComponentInputViewDelegate;
  13. @interface FLEXColorComponentInputView : UIView
  14. #if !TARGET_OS_TV
  15. @property (nonatomic) UISlider *slider;
  16. #else
  17. @property (nonatomic) KBSlider *slider;
  18. #endif
  19. @property (nonatomic) UILabel *valueLabel;
  20. @property (nonatomic, weak) id <FLEXColorComponentInputViewDelegate> delegate;
  21. @end
  22. @protocol FLEXColorComponentInputViewDelegate <NSObject>
  23. - (void)colorComponentInputViewValueDidChange:(FLEXColorComponentInputView *)colorComponentInputView;
  24. @end
  25. @implementation FLEXColorComponentInputView
  26. - (id)initWithFrame:(CGRect)frame {
  27. self = [super initWithFrame:frame];
  28. if (self) {
  29. #if !TARGET_OS_TV
  30. self.slider = [UISlider new];
  31. #else
  32. self.slider = [[KBSlider alloc] initWithFrame:CGRectMake(0, 0, 1000, 53)];
  33. #endif
  34. [self.slider addTarget:self action:@selector(sliderChanged:) forControlEvents:UIControlEventValueChanged];
  35. [self addSubview:self.slider];
  36. self.valueLabel = [UILabel new];
  37. self.valueLabel.backgroundColor = self.backgroundColor;
  38. self.valueLabel.font = [UIFont systemFontOfSize:14.0];
  39. self.valueLabel.textAlignment = NSTextAlignmentRight;
  40. [self addSubview:self.valueLabel];
  41. [self updateValueLabel];
  42. }
  43. return self;
  44. }
  45. - (void)setBackgroundColor:(UIColor *)backgroundColor {
  46. [super setBackgroundColor:backgroundColor];
  47. self.slider.backgroundColor = backgroundColor;
  48. self.valueLabel.backgroundColor = backgroundColor;
  49. }
  50. - (void)layoutSubviews {
  51. [super layoutSubviews];
  52. const CGFloat kValueLabelWidth = 50.0;
  53. UIEdgeInsets sliderInset = UIEdgeInsetsZero;
  54. #if TARGET_OS_TV
  55. sliderInset.left = 40;
  56. sliderInset.right = 40;
  57. #endif
  58. [self.slider sizeToFit];
  59. CGFloat sliderWidth = self.bounds.size.width - kValueLabelWidth - sliderInset.left - sliderInset.right;
  60. self.slider.frame = CGRectMake(sliderInset.left, 0, sliderWidth, self.slider.frame.size.height);
  61. [self.valueLabel sizeToFit];
  62. CGFloat valueLabelOriginX = CGRectGetMaxX(self.slider.frame) + sliderInset.right/2.0;
  63. CGFloat valueLabelOriginY = FLEXFloor((self.slider.frame.size.height - self.valueLabel.frame.size.height) / 2.0);
  64. self.valueLabel.frame = CGRectMake(valueLabelOriginX, valueLabelOriginY, kValueLabelWidth, self.valueLabel.frame.size.height);
  65. }
  66. - (void)sliderChanged:(id)sender {
  67. [self.delegate colorComponentInputViewValueDidChange:self];
  68. [self updateValueLabel];
  69. }
  70. - (void)updateValueLabel {
  71. self.valueLabel.text = [NSString stringWithFormat:@"%.3f", self.slider.value];
  72. }
  73. - (CGSize)sizeThatFits:(CGSize)size {
  74. CGFloat height = [self.slider sizeThatFits:size].height;
  75. return CGSizeMake(size.width, height);
  76. }
  77. @end
  78. @interface FLEXColorPreviewBox : UIView
  79. @property (nonatomic) UIColor *color;
  80. @property (nonatomic) UIView *colorOverlayView;
  81. @end
  82. @implementation FLEXColorPreviewBox
  83. - (id)initWithFrame:(CGRect)frame {
  84. self = [super initWithFrame:frame];
  85. if (self) {
  86. self.layer.borderWidth = 1.0;
  87. self.layer.borderColor = UIColor.blackColor.CGColor;
  88. self.backgroundColor = [UIColor colorWithPatternImage:[[self class] backgroundPatternImage]];
  89. self.colorOverlayView = [[UIView alloc] initWithFrame:self.bounds];
  90. self.colorOverlayView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
  91. self.colorOverlayView.backgroundColor = UIColor.clearColor;
  92. [self addSubview:self.colorOverlayView];
  93. }
  94. return self;
  95. }
  96. - (void)setColor:(UIColor *)color {
  97. self.colorOverlayView.backgroundColor = color;
  98. }
  99. - (UIColor *)color {
  100. return self.colorOverlayView.backgroundColor;
  101. }
  102. + (UIImage *)backgroundPatternImage {
  103. const CGFloat kSquareDimension = 5.0;
  104. CGSize squareSize = CGSizeMake(kSquareDimension, kSquareDimension);
  105. CGSize imageSize = CGSizeMake(2.0 * kSquareDimension, 2.0 * kSquareDimension);
  106. UIGraphicsBeginImageContextWithOptions(imageSize, YES, UIScreen.mainScreen.scale);
  107. [UIColor.whiteColor setFill];
  108. UIRectFill(CGRectMake(0, 0, imageSize.width, imageSize.height));
  109. [UIColor.grayColor setFill];
  110. UIRectFill(CGRectMake(squareSize.width, 0, squareSize.width, squareSize.height));
  111. UIRectFill(CGRectMake(0, squareSize.height, squareSize.width, squareSize.height));
  112. UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
  113. UIGraphicsEndImageContext();
  114. return image;
  115. }
  116. @end
  117. @interface FLEXArgumentInputColorView () <FLEXColorComponentInputViewDelegate>
  118. @property (nonatomic) FLEXColorPreviewBox *colorPreviewBox;
  119. @property (nonatomic) UILabel *hexLabel;
  120. @property (nonatomic) FLEXColorComponentInputView *alphaInput;
  121. @property (nonatomic) FLEXColorComponentInputView *redInput;
  122. @property (nonatomic) FLEXColorComponentInputView *greenInput;
  123. @property (nonatomic) FLEXColorComponentInputView *blueInput;
  124. @end
  125. @implementation FLEXArgumentInputColorView
  126. - (instancetype)initWithArgumentTypeEncoding:(const char *)typeEncoding {
  127. self = [super initWithArgumentTypeEncoding:typeEncoding];
  128. if (self) {
  129. self.colorPreviewBox = [FLEXColorPreviewBox new];
  130. [self addSubview:self.colorPreviewBox];
  131. self.hexLabel = [UILabel new];
  132. self.hexLabel.backgroundColor = [UIColor colorWithWhite:1.0 alpha:0.9];
  133. self.hexLabel.textAlignment = NSTextAlignmentCenter;
  134. self.hexLabel.font = [UIFont systemFontOfSize:12.0];
  135. [self addSubview:self.hexLabel];
  136. self.alphaInput = [FLEXColorComponentInputView new];
  137. self.alphaInput.slider.minimumTrackTintColor = UIColor.blackColor;
  138. self.alphaInput.delegate = self;
  139. [self addSubview:self.alphaInput];
  140. self.redInput = [FLEXColorComponentInputView new];
  141. self.redInput.slider.minimumTrackTintColor = UIColor.redColor;
  142. self.redInput.delegate = self;
  143. [self addSubview:self.redInput];
  144. self.greenInput = [FLEXColorComponentInputView new];
  145. self.greenInput.slider.minimumTrackTintColor = UIColor.greenColor;
  146. self.greenInput.delegate = self;
  147. [self addSubview:self.greenInput];
  148. self.blueInput = [FLEXColorComponentInputView new];
  149. self.blueInput.slider.minimumTrackTintColor = UIColor.blueColor;
  150. self.blueInput.delegate = self;
  151. [self addSubview:self.blueInput];
  152. }
  153. return self;
  154. }
  155. - (void)setBackgroundColor:(UIColor *)backgroundColor {
  156. [super setBackgroundColor:backgroundColor];
  157. self.alphaInput.backgroundColor = backgroundColor;
  158. self.redInput.backgroundColor = backgroundColor;
  159. self.greenInput.backgroundColor = backgroundColor;
  160. self.blueInput.backgroundColor = backgroundColor;
  161. }
  162. - (void)layoutSubviews {
  163. [super layoutSubviews];
  164. CGFloat runningOriginY = 0;
  165. CGSize constrainSize = CGSizeMake(self.bounds.size.width, CGFLOAT_MAX);
  166. self.colorPreviewBox.frame = CGRectMake(0, runningOriginY, self.bounds.size.width, [[self class] colorPreviewBoxHeight]);
  167. runningOriginY = CGRectGetMaxY(self.colorPreviewBox.frame) + [[self class] inputViewVerticalPadding];
  168. [self.hexLabel sizeToFit];
  169. const CGFloat kLabelVerticalOutsetAmount = 0.0;
  170. const CGFloat kLabelHorizontalOutsetAmount = 2.0;
  171. UIEdgeInsets labelOutset = UIEdgeInsetsMake(-kLabelVerticalOutsetAmount, -kLabelHorizontalOutsetAmount, -kLabelVerticalOutsetAmount, -kLabelHorizontalOutsetAmount);
  172. self.hexLabel.frame = UIEdgeInsetsInsetRect(self.hexLabel.frame, labelOutset);
  173. CGFloat hexLabelOriginX = self.colorPreviewBox.layer.borderWidth;
  174. CGFloat hexLabelOriginY = CGRectGetMaxY(self.colorPreviewBox.frame) - self.colorPreviewBox.layer.borderWidth - self.hexLabel.frame.size.height;
  175. self.hexLabel.frame = CGRectMake(hexLabelOriginX, hexLabelOriginY, self.hexLabel.frame.size.width, self.hexLabel.frame.size.height);
  176. NSArray<FLEXColorComponentInputView *> *colorComponentInputViews = @[self.alphaInput, self.redInput, self.greenInput, self.blueInput];
  177. for (FLEXColorComponentInputView *inputView in colorComponentInputViews) {
  178. CGSize fitSize = [inputView sizeThatFits:constrainSize];
  179. inputView.frame = CGRectMake(0, runningOriginY, fitSize.width, fitSize.height);
  180. runningOriginY = CGRectGetMaxY(inputView.frame) + [[self class] inputViewVerticalPadding];
  181. }
  182. }
  183. - (void)setInputValue:(id)inputValue {
  184. if ([inputValue isKindOfClass:[UIColor class]]) {
  185. [self updateWithColor:inputValue];
  186. } else if ([inputValue isKindOfClass:[NSValue class]]) {
  187. const char *type = [inputValue objCType];
  188. if (strcmp(type, @encode(CGColorRef)) == 0) {
  189. CGColorRef colorRef;
  190. [inputValue getValue:&colorRef];
  191. UIColor *color = [[UIColor alloc] initWithCGColor:colorRef];
  192. [self updateWithColor:color];
  193. }
  194. } else {
  195. [self updateWithColor:UIColor.clearColor];
  196. }
  197. }
  198. - (id)inputValue {
  199. return [UIColor colorWithRed:self.redInput.slider.value green:self.greenInput.slider.value blue:self.blueInput.slider.value alpha:self.alphaInput.slider.value];
  200. }
  201. - (void)colorComponentInputViewValueDidChange:(FLEXColorComponentInputView *)colorComponentInputView {
  202. [self updateColorPreview];
  203. }
  204. - (void)updateWithColor:(UIColor *)color {
  205. CGFloat red, green, blue, white, alpha;
  206. if ([color getRed:&red green:&green blue:&blue alpha:&alpha]) {
  207. self.alphaInput.slider.value = alpha;
  208. [self.alphaInput updateValueLabel];
  209. self.redInput.slider.value = red;
  210. [self.redInput updateValueLabel];
  211. self.greenInput.slider.value = green;
  212. [self.greenInput updateValueLabel];
  213. self.blueInput.slider.value = blue;
  214. [self.blueInput updateValueLabel];
  215. } else if ([color getWhite:&white alpha:&alpha]) {
  216. self.alphaInput.slider.value = alpha;
  217. [self.alphaInput updateValueLabel];
  218. self.redInput.slider.value = white;
  219. [self.redInput updateValueLabel];
  220. self.greenInput.slider.value = white;
  221. [self.greenInput updateValueLabel];
  222. self.blueInput.slider.value = white;
  223. [self.blueInput updateValueLabel];
  224. }
  225. [self updateColorPreview];
  226. }
  227. - (void)updateColorPreview {
  228. self.colorPreviewBox.color = self.inputValue;
  229. unsigned char redByte = self.redInput.slider.value * 255;
  230. unsigned char greenByte = self.greenInput.slider.value * 255;
  231. unsigned char blueByte = self.blueInput.slider.value * 255;
  232. self.hexLabel.text = [NSString stringWithFormat:@"#%02X%02X%02X", redByte, greenByte, blueByte];
  233. [self setNeedsLayout];
  234. }
  235. - (CGSize)sizeThatFits:(CGSize)size {
  236. CGFloat height = 0;
  237. height += [[self class] colorPreviewBoxHeight];
  238. height += [[self class] inputViewVerticalPadding];
  239. height += [self.alphaInput sizeThatFits:size].height;
  240. height += [[self class] inputViewVerticalPadding];
  241. height += [self.redInput sizeThatFits:size].height;
  242. height += [[self class] inputViewVerticalPadding];
  243. height += [self.greenInput sizeThatFits:size].height;
  244. height += [[self class] inputViewVerticalPadding];
  245. height += [self.blueInput sizeThatFits:size].height;
  246. return CGSizeMake(size.width, height);
  247. }
  248. + (CGFloat)inputViewVerticalPadding {
  249. return 10.0;
  250. }
  251. + (CGFloat)colorPreviewBoxHeight {
  252. return 40.0;
  253. }
  254. + (BOOL)supportsObjCType:(const char *)type withCurrentValue:(id)value {
  255. NSParameterAssert(type);
  256. // We don't care if currentValue is a color or not; we will default to +clearColor
  257. return (strcmp(type, @encode(CGColorRef)) == 0) || (strcmp(type, FLEXEncodeClass(UIColor)) == 0);
  258. }
  259. @end