• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 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, Callback } from '@ohos.base';
17import { curves, LengthMetrics, SymbolGlyphModifier, window, AtomicServiceBar } from '@kit.ArkUI';
18import { bundleManager, common } from '@kit.AbilityKit';
19import { hilog } from '@kit.PerformanceAnalysisKit';
20import { i18n } from '@kit.LocalizationKit';
21
22/**
23 * 背景渐变色相关数据
24 */
25const backGroundColor: string[] = ['rgb(0,0,0)', 'rgb(255,255,255)', 'rgb(241,243,245)'];
26const backGroundTransparentGradientColor: string[][] = [['rgba(0,0,0,0)', 'rgba(0,0,0,1)'],
27  ['rgba(255,255,255,0)', 'rgba(255,255,255,1)'], ['rgba(241,243,245,0)', 'rgba(241,243,245,1)']];
28const transparencyMapArray: number[] = [0.15, 0.15, 0.4, 0.6, 0.8];
29const RECTANGLE_OUTSIDE_OFFSET_ONE = 1;
30const COLOR_RATIO_THIRTY_PERCENT = 0.3;
31const COLOR_RATIO_FIFTY_PERCENT = 0.5;
32const COLOR_RATIO_SEVENTY_PERCENT = 0.7;
33const COLOR_RATIO_FORTY_PERCENT = 0.4;
34const COLOR_RATIO_SIXTY_PERCENT = 0.6;
35const COLOR_RATIO_ONE_FIFTY_PERCENT = 1.5;
36const COORDINATE_NEGATIVE_ONE = -1;
37const BLUR_CONSTANT = 500;
38
39/**
40 * 三种标题栏相关数据
41 */
42const BREAK_POINT_VP_SM = 600;
43const BREAK_POINT_VP_MD = 840;
44const BREAK_POINT_SM = 'sm';
45const BREAK_POINT_MD = 'md';
46const BREAK_POINT_LG = 'lg';
47const SIDE_BAR_EMBED_MIN_WIDTH = 240;
48const SIDE_BAR_OVERLAY_WIDTH = 304;
49const SIDE_BAR_COMMON_WIDTH = 360;
50const CONTENT_MIN_WIDTH = 600;
51/**
52 * menubar避让区域宽度计算修正时用到的数据
53 */
54const MENUBAR_X_FIRST_THRESHOLD: number = 200;
55const MENUBAR_X_SECOND_THRESHOLD: number = 40;
56const MENUBAR_CORRECTION_OFFSET_VALUE: number = 92;
57
58/**
59 * 背景颜色的不透明度的枚举类型
60 *
61 * @enum { number }.
62 */
63export enum GradientAlpha {
64  /**
65   * 不透明度为0.2
66   *
67   */
68  OPACITY_20 = 1,
69  /**
70   * 不透明度为0.6
71   *
72   */
73  OPACITY_60 = 2,
74  /**
75   * 不透明度为0.8
76   */
77  OPACITY_80 = 3,
78  /**
79   * 不透明度为1.0
80   */
81  OPACITY_100 = 4
82}
83
84/**
85 * 背景颜色融合方式
86 *
87 * @enum { number }.
88 */
89export enum MixMode {
90  /**
91   * 两种颜色所占比例相同
92   */
93  AVERAGE = 1,
94  /**
95   * 一种颜色穿过另一种颜色
96   */
97  CROSS = 2,
98  /**
99   * 一种颜色渐渐转变为另一种颜色
100   */
101  TOWARDS = 3
102}
103
104/**
105 * 背景底色
106 *
107 * @enum { number }.
108 */
109export enum BackgroundTheme {
110  /**
111   * 黑色
112   */
113  DARK = 1,
114  /**
115   * 白色
116   */
117  LIGHT = 2,
118  /**
119   * 颜色值 #F1F3F5
120   */
121  DEFAULT = 3
122}
123
124@Component
125export struct AtomicServiceNavigation {
126  @State navPathStack?: NavPathStack = new NavPathStack();
127  @BuilderParam navigationContent?: Callback<void>;
128  @Prop title?: ResourceStr;
129  @Prop titleOptions?: TitleOptions = { isBlurEnabled: true };
130  @Prop gradientBackground?: GradientBackground;
131  @Prop hideTitleBar?: boolean;
132  @Prop navBarWidth?: Length;
133  @Prop mode?: NavigationMode;
134  @BuilderParam navDestinationBuilder?: NavDestinationBuilder = this.defaultNavDestinationBuilder;
135  @Prop navBarWidthRange?: [Dimension, Dimension];
136  @Prop minContentWidth?: Dimension;
137  @Prop sideBarOptions?: SideBarOptions;
138  @BuilderParam sideBarContent?: Callback<void>;
139  @State private showMaskLayer: boolean = false;
140  @State private marginWindowLeft: number = 16;
141  @State private currentBreakPoint: string = BREAK_POINT_SM;
142  @State private sideBarAttribute: sideBarAttributeSet = new sideBarAttributeSet();
143  @State private controlButtonVisible: boolean = true;
144  @State private titleBuilderPaddingEndWidth: number = 0;
145  @BuilderParam menus?: CustomBuilder | Array<NavigationMenuItem>;
146  stateChangeCallback?: Callback<boolean>;
147  modeChangeCallback?: Callback<NavigationMode>;
148
149  private settings: RenderingContextSettings = new RenderingContextSettings(true);
150  private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings);
151  @State private navigationWidth: number = 0;
152  @State private navigationHeight: number = 0;
153
154  private mainWindow?: window.Window;
155  private onWindowSizeChangeCallback?: Callback<Size>;
156  private onAvoidSafeAreaChangeCallback?: Callback<window.AvoidAreaOptions>;
157  private sideBarHelper?: SideBarHelper;
158  private atomicServiceIcon?: PixelMap;
159  private navigationBarVisibility: boolean = true;
160
161  @Builder
162  defaultNavDestinationBuilder(name: string, param?: Object) {
163  }
164
165  @Builder
166  BackgroundBuilder(gradientBackground: GradientBackground) {
167    Canvas(this.context)
168      .opacity(transparencyMapArray[(gradientBackground.alpha === undefined) ? GradientAlpha.OPACITY_20 :
169      gradientBackground.alpha])
170      .backdropBlur(BLUR_CONSTANT)
171      .height(this.navigationHeight)
172      .backgroundColor(gradientBackground.backgroundTheme === undefined ? backGroundColor[2] :
173      backGroundColor[gradientBackground.backgroundTheme - 1])
174      .onReady(() => {
175        if (gradientBackground.secondaryColor === undefined) {
176          //单色渐变
177          this.drawSingleGradient(this.context, gradientBackground.primaryColor,
178            gradientBackground.backgroundTheme === undefined ? backGroundColor[2] :
179            backGroundColor[gradientBackground.backgroundTheme - 1]);
180        } else {
181          if (gradientBackground.mixMode === MixMode.AVERAGE) {
182            //双色渐变五五分
183            this.drawGradientCanvasHalf(this.context, gradientBackground.primaryColor,
184              gradientBackground.secondaryColor);
185          } else if (gradientBackground.mixMode === MixMode.CROSS) {
186            //第一种双色渐变三七分
187            this.drawGradientCanvasCross(this.context, gradientBackground.primaryColor,
188              gradientBackground.secondaryColor);
189          } else {
190            //第二种双色渐变三七分
191            this.drawGradientCanvasTowards(this.context, gradientBackground.primaryColor,
192              gradientBackground.secondaryColor);
193          }
194          this.drawTransparentGradient(this.context,
195            gradientBackground.backgroundTheme === undefined ? BackgroundTheme.DEFAULT :
196            gradientBackground.backgroundTheme);
197        }
198      }).expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
199  }
200
201  aboutToAppear(): void {
202    this.initWindow();
203    this.initIcon();
204    this.initSideBarAttr();
205  }
206
207  /**
208   * 初始化侧边栏相关信息
209   */
210  private initSideBarAttr(): void {
211    if (this.titleOptions?.titleBarType !== TitleBarType.DRAWER) {
212      return;
213    }
214    this.sideBarAttribute = new sideBarAttributeSet();
215    this.sideBarHelper = new SideBarHelper();
216    let sideBarStatusListener = (show: boolean) => {
217      this.sideBarAttribute.showSideBar = show;
218      this.updateControlButtonVisibility();
219      if (this.sideBarOptions?.onChange) {
220        this.sideBarOptions.onChange(show);
221      }
222    };
223    this.sideBarHelper.registerListener(sideBarStatusListener);
224  }
225
226  /**
227   * 窗口初始化或者尺寸发生变化时,让menubar避让宽度更新
228   */
229  private freshMenubarAvoidAreaWidth(mainWindow: window.Window): void {
230    setTimeout(() => {
231      const atomicServiceBar: Nullable<AtomicServiceBar> = this.getUIContext().getAtomicServiceBar();
232      if (!atomicServiceBar) {
233        this.titleBuilderPaddingEndWidth = 0;
234        return;
235      }
236      let menubarX: number = atomicServiceBar.getBarRect().x;
237      let corretionWidth: number = 0;
238      if (menubarX > MENUBAR_X_FIRST_THRESHOLD) {
239        const mainWindowWidth: number = px2vp(mainWindow.getWindowProperties()?.windowRect?.width) - menubarX;
240        corretionWidth = mainWindowWidth > MENUBAR_X_FIRST_THRESHOLD ? 0 : mainWindowWidth;
241      } else if (menubarX < MENUBAR_X_SECOND_THRESHOLD) {
242        corretionWidth = menubarX + MENUBAR_CORRECTION_OFFSET_VALUE;
243      }
244      let currentWidth: number = atomicServiceBar.getBarRect().width;
245      this.titleBuilderPaddingEndWidth = corretionWidth > currentWidth ? corretionWidth : currentWidth;
246    }, 100);
247  }
248
249  /**
250   * 初始化window,并设置windowSizeChange监听,更新断点信息
251   */
252  private initWindow(): void {
253    let context = getContext(this) as common.UIAbilityContext;
254    context?.windowStage?.getMainWindow().then(mainWindow => {
255      if (!mainWindow) {
256        return;
257      }
258      this.mainWindow = mainWindow;
259      if (this.titleOptions?.titleBarType === TitleBarType.DRAWER) {
260        this.sideBarHelper?.updateLayout(this.currentBreakPoint, this.sideBarAttribute);
261      }
262      this.updateBreakPoint(mainWindow.getWindowProperties()?.windowRect?.width);
263      this.freshMenubarAvoidAreaWidth(mainWindow);
264      this.onWindowSizeChangeCallback = ((windowSize) => {
265        this.updateBreakPoint(windowSize?.width);
266        this.freshMenubarAvoidAreaWidth(mainWindow);
267      });
268      mainWindow.on('windowSizeChange', this.onWindowSizeChangeCallback);
269    }).catch((err: Error) => {
270      console.error(`AtomicServiceNavigation get main window failed, message is ${err?.message}`);
271    });
272  }
273
274  /**
275   * 初始化icon,用户如果设置了则用自定义的icon,没有设置则用元服务图标
276   */
277  private initIcon(): void {
278    if ((this.titleOptions?.titleBarType !== TitleBarType.ROUND_ICON &&
279      this.titleOptions?.titleBarType !== TitleBarType.SQUARED_ICON) || this.titleOptions?.titleIcon) {
280      return;
281    }
282    let bundleFlags: number = bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION;
283    let bundleInfo: bundleManager.BundleInfo = bundleManager.getBundleInfoForSelfSync(bundleFlags);
284    let iconRes: Resource = bundleInfo?.appInfo?.iconResource;
285    this.atomicServiceIcon = getContext(this)?.resourceManager?.getDrawableDescriptor(iconRes)?.getPixelMap();
286  }
287
288  /**
289   * 更新断点信息
290   */
291  private updateBreakPoint(windowWidth: number): void {
292    if (!windowWidth || windowWidth <= 0) {
293      return;
294    }
295    let widthVp = px2vp(windowWidth);
296    let newBreakPoint: string = '';
297    if (widthVp < BREAK_POINT_VP_SM) {
298      newBreakPoint = BREAK_POINT_SM;
299    } else if (widthVp < BREAK_POINT_VP_MD) {
300      newBreakPoint = BREAK_POINT_MD;
301    } else {
302      newBreakPoint = BREAK_POINT_LG;
303    }
304    if (this.currentBreakPoint !== newBreakPoint) {
305      this.currentBreakPoint = newBreakPoint;
306      this.updateMargin();
307      if (this.titleOptions?.titleBarType === TitleBarType.DRAWER) {
308        this.sideBarHelper?.updateLayout(this.currentBreakPoint, this.sideBarAttribute);
309      }
310    }
311  }
312
313  /**
314   * 更新边距
315   */
316  private updateMargin(): void {
317    switch (this.currentBreakPoint) {
318      case BREAK_POINT_MD:
319        this.marginWindowLeft = 24;
320        break;
321      case BREAK_POINT_LG:
322        this.marginWindowLeft = 32;
323        break;
324      case BREAK_POINT_SM:
325      default:
326        this.marginWindowLeft = 16;
327        break;
328    }
329  }
330
331  aboutToDisappear(): void {
332    if (this.onWindowSizeChangeCallback) {
333      this.mainWindow?.off('windowSizeChange', this.onWindowSizeChangeCallback);
334    }
335  }
336
337  /**
338   * 更新control button的可见性
339   */
340  updateControlButtonVisibility(): void {
341    if (this.titleOptions?.titleBarType !== TitleBarType.DRAWER) {
342      return;
343    }
344
345    // 如果侧边栏显示,那么控制图标一定需要显示
346    if (this.sideBarAttribute.showSideBar && this.controlButtonVisible) {
347      return;
348    }
349
350    if (this.currentBreakPoint === BREAK_POINT_LG) {
351      if (this.sideBarAttribute.showSideBar) {
352        if (!this.controlButtonVisible) {
353          this.controlButtonVisible = true;
354        }
355        return;
356      }
357
358      if (!this.navigationBarVisibility && !this.sideBarAttribute.showSideBar) {
359        if (this.controlButtonVisible) {
360          this.controlButtonVisible = false;
361        }
362      } else {
363        if (!this.controlButtonVisible) {
364          this.controlButtonVisible = true;
365        }
366      }
367    } else {
368      if (this.controlButtonVisible !== this.navigationBarVisibility) {
369        this.controlButtonVisible = this.navigationBarVisibility;
370      }
371    }
372  }
373
374  @Builder
375  drawerTitleBuilder() {
376    if (this.titleOptions?.titleBarType === TitleBarType.DRAWER && this.title) {
377      Row() {
378        Text(this.title)
379          .maxLines(2)
380          .minFontSize(14)
381          .maxFontSize(26)
382          .height(36)
383          .fontColor($r('sys.color.font_primary'))
384          .textOverflow({ overflow: TextOverflow.Ellipsis })
385          .fontWeight(FontWeight.Bold)
386          .width(0)
387          .layoutWeight(1)
388          .clip(true)
389          .textAlign(i18n.isRTL(i18n.System.getSystemLocale()) ? TextAlign.End : TextAlign.Start)
390      }
391      .padding({
392        start: LengthMetrics.vp(this.currentBreakPoint !== BREAK_POINT_LG ? this.marginWindowLeft + 36 + 8 :
393          (this.sideBarAttribute.showSideBar ? 8 : this.marginWindowLeft + 36 + 8)),
394        top: LengthMetrics.vp(10),
395        bottom: LengthMetrics.vp(10),
396        // 在Stack模式,或者非分栏模式下右侧需要有一定padding值,避免超长文本时不能避让menuBar
397        end: ((this.currentBreakPoint === BREAK_POINT_SM &&
398          (this.mode === NavigationMode.Auto || !this.mode)) || this.mode === NavigationMode.Stack) ?
399        LengthMetrics.vp(this.titleBuilderPaddingEndWidth) : LengthMetrics.vp(0)
400      })
401      .width('100%')
402    }
403  }
404
405  @Builder
406  maskLayer() {
407    Column() {
408    }
409    .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
410    .backgroundColor($r('sys.color.ohos_id_color_mask_thin'))
411    .transition(TransitionEffect.opacity(0).animation({ duration: 500, curve: Curve.Linear }))
412    .width('100%')
413    .height('100%')
414    .accessibilityLevel('no')
415    .onClick(() => {
416      this.changeSideBarWithAnimation(false);
417    })
418  }
419
420  @Styles
421  controlButtonStyle() {
422    .backgroundColor($r('sys.color.ohos_id_color_button_normal'))
423    .width(36)
424    .height(36)
425    .borderRadius(18)
426    .margin({
427      start: LengthMetrics.vp(this.marginWindowLeft),
428    })
429    .zIndex(2)
430    .draggable(false)
431    .onClick(() => {
432      const isShowSideBar = !this.sideBarHelper?.isShowSideBar();
433      this.changeSideBarWithAnimation(isShowSideBar);
434    })
435    .position({
436      start: LengthMetrics.vp(0),
437      top: LengthMetrics.vp(8)
438    })
439    .visibility(this.controlButtonVisible ? Visibility.Visible : Visibility.None)
440  }
441
442  @Builder
443  controlButton() {
444    if (this.sideBarAttribute.showSideBar) {
445      if (!this.sideBarOptions?.sideBarIcon) {
446        Button({ type: ButtonType.Circle }) {
447          SymbolGlyph($r('sys.symbol.open_sidebar'))
448            .fontWeight(FontWeight.Normal)
449            .fontSize($r('sys.float.ohos_id_text_size_headline7'))
450            .fontColor([$r('sys.color.ohos_id_color_titlebar_icon')])
451        }
452        .controlButtonStyle()
453      } else if (this.sideBarOptions?.sideBarIcon instanceof SymbolGlyphModifier) {
454        Button({ type: ButtonType.Circle }) {
455          SymbolGlyph()
456            .attributeModifier(this.sideBarOptions?.sideBarIcon)
457            .fontSize($r('sys.float.ohos_id_text_size_headline7'))
458            .size({
459              width: $r('sys.float.ohos_id_text_size_headline7'),
460              height: $r('sys.float.ohos_id_text_size_headline7')
461            })
462        }
463        .controlButtonStyle()
464      } else {
465        Image(this.sideBarOptions?.sideBarIcon)
466          .controlButtonStyle()
467      }
468    } else {
469      if (!this.titleOptions?.titleIcon) {
470        Button({ type: ButtonType.Circle }) {
471          SymbolGlyph($r('sys.symbol.close_sidebar'))
472            .fontWeight(FontWeight.Normal)
473            .fontSize($r('sys.float.ohos_id_text_size_headline7'))
474            .fontColor([$r('sys.color.ohos_id_color_titlebar_icon')])
475        }
476        .controlButtonStyle()
477      } else if (this.titleOptions?.titleIcon instanceof SymbolGlyphModifier) {
478        Button({ type: ButtonType.Circle }) {
479          SymbolGlyph()
480            .attributeModifier(this.titleOptions?.titleIcon)
481            .fontSize($r('sys.float.ohos_id_text_size_headline7'))
482            .size({
483              width: $r('sys.float.ohos_id_text_size_headline7'),
484              height: $r('sys.float.ohos_id_text_size_headline7')
485            })
486        }
487        .controlButtonStyle()
488      } else {
489        Image(this.titleOptions?.titleIcon)
490          .controlButtonStyle()
491      }
492    }
493  }
494
495  @Builder
496  sideBar() {
497    Row() {
498      if (this.sideBarContent) {
499        this.sideBarContent();
500      }
501    }
502    .border({ width: {
503      left: 0,
504      right: '1px',
505      top: 0,
506      bottom: 0
507    },
508      color: $r('sys.color.comp_divider'),
509      style: {
510        right: BorderStyle.Solid
511      }
512    })
513    .alignItems(VerticalAlign.Top)
514    .width('100%')
515    .height('100%')
516    .padding({ top: 56 })
517    .focusable(true)
518    .defaultFocus(true)
519    .backgroundColor(this.sideBarOptions?.sideBarBackground ?? $r('sys.color.ohos_id_color_sub_background'))
520    .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
521  }
522
523  @Styles
524  roundIconStyle() {
525    .backgroundColor('rgba(0, 0, 0, 0)')
526    .width(36)
527    .height(36)
528    .borderRadius(18)
529    .margin({
530      start: LengthMetrics.vp(this.marginWindowLeft),
531      end: LengthMetrics.vp(8),
532      top: LengthMetrics.vp(10),
533      bottom: LengthMetrics.vp(10)
534    })
535    .zIndex(2)
536    .draggable(false)
537    .position({
538      start: LengthMetrics.vp(0),
539      top: LengthMetrics.vp(0)
540    })
541  }
542
543  @Builder
544  roundIconBuilder() {
545    if (this.titleOptions?.titleIcon instanceof SymbolGlyphModifier) {
546      Button({ type: ButtonType.Circle }) {
547        SymbolGlyph()
548          .attributeModifier(this.titleOptions?.titleIcon)
549          .fontSize($r('sys.float.ohos_id_text_size_headline7'))
550          .size({
551            width: $r('sys.float.ohos_id_text_size_headline7'),
552            height: $r('sys.float.ohos_id_text_size_headline7')
553          })
554      }
555      .stateEffect(false)
556      .roundIconStyle()
557    } else {
558      Image(this.titleOptions?.titleIcon ?? this.atomicServiceIcon)
559        .roundIconStyle()
560    }
561  }
562
563  @Styles
564  longIconStyles() {
565    .height(36)
566    .width(36)
567    .margin({
568      start: LengthMetrics.vp(this.marginWindowLeft),
569      end: LengthMetrics.vp(-12),
570      top: LengthMetrics.vp(10),
571      bottom: LengthMetrics.vp(10)
572    })
573    .draggable(false)
574    .position({
575      start: LengthMetrics.vp(0),
576      top: LengthMetrics.vp(0)
577    })
578    .backgroundColor('rgba(0, 0, 0, 0)')
579  }
580
581  @Builder
582  longIconBuilder() {
583    if (this.titleOptions?.titleIcon instanceof SymbolGlyphModifier) {
584      Button() {
585        SymbolGlyph()
586          .attributeModifier(this.titleOptions?.titleIcon)
587          .fontSize($r('sys.float.ohos_id_text_size_headline7'))
588          .size({
589            width: $r('sys.float.ohos_id_text_size_headline7'),
590            height: $r('sys.float.ohos_id_text_size_headline7')
591          })
592      }
593      .longIconStyles()
594      .stateEffect(false)
595      .type(ButtonType.Normal)
596    } else {
597      Image(this.titleOptions?.titleIcon ?? this.atomicServiceIcon)
598        .longIconStyles()
599        .objectFit(ImageFit.Auto)
600    }
601  }
602
603  @Builder
604  titleBuilder() {
605    if (this.titleOptions?.titleBarType === TitleBarType.SQUARED_ICON) {
606      this.longIconBuilder();
607    } else {
608      this.roundIconBuilder();
609    }
610  }
611
612  /**
613   * 根据当前屏幕尺寸判断是否要显示用户插入的布局
614   */
615  private isShowMenus(): boolean {
616    return this.mode === NavigationMode.Stack && this.currentBreakPoint !== BREAK_POINT_SM;
617  }
618
619  /**
620   * 根据用户传入的类型和当前屏幕尺寸判断是否要显示NavigationMenuItem列表
621   */
622  private getMenuItemArray(): undefined | Array<NavigationMenuItem> {
623    return this.isShowMenus() && this.menus instanceof Array ? this.menus : undefined;
624  }
625
626  build() {
627    Stack() {
628      Column() {
629        if (this.gradientBackground !== undefined) {
630          this.BackgroundBuilder({
631            primaryColor: this.gradientBackground.primaryColor,
632            secondaryColor: this.gradientBackground.secondaryColor,
633            backgroundTheme: this.gradientBackground.backgroundTheme,
634            mixMode: this.gradientBackground.mixMode,
635            alpha: this.gradientBackground.alpha
636          })
637        }
638      }
639      if (this.titleOptions?.titleBarType === TitleBarType.DRAWER) {
640        this.controlButton();
641
642        // 断点为LG时,侧边栏嵌入到组件内;断点为MD或SM时,侧边栏悬浮在B栏上
643        SideBarContainer(this.currentBreakPoint === BREAK_POINT_LG ? SideBarContainerType.Embed :
644        SideBarContainerType.Overlay) {
645          this.sideBar()
646
647          Stack() {
648            Navigation(this.navPathStack) {
649              if (this.navigationContent) {
650                this.navigationContent();
651              }
652            }
653            .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
654            .title(this.drawerTitleBuilder(), this.getTitleOption())
655            .menus(this.isShowMenus() && this.menus instanceof Function ? this.menus() : this.getMenuItemArray())
656            .titleMode(NavigationTitleMode.Mini)
657            .hideBackButton(true)
658            .hideTitleBar(this.hideTitleBar)
659            .navBarWidth(this.navBarWidth)
660            .navBarPosition(NavBarPosition.Start)
661            .mode(this.mode)
662            .navDestination(this.navDestinationBuilder)
663            .navBarWidthRange(this.navBarWidthRange)
664            .minContentWidth(this.minContentWidth)
665            .onNavigationModeChange(this.modeChangeCallback)
666            .onNavBarStateChange((visible: boolean) => {
667              this.navigationBarVisibility = visible;
668              this.updateControlButtonVisibility();
669              if (this.stateChangeCallback) {
670                this.stateChangeCallback(visible);
671              }
672            })
673
674            if (this.titleOptions?.titleBarType === TitleBarType.DRAWER && this.sideBarAttribute.showSideBar &&
675              this.currentBreakPoint !== BREAK_POINT_LG && this.showMaskLayer) {
676              this.maskLayer();
677            }
678          }
679          .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
680        }
681        .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
682        .sideBarWidth(this.sideBarAttribute.sideBarWidth)
683        .minContentWidth(this.sideBarAttribute.minContentWidthOfSideBar)
684        .minSideBarWidth(this.sideBarAttribute.minSideBarWidth)
685        .maxSideBarWidth(this.sideBarAttribute.maxSideBarWidth)
686        .showControlButton(false)
687        .showSideBar(this.sideBarAttribute.showSideBar)
688        .onChange((showSideBar: boolean) => {
689          if (this.sideBarHelper?.isShowSideBar() !== showSideBar) {
690            this.changeSideBarWithAnimation(showSideBar);
691          }
692        })
693      } else {
694        Navigation(this.navPathStack) {
695          if (this.navigationContent) {
696            this.navigationContent();
697          }
698        }
699        .title((this.titleOptions?.titleIcon || (this.titleOptions?.titleBarType && !this.title)) ? this.titleBuilder() :
700        this.title,
701          this.getTitleOption())
702        .titleMode(NavigationTitleMode.Mini)
703        .menus(this.isShowMenus() && this.menus instanceof Function ? this.menus() : this.getMenuItemArray())
704        .hideBackButton(true)
705        .hideTitleBar(this.hideTitleBar)
706        .navBarWidth(this.navBarWidth)
707        .navBarPosition(NavBarPosition.Start)
708        .mode(this.mode)
709        .navDestination(this.navDestinationBuilder)
710        .navBarWidthRange(this.navBarWidthRange)
711        .minContentWidth(this.minContentWidth)
712        .onNavBarStateChange(this.stateChangeCallback)
713        .onNavigationModeChange(this.modeChangeCallback)
714      }
715    }
716    .align(Alignment.TopStart)
717    .width('100%')
718    .height('100%')
719    .onSizeChange((oldValue: SizeOptions, newValue: SizeOptions) => {
720      this.navigationWidth = newValue.width as number;
721      this.navigationHeight = newValue.height as number;
722    })
723    .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
724  }
725
726  /**
727   * 双色渐变下两种颜色各占50%的实现,把整个画布区域分为两个一样的矩形在绘制
728   * @param context 画布上下文
729   * @param primaryColor 第一种颜色
730   * @param secondaryColor 第二种颜色
731   */
732  drawGradientCanvasHalf(context: CanvasRenderingContext2D, primaryColor: ResourceColor,
733    secondaryColor: ResourceColor): void {
734    let height = this.navigationHeight * COLOR_RATIO_THIRTY_PERCENT;
735    let grad1 =
736      context.createLinearGradient(COORDINATE_NEGATIVE_ONE * this.navigationWidth * COLOR_RATIO_FIFTY_PERCENT,
737        height, this.navigationWidth * COLOR_RATIO_FIFTY_PERCENT, 0);
738    let grad2 = context.createLinearGradient(this.navigationWidth * COLOR_RATIO_ONE_FIFTY_PERCENT, height,
739      this.navigationWidth * COLOR_RATIO_FIFTY_PERCENT, 0);
740    grad1.addColorStop(0, this.resourceColorToString(primaryColor));
741    grad1.addColorStop(COLOR_RATIO_FIFTY_PERCENT, this.resourceColorToString(primaryColor));
742    grad1.addColorStop(1, this.resourceColorToString(secondaryColor));
743    grad2.addColorStop(0, this.resourceColorToString(primaryColor));
744    grad2.addColorStop(COLOR_RATIO_FIFTY_PERCENT, this.resourceColorToString(primaryColor));
745    grad2.addColorStop(1, this.resourceColorToString(secondaryColor));
746    context.fillStyle = grad1;
747    context.fillRect(0, 0, this.navigationWidth * COLOR_RATIO_FIFTY_PERCENT, height);
748    context.fillStyle = grad2;
749    context.fillRect(this.navigationWidth * COLOR_RATIO_FIFTY_PERCENT, 0, this.navigationWidth, height);
750  }
751
752  /**
753   * 双色渐变的一种实现,把画布先分为两个大矩形,再把其中一个矩形分为两个小矩形
754   * @param context 画布上下文
755   * @param primaryColor 第一种颜色
756   * @param secondaryColor 第二种颜色
757   */
758  drawGradientCanvasCross(context: CanvasRenderingContext2D, primaryColor: ResourceColor,
759    secondaryColor: ResourceColor): void {
760    let height = this.navigationHeight * COLOR_RATIO_THIRTY_PERCENT;
761    let grad1 = context.createLinearGradient(0, 0, COLOR_RATIO_SEVENTY_PERCENT * this.navigationWidth, 0);
762    grad1.addColorStop(0, this.resourceColorToString(primaryColor));
763    grad1.addColorStop(COLOR_RATIO_FIFTY_PERCENT, this.resourceColorToString(primaryColor));
764    grad1.addColorStop(1, this.resourceColorToString(secondaryColor));
765    context.fillStyle = grad1;
766    context.fillRect(0, 0, COLOR_RATIO_SEVENTY_PERCENT * this.navigationWidth, height);
767    let y1 = (COLOR_RATIO_FIFTY_PERCENT * height - COLOR_RATIO_THIRTY_PERCENT * this.navigationWidth) > 0 ?
768      COLOR_RATIO_FIFTY_PERCENT * height - COLOR_RATIO_THIRTY_PERCENT * this.navigationWidth : 0;
769    let grad2 =
770      context.createLinearGradient(COLOR_RATIO_SEVENTY_PERCENT * this.navigationWidth, y1, this.navigationWidth,
771        height * COLOR_RATIO_FIFTY_PERCENT);
772    grad2.addColorStop(0, this.resourceColorToString(secondaryColor));
773    grad2.addColorStop(COLOR_RATIO_FORTY_PERCENT, this.resourceColorToString(secondaryColor));
774    grad2.addColorStop(1, this.resourceColorToString(primaryColor));
775    context.fillStyle = grad2;
776    context.fillRect(COLOR_RATIO_SEVENTY_PERCENT * this.navigationWidth - RECTANGLE_OUTSIDE_OFFSET_ONE, 0,
777      this.navigationWidth * COLOR_RATIO_THIRTY_PERCENT + RECTANGLE_OUTSIDE_OFFSET_ONE,
778      height * COLOR_RATIO_FIFTY_PERCENT + RECTANGLE_OUTSIDE_OFFSET_ONE);
779    let y2 = (COLOR_RATIO_FIFTY_PERCENT * height - COLOR_RATIO_THIRTY_PERCENT * this.navigationWidth) > 0 ?
780      COLOR_RATIO_FIFTY_PERCENT * height + COLOR_RATIO_THIRTY_PERCENT * this.navigationWidth : height;
781    let grad3 =
782      context.createLinearGradient(COLOR_RATIO_SEVENTY_PERCENT * this.navigationWidth, y2, this.navigationWidth,
783        height * COLOR_RATIO_FIFTY_PERCENT);
784    grad3.addColorStop(0, this.resourceColorToString(secondaryColor));
785    grad3.addColorStop(COLOR_RATIO_FORTY_PERCENT, this.resourceColorToString(secondaryColor));
786    grad3.addColorStop(1, this.resourceColorToString(primaryColor));
787    context.fillStyle = grad3;
788    context.fillRect(COLOR_RATIO_SEVENTY_PERCENT * this.navigationWidth - RECTANGLE_OUTSIDE_OFFSET_ONE,
789      height * COLOR_RATIO_FIFTY_PERCENT,
790      COLOR_RATIO_THIRTY_PERCENT * this.navigationWidth + RECTANGLE_OUTSIDE_OFFSET_ONE,
791      height * COLOR_RATIO_FIFTY_PERCENT);
792  }
793
794  /**
795   * 双色渐变的一种实现,从矩形左上角颜色渐变到右下角
796   * @param context 画布上下文
797   * @param primaryColor 第一种颜色
798   * @param secondaryColor 第二种颜色
799   */
800  drawGradientCanvasTowards(context: CanvasRenderingContext2D, primaryColor: ResourceColor,
801    secondaryColor: ResourceColor): void {
802    let height = this.navigationHeight * COLOR_RATIO_THIRTY_PERCENT;
803    let grad = context.createLinearGradient(0, 0, this.navigationWidth, height);
804    grad.addColorStop(0, this.resourceColorToString(primaryColor));
805    grad.addColorStop(COLOR_RATIO_FORTY_PERCENT, this.resourceColorToString(primaryColor));
806    grad.addColorStop(1, this.resourceColorToString(secondaryColor));
807    context.fillStyle = grad;
808    context.fillRect(0, 0, this.navigationWidth, height);
809  }
810
811  /**
812   * 双色渐变下透明效果的实现
813   * @param context 画布上下文
814   * @param backgroundTheme 背景色底色
815   */
816  drawTransparentGradient(context: CanvasRenderingContext2D, backgroundTheme: BackgroundTheme): void {
817    let height = this.navigationHeight * COLOR_RATIO_THIRTY_PERCENT;
818    let grad = context.createLinearGradient(0, 0, 0, height);
819    grad.addColorStop(0, backGroundTransparentGradientColor[backgroundTheme - 1][0]);
820    grad.addColorStop(1, backGroundTransparentGradientColor[backgroundTheme - 1][1]);
821    context.fillStyle = grad;
822    context.fillRect(0, 0, this.navigationWidth + RECTANGLE_OUTSIDE_OFFSET_ONE, height +
823      RECTANGLE_OUTSIDE_OFFSET_ONE);
824    if (backgroundTheme === BackgroundTheme.DARK) {
825      context.fillStyle = Color.Black;
826      context.fillRect(0, height, this.navigationWidth, this.navigationHeight - height);
827    }
828  }
829
830  /**
831   * 单色渐变:
832   * @param context 画布上下文
833   * @param primaryColor createLinearGradient初始颜色为primaryColor,结束颜色为底色
834   * @param backgroundColor 颜色线性渐变的结束颜色
835   */
836  drawSingleGradient(context: CanvasRenderingContext2D,
837    primaryColor: ResourceColor, backgroundColor: string): void {
838    let height = this.navigationHeight * COLOR_RATIO_SIXTY_PERCENT;
839    let grad1 = context.createLinearGradient(0, 0, 0, height);
840    grad1.addColorStop(0, this.resourceColorToString(primaryColor));
841    grad1.addColorStop(1, backgroundColor);
842    context.fillStyle = grad1;
843    context.fillRect(0, 0, this.navigationWidth, height);
844    //当背景为黑色的时候需要特殊处理下
845    if (backgroundColor === backGroundColor[0]) {
846      context.fillStyle = Color.Black
847      context.fillRect(0, height, this.navigationWidth, this.navigationHeight * (1 - COLOR_RATIO_SIXTY_PERCENT));
848    }
849  }
850
851  /**
852   * ResourceColor转化为能作为addColorStop使用的字符串
853   * @param resource ResourceColor = Color | number | string | Resource,对于Resource转化为直接使用的字符串需要特殊处理
854   * @returns
855   */
856  resourceColorToString(resource: ResourceColor): string {
857    if (typeof resource === 'object') {
858      try {
859        return getContext(this).resourceManager.getStringSync(resource)
860      } catch (error) {
861        let code = (error as BusinessError).code;
862        let message = (error as BusinessError).message;
863        hilog.error(0x0000, 'AtomicServiceNavigation',
864          `resourceColorToString - systemResourceManager getStringValue failed, error code: ${code}, message: ${message}.`);
865      }
866      return '';
867    } else {
868      return resource.toString();
869    }
870  }
871
872  /**
873   * 获取NavigationTitleOptions
874   */
875  private getTitleOption(): NavigationTitleOptions {
876    return {
877      backgroundColor: this.titleOptions?.backgroundColor,
878      backgroundBlurStyle: this.titleOptions?.isBlurEnabled ? BlurStyle.COMPONENT_THICK : BlurStyle.NONE,
879      barStyle: this.titleOptions?.barStyle
880    };
881  }
882
883  /**
884   * 更新control button的可见性,并运行动画效果
885   */
886  private changeSideBarWithAnimation(isShowSidebar: boolean): void {
887    animateTo({
888      duration: 500,
889      curve: curves.cubicBezierCurve(0.2, 0.2, 0.1, 1),
890      onFinish: () => {
891        this.showMaskLayer = isShowSidebar;
892      }
893    }, () => {
894      if (this.sideBarHelper) {
895        this.sideBarHelper.setShowSideBar(isShowSidebar);
896      }
897      this.showMaskLayer = true;
898    })
899  }
900}
901
902/**
903 * 侧边栏相关参数
904 */
905@Observed
906class sideBarAttributeSet {
907  /**
908   * 侧边栏宽度
909   */
910  public sideBarWidth: number = SIDE_BAR_OVERLAY_WIDTH;
911  /**
912   * 侧边栏最小宽度
913   */
914  public minSideBarWidth = SIDE_BAR_OVERLAY_WIDTH;
915  /**
916   * 侧边栏最大宽度
917   */
918  public maxSideBarWidth = SIDE_BAR_OVERLAY_WIDTH;
919  /**
920   * 侧边栏内容最小宽度
921   */
922  public minContentWidthOfSideBar: Dimension = SIDE_BAR_COMMON_WIDTH;
923  /**
924   * 侧边栏显示隐藏状态
925   */
926  public showSideBar: boolean = false;
927}
928
929/**
930 * 侧边栏对外暴露相关参数
931 */
932export interface SideBarOptions {
933  /**
934   * 侧边栏背景
935   */
936  sideBarBackground?: ResourceColor;
937
938  /**
939   * 侧边栏显示隐藏状态变化监听
940   */
941  onChange?: (show: boolean) => void;
942
943  /**
944   * 侧边栏icon
945   */
946  sideBarIcon?: Resource | SymbolGlyphModifier;
947}
948
949/**
950 * 标题相关参数
951 */
952export interface TitleOptions {
953  /**
954   * 背景色
955   */
956  backgroundColor?: ResourceColor,
957
958  /**
959   * 是否启动模糊效果
960   */
961  isBlurEnabled?: boolean,
962
963  /**
964   * 标题栏风格
965   */
966  barStyle?: BarStyle,
967
968  /**
969   * 标题栏类型
970   */
971  titleBarType?: TitleBarType,
972
973  /**
974   * 标题栏icon
975   */
976  titleIcon?: Resource | SymbolGlyphModifier;
977}
978
979export type NavDestinationBuilder = (name: string, param?: Object) => void;
980
981export interface GradientBackground {
982  primaryColor: ResourceColor,
983  secondaryColor?: ResourceColor,
984  backgroundTheme?: BackgroundTheme,
985  mixMode?: MixMode,
986  alpha?: GradientAlpha
987}
988
989/**
990 * 侧边栏辅助管理类
991 */
992class SideBarHelper {
993  private isExpandSideBar: boolean = false;
994  private listener?: Function;
995
996  /**
997   * 注册侧边栏显隐状态变化监听
998   *
999   * @param listener 监听器对象
1000   */
1001  public registerListener(listener: Function): void {
1002    this.listener = listener;
1003  }
1004
1005  /**
1006   * 取消注册监听
1007   */
1008  public unregisterListener(): void {
1009    this.listener = undefined;
1010  }
1011
1012  /**
1013   * 获取侧边栏显示隐藏状态
1014   *
1015   * @returns 侧边栏是否显示
1016   */
1017  public isShowSideBar(): boolean {
1018    return this.isExpandSideBar;
1019  }
1020
1021  /**
1022   * 设置侧边栏显示隐藏状态
1023   *
1024   * @param value 显示或隐藏状态
1025   */
1026  public setShowSideBar(value: boolean): void {
1027    this.isExpandSideBar = value;
1028    if (this.listener) {
1029      this.listener(value);
1030    }
1031  }
1032
1033  /**
1034   * 更新侧边栏布局
1035   *
1036   * @param breakPoint 当前断点
1037   * @param layout 布局参数
1038   */
1039  public updateLayout(breakPoint: string, layout: sideBarAttributeSet): void {
1040    if (breakPoint === BREAK_POINT_LG) {
1041      this.updateLGLayout(layout);
1042    } else {
1043      this.updateCommonLayout(layout);
1044    }
1045  }
1046
1047  /**
1048   * 更新除LG外窗口模式的布局
1049   */
1050  private updateCommonLayout(layout: sideBarAttributeSet): void {
1051    layout.sideBarWidth = SIDE_BAR_OVERLAY_WIDTH;
1052    layout.minContentWidthOfSideBar = '100%';
1053  }
1054
1055  /**
1056   * 更新LG窗口模式布局
1057   */
1058  private updateLGLayout(layout: sideBarAttributeSet): void {
1059    layout.sideBarWidth = SIDE_BAR_EMBED_MIN_WIDTH;
1060    layout.minContentWidthOfSideBar = CONTENT_MIN_WIDTH;
1061  }
1062}
1063
1064/**
1065 * 标题栏类型
1066 */
1067export enum TitleBarType {
1068  /**
1069   * 长图标类型标题栏
1070   */
1071  SQUARED_ICON = 1,
1072
1073  /**
1074   * 圆形图标类型标题栏
1075   */
1076  ROUND_ICON,
1077
1078  /**
1079   * 抽屉类型标题栏
1080   */
1081  DRAWER
1082}