// 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