1/* 2 * Copyright (c) 2023-2024 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 { BusinessError } from '@ohos.base'; 17import hilog from '@ohos.hilog'; 18import { KeyCode } from '@ohos.multimodalInput.keyCode'; 19import resourceManager from '@ohos.resourceManager'; 20import { Theme } from '@ohos.arkui.theme'; 21import { LengthMetrics } from '@ohos.arkui.node'; 22import common from '@ohos.app.ability.common'; 23import window from '@ohos.window'; 24import { Context } from '@ohos.arkui.UIContext'; 25import { SymbolGlyphModifier } from '@kit.ArkUI'; 26import { EnvironmentCallback } from '@kit.AbilityKit'; 27 28export enum EditableLeftIconType { 29 Back, 30 Cancel, 31} 32 33export declare interface EditableTitleBarMenuItem { 34 value: ResourceStr; 35 symbolStyle?: SymbolGlyphModifier; 36 isEnabled: boolean; 37 label?: ResourceStr; 38 action?: () => void; 39 accessibilityLevel?: string; 40 accessibilityText?: ResourceStr; 41 accessibilityDescription?: ResourceStr; 42 defaultFocus?: boolean; 43} 44 45export type EditableTitleBarItem = EditableTitleBarMenuItem; 46 47export declare interface EditableTitleBarOptions { 48 backgroundColor?: ResourceColor; 49 backgroundBlurStyle?: BlurStyle; 50 safeAreaTypes?: Array<SafeAreaType>; 51 safeAreaEdges?: Array<SafeAreaEdge>; 52} 53 54enum ItemType { 55 Image, 56 Icon, 57 LeftIcon, 58} 59 60const PUBLIC_CANCEL = $r('sys.symbol.xmark'); 61 62const PUBLIC_OK = $r('sys.symbol.checkmark'); 63 64const PUBLIC_BACK = $r('sys.symbol.chevron_backward'); 65 66const PUBLIC_IMAGE_BACK = $r('sys.media.ohos_ic_compnent_titlebar_back'); 67 68const DEFAULT_TITLE_BAR_HEIGHT = 56; 69const DEFAULT_TITLE_PADDING = 2; 70const MAX_LINE_ONE = 1; 71const MAX_LINES_TWO = 2; 72const MAX_MAIN_TITLE_PERCENT = 0.65; 73const MAX_SUB_TITLE_PERCENT = 0.35; 74const MIN_SUBTITLE_SIZE = '10.0vp'; 75const TEXT_EDITABLE_DIALOG = '18.3fp'; 76const IMAGE_SIZE = '64vp'; 77const MAX_DIALOG = '256vp'; 78const MIN_DIALOG = '216vp'; 79const SYMBOL_SIZE = '24vp'; 80const SYMBOL_TITLE_SIZE = '64vp'; 81const TITLE_VP: number = 20; 82const SUBTITLE_VP: number = 14; 83 84// 'sys.float.titlebar_title_tertiary_size' id,value: 20vp 85const TITLE_F: number = getNumberByResource(125831095, TITLE_VP); 86// 'sys.float.titlebar_subheader_size' id,value: 14vp 87const SUBTITLE_F: number = getNumberByResource(125831097, SUBTITLE_VP); 88 89const TITLE_F_VP: string = (TITLE_F > 0 ? TITLE_F : TITLE_VP) + 'vp'; 90const SUBTITLE_F_VP: string = (SUBTITLE_F > 0 ? SUBTITLE_F : SUBTITLE_VP) + 'vp'; 91 92class EditableTitleBarTheme { 93 public iconColor: ResourceColor = $r('sys.color.titlebar_icon_color'); 94 public iconBackgroundColor: ResourceColor = $r('sys.color.titlebar_icon_background_color'); 95 public iconBackgroundPressedColor: ResourceColor = $r('sys.color.titlebar_icon_background_pressed_color'); 96 public iconBackgroundHoverColor: ResourceColor = $r('sys.color.titlebar_icon_background_hover_color'); 97 public iconBackgroundFocusOutlineColor: ResourceColor = $r('sys.color.titlebar_icon_background_focus_outline_color'); 98 public titleColor: ResourceColor = $r('sys.color.titlebar_title_tertiary_color'); 99 public subTitleColor: ResourceColor = $r('sys.color.titlebar_subheader_color'); 100} 101 102class ButtonGestureModifier implements GestureModifier { 103 public static readonly longPressTime: number = 500; 104 public static readonly minFontSize: number = 1.75; 105 public fontSize: number = 1; 106 public controller: CustomDialogController | null = null; 107 108 constructor(controller: CustomDialogController | null) { 109 this.controller = controller; 110 } 111 112 applyGesture(event: UIGestureEvent): void { 113 if (this.fontSize >= ButtonGestureModifier.minFontSize) { 114 event.addGesture( 115 new LongPressGestureHandler({ repeat: false, duration: ButtonGestureModifier.longPressTime }) 116 .onAction(() => { 117 if (event) { 118 this.controller?.open(); 119 } 120 }) 121 .onActionEnd(() => { 122 this.controller?.close(); 123 }) 124 ) 125 } else { 126 event.clearGestures(); 127 } 128 } 129} 130 131@Component 132export struct EditableTitleBar { 133 leftIconStyle: EditableLeftIconType = EditableLeftIconType.Back; 134 title: ResourceStr = ''; 135 subtitle?: ResourceStr = ''; 136 isSaveIconRequired: boolean = true; 137 imageItem?: EditableTitleBarItem; 138 menuItems: Array<EditableTitleBarMenuItem> | undefined = undefined; 139 options: EditableTitleBarOptions = {}; 140 onSave?: () => void; 141 onCancel?: () => void; 142 constraintWidth: number = 0; 143 leftIconDefaultFocus?: boolean = false; 144 saveIconDefaultFocus?: boolean = false; 145 public static readonly maxCountOfExtraItems = 3; 146 public static readonly maxOtherCountOfExtraItems = 2; 147 public static readonly commonZero = 0; 148 public static readonly noneColor = '#00000000'; 149 // 'sys.float.titlebar_default_height' id,value: 56vp 150 public static readonly defaultHeight: number = getNumberByResource(125831115, DEFAULT_TITLE_BAR_HEIGHT); 151 // 'sys.float.padding_level1' id,value: 2vp 152 public static readonly defaultTitlePadding: number = getNumberByResource(125830920, DEFAULT_TITLE_PADDING); 153 public static readonly totalHeight: number = 154 EditableTitleBar.defaultHeight === EditableTitleBar.commonZero ? DEFAULT_TITLE_BAR_HEIGHT : 155 EditableTitleBar.defaultHeight; 156 static readonly titlePadding: number = 157 EditableTitleBar.defaultTitlePadding === EditableTitleBar.commonZero ? 158 DEFAULT_TITLE_PADDING : EditableTitleBar.defaultTitlePadding; 159 private static readonly maxMainTitleHeight = 160 (EditableTitleBar.totalHeight - EditableTitleBar.titlePadding) * MAX_MAIN_TITLE_PERCENT; 161 private static readonly maxSubTitleHeight = 162 (EditableTitleBar.totalHeight - EditableTitleBar.titlePadding) * MAX_SUB_TITLE_PERCENT; 163 private isFollowingSystemFontScale: boolean = false; 164 private maxFontScale: number = 1; 165 private systemFontScale?: number = 1; 166 @Provide editableTitleBarTheme: EditableTitleBarTheme = new EditableTitleBarTheme(); 167 @Prop contentMargin?: LocalizedMargin; 168 @State titleBarMargin: LocalizedMargin = { 169 start: LengthMetrics.resource($r('sys.float.margin_left')), 170 end: LengthMetrics.resource($r('sys.float.margin_right')), 171 } 172 @State fontSize: number = 1; 173 174 onWillApplyTheme(theme: Theme): void { 175 this.editableTitleBarTheme.iconColor = theme.colors.iconPrimary; 176 this.editableTitleBarTheme.titleColor = theme.colors.fontPrimary; 177 this.editableTitleBarTheme.subTitleColor = theme.colors.fontSecondary; 178 this.editableTitleBarTheme.iconBackgroundPressedColor = theme.colors.interactivePressed; 179 this.editableTitleBarTheme.iconBackgroundHoverColor = theme.colors.interactiveHover; 180 this.editableTitleBarTheme.iconBackgroundFocusOutlineColor = theme.colors.interactiveFocus; 181 } 182 183 aboutToAppear(): void { 184 try { 185 let uiContent: UIContext = this.getUIContext(); 186 this.isFollowingSystemFontScale = uiContent.isFollowingSystemFontScale(); 187 this.maxFontScale = uiContent.getMaxFontScale(); 188 } catch (exception) { 189 let code: number = (exception as BusinessError)?.code; 190 let message: string = (exception as BusinessError)?.message; 191 hilog.error(0x3900, 'Ace', `Faild to init fontsizescale info,cause, code: ${code}, message: ${message}`); 192 } 193 } 194 195 decideFontScale(): number { 196 let uiContent: UIContext = this.getUIContext(); 197 this.systemFontScale = (uiContent.getHostContext() as common.UIAbilityContext)?.config?.fontSizeScale ?? 1; 198 if (!this.isFollowingSystemFontScale) { 199 return 1; 200 } 201 return Math.min(this.systemFontScale, this.maxFontScale); 202 } 203 204 build() { 205 Flex({ 206 justifyContent: FlexAlign.SpaceBetween, 207 alignItems: ItemAlign.Stretch, 208 }) { 209 Row() { 210 Row() { 211 this.leftIconLayout(); 212 } 213 .flexShrink(0) 214 215 Row() { 216 if (this.imageItem) { 217 Row() { 218 this.imageItemLayout(); 219 } 220 .flexShrink(0) 221 } 222 223 Row() { 224 this.titleLayout(); 225 } 226 .width('100%') 227 .flexShrink(1) 228 } 229 .width('100%') 230 .flexShrink(1) 231 .accessibilityGroup(true) 232 .accessibilityDescription($r('sys.string.subheader_accessibility_title')) 233 234 Row() { 235 this.rightMenuItemsLayout(); 236 } 237 .flexShrink(0) 238 } 239 .width('100%') 240 .margin(this.contentMargin ?? this.titleBarMargin) 241 .height(EditableTitleBar.totalHeight) 242 } 243 .backgroundColor(this.options.backgroundColor ?? EditableTitleBar.noneColor) 244 .backgroundBlurStyle( 245 this.options.backgroundBlurStyle ?? BlurStyle.NONE, undefined, { disableSystemAdaptation: true }) 246 .expandSafeArea( 247 this.options.safeAreaTypes ? this.options.safeAreaTypes : [SafeAreaType.SYSTEM], 248 this.options.safeAreaEdges ? this.options.safeAreaEdges : [SafeAreaEdge.TOP], 249 ) 250 } 251 252 @Builder 253 imageItemLayout(): void { 254 ImageMenuItem({ 255 item: this.imageItem, 256 attribute: ItemType.Image, 257 }) 258 } 259 260 @Builder 261 leftIconLayout(): void { 262 if (this.leftIconStyle === EditableLeftIconType.Back) { 263 ImageMenuItem({ 264 item: { 265 value: PUBLIC_BACK, 266 isEnabled: true, 267 action: () => this.onCancel ? this.onCancel() : this.getUIContext()?.getRouter()?.back(), 268 defaultFocus: this.leftIconDefaultFocus 269 }, 270 fontSize: this.fontSize, 271 attribute: ItemType.LeftIcon, 272 imageMenuItemId: `BackMenuItem_${this.getUniqueId()}` 273 }) 274 } else { 275 ImageMenuItem({ 276 item: { 277 value: PUBLIC_CANCEL, 278 isEnabled: true, 279 action: () => this.onCancel && this.onCancel(), 280 defaultFocus: this.leftIconDefaultFocus 281 }, 282 fontSize: this.fontSize, 283 attribute: ItemType.LeftIcon, 284 imageMenuItemId: `CancelMenuItem_${this.getUniqueId()}` 285 }) 286 } 287 } 288 289 @Builder 290 titleLayout(): void { 291 Column() { 292 Row() { 293 Text(this.title) 294 .maxFontSize(TITLE_F_VP) 295 .minFontSize(SUBTITLE_F_VP) 296 .fontColor(this.editableTitleBarTheme.titleColor) 297 .maxLines(this.subtitle ? MAX_LINE_ONE : MAX_LINES_TWO) 298 .fontWeight(FontWeight.Bold) 299 .textAlign(TextAlign.Start) 300 .textOverflow({ overflow: TextOverflow.Ellipsis }) 301 .heightAdaptivePolicy(this.subtitle ? 302 TextHeightAdaptivePolicy.MAX_LINES_FIRST : TextHeightAdaptivePolicy.MIN_FONT_SIZE_FIRST) 303 .constraintSize({ 304 maxHeight: this.subtitle ? EditableTitleBar.maxMainTitleHeight : EditableTitleBar.totalHeight, 305 }) 306 } 307 .justifyContent(FlexAlign.Start) 308 309 if (this.subtitle) { 310 Row() { 311 Text(this.subtitle) 312 .maxFontSize(SUBTITLE_F_VP) 313 .minFontSize(MIN_SUBTITLE_SIZE) 314 .fontColor(this.editableTitleBarTheme.subTitleColor) 315 .maxLines(MAX_LINE_ONE) 316 .fontWeight(FontWeight.Regular) 317 .textAlign(TextAlign.Start) 318 .textOverflow({ overflow: TextOverflow.Ellipsis }) 319 .heightAdaptivePolicy(TextHeightAdaptivePolicy.MAX_LINES_FIRST) 320 .constraintSize({ 321 maxHeight: this.title ? EditableTitleBar.maxSubTitleHeight : EditableTitleBar.totalHeight, 322 }) 323 } 324 .margin({ 325 top: $r('sys.float.padding_level1'), 326 }) 327 .justifyContent(FlexAlign.Start) 328 } 329 } 330 .height(EditableTitleBar.totalHeight) 331 .justifyContent(FlexAlign.Center) 332 .margin({ 333 // 'sys.float.titlebar_icon_background_space_horizontal' id,value: 8vp 334 start: LengthMetrics.resource($r('sys.float.titlebar_icon_background_space_horizontal')), 335 }) 336 .alignItems(HorizontalAlign.Start) 337 } 338 339 @Builder 340 rightMenuItemsLayout(): void { 341 EditableTitleBarMenuSection({ 342 menuItems: this.menuItems, 343 onSave: this.onSave, 344 isSaveEnabled: this.isSaveIconRequired, 345 fontSize: this.fontSize, 346 parentUniqueId: this.getUniqueId(), 347 saveIconDefaultFocus: this.saveIconDefaultFocus 348 }) 349 } 350 351 onPlaceChildren(selfLayoutInfo: GeometryInfo, children: Layoutable[], constraint: ConstraintSizeOptions): void { 352 children.forEach((child) => { 353 child.layout({ x: 0, y: 0 }); 354 }) 355 } 356 357 onMeasureSize(selfLayoutInfo: GeometryInfo, children: Measurable[], constraint: ConstraintSizeOptions): SizeResult { 358 let result: SizeResult = { width: selfLayoutInfo.width, height: selfLayoutInfo.height }; 359 this.fontSize = this.decideFontScale(); 360 children.forEach((child) => { 361 result.height = child.measure(constraint).height; 362 result.width = Number(constraint.maxWidth); 363 }) 364 return result; 365 } 366} 367 368@Component 369struct EditableTitleBarMenuSection { 370 menuItems: Array<EditableTitleBarMenuItem> | undefined = undefined; 371 onSave?: () => void; 372 isSaveEnabled: boolean = true; 373 saveIconDefaultFocus?: boolean = false; 374 @Prop fontSize: number = 1; 375 @Prop parentUniqueId?: number; 376 377 build() { 378 Column() { 379 Row() { 380 if (this.menuItems !== undefined && this.menuItems.length > EditableTitleBar.commonZero) { 381 ForEach(this.menuItems.slice(EditableTitleBar.commonZero, 382 this.isSaveEnabled ? 383 EditableTitleBar.maxOtherCountOfExtraItems : EditableTitleBar.maxCountOfExtraItems), 384 (item: EditableTitleBarMenuItem, index: number) => { 385 ImageMenuItem({ 386 item: item, 387 attribute: ItemType.Icon, 388 imageMenuItemId: `ImageMenuItem_${this.parentUniqueId}_${index}` 389 }) 390 }) 391 } 392 if (this.isSaveEnabled) { 393 ImageMenuItem({ 394 item: { 395 value: PUBLIC_OK, 396 isEnabled: true, 397 action: () => this.onSave && this.onSave(), 398 defaultFocus: this.saveIconDefaultFocus 399 }, 400 fontSize: this.fontSize, 401 attribute: ItemType.Icon, 402 imageMenuItemId: `SaveMenuItem_${this.parentUniqueId}` 403 }) 404 } 405 } 406 } 407 .justifyContent(FlexAlign.Center) 408 } 409} 410 411@Component 412struct ImageMenuItem { 413 item: EditableTitleBarMenuItem = { 414 value: '', 415 isEnabled: true, 416 label: '', 417 accessibilityLevel: 'auto', 418 accessibilityText: '', 419 accessibilityDescription: '', 420 }; 421 attribute: ItemType = ItemType.Image; 422 callbackId: number | undefined = undefined; 423 minFontSize: number = 1.75; 424 maxFontSize: number = 3.2; 425 longPressTime: number = 500; 426 systemFontScale?: number = 1; 427 isFollowingSystemFontScale: boolean = this.getUIContext().isFollowingSystemFontScale();; 428 maxFontScale: number = this.getUIContext().getMaxFontScale(); 429 @State fontSize: number = 1; 430 @State isOnFocus: boolean = false; 431 @State isOnHover: boolean = false; 432 @State isOnClick: boolean = false; 433 imageMenuItemId?: string; 434 @Consume editableTitleBarTheme: EditableTitleBarTheme; 435 dialogController: CustomDialogController | null = new CustomDialogController({ 436 builder: EditableTitleBarDialog({ 437 cancel: () => { 438 }, 439 confirm: () => { 440 }, 441 itemEditableDialog: this.item, 442 textEditableTitleBarDialog: this.item.label ? this.item.label : this.textDialog(), 443 fontSize: this.fontSize, 444 }), 445 maskColor: Color.Transparent, 446 isModal: true, 447 customStyle: true, 448 }); 449 @State buttonGestureModifier: ButtonGestureModifier = new ButtonGestureModifier(this.dialogController); 450 451 private textDialog(): ResourceStr { 452 if (this.item.value === PUBLIC_OK) { 453 return $r('sys.string.icon_save'); 454 } else if (this.item.value === PUBLIC_CANCEL) { 455 return $r('sys.string.icon_cancel'); 456 } else if (this.item.value === PUBLIC_BACK) { 457 return $r('sys.string.icon_back'); 458 } else { 459 return this.item.label ? this.item.label : ''; 460 } 461 } 462 463 aboutToAppear() { 464 try { 465 this.callbackId = getContext()?.getApplicationContext()?.on('environment', this.envCallback); 466 } catch (paramError) { 467 let code = (paramError as BusinessError)?.code; 468 let message = (paramError as BusinessError)?.message; 469 hilog.error(0x3900, 'Ace', 470 `EditableTitleBar Faild to get environment param error: ${code}, ${message}`); 471 } 472 this.fontSize = this.decideFontScale(); 473 this.buttonGestureModifier.fontSize = this.fontSize; 474 } 475 476 private envCallback: EnvironmentCallback = { 477 onConfigurationUpdated: (config) => { 478 if (config === undefined || !this.isFollowingSystemFontScale) { 479 this.fontSize = 1; 480 return; 481 } 482 try { 483 this.fontSize = Math.min( 484 this.maxFontScale, config.fontSizeScale ?? 1); 485 this.buttonGestureModifier.fontSize = this.fontSize; 486 } catch (paramError) { 487 let code = (paramError as BusinessError)?.code; 488 let message = (paramError as BusinessError)?.message; 489 hilog.error(0x3900, 'Ace', 490 `EditableTitleBar environmentCallback error: ${code}, ${message}`); 491 } 492 }, 493 onMemoryLevel: (level) => { 494 } 495 } 496 497 decideFontScale(): number { 498 try { 499 let uiContent: UIContext = this.getUIContext(); 500 this.systemFontScale = (uiContent.getHostContext() as common.UIAbilityContext)?.config?.fontSizeScale ?? 1; 501 if (!this.isFollowingSystemFontScale) { 502 return 1; 503 } 504 return Math.min(this.systemFontScale, this.maxFontScale); 505 } catch (exception) { 506 let code: number = (exception as BusinessError)?.code; 507 let message: string = (exception as BusinessError)?.message; 508 hilog.error(0x3900, 'EditableTitleBar', `Faild to decideFontScale,cause, code: ${code}, message: ${message}`); 509 return 1; 510 } 511 } 512 513 @Styles 514 buttonStateStyles() { 515 .stateStyles({ 516 focused: this.focusedStyle, 517 normal: this.notInFocusedStyle, 518 pressed: this.notInFocusedStyle, 519 }) 520 } 521 522 @Styles 523 focusedStyle() { 524 .border({ 525 radius: $r('sys.float.titlebar_icon_background_shape'), 526 width: $r('sys.float.titlebar_icon_background_focus_outline_weight'), 527 color: this.editableTitleBarTheme.iconBackgroundFocusOutlineColor, 528 style: BorderStyle.Solid, 529 }) 530 } 531 532 @Styles 533 notInFocusedStyle() { 534 .border({ 535 radius: $r('sys.float.titlebar_icon_background_shape'), 536 width: EditableTitleBar.commonZero, 537 }) 538 } 539 540 private touchEventAction(event: TouchEvent): void { 541 if (!this.item.isEnabled) { 542 return; 543 } 544 if (event.type === TouchType.Down) { 545 this.isOnClick = true; 546 } 547 if (event.type === TouchType.Up || event.type === TouchType.Cancel) { 548 if (this.fontSize >= this.minFontSize) { 549 this.dialogController?.close() 550 } 551 this.isOnClick = false; 552 } 553 } 554 555 private keyEventAction(event: KeyEvent): void { 556 if (!this.item.isEnabled) { 557 return; 558 } 559 if (event.keyCode !== KeyCode.KEYCODE_ENTER && event.keyCode !== KeyCode.KEYCODE_SPACE) { 560 return; 561 } 562 if (event.type === KeyType.Down) { 563 this.isOnClick = true; 564 } 565 if (event.type === KeyType.Up) { 566 this.isOnClick = false; 567 } 568 } 569 570 @Styles 571 buttonEventStyle() { 572 .onFocus(() => { 573 if (!this.item.isEnabled) { 574 return; 575 } 576 this.isOnFocus = true; 577 }) 578 .onBlur(() => this.isOnFocus = false) 579 .onHover((isOn) => { 580 if (!this.item.isEnabled) { 581 return; 582 } 583 this.isOnHover = isOn; 584 }) 585 .onKeyEvent((event) => { 586 this.keyEventAction(event); 587 }) 588 .onTouch((event) => { 589 this.touchEventAction(event); 590 }) 591 .onClick(() => { 592 if (this.item.isEnabled === undefined) { 593 this.item.isEnabled = true; 594 } 595 this.item.isEnabled && this.item.action && this.item.action() 596 }) 597 } 598 599 @Styles 600 backgroundButtonStyle() { 601 .width($r('sys.float.titlebar_icon_background_width')) 602 .height($r('sys.float.titlebar_icon_background_height')) 603 .focusable(this.item.isEnabled) 604 .enabled(this.item.isEnabled) 605 } 606 607 getBgColor(): ResourceColor { 608 if (this.isOnClick) { 609 return this.editableTitleBarTheme.iconBackgroundPressedColor; 610 } else if (this.isOnHover) { 611 return this.editableTitleBarTheme.iconBackgroundHoverColor; 612 } else { 613 return this.editableTitleBarTheme.iconBackgroundColor; 614 } 615 } 616 617 getFgColor(): ResourceStr { 618 if (this.isOnClick) { 619 return $r('sys.color.titlebar_icon_background_pressed_color'); 620 } else if (this.isOnHover) { 621 return $r('sys.color.titlebar_icon_background_hover_color'); 622 } else { 623 return EditableTitleBar.noneColor; 624 } 625 } 626 627 getStringByNameSync(contextName: string): string { 628 let uiContext: string = ''; 629 try { 630 uiContext = getContext()?.resourceManager?.getStringByNameSync(contextName); 631 } catch (exception) { 632 let code: number = (exception as BusinessError)?.code; 633 let message: string = (exception as BusinessError)?.message; 634 hilog.error(0x3900, 'Ace', `Faild to getStringByNameSync,cause, code: ${code}, message: ${message}`); 635 } 636 return uiContext; 637 } 638 639 private toStringFormat(resource: ResourceStr | undefined): string | undefined { 640 if (typeof resource === 'string' || typeof resource === 'undefined') { 641 return resource; 642 } else { 643 let resourceString: string = ''; 644 try { 645 if (resource.id === -1 ) { 646 resourceString = getContext()?.resourceManager?.getStringByNameSync(resource.params?.[0]?.split('.').pop() ?? ''); 647 } else { 648 resourceString = getContext()?.resourceManager?.getStringSync(resource); 649 } 650 } catch (err) { 651 let code: number = (err as BusinessError)?.code; 652 let message: string = (err as BusinessError)?.message; 653 hilog.error(0x3900, 'Ace', `Faild to EditableTitleBar toStringFormat, code: ${code}, message: ${message}`) 654 } 655 return resourceString; 656 } 657 } 658 659 private getAccessibilityReadText(): string | undefined { 660 if (this.item.value === PUBLIC_OK) { 661 return this.getStringByNameSync('icon_save'); 662 } else if (this.item.value === PUBLIC_CANCEL) { 663 return this.getStringByNameSync('icon_cancel'); 664 } else if (this.item.value === PUBLIC_BACK) { 665 return this.getStringByNameSync('icon_back'); 666 } else if (this.item.accessibilityText) { 667 return this.item.accessibilityText as string; 668 } else if (this.item.label) { 669 return this.item.label as string; 670 } 671 return ' '; 672 } 673 674 private getRightIconAccessibilityLevel(): string { 675 if (this.item.accessibilityLevel && this.item.accessibilityLevel !== '') { 676 return this.item.accessibilityLevel; 677 } 678 return 'auto'; 679 } 680 681 private getAccessibilityDescription(): string | undefined { 682 if (this.item.accessibilityDescription && this.item.accessibilityDescription !== '') { 683 return this.item.accessibilityDescription as string; 684 } 685 return ''; 686 } 687 688 @Builder 689 IconBuilder(): void { 690 Button({ type: ButtonType.Normal, stateEffect: this.item.isEnabled }) { 691 if (this.item.symbolStyle !== undefined) { 692 SymbolGlyph() 693 .fontColor([this.editableTitleBarTheme.iconColor]) 694 .attributeModifier(this.item.symbolStyle) 695 .focusable(this.item.isEnabled) 696 .enabled(this.item.isEnabled) 697 .draggable(false) 698 .accessibilityText(this.getAccessibilityReadText()) 699 .effectStrategy(SymbolEffectStrategy.NONE) 700 .symbolEffect(new SymbolEffect(), false) 701 .fontSize(SYMBOL_SIZE) 702 .defaultFocus(this.item.isEnabled ? this.item.defaultFocus : false) 703 } else { 704 if (Util.isSymbolResource(this.item.value)) { 705 SymbolGlyph(this.item.value as Resource) 706 .fontSize(SYMBOL_SIZE) 707 .fontColor([this.editableTitleBarTheme.iconColor]) 708 .focusable(this.item.isEnabled) 709 .enabled(this.item.isEnabled) 710 .draggable(false) 711 .accessibilityText(this.getAccessibilityReadText()) 712 .defaultFocus(this.item.isEnabled ? this.item.defaultFocus : false) 713 } else { 714 Image(this.item.value) 715 .fillColor(this.editableTitleBarTheme.iconColor) 716 .matchTextDirection(this.item.value === PUBLIC_IMAGE_BACK ? true : false) 717 .width($r('sys.float.titlebar_icon_width')) 718 .height($r('sys.float.titlebar_icon_height')) 719 .focusable(this.item.isEnabled) 720 .enabled(this.item.isEnabled) 721 .draggable(false) 722 .accessibilityText(this.getAccessibilityReadText()) 723 .defaultFocus(this.item.isEnabled ? this.item.defaultFocus : false) 724 } 725 } 726 } 727 .id(this.imageMenuItemId) 728 .backgroundButtonStyle() 729 .borderRadius($r('sys.float.titlebar_icon_background_shape')) 730 .margin({ 731 start: this.attribute === ItemType.LeftIcon ? LengthMetrics.vp(EditableTitleBar.commonZero) : 732 LengthMetrics.resource($r('sys.float.titlebar_icon_background_space_horizontal')), 733 }) 734 .focusOnTouch(true) 735 .foregroundColor(this.getFgColor()) 736 .backgroundColor(this.getBgColor()) 737 .buttonStateStyles() 738 .buttonEventStyle() 739 .gestureModifier(this.buttonGestureModifier) 740 .accessibilityLevel(this.getRightIconAccessibilityLevel()) 741 .accessibilityDescription(this.getAccessibilityDescription()) 742 } 743 744 @Builder 745 ImageBuilder() { 746 Stack({ alignContent: Alignment.Center }) { 747 Image(this.item.value) 748 .width($r('sys.float.titlebar_icon_background_width')) 749 .height($r('sys.float.titlebar_icon_background_height')) 750 .borderRadius($r('sys.float.corner_radius_level10')) 751 .focusable(false) 752 .enabled(this.item.isEnabled) 753 .objectFit(ImageFit.Cover) 754 Button({ type: ButtonType.Circle }) 755 .backgroundButtonStyle() 756 .foregroundColor(this.getFgColor()) 757 .backgroundColor(EditableTitleBar.noneColor) 758 .buttonStateStyles() 759 .buttonEventStyle() 760 .gestureModifier(this.buttonGestureModifier) 761 .defaultFocus(this.item.isEnabled ? this.item.defaultFocus : false) 762 } 763 .margin({ 764 start: LengthMetrics.resource($r('sys.float.titlebar_icon_background_space_horizontal')), 765 }) 766 } 767 768 build() { 769 if (this.attribute === ItemType.Icon || this.attribute === ItemType.LeftIcon) { 770 this.IconBuilder(); 771 } else { 772 this.ImageBuilder(); 773 } 774 } 775} 776 777/** 778 * EditableTitleBarDialog 779 * 780 * @since 2024-05-28 781 */ 782@CustomDialog 783struct EditableTitleBarDialog { 784 itemEditableDialog: EditableTitleBarMenuItem = { 785 value: '', 786 isEnabled: true, 787 }; 788 callbackId: number | undefined = undefined; 789 textEditableTitleBarDialog?: ResourceStr = ''; 790 mainWindowStage: window.Window | undefined = undefined; 791 controller?: CustomDialogController 792 minFontSize: number = 1.75; 793 maxFontSize: number = 3.2; 794 screenWidth: number = 640; 795 verticalScreenLines: number = 6; 796 horizontalsScreenLines: number = 1; 797 cancel: () => void = () => { 798 } 799 confirm: () => void = () => { 800 } 801 @StorageLink('mainWindow') mainWindow: Promise<window.Window> | undefined = undefined; 802 @Prop fontSize: number = 1; 803 @State maxLines: number = 1; 804 @StorageProp('windowStandardHeight') windowStandardHeight: number = 0; 805 806 build() { 807 if (this.textEditableTitleBarDialog) { 808 Column() { 809 if (this.itemEditableDialog.symbolStyle !== undefined) { 810 SymbolGlyph() 811 .fontColor([$r('sys.color.icon_primary')]) 812 .attributeModifier(this.itemEditableDialog.symbolStyle) 813 .margin({ 814 top: $r('sys.float.padding_level24'), 815 bottom: $r('sys.float.padding_level8'), 816 }) 817 .effectStrategy(SymbolEffectStrategy.NONE) 818 .symbolEffect(new SymbolEffect(), false) 819 .fontSize(SYMBOL_TITLE_SIZE) 820 .direction(Direction.Ltr) 821 } else { 822 if (Util.isSymbolResource(this.itemEditableDialog.value)) { 823 SymbolGlyph(this.itemEditableDialog.value as Resource) 824 .margin({ 825 top: $r('sys.float.padding_level24'), 826 bottom: $r('sys.float.padding_level8'), 827 }) 828 .fontColor([$r('sys.color.icon_primary')]) 829 .fontSize(SYMBOL_TITLE_SIZE) 830 .direction(Direction.Ltr) 831 } else { 832 Image(this.itemEditableDialog.value) 833 .width(IMAGE_SIZE) 834 .height(IMAGE_SIZE) 835 .margin({ 836 top: $r('sys.float.padding_level24'), 837 bottom: $r('sys.float.padding_level8'), 838 }) 839 .fillColor($r('sys.color.icon_primary')) 840 .direction(Direction.Ltr) 841 } 842 } 843 Column() { 844 Text(this.textEditableTitleBarDialog) 845 .fontSize(TEXT_EDITABLE_DIALOG) 846 .textOverflow({ overflow: TextOverflow.Ellipsis }) 847 .maxLines(this.maxLines) 848 .width('100%') 849 .textAlign(TextAlign.Center) 850 .fontColor($r('sys.color.font_primary')) 851 } 852 .width('100%') 853 .padding({ 854 left: $r('sys.float.padding_level4'), 855 right: $r('sys.float.padding_level4'), 856 bottom: $r('sys.float.padding_level12'), 857 }) 858 } 859 .width(this.fontSize === this.maxFontSize ? MAX_DIALOG : MIN_DIALOG) 860 .constraintSize({ minHeight: this.fontSize === this.maxFontSize ? MAX_DIALOG : MIN_DIALOG }) 861 .backgroundBlurStyle(BlurStyle.COMPONENT_ULTRA_THICK, undefined, { disableSystemAdaptation: true }) 862 .shadow(ShadowStyle.OUTER_DEFAULT_LG) 863 .borderRadius(($r('sys.float.corner_radius_level10'))) 864 } else { 865 Column() { 866 if (this.itemEditableDialog.symbolStyle !== undefined) { 867 SymbolGlyph() 868 .fontColor([$r('sys.color.icon_primary')]) 869 .attributeModifier(this.itemEditableDialog.symbolStyle) 870 .effectStrategy(SymbolEffectStrategy.NONE) 871 .symbolEffect(new SymbolEffect(), false) 872 .fontSize(SYMBOL_TITLE_SIZE) 873 } else { 874 if (Util.isSymbolResource(this.itemEditableDialog.value)) { 875 SymbolGlyph(this.itemEditableDialog.value as Resource) 876 .fontColor([$r('sys.color.icon_primary')]) 877 .fontSize(SYMBOL_TITLE_SIZE) 878 } else { 879 Image(this.itemEditableDialog.value) 880 .width(IMAGE_SIZE) 881 .height(IMAGE_SIZE) 882 .fillColor($r('sys.color.icon_primary')) 883 } 884 } 885 } 886 .width(this.fontSize === this.maxFontSize ? MAX_DIALOG : MIN_DIALOG) 887 .constraintSize({ minHeight: this.fontSize === this.maxFontSize ? MAX_DIALOG : MIN_DIALOG }) 888 .backgroundBlurStyle(BlurStyle.COMPONENT_ULTRA_THICK, undefined, { disableSystemAdaptation: true }) 889 .shadow(ShadowStyle.OUTER_DEFAULT_LG) 890 .borderRadius(($r('sys.float.corner_radius_level10'))) 891 .justifyContent(FlexAlign.Center) 892 .direction(Direction.Ltr) 893 } 894 } 895 896 async aboutToAppear(): Promise<void> { 897 try { 898 let context = this.getUIContext().getHostContext() as common.UIAbilityContext; 899 this.mainWindowStage = context.windowStage.getMainWindowSync(); 900 } catch (error) { 901 let code: number = (error as BusinessError)?.code; 902 let message: string = (error as BusinessError)?.message; 903 hilog.error(0x3900, 'Ace', `EditableTitleBar getMainWindowStage error, code: ${code},message:${message}`); 904 return 0; 905 } 906 let properties: window.WindowProperties = this.mainWindowStage.getWindowProperties(); 907 let rect = properties.windowRect; 908 if (px2vp(rect.height) > this.screenWidth) { 909 this.maxLines = this.verticalScreenLines; 910 } else { 911 this.maxLines = this.horizontalsScreenLines; 912 } 913 } 914} 915 916/** 917 * get resource size 918 * 919 * @param resourceId resource id 920 * @return resource size 921 */ 922function getNumberByResource(resourceId: number, defaultNumber: number): number { 923 try { 924 let resourceNumber: number = resourceManager.getSystemResourceManager().getNumber(resourceId); 925 if (resourceNumber === 0) { 926 return defaultNumber; 927 } else { 928 return resourceNumber; 929 } 930 } catch (error) { 931 let code: number = (error as BusinessError).code; 932 let message: string = (error as BusinessError).message; 933 hilog.error(0x3900, 'Ace', `EditableTitleBar getNumberByResource error, code: ${code},message:${message}`); 934 return 0; 935 } 936} 937 938class Util { 939 private static RESOURCE_TYPE_SYMBOL = 40000; 940 941 public static isSymbolResource(resourceStr: ResourceStr | undefined): boolean { 942 if (!Util.isResourceType(resourceStr)) { 943 return false; 944 } 945 let resource = resourceStr as Resource; 946 return resource.type === Util.RESOURCE_TYPE_SYMBOL; 947 } 948 949 public static isResourceType(resource: ResourceStr | Resource | undefined): boolean { 950 if (!resource) { 951 return false; 952 } 953 if (typeof resource === 'string' || typeof resource === 'undefined') { 954 return false; 955 } 956 return true; 957 } 958} 959