Files
keyboard/CustomKeyboard/View/KBKeyboardView/KBKeyboardLayoutEngine.m
2026-03-04 12:54:57 +08:00

246 lines
8.8 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.
//
// KBKeyboardLayoutEngine.m
// CustomKeyboard
//
#import "KBKeyboardLayoutEngine.h"
#import "KBKeyboardLayoutConfig.h"
#import "KBKeyboardKeyFactory.h"
#import "KBKey.h"
#import "KBConfig.h"
@interface KBKeyboardLayoutEngine ()
@property (nonatomic, strong) KBKeyboardLayoutConfig *layoutConfig;
@end
@implementation KBKeyboardLayoutEngine
- (instancetype)initWithLayoutConfig:(KBKeyboardLayoutConfig *)layoutConfig {
self = [super init];
if (self) {
_layoutConfig = layoutConfig;
}
return self;
}
- (CGFloat)rowSpacingForLayout:(KBKeyboardLayout *)layout {
KBKeyboardLayoutConfig *config = self.layoutConfig;
NSNumber *layoutSpacing = layout.rowSpacing;
return [self kb_metricValue:layoutSpacing fallback:config.metrics.rowSpacing defaultValue:8.0];
}
- (CGFloat)topInsetForLayout:(KBKeyboardLayout *)layout {
KBKeyboardLayoutConfig *config = self.layoutConfig;
NSNumber *layoutInset = layout.topInset;
return [self kb_metricValue:layoutInset fallback:config.metrics.topInset defaultValue:8.0];
}
- (CGFloat)bottomInsetForLayout:(KBKeyboardLayout *)layout {
KBKeyboardLayoutConfig *config = self.layoutConfig;
NSNumber *layoutInset = layout.bottomInset;
return [self kb_metricValue:layoutInset fallback:config.metrics.bottomInset defaultValue:6.0];
}
- (CGFloat)rowHeightForRow:(KBKeyboardRowConfig *)row {
KBKeyboardLayoutConfig *config = self.layoutConfig;
NSNumber *height = row.height ?: config.metrics.keyHeight;
CGFloat value = [self kb_numberValue:height defaultValue:40.0];
return [self kb_scaledValue:value];
}
- (CGFloat)gapForRow:(KBKeyboardRowConfig *)row {
KBKeyboardLayoutConfig *config = self.layoutConfig;
return [self kb_metricValue:row.gap fallback:config.metrics.gap defaultValue:5.0];
}
- (CGFloat)insetLeftForRow:(KBKeyboardRowConfig *)row {
KBKeyboardLayoutConfig *config = self.layoutConfig;
return [self kb_metricValue:row.insetLeft fallback:config.metrics.edgeInset defaultValue:0.0];
}
- (CGFloat)insetRightForRow:(KBKeyboardRowConfig *)row {
KBKeyboardLayoutConfig *config = self.layoutConfig;
return [self kb_metricValue:row.insetRight fallback:config.metrics.edgeInset defaultValue:0.0];
}
- (CGFloat)widthForItem:(KBKeyboardRowItem *)item key:(KBKey *)key {
CGFloat width = 0.0;
if (item.widthValue.doubleValue > 0.0) {
width = item.widthValue.doubleValue;
} else if (item.width.length > 0) {
if ([item.width.lowercaseString isEqualToString:@"flex"]) {
return 0.0;
}
width = [self kb_metricWidthForKey:item.width];
if (width <= 0.0) {
width = item.width.doubleValue;
}
}
if (width <= 0.0) {
KBKeyboardLayoutMetrics *m = self.layoutConfig.metrics;
if ([item.itemId hasPrefix:@"letter:"] ||
[item.itemId hasPrefix:@"digit:"] ||
[item.itemId hasPrefix:@"sym:"]) {
width = m.letterWidth.doubleValue;
} else if (key.type == KBKeyTypeReturn) {
width = m.sendWidth.doubleValue;
} else if (key.type == KBKeyTypeSpace) {
return 0.0;
} else {
width = m.controlWidth.doubleValue;
}
}
if (width <= 0.0) {
if ([item.itemId hasPrefix:@"letter:"] ||
[item.itemId hasPrefix:@"digit:"] ||
[item.itemId hasPrefix:@"sym:"]) {
width = 32.0;
} else if (key.type == KBKeyTypeReturn) {
width = 88.0;
} else if (key.type == KBKeyTypeSpace) {
return 0.0;
} else {
width = 41.0;
}
}
return width > 0.0 ? [self kb_scaledValue:width] : 0.0;
}
- (CGFloat)fontSizeForItem:(KBKeyboardRowItem *)item key:(KBKey *)key {
NSString *fontKey = nil;
if ([item.itemId hasPrefix:@"letter:"]) {
fontKey = @"letter";
} else if ([item.itemId hasPrefix:@"digit:"]) {
fontKey = @"digit";
} else if ([item.itemId hasPrefix:@"sym:"]) {
fontKey = @"symbol";
} else {
KBKeyboardKeyDef *def = [self.layoutConfig keyDefForIdentifier:item.itemId];
fontKey = def.font;
}
if (fontKey.length == 0) {
switch (key.type) {
case KBKeyTypeModeChange:
case KBKeyTypeSymbolsToggle:
fontKey = @"mode";
break;
case KBKeyTypeSpace:
fontKey = @"space";
break;
case KBKeyTypeReturn:
fontKey = @"send";
break;
default:
fontKey = @"symbol";
break;
}
}
return [self fontSizeForFontKey:fontKey];
}
- (CGFloat)fontSizeForFontKey:(NSString *)fontKey {
KBKeyboardLayoutFonts *fonts = self.layoutConfig.fonts;
CGFloat size = 0.0;
if ([fontKey isEqualToString:@"letter"]) { size = fonts.letter.doubleValue; }
else if ([fontKey isEqualToString:@"digit"]) { size = fonts.digit.doubleValue; }
else if ([fontKey isEqualToString:@"symbol"]) { size = fonts.symbol.doubleValue; }
else if ([fontKey isEqualToString:@"mode"]) { size = fonts.mode.doubleValue; }
else if ([fontKey isEqualToString:@"space"]) { size = fonts.space.doubleValue; }
else if ([fontKey isEqualToString:@"send"]) { size = fonts.send.doubleValue; }
if (size <= 0.0) { size = 18.0; }
return [self kb_scaledValue:size];
}
/// 计算跨行统一字符键宽度:遍历每行各自的 insets/gap/非字符键宽度,
/// 取各行可用字符键宽度的最小值,确保所有行都能容纳。
/// 当各行有效宽度相同时返回 0无需统一
- (CGFloat)calculateUniformCharKeyWidthForRows:(NSArray<KBKeyboardRowConfig *> *)rows
keyFactory:(KBKeyboardKeyFactory *)keyFactory
shiftOn:(BOOL)shiftOn {
CGFloat minWidth = CGFLOAT_MAX;
CGFloat maxWidth = 0.0;
BOOL hasCharRow = NO;
CGFloat containerWidth = KBScreenWidth();
for (KBKeyboardRowConfig *row in rows) {
if (row.segments) { continue; } // 跳过分段行
NSArray<KBKeyboardRowItem *> *items = [row resolvedItems];
NSUInteger charCount = 0;
CGFloat nonCharWidth = 0.0;
for (KBKeyboardRowItem *item in items) {
BOOL isChar = [item.itemId hasPrefix:@"letter:"] ||
[item.itemId hasPrefix:@"digit:"] ||
[item.itemId hasPrefix:@"sym:"];
if (isChar) {
charCount++;
} else {
KBKey *key = [keyFactory keyForItemId:item.itemId shiftOn:shiftOn];
CGFloat w = [self widthForItem:item key:key];
nonCharWidth += w;
}
}
if (charCount == 0) { continue; } // 跳过无字符键的行(如底部控制行)
hasCharRow = YES;
// 使用每行各自配置的 insets 和 gap
CGFloat gap = [self gapForRow:row];
CGFloat insetLeft = [self insetLeftForRow:row];
CGFloat insetRight = [self insetRightForRow:row];
CGFloat totalGaps = (items.count > 1) ? (items.count - 1) * gap : 0.0;
CGFloat available = containerWidth - insetLeft - insetRight - totalGaps - nonCharWidth;
CGFloat width = available / charCount;
if (width < minWidth) { minWidth = width; }
if (width > maxWidth) { maxWidth = width; }
}
if (!hasCharRow || minWidth <= 0.0 || minWidth >= CGFLOAT_MAX) { return 0.0; }
// 各行有效宽度相同时无需统一
if (fabs(maxWidth - minWidth) < 0.5) { return 0.0; }
return minWidth;
}
#pragma mark - Helpers
- (CGFloat)kb_scaledValue:(CGFloat)designValue {
if (self.layoutConfig) {
return [self.layoutConfig scaledValue:designValue];
}
return KBFit(designValue);
}
- (CGFloat)kb_numberValue:(NSNumber *)value defaultValue:(CGFloat)defaultValue {
if ([value isKindOfClass:[NSNumber class]]) {
return value.doubleValue;
}
return defaultValue;
}
- (CGFloat)kb_metricValue:(NSNumber *)value fallback:(NSNumber *)fallback defaultValue:(CGFloat)defaultValue {
CGFloat v = [self kb_numberValue:value defaultValue:-1.0];
if (v < 0.0) {
v = [self kb_numberValue:fallback defaultValue:defaultValue];
}
if (v < 0.0) {
v = defaultValue;
}
return [self kb_scaledValue:v];
}
- (CGFloat)kb_metricWidthForKey:(NSString *)key {
KBKeyboardLayoutMetrics *m = self.layoutConfig.metrics;
if ([key isEqualToString:@"letterWidth"]) { return m.letterWidth.doubleValue; }
if ([key isEqualToString:@"controlWidth"]) { return m.controlWidth.doubleValue; }
if ([key isEqualToString:@"sendWidth"]) { return m.sendWidth.doubleValue; }
if ([key isEqualToString:@"symbolsWideWidth"]) { return m.symbolsWideWidth.doubleValue; }
if ([key isEqualToString:@"symbolsSideWidth"]) { return m.symbolsSideWidth.doubleValue; }
return 0.0;
}
@end