• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2023-2023 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16import { KeyCode } from '@ohos.multimodalInput.keyCode'
17
18export enum EditableLeftIconType {
19  Back,
20  Cancel
21}
22
23export declare type EditableTitleBarMenuItem = {
24  value: ResourceStr
25  isEnabled: boolean
26  action?: () => void
27}
28
29const PUBLIC_CANCEL = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMAAAADACAYAAABS3Gw' +
30  'HAAAAIGNIUk0AAHomAACAhAAA+gAAAIDoAAB1MAAA6mAAADqYAAAXcJy6UTwAAAAEZ0FNQQAAsY58+1GTAAAAAXNSR0IA' +
31  'rs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAAOxAAADsQBlSsOGwAABKpJREFUeNrt3bFrlHccx/Ff4o2dgkOQ4' +
32  'JzR4SYJgksnbYcGOrTQsUPtpE4d/RNcFBeFlg7NUlTSTrXQQgsKGV0KHTs4ODiJSL8PdxaUGJK7pPc893m94TvleZLnnt' +
33  '/7fcc9z5FrDQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAnDQrPTuej2u2a87VvKp5XLNT87OlGiSf1lx' +
34  '6Zz2/q3kkgLdZr7k5lX8/7tZ8XfOCU4OgW887U/n341bN9T6s56menLAfaj464OfdM8iF6XYv+dV7+X+pOX/ANuOatZqH' +
35  'Amjti5prh9jurAgGI//mIbbtIvi15u9FHvBqD07a50fYdqtmt+YDrg1a/jd8tuiD7kMA4yNuL4LlkH+WtV/KAEYz7COC4' +
36  'cs/69ovXQB7M+4ngmHLP8/aL1UAD+bYVwTDlX/etT8W+nAV6M82uQS6PuP+rg4NV/5vBNDa6za5HLY9xzO5CIYl/9OaT5' +
37  'obYf/xrE1uioggQ/6LNf/04QGd6tHJFQH5owMQAfnjAxAB+eMDEAH54wMQAfnjAxAB+eMDEAH54wMQAfnjAxAB+eMDEAH' +
38  '54wMQAfnjAxAB+eMDEAH54wMQAfnjAxAB+eMDEAH54wMQAfnjAxAB+eMDEAH54wMQAfnjAxAB+eMDSI6A/AKIjYD8AoiN' +
39  'gPwCiI2A/AKIjYD8AoiNgPwCiI2A/AKIjYD8AoiNgPwCiI2A/AKIjYD8AoiNgPwCiI2A/AKIjYD8AoiNgPwCiI2A/AKIj' +
40  'YD8AoiNgPwCiI2A/AKIjYD8AoiNgPwCiI1gjfyLY8UpmJnNqbjrc/yO32pOk98rQPIrwWnyCyA5AvILQATkF4AIyC8AEZ' +
41  'BfACIgvwBEQH4BiID8J8qqU3BiPJ8O+XuMO8Eng8/2CID85BcA+ckvAPKT35tg8h+n/KP2/3/2SADojfzf1+yKYH7cBxi' +
42  'm/N39hWX8RnsBkP9Q8r9BBAKIlV8EAoiXXwQCiJdfBAKIl18EAoiXXwQz4D5A/+Tv2KjZmuPvdfu6T+AVYJDyd3Qfo17G' +
43  'b7QXAPkPzTMRCCBVfhEIIF5+EQggXn4RCCBefhEIIF5+EQggXn4RCCBefhEIIF5+EQggXn4RCCBefhEIIF5+EQggXn4RC' +
44  'CBefhEIIF5+EQggXn4RCCBefhEIwD+qFUFuAOQXQWwA5BdBbADkF0FsAOQXQWwA5BdBbADkF0FsAOQXQWwA5BdBbADkF0' +
45  'FsAOQXQWwA5BdBbADkF0FsAOQXQWwA5BdBbADkF0FsAOQXQWwA5BdBbADkF0FsAOQXQWwA5BdBdAD3a8bkj4rgTM2PAmj' +
46  'ty5or5I+L4FzNHzV/LfKB9OGb4rfJP0iO49xvL/pB9CGAMfljIxgLoLUR+WMjGAmgtT3yx0awJ4DWdsgfG8HOog+6D1eB' +
47  'ntR8WLNB/sFzlKtDnfw3BNDa65rfp2+I3hfBo5rL5B9UBFttcoNzP35qk8vfLxZ9sCs9OnHdG6Kvps8e3TXiVzWPp88Ut' +
48  '3k1OLr1vFpz6Z31/LbmntMDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAO/wLvsR65mx80NAAAAABJRU' +
49  '5ErkJggg=='
50
51const PUBLIC_OK = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMAAAADACAYAAABS3' +
52  'GwHAAAAIGNIUk0AAHomAACAhAAA+gAAAIDoAAB1MAAA6mAAADqYAAAXcJy6UTwAAAAEZ0FNQQAAsY58+1GTAAAAAXNSR0IArs4c6QAAAAZ' +
53  'iS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAAOxAAADsQBlSsOGwAAA+lJREFUeNrt3bFrFgccx+GLlSDi4JDBITiJZHBwEBGRIoqKoIu6iVMd3' +
54  'OosCg6W0sm/wEFUDDgpCDoIDoqOKqIoHUrp4CDFoUMRB39HLotoeXMpMXff54EfFE0ivv1+kpQGrmkAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +
55  'AAAAAAAAAAAAAAIBJTHkJGIhddT/V7ajbUPey7l7dlbpPAmCs1tb9Wne2++cvPa07Vfd7nw/+g9eXVT7+m3Wn69Z8421m6w7WXa37KADGN' +
56  'v4TE7ztTN36uvtL/UPWeJ0Z+PgXnejzBwmAMYx/8VuhTQIgcfy9CYCxjP9D3TsBkPqZf95XAFLH3372vyAAEsf/T93Ruvd93tn/CWbo4z9' +
57  'c96jvBxAAseMXANHjFwDR4xcA0eMXANHjFwDR4xcA0eMXANHjFwDR4xcA0eMXANHjFwDR4xcA0eMXANHjFwDR4xcA0eMXANHjFwDR4xcA0' +
58  'eMXANHjFwDR4xcA0eMXANHjFwDR4xcA0eMXANHjFwDR4xcA0eP/HgG0z3f9uVl45uu2ZuGBBu3zXn9rej7mEuMfSgDtA46v1c195ff+rbt' +
59  'U94stGv9KWqnHpLaPsXxSt/k//iXsq9vY9HjUJca/2gNoH2e/c4K32yUC4x9bAOvqrjeTP41GBMY/qgC2151Z4vuIwPhXxEo8I2xdz/c7W' +
60  '3fZXo1/6F8B/q471/N9fSUw/sEH8LEb8hYRGH9iAK3HdafrpkVg/IkBtA8yflt3bBn/3SEC4x9sAK3Xda9EYPypAYjA+OMDEIHxxwcgAuO' +
61  'PD0AExh8fgAiMPz4AERh/fAAiMP74AERg/PEBiMD44wMQgfHHByAC448PQATGHx+ACIw/PgARGH98AMkRGL8AYiMwfgHERmD8AoiNwPgFE' +
62  'BuB8QsgNgLjF0BsBMYvgNgIjF8AsREYvwBiIzB+AcRGYPwCiI3A+AUQG4HxCyA2AuMXQGwExi+A2AiMXwCxERi/AGIjMH4BjDaCTXUP6j5' +
63  '9423aJ2PeMH4BjDWCHd24n9f9+cXvHa+7U7fb+Fe/qeC/ezvU+e5bleX40A11pm6u+xapMX5fARK+Eix+u7O1brbp/1Bw4xfAoCP4Pxi/A' +
64  'GIjMH4BxEZg/AKIjcD4BRAbgfELIDYC4xdAbATGL4DYCIxfAIOL4EXdkbrpZX6sd3WH6p56WQUwJG/qbtX92Cz8EFwfD+sO1L31cq4uU16' +
65  'CibU/M3Sy7nzdlgnf51ndxbrbXj4BjCmEPXX76/Y2Cz8DNNP9+l91f3Sf8e92AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +
66  'AAAAAzKZwK1uX4kZ6mnAAAAAElFTkSuQmCC'
67
68const PUBLIC_BACK = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMAAAADACAY' +
69  'AAABS3GwHAAAAIGNIUk0AAHomAACAhAAA+gAAAIDoAAB1MAAA6mAAADqYAAAXcJy6UTwAAAAEZ0FNQQAAsY58+1GTAAAAA' +
70  'XNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAAOxAAADsQBlSsOGwAAA8VJREFUeNrt3LFLlHEYwPFXz0G' +
71  'iIZpEoikkwsFRIiK3gqCigxIC/4Kmhv6OoChouaGoqKCgCKducGh0cDAIamhwiCaHCIeelztpUszee/vl8/nAM3Vd8nufr' +
72  '+fddVYVAAAAAAAAAAAAAAAAAAAAAABQijFH0KhrMd2Y2ZitmNWYRzHLjkYAB9lUzMOYizv8eS/mZsymoypLxxE0svzvY07' +
73  'vcpu5mOmY145LAAdx+U/u4bZzwx+JPjq2cow7glaWf1vXsQkg6/JvPwoggJTLjwDSL/8nRyiAzN/5nzpGAWRd/n7MM0cpg' +
74  'IzLvx6z6CjL453gdpZ/IWbDcQrA8iMAy48ALD8CsPwIwPIjAMuPACw/ArD8CMDyIwDLjwAsPwKw/AjA8iMAy48ALD8CsPw' +
75  'IwPIjAMuPACw/ArD85A3A8pM2AMtP2gAsP2kDsPykDcDykzYAy0/aACw/aQOw/KQNwPKTNgDLT9oALD9pA7D8pA3A8pM2A' +
76  'MtP2gAsP2kDsPykDcDykzYAy0/aACw/aQOw/KQNwPKTNgDLT9oALD9pA7D8pA3A8pM2AMtP2gAsP2kDsPykDcDykzYAy0/' +
77  'aACw/aQOw/KQNwPLz3xlv6H4mYp5YfrI+AizF9BwnI/AlZi3mbsxy03feaeh+HsQcc60YgSMxMzE3YmZj3sX8LOlHoPoLn' +
78  'HedaEE35n5pzwF856dN9SPBpZICmHRNaNnlkgL46nrQsvmSAqhftlx1TWjR4ZICqPVcE1q0XloA96rBa7XQhl5pAWzFXKm' +
79  '8i8vo9WMeN3VnnQa/sO8xL2POxEy7Toxo+RdjNpu6w1F9HuBqNXi99lw1eKMM9utHzIeYV8MftbccCQAAAAAAsBdt/XLc+s' +
80  'Py9W+MmPqL+1iJuVA1+C4gdFr6d77FvK0GH2nb739lPR5zNuZ51eBnQhFAJQIEIAIEIAIEIAIEIAIEIAIEIAIEIAIEIAIE' +
81  'IAIEIAIEIAIEIAIEIAIEIAIEIAIEIAIEIAIEIAIEIAIEIAIEIAIEIAIEIAIEIAIEIAIEIAIEIAIEIAIEIAIEIAIEIAIEIAI' +
82  'EIAIEIAIEIAIEIAIEIAIEIAIEIAIEIAIEIAIEIAIEIAIEIAIEIAIE8M8jmBlGgABSRnAqZiXms+MUQNYIDnkUKMu4I/gj6z' +
83  'ELMRv7/PsnHKEAMkcw6fgEkDmCNUcngMwRvHFsngRnfWJcL/9tRyaAgxrB+ZijO9ymH7MUs+m4yjLmCBozEXMr5nr1+9We1' +
84  'ZgXMXccDwAAAAAAAAAAAAAAAAAAAAAAwO5+AfVgtqHKRnawAAAAAElFTkSuQmCC'
85
86@Component
87export struct EditableTitleBar {
88  leftIconStyle: EditableLeftIconType
89  title: ResourceStr
90  menuItems: Array<EditableTitleBarMenuItem>
91  onSave?: () => void
92  onCancel?: () => void
93
94  @State titleMaxWidth: number = 0
95  @State backActive: boolean = false
96
97  static readonly maxCountOfExtraItems = 2
98  private static readonly totalHeight = 56
99  private static readonly leftPadding = 12
100  private static readonly rightPadding = 12
101  private static readonly titlePadding = 16
102
103  build() {
104    Flex({
105      justifyContent: FlexAlign.SpaceBetween,
106      alignItems: ItemAlign.Stretch
107    }) {
108      Row() {
109        if (this.leftIconStyle == EditableLeftIconType.Back) {
110          Navigator()
111            .active(this.backActive)
112
113          ImageMenuItem({ item: {
114            value: PUBLIC_BACK,
115            isEnabled: true,
116            action: () => this.backActive = true
117          } })
118        } else {
119          ImageMenuItem({ item: {
120            value: PUBLIC_CANCEL,
121            isEnabled: true,
122            action: () => this.onCancel && this.onCancel()
123          } })
124        }
125
126        Column() {
127          Text(this.title)
128            .fontWeight(FontWeight.Medium)
129            .fontSize($r('sys.float.ohos_id_text_size_headline8'))
130            .fontColor($r('sys.color.ohos_id_color_titlebar_text'))
131            .maxLines(1)
132            .textOverflow({ overflow: TextOverflow.Ellipsis })
133            .constraintSize({ maxWidth: this.titleMaxWidth })
134        }
135        .justifyContent(FlexAlign.Start)
136        .alignItems(HorizontalAlign.Start)
137        .constraintSize({ maxWidth: this.titleMaxWidth })
138      }
139      .margin({ left: $r('sys.float.ohos_id_default_padding_start') })
140
141      EditableTitleBarMenuSection({
142        menuItems: this.menuItems,
143        onSave: this.onSave
144      })
145    }
146    .width('100%')
147    .height(EditableTitleBar.totalHeight)
148    .backgroundColor($r('sys.color.ohos_id_color_background'))
149    .onAreaChange((_oldValue: Area, newValue: Area) => {
150      let nValue = Number(newValue.width)
151      nValue = nValue - EditableTitleBar.leftPadding - EditableTitleBar.rightPadding - EditableTitleBar.titlePadding
152      nValue = nValue - ImageMenuItem.imageHotZoneWidth - ImageMenuItem.imageHotZoneWidth
153      if (this.menuItems === undefined) {
154        this.titleMaxWidth = nValue
155        return
156      }
157      if (this.menuItems.length > EditableTitleBar.maxCountOfExtraItems) {
158        this.titleMaxWidth = nValue - ImageMenuItem.imageHotZoneWidth * EditableTitleBar.maxCountOfExtraItems
159      } else {
160        this.titleMaxWidth = nValue - ImageMenuItem.imageHotZoneWidth * this.menuItems.length
161      }
162    })
163  }
164}
165
166@Component
167struct EditableTitleBarMenuSection {
168  menuItems: Array<EditableTitleBarMenuItem>
169  onSave?: () => void
170
171  build() {
172    Column() {
173      Row() {
174        if (this.menuItems !== undefined && this.menuItems.length > 0) {
175          ForEach(this.menuItems.slice(0, EditableTitleBar.maxCountOfExtraItems), (item: EditableTitleBarMenuItem) => {
176            ImageMenuItem({ item: item })
177          })
178        }
179        ImageMenuItem({ item: {
180          value: PUBLIC_OK,
181          isEnabled: true,
182          action: () => this.onSave && this.onSave()
183        } })
184      }
185    }
186    .margin({
187      left: $r('sys.float.ohos_id_elements_margin_vertical_l'),
188      right: $r('sys.float.ohos_id_default_padding_end')
189    })
190    .justifyContent(FlexAlign.Center)
191  }
192}
193
194@Component
195struct ImageMenuItem {
196  item: EditableTitleBarMenuItem
197
198  static readonly imageSize = 24
199  static readonly imageHotZoneWidth = 48
200  static readonly buttonBorderRadius = 8
201  static readonly focusBorderWidth = 2
202  static readonly disabledImageOpacity = 0.4
203
204  @State isOnFocus: boolean = false
205  @State isOnHover: boolean = false
206  @State isOnClick: boolean = false
207
208  getFgColor() {
209    return this.isOnClick
210      ? $r('sys.color.ohos_id_color_titlebar_icon_pressed')
211      : $r('sys.color.ohos_id_color_titlebar_icon')
212  }
213
214  getBgColor() {
215    if (this.isOnClick) {
216      return $r('sys.color.ohos_id_color_click_effect')
217    } else if (this.isOnHover) {
218      return $r('sys.color.ohos_id_color_hover')
219    } else {
220      return Color.Transparent
221    }
222  }
223
224  build() {
225    Row() {
226      Image(this.item.value)
227        .width(ImageMenuItem.imageSize)
228        .height(ImageMenuItem.imageSize)
229        .focusable(this.item.isEnabled)
230    }
231    .width(ImageMenuItem.imageHotZoneWidth)
232    .height(ImageMenuItem.imageHotZoneWidth)
233    .borderRadius(ImageMenuItem.buttonBorderRadius)
234    .foregroundColor(this.getFgColor())
235    .backgroundColor(this.getBgColor())
236    .justifyContent(FlexAlign.Center)
237    .opacity(this.item.isEnabled ? 1 : ImageMenuItem.disabledImageOpacity)
238    .stateStyles({
239      focused: {
240        .border({
241          radius: $r('sys.float.ohos_id_corner_radius_clicked'),
242          width: ImageMenuItem.focusBorderWidth,
243          color: $r('sys.color.ohos_id_color_focused_outline'),
244          style: BorderStyle.Solid
245        })
246      },
247      normal: {
248        .border({
249          radius: $r('sys.float.ohos_id_corner_radius_clicked'),
250          width: 0
251        })
252      }
253    })
254    .onFocus(() => {
255      if (!this.item.isEnabled) {
256        return
257      }
258      this.isOnFocus = true
259    })
260    .onBlur(() => this.isOnFocus = false)
261    .onHover((isOn) => {
262      if (!this.item.isEnabled) {
263        return
264      }
265      this.isOnHover = isOn
266    })
267    .onKeyEvent((event) => {
268      if (!this.item.isEnabled) {
269        return
270      }
271      if (event.keyCode !== KeyCode.KEYCODE_ENTER && event.keyCode !== KeyCode.KEYCODE_SPACE) {
272        return
273      }
274      if (event.type === KeyType.Down) {
275        this.isOnClick = true
276      }
277      if (event.type === KeyType.Up) {
278        this.isOnClick = false
279      }
280    })
281    .onTouch((event) => {
282      if (!this.item.isEnabled) {
283        return
284      }
285      if (event.type === TouchType.Down) {
286        this.isOnClick = true
287      }
288      if (event.type === TouchType.Up) {
289        this.isOnClick = false
290      }
291    })
292    .onClick(() => this.item.isEnabled && this.item.action && this.item.action())
293  }
294}
295
296export default { EditableLeftIconType, EditableTitleBar }