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) 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 private toStringFormat(resource: ResourceStr | undefined): string | undefined { 628 if (typeof resource === 'string' || typeof resource === 'undefined') { 629 return resource; 630 } else { 631 let resourceString: string = ''; 632 try { 633 if (resource.id === -1 ) { 634 resourceString = getContext()?.resourceManager?.getStringByNameSync(resource.params?.[0]?.split('.').pop() ?? ''); 635 } else { 636 resourceString = getContext()?.resourceManager?.getStringSync(resource); 637 } 638 } catch (err) { 639 let code: number = (err as BusinessError)?.code; 640 let message: string = (err as BusinessError)?.message; 641 hilog.error(0x3900, 'Ace', `Faild to EditableTitleBar toStringFormat, code: ${code}, message: ${message}`) 642 } 643 return resourceString; 644 } 645 } 646 647 private getAccessibilityReadText(): string | undefined { 648 if (this.item.value === PUBLIC_OK) { 649 return getContext()?.resourceManager?.getStringByNameSync('icon_save'); 650 } else if (this.item.value === PUBLIC_CANCEL) { 651 return getContext()?.resourceManager?.getStringByNameSync('icon_cancel'); 652 } else if (this.item.value === PUBLIC_BACK) { 653 return getContext()?.resourceManager?.getStringByNameSync('icon_back'); 654 } else if (this.item.accessibilityText) { 655 return this.toStringFormat(this.item.accessibilityText); 656 } else if (this.item.label) { 657 return this.toStringFormat(this.item.label); 658 } 659 return ' '; 660 } 661 662 private getRightIconAccessibilityLevel(): string { 663 if (this.item.accessibilityLevel && this.item.accessibilityLevel !== '') { 664 return this.item.accessibilityLevel; 665 } 666 return 'auto'; 667 } 668 669 private getAccessibilityDescription(): string | undefined { 670 if (this.item.accessibilityDescription && this.item.accessibilityDescription !== '') { 671 return this.toStringFormat(this.item.accessibilityDescription); 672 } 673 return ''; 674 } 675 676 @Builder 677 IconBuilder(): void { 678 Button({ type: ButtonType.Normal, stateEffect: this.item.isEnabled }) { 679 if (this.item.symbolStyle !== undefined) { 680 SymbolGlyph() 681 .fontColor([this.editableTitleBarTheme.iconColor]) 682 .attributeModifier(this.item.symbolStyle) 683 .focusable(this.item.isEnabled) 684 .enabled(this.item.isEnabled) 685 .draggable(false) 686 .accessibilityText(this.getAccessibilityReadText()) 687 .effectStrategy(SymbolEffectStrategy.NONE) 688 .symbolEffect(new SymbolEffect(), false) 689 .fontSize(SYMBOL_SIZE) 690 .defaultFocus(this.item.isEnabled ? this.item.defaultFocus : false) 691 } else { 692 if (Util.isSymbolResource(this.item.value)) { 693 SymbolGlyph(this.item.value as Resource) 694 .fontSize(SYMBOL_SIZE) 695 .fontColor([this.editableTitleBarTheme.iconColor]) 696 .focusable(this.item.isEnabled) 697 .enabled(this.item.isEnabled) 698 .draggable(false) 699 .accessibilityText(this.getAccessibilityReadText()) 700 .defaultFocus(this.item.isEnabled ? this.item.defaultFocus : false) 701 } else { 702 Image(this.item.value) 703 .fillColor(this.editableTitleBarTheme.iconColor) 704 .matchTextDirection(this.item.value === PUBLIC_IMAGE_BACK ? true : false) 705 .width($r('sys.float.titlebar_icon_width')) 706 .height($r('sys.float.titlebar_icon_height')) 707 .focusable(this.item.isEnabled) 708 .enabled(this.item.isEnabled) 709 .draggable(false) 710 .accessibilityText(this.getAccessibilityReadText()) 711 .defaultFocus(this.item.isEnabled ? this.item.defaultFocus : false) 712 } 713 } 714 } 715 .id(this.imageMenuItemId) 716 .backgroundButtonStyle() 717 .borderRadius($r('sys.float.titlebar_icon_background_shape')) 718 .margin({ 719 start: this.attribute === ItemType.LeftIcon ? LengthMetrics.vp(EditableTitleBar.commonZero) : 720 LengthMetrics.resource($r('sys.float.titlebar_icon_background_space_horizontal')), 721 }) 722 .focusOnTouch(true) 723 .foregroundColor(this.getFgColor()) 724 .backgroundColor(this.getBgColor()) 725 .buttonStateStyles() 726 .buttonEventStyle() 727 .gestureModifier(this.buttonGestureModifier) 728 .accessibilityLevel(this.getRightIconAccessibilityLevel()) 729 .accessibilityDescription(this.getAccessibilityDescription()) 730 } 731 732 @Builder 733 ImageBuilder() { 734 Stack({ alignContent: Alignment.Center }) { 735 Image(this.item.value) 736 .width($r('sys.float.titlebar_icon_background_width')) 737 .height($r('sys.float.titlebar_icon_background_height')) 738 .borderRadius($r('sys.float.corner_radius_level10')) 739 .focusable(false) 740 .enabled(this.item.isEnabled) 741 .objectFit(ImageFit.Cover) 742 Button({ type: ButtonType.Circle }) 743 .backgroundButtonStyle() 744 .foregroundColor(this.getFgColor()) 745 .backgroundColor(EditableTitleBar.noneColor) 746 .buttonStateStyles() 747 .buttonEventStyle() 748 .gestureModifier(this.buttonGestureModifier) 749 .defaultFocus(this.item.isEnabled ? this.item.defaultFocus : false) 750 } 751 .margin({ 752 start: LengthMetrics.resource($r('sys.float.titlebar_icon_background_space_horizontal')), 753 }) 754 } 755 756 build() { 757 if (this.attribute === ItemType.Icon || this.attribute === ItemType.LeftIcon) { 758 this.IconBuilder(); 759 } else { 760 this.ImageBuilder(); 761 } 762 } 763} 764 765/** 766 * EditableTitleBarDialog 767 * 768 * @since 2024-05-28 769 */ 770@CustomDialog 771struct EditableTitleBarDialog { 772 itemEditableDialog: EditableTitleBarMenuItem = { 773 value: '', 774 isEnabled: true, 775 }; 776 callbackId: number | undefined = undefined; 777 textEditableTitleBarDialog?: ResourceStr = ''; 778 mainWindowStage: window.Window | undefined = undefined; 779 controller?: CustomDialogController 780 minFontSize: number = 1.75; 781 maxFontSize: number = 3.2; 782 screenWidth: number = 640; 783 verticalScreenLines: number = 6; 784 horizontalsScreenLines: number = 1; 785 cancel: () => void = () => { 786 } 787 confirm: () => void = () => { 788 } 789 @StorageLink('mainWindow') mainWindow: Promise<window.Window> | undefined = undefined; 790 @Prop fontSize: number = 1; 791 @State maxLines: number = 1; 792 @StorageProp('windowStandardHeight') windowStandardHeight: number = 0; 793 794 build() { 795 if (this.textEditableTitleBarDialog) { 796 Column() { 797 if (this.itemEditableDialog.symbolStyle !== undefined) { 798 SymbolGlyph() 799 .fontColor([$r('sys.color.icon_primary')]) 800 .attributeModifier(this.itemEditableDialog.symbolStyle) 801 .margin({ 802 top: $r('sys.float.padding_level24'), 803 bottom: $r('sys.float.padding_level8'), 804 }) 805 .effectStrategy(SymbolEffectStrategy.NONE) 806 .symbolEffect(new SymbolEffect(), false) 807 .fontSize(SYMBOL_TITLE_SIZE) 808 .direction(Direction.Ltr) 809 } else { 810 if (Util.isSymbolResource(this.itemEditableDialog.value)) { 811 SymbolGlyph(this.itemEditableDialog.value as Resource) 812 .margin({ 813 top: $r('sys.float.padding_level24'), 814 bottom: $r('sys.float.padding_level8'), 815 }) 816 .fontColor([$r('sys.color.icon_primary')]) 817 .fontSize(SYMBOL_TITLE_SIZE) 818 .direction(Direction.Ltr) 819 } else { 820 Image(this.itemEditableDialog.value) 821 .width(IMAGE_SIZE) 822 .height(IMAGE_SIZE) 823 .margin({ 824 top: $r('sys.float.padding_level24'), 825 bottom: $r('sys.float.padding_level8'), 826 }) 827 .fillColor($r('sys.color.icon_primary')) 828 .direction(Direction.Ltr) 829 } 830 } 831 Column() { 832 Text(this.textEditableTitleBarDialog) 833 .fontSize(TEXT_EDITABLE_DIALOG) 834 .textOverflow({ overflow: TextOverflow.Ellipsis }) 835 .maxLines(this.maxLines) 836 .width('100%') 837 .textAlign(TextAlign.Center) 838 .fontColor($r('sys.color.font_primary')) 839 } 840 .width('100%') 841 .padding({ 842 left: $r('sys.float.padding_level4'), 843 right: $r('sys.float.padding_level4'), 844 bottom: $r('sys.float.padding_level12'), 845 }) 846 } 847 .width(this.fontSize === this.maxFontSize ? MAX_DIALOG : MIN_DIALOG) 848 .constraintSize({ minHeight: this.fontSize === this.maxFontSize ? MAX_DIALOG : MIN_DIALOG }) 849 .backgroundBlurStyle(BlurStyle.COMPONENT_ULTRA_THICK) 850 .shadow(ShadowStyle.OUTER_DEFAULT_LG) 851 .borderRadius(($r('sys.float.corner_radius_level10'))) 852 } else { 853 Column() { 854 if (this.itemEditableDialog.symbolStyle !== undefined) { 855 SymbolGlyph() 856 .fontColor([$r('sys.color.icon_primary')]) 857 .attributeModifier(this.itemEditableDialog.symbolStyle) 858 .effectStrategy(SymbolEffectStrategy.NONE) 859 .symbolEffect(new SymbolEffect(), false) 860 .fontSize(SYMBOL_TITLE_SIZE) 861 } else { 862 if (Util.isSymbolResource(this.itemEditableDialog.value)) { 863 SymbolGlyph(this.itemEditableDialog.value as Resource) 864 .fontColor([$r('sys.color.icon_primary')]) 865 .fontSize(SYMBOL_TITLE_SIZE) 866 } else { 867 Image(this.itemEditableDialog.value) 868 .width(IMAGE_SIZE) 869 .height(IMAGE_SIZE) 870 .fillColor($r('sys.color.icon_primary')) 871 } 872 } 873 } 874 .width(this.fontSize === this.maxFontSize ? MAX_DIALOG : MIN_DIALOG) 875 .constraintSize({ minHeight: this.fontSize === this.maxFontSize ? MAX_DIALOG : MIN_DIALOG }) 876 .backgroundBlurStyle(BlurStyle.COMPONENT_ULTRA_THICK) 877 .shadow(ShadowStyle.OUTER_DEFAULT_LG) 878 .borderRadius(($r('sys.float.corner_radius_level10'))) 879 .justifyContent(FlexAlign.Center) 880 .direction(Direction.Ltr) 881 } 882 } 883 884 async aboutToAppear(): Promise<void> { 885 let context = this.getUIContext().getHostContext() as common.UIAbilityContext; 886 this.mainWindowStage = context.windowStage.getMainWindowSync(); 887 let properties: window.WindowProperties = this.mainWindowStage.getWindowProperties(); 888 let rect = properties.windowRect; 889 if (px2vp(rect.height) > this.screenWidth) { 890 this.maxLines = this.verticalScreenLines; 891 } else { 892 this.maxLines = this.horizontalsScreenLines; 893 } 894 } 895} 896 897/** 898 * get resource size 899 * 900 * @param resourceId resource id 901 * @return resource size 902 */ 903function getNumberByResource(resourceId: number, defaultNumber: number): number { 904 try { 905 let resourceNumber: number = resourceManager.getSystemResourceManager().getNumber(resourceId); 906 if (resourceNumber === 0) { 907 return defaultNumber; 908 } else { 909 return resourceNumber; 910 } 911 } catch (error) { 912 let code: number = (error as BusinessError).code; 913 let message: string = (error as BusinessError).message; 914 hilog.error(0x3900, 'Ace', `EditableTitleBar getNumberByResource error, code: ${code},message:${message}`); 915 return 0; 916 } 917} 918 919class Util { 920 private static RESOURCE_TYPE_SYMBOL = 40000; 921 922 public static isSymbolResource(resourceStr: ResourceStr | undefined): boolean { 923 if (!Util.isResourceType(resourceStr)) { 924 return false; 925 } 926 let resource = resourceStr as Resource; 927 return resource.type === Util.RESOURCE_TYPE_SYMBOL; 928 } 929 930 public static isResourceType(resource: ResourceStr | Resource | undefined): boolean { 931 if (!resource) { 932 return false; 933 } 934 if (typeof resource === 'string' || typeof resource === 'undefined') { 935 return false; 936 } 937 return true; 938 } 939} 940