1/* 2 * Copyright (c) 2023 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16import inputMethod from '@ohos.inputMethod'; 17import promptAction from '@ohos.promptAction'; 18import { logger } from '../utils/Logger'; 19import { inputAttribute } from '../utils/InputAttributeInit'; 20 21const LINE_HEIGHT: number = 20; 22const END_FLAG: number = 1000; 23const TAG: string = 'CustomInputText'; 24 25@Component 26export struct CustomInputText { 27 @State inputText: string = ''; 28 @State lastInput: string = ''; 29 @State selectInput: string = ''; 30 @State cursorInfo: inputMethod.CursorInfo = { top: 0, left: 0, width: 1, height: 25 }; 31 @State cursorLeft: number = 0; 32 @State cursorIndex: number = 0; 33 @State selectIndex: number = 0; 34 @State inputWidth: number = 320; 35 @Consume @Watch('isAttachedChange') isAttached: boolean; 36 @Consume @Watch('isOnChange') isOn: boolean; 37 @Consume @Watch('isShowChange') isShow: boolean; 38 @Consume @Watch('changeSelection') isChangeSelection: boolean; 39 @Consume enterKeyIndex: number; 40 @Consume inputTypeIndex: number; 41 @Consume selectStart: number; 42 @Consume selectEnd: number; 43 private inputController: inputMethod.InputMethodController = inputMethod.getController(); 44 private settings: RenderingContextSettings = new RenderingContextSettings(true); 45 private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings); 46 47 build() { 48 Stack() { 49 Row() { 50 Text(this.inputText) 51 .fontSize(16) 52 .fontFamily('sans-serif') 53 .id('inputText') 54 .lineHeight(LINE_HEIGHT) 55 .maxLines(1) 56 .constraintSize({ maxWidth: this.inputWidth }) 57 Text(this.selectInput) 58 .fontSize(16) 59 .fontFamily('sans-serif') 60 .lineHeight(LINE_HEIGHT) 61 .id('selectInput') 62 .maxLines(1) 63 .backgroundColor($r('app.color.select_color')) 64 Text(this.lastInput) 65 .fontSize(16) 66 .fontFamily('sans-serif') 67 .lineHeight(LINE_HEIGHT) 68 .id('lastInput') 69 .maxLines(1) 70 } 71 .width(this.inputWidth) 72 73 Text('') 74 .width(this.cursorInfo.width) 75 .height(this.cursorInfo.height) 76 .backgroundColor($r('app.color.cursor_color')) 77 .margin({ top: 5 }) 78 .position({ x: this.cursorLeft, y: 0 }) 79 .onAreaChange((oldArea: Area, newArea: Area) => { 80 if (newArea.globalPosition.x as number !== this.cursorInfo.left) { 81 this.cursorInfo.left = newArea.globalPosition.x as number; 82 this.cursorInfo.top = newArea.position.y as number; 83 this.cursorInfo.width = newArea.width as number; 84 this.cursorInfo.height = newArea.height as number; 85 logger.info(TAG, `cursor change: this.cursorInfo=${JSON.stringify(this.cursorInfo)}`); 86 this.inputController.updateCursor(this.cursorInfo); 87 } 88 }) 89 90 Canvas(this.context) 91 .width('100%') 92 .height(45) 93 .onReady(() => { 94 let px = vp2px(16); 95 this.context.font = px + 'px sans-serif'; 96 this.inputWidth = this.context.width; 97 }) 98 } 99 .id('customInputText') 100 .width('100%') 101 .borderRadius(20) 102 .backgroundColor($r('app.color.input_text_background')) 103 .padding({ left: 10, right: 10, top: 5, bottom: 5 }) 104 .height(45) 105 .onClick((event?: ClickEvent) => { 106 if (event) { 107 logger.info(TAG, `click event= ${JSON.stringify(event)}`); 108 this.initTextInput(event); 109 } 110 }) 111 } 112 113 async initTextInput(event: ClickEvent): Promise<void> { 114 focusControl.requestFocus('customInputText'); 115 this.inputController.updateAttribute({ 116 textInputType: inputAttribute.getInputType(this.inputTypeIndex), 117 enterKeyType: inputAttribute.getEnterType(this.enterKeyIndex) 118 }) 119 await this.inputController.attach(false, { 120 inputAttribute: { 121 textInputType: inputAttribute.getInputType(this.inputTypeIndex), 122 enterKeyType: inputAttribute.getEnterType(this.enterKeyIndex) 123 } 124 }); 125 this.inputController.showTextInput(); 126 this.isAttached = true; 127 this.isShow = true; 128 this.isOn = true; 129 this.calculateCursor(event.x); 130 } 131 132 async isAttachedChange(): Promise<void> { 133 if (this.isAttached) { 134 focusControl.requestFocus('customInputText'); 135 await this.inputController.attach(false, { 136 inputAttribute: { 137 textInputType: inputAttribute.getInputType(this.inputTypeIndex), 138 enterKeyType: inputAttribute.getEnterType(this.enterKeyIndex) 139 } 140 }); 141 } else { 142 this.detach(); 143 } 144 } 145 146 isShowChange(): void { 147 if (this.isShow) { 148 inputMethod.getController().showTextInput(); 149 } else { 150 inputMethod.getController().hideTextInput(); 151 } 152 } 153 154 isOnChange(): void { 155 if (this.isOn) { 156 this.initListener(); 157 } else { 158 this.off(); 159 } 160 } 161 162 changeSelection(): void { 163 if (this.isChangeSelection) { 164 let message = this.inputText + this.selectInput + this.lastInput; 165 if (this.selectStart <= this.selectEnd) { 166 this.selectIndex = this.selectStart; 167 this.cursorIndex = this.selectEnd; 168 } 169 if (this.selectStart > this.selectEnd) { 170 this.selectIndex = this.selectEnd; 171 this.cursorIndex = this.selectStart; 172 } 173 if (this.cursorIndex > message.length) { 174 this.cursorIndex = message.length; 175 } 176 this.inputText = message.substring(0, this.selectIndex); 177 this.selectInput = message.substring(this.selectIndex, this.cursorIndex); 178 this.lastInput = message.substring(this.cursorIndex, message.length); 179 let cursorText = this.inputText + this.selectInput; 180 this.cursorLeft = this.context.measureText(cursorText).width; 181 this.isChangeSelection = false; 182 } 183 } 184 185 async detach(): Promise<void> { 186 logger.info(TAG, `detach`); 187 await this.off(); 188 this.isOn = false; 189 this.isShow = false; 190 this.inputController.detach(); 191 } 192 193 async off(): Promise<void> { 194 logger.info(TAG, `off`); 195 this.inputController.off('insertText'); 196 this.inputController.off('deleteLeft'); 197 this.inputController.off('deleteRight'); 198 this.inputController.off('moveCursor'); 199 this.inputController.off('selectByMovement'); 200 this.inputController.off('selectByRange'); 201 this.inputController.off('sendFunctionKey') 202 this.inputController.off('handleExtendAction'); 203 this.inputController.off('sendKeyboardStatus'); 204 } 205 206 initListener(): void { 207 this.inputController.on('insertText', (text: string) => { 208 logger.info(TAG, `insertText, text: ${text}`); 209 if ((this.cursorLeft + this.context.measureText(text).width + this.context.measureText(this.lastInput) 210 .width) > this.context.width) { 211 return; 212 } 213 this.inputText += text; 214 this.cursorIndex = this.inputText.length; 215 this.selectIndex = this.cursorIndex; 216 this.selectInput = ''; 217 this.cursorLeft = this.context.measureText(this.inputText).width; 218 }) 219 this.inputController.on('deleteRight', (length: number) => { 220 let message = this.inputText + this.selectInput + this.lastInput; 221 if (this.cursorIndex < message.length) { 222 this.selectIndex = this.cursorIndex; 223 this.selectInput = ''; 224 let deleteIndex = this.cursorIndex + length; 225 if (deleteIndex > message.length) { 226 deleteIndex = message.length; 227 } 228 this.lastInput = message.substring(this.cursorIndex + length, message.length); 229 } 230 }) 231 this.inputController.on('deleteLeft', (length: number) => { 232 this.inputText = this.inputText.substring(0, this.inputText.length - length); 233 this.cursorIndex = this.inputText.length; 234 this.selectIndex = this.cursorIndex; 235 this.cursorLeft = this.context.measureText(this.inputText).width; 236 }) 237 this.inputController.on('moveCursor', (direction: inputMethod.Direction) => { 238 logger.info(TAG, `Succeeded in moveCursor, direction: ${direction}`); 239 let message = this.inputText + this.selectInput + this.lastInput; 240 this.selectInput = ''; 241 if (direction === inputMethod.Direction.CURSOR_UP) { 242 this.cursorIndex = 0; 243 } 244 if (direction === inputMethod.Direction.CURSOR_DOWN) { 245 this.cursorIndex = message.length; 246 } 247 if (direction === inputMethod.Direction.CURSOR_LEFT) { 248 this.cursorIndex--; 249 } 250 if (direction === inputMethod.Direction.CURSOR_RIGHT) { 251 if (this.cursorIndex < message.length) { 252 this.cursorIndex++; 253 } 254 } 255 this.selectIndex = this.cursorIndex; 256 this.inputText = message.substring(0, this.cursorIndex); 257 this.lastInput = message.substring(this.cursorIndex, message.length); 258 this.cursorLeft = this.context.measureText(this.inputText).width; 259 }); 260 this.inputController.on('selectByMovement', (movement: inputMethod.Movement) => { 261 logger.info(TAG, `Succeeded in selectByMovement, direction: ${movement.direction}`); 262 let message = this.inputText + this.selectInput + this.lastInput; 263 if (movement.direction === inputMethod.Direction.CURSOR_UP) { 264 this.selectIndex = 0; 265 } 266 if (movement.direction === inputMethod.Direction.CURSOR_LEFT) { 267 if (this.selectIndex > 0) { 268 this.selectIndex--; 269 } 270 } 271 if (movement.direction === inputMethod.Direction.CURSOR_RIGHT) { 272 if (this.selectIndex < message.length) { 273 this.selectIndex++; 274 } 275 } 276 if (movement.direction === inputMethod.Direction.CURSOR_DOWN) { 277 this.selectIndex = message.length; 278 } 279 if (this.selectIndex > this.cursorIndex) { 280 this.inputText = message.substring(0, this.cursorIndex); 281 this.selectInput = message.substring(this.cursorIndex, this.selectIndex); 282 this.lastInput = message.substring(this.selectIndex, message.length); 283 } else { 284 this.inputText = message.substring(0, this.selectIndex); 285 this.selectInput = message.substring(this.selectIndex, this.cursorIndex); 286 this.lastInput = message.substring(this.cursorIndex, message.length); 287 } 288 }); 289 this.inputController.on('selectByRange', (range: inputMethod.Range) => { 290 logger.info(TAG, `selectByRange this.range: ${JSON.stringify(range)}`); 291 let message = this.inputText + this.selectInput + this.lastInput; 292 if (range.start === 0 && range.end === 0) { 293 this.cursorIndex = 0; 294 let message = this.inputText + this.selectInput + this.lastInput; 295 this.selectInput = ''; 296 this.selectIndex = this.cursorIndex; 297 this.inputText = message.substring(0, this.cursorIndex); 298 this.lastInput = message.substring(this.cursorIndex, message.length); 299 this.cursorLeft = this.context.measureText(this.inputText).width; 300 } else if (range.end > range.start) { 301 if (range.end === END_FLAG) { 302 this.lastInput = ''; 303 this.selectIndex = message.length; 304 this.inputText = message.substring(0, this.cursorIndex); 305 this.selectInput = message.substring(this.cursorIndex, this.selectIndex); 306 } else { 307 this.selectIndex = 0; 308 this.inputText = '' 309 this.selectInput = message.substring(0, this.cursorIndex); 310 this.lastInput = message.substring(this.cursorIndex, message.length); 311 } 312 } else { 313 this.cursorIndex = message.length; 314 this.selectIndex = this.cursorIndex; 315 this.inputText = message.substring(0, this.cursorIndex); 316 this.lastInput = message.substring(this.cursorIndex, message.length); 317 this.cursorLeft = this.context.measureText(this.inputText).width; 318 } 319 logger.info(TAG, `selectByRange this.selectInput: ${this.selectInput}`); 320 }) 321 this.inputController.on('sendFunctionKey', (enterKey: inputMethod.FunctionKey) => { 322 promptAction.showToast({ message: `enterKey Clicked ${enterKey.enterKeyType.toString()}`, bottom: 500 }); 323 }) 324 this.inputController.on('sendKeyboardStatus', (keyBoardStatus: inputMethod.KeyboardStatus) => { 325 logger.info(TAG, `sendKeyboardStatus keyBoardStatus: ${keyBoardStatus}`); 326 }); 327 this.inputController.on('handleExtendAction', (action: inputMethod.ExtendAction) => { 328 if (action === inputMethod.ExtendAction.SELECT_ALL) { 329 let message = this.inputText + this.selectInput + this.lastInput; 330 this.cursorIndex = message.length; 331 this.selectIndex = 0; 332 this.inputText = '' 333 this.selectInput = message.substring(0, this.cursorIndex); 334 this.lastInput = ''; 335 this.cursorLeft = this.context.measureText(this.selectInput).width; 336 } 337 }) 338 } 339 340 calculateCursor(x: number): void { 341 let message = this.inputText + this.selectInput + this.lastInput; 342 let charWidth = this.context.measureText(message).width / message.length; 343 this.cursorIndex = Math.floor(x / charWidth); 344 if (this.cursorIndex < 0) { 345 this.cursorIndex = 0; 346 this.inputText = ''; 347 this.lastInput = message; 348 } else if (this.cursorIndex > message.length) { 349 this.cursorIndex = message.length; 350 this.inputText = message; 351 this.lastInput = ''; 352 } else { 353 this.inputText = message.substring(0, this.cursorIndex); 354 this.lastInput = message.substring(this.cursorIndex, message.length); 355 } 356 this.selectIndex = this.cursorIndex; 357 this.selectInput = ''; 358 this.cursorLeft = this.context.measureText(message.substring(0, this.cursorIndex)).width; 359 } 360}