1/* 2 * Copyright (c) 2023-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 { KeyCode } from '@ohos.multimodalInput.keyCode' 17 18export declare type SelectTitleBarMenuItem = { 19 value: ResourceStr 20 isEnabled: boolean 21 action?: () => void 22} 23 24const PUBLIC_MORE = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMAAAADACAY' + 25 'AAABS3GwHAAAAIGNIUk0AAHomAACAhAAA+gAAAIDoAAB1MAAA6mAAADqYAAAXcJy6UTwAAAAEZ0FNQQAAsY58+1GTAAA' + 26 'AAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAAOxAAADsQBlSsOGwAABEZJREFUeNrt3D1rFFEUBuA' + 27 'xhmAhFlYpUohYiYWFRcAmKAhWK2pjo1iKf8BCMIKFf8BarCyMhVj4VZhGSKEg2FqJyCKWIhYWnstMINgYsh+cmfs88BI' + 28 'Cydxw7jmzu2HvNg0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADBN+3r6dx+LXIqsRpa7FF8j48hm5Fn3Peo9mAEYRdY' + 29 'jJ3f582Vj7nZfUe/eDsCRyMPI2h5/fyNyI/JDT6v3Tvt7sBllE15ETkxwjeORi5G3ke/6W737MgBnI68jh6ZwrcORq5H' + 30 'nhkC9+zAA5YXXy8jBKV5zKXIu8jjyS7+rd+YBeNVtyrSVO9PRyBM9r94LSTfjWuTUDK9/eYIXeENUbb0zDsBi5PYc1rm' + 31 'j79U74wCszuih+F/ljrSi/+uud8YBGA10rayqrnfGAVgb6FpZVV3vjAOwPNC1sqq63hkHYGWga2VVdb0XKt/8Rf1fd70' + 32 'zDsB4jmt5u3Tl9a59AMb6v+56ZxyArYGulVXV9c44ABtzXOup/q+73hkH4N2cHio/Rj7r/7rrnXEAfkfuz2Gddb2v3ln' + 33 '/DfpgxneLzaY9xE3l9c46AH8iVyI/Z3Dt8nB/Xc+rd5H5QMy3yJemPVs6zY0edc9HUe/0Z4I/dQ/N5Vjd0oTXKp9QcKF' + 34 'pD2qj3r0YgO1NeRM507TH6/bifeR85IMeV++d+vTBWOV9JDcjt5rdv6uw3M3uRR7pa/Xu+wBsOxA53bTnTP/3UX1b3fN' + 35 'Q1BsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKqyr6d/97HIpchqZLlL8TUyjmxGnnX' + 36 'fo96DGYBRZD1ycpc/XzbmbvcV9e7tAByJPIys7fH3NyI3Ij/0tHrvtL8Hm1E24UXkxATXOB65GHkb+a6/1bsvA3A28jp' + 37 'yaArXOhy5GnluCNS7DwNQXni9jByc4jWXIucijyO/9Lt6Zx6AV92mTFu5Mx2NPNHz6r2QdDOuRU7N8PqXJ3iBN0TV1jv' + 38 'jACxGbs9hnTv6Xr0zDsDqjB6K/1XuSCv6v+56ZxyA0UDXyqrqemccgLWBrpVV1fXOOADLA10rq6rrnXEAVga6VlZV13u' + 39 'h8s1f1P911zvjAIznuJa3S1de79oHYKz/6653xgHYGuhaWVVd74wDsDHHtZ7q/7rrnXEA3s3pofJj5LP+r7veGQfgd+T' + 40 '+HNZZ1/vqnfXfoA9mfLfYbNpD3FRe76wD8CdyJfJzBtcuD/fX9bx6F5kPxHyLfGnas6XT3OhR93wU9U5/JvhT99BcjtU' + 41 'tTXit8gkFF5r2oDbq3YsB2N6UN5EzTXu8bi/eR85HPuhx9d6pTx+MVd5HcjNyq9n9uwrL3exe5JG+Vu++D8C2A5HTTXv' + 42 'O9H8f1bfVPQ9FvQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgCn7C9HjBtwWfXpKAAAAAElFTkSuQmCC' 43 44const PUBLIC_BACK = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMAAAADACAY' + 45 'AAABS3GwHAAAAIGNIUk0AAHomAACAhAAA+gAAAIDoAAB1MAAA6mAAADqYAAAXcJy6UTwAAAAEZ0FNQQAAsY58+1GTAAAAA' + 46 'XNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAAOxAAADsQBlSsOGwAAA8VJREFUeNrt3LFLlHEYwPFXz0G' + 47 'iIZpEoikkwsFRIiK3gqCigxIC/4Kmhv6OoChouaGoqKCgCKducGh0cDAIamhwiCaHCIeelztpUszee/vl8/nAM3Vd8nufr' + 48 '+fddVYVAAAAAAAAAAAAAAAAAAAAAABQijFH0KhrMd2Y2ZitmNWYRzHLjkYAB9lUzMOYizv8eS/mZsymoypLxxE0svzvY07' + 49 'vcpu5mOmY145LAAdx+U/u4bZzwx+JPjq2cow7glaWf1vXsQkg6/JvPwoggJTLjwDSL/8nRyiAzN/5nzpGAWRd/n7MM0cpg' + 50 'IzLvx6z6CjL453gdpZ/IWbDcQrA8iMAy48ALD8CsPwIwPIjAMuPACw/ArD8CMDyIwDLjwAsPwKw/AjA8iMAy48ALD8CsPw' + 51 'IwPIjAMuPACw/ArD85A3A8pM2AMtP2gAsP2kDsPykDcDykzYAy0/aACw/aQOw/KQNwPKTNgDLT9oALD9pA7D8pA3A8pM2A' + 52 'MtP2gAsP2kDsPykDcDykzYAy0/aACw/aQOw/KQNwPKTNgDLT9oALD9pA7D8pA3A8pM2AMtP2gAsP2kDsPykDcDykzYAy0/' + 53 'aACw/aQOw/KQNwPLz3xlv6H4mYp5YfrI+AizF9BwnI/AlZi3mbsxy03feaeh+HsQcc60YgSMxMzE3YmZj3sX8LOlHoPoLn' + 54 'HedaEE35n5pzwF856dN9SPBpZICmHRNaNnlkgL46nrQsvmSAqhftlx1TWjR4ZICqPVcE1q0XloA96rBa7XQhl5pAWzFXKm' + 55 '8i8vo9WMeN3VnnQa/sO8xL2POxEy7Toxo+RdjNpu6w1F9HuBqNXi99lw1eKMM9utHzIeYV8MftbccCQAAAAAAsBdt/XLc+s' + 56 'Py9W+MmPqL+1iJuVA1+C4gdFr6d77FvK0GH2nb739lPR5zNuZ51eBnQhFAJQIEIAIEIAIEIAIEIAIEIAIEIAIEIAIEIAIE' + 57 'IAIEIAIEIAIEIAIEIAIEIAIEIAIEIAIEIAIEIAIEIAIEIAIEIAIEIAIEIAIEIAIEIAIEIAIEIAIEIAIEIAIEIAIEIAIEIAI' + 58 'EIAIEIAIEIAIEIAIEIAIEIAIEIAIEIAIEIAIEIAIEIAIEIAIEIAIE8M8jmBlGgABSRnAqZiXms+MUQNYIDnkUKMu4I/gj6z' + 59 'ELMRv7/PsnHKEAMkcw6fgEkDmCNUcngMwRvHFsngRnfWJcL/9tRyaAgxrB+ZijO9ymH7MUs+m4yjLmCBozEXMr5nr1+9We1' + 60 'ZgXMXccDwAAAAAAAAAAAAAAAAAAAAAAwO5+AfVgtqHKRnawAAAAAElFTkSuQmCC' 61 62@Component 63export struct SelectTitleBar { 64 @State selected: number = 0 65 66 options: Array<SelectOption> 67 menuItems: Array<SelectTitleBarMenuItem> 68 69 subtitle: ResourceStr 70 badgeValue: number 71 hidesBackButton: boolean 72 73 onSelected: ((index: number) => void) 74 75 private static readonly badgeSize = 16 76 private static readonly totalHeight = 56 77 private static readonly leftPadding = 24 78 private static readonly leftPaddingWithBack = 12 79 private static readonly rightPadding = 24 80 private static readonly badgePadding = 16 81 private static readonly subtitleLeftPadding = 8 82 83 @State selectMaxWidth: number = 0 84 85 build() { 86 Flex({ 87 justifyContent: FlexAlign.SpaceBetween, 88 alignItems: ItemAlign.Stretch 89 }) { 90 Row() { 91 if (!this.hidesBackButton) { 92 Navigator() { 93 ImageMenuItem({ item: { 94 value: PUBLIC_BACK, 95 isEnabled: true 96 } }) 97 } 98 } 99 Column() { 100 if (this.badgeValue !== undefined) { 101 Badge({ 102 count: this.badgeValue, 103 position: BadgePosition.Right, 104 style: { 105 badgeSize: SelectTitleBar.badgeSize, 106 badgeColor: $r('sys.color.ohos_id_color_emphasize'), 107 borderColor: $r('sys.color.ohos_id_color_emphasize'), 108 borderWidth: 0 109 } 110 }) { 111 Row() { 112 Select(this.options) 113 .selected(this.selected) 114 .value(this.selected < this.options.length ? this.options[this.selected].value.toString() : "") 115 .font({ size: $r('sys.float.ohos_id_text_size_headline8') }) 116 .fontColor($r('sys.color.ohos_id_color_titlebar_text')) 117 .onSelect(this.onSelected) 118 .constraintSize({ maxWidth: this.selectMaxWidth }) 119 } 120 .justifyContent(FlexAlign.Start) 121 .margin({ right: $r('sys.float.ohos_id_elements_margin_horizontal_l') }) 122 } 123 } else { 124 Row() { 125 Select(this.options) 126 .selected(this.selected) 127 .value(this.selected < this.options.length ? this.options[this.selected].value.toString() : "") 128 .font({ size: $r('sys.float.ohos_id_text_size_headline8') }) 129 .fontColor($r('sys.color.ohos_id_color_titlebar_text')) 130 .onSelect(this.onSelected) 131 .constraintSize({ maxWidth: this.selectMaxWidth }) 132 } 133 .justifyContent(FlexAlign.Start) 134 } 135 if (this.subtitle !== undefined) { 136 Row() { 137 Text(this.subtitle) 138 .fontSize($r('sys.float.ohos_id_text_size_over_line')) 139 .fontColor($r('sys.color.ohos_id_color_titlebar_subtitle_text')) 140 .maxLines(1) 141 .textOverflow({ overflow: TextOverflow.Ellipsis }) 142 .constraintSize({ maxWidth: this.selectMaxWidth }) 143 } 144 .justifyContent(FlexAlign.Start) 145 .margin({ left: SelectTitleBar.subtitleLeftPadding }) 146 } 147 } 148 .justifyContent(FlexAlign.Start) 149 .alignItems(HorizontalAlign.Start) 150 .constraintSize({ maxWidth: this.selectMaxWidth }) 151 } 152 .margin({ left: this.hidesBackButton ? $r('sys.float.ohos_id_max_padding_start') : $r('sys.float.ohos_id_default_padding_start') }) 153 if (this.menuItems !== undefined && this.menuItems.length > 0) { 154 CollapsibleMenuSection({ menuItems: this.menuItems }) 155 } 156 } 157 .width('100%') 158 .height(SelectTitleBar.totalHeight) 159 .backgroundColor($r('sys.color.ohos_id_color_background')) 160 .onAreaChange((_oldValue: Area, newValue: Area) => { 161 let newWidth = Number(newValue.width) 162 if (!this.hidesBackButton) { 163 newWidth -= ImageMenuItem.imageHotZoneWidth 164 newWidth += SelectTitleBar.leftPadding 165 newWidth -= SelectTitleBar.leftPaddingWithBack 166 } 167 if (this.menuItems !== undefined) { 168 let menusLength = this.menuItems.length 169 if (menusLength >= CollapsibleMenuSection.maxCountOfVisibleItems) { 170 newWidth -= ImageMenuItem.imageHotZoneWidth * CollapsibleMenuSection.maxCountOfVisibleItems 171 } else if (menusLength > 0) { 172 newWidth -= ImageMenuItem.imageHotZoneWidth * menusLength 173 } 174 } 175 if (this.badgeValue !== undefined) { 176 this.selectMaxWidth = newWidth - SelectTitleBar.badgeSize - SelectTitleBar.leftPadding - SelectTitleBar.rightPadding - SelectTitleBar.badgePadding 177 } else { 178 this.selectMaxWidth = newWidth - SelectTitleBar.leftPadding - SelectTitleBar.rightPadding 179 } 180 }) 181 } 182} 183 184@Component 185struct CollapsibleMenuSection { 186 menuItems: Array<SelectTitleBarMenuItem> 187 188 static readonly maxCountOfVisibleItems = 3 189 private static readonly focusPadding = 4 190 private static readonly marginsNum = 2 191 192 @State isPopupShown: boolean = false 193 194 @State isMoreIconOnFocus: boolean = false 195 @State isMoreIconOnHover: boolean = false 196 @State isMoreIconOnClick: boolean = false 197 198 getMoreIconFgColor() { 199 return this.isMoreIconOnClick 200 ? $r('sys.color.ohos_id_color_titlebar_icon_pressed') 201 : $r('sys.color.ohos_id_color_titlebar_icon') 202 } 203 204 getMoreIconBgColor() { 205 if (this.isMoreIconOnClick) { 206 return $r('sys.color.ohos_id_color_click_effect') 207 } else if (this.isMoreIconOnHover) { 208 return $r('sys.color.ohos_id_color_hover') 209 } else { 210 return Color.Transparent 211 } 212 } 213 214 build() { 215 Column() { 216 Row() { 217 if (this.menuItems.length <= CollapsibleMenuSection.maxCountOfVisibleItems) { 218 ForEach(this.menuItems, (item) => { 219 ImageMenuItem({ item: item }) 220 }) 221 } else { 222 ForEach(this.menuItems.slice(0, CollapsibleMenuSection.maxCountOfVisibleItems - 1), (item) => { 223 ImageMenuItem({ item: item }) 224 }) 225 226 Row() { 227 Image(PUBLIC_MORE) 228 .width(ImageMenuItem.imageSize) 229 .height(ImageMenuItem.imageSize) 230 .focusable(true) 231 } 232 .width(ImageMenuItem.imageHotZoneWidth) 233 .height(ImageMenuItem.imageHotZoneWidth) 234 .borderRadius(ImageMenuItem.buttonBorderRadius) 235 .foregroundColor(this.getMoreIconFgColor()) 236 .backgroundColor(this.getMoreIconBgColor()) 237 .justifyContent(FlexAlign.Center) 238 .border(this.isMoreIconOnFocus ? 239 { width: ImageMenuItem.focusBorderWidth, 240 color: $r('sys.color.ohos_id_color_emphasize'), 241 style: BorderStyle.Solid 242 } : { width: 0 }) 243 .onFocus(() => this.isMoreIconOnFocus = true) 244 .onBlur(() => this.isMoreIconOnFocus = false) 245 .onHover((isOn) => this.isMoreIconOnHover = isOn) 246 .onKeyEvent((event) => { 247 if (event.keyCode !== KeyCode.KEYCODE_ENTER && event.keyCode !== KeyCode.KEYCODE_SPACE) { 248 return 249 } 250 if (event.type === KeyType.Down) { 251 this.isMoreIconOnClick = true 252 } 253 if (event.type === KeyType.Up) { 254 this.isMoreIconOnClick = false 255 } 256 }) 257 .onTouch((event) => { 258 if (event.type === TouchType.Down) { 259 this.isMoreIconOnClick = true 260 } 261 if (event.type === TouchType.Up) { 262 this.isMoreIconOnClick = false 263 } 264 }) 265 .onClick(() => this.isPopupShown = true) 266 .bindPopup(this.isPopupShown, { 267 builder: this.popupBuilder, 268 placement: Placement.Bottom, 269 popupColor: Color.White, 270 enableArrow: false, 271 onStateChange: (e) => this.isPopupShown = e.isVisible 272 }) 273 } 274 } 275 } 276 .height('100%') 277 .margin({ right: $r('sys.float.ohos_id_default_padding_end') }) 278 .justifyContent(FlexAlign.Center) 279 } 280 281 @Builder popupBuilder() { 282 Column() { 283 ForEach(this.menuItems.slice(CollapsibleMenuSection.maxCountOfVisibleItems - 1, this.menuItems.length), (item, _index?) => { 284 ImageMenuItem({ item: item }) 285 }) 286 } 287 .width(ImageMenuItem.imageHotZoneWidth + CollapsibleMenuSection.focusPadding * CollapsibleMenuSection.marginsNum) 288 .margin({ top: CollapsibleMenuSection.focusPadding, bottom: CollapsibleMenuSection.focusPadding }) 289 } 290} 291 292@Component 293struct ImageMenuItem { 294 item: SelectTitleBarMenuItem 295 296 static readonly imageSize = 24 297 static readonly imageHotZoneWidth = 48 298 static readonly buttonBorderRadius = 8 299 static readonly focusBorderWidth = 2 300 static readonly disabledImageOpacity = 0.4 301 302 @State isOnFocus: boolean = false 303 @State isOnHover: boolean = false 304 @State isOnClick: boolean = false 305 306 getFgColor() { 307 return this.isOnClick 308 ? $r('sys.color.ohos_id_color_titlebar_icon_pressed') 309 : $r('sys.color.ohos_id_color_titlebar_icon') 310 } 311 312 getBgColor() { 313 if (this.isOnClick) { 314 return $r('sys.color.ohos_id_color_click_effect') 315 } else if (this.isOnHover) { 316 return $r('sys.color.ohos_id_color_hover') 317 } else { 318 return Color.Transparent 319 } 320 } 321 322 build() { 323 Row() { 324 Image(this.item.value) 325 .width(ImageMenuItem.imageSize) 326 .height(ImageMenuItem.imageSize) 327 .focusable(this.item.isEnabled) 328 } 329 .width(ImageMenuItem.imageHotZoneWidth) 330 .height(ImageMenuItem.imageHotZoneWidth) 331 .borderRadius(ImageMenuItem.buttonBorderRadius) 332 .foregroundColor(this.getFgColor()) 333 .backgroundColor(this.getBgColor()) 334 .justifyContent(FlexAlign.Center) 335 .opacity(this.item.isEnabled ? 1 : ImageMenuItem.disabledImageOpacity) 336 .border(this.isOnFocus ? 337 { width: ImageMenuItem.focusBorderWidth, 338 color: $r('sys.color.ohos_id_color_emphasize'), 339 style: BorderStyle.Solid 340 } : { width: 0 }) 341 .onFocus(() => { 342 if (!this.item.isEnabled) { 343 return 344 } 345 this.isOnFocus = true 346 }) 347 .onBlur(() => this.isOnFocus = false) 348 .onHover((isOn) => { 349 if (!this.item.isEnabled) { 350 return 351 } 352 this.isOnHover = isOn 353 }) 354 .onKeyEvent((event) => { 355 if (!this.item.isEnabled) { 356 return 357 } 358 if (event.keyCode !== KeyCode.KEYCODE_ENTER && event.keyCode !== KeyCode.KEYCODE_SPACE) { 359 return 360 } 361 if (event.type === KeyType.Down) { 362 this.isOnClick = true 363 } 364 if (event.type === KeyType.Up) { 365 this.isOnClick = false 366 } 367 }) 368 .onTouch((event) => { 369 if (!this.item.isEnabled) { 370 return 371 } 372 if (event.type === TouchType.Down) { 373 this.isOnClick = true 374 } 375 if (event.type === TouchType.Up) { 376 this.isOnClick = false 377 } 378 }) 379 .onClick(() => this.item.isEnabled && this.item.action && this.item.action()) 380 } 381} 382 383export default { SelectTitleBar }