blob: 5208925ec7340e85bc1f8e38cb167b6bebcab65a [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterTextInputModel.h"
static NSString* const kTextAffinityDownstream = @"TextAffinity.downstream";
static NSString* const kTextAffinityUpstream = @"TextAffinity.upstream";
static NSString* const kTextInputAction = @"inputAction";
static NSString* const kTextInputType = @"inputType";
static NSString* const kTextInputTypeName = @"name";
static NSString* const kSelectionBaseKey = @"selectionBase";
static NSString* const kSelectionExtentKey = @"selectionExtent";
static NSString* const kSelectionAffinityKey = @"selectionAffinity";
static NSString* const kSelectionIsDirectionalKey = @"selectionIsDirectional";
static NSString* const kComposingBaseKey = @"composingBase";
static NSString* const kComposingExtentKey = @"composingExtent";
static NSString* const kTextKey = @"text";
/**
* These three static methods are necessary because Cocoa and Flutter have different idioms for
* signaling an empty range: Flutter uses {-1, -1} while Cocoa uses {NSNotFound, 0}. Also,
* despite the name, the "extent" fields are actually end indices, not lengths.
*/
/**
* Updates a range given base and extent fields.
*/
static NSRange UpdateRangeFromBaseExtent(NSNumber* base, NSNumber* extent, NSRange range) {
if (base == nil || extent == nil) {
return range;
}
if (base.intValue == -1 && extent.intValue == -1) {
range.location = NSNotFound;
range.length = 0;
} else {
range.location = [base unsignedLongValue];
range.length = [extent unsignedLongValue] - range.location;
}
return range;
}
/**
* Returns the appropriate base field for a given range.
*/
static long GetBaseForRange(NSRange range) {
if (range.location == NSNotFound) {
return -1;
}
return range.location;
}
/**
* Returns the appropriate extent field for a given range.
*/
static long GetExtentForRange(NSRange range) {
if (range.location == NSNotFound) {
return -1;
}
return range.location + range.length;
}
@implementation FlutterTextInputModel
- (instancetype)initWithClientID:(NSNumber*)clientID configuration:(NSDictionary*)config {
self = [super init];
if (self != nil) {
_clientID = clientID;
_inputAction = config[kTextInputAction];
// There's more information that can be used from this dictionary.
// Add more as needed.
NSDictionary* inputTypeInfo = config[kTextInputType];
_inputType = inputTypeInfo[kTextInputTypeName];
if (!_clientID || !_inputAction || !_inputType) {
NSLog(@"Missing arguments for %@ init.", [self class]);
return nil;
}
_text = [[NSMutableString alloc] init];
_selectedRange = NSMakeRange(NSNotFound, 0);
_markedRange = NSMakeRange(NSNotFound, 0);
_textAffinity = FlutterTextAffinityUpstream;
}
return self;
}
- (NSDictionary*)state {
NSString* const textAffinity = (_textAffinity == FlutterTextAffinityUpstream)
? kTextAffinityUpstream
: kTextAffinityDownstream;
NSDictionary* state = @{
kSelectionBaseKey : @(GetBaseForRange(_selectedRange)),
kSelectionExtentKey : @(GetExtentForRange(_selectedRange)),
kSelectionAffinityKey : textAffinity,
kSelectionIsDirectionalKey : @NO,
kComposingBaseKey : @(GetBaseForRange(_markedRange)),
kComposingExtentKey : @(GetExtentForRange(_markedRange)),
kTextKey : _text
};
return state;
}
- (void)setState:(NSDictionary*)state {
if (state == nil)
return;
_selectedRange = UpdateRangeFromBaseExtent(state[kSelectionBaseKey], state[kSelectionExtentKey],
_selectedRange);
NSString* selectionAffinity = state[kSelectionAffinityKey];
if (selectionAffinity != nil) {
_textAffinity = [selectionAffinity isEqualToString:kTextAffinityUpstream]
? FlutterTextAffinityUpstream
: FlutterTextAffinityDownstream;
}
_markedRange =
UpdateRangeFromBaseExtent(state[kComposingBaseKey], state[kComposingExtentKey], _markedRange);
NSString* text = state[kTextKey];
if (text != nil)
[_text setString:text];
}
@end