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