Files
keyboard/CustomKeyboard/View/KBToolBar.m
2025-12-19 21:36:11 +08:00

368 lines
14 KiB
Objective-C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// KBToolBar.m
// CustomKeyboard
//
// Created by Mac on 2025/10/28.
//
#import "KBToolBar.h"
#import "KBResponderUtils.h" // 查找 UIInputViewController用于系统切换输入法
#import "KBBackspaceUndoManager.h"
#import "KBSkinManager.h"
@interface KBToolBar ()
@property (nonatomic, strong) UIView *leftContainer;
@property (nonatomic, strong) NSArray<UIButton *> *leftButtonsInternal;
//@property (nonatomic, strong) UIButton *settingsButtonInternal;
@property (nonatomic, strong) UIButton *globeButtonInternal; // 可选:系统“切换输入法”键
@property (nonatomic, strong) UIButton *undoButtonInternal; // 右侧撤销删除
@property (nonatomic, assign) BOOL kbNeedsInputModeSwitchKey;
@property (nonatomic, assign) BOOL kbUndoVisible;
@end
@implementation KBToolBar
static NSString * const kKBAIKeyIdentifier = @"ai";
static const CGFloat kKBAIButtonWidth = 36.0f;
static const CGFloat kKBAIButtonHeight = 36.0f;
- (instancetype)initWithFrame:(CGRect)frame{
if (self = [super initWithFrame:frame]) {
self.backgroundColor = [UIColor clearColor];
_leftButtonTitles = @[@"AI"]; // 默认标题
[self setupUI];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(kb_undoStateChanged)
name:KBBackspaceUndoStateDidChangeNotification
object:nil];
}
return self;
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
#pragma mark - Public
- (NSArray<UIButton *> *)leftButtons {
return self.leftButtonsInternal;
}
- (UIButton *)undoButton {
return self.undoButtonInternal;
}
//- (UIButton *)settingsButton {
// return self.settingsButtonInternal;
//}
- (void)setLeftButtonTitles:(NSArray<NSString *> *)leftButtonTitles {
_leftButtonTitles = [leftButtonTitles copy];
// Update titles if buttons already exist
[self.leftButtonsInternal enumerateObjectsUsingBlock:^(UIButton * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if (idx < self.leftButtonTitles.count) {
[obj setTitle:self.leftButtonTitles[idx] forState:UIControlStateNormal];
}
}];
[self kb_updateAIButtonAppearance];
}
#pragma mark - 视图搭建
- (void)setupUI {
[self addSubview:self.leftContainer];
// [self addSubview:self.settingsButtonInternal];
[self addSubview:self.globeButtonInternal];
[self addSubview:self.undoButtonInternal];
// 右侧设置按钮
// [self.settingsButtonInternal mas_makeConstraints:^(MASConstraintMaker *make) {
// make.right.equalTo(self.mas_right).offset(-12);
// make.centerY.equalTo(self.mas_centerY);
// make.width.height.mas_equalTo(32);
// }];
// 左侧地球键(按需显示)
[self.globeButtonInternal mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.mas_left).offset(12);
make.centerY.equalTo(self.mas_centerY);
make.width.height.mas_equalTo(32);
}];
// 右侧撤销按钮
[self.undoButtonInternal mas_makeConstraints:^(MASConstraintMaker *make) {
make.right.equalTo(self.mas_right).offset(-12);
make.centerY.equalTo(self.mas_centerY);
make.height.mas_equalTo(32);
}];
[self kb_updateLeftContainerConstraints];
// 在左侧容器中创建按钮(数量 = 标题数量)
NSMutableArray<UIButton *> *buttons = [NSMutableArray arrayWithCapacity:_leftButtonTitles.count];
UIView *previous = nil;
for (NSInteger i = 0; i < _leftButtonTitles.count; i++) {
UIButton *btn = [self buildActionButtonAtIndex:i];
[self.leftContainer addSubview:btn];
[buttons addObject:btn];
[btn mas_makeConstraints:^(MASConstraintMaker *make) {
if (i == 0) {
make.centerY.equalTo(self.leftContainer);
make.width.mas_equalTo(kKBAIButtonWidth);
make.height.mas_equalTo(kKBAIButtonHeight);
} else {
make.top.bottom.equalTo(self.leftContainer);
}
if (previous) {
make.left.equalTo(previous.mas_right).offset(8);
} else {
make.left.equalTo(self.leftContainer.mas_left);
}
// 不再设置宽度约束,交给 intrinsicContentSize
}];
previous = btn;
}
// 可选:最后一个不要一定贴右边,避免被拉伸
// 如果你希望它最多到右边,可以这样:
if (previous) {
[previous mas_makeConstraints:^(MASConstraintMaker *make) {
make.right.lessThanOrEqualTo(self.leftContainer.mas_right);
}];
}
self.leftButtonsInternal = buttons.copy;
// 初始刷新地球键的可见性与事件绑定
[self kb_refreshGlobeVisibility];
[self kb_updateUndoVisibilityAnimated:NO];
[self kb_applyTheme];
}
- (UIButton *)buildActionButtonAtIndex:(NSInteger)idx {
UIButton *btn = [UIButton buttonWithType:UIButtonTypeSystem];
btn.layer.cornerRadius = 16;
btn.layer.masksToBounds = YES;
btn.backgroundColor = [UIColor colorWithWhite:1 alpha:0.9];
btn.titleLabel.font = [UIFont systemFontOfSize:14 weight:UIFontWeightMedium];
NSString *title = (idx < self.leftButtonTitles.count) ? self.leftButtonTitles[idx] : @"";
[btn setTitle:title forState:UIControlStateNormal];
[btn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
// 关键:左右各 10按钮宽度 = 文本宽度 + 20
btn.contentEdgeInsets = UIEdgeInsetsMake(0, 10, 0, 10);
[btn setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal];
[btn setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal];
btn.tag = idx;
[btn addTarget:self action:@selector(onLeftAction:) forControlEvents:UIControlEventTouchUpInside];
return btn;
}
#pragma mark - Theme
- (void)kb_applyTheme {
[self kb_updateAIButtonAppearance];
}
- (void)kb_updateAIButtonAppearance {
UIButton *aiButton = [self kb_aiButton];
if (!aiButton) { return; }
KBSkinManager *skinManager = [KBSkinManager shared];
UIImage *icon = [skinManager iconImageForKeyIdentifier:kKBAIKeyIdentifier caseVariant:0];
NSString *skinId = skinManager.current.skinId ?: @"";
BOOL usingDefaultSkin = (skinId.length == 0 || [skinId isEqualToString:@"default"]);
if (!icon && usingDefaultSkin) {
icon = [UIImage imageNamed:@"ai_key_icon"];
}
if (icon) {
[aiButton setBackgroundImage:icon forState:UIControlStateNormal];
[aiButton setBackgroundImage:icon forState:UIControlStateHighlighted];
[aiButton setBackgroundImage:icon forState:UIControlStateSelected];
[aiButton setTitle:@"" forState:UIControlStateNormal];
aiButton.backgroundColor = [UIColor clearColor];
aiButton.layer.cornerRadius = 0;
aiButton.layer.masksToBounds = NO;
aiButton.contentEdgeInsets = UIEdgeInsetsZero;
} else {
[aiButton setBackgroundImage:nil forState:UIControlStateNormal];
[aiButton setBackgroundImage:nil forState:UIControlStateHighlighted];
[aiButton setBackgroundImage:nil forState:UIControlStateSelected];
NSString *title = (self.leftButtonTitles.count > 0) ? self.leftButtonTitles[0] : @"AI";
[aiButton setTitle:title forState:UIControlStateNormal];
aiButton.backgroundColor = [UIColor colorWithWhite:1 alpha:0.9];
aiButton.layer.cornerRadius = 16;
aiButton.layer.masksToBounds = YES;
aiButton.contentEdgeInsets = UIEdgeInsetsMake(0, 10, 0, 10);
}
}
#pragma mark - Actions
- (void)onLeftAction:(UIButton *)sender {
if ([self.delegate respondsToSelector:@selector(toolBar:didTapActionAtIndex:)]) {
[self.delegate toolBar:self didTapActionAtIndex:sender.tag];
}
}
- (void)onSettings {
if ([self.delegate respondsToSelector:@selector(toolBarDidTapSettings:)]) {
[self.delegate toolBarDidTapSettings:self];
}
}
- (void)onUndo {
if ([self.delegate respondsToSelector:@selector(toolBarDidTapUndo:)]) {
[self.delegate toolBarDidTapUndo:self];
}
}
#pragma mark - Lazy
- (UIView *)leftContainer {
if (!_leftContainer) {
_leftContainer = [[UIView alloc] init];
_leftContainer.backgroundColor = [UIColor clearColor];
}
return _leftContainer;
}
//- (UIButton *)settingsButtonInternal {
// if (!_settingsButtonInternal) {
// _settingsButtonInternal = [UIButton buttonWithType:UIButtonTypeSystem];
// _settingsButtonInternal.layer.cornerRadius = 16;
// _settingsButtonInternal.layer.masksToBounds = YES;
// _settingsButtonInternal.backgroundColor = [UIColor colorWithWhite:1 alpha:0.9];
// [_settingsButtonInternal setTitle:@"⚙︎" forState:UIControlStateNormal]; // 简单的齿轮符号
// [_settingsButtonInternal setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
// [_settingsButtonInternal addTarget:self action:@selector(onSettings) forControlEvents:UIControlEventTouchUpInside];
// }
// return _settingsButtonInternal;
//}
- (UIButton *)globeButtonInternal {
if (!_globeButtonInternal) {
_globeButtonInternal = [UIButton buttonWithType:UIButtonTypeSystem];
_globeButtonInternal.layer.cornerRadius = 16;
_globeButtonInternal.layer.masksToBounds = YES;
_globeButtonInternal.backgroundColor = [UIColor colorWithWhite:1 alpha:0.9];
[_globeButtonInternal setTitle:@"🌐" forState:UIControlStateNormal];
[_globeButtonInternal setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
}
return _globeButtonInternal;
}
- (UIButton *)undoButtonInternal {
if (!_undoButtonInternal) {
_undoButtonInternal = [UIButton buttonWithType:UIButtonTypeSystem];
_undoButtonInternal.layer.cornerRadius = 16;
_undoButtonInternal.layer.masksToBounds = YES;
_undoButtonInternal.backgroundColor = [UIColor colorWithWhite:1 alpha:0.9];
_undoButtonInternal.titleLabel.font = [UIFont systemFontOfSize:14 weight:UIFontWeightMedium];
[_undoButtonInternal setTitle:@"撤销删除" forState:UIControlStateNormal];
[_undoButtonInternal setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
_undoButtonInternal.contentEdgeInsets = UIEdgeInsetsMake(0, 10, 0, 10);
_undoButtonInternal.hidden = YES;
_undoButtonInternal.alpha = 0.0;
[_undoButtonInternal addTarget:self action:@selector(onUndo) forControlEvents:UIControlEventTouchUpInside];
}
return _undoButtonInternal;
}
- (UIButton *)kb_aiButton {
if (self.leftButtonsInternal.count == 0) { return nil; }
return self.leftButtonsInternal[0];
}
#pragma mark - Globe (Input Mode Switch)
// 根据宿主是否已提供系统切换键,决定是否显示地球按钮;并绑定系统事件。
- (void)kb_refreshGlobeVisibility {
UIInputViewController *ivc = KBFindInputViewController(self);
BOOL needSwitchKey = YES;
if (ivc && [ivc respondsToSelector:@selector(needsInputModeSwitchKey)]) {
needSwitchKey = ivc.needsInputModeSwitchKey; // YES 表示自定义键盘需要提供切换键
}
self.globeButtonInternal.hidden = !needSwitchKey;
self.kbNeedsInputModeSwitchKey = needSwitchKey;
[self kb_updateLeftContainerConstraints];
// 绑定系统提供的输入法切换处理(点按切换、长按弹出列表)
// 仅在需要时绑定,避免多余的事件转发
[self.globeButtonInternal removeTarget:nil action:NULL forControlEvents:UIControlEventAllEvents];
if (needSwitchKey && ivc) {
SEL sel = NSSelectorFromString(@"handleInputModeListFromView:withEvent:");
if ([ivc respondsToSelector:sel]) {
[self.globeButtonInternal addTarget:ivc action:sel forControlEvents:UIControlEventAllTouchEvents];
} else {
// 回退:至少在点按时切换
[self.globeButtonInternal addTarget:ivc action:@selector(advanceToNextInputMode) forControlEvents:UIControlEventTouchUpInside];
}
}
}
- (void)kb_updateLeftContainerConstraints {
[self.leftContainer mas_remakeConstraints:^(MASConstraintMaker *make) {
if (self.kbNeedsInputModeSwitchKey) {
make.left.equalTo(self.globeButtonInternal.mas_right).offset(8);
} else {
make.left.equalTo(self.mas_left).offset(12);
}
if (self.kbUndoVisible) {
make.right.equalTo(self.undoButtonInternal.mas_left).offset(-8);
} else {
make.right.equalTo(self).offset(-12);
}
make.centerY.equalTo(self.mas_centerY);
make.height.mas_equalTo(MAX(32.0f, kKBAIButtonHeight));
}];
}
- (void)kb_undoStateChanged {
[self kb_updateUndoVisibilityAnimated:YES];
}
- (void)kb_updateUndoVisibilityAnimated:(BOOL)animated {
BOOL visible = [KBBackspaceUndoManager shared].hasUndo;
if (self.kbUndoVisible == visible) { return; }
self.kbUndoVisible = visible;
self.undoButtonInternal.hidden = NO;
[self kb_updateLeftContainerConstraints];
void (^changes)(void) = ^{
self.undoButtonInternal.alpha = visible ? 1.0 : 0.0;
[self layoutIfNeeded];
};
void (^finish)(BOOL) = ^(BOOL finished) {
self.undoButtonInternal.hidden = !visible;
};
if (animated) {
[UIView animateWithDuration:0.18
delay:0
options:UIViewAnimationOptionCurveEaseInOut
animations:changes
completion:finish];
} else {
changes();
finish(YES);
}
}
- (void)didMoveToWindow {
[super didMoveToWindow];
[self kb_refreshGlobeVisibility];
}
@end