增加换肤功能
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaPasterConfig.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaPopupController.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol TUIMultimediaPasterSelectControllerDelegate;
|
||||
|
||||
/**
|
||||
贴纸选择界面Controller
|
||||
*/
|
||||
@interface TUIMultimediaPasterSelectController : TUIMultimediaPopupController
|
||||
@property(weak, nullable, nonatomic) id<TUIMultimediaPasterSelectControllerDelegate> delegate;
|
||||
@end
|
||||
|
||||
@protocol TUIMultimediaPasterSelectControllerDelegate <NSObject>
|
||||
- (void)pasterSelectController:(TUIMultimediaPasterSelectController *)c onPasterSelected:(UIImage *)image;
|
||||
- (void)onPasterSelectControllerExit:(TUIMultimediaPasterSelectController *)c;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,92 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import "TUIMultimediaPasterSelectController.h"
|
||||
#import <Masonry/Masonry.h>
|
||||
#import <ReactiveObjC/RACEXTScope.h>
|
||||
#import "TUIMultimediaPlugin/NSArray+Functional.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaCommon.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaImagePicker.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaPasterSelectView.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaPersistence.h"
|
||||
|
||||
@interface TUIMultimediaPasterSelectController () <TUIMultimediaPasterSelectViewDelegate> {
|
||||
TUIMultimediaPasterSelectView *_selectView;
|
||||
TUIMultimediaImagePicker *_picker;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIMultimediaPasterSelectController
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
_selectView = [[TUIMultimediaPasterSelectView alloc] init];
|
||||
_selectView.config = [TUIMultimediaPasterConfig loadConfig];
|
||||
_selectView.delegate = self;
|
||||
self.mainView = _selectView;
|
||||
}
|
||||
|
||||
- (void)popupControllerDidCanceled {
|
||||
[_delegate onPasterSelectControllerExit:self];
|
||||
}
|
||||
|
||||
- (void)onPasterSelected:(UIImage *)image {
|
||||
[_delegate pasterSelectController:self onPasterSelected:image];
|
||||
}
|
||||
|
||||
- (void)pasterSelectView:(TUIMultimediaPasterSelectView *)v needAddCustomPaster:(TUIMultimediaPasterGroupConfig *)group completeCallback:(void (^)(void))callback {
|
||||
_picker = [[TUIMultimediaImagePicker alloc] init];
|
||||
@weakify(self);
|
||||
_picker.callback = ^(UIImage *img) {
|
||||
@strongify(self);
|
||||
if (img == nil || self == nil) {
|
||||
return;
|
||||
}
|
||||
NSURL *url = [TUIMultimediaPasterConfig saveCustomPaster:img];
|
||||
if (url == nil) {
|
||||
return;
|
||||
}
|
||||
TUIMultimediaPasterItemConfig *item = [[TUIMultimediaPasterItemConfig alloc] init];
|
||||
item.imageUrl = url;
|
||||
item.isUserAdded = YES;
|
||||
group.itemList = [group.itemList arrayByAddingObject:item];
|
||||
[TUIMultimediaPasterConfig saveConfig:v.config];
|
||||
callback();
|
||||
};
|
||||
[_picker presentOn:self];
|
||||
}
|
||||
|
||||
- (void)pasterSelectView:(TUIMultimediaPasterSelectView *)v
|
||||
needDeleteCustomPasterInGroup:(TUIMultimediaPasterGroupConfig *)group
|
||||
index:(NSInteger)index
|
||||
completeCallback:(void (^)(BOOL deleted))callback {
|
||||
UIAlertController *alert = [UIAlertController alertControllerWithTitle:[TUIMultimediaCommon localizedStringForKey:@"remove_paster"]
|
||||
message:nil
|
||||
preferredStyle:UIAlertControllerStyleActionSheet];
|
||||
|
||||
UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:[TUIMultimediaCommon localizedStringForKey:@"cancel"]
|
||||
style:UIAlertActionStyleCancel
|
||||
handler:^(UIAlertAction *action) {
|
||||
callback(NO);
|
||||
}];
|
||||
UIAlertAction *deleteAction = [UIAlertAction actionWithTitle:[TUIMultimediaCommon localizedStringForKey:@"delete"]
|
||||
style:UIAlertActionStyleDestructive
|
||||
handler:^(UIAlertAction *action) {
|
||||
NSMutableArray<TUIMultimediaPasterItemConfig *> *list = [NSMutableArray arrayWithArray:group.itemList];
|
||||
TUIMultimediaPasterItemConfig *paster = list[index];
|
||||
[list removeObjectAtIndex:index];
|
||||
group.itemList = list;
|
||||
[TUIMultimediaPasterConfig removeCustomPaster:paster];
|
||||
[TUIMultimediaPasterConfig saveConfig:self->_selectView.config];
|
||||
callback(YES);
|
||||
}];
|
||||
[alert addAction:cancelAction];
|
||||
[alert addAction:deleteAction];
|
||||
[self presentViewController:alert animated:YES completion:nil];
|
||||
}
|
||||
@end
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaPasterConfig.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaSubtitleInfo.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class TUIMultimediaSubtitleEditController;
|
||||
|
||||
typedef void (^TUIMultimediaSubtitleEditControllerCallback)(TUIMultimediaSubtitleEditController *c, BOOL isOk);
|
||||
|
||||
/**
|
||||
字幕编辑界面Controller
|
||||
*/
|
||||
@interface TUIMultimediaSubtitleEditController : UIViewController
|
||||
@property(copy, nonatomic) TUIMultimediaSubtitleInfo *subtitleInfo;
|
||||
@property(nullable, nonatomic) TUIMultimediaSubtitleEditControllerCallback callback;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,59 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import "TUIMultimediaSubtitleEditController.h"
|
||||
#import "TUIMultimediaPlugin/NSArray+Functional.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaCommon.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaSubtitleEditView.h"
|
||||
|
||||
@interface TUIMultimediaSubtitleEditController () <TUIMultimediaSubtitleEditViewDelegate> {
|
||||
TUIMultimediaSubtitleEditView *_editView;
|
||||
BOOL _hasCallback;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIMultimediaSubtitleEditController
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)viewDidLoad {
|
||||
_editView = [[TUIMultimediaSubtitleEditView alloc] initWithFrame:self.view.bounds];
|
||||
_editView.subtitleInfo = _subtitleInfo;
|
||||
_editView.delegate = self;
|
||||
[self.view addSubview:_editView];
|
||||
}
|
||||
|
||||
- (void)viewDidAppear:(BOOL)animated {
|
||||
[_editView activate];
|
||||
_hasCallback = false;
|
||||
}
|
||||
|
||||
- (void)viewDidLayoutSubviews {
|
||||
[super viewDidLayoutSubviews];
|
||||
_editView.frame = self.view.bounds;
|
||||
}
|
||||
|
||||
#pragma mark - TUIMultimediaSubtitleEditViewDelegate protocol
|
||||
- (void)subtitleEditViewOnOk:(TUIMultimediaSubtitleEditView *)view {
|
||||
if (_callback != nil && !_hasCallback) {
|
||||
_hasCallback = true;
|
||||
_callback(self, YES);
|
||||
}
|
||||
}
|
||||
- (void)subtitleEditViewOnCancel:(TUIMultimediaSubtitleEditView *)view {
|
||||
if (_callback != nil && !_hasCallback) {
|
||||
_hasCallback = true;
|
||||
_callback(self, NO);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Setters
|
||||
- (void)setSubtitleInfo:(TUIMultimediaSubtitleInfo *)subtitleInfo {
|
||||
_subtitleInfo = [subtitleInfo copy];
|
||||
_editView.subtitleInfo = _subtitleInfo;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIMultimediaColorCell : UICollectionViewCell
|
||||
@property(nullable, nonatomic) UIColor *color;
|
||||
@property(nonatomic) BOOL colorSelected;
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame;
|
||||
|
||||
+ (NSString *)reuseIdentifier;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,73 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import "TUIMultimediaColorCell.h"
|
||||
|
||||
const CGFloat ContentMarginSelected = 0;
|
||||
const CGFloat ContentMarginDeselected = 5;
|
||||
|
||||
const CGFloat FrontMargin = 4;
|
||||
|
||||
@interface TUIMultimediaColorCell () {
|
||||
UIView *_front;
|
||||
UIView *_back;
|
||||
CGFloat _contentMargin;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation TUIMultimediaColorCell
|
||||
|
||||
+ (NSString *)reuseIdentifier {
|
||||
return NSStringFromClass([self class]);
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self != nil) {
|
||||
_contentMargin = ContentMarginDeselected;
|
||||
_color = UIColor.blackColor;
|
||||
[self initUI];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)initUI {
|
||||
_back = [[UIView alloc] init];
|
||||
_back.backgroundColor = UIColor.whiteColor;
|
||||
[self.contentView addSubview:_back];
|
||||
|
||||
_front = [[UIView alloc] init];
|
||||
_front.backgroundColor = _color;
|
||||
[self.contentView addSubview:_front];
|
||||
|
||||
[self layoutSubviews];
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
CGFloat len = MIN(self.contentView.bounds.size.width, self.contentView.bounds.size.height) - _contentMargin;
|
||||
CGPoint center = CGPointMake(self.contentView.bounds.size.width / 2, self.contentView.bounds.size.height / 2);
|
||||
|
||||
_back.bounds = CGRectMake(0, 0, len, len);
|
||||
_back.center = center;
|
||||
_back.layer.cornerRadius = len / 2;
|
||||
_back.clipsToBounds = YES;
|
||||
|
||||
_front.bounds = CGRectMake(0, 0, len - FrontMargin, len - FrontMargin);
|
||||
_front.center = center;
|
||||
_front.layer.cornerRadius = (len - FrontMargin) / 2;
|
||||
_front.clipsToBounds = YES;
|
||||
}
|
||||
|
||||
- (void)setColor:(UIColor *)color {
|
||||
_color = color;
|
||||
_front.backgroundColor = _color;
|
||||
}
|
||||
|
||||
- (void)setColorSelected:(BOOL)colorSelected {
|
||||
_colorSelected = colorSelected;
|
||||
_contentMargin = colorSelected ? ContentMarginSelected : ContentMarginDeselected;
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIMultimediaImageCell : UICollectionViewCell
|
||||
@property(nullable, nonatomic) UIImage *image;
|
||||
- (instancetype)initWithFrame:(CGRect)frame;
|
||||
|
||||
+ (NSString *)reuseIdentifier;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,44 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import "TUIMultimediaImageCell.h"
|
||||
#import <Masonry/Masonry.h>
|
||||
|
||||
@interface TUIMultimediaImageCell () {
|
||||
UIImageView *_imgView;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation TUIMultimediaImageCell
|
||||
|
||||
@dynamic image;
|
||||
|
||||
+ (NSString *)reuseIdentifier {
|
||||
return NSStringFromClass([self class]);
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self != nil) {
|
||||
[self initUI];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)initUI {
|
||||
_imgView = [[UIImageView alloc] init];
|
||||
_imgView.contentMode = UIViewContentModeScaleAspectFit;
|
||||
[self.contentView addSubview:_imgView];
|
||||
|
||||
[_imgView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.edges.equalTo(self);
|
||||
}];
|
||||
}
|
||||
- (UIImage *)image {
|
||||
return _imgView.image;
|
||||
}
|
||||
- (void)setImage:(UIImage *)image {
|
||||
_imgView.image = image;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,13 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "TUIMultimediaStickerView.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIMultimediaPasterView : TUIMultimediaStickerView
|
||||
@property(nullable, nonatomic) UIImage *paster;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,44 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import "TUIMultimediaPasterView.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaCommon.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaStickerView.h"
|
||||
|
||||
#define PASTER_INITIAL_WIDTH 150
|
||||
|
||||
@interface TUIMultimediaPasterView () {
|
||||
UIImageView *_imgView;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIMultimediaPasterView
|
||||
|
||||
- (id)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self != nil) {
|
||||
[self initUI];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)initUI {
|
||||
_imgView = [[UIImageView alloc] init];
|
||||
_imgView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
self.editButtonHidden = YES;
|
||||
self.content = _imgView;
|
||||
}
|
||||
- (UIImage *)paster {
|
||||
return _imgView.image;
|
||||
}
|
||||
|
||||
- (void)setPaster:(UIImage *)paster {
|
||||
self.content = nil;
|
||||
_imgView.image = paster;
|
||||
int width = MIN(PASTER_INITIAL_WIDTH, paster.size.width);
|
||||
int height = width / paster.size.width * paster.size.height;
|
||||
_imgView.bounds = CGRectMake(0, 0, width, height);
|
||||
self.content = _imgView;
|
||||
}
|
||||
@end
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol TUIMultimediaStickerViewDelegate;
|
||||
|
||||
@interface TUIMultimediaStickerView : UIView
|
||||
@property(nullable, nonatomic) UIView *content;
|
||||
@property(nonatomic) BOOL selected;
|
||||
@property(nonatomic) BOOL editButtonHidden;
|
||||
@property(nonatomic, readonly) CGFloat rotation;
|
||||
@property(nonatomic) CGFloat contentMargin;
|
||||
// 旋转夹角小于此值时,吸附到水平和竖直方向
|
||||
@property(nonatomic) CGFloat rotateAdsorptionLimitAngle;
|
||||
@property(weak, nullable, nonatomic) id<TUIMultimediaStickerViewDelegate> delegate;
|
||||
|
||||
- (void)scale:(CGFloat) scale;
|
||||
@end
|
||||
|
||||
@protocol TUIMultimediaStickerViewDelegate <NSObject>
|
||||
- (void)onStickerViewSelected:(TUIMultimediaStickerView *)v;
|
||||
- (void)onStickerViewShouldDelete:(TUIMultimediaStickerView *)v;
|
||||
- (void)onStickerViewShouldEdit:(TUIMultimediaStickerView *)v;
|
||||
- (void)onStickerViewSizeChanged:(TUIMultimediaStickerView *)v;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,255 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import "TUIMultimediaStickerView.h"
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
#import "Masonry/Masonry.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaCommon.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaGeometry.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaImageUtil.h"
|
||||
|
||||
@interface TUIMultimediaStickerView () <UIGestureRecognizerDelegate> {
|
||||
UIButton *_btnEdit;
|
||||
UIButton *_btnTransform;
|
||||
UIButton *_btnDelete;
|
||||
UIView *_borderView;
|
||||
BOOL _hideEditButton;
|
||||
BOOL _active;
|
||||
|
||||
CGFloat _rawRotation;
|
||||
|
||||
UITapGestureRecognizer *_tapRec;
|
||||
UITapGestureRecognizer *_doubleTapRec;
|
||||
UIPanGestureRecognizer *_panRec;
|
||||
UIPinchGestureRecognizer *_pinchRec;
|
||||
UIRotationGestureRecognizer *_rotationRec;
|
||||
UIPanGestureRecognizer *_btnTransformPanRec;
|
||||
UIImpactFeedbackGenerator *_impactGen;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation TUIMultimediaStickerView
|
||||
- (id)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self != nil) {
|
||||
_contentMargin = 5;
|
||||
_rotateAdsorptionLimitAngle = M_PI / 180 * 5;
|
||||
_impactGen = [[UIImpactFeedbackGenerator alloc] init];
|
||||
[self frv_initUI];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)frv_initUI {
|
||||
_borderView = [[UIView alloc] init];
|
||||
_borderView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
_borderView.layer.borderWidth = 1;
|
||||
_borderView.layer.borderColor = UIColor.whiteColor.CGColor;
|
||||
_borderView.backgroundColor = [UIColor clearColor];
|
||||
_borderView.hidden = YES;
|
||||
[self addSubview:_borderView];
|
||||
[_borderView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.edges.equalTo(self);
|
||||
}];
|
||||
|
||||
_btnEdit = [self newCornerButtonWithImage:TUIMultimediaPluginBundleThemeImage(@"sticker_edit_img", @"round_edit") left:YES top:YES];
|
||||
_btnTransform = [self newCornerButtonWithImage:[TUIMultimediaImageUtil rotateImage:TUIMultimediaPluginBundleThemeImage(@"sticker_transform_img", @"round_transform") angle:90.0f] left:NO top:NO];
|
||||
_btnDelete = [self newCornerButtonWithImage:TUIMultimediaPluginBundleThemeImage(@"sticker_delete_img", @"round_close") left:NO top:YES];
|
||||
|
||||
[_btnEdit addTarget:self action:@selector(onBtnEditClicked) forControlEvents:UIControlEventTouchUpInside];
|
||||
[_btnDelete addTarget:self action:@selector(onBtnDeleteClicked) forControlEvents:UIControlEventTouchUpInside];
|
||||
|
||||
_tapRec = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onTap)];
|
||||
[self addGestureRecognizer:_tapRec];
|
||||
|
||||
_panRec = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(onPan:)];
|
||||
_panRec.delegate = self;
|
||||
[self addGestureRecognizer:_panRec];
|
||||
|
||||
_pinchRec = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(onPinch:)];
|
||||
_pinchRec.delegate = self;
|
||||
[self addGestureRecognizer:_pinchRec];
|
||||
|
||||
_rotationRec = [[UIRotationGestureRecognizer alloc] initWithTarget:self action:@selector(onGestureRotation:)];
|
||||
_rotationRec.delegate = self;
|
||||
[self addGestureRecognizer:_rotationRec];
|
||||
|
||||
_btnTransformPanRec = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(onBtnTransformPan:)];
|
||||
[_btnTransform addGestureRecognizer:_btnTransformPanRec];
|
||||
|
||||
_doubleTapRec = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onDoubleTap:)];
|
||||
_doubleTapRec.delaysTouchesEnded = NO;
|
||||
_doubleTapRec.numberOfTapsRequired = 2;
|
||||
[self addGestureRecognizer:_doubleTapRec];
|
||||
}
|
||||
|
||||
- (UIButton *)newCornerButtonWithImage:(UIImage *)image left:(BOOL)left top:(BOOL)top {
|
||||
UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
btn.hidden = YES;
|
||||
[self addSubview:btn];
|
||||
btn.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
[btn setImage:image forState:UIControlStateNormal];
|
||||
[btn mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.width.height.mas_equalTo(24);
|
||||
make.centerX.equalTo(left ? self.mas_left : self.mas_right);
|
||||
make.centerY.equalTo(top ? self.mas_top : self.mas_bottom);
|
||||
}];
|
||||
return btn;
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
[_delegate onStickerViewSizeChanged:self];
|
||||
}
|
||||
|
||||
- (void)updateRotation {
|
||||
// 规范化到[0,2pi)
|
||||
_rawRotation -= floor(_rawRotation / (M_PI * 2)) * M_PI * 2;
|
||||
BOOL adsorped = NO;
|
||||
// 吸附到 0,90,180,270度
|
||||
for (int i = 0; i <= 3; i++) {
|
||||
CGFloat diff = fabs(_rawRotation - i * M_PI_2);
|
||||
CGFloat diff2 = fabs(_rawRotation - M_PI * 2 - i * M_PI_2);
|
||||
if (diff < _rotateAdsorptionLimitAngle || diff2 < _rotateAdsorptionLimitAngle) {
|
||||
adsorped = YES;
|
||||
if (_rotation != i * M_PI_2) {
|
||||
_rotation = i * M_PI_2;
|
||||
[_impactGen impactOccurred];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!adsorped) {
|
||||
_rotation = _rawRotation;
|
||||
}
|
||||
self.transform = CGAffineTransformMakeRotation(_rotation);
|
||||
NSLog(@"updateRotation %@",NSStringFromCGRect(self.frame));
|
||||
}
|
||||
|
||||
#pragma mark - Actions
|
||||
- (void)onTap {
|
||||
if (!self.selected) {
|
||||
self.selected = YES;
|
||||
[_delegate onStickerViewSelected:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)onDoubleTap:(UITapGestureRecognizer *)rec {
|
||||
[_delegate onStickerViewShouldEdit:self];
|
||||
}
|
||||
|
||||
- (void)onPan:(UIPanGestureRecognizer *)rec {
|
||||
CGPoint offset = [rec translationInView:self.superview];
|
||||
[rec setTranslation:CGPointZero inView:self.superview];
|
||||
// 平移
|
||||
self.center = Vec2AddVector(self.center, offset);
|
||||
NSLog(@"onPan %@",NSStringFromCGRect(self.frame));
|
||||
}
|
||||
|
||||
|
||||
- (void)onPinch:(UIPinchGestureRecognizer *)rec {
|
||||
// pow用于加快放大/缩小速度,优化体验
|
||||
CGFloat scale = pow(rec.scale, 1.5);
|
||||
rec.scale = 1;
|
||||
CGSize raw = self.bounds.size;
|
||||
self.bounds = CGRectMake(0, 0, raw.width * scale, raw.height * scale);
|
||||
NSLog(@"onPinch %@",NSStringFromCGRect(self.frame));
|
||||
}
|
||||
|
||||
- (void)scale:(CGFloat) scale {
|
||||
CGSize raw = self.bounds.size;
|
||||
self.bounds = CGRectMake(0, 0, raw.width * scale, raw.height * scale);
|
||||
self.center = CGPointMake(self.center.x * scale, self.center.y * scale);
|
||||
}
|
||||
|
||||
- (void)onGestureRotation:(UIRotationGestureRecognizer *)rec {
|
||||
_rawRotation += rec.rotation;
|
||||
[self updateRotation];
|
||||
rec.rotation = 0;
|
||||
}
|
||||
|
||||
- (void)onBtnTransformPan:(UIPanGestureRecognizer *)rec {
|
||||
CGPoint offset = [rec translationInView:self.superview];
|
||||
[rec setTranslation:CGPointZero inView:self.superview];
|
||||
|
||||
CGPoint originSize = CGPointMake(self.bounds.size.width, self.bounds.size.height);
|
||||
CGPoint originV = Vec2Rotate(Vec2Mul(originSize, 0.5), _rawRotation);
|
||||
CGPoint newV = Vec2AddVector(originV, offset);
|
||||
// 缩放
|
||||
CGPoint newSize = Vec2Mul(originSize, Vec2Len(newV) / Vec2Len(originV));
|
||||
self.bounds = CGRectMake(0, 0, newSize.x, newSize.y);
|
||||
// 旋转
|
||||
_rawRotation += Vec2Degree(originV, newV);
|
||||
[self updateRotation];
|
||||
}
|
||||
|
||||
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
|
||||
if (CGRectContainsPoint(self.bounds, point)) {
|
||||
return YES;
|
||||
}
|
||||
for (UIView *v in self.subviews) {
|
||||
CGPoint localPoint = [v convertPoint:point fromView:self];
|
||||
if (CGRectContainsPoint(v.bounds, localPoint)) {
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)onBtnEditClicked {
|
||||
[_delegate onStickerViewShouldEdit:self];
|
||||
}
|
||||
|
||||
- (void)onBtnDeleteClicked {
|
||||
[_delegate onStickerViewShouldDelete:self];
|
||||
}
|
||||
|
||||
#pragma mark - Properties
|
||||
- (BOOL)editButtonHidden {
|
||||
return _hideEditButton;
|
||||
}
|
||||
|
||||
- (void)setEditButtonHidden:(BOOL)hideEditButton {
|
||||
_hideEditButton = hideEditButton;
|
||||
}
|
||||
|
||||
- (void)setContent:(UIView *)content {
|
||||
[_content removeFromSuperview];
|
||||
_content = content;
|
||||
if (content == nil) {
|
||||
return;
|
||||
}
|
||||
self.bounds = CGRectMake(0, 0, content.bounds.size.width + _contentMargin * 2, content.bounds.size.height + _contentMargin * 2);
|
||||
|
||||
|
||||
|
||||
// [self mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
// make.center.equalTo(self.superview);
|
||||
// }];
|
||||
|
||||
|
||||
[self addSubview:content];
|
||||
[self bringSubviewToFront:_btnEdit];
|
||||
[self bringSubviewToFront:_btnTransform];
|
||||
[self bringSubviewToFront:_btnDelete];
|
||||
[content mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.edges.equalTo(self).inset(_contentMargin);
|
||||
}];
|
||||
|
||||
|
||||
}
|
||||
|
||||
- (void)setSelected:(BOOL)selected {
|
||||
_selected = selected;
|
||||
_borderView.hidden = !selected;
|
||||
_btnEdit.hidden = !selected || _hideEditButton;
|
||||
_btnTransform.hidden = !selected;
|
||||
_btnDelete.hidden = !selected;
|
||||
}
|
||||
|
||||
#pragma mark - UIGestureRecognizerDelegate protocol
|
||||
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)g1 shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)g2 {
|
||||
NSArray<UIGestureRecognizer *> *recList = @[ _pinchRec, _rotationRec, _panRec ];
|
||||
return [recList containsObject:g1] && [recList containsObject:g2];
|
||||
}
|
||||
@end
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaStickerView.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaSubtitleInfo.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIMultimediaSubtitleView : TUIMultimediaStickerView
|
||||
@property(nullable, nonatomic) TUIMultimediaSubtitleInfo *subtitleInfo;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,123 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import "TUIMultimediaSubtitleView.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaCommon.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaGeometry.h"
|
||||
|
||||
#define MIN_SUBTITLE_PASTER_WIDTH 100
|
||||
#define MAX_SUBTITLE_PASTER_HEIGH 400
|
||||
|
||||
@interface TUIMultimediaSubtitleView () <TUIMultimediaStickerViewDelegate> {
|
||||
id<TUIMultimediaStickerViewDelegate> _outerDelegate;
|
||||
UILabel *_label;
|
||||
BOOL _isSizeToFitingSubtitleInfo;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIMultimediaSubtitleView
|
||||
- (id)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self != nil) {
|
||||
[self initUI];
|
||||
}
|
||||
_isSizeToFitingSubtitleInfo = NO;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)initUI {
|
||||
[super setDelegate:self];
|
||||
_label = [[UILabel alloc] init];
|
||||
_label.font = [UIFont systemFontOfSize:20];
|
||||
_label.numberOfLines = 0;
|
||||
_label.lineBreakMode = NSLineBreakByClipping;
|
||||
_label.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
}
|
||||
|
||||
#pragma mark - Getters & Setters
|
||||
- (void)setSubtitleInfo:(TUIMultimediaSubtitleInfo *)subtitleInfo {
|
||||
NSLog(@"TUIMultimediaSubtitleView setSubtitleInfo text = %@",subtitleInfo.wrappedText);
|
||||
_subtitleInfo = subtitleInfo;
|
||||
|
||||
self.content = nil;
|
||||
_label.text = subtitleInfo.wrappedText;
|
||||
_label.textColor = subtitleInfo.color;
|
||||
[self labeSizeToFit];
|
||||
self.content = _label;
|
||||
}
|
||||
|
||||
-(void)labeSizeToFit {
|
||||
_isSizeToFitingSubtitleInfo = YES;
|
||||
if (_subtitleInfo.wrappedText.length > 0) {
|
||||
_label.text = [NSString stringWithFormat:@"%C", [_subtitleInfo.wrappedText characterAtIndex:0]];
|
||||
} else {
|
||||
NSLog(@"_subtitleInfo.wrappedText is empty or nil.");
|
||||
_label.text = @"";
|
||||
}
|
||||
CGSize size = [_label sizeThatFits:CGSizeMake(MAXFLOAT, MAX_SUBTITLE_PASTER_HEIGH)];
|
||||
CGFloat singleLineTextHeight = size.height;
|
||||
|
||||
_label.text = _subtitleInfo.wrappedText;
|
||||
size = [_label sizeThatFits:CGSizeMake(MAXFLOAT, MAX_SUBTITLE_PASTER_HEIGH)];
|
||||
CGFloat adjustedWidth = size.width;
|
||||
if (size.height > singleLineTextHeight * 1.5) {
|
||||
// multi line text
|
||||
adjustedWidth = MAX(size.width, MIN_SUBTITLE_PASTER_WIDTH);
|
||||
}
|
||||
CGFloat adjustedHeight = MIN(size.height, MAX_SUBTITLE_PASTER_HEIGH);
|
||||
_label.frame = CGRectMake(_label.frame.origin.x, _label.frame.origin.y, adjustedWidth, adjustedHeight);
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
self->_isSizeToFitingSubtitleInfo = NO;
|
||||
});
|
||||
}
|
||||
|
||||
- (id<TUIMultimediaStickerViewDelegate>)delegate {
|
||||
return _outerDelegate;
|
||||
}
|
||||
|
||||
- (void)setDelegate:(id<TUIMultimediaStickerViewDelegate>)delegate {
|
||||
_outerDelegate = delegate;
|
||||
}
|
||||
|
||||
#pragma mark - TUIMultimediaFloatingResizableDelegate protocol
|
||||
- (void)onStickerViewShouldDelete:(TUIMultimediaStickerView *)v {
|
||||
[_outerDelegate onStickerViewShouldDelete:self];
|
||||
}
|
||||
|
||||
- (void)onStickerViewShouldEdit:(TUIMultimediaStickerView *)v {
|
||||
[_outerDelegate onStickerViewShouldEdit:self];
|
||||
}
|
||||
|
||||
- (void)onStickerViewSelected:(TUIMultimediaStickerView *)v {
|
||||
[_outerDelegate onStickerViewSelected:self];
|
||||
}
|
||||
|
||||
- (void)onStickerViewSizeChanged:(TUIMultimediaStickerView *)v {
|
||||
[_outerDelegate onStickerViewSizeChanged:self];
|
||||
if (_isSizeToFitingSubtitleInfo == NO) {
|
||||
[self adjustFontSize];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)adjustFontSize {
|
||||
NSString *text = _label.text;
|
||||
CGSize labelSize = _label.bounds.size;
|
||||
UIFont *font = _label.font;
|
||||
CGFloat minFontSize = 0;
|
||||
CGFloat maxFontSize = 1000;
|
||||
const CGFloat eps = 0.1;
|
||||
// 理论上需要14次二分
|
||||
while (maxFontSize - minFontSize > eps) {
|
||||
CGFloat mid = (maxFontSize + minFontSize) / 2;
|
||||
font = [font fontWithSize:mid];
|
||||
CGSize size = [text sizeWithAttributes:@{NSFontAttributeName : font}];
|
||||
if (size.width < labelSize.width && size.height < labelSize.height) {
|
||||
minFontSize = mid;
|
||||
} else {
|
||||
maxFontSize = mid;
|
||||
}
|
||||
}
|
||||
_label.font = [_label.font fontWithSize:minFontSize];
|
||||
}
|
||||
@end
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol TUIMultimediaColorPanelDelegate;
|
||||
|
||||
/**
|
||||
条形颜色选择器
|
||||
*/
|
||||
@interface TUIMultimediaColorPanel : UIView
|
||||
@property(weak, nullable, nonatomic) id<TUIMultimediaColorPanelDelegate> delegate;
|
||||
@property(nonatomic) NSArray<UIColor *> *colorList;
|
||||
@property(nonatomic) UIColor *selectedColor;
|
||||
@end
|
||||
|
||||
@protocol TUIMultimediaColorPanelDelegate <NSObject>
|
||||
- (void)onColorPanel:(TUIMultimediaColorPanel *)panel selectColor:(UIColor *)color;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,120 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import "TUIMultimediaColorPanel.h"
|
||||
#import <Masonry/Masonry.h>
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaColorCell.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaCommon.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaGeometry.h"
|
||||
|
||||
const static CGFloat CellMargin = 4;
|
||||
|
||||
@interface TUIMultimediaColorPanel () <UICollectionViewDelegateFlowLayout, UICollectionViewDataSource> {
|
||||
UICollectionView *_collectionView;
|
||||
NSInteger _selectIndex;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIMultimediaColorPanel
|
||||
- (id)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self != nil) {
|
||||
_colorList = @[
|
||||
UIColor.whiteColor,
|
||||
UIColor.blackColor,
|
||||
UIColor.grayColor,
|
||||
UIColor.redColor,
|
||||
UIColor.greenColor,
|
||||
UIColor.blueColor,
|
||||
UIColor.cyanColor,
|
||||
UIColor.yellowColor,
|
||||
UIColor.magentaColor,
|
||||
UIColor.orangeColor,
|
||||
UIColor.purpleColor,
|
||||
UIColor.brownColor,
|
||||
];
|
||||
[self initUI];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)initUI {
|
||||
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
|
||||
layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
|
||||
layout.minimumInteritemSpacing = 0;
|
||||
layout.minimumLineSpacing = 2;
|
||||
|
||||
_collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
|
||||
_collectionView.showsHorizontalScrollIndicator = NO;
|
||||
_collectionView.backgroundColor = UIColor.clearColor;
|
||||
_collectionView.delegate = self;
|
||||
_collectionView.dataSource = self;
|
||||
[_collectionView registerClass:TUIMultimediaColorCell.class forCellWithReuseIdentifier:TUIMultimediaColorCell.reuseIdentifier];
|
||||
[self addSubview:_collectionView];
|
||||
|
||||
[_collectionView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.edges.equalTo(self);
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - UICollectionViewDataSource protocol
|
||||
- (nonnull __kindof UICollectionViewCell *)collectionView:(nonnull UICollectionView *)collectionView cellForItemAtIndexPath:(nonnull NSIndexPath *)indexPath {
|
||||
TUIMultimediaColorCell *cell = (TUIMultimediaColorCell *)[collectionView dequeueReusableCellWithReuseIdentifier:TUIMultimediaColorCell.reuseIdentifier forIndexPath:indexPath];
|
||||
cell.color = _colorList[indexPath.item];
|
||||
cell.colorSelected = indexPath.item == _selectIndex;
|
||||
return cell;
|
||||
}
|
||||
|
||||
- (NSInteger)collectionView:(nonnull UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
|
||||
return _colorList.count;
|
||||
}
|
||||
|
||||
#pragma mark - UICollectionViewDelegateFlowLayout protocol
|
||||
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
[collectionView deselectItemAtIndexPath:indexPath animated:NO];
|
||||
_selectIndex = indexPath.item;
|
||||
[_collectionView reloadData];
|
||||
|
||||
UIColor *c = _colorList[indexPath.item];
|
||||
[_delegate onColorPanel:self selectColor:c];
|
||||
}
|
||||
- (BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
return YES;
|
||||
}
|
||||
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)index {
|
||||
CGFloat len = collectionView.bounds.size.height - CellMargin;
|
||||
return CGSizeMake(len, len);
|
||||
}
|
||||
|
||||
#pragma mark - Setters
|
||||
- (void)setColorList:(NSArray<UIColor *> *)colorList {
|
||||
_colorList = colorList;
|
||||
_selectIndex = 0;
|
||||
if (_colorList.count > 0) {
|
||||
[_collectionView cellForItemAtIndexPath:[NSIndexPath indexPathWithIndex:0]].selected = YES;
|
||||
}
|
||||
[_collectionView reloadData];
|
||||
}
|
||||
|
||||
- (UIColor *)selectedColor {
|
||||
if (_selectIndex < 0 || _selectIndex >= _colorList.count) {
|
||||
return nil;
|
||||
}
|
||||
return _colorList[_selectIndex];
|
||||
}
|
||||
|
||||
- (void)setSelectedColor:(UIColor *)selectedColor {
|
||||
if (selectedColor == nil) {
|
||||
_selectIndex = -1;
|
||||
} else {
|
||||
for (int i = 0; i < _colorList.count; i++) {
|
||||
if ([_colorList[i] isEqual:selectedColor]) {
|
||||
_selectIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
[_collectionView reloadData];
|
||||
}
|
||||
@end
|
||||
@@ -0,0 +1,66 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#include "TUIMultimediaPlugin/TUIMultimediaPasterConfig.h"
|
||||
#include "TUIMultimediaPlugin/TUIMultimediaSticker.h"
|
||||
#include "TUIMultimediaPlugin/TUIMultimediaSubtitleInfo.h"
|
||||
#include "TUIMultimediaStickerView.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol TUIMultimediaCommonEditorControlViewDelegate;
|
||||
@class TUIMultimediaCommonEditorConfig;
|
||||
|
||||
/**
|
||||
编辑页面View
|
||||
*/
|
||||
@interface TUIMultimediaCommonEditorControlView : UIView
|
||||
@property(readonly, nonatomic) UIView *previewView;
|
||||
@property(nonatomic) BOOL isGenerating;
|
||||
@property(nonatomic) CGFloat progressBarProgress;
|
||||
@property(nonatomic) BOOL musicEdited;
|
||||
@property(nonatomic) CGSize previewSize;
|
||||
@property(nonatomic) BOOL modifyButtonsHidden;
|
||||
@property(nonatomic) BOOL isStartCrop;
|
||||
@property(nonatomic) int sourceType;
|
||||
@property(weak, nullable, nonatomic) id<TUIMultimediaCommonEditorControlViewDelegate> delegate;
|
||||
@property (nonatomic, strong) UIImage *mosaciOriginalImage;
|
||||
@property(nonatomic) CGRect previewLimitRect;
|
||||
|
||||
- (instancetype)initWithConfig:(TUIMultimediaCommonEditorConfig *)config;
|
||||
|
||||
- (void)addPaster:(UIImage *)paster;
|
||||
- (void)addSubtitle:(TUIMultimediaSubtitleInfo *)subtitle;
|
||||
|
||||
// crop相关
|
||||
- (void)previewScale:(CGFloat)scale center:(CGPoint)center;
|
||||
- (void)previewMove:(CGPoint)offset;
|
||||
- (void)previewRotation90:(CGPoint)center;
|
||||
- (void)previewRotationToZero;
|
||||
- (void)previewAdjustToLimitRect;
|
||||
@end
|
||||
|
||||
@protocol TUIMultimediaCommonEditorControlViewDelegate <NSObject>
|
||||
- (void)onCommonEditorControlViewComplete:(TUIMultimediaCommonEditorControlView *)view stickers:(NSArray<TUIMultimediaSticker *> *)stickers;
|
||||
- (void)onCommonEditorControlViewCrop:(NSInteger)rotationAngle normalizedCropRect:(CGRect)normalizedCropRect
|
||||
stickers:(NSArray<TUIMultimediaSticker *> *)stickers;
|
||||
- (void)onCommonEditorControlViewCancel:(TUIMultimediaCommonEditorControlView *)view;
|
||||
- (void)onCommonEditorControlViewNeedAddPaster:(TUIMultimediaCommonEditorControlView *)view;
|
||||
- (void)onCommonEditorControlViewNeedModifySubtitle:(TUIMultimediaSubtitleInfo *)info callback:(void (^)(TUIMultimediaSubtitleInfo *newInfo, BOOL isOk))callback;
|
||||
- (void)onCommonEditorControlViewNeedEditMusic:(TUIMultimediaCommonEditorControlView *)view;
|
||||
- (void)onCommonEditorControlViewCancelGenerate:(TUIMultimediaCommonEditorControlView *)view;
|
||||
@end
|
||||
|
||||
@interface TUIMultimediaCommonEditorConfig : NSObject
|
||||
@property(nonatomic) BOOL pasterEnabled;
|
||||
@property(nonatomic) BOOL subtitleEnabled;
|
||||
@property(nonatomic) BOOL drawGraffitiEnabled;
|
||||
@property(nonatomic) BOOL drawMosaicEnabled;
|
||||
@property(nonatomic) BOOL musicEditEnabled;
|
||||
@property(nonatomic) BOOL cropEnabled;
|
||||
+ (instancetype)configForVideoEditor;
|
||||
+ (instancetype)configForPictureEditor;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,701 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import "TUIMultimediaCommonEditorControlView.h"
|
||||
#import <Masonry/Masonry.h>
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
#import "TUIMultimediaPlugin/NSArray+Functional.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaCircleProgressView.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaCommon.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaDrawView.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaImageUtil.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaPasterSelectView.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaPasterView.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaSplitter.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaStickerView.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaSubtitleView.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaTabPanel.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaConfig.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaConstant.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaGeometry.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaCropControlView.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaDrawCtrlView.h"
|
||||
|
||||
#define FUNCTION_BUTTON_SIZE CGSizeMake(28, 28)
|
||||
#define BUTTON_SEND_SIZE CGSizeMake(60, 28)
|
||||
|
||||
@interface TUIMultimediaCommonEditorControlView () <TUIMultimediaStickerViewDelegate, UIGestureRecognizerDelegate, TUIMultimediaDrawCtrlViewDelegate, TUIMultimediaCropControlDelegate> {
|
||||
TUIMultimediaCommonEditorConfig *_config;
|
||||
UIStackView *_stkViewButtons;
|
||||
UIButton *_btnDrawGraffiti;
|
||||
UIButton *_btnDrawMosaic;
|
||||
UIButton *_btnMusic;
|
||||
UIButton *_btnSubtitle;
|
||||
UIButton *_btnPaster;
|
||||
UIButton *_btnCrop;
|
||||
UIButton *_btnSend;
|
||||
UIButton *_btnCancel;
|
||||
|
||||
NSMutableArray<TUIMultimediaStickerView *> *_stickerViewList;
|
||||
TUIMultimediaStickerView *_lastSelectedStickerView;
|
||||
UIView *_editContainerView;
|
||||
|
||||
TUIMultimediaDrawView *_drawView;
|
||||
TUIMultimediaDrawCtrlView *_drawCtrlView;
|
||||
|
||||
UIView *_generateView;
|
||||
TUIMultimediaCircleProgressView *_progressView;
|
||||
UIButton *_btnGenerateCancel;
|
||||
|
||||
BOOL _isStartCrop;
|
||||
NSInteger _previewRotationAngle;
|
||||
TUIMultimediaCropControlView* _cropControlView;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIMultimediaCommonEditorControlView
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
return [self initWithConfig:[[TUIMultimediaCommonEditorConfig alloc] init]];
|
||||
}
|
||||
- (instancetype)initWithConfig:(TUIMultimediaCommonEditorConfig *)config {
|
||||
self = [super initWithFrame:CGRectZero];
|
||||
if (self != nil) {
|
||||
_config = config;
|
||||
_sourceType = SOURCE_TYPE_RECORD;
|
||||
_isStartCrop = NO;
|
||||
_stickerViewList = [NSMutableArray array];
|
||||
[self initUI];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)addPaster:(UIImage *)paster {
|
||||
TUIMultimediaPasterView *vpaster = [[TUIMultimediaPasterView alloc] init];
|
||||
vpaster.delegate = self;
|
||||
[_stickerViewList addObject:vpaster];
|
||||
vpaster.paster = paster;
|
||||
vpaster.center = CGPointMake(_editContainerView.bounds.size.width / 2, _editContainerView.bounds.size.height / 2);
|
||||
[_editContainerView addSubview:vpaster];
|
||||
}
|
||||
|
||||
- (void)addSubtitle:(TUIMultimediaSubtitleInfo *)subtitle {
|
||||
TUIMultimediaSubtitleView *vsub = [[TUIMultimediaSubtitleView alloc] init];
|
||||
vsub.delegate = self;
|
||||
[_stickerViewList addObject:vsub];
|
||||
vsub.subtitleInfo = subtitle;
|
||||
vsub.center = CGPointMake(_editContainerView.bounds.size.width / 2, _editContainerView.bounds.size.height / 2);
|
||||
[_editContainerView addSubview:vsub];
|
||||
}
|
||||
|
||||
#pragma mark crop function
|
||||
- (void)previewScale:(CGFloat) scale center:(CGPoint)center {
|
||||
if (_isStartCrop) {
|
||||
return;
|
||||
}
|
||||
|
||||
CGFloat width = _previewView.frame.size.width * scale;
|
||||
CGFloat height = _previewView.frame.size.height * scale;
|
||||
|
||||
CGFloat left = center.x - (center.x - _previewView.frame.origin.x) * scale;
|
||||
CGFloat top = center.y - (center.y - _previewView.frame.origin.y) * scale;
|
||||
_previewView.frame = CGRectMake(left, top, width, height);
|
||||
|
||||
for (UIView *subview in _editContainerView.subviews) {
|
||||
if ([subview isKindOfClass:[TUIMultimediaStickerView class]]) {
|
||||
TUIMultimediaStickerView* stickView = (TUIMultimediaStickerView*)subview;
|
||||
[stickView scale:scale];
|
||||
}
|
||||
}
|
||||
|
||||
if (_cropControlView != nil) {
|
||||
[_cropControlView changeResetButtonStatus];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)previewMove:(CGPoint)offset {
|
||||
if (_isStartCrop) {
|
||||
return;
|
||||
}
|
||||
_previewView.frame = CGRectMake(_previewView.frame.origin.x + offset.x,
|
||||
_previewView.frame.origin.y + offset.y,
|
||||
_previewView.frame.size.width,
|
||||
_previewView.frame.size.height);
|
||||
if (_cropControlView != nil) {
|
||||
[_cropControlView changeResetButtonStatus];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
- (void)previewRotation90:(CGPoint)center {
|
||||
_previewRotationAngle = (_previewRotationAngle + 90 + 360) % 360;
|
||||
float radians = _previewRotationAngle * M_PI / 180;
|
||||
int newTop = center.y - center.x + _previewView.frame.origin.x;
|
||||
int newLeft = center.x + center.y - _previewView.frame.origin.y - _previewView.frame.size.height;
|
||||
|
||||
_editContainerView.transform = CGAffineTransformMakeRotation(radians);
|
||||
_previewView.transform = CGAffineTransformMakeRotation(radians);
|
||||
_previewView.frame = CGRectMake(newLeft, newTop, _previewView.frame.size.width, _previewView.frame.size.height);
|
||||
|
||||
if (_cropControlView != nil) {
|
||||
[_cropControlView changeResetButtonStatus];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)previewRotationToZero {
|
||||
_previewRotationAngle = 0;
|
||||
_editContainerView.transform = CGAffineTransformMakeRotation(0);
|
||||
_previewView.transform = CGAffineTransformMakeRotation(0);
|
||||
}
|
||||
|
||||
- (void)previewAdjustToLimitRect {
|
||||
NSLog(@"preview adjust to limit rect");
|
||||
double scaleLimitX = _previewLimitRect.size.width * 1.0f / _previewView.frame.size.width;
|
||||
double scaleLimitY = _previewLimitRect.size.height * 1.0f / _previewView.frame.size.height;
|
||||
double scaleLimit = MAX(scaleLimitX, scaleLimitY);
|
||||
if (scaleLimit > 1.0f) {
|
||||
[self previewScale:scaleLimit center:CGPointMake(CGRectGetMidX(_previewView.frame), CGRectGetMidY(_previewView.frame))];
|
||||
}
|
||||
|
||||
|
||||
int maxLeft = _previewLimitRect.origin.x;
|
||||
int minLeft = _previewLimitRect.size.width + _previewLimitRect.origin.x - _previewView.frame.size.width;
|
||||
int left = MAX(MIN(_previewView.frame.origin.x , maxLeft), minLeft);
|
||||
int maxTop = _previewLimitRect.origin.y;
|
||||
int minTop = _previewLimitRect.size.height + _previewLimitRect.origin.y - _previewView.frame.size.height;
|
||||
int top = MAX(MIN(_previewView.frame.origin.y , maxTop), minTop);
|
||||
_previewView.frame = CGRectMake(left, top, _previewView.frame.size.width, _previewView.frame.size.height);
|
||||
}
|
||||
|
||||
#pragma mark UI init
|
||||
- (void)initUI {
|
||||
self.backgroundColor = UIColor.blackColor;
|
||||
|
||||
_previewView = [[UIView alloc] init];
|
||||
[self addSubview:_previewView];
|
||||
|
||||
[self initFuncitonBtnStackView];
|
||||
[self initGraffitiFunction];
|
||||
[self initMosaicFunction];
|
||||
[self initPasterFunciton];
|
||||
[self initSubtitleFunciton];
|
||||
[self initBGMFunction];
|
||||
[self initCropFunciton];
|
||||
[self initSendAndCancelBtn];
|
||||
[self initGenerateView];
|
||||
[self bringSubviewToFront:_stkViewButtons];
|
||||
}
|
||||
|
||||
- (void) initFuncitonBtnStackView {
|
||||
_stkViewButtons = [[UIStackView alloc] init];
|
||||
_stkViewButtons.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
[self addSubview:_stkViewButtons];
|
||||
_stkViewButtons.axis = UILayoutConstraintAxisHorizontal;
|
||||
_stkViewButtons.alignment = UIStackViewAlignmentCenter;
|
||||
_stkViewButtons.distribution = UIStackViewDistributionEqualSpacing;
|
||||
[_stkViewButtons mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.right.equalTo(self).inset(10);
|
||||
make.bottom.equalTo(self.mas_safeAreaLayoutGuideBottom);
|
||||
make.height.mas_equalTo(FUNCTION_BUTTON_SIZE.height);
|
||||
}];
|
||||
}
|
||||
|
||||
- (void) initGraffitiFunction {
|
||||
if (!_config.drawGraffitiEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
_btnDrawGraffiti = [self addFunctionIconButtonWithImage:TUIMultimediaPluginBundleThemeImage(@"editor_scrawl_img", @"modify_scrawl")
|
||||
onTouchUpInside:@selector(onBtnDrawGraffitiClicked)];
|
||||
|
||||
[self initEditContainerView];
|
||||
[self initDrawCtrlView];
|
||||
}
|
||||
|
||||
- (void) initMosaicFunction {
|
||||
if (!_config.drawMosaicEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
_btnDrawMosaic = [self addFunctionIconButtonWithImage:TUIMultimediaPluginBundleThemeImage(@"edit_mosaic_img", @"edit_mosaic")
|
||||
onTouchUpInside:@selector(onBtnDrawMosaicClicked)];
|
||||
|
||||
[self initEditContainerView];
|
||||
[self initDrawCtrlView];
|
||||
_drawCtrlView.drawView.mosaciOriginalImage = _mosaciOriginalImage;
|
||||
}
|
||||
|
||||
- (void) initPasterFunciton {
|
||||
if (!_config.pasterEnabled) {
|
||||
return;
|
||||
}
|
||||
_btnPaster = [self addFunctionIconButtonWithImage:TUIMultimediaPluginBundleThemeImage(@"editor_paster_img", @"modify_paster")
|
||||
onTouchUpInside:@selector(onBtnPasterClicked)];
|
||||
[self initEditContainerView];
|
||||
}
|
||||
|
||||
- (void) initSubtitleFunciton {
|
||||
if (!_config.subtitleEnabled) {
|
||||
return;
|
||||
}
|
||||
_btnSubtitle = [self addFunctionIconButtonWithImage:TUIMultimediaPluginBundleThemeImage(@"editor_subtitle_img", @"modify_subtitle")
|
||||
onTouchUpInside:@selector(onBtnSubtitleClicked)];
|
||||
[self initEditContainerView];
|
||||
}
|
||||
|
||||
- (void) initBGMFunction {
|
||||
if (!_config.musicEditEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
_btnMusic = [self addFunctionIconButtonWithImage:TUIMultimediaPluginBundleThemeImage(@"editor_music_img", @"modify_music")
|
||||
onTouchUpInside:@selector(onBtnMusicClicked)];
|
||||
}
|
||||
|
||||
- (void) initCropFunciton {
|
||||
if (!_config.cropEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
_btnCrop = [self addFunctionIconButtonWithImage:TUIMultimediaPluginBundleThemeImage(@"editor_crop_img", @"modify_crop")
|
||||
onTouchUpInside:@selector(onBtnCropClicked)];
|
||||
[self initCropCtrlView];
|
||||
}
|
||||
|
||||
- (void)initCropCtrlView {
|
||||
if (_cropControlView != nil) {
|
||||
return;
|
||||
}
|
||||
|
||||
_cropControlView = [[TUIMultimediaCropControlView alloc] initWithFrame:self.frame editorControl:self];
|
||||
[self addSubview:_cropControlView];
|
||||
_cropControlView.hidden = YES;
|
||||
[_cropControlView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.edges.equalTo(self);
|
||||
}];
|
||||
_cropControlView.delegate = self;
|
||||
}
|
||||
|
||||
- (void)initDrawCtrlView {
|
||||
if (_drawCtrlView != nil) {
|
||||
return;
|
||||
}
|
||||
|
||||
_drawCtrlView = [[TUIMultimediaDrawCtrlView alloc] init];
|
||||
[self addSubview:_drawCtrlView];
|
||||
[_drawCtrlView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.right.equalTo(self);
|
||||
make.top.equalTo(_stkViewButtons).offset(-50);
|
||||
make.bottom.equalTo(self);
|
||||
}];
|
||||
_drawCtrlView.drawEnable = NO;
|
||||
_drawCtrlView.delegate = self;
|
||||
|
||||
[_editContainerView addSubview:_drawCtrlView.drawView];
|
||||
[_drawCtrlView.drawView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.edges.equalTo(_previewView);
|
||||
}];
|
||||
}
|
||||
|
||||
- (void) initEditContainerView {
|
||||
if (_editContainerView != nil) {
|
||||
return;
|
||||
}
|
||||
|
||||
_editContainerView = [[UIView alloc] init];
|
||||
[self addSubview:_editContainerView];
|
||||
_editContainerView.userInteractionEnabled = YES;
|
||||
_editContainerView.clipsToBounds = YES;
|
||||
|
||||
UITapGestureRecognizer *containerTapRec = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onTapContainerView:)];
|
||||
containerTapRec.cancelsTouchesInView = NO;
|
||||
[_editContainerView addGestureRecognizer:containerTapRec];
|
||||
[_editContainerView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.edges.equalTo(_previewView);
|
||||
}];
|
||||
}
|
||||
|
||||
- (void) initSendAndCancelBtn {
|
||||
_btnSend = [UIButton buttonWithType:UIButtonTypeSystem];
|
||||
|
||||
if (_stkViewButtons.arrangedSubviews.count == 0) {
|
||||
[_stkViewButtons removeFromSuperview];
|
||||
[self addSubview:_btnSend];
|
||||
[_btnSend mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.size.mas_equalTo(BUTTON_SEND_SIZE);
|
||||
make.right.equalTo(self).inset(20);
|
||||
make.bottom.equalTo(self.mas_safeAreaLayoutGuideBottom);
|
||||
}];
|
||||
} else {
|
||||
[_stkViewButtons addArrangedSubview:_btnSend];
|
||||
[_btnSend mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.size.mas_equalTo(BUTTON_SEND_SIZE);
|
||||
}];
|
||||
}
|
||||
|
||||
_btnSend.backgroundColor = [[TUIMultimediaConfig sharedInstance] getThemeColor];
|
||||
[_btnSend setTitleColor:UIColor.whiteColor forState:UIControlStateNormal];
|
||||
_btnSend.layer.cornerRadius = 5;
|
||||
NSString* titile = [TUIMultimediaCommon localizedStringForKey:@"send"];
|
||||
if (_sourceType == SOURCE_TYPE_ALBUM) {
|
||||
titile = [TUIMultimediaCommon localizedStringForKey:@"done"];
|
||||
}
|
||||
[_btnSend setTitle:titile forState:UIControlStateNormal];
|
||||
[_btnSend addTarget:self action:@selector(onBtnSendClicked) forControlEvents:UIControlEventTouchUpInside];
|
||||
|
||||
|
||||
_btnCancel = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
[self addSubview:_btnCancel];
|
||||
[_btnCancel setImage:TUIMultimediaPluginBundleThemeImage(@"editor_cancel_img", @"return_arrow") forState:UIControlStateNormal];
|
||||
[_btnCancel addTarget:self action:@selector(onBtnCancelClicked) forControlEvents:UIControlEventTouchUpInside];
|
||||
[_btnCancel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.size.mas_equalTo(CGSizeMake(24, 24));
|
||||
make.top.equalTo(self.mas_safeAreaLayoutGuideTop).inset(10);
|
||||
if([[TUIGlobalization getPreferredLanguage] hasPrefix:@"ar"]) {
|
||||
make.right.equalTo(self.mas_safeAreaLayoutGuideRight).offset(-15);
|
||||
} else {
|
||||
make.left.equalTo(self.mas_safeAreaLayoutGuideLeft).offset(15);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)setSourceType:(int)sourceType {
|
||||
NSLog(@"setSourceType sourceType = %d",sourceType);
|
||||
_sourceType = sourceType;
|
||||
NSString* titile = [TUIMultimediaCommon localizedStringForKey:@"send"];
|
||||
if (_sourceType == SOURCE_TYPE_ALBUM) {
|
||||
titile = [TUIMultimediaCommon localizedStringForKey:@"done"];
|
||||
}
|
||||
[_btnSend setTitle:titile forState:UIControlStateNormal];
|
||||
}
|
||||
|
||||
- (void)initGenerateView {
|
||||
_generateView = [[UIView alloc] init];
|
||||
_generateView.backgroundColor = TUIMultimediaPluginDynamicColor(@"editor_generate_view_bg_color", @"#0000007F");
|
||||
_generateView.hidden = YES;
|
||||
[self addSubview:_generateView];
|
||||
|
||||
_progressView = [[TUIMultimediaCircleProgressView alloc] init];
|
||||
_progressView.progressColor = [[TUIMultimediaConfig sharedInstance] getThemeColor];
|
||||
[_generateView addSubview:_progressView];
|
||||
|
||||
_btnGenerateCancel = [UIButton buttonWithType:UIButtonTypeSystem];
|
||||
[_btnGenerateCancel addTarget:self action:@selector(onBtnGenerateCancelClicked) forControlEvents:UIControlEventTouchUpInside];
|
||||
[_btnGenerateCancel setTitle:[TUIMultimediaCommon localizedStringForKey:@"cancel"] forState:UIControlStateNormal];
|
||||
[_btnGenerateCancel setTitleColor:UIColor.whiteColor forState:UIControlStateNormal];
|
||||
[_generateView addSubview:_btnGenerateCancel];
|
||||
|
||||
[_generateView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.edges.equalTo(self);
|
||||
}];
|
||||
[_progressView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.size.mas_equalTo(CGSizeMake(100, 100));
|
||||
make.center.equalTo(_generateView);
|
||||
}];
|
||||
[_btnGenerateCancel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(self.mas_safeAreaLayoutGuideTop).inset(10);
|
||||
make.left.equalTo(self).inset(10);
|
||||
make.size.mas_equalTo(CGSizeMake(54, 32));
|
||||
}];
|
||||
}
|
||||
|
||||
- (UIButton *)addFunctionIconButtonWithImage:(UIImage *)img onTouchUpInside:(SEL)sel {
|
||||
UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
[_stkViewButtons addArrangedSubview:btn];
|
||||
UIImage *imgNormal = [TUIMultimediaImageUtil imageFromImage:img withTintColor:
|
||||
TUIMultimediaPluginDynamicColor(@"editor_func_btn_normal_color", @"#FFFFFF")];
|
||||
UIImage *imgDisabled = [TUIMultimediaImageUtil imageFromImage:img withTintColor:
|
||||
TUIMultimediaPluginDynamicColor(@"editor_func_btn_disabled_color", @"#6D6D6D")];
|
||||
UIImage *imgPressed = [TUIMultimediaImageUtil imageFromImage:img withTintColor:
|
||||
TUIMultimediaPluginDynamicColor(@"editor_func_btn_pressed_color", @"#7F7F7F")];
|
||||
UIImage *imgSelected = [TUIMultimediaImageUtil imageFromImage:img withTintColor:
|
||||
[[TUIMultimediaConfig sharedInstance] getThemeColor]];
|
||||
[btn setImage:imgNormal forState:UIControlStateNormal];
|
||||
[btn setImage:imgDisabled forState:UIControlStateDisabled];
|
||||
[btn setImage:imgPressed forState:UIControlStateHighlighted];
|
||||
[btn setImage:imgSelected forState:UIControlStateSelected];
|
||||
[btn setImageEdgeInsets:UIEdgeInsetsMake(0, 0, 0, 0)];
|
||||
[btn addTarget:self action:sel forControlEvents:UIControlEventTouchUpInside];
|
||||
|
||||
[btn mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.size.mas_equalTo(FUNCTION_BUTTON_SIZE);
|
||||
}];
|
||||
return btn;
|
||||
}
|
||||
|
||||
#pragma mark Internal Functions
|
||||
- (void)setDrawEnabled:(BOOL)drawGraffitiEnabled {
|
||||
if (!_config.drawGraffitiEnabled) {
|
||||
return;
|
||||
}
|
||||
_lastSelectedStickerView.selected = NO;
|
||||
_lastSelectedStickerView = nil;
|
||||
_btnDrawGraffiti.selected = drawGraffitiEnabled;
|
||||
if(drawGraffitiEnabled) {
|
||||
_btnDrawMosaic.selected = NO;
|
||||
}
|
||||
_drawCtrlView.drawMode = GRAFFITI;
|
||||
_drawCtrlView.drawEnable = drawGraffitiEnabled;
|
||||
for (TUIMultimediaStickerView *v in _stickerViewList) {
|
||||
v.userInteractionEnabled = !drawGraffitiEnabled;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setDrawMosaicEnabled:(BOOL)drawMosaicEnabled {
|
||||
if (!_config.drawMosaicEnabled) {
|
||||
return;
|
||||
}
|
||||
_lastSelectedStickerView.selected = NO;
|
||||
_lastSelectedStickerView = nil;
|
||||
_btnDrawMosaic.selected = drawMosaicEnabled;
|
||||
if(drawMosaicEnabled) {
|
||||
_btnDrawGraffiti.selected = NO;
|
||||
}
|
||||
_drawCtrlView.drawMode = MOSAIC;
|
||||
_drawCtrlView.drawEnable = drawMosaicEnabled;
|
||||
for (TUIMultimediaStickerView *v in _stickerViewList) {
|
||||
v.userInteractionEnabled = !drawMosaicEnabled;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSArray<TUIMultimediaSticker *> *)getStickers {
|
||||
NSArray<TUIMultimediaSticker *> *stickers = [_stickerViewList tui_multimedia_map:^TUIMultimediaSticker *(TUIMultimediaStickerView *v) {
|
||||
[v resignFirstResponder];
|
||||
TUIMultimediaSticker *sticker = [[TUIMultimediaSticker alloc] init];
|
||||
sticker.image = [TUIMultimediaImageUtil imageFromView:v withRotate:v.rotation];
|
||||
sticker.frame = v.frame;
|
||||
return sticker;
|
||||
}];
|
||||
|
||||
TUIMultimediaSticker *drawSticker = _drawCtrlView != nil ? _drawCtrlView.drawSticker : nil;
|
||||
if (drawSticker != nil) {
|
||||
stickers = [NSArray tui_multimedia_arrayWithArray:stickers append:@[ drawSticker ]];
|
||||
}
|
||||
return stickers;
|
||||
}
|
||||
|
||||
- (void) clearAllStaticker {
|
||||
if (_drawCtrlView != nil) {
|
||||
[_drawCtrlView clearAllDraw];
|
||||
}
|
||||
|
||||
for (TUIMultimediaStickerView *v in _stickerViewList) {
|
||||
[v removeFromSuperview];
|
||||
}
|
||||
[_stickerViewList removeAllObjects];
|
||||
}
|
||||
|
||||
#pragma mark - UIGestureRecognizer actions
|
||||
- (void)onTapContainerView:(UITapGestureRecognizer *)rec {
|
||||
_lastSelectedStickerView.selected = NO;
|
||||
_lastSelectedStickerView = nil;
|
||||
}
|
||||
|
||||
#pragma mark - Actions
|
||||
- (void)onBtnSendClicked {
|
||||
[self setDrawEnabled:NO];
|
||||
[self setDrawMosaicEnabled:NO];
|
||||
[_delegate onCommonEditorControlViewComplete:self stickers:[self getStickers]];
|
||||
}
|
||||
|
||||
- (void)onBtnCancelClicked {
|
||||
[_delegate onCommonEditorControlViewCancel:self];
|
||||
}
|
||||
|
||||
- (void)onBtnSubtitleClicked {
|
||||
[_delegate onCommonEditorControlViewNeedModifySubtitle:[[TUIMultimediaSubtitleInfo alloc] init]
|
||||
callback:^(TUIMultimediaSubtitleInfo *subtitle, BOOL isOk) {
|
||||
if (isOk) {
|
||||
[self addSubtitle:subtitle];
|
||||
}
|
||||
}];
|
||||
[self setDrawEnabled:NO];
|
||||
[self setDrawMosaicEnabled:NO];
|
||||
_lastSelectedStickerView.selected = NO;
|
||||
_lastSelectedStickerView = nil;
|
||||
}
|
||||
- (void)onBtnPasterClicked {
|
||||
[_delegate onCommonEditorControlViewNeedAddPaster:self];
|
||||
[self setDrawEnabled:NO];
|
||||
[self setDrawMosaicEnabled:NO];
|
||||
_lastSelectedStickerView.selected = NO;
|
||||
_lastSelectedStickerView = nil;
|
||||
}
|
||||
|
||||
- (void)onBtnDrawGraffitiClicked {
|
||||
[self setDrawEnabled:!_btnDrawGraffiti.selected];
|
||||
_drawCtrlView.drawMode = GRAFFITI;
|
||||
}
|
||||
|
||||
- (void)onBtnDrawMosaicClicked {
|
||||
[self setDrawMosaicEnabled:!_btnDrawMosaic.selected];
|
||||
_drawCtrlView.drawMode = MOSAIC;
|
||||
}
|
||||
|
||||
- (void)onBtnMusicClicked {
|
||||
[self setDrawEnabled:NO];
|
||||
[self setDrawMosaicEnabled:NO];
|
||||
[_delegate onCommonEditorControlViewNeedEditMusic:self];
|
||||
_lastSelectedStickerView.selected = NO;
|
||||
_lastSelectedStickerView = nil;
|
||||
}
|
||||
|
||||
- (void)onBtnCropClicked {
|
||||
[self setDrawEnabled:NO];
|
||||
[self setDrawMosaicEnabled:NO];
|
||||
[_cropControlView show];
|
||||
_drawCtrlView.drawEnable = NO;
|
||||
_stkViewButtons.hidden = YES;
|
||||
_btnCancel.hidden = YES;
|
||||
}
|
||||
|
||||
- (void)onBtnGenerateCancelClicked {
|
||||
[_delegate onCommonEditorControlViewCancelGenerate:self];
|
||||
self.isGenerating = false;
|
||||
_progressView.progress = 0;
|
||||
}
|
||||
|
||||
#pragma mark - Properties
|
||||
- (BOOL)modifyButtonsHidden {
|
||||
return _stkViewButtons.hidden;
|
||||
}
|
||||
|
||||
- (void)setModifyButtonsHidden:(BOOL)modifyButtonsHidden {
|
||||
_stkViewButtons.hidden = modifyButtonsHidden;
|
||||
}
|
||||
|
||||
- (void)setPreviewSize:(CGSize)previewSize {
|
||||
_previewSize = previewSize;
|
||||
if (previewSize.width == 0 || previewSize.height == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
int width = self.frame.size.width;
|
||||
int height = previewSize.height / previewSize.width * width;
|
||||
_previewView.frame = CGRectMake(0,0, width, height);
|
||||
_previewView.center = self.center;
|
||||
_previewLimitRect = _previewView.frame;
|
||||
}
|
||||
|
||||
- (BOOL)isGenerating {
|
||||
return !_generateView.hidden;
|
||||
}
|
||||
|
||||
- (void)setIsGenerating:(BOOL)isGenerating {
|
||||
_generateView.hidden = !isGenerating;
|
||||
_stkViewButtons.hidden = isGenerating;
|
||||
_btnCancel.hidden = isGenerating;
|
||||
}
|
||||
|
||||
- (CGFloat)progressBarProgress {
|
||||
return _progressView.progress;
|
||||
}
|
||||
|
||||
- (void)setProgressBarProgress:(CGFloat)progressBarProgress {
|
||||
[_progressView setProgress:progressBarProgress animated:YES];
|
||||
}
|
||||
|
||||
- (BOOL)musicEdited {
|
||||
return _btnMusic != nil ? _btnMusic.selected : FALSE;
|
||||
}
|
||||
|
||||
- (void)setMusicEdited:(BOOL)musicEdited {
|
||||
if (_btnMusic != nil) {
|
||||
_btnMusic.selected = musicEdited;
|
||||
}
|
||||
}
|
||||
|
||||
-(void)setMosaciOriginalImage:(UIImage *)mosaciOriginalImage {
|
||||
_mosaciOriginalImage = mosaciOriginalImage;
|
||||
if (_drawCtrlView) {
|
||||
_drawCtrlView.drawView.mosaciOriginalImage = mosaciOriginalImage;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - TUIMultimediaCropControlDelegate protocol
|
||||
|
||||
- (void)onCancelCrop {
|
||||
[self previewRotationToZero];
|
||||
self.previewSize = _previewSize;
|
||||
_stkViewButtons.hidden = NO;
|
||||
_btnCancel.hidden = NO;
|
||||
}
|
||||
|
||||
- (void)onConfirmCrop:(CGRect)cropRect {
|
||||
CGFloat cropX = (cropRect.origin.x - _previewView.frame.origin.x) / _previewView.frame.size.width;
|
||||
CGFloat cropY = (cropRect.origin.y - _previewView.frame.origin.y) / _previewView.frame.size.height;
|
||||
CGFloat cropWidth = cropRect.size.width / _previewView.frame.size.width;
|
||||
CGFloat cropHeight = cropRect.size.height / _previewView.frame.size.height;
|
||||
CGRect normalizedCropRect = CGRectMake(cropX, cropY, cropWidth, cropHeight);
|
||||
|
||||
CGFloat rotationAngle = _previewRotationAngle;
|
||||
if ((_previewRotationAngle + 360) % 360 != 0) {
|
||||
_previewView.hidden = YES;
|
||||
}
|
||||
[self previewRotationToZero];
|
||||
|
||||
[_delegate onCommonEditorControlViewCrop:rotationAngle normalizedCropRect:normalizedCropRect stickers:[self getStickers]];
|
||||
_stkViewButtons.hidden = NO;
|
||||
_btnCancel.hidden = NO;
|
||||
[self clearAllStaticker];
|
||||
}
|
||||
|
||||
#pragma mark - TUIMultimediaStickerViewDelegate protocol
|
||||
- (void)onStickerViewSelected:(TUIMultimediaStickerView *)v {
|
||||
[_editContainerView bringSubviewToFront:v];
|
||||
_lastSelectedStickerView.selected = NO;
|
||||
_lastSelectedStickerView = v;
|
||||
}
|
||||
|
||||
- (void)onStickerViewShouldDelete:(TUIMultimediaStickerView *)v {
|
||||
[v removeFromSuperview];
|
||||
[_stickerViewList removeObject:v];
|
||||
}
|
||||
|
||||
- (void)onStickerViewShouldEdit:(TUIMultimediaStickerView *)v {
|
||||
if ([v isKindOfClass:TUIMultimediaSubtitleView.class]) {
|
||||
TUIMultimediaSubtitleView *vsub = (TUIMultimediaSubtitleView *)v;
|
||||
vsub.hidden = YES;
|
||||
TUIMultimediaSubtitleInfo *info = vsub.subtitleInfo;
|
||||
[_delegate onCommonEditorControlViewNeedModifySubtitle:info
|
||||
callback:^(TUIMultimediaSubtitleInfo *newInfo, BOOL isOk) {
|
||||
if (isOk) {
|
||||
vsub.subtitleInfo = newInfo;
|
||||
}
|
||||
vsub.hidden = NO;
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)onStickerViewSizeChanged:(TUIMultimediaStickerView *)v {
|
||||
}
|
||||
|
||||
#pragma mark - TUIMultimediaDrawCtrlViewDelegate
|
||||
- (void)onIsDrawCtrlViewDrawing:(BOOL)Hidden {
|
||||
_stkViewButtons.hidden = Hidden;
|
||||
}
|
||||
@end
|
||||
|
||||
#pragma mark - TUIMultimediaCommonEditorConfig
|
||||
@implementation TUIMultimediaCommonEditorConfig
|
||||
+ (instancetype)configForVideoEditor {
|
||||
TUIMultimediaCommonEditorConfig *config = [[TUIMultimediaCommonEditorConfig alloc] init];
|
||||
config.pasterEnabled = [[TUIMultimediaConfig sharedInstance] isSupportVideoEditPaster];
|
||||
config.subtitleEnabled = [[TUIMultimediaConfig sharedInstance] isSupportVideoEditSubtitle];
|
||||
config.drawGraffitiEnabled = [[TUIMultimediaConfig sharedInstance] isSupportVideoEditGraffiti];
|
||||
config.musicEditEnabled = [[TUIMultimediaConfig sharedInstance] isSupportVideoEditBGM];
|
||||
config.cropEnabled = NO;
|
||||
config.drawMosaicEnabled = NO;
|
||||
return config;
|
||||
}
|
||||
+ (instancetype)configForPictureEditor {
|
||||
TUIMultimediaCommonEditorConfig *config = [[TUIMultimediaCommonEditorConfig alloc] init];
|
||||
config.pasterEnabled = [[TUIMultimediaConfig sharedInstance] isSupportPictureEditPaster];
|
||||
config.subtitleEnabled = [[TUIMultimediaConfig sharedInstance] isSupportPictureEditSubtitle];
|
||||
config.drawGraffitiEnabled = [[TUIMultimediaConfig sharedInstance] isSupportPictureEditGraffiti];
|
||||
config.drawMosaicEnabled = [[TUIMultimediaConfig sharedInstance] isSupportPictureEditMosaic];
|
||||
config.cropEnabled = [[TUIMultimediaConfig sharedInstance] isSupportPictureEditCrop];
|
||||
config.musicEditEnabled = NO;
|
||||
return config;
|
||||
}
|
||||
@end
|
||||
@@ -0,0 +1,25 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "TUIMultimediaCropView.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class TUIMultimediaCommonEditorControlView;
|
||||
@protocol TUIMultimediaCropControlDelegate;
|
||||
|
||||
@interface TUIMultimediaCropControlView : UIView
|
||||
@property(weak, nullable, nonatomic) id<TUIMultimediaCropControlDelegate> delegate;
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame editorControl:(TUIMultimediaCommonEditorControlView*)editorControl;
|
||||
- (void)show;
|
||||
- (void)changeResetButtonStatus;
|
||||
@end
|
||||
|
||||
@protocol TUIMultimediaCropControlDelegate <NSObject>
|
||||
- (void)onCancelCrop;
|
||||
- (void)onConfirmCrop:(CGRect)cropRect;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,166 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import "TUIMultimediaCropControlView.h"
|
||||
#import <Masonry/Masonry.h>
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaImageUtil.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaCommon.h"
|
||||
|
||||
#import "TUIMultimediaCropView.h"
|
||||
#import "TUIMultimediaCommonEditorControlView.h"
|
||||
|
||||
@interface TUIMultimediaCropControlView()<TUIMultimediaCropDelegate> {
|
||||
TUIMultimediaCommonEditorControlView* _editorControl;
|
||||
TUIMultimediaCropView *_cropView;
|
||||
UIButton* _restoreButton;
|
||||
UIButton* _rotationButton;
|
||||
UIButton* _confirmButton;
|
||||
UIButton* _cancelButton;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation TUIMultimediaCropControlView
|
||||
- (instancetype)initWithFrame:(CGRect)frame editorControl:(TUIMultimediaCommonEditorControlView*)editorControl{
|
||||
self = [super initWithFrame:frame];
|
||||
if (self != nil) {
|
||||
_editorControl = editorControl;
|
||||
[self initUI];
|
||||
self.backgroundColor = [UIColor clearColor];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
#pragma mark - UI init
|
||||
|
||||
- (void)initUI {
|
||||
_cropView = [[TUIMultimediaCropView alloc] initWithFrame:self.frame];
|
||||
[self addSubview:_cropView];
|
||||
[_cropView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.size.equalTo(self);
|
||||
}];
|
||||
_cropView.delegate = self;
|
||||
|
||||
_restoreButton = [self addFuncitonButtonWithText:[TUIMultimediaCommon localizedStringForKey:@"restore"]
|
||||
action:@selector(onResetBtnClicked) bottomOffset:-75 leftOffset:7.5 rightOffset:0 size:CGSizeMake(80, 22)];
|
||||
_cancelButton = [self addFunctionButtonWithImage:TUIMultimediaPluginBundleThemeImage(@"editor_crop_cancel", @"crop_cancel")
|
||||
action:@selector(onCancelBtnClicked) bottomOffset:-25 leftOffset:35 rightOffset:0 size:CGSizeMake(25, 25)];
|
||||
_confirmButton = [self addFunctionButtonWithImage:TUIMultimediaPluginBundleThemeImage(@"editor_crop_confirm", @"crop_ok")
|
||||
action:@selector(onConfirmBtnClicked) bottomOffset:-25 leftOffset:0 rightOffset:-32.5 size:CGSizeMake(30, 28)];
|
||||
_rotationButton = [self addFunctionButtonWithImage:TUIMultimediaPluginBundleThemeImage(@"editor_crop_rotation", @"crop_rotation")
|
||||
action:@selector(onRotationBtnClicked) bottomOffset:-75 leftOffset:0 rightOffset:-35 size:CGSizeMake(25, 25)];
|
||||
|
||||
_restoreButton.enabled = NO;
|
||||
}
|
||||
|
||||
-(void)show {
|
||||
self.hidden = NO;
|
||||
_cropView.preViewFrame = _editorControl.previewView.frame;
|
||||
[_cropView reset];
|
||||
_restoreButton.enabled = NO;
|
||||
}
|
||||
|
||||
- (void)changeResetButtonStatus {
|
||||
_restoreButton.enabled = ![self isApproximateSize:[_cropView getCropRect].size size2:_editorControl.previewView.frame.size];
|
||||
}
|
||||
|
||||
-(void)onResetBtnClicked {
|
||||
[_editorControl previewRotationToZero];
|
||||
_cropView.preViewFrame = _editorControl.previewView.frame;
|
||||
[_cropView reset];
|
||||
_restoreButton.enabled = NO;
|
||||
}
|
||||
|
||||
-(void)onRotationBtnClicked {
|
||||
CGRect cropRect = [_cropView getCropRect];
|
||||
CGPoint rotationCenter = CGPointMake(CGRectGetMidX(cropRect), CGRectGetMidY(cropRect));
|
||||
[_editorControl previewRotation90:rotationCenter];
|
||||
[_cropView rotation90];
|
||||
_restoreButton.enabled = YES;
|
||||
}
|
||||
|
||||
-(void)onCancelBtnClicked {
|
||||
self.hidden = YES;
|
||||
if (_delegate != nil) {
|
||||
[_delegate onCancelCrop];
|
||||
}
|
||||
}
|
||||
|
||||
-(void)onConfirmBtnClicked {
|
||||
self.hidden = YES;
|
||||
if (_delegate != nil) {
|
||||
if (_restoreButton.isEnabled) {
|
||||
[_delegate onConfirmCrop:[_cropView getCropRect]];
|
||||
} else {
|
||||
[_delegate onCancelCrop];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-(UIButton*) addFuncitonButtonWithText:(NSString*)text action:(SEL)actionSel bottomOffset:(int)bottomOffset
|
||||
leftOffset:(int)leftOffset rightOffset:(int)rightOffset size:(CGSize)size {
|
||||
UIButton* button = [UIButton buttonWithType:UIButtonTypeSystem];
|
||||
[self addSubview:button];
|
||||
button.backgroundColor = [UIColor clearColor];
|
||||
[button mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
if (leftOffset > 0) {
|
||||
make.leading.equalTo(self).offset(leftOffset);
|
||||
}
|
||||
|
||||
if (rightOffset < 0) {
|
||||
make.trailing.equalTo(self).offset(rightOffset);
|
||||
}
|
||||
|
||||
make.bottom.equalTo(self).offset(bottomOffset);
|
||||
make.size.mas_equalTo(size);
|
||||
}];
|
||||
[button addTarget:self action:actionSel forControlEvents:UIControlEventTouchUpInside];
|
||||
[button setTitle:text forState:UIControlStateNormal];
|
||||
[button setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
|
||||
[button setTitleColor:[UIColor grayColor] forState:UIControlStateDisabled];
|
||||
return button;
|
||||
}
|
||||
|
||||
- (UIButton *)addFunctionButtonWithImage:(UIImage *)image action:(SEL)actionSel bottomOffset:(int)bottomOffset
|
||||
leftOffset:(int)leftOffset rightOffset:(int)rightOffset size:(CGSize)size{
|
||||
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
[self addSubview:button];
|
||||
UIImage *imgNormal = [TUIMultimediaImageUtil imageFromImage:image withTintColor:
|
||||
TUIMultimediaPluginDynamicColor(@"editor_func_btn_normal_color", @"#FFFFFF")];
|
||||
[button setImage:imgNormal forState:UIControlStateNormal];
|
||||
[button setImageEdgeInsets:UIEdgeInsetsMake(0, 0, 0, 0)];
|
||||
[button addTarget:self action:actionSel forControlEvents:UIControlEventTouchUpInside];
|
||||
|
||||
[button mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
if (leftOffset > 0) {
|
||||
make.leading.equalTo(self).offset(leftOffset);
|
||||
}
|
||||
|
||||
if (rightOffset < 0) {
|
||||
make.trailing.equalTo(self).offset(rightOffset);
|
||||
}
|
||||
|
||||
make.bottom.equalTo(self).offset(bottomOffset);
|
||||
make.size.mas_equalTo(size);
|
||||
}];
|
||||
return button;
|
||||
}
|
||||
|
||||
|
||||
-(BOOL)isApproximateSize:(CGSize)size1 size2:(CGSize)size2 {
|
||||
return ABS(size1.width - size1.width ) < 1 && ABS(size1.height - size2.height) < 1;
|
||||
}
|
||||
|
||||
#pragma mark - TUIMultimediaCropViewDelegate
|
||||
- (void)onStartCrop {
|
||||
_editorControl.isStartCrop = YES;
|
||||
_restoreButton.enabled = YES;
|
||||
}
|
||||
|
||||
- (void)onCropComplete:(CGFloat)scale centerPoint:(CGPoint)centerPoint offset:(CGPoint)offset {
|
||||
_editorControl.isStartCrop = NO;
|
||||
_editorControl.previewLimitRect = [_cropView getCropRect];
|
||||
[_editorControl previewScale:scale center:centerPoint];
|
||||
[_editorControl previewMove:offset];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,19 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@protocol TUIMultimediaCropDelegate;
|
||||
|
||||
@interface TUIMultimediaCropView : UIView
|
||||
@property(nonatomic) CGRect preViewFrame;
|
||||
@property(weak, nullable, nonatomic) id<TUIMultimediaCropDelegate> delegate;
|
||||
-(void)reset;
|
||||
-(void)rotation90;
|
||||
-(CGRect)getCropRect;
|
||||
@end
|
||||
|
||||
@protocol TUIMultimediaCropDelegate <NSObject>
|
||||
- (void)onStartCrop;
|
||||
- (void)onCropComplete:(CGFloat)scale centerPoint:(CGPoint)centerPoint offset:(CGPoint)offset;
|
||||
@end
|
||||
@@ -0,0 +1,334 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import "TUIMultimediaCropView.h"
|
||||
|
||||
#import <ReactiveObjC/ReactiveObjC.h>
|
||||
#import <Masonry/Masonry.h>
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
|
||||
#define TOUCH_MAX_DIS 40
|
||||
#define CORNER_LINE_WIDTH 4.0
|
||||
#define CORNER_LENGTH 25.0
|
||||
#define CROP_RECT_BORDER_LINE_WIDTH 2.0
|
||||
#define CROP_RECT_GRID_LINE_WIDTH 0.8
|
||||
|
||||
@interface TUIMultimediaCropView()<UIGestureRecognizerDelegate, UIGestureRecognizerDelegate>{
|
||||
UIColor * _borderColor;
|
||||
UIColor* _backgroundColor;
|
||||
|
||||
NSDate * _lastShowTransparentBackgroundTime;
|
||||
NSDate * _lastShowGridTime;
|
||||
BOOL _isShowTransparentBackground;
|
||||
BOOL _isShowGird;
|
||||
|
||||
CGRect _limitRect;
|
||||
CGRect _cropRect;
|
||||
|
||||
CGFloat _isMoveLeft;
|
||||
CGFloat _isMoveRight;
|
||||
CGFloat _isMoveTop;
|
||||
CGFloat _isMoveBottom;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation TUIMultimediaCropView
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
self.backgroundColor = [UIColor clearColor];
|
||||
|
||||
_borderColor = [UIColor whiteColor];
|
||||
_backgroundColor = [UIColor blackColor];
|
||||
_isShowTransparentBackground = NO;
|
||||
_isShowGird = NO;
|
||||
|
||||
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(onPan:)];
|
||||
panGesture.maximumNumberOfTouches = 1;
|
||||
panGesture.delegate = self;
|
||||
[self addGestureRecognizer:panGesture];
|
||||
|
||||
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onTap:)];
|
||||
[self addGestureRecognizer:tapGesture];
|
||||
|
||||
UIPinchGestureRecognizer *pinchRec = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(onPinch:)];
|
||||
[self addGestureRecognizer:pinchRec];
|
||||
pinchRec.delegate = self;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
-(CGRect)getCropRect {
|
||||
return _cropRect;
|
||||
}
|
||||
|
||||
-(void)reset {
|
||||
[self adjustCropRect:_preViewFrame];
|
||||
[self setNeedsDisplay];
|
||||
}
|
||||
|
||||
-(void)rotation90 {
|
||||
int centerX = CGRectGetMidX(_cropRect);
|
||||
int centerY = CGRectGetMidY(_cropRect);
|
||||
int width = _cropRect.size.width;
|
||||
int height = _cropRect.size.height;
|
||||
|
||||
_cropRect.origin.y = centerY - width / 2.0f;
|
||||
_cropRect.size.height = width;
|
||||
|
||||
_cropRect.origin.x = centerX - height / 2.0f;
|
||||
_cropRect.size.width = height;
|
||||
|
||||
[self adjustCropRect:_cropRect];
|
||||
[self setNeedsDisplay];
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
_limitRect = CGRectMake(10, 50, self.frame.size.width - 20, self.frame.size.height - 160);
|
||||
_cropRect = _limitRect;
|
||||
}
|
||||
|
||||
- (void)onTap:(UITapGestureRecognizer *)gesture {
|
||||
[self showTransparentBackground];
|
||||
[self showGrid];
|
||||
}
|
||||
|
||||
- (void)onPinch:(UIPinchGestureRecognizer *)gestureRecognizer {
|
||||
[self showTransparentBackground];
|
||||
[self showGrid];
|
||||
}
|
||||
|
||||
- (void)onPan:(UIPanGestureRecognizer *)gesture {
|
||||
_backgroundColor = [UIColor clearColor];
|
||||
[self showTransparentBackground];
|
||||
[self showGrid];
|
||||
switch (gesture.state) {
|
||||
case UIGestureRecognizerStateBegan: {
|
||||
CGPoint p = [gesture locationInView:self];
|
||||
_isMoveLeft = fabs(p.x - _cropRect.origin.x) < TOUCH_MAX_DIS;
|
||||
_isMoveRight = fabs(p.x - CGRectGetMaxX(_cropRect)) < TOUCH_MAX_DIS;
|
||||
_isMoveTop = fabs(p.y - _cropRect.origin.y) < TOUCH_MAX_DIS;
|
||||
_isMoveBottom = fabs(p.y - CGRectGetMaxY(_cropRect)) < TOUCH_MAX_DIS;
|
||||
if (_isMoveTop || _isMoveLeft || _isMoveRight || _isMoveBottom) {
|
||||
[_delegate onStartCrop];
|
||||
}
|
||||
break;
|
||||
}
|
||||
case UIGestureRecognizerStateChanged: {
|
||||
CGPoint p = [gesture locationInView:self];
|
||||
if (_isMoveLeft && p.x > _limitRect.origin.x && p.x < CGRectGetMaxX(_cropRect)) {
|
||||
_cropRect.size.width += (_cropRect.origin.x - p.x);
|
||||
_cropRect.origin.x = p.x;
|
||||
}
|
||||
|
||||
if (_isMoveRight && p.x > _cropRect.origin.x && p.x < CGRectGetMaxX(_limitRect)) {
|
||||
_cropRect.size.width = p.x - _cropRect.origin.x;
|
||||
}
|
||||
|
||||
if (_isMoveTop && p.y > _limitRect.origin.y && p.y < CGRectGetMaxY(_cropRect)) {
|
||||
_cropRect.size.height += (_cropRect.origin.y - p.y);
|
||||
_cropRect.origin.y = p.y;
|
||||
}
|
||||
|
||||
if (_isMoveBottom && p.y > _cropRect.origin.y && p.y < CGRectGetMaxY(_limitRect)) {
|
||||
_cropRect.size.height = p.y - _cropRect.origin.y;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case UIGestureRecognizerStateEnded: {
|
||||
if (_isMoveTop || _isMoveLeft || _isMoveRight || _isMoveBottom) {
|
||||
[self adjustCropRect:_cropRect];
|
||||
}
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
[gesture setTranslation:CGPointZero inView:self.superview];
|
||||
[self setNeedsDisplay];
|
||||
}
|
||||
|
||||
|
||||
- (void) adjustCropRect:(CGRect) sourceCropRect {
|
||||
if (CGRectIsEmpty(sourceCropRect)) {
|
||||
return;
|
||||
}
|
||||
|
||||
float limitRectRation = _limitRect.size.width * 1.0f / _limitRect.size.height;
|
||||
float sourceRectRation = sourceCropRect.size.width * 1.0f / sourceCropRect.size.height;
|
||||
|
||||
if (sourceRectRation > limitRectRation) {
|
||||
_cropRect.origin.x = _limitRect.origin.x;
|
||||
_cropRect.size.width = _limitRect.size.width;
|
||||
_cropRect.size.height = _cropRect.size.width / sourceRectRation;
|
||||
_cropRect.origin.y = (_limitRect.size.height - _cropRect.size.height) / 2.0f + _limitRect.origin.y;
|
||||
} else {
|
||||
_cropRect.origin.y = _limitRect.origin.y;
|
||||
_cropRect.size.height = _limitRect.size.height;
|
||||
_cropRect.size.width = _cropRect.size.height * sourceRectRation;
|
||||
_cropRect.origin.x = (_limitRect.size.width - _cropRect.size.width) / 2.0f + _limitRect.origin.x;
|
||||
}
|
||||
|
||||
CGFloat scale = _cropRect.size.width * 1.0f / sourceCropRect.size.width;
|
||||
CGFloat moveX = _cropRect.origin.x - sourceCropRect.origin.x;
|
||||
CGFloat moveY = _cropRect.origin.y - sourceCropRect.origin.y;
|
||||
|
||||
[_delegate onCropComplete:scale centerPoint:sourceCropRect.origin
|
||||
offset:CGPointMake(moveX, moveY)];
|
||||
}
|
||||
|
||||
- (void)drawRect:(CGRect)rect {
|
||||
[super drawRect:rect];
|
||||
|
||||
if (_cropRect.size.width == 0 || _cropRect.size.height == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
CGContextRef context = UIGraphicsGetCurrentContext();
|
||||
if (_isShowTransparentBackground) {
|
||||
[[UIColor clearColor] setFill];
|
||||
CGContextFillRect(context, rect);
|
||||
CGContextFillPath(context);
|
||||
} else {
|
||||
[[UIColor blackColor] setFill];
|
||||
CGContextFillRect(context, rect);
|
||||
CGContextAddRect(context, _cropRect);
|
||||
CGContextClip(context);
|
||||
CGContextSetBlendMode(context, kCGBlendModeClear);
|
||||
CGContextAddRect(context, _cropRect);
|
||||
CGContextFillPath(context);
|
||||
}
|
||||
|
||||
CGContextSetBlendMode(context, kCGBlendModeNormal);
|
||||
[self drawCropRect:context];
|
||||
[self drawCorner:context];
|
||||
if (_isShowGird) {
|
||||
[self drawGrid:context];
|
||||
}
|
||||
}
|
||||
|
||||
- (void) drawCropRect:(CGContextRef) context {
|
||||
CGContextSetStrokeColorWithColor(context, _borderColor.CGColor);
|
||||
CGContextSetLineWidth(context, CROP_RECT_BORDER_LINE_WIDTH );
|
||||
|
||||
CGContextMoveToPoint(context, CGRectGetMinX(_cropRect) + CORNER_LENGTH, CGRectGetMinY(_cropRect));
|
||||
CGContextAddLineToPoint(context, CGRectGetMaxX(_cropRect) - CORNER_LENGTH, CGRectGetMinY(_cropRect));
|
||||
|
||||
CGContextMoveToPoint(context, CGRectGetMaxX(_cropRect), CGRectGetMinY(_cropRect) + CORNER_LENGTH);
|
||||
CGContextAddLineToPoint(context, CGRectGetMaxX(_cropRect), CGRectGetMaxY(_cropRect) - CORNER_LENGTH);
|
||||
|
||||
CGContextMoveToPoint(context, CGRectGetMaxX(_cropRect) - CORNER_LENGTH, CGRectGetMaxY(_cropRect));
|
||||
CGContextAddLineToPoint(context, CGRectGetMinX(_cropRect) + CORNER_LENGTH, CGRectGetMaxY(_cropRect));
|
||||
|
||||
CGContextMoveToPoint(context, CGRectGetMinX(_cropRect), CGRectGetMaxY(_cropRect) - CORNER_LENGTH);
|
||||
CGContextAddLineToPoint(context, CGRectGetMinX(_cropRect), CGRectGetMinY(_cropRect) + CORNER_LENGTH);
|
||||
|
||||
CGContextStrokePath(context);
|
||||
}
|
||||
|
||||
- (void) drawCorner:(CGContextRef) context {
|
||||
float corner_width = CORNER_LINE_WIDTH;
|
||||
if (!_isShowTransparentBackground) {
|
||||
corner_width = 1.5f * corner_width;
|
||||
}
|
||||
CGContextSetLineWidth(context, corner_width);
|
||||
float halfLineWidth = corner_width / 2.0f;
|
||||
|
||||
CGContextMoveToPoint(context, CGRectGetMinX(_cropRect) - halfLineWidth, CGRectGetMinY(_cropRect));
|
||||
CGContextAddLineToPoint(context, CGRectGetMinX(_cropRect) + CORNER_LENGTH, CGRectGetMinY(_cropRect));
|
||||
CGContextMoveToPoint(context, CGRectGetMinX(_cropRect), CGRectGetMinY(_cropRect) - halfLineWidth);
|
||||
CGContextAddLineToPoint(context, CGRectGetMinX(_cropRect), CGRectGetMinY(_cropRect) + CORNER_LENGTH);
|
||||
|
||||
CGContextMoveToPoint(context, CGRectGetMaxX(_cropRect) - CORNER_LENGTH, CGRectGetMinY(_cropRect));
|
||||
CGContextAddLineToPoint(context, CGRectGetMaxX(_cropRect) + halfLineWidth, CGRectGetMinY(_cropRect));
|
||||
CGContextMoveToPoint(context, CGRectGetMaxX(_cropRect), CGRectGetMinY(_cropRect) - halfLineWidth);
|
||||
CGContextAddLineToPoint(context, CGRectGetMaxX(_cropRect), CGRectGetMinY(_cropRect) + CORNER_LENGTH);
|
||||
|
||||
CGContextMoveToPoint(context, CGRectGetMaxX(_cropRect), CGRectGetMaxY(_cropRect) - CORNER_LENGTH);
|
||||
CGContextAddLineToPoint(context, CGRectGetMaxX(_cropRect), CGRectGetMaxY(_cropRect) + halfLineWidth);
|
||||
CGContextMoveToPoint(context, CGRectGetMaxX(_cropRect) - CORNER_LENGTH, CGRectGetMaxY(_cropRect));
|
||||
CGContextAddLineToPoint(context, CGRectGetMaxX(_cropRect) + halfLineWidth, CGRectGetMaxY(_cropRect));
|
||||
|
||||
CGContextMoveToPoint(context, CGRectGetMinX(_cropRect), CGRectGetMaxY(_cropRect) - CORNER_LENGTH);
|
||||
CGContextAddLineToPoint(context, CGRectGetMinX(_cropRect), CGRectGetMaxY(_cropRect) + halfLineWidth);
|
||||
CGContextMoveToPoint(context, CGRectGetMinX(_cropRect) - halfLineWidth, CGRectGetMaxY(_cropRect));
|
||||
CGContextAddLineToPoint(context, CGRectGetMinX(_cropRect) + CORNER_LENGTH, CGRectGetMaxY(_cropRect));
|
||||
|
||||
CGContextStrokePath(context);
|
||||
}
|
||||
|
||||
- (void) drawGrid:(CGContextRef) context {
|
||||
CGContextSetLineWidth(context, CROP_RECT_GRID_LINE_WIDTH);
|
||||
|
||||
CGContextMoveToPoint(context, CGRectGetMinX(_cropRect) + _cropRect.size.width / 3, CGRectGetMinY(_cropRect));
|
||||
CGContextAddLineToPoint(context, CGRectGetMinX(_cropRect) + _cropRect.size.width / 3, CGRectGetMaxY(_cropRect));
|
||||
|
||||
CGContextMoveToPoint(context, CGRectGetMinX(_cropRect) + _cropRect.size.width * 2 / 3, CGRectGetMinY(_cropRect));
|
||||
CGContextAddLineToPoint(context, CGRectGetMinX(_cropRect) + _cropRect.size.width * 2 / 3, CGRectGetMaxY(_cropRect));
|
||||
|
||||
CGContextMoveToPoint(context, CGRectGetMinX(_cropRect), CGRectGetMinY(_cropRect) + _cropRect.size.height / 3);
|
||||
CGContextAddLineToPoint(context, CGRectGetMaxX(_cropRect), CGRectGetMinY(_cropRect) + _cropRect.size.height / 3);
|
||||
|
||||
CGContextMoveToPoint(context, CGRectGetMinX(_cropRect), CGRectGetMinY(_cropRect) + _cropRect.size.height * 2 / 3);
|
||||
CGContextAddLineToPoint(context, CGRectGetMaxX(_cropRect), CGRectGetMinY(_cropRect) + _cropRect.size.height * 2 / 3);
|
||||
|
||||
CGContextStrokePath(context);
|
||||
}
|
||||
|
||||
|
||||
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void) showTransparentBackground {
|
||||
_lastShowTransparentBackgroundTime = [NSDate date];
|
||||
if (!_isShowTransparentBackground) {
|
||||
_isShowTransparentBackground = YES;
|
||||
[self setNeedsDisplay];
|
||||
[self delayCancelShowTransparentBackground];
|
||||
}
|
||||
}
|
||||
|
||||
- (void) delayCancelShowTransparentBackground {
|
||||
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0f * NSEC_PER_SEC));
|
||||
|
||||
@weakify(self)
|
||||
dispatch_after(popTime, dispatch_get_main_queue(), ^(void) {
|
||||
@strongify(self)
|
||||
if ([[NSDate date] timeIntervalSinceDate:self->_lastShowTransparentBackgroundTime] < 2.0f) {
|
||||
[self delayCancelShowTransparentBackground];
|
||||
return;
|
||||
}
|
||||
self->_isShowTransparentBackground = NO;
|
||||
[self setNeedsDisplay];
|
||||
});
|
||||
}
|
||||
|
||||
- (void) showGrid {
|
||||
_lastShowGridTime = [NSDate date];
|
||||
if (!_isShowGird) {
|
||||
_isShowGird = YES;
|
||||
[self setNeedsDisplay];
|
||||
[self delayCancelShowGrid];
|
||||
}
|
||||
}
|
||||
|
||||
- (void) delayCancelShowGrid {
|
||||
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0f * NSEC_PER_SEC));
|
||||
|
||||
@weakify(self)
|
||||
dispatch_after(popTime, dispatch_get_main_queue(), ^(void) {
|
||||
@strongify(self)
|
||||
if ([[NSDate date] timeIntervalSinceDate:self->_lastShowGridTime] < 4.0f) {
|
||||
[self delayCancelShowGrid];
|
||||
return;
|
||||
}
|
||||
self->_isShowGird = NO;
|
||||
[self setNeedsDisplay];
|
||||
});
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
@class TUIMultimediaDrawView;
|
||||
@class TUIMultimediaSticker;
|
||||
@protocol TUIMultimediaDrawCtrlViewDelegate;
|
||||
enum DrawMode:NSInteger;
|
||||
|
||||
@interface TUIMultimediaDrawCtrlView : UIView
|
||||
-(void)flushRedoUndoState;
|
||||
-(void)clearAllDraw;
|
||||
|
||||
@property (nonatomic, strong) TUIMultimediaDrawView* drawView;
|
||||
@property (nonatomic) enum DrawMode drawMode;
|
||||
@property (nonatomic) BOOL drawEnable;
|
||||
@property (nonatomic, strong, readonly) TUIMultimediaSticker * drawSticker;
|
||||
@property (weak, nullable, nonatomic) id<TUIMultimediaDrawCtrlViewDelegate> delegate;
|
||||
@end
|
||||
|
||||
@protocol TUIMultimediaDrawCtrlViewDelegate <NSObject>
|
||||
- (void)onIsDrawCtrlViewDrawing:(BOOL)Hidden;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,201 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import "TUIMultimediaDrawCtrlView.h"
|
||||
#import <Masonry/Masonry.h>
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaColorPanel.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaSplitter.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaImageUtil.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaDrawView.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaSticker.h"
|
||||
|
||||
#define FunctionButtonColorNormal TUIMultimediaPluginDynamicColor(@"editor_func_btn_normal_color", @"#FFFFFF")
|
||||
#define FunctionButtonColorDisabled TUIMultimediaPluginDynamicColor(@"editor_func_btn_disabled_color", @"#6D6D6D")
|
||||
|
||||
@interface TUIMultimediaDrawCtrlView () <TUIMultimediaColorPanelDelegate, TUIMultimediaDrawViewDelegate> {
|
||||
TUIMultimediaColorPanel *_colorPanel;
|
||||
UIButton *_btnRedo;
|
||||
UIButton *_btnUndo;
|
||||
TUIMultimediaSplitter *_vertical_splitter;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation TUIMultimediaDrawCtrlView
|
||||
- (id)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self != nil) {
|
||||
[self initCtrlView];
|
||||
[self initDrawView];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)initCtrlView {
|
||||
self.backgroundColor = TUIMultimediaPluginDynamicColor(@"editor_draw_panel_bg_color", @"#3333337F");
|
||||
|
||||
_colorPanel = [[TUIMultimediaColorPanel alloc] init];
|
||||
_colorPanel.delegate = self;
|
||||
[self addSubview:_colorPanel];
|
||||
|
||||
UIImage *imgUndo = TUIMultimediaPluginBundleThemeImage(@"editor_undo_img", @"undo");
|
||||
_btnUndo = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
[self addSubview:_btnUndo];
|
||||
_btnUndo.enabled = NO;
|
||||
[_btnUndo addTarget:self action:@selector(onBtnUndoClicked) forControlEvents:UIControlEventTouchUpInside];
|
||||
[_btnUndo setImage:[TUIMultimediaImageUtil imageFromImage:imgUndo withTintColor:FunctionButtonColorNormal] forState:UIControlStateNormal];
|
||||
[_btnUndo setImage:[TUIMultimediaImageUtil imageFromImage:imgUndo withTintColor:FunctionButtonColorDisabled] forState:UIControlStateDisabled];
|
||||
|
||||
UIImage *imgRedo = TUIMultimediaPluginBundleThemeImage(@"editor_redo_img", @"redo");
|
||||
_btnRedo = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
[self addSubview:_btnRedo];
|
||||
_btnRedo.enabled = NO;
|
||||
[_btnRedo addTarget:self action:@selector(onBtnRedoClicked) forControlEvents:UIControlEventTouchUpInside];
|
||||
[_btnRedo setImage:[TUIMultimediaImageUtil imageFromImage:imgRedo withTintColor:FunctionButtonColorNormal] forState:UIControlStateNormal];
|
||||
[_btnRedo setImage:[TUIMultimediaImageUtil imageFromImage:imgRedo withTintColor:FunctionButtonColorDisabled] forState:UIControlStateDisabled];
|
||||
|
||||
TUIMultimediaSplitter * horizontal_splitter = [[TUIMultimediaSplitter alloc] init];
|
||||
[self addSubview:horizontal_splitter];
|
||||
horizontal_splitter.axis = UILayoutConstraintAxisHorizontal;
|
||||
|
||||
_vertical_splitter = [[TUIMultimediaSplitter alloc] init];
|
||||
[self addSubview:_vertical_splitter];
|
||||
_vertical_splitter.axis = UILayoutConstraintAxisVertical;
|
||||
|
||||
[_btnRedo mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.right.equalTo(self).inset(5);
|
||||
make.centerY.equalTo(_colorPanel);
|
||||
make.size.mas_equalTo(CGSizeMake(30, 30));
|
||||
}];
|
||||
[_btnUndo mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.centerY.equalTo(_colorPanel);
|
||||
make.right.equalTo(_btnRedo.mas_left).inset(20);
|
||||
make.size.mas_equalTo(CGSizeMake(30, 30));
|
||||
}];
|
||||
[_colorPanel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.left.equalTo(self).inset(5);
|
||||
make.right.equalTo(_btnUndo.mas_left).inset(10);
|
||||
make.height.mas_equalTo(32);
|
||||
}];
|
||||
[horizontal_splitter mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.right.equalTo(self).inset(3);
|
||||
make.top.equalTo(_colorPanel.mas_bottom).inset(5);
|
||||
make.height.mas_equalTo(5);
|
||||
}];
|
||||
[_vertical_splitter mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(self).inset(3);
|
||||
make.bottom.equalTo(horizontal_splitter.mas_top).inset(3);
|
||||
make.left.equalTo(_colorPanel.mas_right).inset(3);
|
||||
make.width.mas_equalTo(5);
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)initDrawView {
|
||||
_drawView = [[TUIMultimediaDrawView alloc] init];
|
||||
_drawView.userInteractionEnabled = NO;
|
||||
UITapGestureRecognizer *drawTapRec = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onTapDrawView:)];
|
||||
[_drawView addGestureRecognizer:drawTapRec];
|
||||
_drawView.delegate = self;
|
||||
}
|
||||
|
||||
- (void) setDrawEnable:(BOOL)drawEnable {
|
||||
_drawView.userInteractionEnabled = drawEnable;
|
||||
self.hidden = !drawEnable;
|
||||
}
|
||||
|
||||
- (void) setDrawMode:(enum DrawMode)drawMode {
|
||||
_drawMode = drawMode;
|
||||
_drawView.drawMode = drawMode;
|
||||
[self updateUIAccordingToDrawMode];
|
||||
[self flushRedoUndoState];
|
||||
}
|
||||
|
||||
- (TUIMultimediaSticker *)drawSticker {
|
||||
if (_drawView != nil && _drawView.pathCount > 0) {
|
||||
TUIMultimediaSticker *drawSticker = [[TUIMultimediaSticker alloc] init];
|
||||
drawSticker.image = [TUIMultimediaImageUtil imageFromView:_drawView];
|
||||
drawSticker.frame = _drawView.frame;
|
||||
return drawSticker;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)onTapDrawView:(UITapGestureRecognizer *)rec {
|
||||
self.hidden = !self.hidden;
|
||||
[self.delegate onIsDrawCtrlViewDrawing:self.hidden];
|
||||
}
|
||||
|
||||
- (void)flushRedoUndoState {
|
||||
_btnUndo.enabled = _drawView.canUndo;
|
||||
_btnRedo.enabled = _drawView.canRedo;
|
||||
}
|
||||
|
||||
-(void)clearAllDraw {
|
||||
[_drawView clear];
|
||||
}
|
||||
|
||||
-(void) updateUIAccordingToDrawMode{
|
||||
if (_drawMode == GRAFFITI) {
|
||||
_colorPanel.hidden = NO;
|
||||
_vertical_splitter.hidden = NO;
|
||||
|
||||
[_btnRedo mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.right.equalTo(self).inset(5);
|
||||
make.centerY.equalTo(_colorPanel);
|
||||
make.size.mas_equalTo(CGSizeMake(30, 30));
|
||||
}];
|
||||
[_btnUndo mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.centerY.equalTo(_colorPanel);
|
||||
make.right.equalTo(_btnRedo.mas_left).inset(20);
|
||||
make.size.mas_equalTo(CGSizeMake(30, 30));
|
||||
}];
|
||||
|
||||
} else {
|
||||
_colorPanel.hidden = YES;
|
||||
_vertical_splitter.hidden = YES;
|
||||
|
||||
[_btnUndo mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.centerY.equalTo(_colorPanel);
|
||||
make.centerX.equalTo(self.mas_trailing).multipliedBy(0.25);
|
||||
make.size.mas_equalTo(CGSizeMake(30, 30));
|
||||
}];
|
||||
|
||||
[_btnRedo mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.centerY.equalTo(_colorPanel);
|
||||
make.centerX.equalTo(self.mas_trailing).multipliedBy(0.75);
|
||||
make.size.mas_equalTo(CGSizeMake(30, 30));
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - TUIMultimediaColorPanelDelegate protocol
|
||||
- (void)onColorPanel:(TUIMultimediaColorPanel *)panel selectColor:(UIColor *)color {
|
||||
_drawView.color = color;
|
||||
}
|
||||
|
||||
- (void)onBtnUndoClicked {
|
||||
[_drawView undo];
|
||||
[self flushRedoUndoState];
|
||||
}
|
||||
|
||||
- (void)onBtnRedoClicked {
|
||||
[_drawView redo];
|
||||
[self flushRedoUndoState];
|
||||
}
|
||||
|
||||
#pragma mark - TUIMultimediaDrawViewDelegate
|
||||
- (void)drawViewPathListChanged:(TUIMultimediaDrawView *)v {
|
||||
[self flushRedoUndoState];
|
||||
}
|
||||
|
||||
- (void)drawViewDrawStarted:(TUIMultimediaDrawView *)v {
|
||||
[self.delegate onIsDrawCtrlViewDrawing:YES];
|
||||
self.hidden = YES;
|
||||
}
|
||||
|
||||
- (void)drawViewDrawEnded:(TUIMultimediaDrawView *)v {
|
||||
[self.delegate onIsDrawCtrlViewDrawing:NO];
|
||||
self.hidden = NO;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,44 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class TUIMultimediaPath;
|
||||
@protocol TUIMultimediaDrawViewDelegate;
|
||||
|
||||
typedef NS_ENUM(NSInteger, DrawMode){
|
||||
GRAFFITI,
|
||||
MOSAIC
|
||||
};
|
||||
|
||||
@interface TUIMultimediaDrawView : UIView
|
||||
@property(nonatomic) UIColor *color;
|
||||
@property(nonatomic) CGFloat lineWidth;
|
||||
@property(readonly, nonatomic) NSInteger pathCount;
|
||||
@property(weak, nullable, nonatomic) id<TUIMultimediaDrawViewDelegate> delegate;
|
||||
@property(readonly, nonatomic) BOOL canUndo;
|
||||
@property(readonly, nonatomic) BOOL canRedo;
|
||||
@property(nonatomic) DrawMode drawMode;
|
||||
@property (nonatomic, strong) UIImage *mosaciOriginalImage;
|
||||
|
||||
- (void)clear;
|
||||
- (void)undo;
|
||||
- (void)redo;
|
||||
@end
|
||||
|
||||
@protocol TUIMultimediaDrawViewDelegate <NSObject>
|
||||
- (void)drawViewPathListChanged:(TUIMultimediaDrawView *)v;
|
||||
- (void)drawViewDrawStarted:(TUIMultimediaDrawView *)v;
|
||||
- (void)drawViewDrawEnded:(TUIMultimediaDrawView *)v;
|
||||
@end
|
||||
|
||||
|
||||
@interface TUIMultimediaPath : NSObject
|
||||
@property(nonatomic) CGSize originCanvasSize;
|
||||
@property(nonatomic, nonnull) NSMutableArray<NSValue *> *pathPoints;
|
||||
@property(nonatomic, nonnull) UIColor *color;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,112 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import "TUIMultimediaDrawView.h"
|
||||
#import <Masonry/Masonry.h>
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaColorPanel.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaCommon.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaGeometry.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaMosaicDrawView.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaGraffitiDrawView.h"
|
||||
|
||||
@interface TUIMultimediaDrawView () {
|
||||
TUIMultimediaMosaicDrawView* _mosaciDrawView;
|
||||
TUIMultimediaGraffitiDrawView* _graffitiDrawView;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIMultimediaDrawView
|
||||
- (id)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self != nil) {
|
||||
self.backgroundColor = UIColor.clearColor;
|
||||
UIPanGestureRecognizer *panRec = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(onPan:)];
|
||||
panRec.maximumNumberOfTouches = 1;
|
||||
[self addGestureRecognizer:panRec];
|
||||
|
||||
_mosaciDrawView = [[TUIMultimediaMosaicDrawView alloc] initWithFrame:self.bounds];
|
||||
[self addSubview:_mosaciDrawView];
|
||||
[_mosaciDrawView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.edges.equalTo(self);
|
||||
}];
|
||||
|
||||
_graffitiDrawView = [[TUIMultimediaGraffitiDrawView alloc] initWithFrame:self.bounds];
|
||||
[self addSubview:_graffitiDrawView];
|
||||
[_graffitiDrawView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.edges.equalTo(self);
|
||||
}];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)canUndo {
|
||||
return _drawMode == GRAFFITI ? _graffitiDrawView.canUndo : _mosaciDrawView.canUndo;
|
||||
}
|
||||
|
||||
- (BOOL)canRedo {
|
||||
return _drawMode == GRAFFITI ? _graffitiDrawView.canRedo : _mosaciDrawView.canRedo;
|
||||
}
|
||||
|
||||
- (void)clear {
|
||||
[_graffitiDrawView clearGraffiti];
|
||||
[_mosaciDrawView clearMosaic];
|
||||
}
|
||||
|
||||
- (void)undo {
|
||||
if (_drawMode == GRAFFITI) {
|
||||
[_graffitiDrawView undo];
|
||||
} else {
|
||||
[_mosaciDrawView undo];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)redo {
|
||||
if (_drawMode == GRAFFITI) {
|
||||
[_graffitiDrawView redo];
|
||||
} else {
|
||||
[_mosaciDrawView redo];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)onPan:(UIPanGestureRecognizer *)rec {
|
||||
switch (rec.state) {
|
||||
case UIGestureRecognizerStateBegan:
|
||||
case UIGestureRecognizerStateChanged: {
|
||||
CGPoint p = [rec locationInView:self];
|
||||
if (_drawMode == MOSAIC) {
|
||||
[_mosaciDrawView addPathPoint:p];
|
||||
} else {
|
||||
[_graffitiDrawView addPathPoint:p];
|
||||
}
|
||||
[_delegate drawViewDrawStarted:self];
|
||||
break;
|
||||
}
|
||||
case UIGestureRecognizerStateEnded: {
|
||||
if (_drawMode == MOSAIC) {
|
||||
[_mosaciDrawView completeAddPoint];
|
||||
} else {
|
||||
[_graffitiDrawView completeAddPoint];
|
||||
}
|
||||
[_delegate drawViewDrawEnded:self];
|
||||
[_delegate drawViewPathListChanged:self];
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSInteger) pathCount {
|
||||
return _mosaciDrawView.pathCount + _graffitiDrawView.pathCount;
|
||||
}
|
||||
|
||||
-(void) setMosaciOriginalImage:(UIImage *)mosaciOriginalImage {
|
||||
_mosaciDrawView.originalImage = mosaciOriginalImage;
|
||||
}
|
||||
|
||||
-(void) setColor:(UIColor *)color {
|
||||
_graffitiDrawView.color = color;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIMultimediaGraffitiDrawView : UIView
|
||||
@property(nonatomic) UIColor *color;
|
||||
@property(readonly, nonatomic) BOOL canUndo;
|
||||
@property(readonly, nonatomic) BOOL canRedo;
|
||||
@property(readonly, nonatomic) NSInteger pathCount;
|
||||
|
||||
- (void)clearGraffiti;
|
||||
- (void)undo;
|
||||
- (void)redo;
|
||||
- (void)addPathPoint:(CGPoint) point;
|
||||
- (void)completeAddPoint;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,148 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import "TUIMultimediaGraffitiDrawView.h"
|
||||
#import <Masonry/Masonry.h>
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaColorPanel.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaCommon.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaGeometry.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaDrawView.h"
|
||||
|
||||
#define GRAFFITI_LINE_WIDTH 4
|
||||
#define CGPointMultiply(point, scalar) CGPointMake(point.x * scalar, point.y * scalar)
|
||||
|
||||
@interface TUIMultimediaGraffitiDrawView () {
|
||||
NSMutableArray<NSValue *> *_currentPathPoints;
|
||||
UIBezierPath *_currentPath;
|
||||
NSMutableArray<TUIMultimediaPath *> *_pathList;
|
||||
NSMutableArray<TUIMultimediaPath *> *_redoPathList;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation TUIMultimediaGraffitiDrawView
|
||||
- (id)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self != nil) {
|
||||
self.backgroundColor = UIColor.clearColor;
|
||||
_pathList = [NSMutableArray array];
|
||||
_redoPathList = [NSMutableArray array];
|
||||
_currentPathPoints = [NSMutableArray array];
|
||||
_color = UIColor.whiteColor;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSInteger) pathCount {
|
||||
return _pathList.count;
|
||||
}
|
||||
|
||||
- (BOOL)canUndo {
|
||||
return _pathList.count != 0;
|
||||
}
|
||||
|
||||
- (BOOL)canRedo {
|
||||
return _redoPathList.count != 0;
|
||||
}
|
||||
|
||||
- (void)clearGraffiti {
|
||||
[_pathList removeAllObjects];
|
||||
[_redoPathList removeAllObjects];
|
||||
[_currentPath removeAllPoints];
|
||||
[_currentPathPoints removeAllObjects];
|
||||
[self setNeedsDisplay];
|
||||
}
|
||||
|
||||
- (void)undo {
|
||||
if (_pathList.count == 0) {
|
||||
return;
|
||||
}
|
||||
TUIMultimediaPath *path = [_pathList lastObject];
|
||||
[_pathList removeLastObject];
|
||||
[_redoPathList addObject:path];
|
||||
[self setNeedsDisplay];
|
||||
}
|
||||
|
||||
- (void)redo {
|
||||
if (_redoPathList.count == 0) {
|
||||
return;
|
||||
}
|
||||
TUIMultimediaPath *path = [_redoPathList lastObject];
|
||||
[_redoPathList removeLastObject];
|
||||
[_pathList addObject:path];
|
||||
[self setNeedsDisplay];
|
||||
}
|
||||
|
||||
- (void)drawRect:(CGRect)rect {
|
||||
[super drawRect:rect];
|
||||
|
||||
[[UIColor clearColor] setFill];
|
||||
UIRectFill(rect);
|
||||
|
||||
for (TUIMultimediaPath *path in _pathList) {
|
||||
[path.color set];
|
||||
CGFloat scale = self.frame.size.width / path.originCanvasSize.width;
|
||||
UIBezierPath * bezierPath = [TUIMultimediaGraffitiDrawView smoothBezierPathFromPoints:path.pathPoints scale:scale];
|
||||
[bezierPath stroke];
|
||||
}
|
||||
|
||||
if (_currentPath != nil) {
|
||||
[_color set];
|
||||
[_currentPath stroke];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSArray<TUIMultimediaPath *> *)pathList {
|
||||
return _pathList;
|
||||
}
|
||||
|
||||
- (void)addPathPoint:(CGPoint) point {
|
||||
[_currentPathPoints addObject:@(point)];
|
||||
_currentPath = [TUIMultimediaGraffitiDrawView smoothBezierPathFromPoints:_currentPathPoints scale:1.0];
|
||||
[self setNeedsDisplay];
|
||||
}
|
||||
|
||||
- (void)completeAddPoint {
|
||||
TUIMultimediaPath *path = [[TUIMultimediaPath alloc] init];
|
||||
path.pathPoints = [[NSMutableArray alloc] initWithArray:_currentPathPoints copyItems:YES];
|
||||
path.color = _color;
|
||||
path.originCanvasSize = self.frame.size;
|
||||
[_pathList addObject:path];
|
||||
[_redoPathList removeAllObjects];
|
||||
[_currentPathPoints removeAllObjects];
|
||||
_currentPath = nil;
|
||||
}
|
||||
|
||||
+ (UIBezierPath *)smoothBezierPathFromPoints:(NSMutableArray<NSValue *> *) pathPoints scale:(CGFloat)scale{
|
||||
UIBezierPath *path = [UIBezierPath bezierPath];
|
||||
path.lineCapStyle = kCGLineCapRound;
|
||||
path.lineJoinStyle = kCGLineJoinRound;
|
||||
path.lineWidth = GRAFFITI_LINE_WIDTH * scale;
|
||||
|
||||
if (pathPoints.count <= 1) {
|
||||
return nil;
|
||||
}
|
||||
[path moveToPoint:CGPointMultiply(pathPoints.firstObject.CGPointValue, scale)];
|
||||
if (pathPoints.count == 2) {
|
||||
[path addLineToPoint:CGPointMultiply(pathPoints.lastObject.CGPointValue,scale)];
|
||||
return path;
|
||||
}
|
||||
|
||||
for (int i = 3; i < pathPoints.count; i++) {
|
||||
CGPoint p = CGPointMultiply(pathPoints[i].CGPointValue, scale);
|
||||
CGPoint prev = CGPointMultiply(pathPoints[i - 1].CGPointValue, scale);
|
||||
CGPoint midPoint = Vec2Mul(Vec2AddVector(p, prev), 0.5);
|
||||
[path addQuadCurveToPoint:midPoint controlPoint:prev];
|
||||
}
|
||||
[path addLineToPoint:CGPointMultiply(pathPoints.lastObject.CGPointValue, scale)];
|
||||
return path;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIMultimediaPath
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
_color = UIColor.clearColor;
|
||||
return self;
|
||||
}
|
||||
@end
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright (c) 2025 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface TUIMultimediaMosaicDrawView : UIImageView
|
||||
@property (nonatomic, strong) UIImage *originalImage;
|
||||
@property(readonly, nonatomic) BOOL canUndo;
|
||||
@property(readonly, nonatomic) BOOL canRedo;
|
||||
@property(readonly, nonatomic) NSInteger pathCount;
|
||||
|
||||
- (void)clearMosaic;
|
||||
- (void)undo;
|
||||
- (void)redo;
|
||||
- (void)addPathPoint:(CGPoint) point;
|
||||
- (void)completeAddPoint;
|
||||
@end
|
||||
@@ -0,0 +1,172 @@
|
||||
// Copyright (c) 2025 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import "TUIMultimediaMosaicDrawView.h"
|
||||
#import <CoreImage/CoreImage.h>
|
||||
#import <Masonry/Masonry.h>
|
||||
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaDrawView.h"
|
||||
|
||||
#define MOSAIC_LINE_WIDTH 20
|
||||
#define MOSAIC_SCALE_VALUE 50
|
||||
|
||||
@interface TUIMultimediaMosaicDrawView () {
|
||||
UIBezierPath *_currentPath;
|
||||
|
||||
NSMutableArray<CAShapeLayer *> *_layerList;
|
||||
NSMutableArray<CAShapeLayer *> *_redoLayerList;
|
||||
|
||||
CALayer *_maskParentLayer;
|
||||
CAShapeLayer *_currentShapeLayer;
|
||||
BOOL _isAdddPathFirstPoint;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation TUIMultimediaMosaicDrawView
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
if (self = [super initWithFrame:frame]) {
|
||||
[self setupView];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setupView {
|
||||
NSLog(@"Mosaic setupView");
|
||||
_maskParentLayer = [CALayer layer];
|
||||
_maskParentLayer.frame = self.bounds;
|
||||
self.layer.mask = _maskParentLayer;
|
||||
|
||||
_layerList = [NSMutableArray array];
|
||||
_redoLayerList = [NSMutableArray array];
|
||||
_isAdddPathFirstPoint = YES;
|
||||
}
|
||||
|
||||
- (void)setOriginalImage:(UIImage *)originalImage {
|
||||
NSLog(@"Mosaic setOriginalImage");
|
||||
_originalImage = originalImage;
|
||||
self.image = [self generateMosaicImage:originalImage];
|
||||
}
|
||||
|
||||
-(void)addPathPoint:(CGPoint) point {
|
||||
if (_isAdddPathFirstPoint) {
|
||||
_isAdddPathFirstPoint = NO;
|
||||
_currentShapeLayer = [self createShapeLayer];
|
||||
[_maskParentLayer addSublayer:_currentShapeLayer];
|
||||
[_layerList addObject:_currentShapeLayer];
|
||||
_currentPath = [UIBezierPath bezierPath];
|
||||
[_currentPath moveToPoint:point];
|
||||
} else {
|
||||
[_currentPath addLineToPoint:point];
|
||||
_currentShapeLayer.path = _currentPath.CGPath;
|
||||
}
|
||||
}
|
||||
|
||||
-(CAShapeLayer*)createShapeLayer{
|
||||
CAShapeLayer* shapeLayer = [CAShapeLayer layer];
|
||||
shapeLayer.frame = self.bounds;
|
||||
shapeLayer.lineCap = kCALineCapRound;
|
||||
shapeLayer.lineJoin = kCALineJoinRound;
|
||||
shapeLayer.lineWidth = MOSAIC_LINE_WIDTH;
|
||||
shapeLayer.strokeColor = [UIColor blackColor].CGColor;
|
||||
shapeLayer.fillColor = nil;
|
||||
return shapeLayer;
|
||||
}
|
||||
|
||||
-(void)completeAddPoint {
|
||||
_isAdddPathFirstPoint = YES;
|
||||
for (CAShapeLayer * layer in _redoLayerList) {
|
||||
[self safeRemoveShapeLayer:layer];
|
||||
}
|
||||
[_redoLayerList removeAllObjects];
|
||||
}
|
||||
|
||||
- (NSInteger) pathCount {
|
||||
return _layerList.count;
|
||||
}
|
||||
|
||||
- (BOOL)canUndo {
|
||||
return _layerList.count != 0;
|
||||
}
|
||||
|
||||
- (BOOL)canRedo {
|
||||
return _redoLayerList.count != 0;
|
||||
}
|
||||
|
||||
- (void)clearMosaic {
|
||||
for (CAShapeLayer * layer in _layerList) {
|
||||
[self safeRemoveShapeLayer:layer];
|
||||
}
|
||||
|
||||
for (CAShapeLayer * layer in _redoLayerList) {
|
||||
[self safeRemoveShapeLayer:layer];
|
||||
}
|
||||
|
||||
[_currentPath removeAllPoints];
|
||||
[_layerList removeAllObjects];
|
||||
[_redoLayerList removeAllObjects];
|
||||
}
|
||||
|
||||
- (void)undo {
|
||||
if (_layerList.count == 0) {
|
||||
return;
|
||||
}
|
||||
CAShapeLayer *layer = [_layerList lastObject];
|
||||
[_layerList removeLastObject];
|
||||
layer.hidden = YES;
|
||||
[_redoLayerList addObject:layer];
|
||||
}
|
||||
|
||||
- (void)redo {
|
||||
if (_redoLayerList.count == 0) {
|
||||
return;
|
||||
}
|
||||
CAShapeLayer *layer = [_redoLayerList lastObject];
|
||||
[_redoLayerList removeLastObject];
|
||||
layer.hidden = NO;
|
||||
[_layerList addObject:layer];
|
||||
}
|
||||
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
_maskParentLayer.frame = self.bounds;
|
||||
|
||||
for (CAShapeLayer *layer in _maskParentLayer.sublayers) {
|
||||
if ([layer isKindOfClass:[CAShapeLayer class]]) {
|
||||
float scale = self.bounds.size.width / layer.frame.size.width;
|
||||
layer.frame = self.bounds;
|
||||
CGAffineTransform transform = CGAffineTransformMakeScale(scale, scale);
|
||||
CGPathRef scaledPath = CGPathCreateCopyByTransformingPath(layer.path, &transform);
|
||||
layer.lineWidth = layer.lineWidth * scale;
|
||||
layer.path = scaledPath;
|
||||
CGPathRelease(scaledPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (UIImage *)generateMosaicImage:(UIImage *)inputImage {
|
||||
CIImage *ciImage = [[CIImage alloc] initWithImage:inputImage];
|
||||
|
||||
CIFilter *filter = [CIFilter filterWithName:@"CIPixellate"];
|
||||
[filter setValue:ciImage forKey:kCIInputImageKey];
|
||||
[filter setValue:@(MOSAIC_SCALE_VALUE) forKey:kCIInputScaleKey];
|
||||
|
||||
CIImage *outputImage = filter.outputImage;
|
||||
CIContext *context = [CIContext contextWithOptions:nil];
|
||||
CGImageRef cgImage = [context createCGImage:outputImage fromRect:outputImage.extent];
|
||||
return [UIImage imageWithCGImage:cgImage];
|
||||
}
|
||||
|
||||
- (void)safeRemoveShapeLayer:(CAShapeLayer *)layer {
|
||||
if (!layer) return;
|
||||
|
||||
[layer removeAllAnimations];
|
||||
layer.delegate = nil;
|
||||
layer.path = NULL;
|
||||
layer.fillColor = NULL;
|
||||
layer.strokeColor = NULL;
|
||||
[layer removeFromSuperlayer];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaPasterConfig.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol TUIMultimediaPasterSelectViewDelegate;
|
||||
|
||||
@interface TUIMultimediaPasterSelectView : UIView
|
||||
@property(nonatomic) TUIMultimediaPasterConfig *config;
|
||||
@property(weak, nullable, nonatomic) id<TUIMultimediaPasterSelectViewDelegate> delegate;
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame;
|
||||
@end
|
||||
|
||||
@protocol TUIMultimediaPasterSelectViewDelegate <NSObject>
|
||||
- (void)onPasterSelected:(UIImage *)image;
|
||||
- (void)pasterSelectView:(TUIMultimediaPasterSelectView *)v needAddCustomPaster:(TUIMultimediaPasterGroupConfig *)group completeCallback:(void (^)(void))callback;
|
||||
- (void)pasterSelectView:(TUIMultimediaPasterSelectView *)v
|
||||
needDeleteCustomPasterInGroup:(TUIMultimediaPasterGroupConfig *)group
|
||||
index:(NSInteger)index
|
||||
completeCallback:(void (^)(BOOL deleted))callback;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,168 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import "TUIMultimediaPasterSelectView.h"
|
||||
#import <Masonry/Masonry.h>
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
#import "TUIMultimediaPlugin/NSArray+Functional.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaCommon.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaImageCell.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaTabPanel.h"
|
||||
|
||||
static const CGFloat ItemInsect = 10;
|
||||
static const CGFloat ItemCountPerLine = 5;
|
||||
static const CGFloat ItemCountPerColumn = 3;
|
||||
|
||||
@interface TUIMultimediaPasterSelectView () <UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout, TUIMultimediaTabPanelDelegate> {
|
||||
TUIMultimediaTabPanel *_tabPanel;
|
||||
UILongPressGestureRecognizer *_longPressRec;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIMultimediaPasterSelectView
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self != nil) {
|
||||
_config = [[TUIMultimediaPasterConfig alloc] init];
|
||||
[self initUI];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)reloadTabPanel {
|
||||
for (TUIMultimediaTabPanelTab *tab in _tabPanel.tabs) {
|
||||
if (![tab.view isKindOfClass:UICollectionView.class]) continue;
|
||||
UICollectionView *v = (UICollectionView *)tab.view;
|
||||
[v reloadData];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
for (TUIMultimediaTabPanelTab *tab in _tabPanel.tabs) {
|
||||
if (![tab.view isKindOfClass:UICollectionView.class]) continue;
|
||||
UICollectionView *v = (UICollectionView *)tab.view;
|
||||
CGFloat len = (self.bounds.size.width - ItemInsect * (ItemCountPerLine - 1)) / ItemCountPerLine;
|
||||
[v mas_updateConstraints:^(MASConstraintMaker *make) {
|
||||
make.width.equalTo(self);
|
||||
make.height.mas_equalTo(len * ItemCountPerColumn + ItemInsect * (ItemCountPerColumn - 1));
|
||||
}];
|
||||
[v reloadData];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - UI init
|
||||
|
||||
- (void)initUI {
|
||||
_tabPanel = [[TUIMultimediaTabPanel alloc] init];
|
||||
_tabPanel.backgroundColor = TUIMultimediaPluginDynamicColor(@"editor_popup_view_bg_color", @"#000000BF");
|
||||
_tabPanel.delegate = self;
|
||||
[self addSubview:_tabPanel];
|
||||
|
||||
[_tabPanel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.edges.equalTo(self);
|
||||
}];
|
||||
|
||||
_longPressRec = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(onCollectionViewLongPress:)];
|
||||
}
|
||||
|
||||
#pragma mark - UICollectionViewDataSource protocol
|
||||
- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
TUIMultimediaImageCell *cell = (TUIMultimediaImageCell *)[collectionView dequeueReusableCellWithReuseIdentifier:TUIMultimediaImageCell.reuseIdentifier forIndexPath:indexPath];
|
||||
TUIMultimediaPasterGroupConfig *config = _config.groups[collectionView.tag];
|
||||
cell.image = [config.itemList[indexPath.item] loadIcon];
|
||||
return cell;
|
||||
}
|
||||
|
||||
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
|
||||
TUIMultimediaPasterGroupConfig *config = _config.groups[collectionView.tag];
|
||||
return config.itemList.count;
|
||||
}
|
||||
|
||||
#pragma mark - UICollectionViewDelegate protocol
|
||||
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
TUIMultimediaPasterGroupConfig *config = _config.groups[collectionView.tag];
|
||||
TUIMultimediaPasterItemConfig *item = config.itemList[indexPath.item];
|
||||
if (item.isAddButton) {
|
||||
[_delegate pasterSelectView:self
|
||||
needAddCustomPaster:config
|
||||
completeCallback:^{
|
||||
[self reloadTabPanel];
|
||||
}];
|
||||
return;
|
||||
}
|
||||
[_delegate onPasterSelected:[item loadImage]];
|
||||
}
|
||||
|
||||
- (CGSize)collectionView:(UICollectionView *)collectionView
|
||||
layout:(UICollectionViewLayout *)collectionViewLayout
|
||||
sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
CGFloat len = (self.bounds.size.width - ItemInsect * (ItemCountPerLine + 1)) / ItemCountPerLine;
|
||||
return CGSizeMake(len, len);
|
||||
}
|
||||
|
||||
- (CGFloat)collectionView:(UICollectionView *)collectionView
|
||||
layout:(UICollectionViewLayout *)collectionViewLayout
|
||||
minimumLineSpacingForSectionAtIndex:(NSInteger)section {
|
||||
return ItemInsect;
|
||||
}
|
||||
- (CGFloat)collectionView:(UICollectionView *)collectionView
|
||||
layout:(UICollectionViewLayout *)collectionViewLayout
|
||||
minimumInteritemSpacingForSectionAtIndex:(NSInteger)section {
|
||||
return ItemInsect;
|
||||
}
|
||||
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView
|
||||
layout:(UICollectionViewLayout *)collectionViewLayout
|
||||
insetForSectionAtIndex:(NSInteger)section {
|
||||
return UIEdgeInsetsMake(ItemInsect, ItemInsect, 0, ItemInsect);
|
||||
}
|
||||
|
||||
#pragma mark - Actions
|
||||
- (void)onCollectionViewLongPress:(UILongPressGestureRecognizer *)rec {
|
||||
UICollectionView *collectionView = (UICollectionView *)rec.view;
|
||||
CGPoint p = [rec locationInView:collectionView];
|
||||
NSIndexPath *indexPath = [collectionView indexPathForItemAtPoint:p];
|
||||
if (indexPath == nil) {
|
||||
return;
|
||||
}
|
||||
TUIMultimediaPasterGroupConfig *config = _config.groups[_tabPanel.selectedIndex];
|
||||
TUIMultimediaPasterItemConfig *item = config.itemList[indexPath.item];
|
||||
if (!item.isUserAdded) {
|
||||
return;
|
||||
}
|
||||
[_delegate pasterSelectView:self
|
||||
needDeleteCustomPasterInGroup:_config.groups[_tabPanel.selectedIndex]
|
||||
index:indexPath.item
|
||||
completeCallback:^(BOOL deleted) {
|
||||
if (deleted) {
|
||||
[collectionView reloadData];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - TUIMultimediaTabPanelDelegate
|
||||
- (void)tabPanel:(TUIMultimediaTabPanel *)panel selectedIndexChanged:(NSInteger)selectedIndex {
|
||||
[panel.tabs[selectedIndex].view addGestureRecognizer:_longPressRec];
|
||||
}
|
||||
|
||||
#pragma mark - Properties
|
||||
- (void)setConfig:(TUIMultimediaPasterConfig *)config {
|
||||
_config = config;
|
||||
_tabPanel.tabs = [_config.groups tui_multimedia_mapWithIndex:^id(TUIMultimediaPasterGroupConfig *config, NSUInteger idx) {
|
||||
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
|
||||
layout.scrollDirection = UICollectionViewScrollDirectionVertical;
|
||||
UICollectionView *cv = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
|
||||
cv.backgroundColor = UIColor.clearColor;
|
||||
cv.showsVerticalScrollIndicator = NO;
|
||||
cv.delegate = self;
|
||||
cv.dataSource = self;
|
||||
cv.tag = idx;
|
||||
[cv registerClass:TUIMultimediaImageCell.class forCellWithReuseIdentifier:TUIMultimediaImageCell.reuseIdentifier];
|
||||
NSString *localizedName = [TUIMultimediaCommon localizedStringForKey:config.name];
|
||||
UIImage *icon = [config loadIcon];
|
||||
return [[TUIMultimediaTabPanelTab alloc] initWithName:localizedName icon:icon view:cv];
|
||||
}];
|
||||
[_tabPanel.tabs.firstObject.view addGestureRecognizer:_longPressRec];
|
||||
}
|
||||
@end
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaSubtitleInfo.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol TUIMultimediaSubtitleEditViewDelegate;
|
||||
|
||||
@interface TUIMultimediaSubtitleEditView : UIView
|
||||
@property(nonatomic) TUIMultimediaSubtitleInfo *subtitleInfo;
|
||||
@property(weak, nullable, nonatomic) id<TUIMultimediaSubtitleEditViewDelegate> delegate;
|
||||
- (void)activate;
|
||||
@end
|
||||
|
||||
@protocol TUIMultimediaSubtitleEditViewDelegate <NSObject>
|
||||
- (void)subtitleEditViewOnOk:(TUIMultimediaSubtitleEditView *)view;
|
||||
- (void)subtitleEditViewOnCancel:(TUIMultimediaSubtitleEditView *)view;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,144 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import "TUIMultimediaSubtitleEditView.h"
|
||||
#import <Masonry/Masonry.h>
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaColorPanel.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaCommon.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaGeometry.h"
|
||||
|
||||
@interface TUIMultimediaSubtitleEditView () <TUIMultimediaColorPanelDelegate, UITextViewDelegate> {
|
||||
UIButton *_btnOk;
|
||||
UIButton *_btnCancel;
|
||||
UITextView *_textView;
|
||||
TUIMultimediaColorPanel *_colorPanel;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIMultimediaSubtitleEditView
|
||||
- (id)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self != nil) {
|
||||
_subtitleInfo = [[TUIMultimediaSubtitleInfo alloc] init];
|
||||
[self initUI];
|
||||
[NSNotificationCenter.defaultCenter addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
|
||||
[NSNotificationCenter.defaultCenter addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)activate {
|
||||
[_textView becomeFirstResponder];
|
||||
}
|
||||
|
||||
- (void)initUI {
|
||||
self.backgroundColor = TUIMultimediaPluginDynamicColor(@"editor_popup_view_bg_color", @"#000000BF");
|
||||
|
||||
_btnOk = [UIButton buttonWithType:UIButtonTypeSystem];
|
||||
[_btnOk setTitle:[TUIMultimediaCommon localizedStringForKey:@"ok"] forState:UIControlStateNormal];
|
||||
[_btnOk setTitleColor:UIColor.whiteColor forState:UIControlStateNormal];
|
||||
_btnOk.titleLabel.font = [UIFont systemFontOfSize:20];
|
||||
[_btnOk addTarget:self action:@selector(onOk) forControlEvents:UIControlEventTouchUpInside];
|
||||
[self addSubview:_btnOk];
|
||||
|
||||
_btnCancel = [UIButton buttonWithType:UIButtonTypeSystem];
|
||||
[_btnCancel setTitle:[TUIMultimediaCommon localizedStringForKey:@"cancel"] forState:UIControlStateNormal];
|
||||
[_btnCancel setTitleColor:UIColor.whiteColor forState:UIControlStateNormal];
|
||||
_btnCancel.titleLabel.font = [UIFont systemFontOfSize:20];
|
||||
_btnCancel.titleLabel.textColor = UIColor.whiteColor;
|
||||
[_btnCancel addTarget:self action:@selector(onCancel) forControlEvents:UIControlEventTouchUpInside];
|
||||
[self addSubview:_btnCancel];
|
||||
|
||||
_textView = [[UITextView alloc] init];
|
||||
_textView.backgroundColor = UIColor.clearColor;
|
||||
_textView.font = [UIFont systemFontOfSize:20];
|
||||
_textView.text = _subtitleInfo.text;
|
||||
_textView.textColor = _subtitleInfo.color;
|
||||
_textView.delegate = self;
|
||||
[self addSubview:_textView];
|
||||
|
||||
_colorPanel = [[TUIMultimediaColorPanel alloc] init];
|
||||
_colorPanel.delegate = self;
|
||||
[self addSubview:_colorPanel];
|
||||
|
||||
[_btnOk mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(self.mas_safeAreaLayoutGuideTop);
|
||||
make.right.equalTo(self).inset(30);
|
||||
make.width.mas_greaterThanOrEqualTo(100);
|
||||
make.height.mas_greaterThanOrEqualTo(50);
|
||||
}];
|
||||
[_btnCancel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(self.mas_safeAreaLayoutGuideTop);
|
||||
make.left.equalTo(self).inset(30);
|
||||
make.width.mas_greaterThanOrEqualTo(100);
|
||||
make.height.mas_greaterThanOrEqualTo(50);
|
||||
}];
|
||||
[_textView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.right.equalTo(self).inset(50);
|
||||
make.top.equalTo(_btnOk.mas_bottom).inset(50);
|
||||
make.bottom.equalTo(_colorPanel.mas_top).inset(10);
|
||||
}];
|
||||
[_colorPanel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.right.equalTo(self).inset(10);
|
||||
make.height.mas_equalTo(32);
|
||||
make.bottom.equalTo(self);
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - Actions
|
||||
- (void)onOk {
|
||||
NSMutableString *wrappedText = [NSMutableString string];
|
||||
NSString *text = _textView.text;
|
||||
NSLayoutManager *layoutManager = _textView.layoutManager;
|
||||
NSUInteger numberOfLines, index;
|
||||
NSUInteger numberOfGlyphs = [layoutManager numberOfGlyphs];
|
||||
BOOL lastLineBreak = NO;
|
||||
for (numberOfLines = 0, index = 0; index < numberOfGlyphs; numberOfLines++) {
|
||||
NSRange lineRange;
|
||||
[layoutManager lineFragmentRectForGlyphAtIndex:index effectiveRange:&lineRange];
|
||||
// NSLog(@"TUIMultimedia Subtitle Line:%@", [text substringWithRange:lineRange]);
|
||||
index = NSMaxRange(lineRange);
|
||||
NSString *line = [text substringWithRange:lineRange];
|
||||
if (numberOfLines != 0 && !lastLineBreak) {
|
||||
[wrappedText appendString:@"\n"];
|
||||
}
|
||||
[wrappedText appendString:line];
|
||||
lastLineBreak = [line containsString:@"\n"];
|
||||
}
|
||||
_subtitleInfo.text = _textView.text;
|
||||
_subtitleInfo.wrappedText = wrappedText;
|
||||
[_delegate subtitleEditViewOnOk:self];
|
||||
}
|
||||
|
||||
- (void)onCancel {
|
||||
[_delegate subtitleEditViewOnCancel:self];
|
||||
}
|
||||
|
||||
- (void)keyboardWillShow:(NSNotification *)notification {
|
||||
CGRect rect = [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
|
||||
[_colorPanel mas_updateConstraints:^(MASConstraintMaker *make) {
|
||||
make.bottom.equalTo(self).offset(-CGRectGetHeight(rect));
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)keyboardWillHide:(NSNotification *)notification {
|
||||
[self onCancel];
|
||||
}
|
||||
|
||||
#pragma mark - UITextViewDelegate protocol
|
||||
- (void)onColorPanel:(TUIMultimediaColorPanel *)panel selectColor:(UIColor *)color {
|
||||
_subtitleInfo.color = color;
|
||||
_textView.textColor = color;
|
||||
}
|
||||
|
||||
#pragma mark - Setters
|
||||
- (void)setSubtitleInfo:(TUIMultimediaSubtitleInfo *)subtitleInfo {
|
||||
_subtitleInfo = subtitleInfo;
|
||||
_textView.textColor = _subtitleInfo.color;
|
||||
_textView.text = subtitleInfo.text;
|
||||
_colorPanel.selectedColor = _subtitleInfo.color;
|
||||
}
|
||||
|
||||
@end
|
||||
Reference in New Issue
Block a user