1// Copyright (C) 2024 The Android Open Source Project 2// 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 15import {raf} from '../core/raf_scheduler'; 16 17export enum OmniboxMode { 18 Search, 19 Query, 20 Command, 21 Prompt, 22} 23 24export interface PromptOption { 25 key: string; 26 displayName: string; 27} 28 29interface Prompt { 30 text: string; 31 options?: PromptOption[]; 32 resolve(result: string): void; 33 reject(): void; 34} 35 36const defaultMode = OmniboxMode.Search; 37 38export class OmniboxManager { 39 private _omniboxMode = defaultMode; 40 private _focusOmniboxNextRender = false; 41 private _pendingCursorPlacement?: number; 42 private _pendingPrompt?: Prompt; 43 private _text = ''; 44 private _omniboxSelectionIndex = 0; 45 46 get omniboxMode(): OmniboxMode { 47 return this._omniboxMode; 48 } 49 50 get pendingPrompt(): Prompt | undefined { 51 return this._pendingPrompt; 52 } 53 54 get text(): string { 55 return this._text; 56 } 57 58 get omniboxSelectionIndex(): number { 59 return this._omniboxSelectionIndex; 60 } 61 62 get focusOmniboxNextRender(): boolean { 63 return this._focusOmniboxNextRender; 64 } 65 66 get pendingCursorPlacement(): number | undefined { 67 return this._pendingCursorPlacement; 68 } 69 70 setText(value: string): void { 71 this._text = value; 72 } 73 74 setOmniboxSelectionIndex(index: number): void { 75 this._omniboxSelectionIndex = index; 76 } 77 78 focusOmnibox(cursorPlacement?: number): void { 79 this._focusOmniboxNextRender = true; 80 this._pendingCursorPlacement = cursorPlacement; 81 raf.scheduleFullRedraw(); 82 } 83 84 clearOmniboxFocusFlag(): void { 85 this._focusOmniboxNextRender = false; 86 this._pendingCursorPlacement = undefined; 87 } 88 89 setMode(mode: OmniboxMode): void { 90 this._omniboxMode = mode; 91 this._focusOmniboxNextRender = true; 92 this.resetOmniboxText(); 93 this.rejectPendingPrompt(); 94 raf.scheduleFullRedraw(); 95 } 96 97 // Start a prompt. If options are supplied, the user must pick one from the 98 // list, otherwise the input is free-form text. 99 prompt(text: string, options?: PromptOption[]): Promise<string> { 100 this._omniboxMode = OmniboxMode.Prompt; 101 this.resetOmniboxText(); 102 this.rejectPendingPrompt(); 103 104 const promise = new Promise<string>((resolve, reject) => { 105 this._pendingPrompt = { 106 text, 107 options, 108 resolve, 109 reject, 110 }; 111 }); 112 113 this._focusOmniboxNextRender = true; 114 raf.scheduleFullRedraw(); 115 116 return promise; 117 } 118 119 // Resolve the pending prompt with a value to return to the prompter. 120 resolvePrompt(value: string): void { 121 if (this._pendingPrompt) { 122 this._pendingPrompt.resolve(value); 123 this._pendingPrompt = undefined; 124 } 125 this.setMode(OmniboxMode.Search); 126 } 127 128 // Reject the prompt outright. Doing this will force the owner of the prompt 129 // promise to catch, so only do this when things go seriously wrong. 130 // Use |resolvePrompt(null)| to indicate cancellation. 131 rejectPrompt(): void { 132 if (this._pendingPrompt) { 133 this._pendingPrompt.reject(); 134 this._pendingPrompt = undefined; 135 } 136 this.setMode(OmniboxMode.Search); 137 } 138 139 reset(): void { 140 this.setMode(defaultMode); 141 this.resetOmniboxText(); 142 raf.scheduleFullRedraw(); 143 } 144 145 private rejectPendingPrompt() { 146 if (this._pendingPrompt) { 147 this._pendingPrompt.reject(); 148 this._pendingPrompt = undefined; 149 } 150 } 151 152 private resetOmniboxText() { 153 this._text = ''; 154 this._omniboxSelectionIndex = 0; 155 } 156} 157