Files
keyboard/CustomKeyboard/View/KBKeyboardView/KBKeyboardLayoutEngine.m

246 lines
8.8 KiB
Mathematica
Raw Normal View History

2026-03-04 12:54:57 +08:00
//
// 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