From f1ee3e5ee82fb39dc191a069d6156ff03005c7c6 Mon Sep 17 00:00:00 2001 From: mamingshuai Date: Thu, 11 Mar 2021 18:38:06 +0800 Subject: [PATCH] update openharmony 1.0.1 --- .clang-format | 160 ++ .gitignore | 1 + BUILD.gn | 170 ++ LICENSE | 177 ++ README.en.md | 36 - README.md | 147 +- README_zh.md | 124 ++ figures/graphics-subsystem-architecture.png | Bin 0 -> 41642 bytes figures/graphics-subsystem-components.png | Bin 0 -> 39787 bytes figures/图形子系统架构图.png | Bin 0 -> 41642 bytes figures/图形组件一览.png | Bin 0 -> 39787 bytes frameworks/animator/animator.cpp | 110 ++ frameworks/animator/easing_equation.cpp | 254 +++ frameworks/animator/interpolation.cpp | 35 + frameworks/common/graphic_startup.cpp | 93 + frameworks/common/image.cpp | 449 +++++ frameworks/common/input_device_manager.cpp | 66 + frameworks/common/screen.cpp | 29 + frameworks/common/screen_device_proxy.cpp | 155 ++ frameworks/common/task.cpp | 34 + frameworks/common/text.cpp | 368 ++++ frameworks/common/typed_text.cpp | 384 ++++ frameworks/common/typed_text.h | 83 + frameworks/common/ui_font_header.cpp | 55 + frameworks/components/root_view.cpp | 633 +++++++ frameworks/components/text_adapter.cpp | 229 +++ frameworks/components/ui_abstract_clock.cpp | 57 + .../components/ui_abstract_progress.cpp | 230 +++ frameworks/components/ui_abstract_scroll.cpp | 182 ++ frameworks/components/ui_analog_clock.cpp | 336 ++++ frameworks/components/ui_arc_label.cpp | 219 +++ frameworks/components/ui_axis.cpp | 197 ++ frameworks/components/ui_box_progress.cpp | 237 +++ frameworks/components/ui_button.cpp | 278 +++ frameworks/components/ui_canvas.cpp | 807 ++++++++ frameworks/components/ui_chart.cpp | 832 +++++++++ frameworks/components/ui_checkbox.cpp | 95 + frameworks/components/ui_circle_progress.cpp | 134 ++ frameworks/components/ui_dialog.cpp | 694 +++++++ frameworks/components/ui_digital_clock.cpp | 249 +++ frameworks/components/ui_image_animator.cpp | 204 ++ frameworks/components/ui_image_view.cpp | 424 +++++ frameworks/components/ui_label.cpp | 365 ++++ frameworks/components/ui_label_button.cpp | 48 + frameworks/components/ui_list.cpp | 763 ++++++++ frameworks/components/ui_picker.cpp | 462 +++++ frameworks/components/ui_qrcode.cpp | 215 +++ frameworks/components/ui_radio_button.cpp | 166 ++ frameworks/components/ui_repeat_button.cpp | 63 + frameworks/components/ui_scroll_view.cpp | 417 +++++ frameworks/components/ui_slider.cpp | 299 +++ frameworks/components/ui_surface_view.cpp | 157 ++ frameworks/components/ui_swipe_view.cpp | 480 +++++ frameworks/components/ui_texture_mapper.cpp | 95 + frameworks/components/ui_time_picker.cpp | 291 +++ frameworks/components/ui_toggle_button.cpp | 119 ++ frameworks/components/ui_video.cpp | 719 ++++++++ frameworks/components/ui_view.cpp | 785 ++++++++ frameworks/components/ui_view_group.cpp | 299 +++ frameworks/core/render_manager.cpp | 230 +++ frameworks/core/render_manager.h | 121 ++ frameworks/core/task_manager.cpp | 67 + frameworks/default_resource/check_box_res.cpp | 1379 ++++++++++++++ frameworks/default_resource/check_box_res.h | 25 + frameworks/dfx/event_injector.cpp | 269 +++ frameworks/dfx/key_event_injector.cpp | 82 + frameworks/dfx/key_event_injector.h | 41 + frameworks/dfx/performance_task.h | 56 + frameworks/dfx/point_event_injector.cpp | 92 + frameworks/dfx/point_event_injector.h | 47 + frameworks/dfx/ui_dump_dom_tree.cpp | 386 ++++ frameworks/dfx/ui_screenshot.cpp | 167 ++ frameworks/dock/input_device.cpp | 28 + frameworks/dock/input_device.h | 58 + frameworks/dock/key_input_device.cpp | 29 + frameworks/dock/key_input_device.h | 39 + frameworks/dock/ohos/ohos_input_device.cpp | 26 + frameworks/dock/ohos/ohos_input_device.h | 31 + frameworks/dock/pointer_input_device.cpp | 324 ++++ frameworks/dock/pointer_input_device.h | 76 + frameworks/dock/rotate_input_device.cpp | 43 + frameworks/dock/screen_device.h | 202 ++ frameworks/dock/screen_device_proxy.h | 232 +++ frameworks/dock/virtual_input_device.cpp | 30 + frameworks/dock/virtual_input_device.h | 43 + frameworks/draw/draw_arc.cpp | 490 +++++ frameworks/draw/draw_arc.h | 146 ++ frameworks/draw/draw_curve.cpp | 40 + frameworks/draw/draw_curve.h | 31 + frameworks/draw/draw_image.cpp | 88 + frameworks/draw/draw_image.h | 41 + frameworks/draw/draw_label.cpp | 167 ++ frameworks/draw/draw_label.h | 38 + frameworks/draw/draw_line.cpp | 472 +++++ frameworks/draw/draw_line.h | 39 + frameworks/draw/draw_rect.cpp | 728 ++++++++ frameworks/draw/draw_rect.h | 61 + frameworks/draw/draw_triangle.cpp | 121 ++ frameworks/draw/draw_triangle.h | 45 + frameworks/draw/draw_utils.cpp | 1643 +++++++++++++++++ frameworks/draw/draw_utils.h | 291 +++ frameworks/events/event.cpp | 23 + frameworks/font/base_font.cpp | 64 + frameworks/font/glyphs_manager.cpp | 383 ++++ frameworks/font/glyphs_manager.h | 113 ++ frameworks/font/ui_font.cpp | 132 ++ frameworks/font/ui_font_adaptor.cpp | 46 + frameworks/font/ui_font_adaptor.h | 31 + frameworks/font/ui_font_allocator.cpp | 160 ++ frameworks/font/ui_font_allocator.h | 67 + frameworks/font/ui_font_cache.cpp | 106 ++ frameworks/font/ui_font_cache.h | 73 + frameworks/font/ui_font_vector.cpp | 428 +++++ frameworks/font/ui_line_break.cpp | 27 + frameworks/font/ui_line_break.h | 61 + frameworks/font/ui_multi_font_manager.cpp | 273 +++ frameworks/font/ui_multi_font_manager.h | 90 + frameworks/font/ui_text_shaping.cpp | 26 + frameworks/font/ui_text_shaping.h | 61 + frameworks/imgdecode/cache_manager.cpp | 293 +++ frameworks/imgdecode/cache_manager.h | 114 ++ frameworks/imgdecode/file_img_decoder.cpp | 159 ++ frameworks/imgdecode/file_img_decoder.h | 82 + frameworks/imgdecode/image_load.cpp | 206 +++ frameworks/imgdecode/image_load.h | 44 + frameworks/layout/flex_layout.cpp | 502 +++++ frameworks/layout/grid_layout.cpp | 118 ++ frameworks/layout/list_layout.cpp | 50 + frameworks/themes/theme.cpp | 63 + frameworks/themes/theme_manager.cpp | 26 + frameworks/window/window.cpp | 44 + frameworks/window/window_impl.cpp | 227 +++ frameworks/window/window_impl.h | 62 + interfaces/innerkits/common/graphic_startup.h | 31 + .../innerkits/common/image_decode_ability.h | 64 + .../innerkits/common/input_device_manager.h | 74 + interfaces/innerkits/common/task_manager.h | 90 + interfaces/innerkits/dock/focus_manager.h | 85 + .../innerkits/dock/rotate_input_device.h | 38 + interfaces/innerkits/font/ui_font_vector.h | 80 + interfaces/kits/animator/animator.h | 349 ++++ interfaces/kits/animator/easing_equation.h | 425 +++++ interfaces/kits/animator/interpolation.h | 73 + interfaces/kits/common/image.h | 169 ++ interfaces/kits/common/screen.h | 82 + interfaces/kits/common/task.h | 125 ++ interfaces/kits/common/text.h | 367 ++++ interfaces/kits/components/abstract_adapter.h | 98 + interfaces/kits/components/root_view.h | 305 +++ interfaces/kits/components/text_adapter.h | 282 +++ .../kits/components/ui_abstract_clock.h | 244 +++ .../kits/components/ui_abstract_progress.h | 333 ++++ .../kits/components/ui_abstract_scroll.h | 356 ++++ interfaces/kits/components/ui_analog_clock.h | 374 ++++ interfaces/kits/components/ui_arc_label.h | 381 ++++ interfaces/kits/components/ui_axis.h | 314 ++++ interfaces/kits/components/ui_box_progress.h | 240 +++ interfaces/kits/components/ui_button.h | 415 +++++ interfaces/kits/components/ui_canvas.h | 681 +++++++ interfaces/kits/components/ui_chart.h | 862 +++++++++ interfaces/kits/components/ui_checkbox.h | 225 +++ .../kits/components/ui_circle_progress.h | 260 +++ interfaces/kits/components/ui_dialog.h | 248 +++ interfaces/kits/components/ui_digital_clock.h | 222 +++ .../kits/components/ui_image_animator.h | 484 +++++ interfaces/kits/components/ui_image_view.h | 305 +++ interfaces/kits/components/ui_label.h | 443 +++++ interfaces/kits/components/ui_label_button.h | 312 ++++ interfaces/kits/components/ui_list.h | 449 +++++ interfaces/kits/components/ui_picker.h | 429 +++++ interfaces/kits/components/ui_qrcode.h | 129 ++ interfaces/kits/components/ui_radio_button.h | 153 ++ interfaces/kits/components/ui_repeat_button.h | 153 ++ interfaces/kits/components/ui_scroll_view.h | 297 +++ interfaces/kits/components/ui_slider.h | 311 ++++ interfaces/kits/components/ui_surface_view.h | 176 ++ interfaces/kits/components/ui_swipe_view.h | 368 ++++ .../kits/components/ui_texture_mapper.h | 291 +++ interfaces/kits/components/ui_time_picker.h | 334 ++++ interfaces/kits/components/ui_toggle_button.h | 128 ++ interfaces/kits/components/ui_video.h | 498 +++++ interfaces/kits/components/ui_view.h | 1516 +++++++++++++++ interfaces/kits/components/ui_view_group.h | 265 +++ interfaces/kits/dfx/event_injector.h | 191 ++ interfaces/kits/dfx/ui_dump_dom_tree.h | 154 ++ interfaces/kits/dfx/ui_screenshot.h | 96 + interfaces/kits/events/aod_callback.h | 68 + interfaces/kits/events/cancel_event.h | 66 + interfaces/kits/events/click_event.h | 66 + interfaces/kits/events/drag_event.h | 173 ++ interfaces/kits/events/event.h | 114 ++ interfaces/kits/events/key_event.h | 94 + interfaces/kits/events/long_press_event.h | 71 + interfaces/kits/events/press_event.h | 65 + interfaces/kits/events/release_event.h | 65 + interfaces/kits/events/rotate_event.h | 80 + interfaces/kits/events/virtual_device_event.h | 110 ++ interfaces/kits/font/base_font.h | 222 +++ interfaces/kits/font/ui_font.h | 238 +++ interfaces/kits/font/ui_font_header.h | 412 +++++ interfaces/kits/layout/flex_layout.h | 141 ++ interfaces/kits/layout/grid_layout.h | 103 ++ interfaces/kits/layout/layout.h | 110 ++ interfaces/kits/layout/list_layout.h | 68 + interfaces/kits/themes/theme.h | 192 ++ interfaces/kits/themes/theme_manager.h | 90 + interfaces/kits/window/window.h | 226 +++ test/framework/BUILD.gn | 84 + .../include/test_case_list_adapter.h | 43 + test/framework/include/test_resource_config.h | 70 + test/framework/include/ui_test.h | 111 ++ test/framework/include/ui_test_app.h | 53 + test/framework/include/ui_test_group.h | 41 + test/framework/src/test_ability.cpp | 21 + test/framework/src/test_case_list_adapter.cpp | 197 ++ test/framework/src/ui_test_app.cpp | 100 + test/framework/src/ui_test_group.cpp | 164 ++ .../uitest/test_animator/ui_test_animator.cpp | 310 ++++ test/uitest/test_animator/ui_test_animator.h | 128 ++ .../ui_test_anti_aliasing.cpp | 167 ++ .../ui_test_anti_aliasing.h | 53 + .../test_arc_label/ui_test_arc_label.cpp | 200 ++ .../uitest/test_arc_label/ui_test_arc_label.h | 78 + test/uitest/test_button/ui_test_button.cpp | 671 +++++++ test/uitest/test_button/ui_test_button.h | 71 + test/uitest/test_canvas/ui_test_canvas.cpp | 927 ++++++++++ test/uitest/test_canvas/ui_test_canvas.h | 113 ++ .../test_chart/ui_test_chart_pillar.cpp | 225 +++ test/uitest/test_chart/ui_test_chart_pillar.h | 63 + .../test_chart/ui_test_chart_polyline.cpp | 416 +++++ .../test_chart/ui_test_chart_polyline.h | 74 + test/uitest/test_dialog/ui_test_dialog.cpp | 493 +++++ test/uitest/test_dialog/ui_test_dialog.h | 72 + .../ui_test_digital_clock.cpp | 214 +++ .../ui_test_digital_clock.h | 47 + .../test_draw_line/ui_test_draw_line.cpp | 398 ++++ .../uitest/test_draw_line/ui_test_draw_line.h | 57 + .../test_draw_rect/ui_test_draw_rect.cpp | 1427 ++++++++++++++ .../uitest/test_draw_rect/ui_test_draw_rect.h | 184 ++ .../ui_test_event_injector.cpp | 472 +++++ .../ui_test_event_injector.h | 90 + test/uitest/test_font/ui_test_font.cpp | 208 +++ test/uitest/test_font/ui_test_font.h | 77 + test/uitest/test_image/ui_test_image.cpp | 518 ++++++ test/uitest/test_image/ui_test_image.h | 79 + .../ui_test_image_animator.cpp | 428 +++++ .../ui_test_image_animator.h | 119 ++ .../test_input_event/ui_test_input_event.cpp | 617 +++++++ .../test_input_event/ui_test_input_event.h | 252 +++ test/uitest/test_label/ui_test_label.cpp | 280 +++ test/uitest/test_label/ui_test_label.h | 92 + .../test_layout/ui_test_advanced_layout.cpp | 284 +++ .../test_layout/ui_test_advanced_layout.h | 133 ++ .../test_layout/ui_test_basic_layout.cpp | 249 +++ .../uitest/test_layout/ui_test_basic_layout.h | 94 + test/uitest/test_opacity/ui_test_opacity.cpp | 492 +++++ test/uitest/test_opacity/ui_test_opacity.h | 119 ++ test/uitest/test_picker/ui_test_ui_picker.cpp | 249 +++ test/uitest/test_picker/ui_test_ui_picker.h | 62 + .../ui_test_box_progress.cpp | 435 +++++ .../test_progress_bar/ui_test_box_progress.h | 87 + .../ui_test_circle_progress.cpp | 473 +++++ .../ui_test_circle_progress.h | 92 + test/uitest/test_qrcode/ui_test_qrcode.cpp | 234 +++ test/uitest/test_qrcode/ui_test_qrcode.h | 48 + test/uitest/test_render/ui_test_render.cpp | 155 ++ test/uitest/test_render/ui_test_render.h | 52 + .../ui_test_rotate_input.cpp | 361 ++++ .../test_rotate_input/ui_test_rotate_input.h | 59 + .../test_screenshot/ui_test_screenshot.cpp | 121 ++ .../test_screenshot/ui_test_screenshot.h | 60 + test/uitest/test_slider/ui_test_slider.cpp | 549 ++++++ test/uitest/test_slider/ui_test_slider.h | 119 ++ .../ui_test_texture_mapper.cpp | 273 +++ .../ui_test_texture_mapper.h | 108 ++ .../test_transform/ui_test_transform.cpp | 134 ++ .../uitest/test_transform/ui_test_transform.h | 54 + .../ui_test_analog_clock.cpp | 315 ++++ .../ui_test_analog_clock.h | 79 + .../ui_test_dump_dom.cpp | 435 +++++ .../test_ui_dump_dom_tree/ui_test_dump_dom.h | 51 + test/uitest/test_ui_list/ui_test_ui_list.cpp | 367 ++++ test/uitest/test_ui_list/ui_test_ui_list.h | 74 + .../test_ui_list_view/ui_test_list_layout.cpp | 242 +++ .../test_ui_list_view/ui_test_list_layout.h | 47 + .../ui_test_ui_scroll_view.cpp | 318 ++++ .../ui_test_ui_scroll_view.h | 48 + .../ui_test_ui_swipe_view.cpp | 366 ++++ .../ui_test_ui_swipe_view.h | 58 + .../test_vector_font/ui_test_vector_font.cpp | 322 ++++ .../test_vector_font/ui_test_vector_font.h | 83 + test/uitest/test_video/ui_test_video.cpp | 333 ++++ test/uitest/test_video/ui_test_video.h | 130 ++ .../ui_test_view_percent.cpp | 254 +++ .../test_view_percent/ui_test_view_percent.h | 71 + .../ui_test_view_scale_rotate.cpp | 213 +++ .../ui_test_view_scale_rotate.h | 82 + test/unittest/BUILD.gn | 287 +++ test/unittest/animator/animator_unit_test.cpp | 229 +++ .../animator/easing_equation_unit_test.cpp | 340 ++++ .../animator/interpolation_unit_test.cpp | 52 + .../hardware_acceleration_unit_test.cpp | 291 +++ test/unittest/common/screen_unit_test.cpp | 50 + test/unittest/common/text_unit_test.cpp | 122 ++ .../ui_abstract_progress_unit_test.cpp | 209 +++ .../components/ui_analog_clock_unit_test.cpp | 232 +++ .../components/ui_arc_label_unit_test.cpp | 217 +++ .../unittest/components/ui_axis_unit_test.cpp | 149 ++ .../components/ui_box_progress_unit_test.cpp | 201 ++ .../components/ui_button_unit_test.cpp | 340 ++++ .../components/ui_canvas_unit_test.cpp | 567 ++++++ .../components/ui_chart_unit_test.cpp | 491 +++++ .../components/ui_checkbox_unit_test.cpp | 97 + .../ui_circle_progress_unit_test.cpp | 145 ++ .../components/ui_dialog_unit_test.cpp | 228 +++ .../components/ui_digital_clock_unit_test.cpp | 177 ++ .../ui_image_animator_unit_test.cpp | 367 ++++ .../components/ui_image_unit_test.cpp | 409 ++++ .../components/ui_label_button_unit_test.cpp | 207 +++ .../components/ui_label_unit_test.cpp | 227 +++ .../components/ui_picker_unit_test.cpp | 171 ++ .../components/ui_qrcode_unit_test.cpp | 97 + .../components/ui_radio_button_unit_test.cpp | 143 ++ .../components/ui_repeat_button_unit_test.cpp | 78 + .../components/ui_scroll_view_unit_test.cpp | 205 ++ .../components/ui_slider_unit_test.cpp | 94 + .../components/ui_surface_view_unit_test.cpp | 183 ++ .../components/ui_swipe_view_unit_test.cpp | 316 ++++ .../components/ui_time_picker_unit_test.cpp | 95 + .../components/ui_toggle_button_unit_test.cpp | 80 + .../components/ui_video_unit_test.cpp | 457 +++++ .../components/ui_view_group_unit_test.cpp | 161 ++ .../unittest/components/ui_view_unit_test.cpp | 1040 +++++++++++ .../unittest/dfx/event_injector_unit_test.cpp | 443 +++++ .../events/cancel_event_unit_test.cpp | 65 + .../unittest/events/click_event_unit_test.cpp | 64 + test/unittest/events/drag_event_unit_test.cpp | 180 ++ .../events/event_bubble_unit_test.cpp | 438 +++++ test/unittest/events/event_unit_test.cpp | 82 + test/unittest/events/key_event_unit_test.cpp | 81 + .../events/long_press_event_unit_test.cpp | 81 + .../unittest/events/press_event_unit_test.cpp | 65 + .../events/release_event_unit_test.cpp | 61 + .../events/virtual_device_event_unit_test.cpp | 80 + test/unittest/font/ui_font_unit_test.cpp | 109 ++ test/unittest/image/image_load_unit_test.cpp | 130 ++ .../unittest/layout/list_layout_unit_test.cpp | 63 + test/unittest/render/render_uni_test.cpp | 141 ++ .../rotate/ui_rotate_input_unit_test.cpp | 173 ++ .../themes/theme_manager_unit_test.cpp | 47 + .../config/faces/default/B024_002.bin | Bin 0 -> 824472 bytes .../config/faces/default/B024_009.bin | Bin 0 -> 63008 bytes .../config/faces/default/B024_010.bin | Bin 0 -> 63008 bytes .../config/faces/default/B024_011.bin | Bin 0 -> 63008 bytes tools/qt/simulator/config/images/blue.png | Bin 0 -> 2877 bytes .../simulator/config/images/blue_argb8888.bin | Bin 0 -> 35352 bytes .../simulator/config/images/blue_compress.bin | Bin 0 -> 4056 bytes .../simulator/config/images/blue_rgb565.bin | Bin 0 -> 26516 bytes .../simulator/config/images/blue_rgb888.bin | Bin 0 -> 26516 bytes tools/qt/simulator/config/images/green.png | Bin 0 -> 2381 bytes .../config/images/ic_arrow_right.png | Bin 0 -> 293 bytes tools/qt/simulator/config/images/ic_back.png | Bin 0 -> 296 bytes .../config/images/image_animator_0.bin | Bin 0 -> 40376 bytes .../config/images/image_animator_1.bin | Bin 0 -> 40376 bytes .../config/images/image_animator_2.bin | Bin 0 -> 40376 bytes .../config/images/image_animator_3.bin | Bin 0 -> 40376 bytes tools/qt/simulator/config/images/jpeg.jpg | Bin 0 -> 5764 bytes .../simulator/config/images/png_16_gray.png | Bin 0 -> 2030 bytes .../simulator/config/images/png_1_palette.png | Bin 0 -> 332 bytes .../qt/simulator/config/images/png_24_rgb.png | Bin 0 -> 2236 bytes .../simulator/config/images/png_2_palette.png | Bin 0 -> 440 bytes .../simulator/config/images/png_32_rgba.png | Bin 0 -> 3798 bytes .../simulator/config/images/png_4_palette.png | Bin 0 -> 585 bytes .../qt/simulator/config/images/png_8_gray.png | Bin 0 -> 1543 bytes .../simulator/config/images/png_8_palette.png | Bin 0 -> 1837 bytes tools/qt/simulator/config/images/red.png | Bin 0 -> 3375 bytes .../simulator/config/images/red_compress.bin | Bin 0 -> 5512 bytes .../config/images/sliderBackground.bin | Bin 0 -> 4968 bytes .../config/images/sliderIndicator.bin | Bin 0 -> 4968 bytes .../qt/simulator/config/images/sliderKnob.bin | Bin 0 -> 6408 bytes tools/qt/simulator/config/images/yellow.png | Bin 0 -> 2376 bytes .../default_resource/videoplayer_mute.png | Bin 0 -> 509 bytes .../default_resource/videoplayer_pause.png | Bin 0 -> 582 bytes .../default_resource/videoplayer_play.png | Bin 0 -> 706 bytes .../videoplayer_play_center.png | Bin 0 -> 2367 bytes .../default_resource/videoplayer_volume.png | Bin 0 -> 786 bytes tools/qt/simulator/drivers/config.h | 22 + .../simulator/drivers/display/gui_thread.cpp | 42 + .../qt/simulator/drivers/display/gui_thread.h | 42 + .../qt/simulator/drivers/display/monitor.cpp | 110 ++ tools/qt/simulator/drivers/display/monitor.h | 61 + .../simulator/drivers/display/task_thread.cpp | 32 + .../simulator/drivers/display/task_thread.h | 36 + .../simulator/drivers/indev/mouse_input.cpp | 57 + .../qt/simulator/drivers/indev/mouse_input.h | 44 + .../drivers/indev/mousewheel_input.cpp | 37 + .../drivers/indev/mousewheel_input.h | 40 + tools/qt/simulator/font/LICENSE.txt | 96 + .../font/SourceHanSansSC-Regular.otf | Bin 0 -> 17281372 bytes tools/qt/simulator/libui/libui.pro | 259 +++ tools/qt/simulator/simulator.pro | 15 + tools/qt/simulator/test/test.pro | 128 ++ .../third_party/freetype/freetype.pro | 66 + .../third_party/harfbuzz/harfbuzz.pro | 72 + tools/qt/simulator/third_party/icu/icu.pro | 36 + .../simulator/third_party/libjpeg/libjpeg.pro | 90 + .../simulator/third_party/libpng/libpng.pro | 41 + .../third_party/qrcodegen/qrcodegen.pro | 25 + tools/qt/simulator/third_party/zlib/unistd.h | 18 + tools/qt/simulator/third_party/zlib/zlib.pro | 36 + tools/qt/simulator/uitest/UITest.pro | 71 + tools/qt/simulator/uitest/main.cpp | 35 + tools/qt/simulator/uitest/main_widget.cpp | 92 + tools/qt/simulator/uitest/main_widget.h | 81 + tools/qt/simulator/uitest/main_widget.ui | 33 + tools/qt/simulator/uitest/ui_mainwidget.h | 44 + ui.gni | 101 + 417 files changed, 77916 insertions(+), 67 deletions(-) create mode 100644 .clang-format create mode 100644 .gitignore create mode 100644 BUILD.gn create mode 100644 LICENSE delete mode 100644 README.en.md create mode 100644 README_zh.md create mode 100644 figures/graphics-subsystem-architecture.png create mode 100644 figures/graphics-subsystem-components.png create mode 100644 figures/图形子系统架构图.png create mode 100644 figures/图形组件一览.png create mode 100755 frameworks/animator/animator.cpp create mode 100755 frameworks/animator/easing_equation.cpp create mode 100755 frameworks/animator/interpolation.cpp create mode 100644 frameworks/common/graphic_startup.cpp create mode 100755 frameworks/common/image.cpp create mode 100755 frameworks/common/input_device_manager.cpp create mode 100755 frameworks/common/screen.cpp create mode 100644 frameworks/common/screen_device_proxy.cpp create mode 100755 frameworks/common/task.cpp create mode 100755 frameworks/common/text.cpp create mode 100755 frameworks/common/typed_text.cpp create mode 100755 frameworks/common/typed_text.h create mode 100755 frameworks/common/ui_font_header.cpp create mode 100755 frameworks/components/root_view.cpp create mode 100755 frameworks/components/text_adapter.cpp create mode 100755 frameworks/components/ui_abstract_clock.cpp create mode 100755 frameworks/components/ui_abstract_progress.cpp create mode 100755 frameworks/components/ui_abstract_scroll.cpp create mode 100755 frameworks/components/ui_analog_clock.cpp create mode 100755 frameworks/components/ui_arc_label.cpp create mode 100755 frameworks/components/ui_axis.cpp create mode 100755 frameworks/components/ui_box_progress.cpp create mode 100755 frameworks/components/ui_button.cpp create mode 100755 frameworks/components/ui_canvas.cpp create mode 100755 frameworks/components/ui_chart.cpp create mode 100755 frameworks/components/ui_checkbox.cpp create mode 100755 frameworks/components/ui_circle_progress.cpp create mode 100755 frameworks/components/ui_dialog.cpp create mode 100755 frameworks/components/ui_digital_clock.cpp create mode 100755 frameworks/components/ui_image_animator.cpp create mode 100755 frameworks/components/ui_image_view.cpp create mode 100644 frameworks/components/ui_label.cpp create mode 100755 frameworks/components/ui_label_button.cpp create mode 100755 frameworks/components/ui_list.cpp create mode 100755 frameworks/components/ui_picker.cpp create mode 100755 frameworks/components/ui_qrcode.cpp create mode 100755 frameworks/components/ui_radio_button.cpp create mode 100755 frameworks/components/ui_repeat_button.cpp create mode 100755 frameworks/components/ui_scroll_view.cpp create mode 100755 frameworks/components/ui_slider.cpp create mode 100755 frameworks/components/ui_surface_view.cpp create mode 100755 frameworks/components/ui_swipe_view.cpp create mode 100755 frameworks/components/ui_texture_mapper.cpp create mode 100755 frameworks/components/ui_time_picker.cpp create mode 100755 frameworks/components/ui_toggle_button.cpp create mode 100755 frameworks/components/ui_video.cpp create mode 100755 frameworks/components/ui_view.cpp create mode 100755 frameworks/components/ui_view_group.cpp create mode 100755 frameworks/core/render_manager.cpp create mode 100755 frameworks/core/render_manager.h create mode 100755 frameworks/core/task_manager.cpp create mode 100755 frameworks/default_resource/check_box_res.cpp create mode 100755 frameworks/default_resource/check_box_res.h create mode 100755 frameworks/dfx/event_injector.cpp create mode 100755 frameworks/dfx/key_event_injector.cpp create mode 100755 frameworks/dfx/key_event_injector.h create mode 100755 frameworks/dfx/performance_task.h create mode 100755 frameworks/dfx/point_event_injector.cpp create mode 100755 frameworks/dfx/point_event_injector.h create mode 100755 frameworks/dfx/ui_dump_dom_tree.cpp create mode 100755 frameworks/dfx/ui_screenshot.cpp create mode 100755 frameworks/dock/input_device.cpp create mode 100755 frameworks/dock/input_device.h create mode 100755 frameworks/dock/key_input_device.cpp create mode 100755 frameworks/dock/key_input_device.h create mode 100755 frameworks/dock/ohos/ohos_input_device.cpp create mode 100755 frameworks/dock/ohos/ohos_input_device.h create mode 100755 frameworks/dock/pointer_input_device.cpp create mode 100755 frameworks/dock/pointer_input_device.h create mode 100755 frameworks/dock/rotate_input_device.cpp create mode 100755 frameworks/dock/screen_device.h create mode 100755 frameworks/dock/screen_device_proxy.h create mode 100755 frameworks/dock/virtual_input_device.cpp create mode 100755 frameworks/dock/virtual_input_device.h create mode 100755 frameworks/draw/draw_arc.cpp create mode 100755 frameworks/draw/draw_arc.h create mode 100755 frameworks/draw/draw_curve.cpp create mode 100755 frameworks/draw/draw_curve.h create mode 100755 frameworks/draw/draw_image.cpp create mode 100755 frameworks/draw/draw_image.h create mode 100755 frameworks/draw/draw_label.cpp create mode 100755 frameworks/draw/draw_label.h create mode 100755 frameworks/draw/draw_line.cpp create mode 100755 frameworks/draw/draw_line.h create mode 100755 frameworks/draw/draw_rect.cpp create mode 100755 frameworks/draw/draw_rect.h create mode 100755 frameworks/draw/draw_triangle.cpp create mode 100755 frameworks/draw/draw_triangle.h create mode 100644 frameworks/draw/draw_utils.cpp create mode 100644 frameworks/draw/draw_utils.h create mode 100755 frameworks/events/event.cpp create mode 100755 frameworks/font/base_font.cpp create mode 100755 frameworks/font/glyphs_manager.cpp create mode 100755 frameworks/font/glyphs_manager.h create mode 100755 frameworks/font/ui_font.cpp create mode 100755 frameworks/font/ui_font_adaptor.cpp create mode 100755 frameworks/font/ui_font_adaptor.h create mode 100755 frameworks/font/ui_font_allocator.cpp create mode 100755 frameworks/font/ui_font_allocator.h create mode 100755 frameworks/font/ui_font_cache.cpp create mode 100755 frameworks/font/ui_font_cache.h create mode 100755 frameworks/font/ui_font_vector.cpp create mode 100755 frameworks/font/ui_line_break.cpp create mode 100755 frameworks/font/ui_line_break.h create mode 100755 frameworks/font/ui_multi_font_manager.cpp create mode 100755 frameworks/font/ui_multi_font_manager.h create mode 100755 frameworks/font/ui_text_shaping.cpp create mode 100755 frameworks/font/ui_text_shaping.h create mode 100755 frameworks/imgdecode/cache_manager.cpp create mode 100755 frameworks/imgdecode/cache_manager.h create mode 100755 frameworks/imgdecode/file_img_decoder.cpp create mode 100755 frameworks/imgdecode/file_img_decoder.h create mode 100755 frameworks/imgdecode/image_load.cpp create mode 100755 frameworks/imgdecode/image_load.h create mode 100755 frameworks/layout/flex_layout.cpp create mode 100755 frameworks/layout/grid_layout.cpp create mode 100755 frameworks/layout/list_layout.cpp create mode 100755 frameworks/themes/theme.cpp create mode 100755 frameworks/themes/theme_manager.cpp create mode 100755 frameworks/window/window.cpp create mode 100755 frameworks/window/window_impl.cpp create mode 100755 frameworks/window/window_impl.h create mode 100755 interfaces/innerkits/common/graphic_startup.h create mode 100755 interfaces/innerkits/common/image_decode_ability.h create mode 100755 interfaces/innerkits/common/input_device_manager.h create mode 100755 interfaces/innerkits/common/task_manager.h create mode 100644 interfaces/innerkits/dock/focus_manager.h create mode 100755 interfaces/innerkits/dock/rotate_input_device.h create mode 100755 interfaces/innerkits/font/ui_font_vector.h create mode 100755 interfaces/kits/animator/animator.h create mode 100755 interfaces/kits/animator/easing_equation.h create mode 100755 interfaces/kits/animator/interpolation.h create mode 100755 interfaces/kits/common/image.h create mode 100755 interfaces/kits/common/screen.h create mode 100755 interfaces/kits/common/task.h create mode 100755 interfaces/kits/common/text.h create mode 100755 interfaces/kits/components/abstract_adapter.h create mode 100755 interfaces/kits/components/root_view.h create mode 100755 interfaces/kits/components/text_adapter.h create mode 100755 interfaces/kits/components/ui_abstract_clock.h create mode 100755 interfaces/kits/components/ui_abstract_progress.h create mode 100755 interfaces/kits/components/ui_abstract_scroll.h create mode 100755 interfaces/kits/components/ui_analog_clock.h create mode 100755 interfaces/kits/components/ui_arc_label.h create mode 100755 interfaces/kits/components/ui_axis.h create mode 100755 interfaces/kits/components/ui_box_progress.h create mode 100755 interfaces/kits/components/ui_button.h create mode 100755 interfaces/kits/components/ui_canvas.h create mode 100755 interfaces/kits/components/ui_chart.h create mode 100755 interfaces/kits/components/ui_checkbox.h create mode 100755 interfaces/kits/components/ui_circle_progress.h create mode 100755 interfaces/kits/components/ui_dialog.h create mode 100755 interfaces/kits/components/ui_digital_clock.h create mode 100755 interfaces/kits/components/ui_image_animator.h create mode 100755 interfaces/kits/components/ui_image_view.h create mode 100755 interfaces/kits/components/ui_label.h create mode 100755 interfaces/kits/components/ui_label_button.h create mode 100755 interfaces/kits/components/ui_list.h create mode 100755 interfaces/kits/components/ui_picker.h create mode 100755 interfaces/kits/components/ui_qrcode.h create mode 100755 interfaces/kits/components/ui_radio_button.h create mode 100755 interfaces/kits/components/ui_repeat_button.h create mode 100755 interfaces/kits/components/ui_scroll_view.h create mode 100755 interfaces/kits/components/ui_slider.h create mode 100755 interfaces/kits/components/ui_surface_view.h create mode 100755 interfaces/kits/components/ui_swipe_view.h create mode 100755 interfaces/kits/components/ui_texture_mapper.h create mode 100755 interfaces/kits/components/ui_time_picker.h create mode 100755 interfaces/kits/components/ui_toggle_button.h create mode 100755 interfaces/kits/components/ui_video.h create mode 100755 interfaces/kits/components/ui_view.h create mode 100755 interfaces/kits/components/ui_view_group.h create mode 100755 interfaces/kits/dfx/event_injector.h create mode 100755 interfaces/kits/dfx/ui_dump_dom_tree.h create mode 100755 interfaces/kits/dfx/ui_screenshot.h create mode 100755 interfaces/kits/events/aod_callback.h create mode 100755 interfaces/kits/events/cancel_event.h create mode 100755 interfaces/kits/events/click_event.h create mode 100755 interfaces/kits/events/drag_event.h create mode 100755 interfaces/kits/events/event.h create mode 100755 interfaces/kits/events/key_event.h create mode 100755 interfaces/kits/events/long_press_event.h create mode 100755 interfaces/kits/events/press_event.h create mode 100755 interfaces/kits/events/release_event.h create mode 100755 interfaces/kits/events/rotate_event.h create mode 100755 interfaces/kits/events/virtual_device_event.h create mode 100755 interfaces/kits/font/base_font.h create mode 100755 interfaces/kits/font/ui_font.h create mode 100755 interfaces/kits/font/ui_font_header.h create mode 100755 interfaces/kits/layout/flex_layout.h create mode 100755 interfaces/kits/layout/grid_layout.h create mode 100755 interfaces/kits/layout/layout.h create mode 100755 interfaces/kits/layout/list_layout.h create mode 100755 interfaces/kits/themes/theme.h create mode 100755 interfaces/kits/themes/theme_manager.h create mode 100755 interfaces/kits/window/window.h create mode 100755 test/framework/BUILD.gn create mode 100755 test/framework/include/test_case_list_adapter.h create mode 100755 test/framework/include/test_resource_config.h create mode 100755 test/framework/include/ui_test.h create mode 100755 test/framework/include/ui_test_app.h create mode 100755 test/framework/include/ui_test_group.h create mode 100755 test/framework/src/test_ability.cpp create mode 100755 test/framework/src/test_case_list_adapter.cpp create mode 100755 test/framework/src/ui_test_app.cpp create mode 100755 test/framework/src/ui_test_group.cpp create mode 100755 test/uitest/test_animator/ui_test_animator.cpp create mode 100755 test/uitest/test_animator/ui_test_animator.h create mode 100755 test/uitest/test_anti_aliasing/ui_test_anti_aliasing.cpp create mode 100755 test/uitest/test_anti_aliasing/ui_test_anti_aliasing.h create mode 100755 test/uitest/test_arc_label/ui_test_arc_label.cpp create mode 100755 test/uitest/test_arc_label/ui_test_arc_label.h create mode 100755 test/uitest/test_button/ui_test_button.cpp create mode 100755 test/uitest/test_button/ui_test_button.h create mode 100755 test/uitest/test_canvas/ui_test_canvas.cpp create mode 100755 test/uitest/test_canvas/ui_test_canvas.h create mode 100755 test/uitest/test_chart/ui_test_chart_pillar.cpp create mode 100755 test/uitest/test_chart/ui_test_chart_pillar.h create mode 100755 test/uitest/test_chart/ui_test_chart_polyline.cpp create mode 100755 test/uitest/test_chart/ui_test_chart_polyline.h create mode 100755 test/uitest/test_dialog/ui_test_dialog.cpp create mode 100755 test/uitest/test_dialog/ui_test_dialog.h create mode 100755 test/uitest/test_digital_clock/ui_test_digital_clock.cpp create mode 100755 test/uitest/test_digital_clock/ui_test_digital_clock.h create mode 100755 test/uitest/test_draw_line/ui_test_draw_line.cpp create mode 100755 test/uitest/test_draw_line/ui_test_draw_line.h create mode 100755 test/uitest/test_draw_rect/ui_test_draw_rect.cpp create mode 100755 test/uitest/test_draw_rect/ui_test_draw_rect.h create mode 100755 test/uitest/test_event_injector/ui_test_event_injector.cpp create mode 100755 test/uitest/test_event_injector/ui_test_event_injector.h create mode 100755 test/uitest/test_font/ui_test_font.cpp create mode 100755 test/uitest/test_font/ui_test_font.h create mode 100755 test/uitest/test_image/ui_test_image.cpp create mode 100755 test/uitest/test_image/ui_test_image.h create mode 100755 test/uitest/test_image_animator/ui_test_image_animator.cpp create mode 100755 test/uitest/test_image_animator/ui_test_image_animator.h create mode 100755 test/uitest/test_input_event/ui_test_input_event.cpp create mode 100755 test/uitest/test_input_event/ui_test_input_event.h create mode 100755 test/uitest/test_label/ui_test_label.cpp create mode 100755 test/uitest/test_label/ui_test_label.h create mode 100755 test/uitest/test_layout/ui_test_advanced_layout.cpp create mode 100755 test/uitest/test_layout/ui_test_advanced_layout.h create mode 100755 test/uitest/test_layout/ui_test_basic_layout.cpp create mode 100755 test/uitest/test_layout/ui_test_basic_layout.h create mode 100755 test/uitest/test_opacity/ui_test_opacity.cpp create mode 100755 test/uitest/test_opacity/ui_test_opacity.h create mode 100755 test/uitest/test_picker/ui_test_ui_picker.cpp create mode 100755 test/uitest/test_picker/ui_test_ui_picker.h create mode 100755 test/uitest/test_progress_bar/ui_test_box_progress.cpp create mode 100755 test/uitest/test_progress_bar/ui_test_box_progress.h create mode 100755 test/uitest/test_progress_bar/ui_test_circle_progress.cpp create mode 100755 test/uitest/test_progress_bar/ui_test_circle_progress.h create mode 100755 test/uitest/test_qrcode/ui_test_qrcode.cpp create mode 100755 test/uitest/test_qrcode/ui_test_qrcode.h create mode 100755 test/uitest/test_render/ui_test_render.cpp create mode 100755 test/uitest/test_render/ui_test_render.h create mode 100644 test/uitest/test_rotate_input/ui_test_rotate_input.cpp create mode 100644 test/uitest/test_rotate_input/ui_test_rotate_input.h create mode 100755 test/uitest/test_screenshot/ui_test_screenshot.cpp create mode 100755 test/uitest/test_screenshot/ui_test_screenshot.h create mode 100755 test/uitest/test_slider/ui_test_slider.cpp create mode 100755 test/uitest/test_slider/ui_test_slider.h create mode 100755 test/uitest/test_texture_mapper/ui_test_texture_mapper.cpp create mode 100755 test/uitest/test_texture_mapper/ui_test_texture_mapper.h create mode 100755 test/uitest/test_transform/ui_test_transform.cpp create mode 100755 test/uitest/test_transform/ui_test_transform.h create mode 100755 test/uitest/test_ui_analog_clock/ui_test_analog_clock.cpp create mode 100755 test/uitest/test_ui_analog_clock/ui_test_analog_clock.h create mode 100755 test/uitest/test_ui_dump_dom_tree/ui_test_dump_dom.cpp create mode 100755 test/uitest/test_ui_dump_dom_tree/ui_test_dump_dom.h create mode 100755 test/uitest/test_ui_list/ui_test_ui_list.cpp create mode 100755 test/uitest/test_ui_list/ui_test_ui_list.h create mode 100755 test/uitest/test_ui_list_view/ui_test_list_layout.cpp create mode 100755 test/uitest/test_ui_list_view/ui_test_list_layout.h create mode 100755 test/uitest/test_ui_scroll_view/ui_test_ui_scroll_view.cpp create mode 100755 test/uitest/test_ui_scroll_view/ui_test_ui_scroll_view.h create mode 100755 test/uitest/test_ui_swipe_view/ui_test_ui_swipe_view.cpp create mode 100755 test/uitest/test_ui_swipe_view/ui_test_ui_swipe_view.h create mode 100755 test/uitest/test_vector_font/ui_test_vector_font.cpp create mode 100755 test/uitest/test_vector_font/ui_test_vector_font.h create mode 100755 test/uitest/test_video/ui_test_video.cpp create mode 100755 test/uitest/test_video/ui_test_video.h create mode 100755 test/uitest/test_view_percent/ui_test_view_percent.cpp create mode 100755 test/uitest/test_view_percent/ui_test_view_percent.h create mode 100755 test/uitest/test_view_scale_rotate/ui_test_view_scale_rotate.cpp create mode 100755 test/uitest/test_view_scale_rotate/ui_test_view_scale_rotate.h create mode 100755 test/unittest/BUILD.gn create mode 100755 test/unittest/animator/animator_unit_test.cpp create mode 100755 test/unittest/animator/easing_equation_unit_test.cpp create mode 100755 test/unittest/animator/interpolation_unit_test.cpp create mode 100644 test/unittest/common/hardware_acceleration_unit_test.cpp create mode 100755 test/unittest/common/screen_unit_test.cpp create mode 100755 test/unittest/common/text_unit_test.cpp create mode 100755 test/unittest/components/ui_abstract_progress_unit_test.cpp create mode 100755 test/unittest/components/ui_analog_clock_unit_test.cpp create mode 100755 test/unittest/components/ui_arc_label_unit_test.cpp create mode 100755 test/unittest/components/ui_axis_unit_test.cpp create mode 100755 test/unittest/components/ui_box_progress_unit_test.cpp create mode 100755 test/unittest/components/ui_button_unit_test.cpp create mode 100755 test/unittest/components/ui_canvas_unit_test.cpp create mode 100755 test/unittest/components/ui_chart_unit_test.cpp create mode 100755 test/unittest/components/ui_checkbox_unit_test.cpp create mode 100755 test/unittest/components/ui_circle_progress_unit_test.cpp create mode 100755 test/unittest/components/ui_dialog_unit_test.cpp create mode 100644 test/unittest/components/ui_digital_clock_unit_test.cpp create mode 100644 test/unittest/components/ui_image_animator_unit_test.cpp create mode 100644 test/unittest/components/ui_image_unit_test.cpp create mode 100644 test/unittest/components/ui_label_button_unit_test.cpp create mode 100755 test/unittest/components/ui_label_unit_test.cpp create mode 100755 test/unittest/components/ui_picker_unit_test.cpp create mode 100644 test/unittest/components/ui_qrcode_unit_test.cpp create mode 100755 test/unittest/components/ui_radio_button_unit_test.cpp create mode 100755 test/unittest/components/ui_repeat_button_unit_test.cpp create mode 100755 test/unittest/components/ui_scroll_view_unit_test.cpp create mode 100755 test/unittest/components/ui_slider_unit_test.cpp create mode 100755 test/unittest/components/ui_surface_view_unit_test.cpp create mode 100755 test/unittest/components/ui_swipe_view_unit_test.cpp create mode 100755 test/unittest/components/ui_time_picker_unit_test.cpp create mode 100755 test/unittest/components/ui_toggle_button_unit_test.cpp create mode 100755 test/unittest/components/ui_video_unit_test.cpp create mode 100755 test/unittest/components/ui_view_group_unit_test.cpp create mode 100755 test/unittest/components/ui_view_unit_test.cpp create mode 100755 test/unittest/dfx/event_injector_unit_test.cpp create mode 100755 test/unittest/events/cancel_event_unit_test.cpp create mode 100755 test/unittest/events/click_event_unit_test.cpp create mode 100755 test/unittest/events/drag_event_unit_test.cpp create mode 100755 test/unittest/events/event_bubble_unit_test.cpp create mode 100755 test/unittest/events/event_unit_test.cpp create mode 100755 test/unittest/events/key_event_unit_test.cpp create mode 100755 test/unittest/events/long_press_event_unit_test.cpp create mode 100755 test/unittest/events/press_event_unit_test.cpp create mode 100755 test/unittest/events/release_event_unit_test.cpp create mode 100755 test/unittest/events/virtual_device_event_unit_test.cpp create mode 100755 test/unittest/font/ui_font_unit_test.cpp create mode 100755 test/unittest/image/image_load_unit_test.cpp create mode 100755 test/unittest/layout/list_layout_unit_test.cpp create mode 100644 test/unittest/render/render_uni_test.cpp create mode 100644 test/unittest/rotate/ui_rotate_input_unit_test.cpp create mode 100755 test/unittest/themes/theme_manager_unit_test.cpp create mode 100644 tools/qt/simulator/config/faces/default/B024_002.bin create mode 100644 tools/qt/simulator/config/faces/default/B024_009.bin create mode 100644 tools/qt/simulator/config/faces/default/B024_010.bin create mode 100644 tools/qt/simulator/config/faces/default/B024_011.bin create mode 100644 tools/qt/simulator/config/images/blue.png create mode 100644 tools/qt/simulator/config/images/blue_argb8888.bin create mode 100644 tools/qt/simulator/config/images/blue_compress.bin create mode 100644 tools/qt/simulator/config/images/blue_rgb565.bin create mode 100644 tools/qt/simulator/config/images/blue_rgb888.bin create mode 100644 tools/qt/simulator/config/images/green.png create mode 100644 tools/qt/simulator/config/images/ic_arrow_right.png create mode 100644 tools/qt/simulator/config/images/ic_back.png create mode 100644 tools/qt/simulator/config/images/image_animator_0.bin create mode 100644 tools/qt/simulator/config/images/image_animator_1.bin create mode 100644 tools/qt/simulator/config/images/image_animator_2.bin create mode 100644 tools/qt/simulator/config/images/image_animator_3.bin create mode 100644 tools/qt/simulator/config/images/jpeg.jpg create mode 100644 tools/qt/simulator/config/images/png_16_gray.png create mode 100644 tools/qt/simulator/config/images/png_1_palette.png create mode 100644 tools/qt/simulator/config/images/png_24_rgb.png create mode 100644 tools/qt/simulator/config/images/png_2_palette.png create mode 100644 tools/qt/simulator/config/images/png_32_rgba.png create mode 100644 tools/qt/simulator/config/images/png_4_palette.png create mode 100644 tools/qt/simulator/config/images/png_8_gray.png create mode 100644 tools/qt/simulator/config/images/png_8_palette.png create mode 100644 tools/qt/simulator/config/images/red.png create mode 100644 tools/qt/simulator/config/images/red_compress.bin create mode 100644 tools/qt/simulator/config/images/sliderBackground.bin create mode 100644 tools/qt/simulator/config/images/sliderIndicator.bin create mode 100644 tools/qt/simulator/config/images/sliderKnob.bin create mode 100644 tools/qt/simulator/config/images/yellow.png create mode 100644 tools/qt/simulator/default_resource/videoplayer_mute.png create mode 100644 tools/qt/simulator/default_resource/videoplayer_pause.png create mode 100644 tools/qt/simulator/default_resource/videoplayer_play.png create mode 100644 tools/qt/simulator/default_resource/videoplayer_play_center.png create mode 100644 tools/qt/simulator/default_resource/videoplayer_volume.png create mode 100755 tools/qt/simulator/drivers/config.h create mode 100755 tools/qt/simulator/drivers/display/gui_thread.cpp create mode 100755 tools/qt/simulator/drivers/display/gui_thread.h create mode 100755 tools/qt/simulator/drivers/display/monitor.cpp create mode 100755 tools/qt/simulator/drivers/display/monitor.h create mode 100755 tools/qt/simulator/drivers/display/task_thread.cpp create mode 100755 tools/qt/simulator/drivers/display/task_thread.h create mode 100755 tools/qt/simulator/drivers/indev/mouse_input.cpp create mode 100755 tools/qt/simulator/drivers/indev/mouse_input.h create mode 100755 tools/qt/simulator/drivers/indev/mousewheel_input.cpp create mode 100755 tools/qt/simulator/drivers/indev/mousewheel_input.h create mode 100644 tools/qt/simulator/font/LICENSE.txt create mode 100644 tools/qt/simulator/font/SourceHanSansSC-Regular.otf create mode 100755 tools/qt/simulator/libui/libui.pro create mode 100644 tools/qt/simulator/simulator.pro create mode 100644 tools/qt/simulator/test/test.pro create mode 100644 tools/qt/simulator/third_party/freetype/freetype.pro create mode 100644 tools/qt/simulator/third_party/harfbuzz/harfbuzz.pro create mode 100644 tools/qt/simulator/third_party/icu/icu.pro create mode 100644 tools/qt/simulator/third_party/libjpeg/libjpeg.pro create mode 100644 tools/qt/simulator/third_party/libpng/libpng.pro create mode 100644 tools/qt/simulator/third_party/qrcodegen/qrcodegen.pro create mode 100644 tools/qt/simulator/third_party/zlib/unistd.h create mode 100644 tools/qt/simulator/third_party/zlib/zlib.pro create mode 100644 tools/qt/simulator/uitest/UITest.pro create mode 100755 tools/qt/simulator/uitest/main.cpp create mode 100755 tools/qt/simulator/uitest/main_widget.cpp create mode 100755 tools/qt/simulator/uitest/main_widget.h create mode 100644 tools/qt/simulator/uitest/main_widget.ui create mode 100644 tools/qt/simulator/uitest/ui_mainwidget.h create mode 100644 ui.gni diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..ac909da --- /dev/null +++ b/.clang-format @@ -0,0 +1,160 @@ +Language: Cpp +# BasedOnStyle: LLVM +# 访问说明符(public、private等)的偏移 +AccessModifierOffset: -4 +# 开括号(开圆括号、开尖括号、开方括号)后的对齐 +AlignAfterOpenBracket: Align +# 连续赋值时,等号对齐 +AlignConsecutiveAssignments: false +# 连续赋值时,变量名对齐 +AlignConsecutiveDeclarations: false +# 左对齐逃脱换行(使用反斜杠换行)的反斜杠 +AlignEscapedNewlinesLeft: true +# 水平对齐二元和三元表达式的操作数 +AlignOperands: true +# 对齐连续的尾随的注释 +AlignTrailingComments: true +# 允许函数声明的所有参数在放在下一行 +AllowAllParametersOfDeclarationOnNextLine: false +# 允许短的块放在同一行 +AllowShortBlocksOnASingleLine: false +# 允许短的case标签放在同一行 +AllowShortCaseLabelsOnASingleLine: false +# 允许短的函数放在同一行: None, InlineOnly(定义在类中), Empty(空函数), Inline(定义在类中,空函数), All +AllowShortFunctionsOnASingleLine: Empty +# 允许短的if语句保持在同一行 +AllowShortIfStatementsOnASingleLine: false +# 允许短的循环保持在同一行 +AllowShortLoopsOnASingleLine: false +# 总是在定义返回类型后换行(deprecated) +AlwaysBreakAfterDefinitionReturnType: None +# 总是在返回类型后换行: None, All, TopLevel(顶级函数,不包括在类中的函数), +# AllDefinitions(所有的定义,不包括声明), TopLevelDefinitions(所有的顶级函数的定义) +AlwaysBreakAfterReturnType: None +# 总是在多行string字面量前换行 +AlwaysBreakBeforeMultilineStrings: true +# 总是在template声明后换行 +AlwaysBreakTemplateDeclarations: false +# false表示函数实参要么都在同一行,要么都各自一行 +BinPackArguments: true +# false表示所有形参要么都在同一行,要么都各自一行 +BinPackParameters: false +# 大括号换行,只有当BreakBeforeBraces设置为Custom时才有效 +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: true + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false +# 在二元运算符前换行: None(在操作符后换行), NonAssignment(在非赋值的操作符前换行), All(在操作符前换行) +BreakBeforeBinaryOperators: None +# 在大括号前换行: Attach(始终将大括号附加到周围的上下文), Linux(除函数、命名空间和类定义,与Attach类似), +# Mozilla(除枚举、函数、记录定义,与Attach类似), Stroustrup(除函数定义、catch、else,与Attach类似), +# Allman(总是在大括号前换行), GNU(总是在大括号前换行,并对于控制语句的大括号增加额外的缩进), WebKit(在函数前换行), Custom +# 注:这里认为语句块也属于函数 +BreakBeforeBraces: Custom +# 在三元运算符前换行 +BreakBeforeTernaryOperators: false +# 在构造函数的初始化列表的逗号前换行 +BreakConstructorInitializersBeforeComma: false +# 每行字符的限制,0表示没有限制 +ColumnLimit: 120 +# 描述具有特殊意义的注释的正则表达式,它不应该被分割为多行或以其它方式改变 +CommentPragmas: "^ IWYU pragma:" +# 构造函数的初始化列表要么都在同一行,要么都各自一行 +ConstructorInitializerAllOnOneLineOrOnePerLine: true +# 构造函数的初始化列表的缩进宽度 +ConstructorInitializerIndentWidth: 4 +# 延续的行的缩进宽度 +ContinuationIndentWidth: 4 +# 去除C++11的列表初始化的大括号{后和}前的空格 +Cpp11BracedListStyle: true +# 继承最常用的指针和引用的对齐方式 +DerivePointerAlignment: false +# 关闭格式化 +DisableFormat: false +# 自动检测函数的调用和定义是否被格式为每行一个参数(Experimental) +ExperimentalAutoDetectBinPacking: false +# 需要被解读为foreach循环而不是函数调用的宏 +ForEachMacros: [foreach, Q_FOREACH, BOOST_FOREACH] +# 对#include进行排序,匹配了某正则表达式的#include拥有对应的优先级,匹配不到的则默认优先级为INT_MAX(优先级越小排序越靠前), +# 可以定义负数优先级从而保证某些#include永远在最前面 +IncludeCategories: + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + - Regex: '^(<|"(gtest|isl|json)/)' + Priority: 3 + - Regex: ".*" + Priority: 1 +# 缩进case标签 +IndentCaseLabels: true +# 缩进宽度 +IndentWidth: 4 +# 函数返回类型换行时,缩进函数声明或函数定义的函数名 +IndentWrappedFunctionNames: true +# 保留在块开始处的空行 +KeepEmptyLinesAtTheStartOfBlocks: true +# 开始一个块的宏的正则表达式 +MacroBlockBegin: "" +# 结束一个块的宏的正则表达式 +MacroBlockEnd: "" +# 连续空行的最大数量 +MaxEmptyLinesToKeep: 1 +# 命名空间的缩进: None, Inner(缩进嵌套的命名空间中的内容), All +NamespaceIndentation: None +# 使用ObjC块时缩进宽度 +ObjCBlockIndentWidth: 4 +# 在ObjC的@property后添加一个空格 +ObjCSpaceAfterProperty: false +# 在ObjC的protocol列表前添加一个空格 +ObjCSpaceBeforeProtocolList: true +# 在call(后对函数调用换行的penalty +PenaltyBreakBeforeFirstCallParameter: 19 +# 在一个注释中引入换行的penalty +PenaltyBreakComment: 300 +# 第一次在<<前换行的penalty +PenaltyBreakFirstLessLess: 120 +# 在一个字符串字面量中引入换行的penalty +PenaltyBreakString: 1000 +# 对于每个在行字符数限制之外的字符的penalt +PenaltyExcessCharacter: 1000000 +# 将函数的返回类型放到它自己的行的penalty +PenaltyReturnTypeOnItsOwnLine: 60 +# 指针和引用的对齐: Left, Right, Middle +PointerAlignment: Left +# 允许重新排版注释 +ReflowComments: true +# 允许排序#include +SortIncludes: true +# 在C风格类型转换后添加空格 +SpaceAfterCStyleCast: false +# 在赋值运算符之前添加空格 +SpaceBeforeAssignmentOperators: true +# 开圆括号之前添加一个空格: Never, ControlStatements, Always +SpaceBeforeParens: ControlStatements +# 在空的圆括号中添加空格 +SpaceInEmptyParentheses: false +# 在尾随的评论前添加的空格数(只适用于//) +SpacesBeforeTrailingComments: 1 +# 在尖括号的<后和>前添加空格 +SpacesInAngles: false +# 在容器(ObjC和JavaScript的数组和字典等)字面量中添加空格 +SpacesInContainerLiterals: true +# 在C风格类型转换的括号中添加空格 +SpacesInCStyleCastParentheses: false +# 在圆括号的(后和)前添加空格 +SpacesInParentheses: false +# 在方括号的[后和]前添加空格,lamda表达式和未指明大小的数组的声明不受影响 +SpacesInSquareBrackets: false +# 标准: Cpp03, Cpp11, Auto +Standard: Cpp11 +# tab宽度 +TabWidth: 4 +# 使用tab字符: Never, ForIndentation, ForContinuationAndIndentation, Always +UseTab: Never diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7ff7443 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/huawei_proprietary diff --git a/BUILD.gn b/BUILD.gn new file mode 100644 index 0000000..95e0f28 --- /dev/null +++ b/BUILD.gn @@ -0,0 +1,170 @@ +# Copyright (c) 2020-2021 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("//build/lite/config/component/lite_component.gni") +import("//build/lite/config/subsystem/graphic/config.gni") +import("//build/lite/ndk/ndk.gni") + +lite_component("lite_ui") { + features = [ ":ui" ] + public_deps = features +} + +ndk_lib("lite_ui_ndk") { + lib_extension = ".so" + deps = [ ":ui" ] + head_files = [ "interfaces/kits" ] +} + +config("graphic_define_config") { + include_dirs = [ + "interfaces/kits", + "interfaces/innerkits", + ] + + defines = [ + "ENABLE_VECTOR_FONT=1", + "ENABLE_SHAPING=0", + "ENABLE_ICU=0", + "ENABLE_MULTI_FONT=0", + ] + + if (ohos_kernel_type == "linux") { + defines += [ "RESOURCE_DIR=\"/storage/data/\"" ] + } else { + defines += [ "RESOURCE_DIR=\"/user/data/\"" ] + } +} + +if (enable_graphic_font) { + copy("utils_config") { + sources = [ "tools/qt/simulator/font/SourceHanSansSC-Regular.otf" ] + outputs = [ "$root_out_dir/data/SourceHanSansSC-Regular.otf" ] + } +} + +shared_library("ui") { + sources = [ + "frameworks/animator/animator.cpp", + "frameworks/animator/easing_equation.cpp", + "frameworks/animator/interpolation.cpp", + "frameworks/common/graphic_startup.cpp", + "frameworks/common/image.cpp", + "frameworks/common/input_device_manager.cpp", + "frameworks/common/screen.cpp", + "frameworks/common/screen_device_proxy.cpp", + "frameworks/common/task.cpp", + "frameworks/common/text.cpp", + "frameworks/common/typed_text.cpp", + "frameworks/common/ui_font_header.cpp", + "frameworks/components/root_view.cpp", + "frameworks/components/text_adapter.cpp", + "frameworks/components/ui_abstract_clock.cpp", + "frameworks/components/ui_abstract_progress.cpp", + "frameworks/components/ui_abstract_scroll.cpp", + "frameworks/components/ui_analog_clock.cpp", + "frameworks/components/ui_arc_label.cpp", + "frameworks/components/ui_axis.cpp", + "frameworks/components/ui_box_progress.cpp", + "frameworks/components/ui_button.cpp", + "frameworks/components/ui_canvas.cpp", + "frameworks/components/ui_chart.cpp", + "frameworks/components/ui_checkbox.cpp", + "frameworks/components/ui_circle_progress.cpp", + "frameworks/components/ui_dialog.cpp", + "frameworks/components/ui_digital_clock.cpp", + "frameworks/components/ui_image_animator.cpp", + "frameworks/components/ui_image_view.cpp", + "frameworks/components/ui_label.cpp", + "frameworks/components/ui_label_button.cpp", + "frameworks/components/ui_list.cpp", + "frameworks/components/ui_picker.cpp", + "frameworks/components/ui_qrcode.cpp", + "frameworks/components/ui_radio_button.cpp", + "frameworks/components/ui_repeat_button.cpp", + "frameworks/components/ui_scroll_view.cpp", + "frameworks/components/ui_slider.cpp", + "frameworks/components/ui_surface_view.cpp", + "frameworks/components/ui_swipe_view.cpp", + "frameworks/components/ui_texture_mapper.cpp", + "frameworks/components/ui_time_picker.cpp", + "frameworks/components/ui_toggle_button.cpp", + "frameworks/components/ui_video.cpp", + "frameworks/components/ui_view.cpp", + "frameworks/components/ui_view_group.cpp", + "frameworks/core/render_manager.cpp", + "frameworks/core/task_manager.cpp", + "frameworks/default_resource/check_box_res.cpp", + "frameworks/dfx/event_injector.cpp", + "frameworks/dfx/key_event_injector.cpp", + "frameworks/dfx/point_event_injector.cpp", + "frameworks/dfx/ui_dump_dom_tree.cpp", + "frameworks/dfx/ui_screenshot.cpp", + "frameworks/dock/input_device.cpp", + "frameworks/dock/key_input_device.cpp", + "frameworks/dock/ohos/ohos_input_device.cpp", + "frameworks/dock/pointer_input_device.cpp", + "frameworks/dock/rotate_input_device.cpp", + "frameworks/dock/virtual_input_device.cpp", + "frameworks/draw/draw_arc.cpp", + "frameworks/draw/draw_curve.cpp", + "frameworks/draw/draw_image.cpp", + "frameworks/draw/draw_label.cpp", + "frameworks/draw/draw_line.cpp", + "frameworks/draw/draw_rect.cpp", + "frameworks/draw/draw_triangle.cpp", + "frameworks/draw/draw_utils.cpp", + "frameworks/events/event.cpp", + "frameworks/font/base_font.cpp", + "frameworks/font/glyphs_manager.cpp", + "frameworks/font/ui_font.cpp", + "frameworks/font/ui_font_adaptor.cpp", + "frameworks/font/ui_font_allocator.cpp", + "frameworks/font/ui_font_cache.cpp", + "frameworks/font/ui_font_vector.cpp", + "frameworks/font/ui_line_break.cpp", + "frameworks/font/ui_multi_font_manager.cpp", + "frameworks/font/ui_text_shaping.cpp", + "frameworks/imgdecode/cache_manager.cpp", + "frameworks/imgdecode/file_img_decoder.cpp", + "frameworks/imgdecode/image_load.cpp", + "frameworks/layout/flex_layout.cpp", + "frameworks/layout/grid_layout.cpp", + "frameworks/layout/list_layout.cpp", + "frameworks/themes/theme.cpp", + "frameworks/themes/theme_manager.cpp", + "frameworks/window/window.cpp", + "frameworks/window/window_impl.cpp", + ] + include_dirs = [ "//foundation/graphic/ui/frameworks" ] + deps = [ + "//build/lite/config/component/cJSON:cjson_shared", + "//foundation/graphic/surface:lite_surface", + "//foundation/graphic/utils:lite_graphic_hals", + "//foundation/graphic/wms:lite_wms", + "//foundation/multimedia/media_lite/frameworks/player_lite:player_lite", + "//third_party/bounds_checking_function:libsec_shared", + "//third_party/freetype:freetype", + "//third_party/giflib:libgif", + "//third_party/libjpeg:libjpeg", + "//third_party/libpng:libpng", + "//third_party/qrcodegen:qrcodegen", + ] + + public_deps = [ "//foundation/graphic/utils:lite_graphic_utils" ] + public_configs = [ ":graphic_define_config" ] + cflags = [ + "-Wall", + "-fno-exceptions", + ] + cflags_cc = cflags + ldflags = [ "-Wl,-rpath-link=$ohos_root_path/$root_out_dir" ] +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4947287 --- /dev/null +++ b/LICENSE @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/README.en.md b/README.en.md deleted file mode 100644 index 25d0af2..0000000 --- a/README.en.md +++ /dev/null @@ -1,36 +0,0 @@ -# graphic_ui - -#### Description -{**When you're done, you can delete the content in this README and update the file with details for others getting started with your repository**} - -#### Software Architecture -Software architecture description - -#### Installation - -1. xxxx -2. xxxx -3. xxxx - -#### Instructions - -1. xxxx -2. xxxx -3. xxxx - -#### Contribution - -1. Fork the repository -2. Create Feat_xxx branch -3. Commit your code -4. Create Pull Request - - -#### Gitee Feature - -1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md -2. Gitee blog [blog.gitee.com](https://blog.gitee.com) -3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore) -4. The most valuable open source project [GVP](https://gitee.com/gvp) -5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help) -6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) diff --git a/README.md b/README.md index 9aad453..e75dce0 100644 --- a/README.md +++ b/README.md @@ -1,39 +1,124 @@ -# graphic_ui +# UI -#### 介绍 -{**以下是 Gitee 平台说明,您可以替换此简介** -Gitee 是 OSCHINA 推出的基于 Git 的代码托管平台(同时支持 SVN)。专为开发者提供稳定、高效、安全的云端软件开发协作平台 -无论是个人、团队、或是企业,都能够用 Gitee 实现代码托管、项目管理、协作开发。企业项目请看 [https://gitee.com/enterprises](https://gitee.com/enterprises)} +- [Introduction](#section11660541593) +- [Directory Structure](#section161941989596) +- [Constraints](#section119744591305) +- [Compilation and Building](#section137768191623) +- [Description](#section1312121216216) + - [Component Description](#section66002422015) + - [Usage](#section129654513264) -#### 软件架构 -软件架构说明 +- [Repositories Involved](#section1371113476307) + +## Introduction + +The graphics UI module implements a system-level graphics engine. + +This module provides the UIKit APIs for application development. You can use the APIs to add animations, manage layouts, transform images, process events, and operates on rich UI components. + +The graphics UI directly calls the HAL API or uses the client provided by the Window Manager Service \(WMS\) to interact with the hardware to complete operations such as event response and image drawing. + +**Figure 1** Graphics subsystem architecture +![](figures/graphics-subsystem-architecture.png "graphics-subsystem-architecture") + +## Directory Structure + +``` +/foundation/graphic/ui +├── frameworks # Framework code +│ ├── animator # Animator module +│ ├── common # Common module +│ ├── components # Components +│ ├── core # UI main processes (such as rendering and task management) +│ ├── default_resource +│ ├── dfx # Maintenance and test +│ ├── dock # Driver adaptation layer +│ │ └── ohos # OHOS platform adaptation +│ ├── draw # Drawing logic +│ ├── engines # Drawing engines +│ │ ├── dfb +│ │ ├── general +│ │ ├── gpu_vglite +│ │ └── software_zlite +│ ├── events # Events +│ ├── font # Fonts +│ ├── imgdecode # Image management +│ ├── layout # Page layout +│ ├── themes # Theme management +│ ├── window # Window management adaptation layer +│ └── window_manager +│ └── dfb +├── interfaces # APIs +│ ├── innerkits # APIs between modules +│ │ └── xxx # Sub-module APIs +│ └── kits # Externel APIs +│ │ └── xxx # Sub-module APIs +├── test # Test code +│ ├── framework +│ │ ├── include # Header files for the test framework +│ │ └── src # Source code for the test framework +│ ├── uitest # Display effect test (The executable program is in foundation/graphic/wms/test:sample_ui.) +│ │ └── test_xxx # Specific UI effect test +│ └── unittest # Unit testing +│ └── xxx # Unit testing for a specific UI component +└── tools # Test and simulation tools (simulator projects and resource files) + └── qt # Qt project +``` + +## Constraints + +Platform Constraints + +- The Windows platform supports only Qt and OHOS IDE. +- For the support of other platforms, see the **graphic** tag in **vender/hisilicon/\[product\_name\]/config.json**. If the **graphic** tag does not exist, it indicates that the product does not have the graphics subsystem. + +## Compilation and Building + +``` +# Generate the libui.so file in the out directory of the product folder through GN compilation. +hb build lite_ui + +# To compile the Qt library, see the Qt simulator project at graphic/ui/tools/qt/simulator/simulator.pro. +``` + +## Description + +### Component Description + +Components are classified into basic components and container components. + +- Basic components: Implement only a single function, such as **Text**, **Button**, **Image**, and **List**. +- Container components: Hold and combine child components to implement complex functions. + +**Figure 2** Graphics subsystem components +![](figures/graphics-subsystem-components.png "graphics-subsystem-components") + +### Usage + +For details about how to use components and APIs of graphics, see the examples provided in **foundation/graphic/ui/test/uitest**. + +- The Qt project can be debugged in the Windows environment. + + Project file path: + + ``` + graphic/ui/tools/qt/simulator/simulator.pro + ``` + +- For other debugging environments, you can run **foundation/graphic/wms/test:sample\_ui**. + + ``` + hb build lite_wms -b debug + ``` + + After the building is successful, the executable program **out/\[product\_name\]/dev\_tools/bin/sample\_ui** is obtained. You can run the program in an environment to view the display effect of a specific component. -#### 安装教程 +## Repositories Involved -1. xxxx -2. xxxx -3. xxxx +/hmf/graphic/surface -#### 使用说明 +/hmf/graphic/wms -1. xxxx -2. xxxx -3. xxxx +/hmf/graphic/utils -#### 参与贡献 - -1. Fork 本仓库 -2. 新建 Feat_xxx 分支 -3. 提交代码 -4. 新建 Pull Request - - -#### 特技 - -1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md -2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com) -3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目 -4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目 -5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help) -6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) diff --git a/README_zh.md b/README_zh.md new file mode 100644 index 0000000..621bf8a --- /dev/null +++ b/README_zh.md @@ -0,0 +1,124 @@ +# 图形UI组件 + +- [简介](#section11660541593) +- [目录](#section161941989596) +- [约束](#section119744591305) +- [编译构建](#section137768191623) +- [说明](#section1312121216216) + - [组件说明](#section66002422015) + - [使用说明](#section129654513264) + +- [相关仓](#section1371113476307) + +## 简介 + +图形UI组件实现了一套系统级的图形引擎。 + +该组件为应用开发提供UIKit接口,包括了动画、布局、图形转换、事件处理,以及丰富的UI组件。 + +组件内部直接调用HAL接口,或者使用WMS\(Window Manager Service\)提供的客户端与硬件交互,以完成事件响应、图像绘制等操作。 + +**图 1** 图形子系统架构图 +![](figures/图形子系统架构图.png "图形子系统架构图") + +## 目录 + +``` +/foundation/graphic/ui +├── frameworks # 框架代码 +│ ├── animator # 动画模块 +│ ├── common # 公共模块 +│ ├── components # 组件 +│ ├── core # ui主流程(渲染、任务管理等) +│ ├── default_resource +│ ├── dfx # 维测功能 +│ ├── dock # 驱动适配层 +│ │ └── ohos # ohos平台适配 +│ ├── draw # 绘制逻辑 +│ ├── engines # 绘制引擎 +│ │ ├── dfb +│ │ ├── general +│ │ ├── gpu_vglite +│ │ └── software_zlite +│ ├── events # 事件 +│ ├── font # 字体 +│ ├── imgdecode # 图片管理 +│ ├── layout # 页面布局 +│ ├── themes # 主题管理 +│ ├── window # 窗口管理适配层 +│ └── window_manager +│ └── dfb +├── interfaces # 接口 +│ ├── innerkits # 模块间接口 +│ │ └── xxx # 子模块的接口 +│ └── kits # 对外接口 +│ └── xxx # 子模块的接口 +├── test # 测试代码 +│ ├── framework +│ │ ├── include # 测试框架头文件 +│ │ └── src # 测试框架源码 +│ ├── uitest # 显示效果测试(可执行程序在foundation/graphic/wms/test:sample_ui) +│ │ └── test_xxx # 具体UI组件效果测试 +│ └── unittest # 单元测试 +│ └── xxx # 具体UI组件单元测试 +└── tools # 测试和模拟器工具(模拟器工程、资源文件) + └── qt # QT工程 +``` + +## 约束 + +平台约束 + +- Windows平台仅支持QT和OHOS IDE。 +- 其他平台支持情况参考vender/hisilicon/\[product\_name\]/config.json中的graphic标签(不存在graphic标签即该产品不存在图形子系统)。 + +## 编译构建 + +``` +# 通过gn编译,在out目录下对应产品的文件夹中生成libui.so +hb build lite_ui + +# 编译qt库可直接参考qt模拟器工程:graphic/ui/tools/qt/simulator/simulator.pro +``` + +## 说明 + +### 组件说明 + +组件分为基础组件和容器组件 + +- 基础组件:仅实现组件自身单一功能,比如按钮、文字、图片等; +- 容器组件:可将其他组件作为自己的子组件,通过组合实现复杂功能。 + +**图 2** 图形组件一览 +![](figures/图形组件一览.png "图形组件一览") + +### 使用说明 + +foundation/graphic/ui/test/uitest中提供了图形所有组件和功能接口的使用范例。 + +- Windows环境可运行QT工程调试 + + 工程文件路径: + + ``` + graphic/ui/tools/qt/simulator/simulator.pro + ``` + +- 其他调试环境可以编译运行foundation/graphic/wms/test:sample\_ui + + ``` + hb build lite_wms -b debug + ``` + + 编译成功后得到可执行程序out/\[product\_name\]/dev\_tools/bin/sample\_ui,在实际环境上运行即可观察对应组件显示效果。 + + +## 相关仓 + +/hmf/graphic/surface + +/hmf/graphic/wms + +/hmf/graphic/utils + diff --git a/figures/graphics-subsystem-architecture.png b/figures/graphics-subsystem-architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..b848d792280f09748bc5f347bee53223581f8fa2 GIT binary patch literal 41642 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 YwADu_FcVP&sYehX-TE7vewjbalpiy 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 literal 0 HcmV?d00001 diff --git a/figures/图形子系统架构图.png b/figures/图形子系统架构图.png new file mode 100644 index 0000000000000000000000000000000000000000..b848d792280f09748bc5f347bee53223581f8fa2 GIT binary patch literal 41642 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 YwADu_FcVP&sYehX-TE7vewjbalpiy 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 literal 0 HcmV?d00001 diff --git a/frameworks/animator/animator.cpp b/frameworks/animator/animator.cpp new file mode 100755 index 0000000..0629cbe --- /dev/null +++ b/frameworks/animator/animator.cpp @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "animator/animator.h" + +#include "common/task_manager.h" +#include "hal_tick.h" + +namespace OHOS { +void Animator::Start() +{ + SetState(START); + runTime_ = 0; + lastRunTime_ = 0; +} + +void Animator::Stop() +{ + SetState(STOP); + if (callback_ != nullptr) { + callback_->OnStop(*view_); + } +} + +void Animator::Pause() +{ + SetState(PAUSE); +} + +void Animator::Resume() +{ + SetState(START); + lastRunTime_ = HALTick::GetInstance().GetTime(); +} + +void Animator::Run() +{ + if (lastRunTime_ == 0) { + lastRunTime_ = HALTick::GetInstance().GetTime(); + } + + uint32_t elepse = HALTick::GetInstance().GetElapseTime(lastRunTime_); + + runTime_ = (UINT32_MAX - elepse > runTime_) ? (runTime_ + elepse) : time_; + lastRunTime_ = HALTick::GetInstance().GetTime(); + if (callback_ != nullptr) { + callback_->Callback(view_); + } +} + +void AnimatorManager::Init() +{ + Task::Init(); +} + +void AnimatorManager::Add(Animator* animator) +{ + if (animator == nullptr) { + return; + } + + list_.PushBack(animator); +} + +void AnimatorManager::Remove(const Animator* animator) +{ + if (animator == nullptr) { + return; + } + ListNode* pos = list_.Begin(); + while (pos != list_.End()) { + if (pos->data_ == animator) { + list_.Remove(pos); + return; + } + pos = pos->next_; + } +} + +void AnimatorManager::AnimatorTask() +{ + ListNode* pos = list_.Begin(); + Animator* animator = nullptr; + + while (pos != list_.End()) { + animator = pos->data_; + if (animator->GetState() == Animator::START) { + if (animator->IsRepeat() || (animator->GetRunTime() <= animator->GetTime())) { + animator->Run(); + } else { + animator->Stop(); + } + } + + pos = pos->next_; + } +} +} diff --git a/frameworks/animator/easing_equation.cpp b/frameworks/animator/easing_equation.cpp new file mode 100755 index 0000000..9d46fb0 --- /dev/null +++ b/frameworks/animator/easing_equation.cpp @@ -0,0 +1,254 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "animator/easing_equation.h" +#include "graphic_math.h" + +namespace OHOS { +double EasingEquation::overshoot_ = 1.7; // The empirical value commonly used in easing equation + +void EasingEquation::SetBackOvershoot(double overshoot) +{ + if ((overshoot >= OVERSHOOT_MIN) && (overshoot <= OVERSHOOT_MAX)) { + overshoot_ = overshoot; + } +} + +int16_t EasingEquation::BackEaseIn(int16_t startPos, int16_t endPos, uint16_t curTime, uint16_t durationTime) +{ + if (curTime < durationTime) { + double t = -(static_cast(curTime) / durationTime); + double x = -t * t * ((overshoot_ + 1) * t + overshoot_); + return static_cast((x * (static_cast(endPos) - startPos)) + startPos); + } + + return endPos; +} + +int16_t EasingEquation::BackEaseOut(int16_t startPos, int16_t endPos, uint16_t curTime, uint16_t durationTime) +{ + if (curTime < durationTime) { + double t = static_cast(curTime) / durationTime; + t -= 1.0; + double x = t * t * ((overshoot_ + 1) * t + overshoot_) + 1; + return static_cast((x * (static_cast(endPos) - startPos)) + startPos); + } + + return endPos; +} + +int16_t EasingEquation::BackEaseInOut(int16_t startPos, int16_t endPos, uint16_t curTime, uint16_t durationTime) +{ + uint16_t halfTime = durationTime >> 1; + int16_t halfStep = (endPos >> 1) + (startPos >> 1); + if (curTime < halfTime) { + return BackEaseIn(startPos, halfStep, curTime, halfTime); + } + return BackEaseOut(halfStep, endPos, curTime - halfTime, halfTime); +} + +/* 1 - sqrt(1 - t^2) */ +int16_t EasingEquation::CircEaseIn(int16_t startPos, int16_t endPos, uint16_t curTime, uint16_t durationTime) +{ + if (curTime < durationTime) { + int32_t t = (curTime << INTERPOLATION_RANGE_OFFSET) / durationTime; + uint32_t x = INTERPOLATION_RANGE - Sqrt(INTERPOLATION_RANGE_SQUARE - t * t); + return static_cast(((x * (static_cast(endPos) - startPos)) >> INTERPOLATION_RANGE_OFFSET) + + startPos); + } + + return endPos; +} + +/* sqrt(1 - (1 - t)^2) */ +int16_t EasingEquation::CircEaseOut(int16_t startPos, int16_t endPos, uint16_t curTime, uint16_t durationTime) +{ + if (curTime < durationTime) { + int32_t t = INTERPOLATION_RANGE - (curTime << INTERPOLATION_RANGE_OFFSET) / durationTime; + uint32_t x = static_cast(Sqrt(INTERPOLATION_RANGE_SQUARE - t * t)); + return static_cast(((x * (static_cast(endPos) - startPos)) >> INTERPOLATION_RANGE_OFFSET) + + startPos); + } + + return endPos; +} + +int16_t EasingEquation::CircEaseInOut(int16_t startPos, int16_t endPos, uint16_t curTime, uint16_t durationTime) +{ + uint16_t halfTime = durationTime >> 1; + int16_t halfStep = (endPos >> 1) + (startPos >> 1); + if (curTime < halfTime) { + return CircEaseIn(startPos, halfStep, curTime, halfTime); + } + return CircEaseOut(halfStep, endPos, curTime - halfTime, halfTime); +} + +/* t^3 */ +int16_t EasingEquation::CubicEaseIn(int16_t startPos, int16_t endPos, uint16_t curTime, uint16_t durationTime) +{ + if (curTime < durationTime) { + int32_t t = (curTime << INTERPOLATION_RANGE_OFFSET) / durationTime; + int16_t x = (t * t * t) >> (INTERPOLATION_RANGE_OFFSET << 1); + return static_cast(((x * (static_cast(endPos) - startPos)) >> INTERPOLATION_RANGE_OFFSET) + + startPos); + } + + return endPos; +} + +/* 1 - (1 - t)^3 */ +int16_t EasingEquation::CubicEaseOut(int16_t startPos, int16_t endPos, uint16_t curTime, uint16_t durationTime) +{ + if (curTime < durationTime) { + int32_t t = (curTime << INTERPOLATION_RANGE_OFFSET) / durationTime; + t = INTERPOLATION_RANGE - t; + int16_t x = INTERPOLATION_RANGE - ((t * t * t) >> (INTERPOLATION_RANGE_OFFSET << 1)); + return static_cast(((x * (static_cast(endPos) - startPos)) >> INTERPOLATION_RANGE_OFFSET) + + startPos); + } + + return endPos; +} + +int16_t EasingEquation::CubicEaseInOut(int16_t startPos, int16_t endPos, uint16_t curTime, uint16_t durationTime) +{ + uint16_t halfTime = durationTime >> 1; + int16_t halfStep = (endPos >> 1) + (startPos >> 1); + if (curTime < halfTime) { + return CubicEaseIn(startPos, halfStep, curTime, halfTime); + } + return CubicEaseOut(halfStep, endPos, curTime - halfTime, halfTime); +} + +int16_t EasingEquation::LinearEaseNone(int16_t startPos, int16_t endPos, uint16_t curTime, uint16_t durationTime) +{ + if (curTime < durationTime) { + int32_t t = (curTime << INTERPOLATION_RANGE_OFFSET) / durationTime; + return static_cast(((t * (static_cast(endPos) - startPos)) >> INTERPOLATION_RANGE_OFFSET) + + startPos); + } + + return endPos; +} + +/* t^2 */ +int16_t EasingEquation::QuadEaseIn(int16_t startPos, int16_t endPos, uint16_t curTime, uint16_t durationTime) +{ + if (curTime < durationTime) { + int32_t t = (curTime << INTERPOLATION_RANGE_OFFSET) / durationTime; + int16_t x = (t * t) >> INTERPOLATION_RANGE_OFFSET; + return static_cast(((x * (static_cast(endPos) - startPos)) >> INTERPOLATION_RANGE_OFFSET) + + startPos); + } + + return endPos; +} + +/* 1 - (1 - t)^2 */ +int16_t EasingEquation::QuadEaseOut(int16_t startPos, int16_t endPos, uint16_t curTime, uint16_t durationTime) +{ + if (curTime < durationTime) { + int32_t t = INTERPOLATION_RANGE - (curTime << INTERPOLATION_RANGE_OFFSET) / durationTime; + int16_t x = INTERPOLATION_RANGE - ((t * t) >> INTERPOLATION_RANGE_OFFSET); + return static_cast(((x * (static_cast(endPos) - startPos)) >> INTERPOLATION_RANGE_OFFSET) + + startPos); + } + + return endPos; +} + +int16_t EasingEquation::QuadEaseInOut(int16_t startPos, int16_t endPos, uint16_t curTime, uint16_t durationTime) +{ + uint16_t halfTime = durationTime >> 1; + int16_t halfStep = (endPos >> 1) + (startPos >> 1); + if (curTime < halfTime) { + return QuadEaseIn(startPos, halfStep, curTime, halfTime); + } + return QuadEaseOut(halfStep, endPos, curTime - halfTime, halfTime); +} + +/* t^5 */ +int16_t EasingEquation::QuintEaseIn(int16_t startPos, int16_t endPos, uint16_t curTime, uint16_t durationTime) +{ + if (curTime < durationTime) { + int64_t t = (curTime << INTERPOLATION_RANGE_OFFSET) / durationTime; + + /* 4: the fourth power of t */ + int16_t x = (t * t * t * t * t) >> (INTERPOLATION_RANGE_OFFSET * 4); + return static_cast(((x * (static_cast(endPos) - startPos)) >> INTERPOLATION_RANGE_OFFSET) + + startPos); + } + + return endPos; +} + +/* 1 - (1 - t)^5 */ +int16_t EasingEquation::QuintEaseOut(int16_t startPos, int16_t endPos, uint16_t curTime, uint16_t durationTime) +{ + if (curTime < durationTime) { + int64_t t = (curTime << INTERPOLATION_RANGE_OFFSET) / durationTime; + t = INTERPOLATION_RANGE - t; + + /* 4: the fourth power of t */ + int16_t x = INTERPOLATION_RANGE - ((t * t * t * t * t) >> (INTERPOLATION_RANGE_OFFSET * 4)); + return static_cast(((x * (static_cast(endPos) - startPos)) >> INTERPOLATION_RANGE_OFFSET) + + startPos); + } + + return endPos; +} + +int16_t EasingEquation::QuintEaseInOut(int16_t startPos, int16_t endPos, uint16_t curTime, uint16_t durationTime) +{ + uint16_t halfTime = durationTime >> 1; + int16_t halfStep = (endPos >> 1) + (startPos >> 1); + if (curTime < halfTime) { + return QuintEaseIn(startPos, halfStep, curTime, halfTime); + } + return QuintEaseOut(halfStep, endPos, curTime - halfTime, halfTime); +} + +int16_t EasingEquation::SineEaseIn(int16_t startPos, int16_t endPos, uint16_t curTime, uint16_t durationTime) +{ + if (curTime < durationTime) { + int16_t t = (curTime * QUARTER_IN_DEGREE) / durationTime - QUARTER_IN_DEGREE; + float x = Sin(t) + 1; + return static_cast(x * (endPos - startPos)) + startPos; + } + + return endPos; +} + +int16_t EasingEquation::SineEaseOut(int16_t startPos, int16_t endPos, uint16_t curTime, uint16_t durationTime) +{ + if (curTime < durationTime) { + int16_t t = (curTime * QUARTER_IN_DEGREE) / durationTime; + float x = Sin(t); + return static_cast(x * (endPos - startPos)) + startPos; + } + + return endPos; +} + +int16_t EasingEquation::SineEaseInOut(int16_t startPos, int16_t endPos, uint16_t curTime, uint16_t durationTime) +{ + uint16_t halfTime = durationTime >> 1; + int16_t halfStep = (endPos >> 1) + (startPos >> 1); + if (curTime < halfTime) { + return SineEaseIn(startPos, halfStep, curTime, halfTime); + } + return SineEaseOut(halfStep, endPos, curTime - halfTime, halfTime); +} +} \ No newline at end of file diff --git a/frameworks/animator/interpolation.cpp b/frameworks/animator/interpolation.cpp new file mode 100755 index 0000000..02c8471 --- /dev/null +++ b/frameworks/animator/interpolation.cpp @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "animator/interpolation.h" + +namespace OHOS { +/* B(t) = P0*(1-t)^3 + 3*P1*t*(1-t)^2 + 3*P2*t^2*(1-t) + P3*t^3 */ +int16_t Interpolation::GetBezierInterpolation(int16_t t, int16_t u0, int16_t u1, int16_t u2, int16_t u3) +{ + int64_t invT = INTERPOLATION_RANGE - t; + int64_t invT2 = invT * invT; + int64_t invT3 = invT2 * invT; + int64_t t2 = t * t; + int64_t t3 = t2 * t; + + int64_t ret = invT3 * u0; + ret += BESSEL_COEFFICIENT * invT2 * t * u1; + ret += BESSEL_COEFFICIENT * invT * t2 * u2; + ret += t3 * u3; + ret = ret >> CUBIC_BEZIER_CALCULATE_OFFSET; + return static_cast(ret); +} +} diff --git a/frameworks/common/graphic_startup.cpp b/frameworks/common/graphic_startup.cpp new file mode 100644 index 0000000..5e42e99 --- /dev/null +++ b/frameworks/common/graphic_startup.cpp @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "common/graphic_startup.h" +#include "animator/animator.h" +#include "common/input_device_manager.h" +#include "common/task_manager.h" +#include "core/render_manager.h" +#include "dfx/performance_task.h" +#include "file.h" +#include "font/ui_font.h" +#if ENABLE_SHAPING +#include "font/ui_text_shaping.h" +#endif +#include "graphic_log.h" +#include "imgdecode/cache_manager.h" +#ifdef VERSION_STANDARD +#include "dock/ohos/ohos_input_device.h" +#endif +#if ENABLE_WINDOW +#include "iwindows_manager.h" +#endif +#if ENABLE_GFX_ENGINES +#include "hals/gfx_engines.h" +#endif + +namespace OHOS { +void GraphicStartUp::InitFontEngine(uintptr_t psramAddr, uint32_t psramLen, const char* dPath, const char* ttfName) +{ +#if ENABLE_VECTOR_FONT + UIFont* fontEngine = UIFont::GetInstance(); + if (fontEngine == nullptr) { + GRAPHIC_LOGE("Get UIFont error"); + return; + } + fontEngine->SetPsramMemory(psramAddr, psramLen); + // font and glyph path + int8_t ret = fontEngine->SetFontPath(const_cast(dPath), nullptr); + if (ret == INVALID_RET_VALUE) { + GRAPHIC_LOGW("SetFontPath failed"); + } + if (ttfName != nullptr) { + uint8_t ret2 = fontEngine->RegisterFontInfo(ttfName); + if (ret2 == INVALID_UCHAR_ID) { + GRAPHIC_LOGW("SetTtfName failed"); + } + } +#endif +} + +void GraphicStartUp::Init() +{ + TaskManager::GetInstance()->SetTaskRun(true); + DEBUG_PERFORMANCE_TASK_INIT(); + + if (INDEV_READ_PERIOD > 0) { + InputDeviceManager::GetInstance()->Init(); + } + AnimatorManager::GetInstance()->Init(); + + StyleDefault::Init(); + RenderManager::GetInstance().Init(); + + CacheManager::GetInstance().Init(IMG_CACHE_SIZE); +#ifdef VERSION_STANDARD + OHOSInputDevice* input = new OHOSInputDevice(); + if (input == nullptr) { + GRAPHIC_LOGE("new OHOSInputDevice fail"); + return; + } + InputDeviceManager::GetInstance()->Add(input); +#endif + +#if ENABLE_WINDOW + IWindowsManager::GetInstance()->Init(); +#endif +#if ENABLE_GFX_ENGINES + GfxEngines::GetInstance()->InitDriver(); +#endif +} +} // namespace OHOS diff --git a/frameworks/common/image.cpp b/frameworks/common/image.cpp new file mode 100755 index 0000000..e58af8c --- /dev/null +++ b/frameworks/common/image.cpp @@ -0,0 +1,449 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "common/image.h" +#include "common/image_decode_ability.h" +#include "draw/draw_image.h" +#include "file.h" +#include "graphic_log.h" +#include "imgdecode/cache_manager.h" +#if ENABLE_JPEG_AND_PNG +#include "jpeglib.h" +#include "png.h" +#endif +#include "securec.h" + +namespace OHOS { +Image::Image() : imageInfo_(nullptr), path_(nullptr), srcType_(IMG_SRC_UNKNOWN), mallocFlag_(false) {} + +Image::~Image() +{ + if (srcType_ == IMG_SRC_FILE) { + CacheManager::GetInstance().Close(path_); + } + if (imageInfo_ != nullptr) { + if (mallocFlag_) { + if (imageInfo_->data != nullptr) { + UIFree(reinterpret_cast(const_cast(imageInfo_->data))); + } + mallocFlag_ = false; + } + UIFree(reinterpret_cast(const_cast(imageInfo_))); + imageInfo_ = nullptr; + } + if (path_ != nullptr) { + UIFree(reinterpret_cast(const_cast(path_))); + path_ = nullptr; + } + srcType_ = IMG_SRC_UNKNOWN; +} + +void Image::GetHeader(ImageHeader& header) const +{ + if ((srcType_ == IMG_SRC_VARIABLE) && (imageInfo_ != nullptr)) { + header = imageInfo_->header; + } else if ((srcType_ == IMG_SRC_FILE) && (path_ != nullptr)) { + CacheManager::GetInstance().GetImageHeader(path_, header); + } +} + +#if ENABLE_JPEG_AND_PNG +Image::ImageType Image::CheckImgType(const char* src) +{ + char buf[IMG_BYTES_TO_CHECK] = {0}; +#ifdef _WIN32 + int32_t fd = open(src, O_RDONLY | O_BINARY); +#else + int32_t fd = open(src, O_RDONLY); +#endif + if (fd < 0) { + GRAPHIC_LOGE("can't open %s\n", src); + return IMG_UNKNOWN; + } + if (read(fd, buf, IMG_BYTES_TO_CHECK) != IMG_BYTES_TO_CHECK) { + close(fd); + return IMG_UNKNOWN; + } + close(fd); + if (!png_sig_cmp(reinterpret_cast(buf), 0, IMG_BYTES_TO_CHECK)) { + return IMG_PNG; + // 0xFF 0xD8: JPEG file's header + } else if ((static_cast(buf[0]) == 0xFF) && (static_cast(buf[1]) == 0xD8)) { + return IMG_JPEG; + } + return IMG_UNKNOWN; +} +#endif + +bool Image::SetStandardSrc(const char* src) +{ + if (src == nullptr) { + return false; + } + const char* ptr = strrchr(src, '.'); + if (ptr == nullptr) { + srcType_ = IMG_SRC_UNKNOWN; + return false; + } + +#if ENABLE_JPEG_AND_PNG + ImageType imageType = CheckImgType(src); + if (imageType == IMG_PNG) { + return SetPNGSrc(src); + } else if (imageType == IMG_JPEG) { + return SetJPEGSrc(src); + } +#endif + + size_t strLen = strlen(src) + 1; + char* imagePath = static_cast(UIMalloc(static_cast(strLen))); + if (imagePath == nullptr) { + srcType_ = IMG_SRC_UNKNOWN; + return false; + } + + if (strcpy_s(imagePath, strLen, src) != EOK) { + UIFree(reinterpret_cast(imagePath)); + imagePath = nullptr; + srcType_ = IMG_SRC_UNKNOWN; + return false; + } + path_ = imagePath; + srcType_ = IMG_SRC_FILE; + return true; +} + +bool Image::SetLiteSrc(const char* src) +{ + if (src == nullptr) { + return false; + } + const char* ptr = strrchr(src, '.'); + if (ptr == nullptr) { + srcType_ = IMG_SRC_UNKNOWN; + return false; + } + + size_t strLen = strlen(src) + 1; + char* imagePath = static_cast(UIMalloc(static_cast(strLen))); + if (imagePath == nullptr) { + srcType_ = IMG_SRC_UNKNOWN; + return false; + } + if (IsImgValid(ptr)) { + const char* suffixName = "bin"; + if (memcpy_s(imagePath, strLen, src, strLen) != EOK) { + UIFree(reinterpret_cast(imagePath)); + imagePath = nullptr; + srcType_ = IMG_SRC_UNKNOWN; + return false; + } + (ptr - src + imagePath)[1] = '\0'; // remove suffix + if (strcat_s(imagePath, strLen, suffixName) != EOK) { + UIFree(reinterpret_cast(imagePath)); + imagePath = nullptr; + srcType_ = IMG_SRC_UNKNOWN; + return false; + } + } else { + if (memcpy_s(imagePath, strLen, src, strLen) != EOK) { + UIFree(reinterpret_cast(imagePath)); + imagePath = nullptr; + srcType_ = IMG_SRC_UNKNOWN; + return false; + } + } + path_ = imagePath; + srcType_ = IMG_SRC_FILE; + return true; +} + +bool Image::SetSrc(const char* src) +{ + if (path_ != nullptr) { + UIFree(reinterpret_cast(const_cast(path_))); + path_ = nullptr; + } + + if (src != nullptr) { + uint32_t imageType = ImageDecodeAbility::GetInstance().GetImageDecodeAbility(); + if (((imageType & IMG_SUPPORT_JPEG) == IMG_SUPPORT_JPEG) || + ((imageType & IMG_SUPPORT_PNG) == IMG_SUPPORT_PNG)) { + return SetStandardSrc(src); + } + return SetLiteSrc(src); + } else { + path_ = src; + srcType_ = IMG_SRC_UNKNOWN; + } + return true; +} + +bool Image::SetSrc(const ImageInfo* src) +{ + if (imageInfo_ != nullptr) { + if (mallocFlag_) { + if (imageInfo_->data != nullptr) { + UIFree(reinterpret_cast(const_cast(imageInfo_->data))); + } + mallocFlag_ = false; + } + UIFree(reinterpret_cast(const_cast(imageInfo_))); + imageInfo_ = nullptr; + } + + if (src != nullptr) { + imageInfo_ = static_cast(UIMalloc(static_cast(sizeof(ImageInfo)))); + if (imageInfo_ == nullptr) { + srcType_ = IMG_SRC_UNKNOWN; + return false; + } + + if (memcpy_s(const_cast(imageInfo_), sizeof(ImageInfo), src, sizeof(ImageInfo)) != EOK) { + srcType_ = IMG_SRC_UNKNOWN; + return false; + } + + srcType_ = IMG_SRC_VARIABLE; + } else { + imageInfo_ = src; + srcType_ = IMG_SRC_UNKNOWN; + } + return true; +} + +void Image::DrawImage(const Rect& coords, const Rect& mask, const Style& style, uint8_t opaScale) const +{ + if (srcType_ == IMG_SRC_VARIABLE) { + DrawImage::DrawCommon(coords, mask, imageInfo_, style, opaScale); + } else if (srcType_ == IMG_SRC_FILE) { + DrawImage::DrawCommon(coords, mask, path_, style, opaScale); + } else { + GRAPHIC_LOGE("Image::DrawImage:: failed with error srctype!\n"); + } +} + +#if ENABLE_JPEG_AND_PNG +bool Image::SetPNGSrc(const char* src) +{ + FILE* infile = nullptr; + png_bytep* rowPointer = nullptr; + png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); + if (png == nullptr) { + srcType_ = IMG_SRC_UNKNOWN; + return false; + } + png_infop info = png_create_info_struct(png); + if (info == nullptr) { + srcType_ = IMG_SRC_UNKNOWN; + png_destroy_read_struct(&png, &info, nullptr); + return false; + } + if ((infile = fopen(src, "rb")) == nullptr) { + GRAPHIC_LOGE("can't open %s\n", src); + srcType_ = IMG_SRC_UNKNOWN; + png_destroy_read_struct(&png, &info, nullptr); + return false; + } + png_init_io(png, infile); + png_read_info(png, info); + + uint8_t pixelByteSize = DrawUtils::GetPxSizeByColorMode(ARGB8888) >> 3; // 3: Shift right 3 bits + uint16_t width = png_get_image_width(png, info); + uint16_t height = png_get_image_height(png, info); + uint8_t colorType = png_get_color_type(png, info); + uint8_t bitDepth = png_get_bit_depth(png, info); + uint32_t dataSize = height * width * pixelByteSize; + + if ((colorType == PNG_COLOR_TYPE_GRAY) && (bitDepth < 8)) { // 8: Expand grayscale images to the full 8 bits + png_set_expand_gray_1_2_4_to_8(png); + } + if ((colorType == PNG_COLOR_TYPE_GRAY) || (colorType == PNG_COLOR_TYPE_GRAY_ALPHA)) { + png_set_gray_to_rgb(png); + } + if (colorType == PNG_COLOR_TYPE_PALETTE) { + png_set_palette_to_rgb(png); + } + if (bitDepth == 16) { // 16: Chop 16-bit depth images to 8-bit depth + png_set_strip_16(png); + } + if (png_get_valid(png, info, PNG_INFO_tRNS)) { + png_set_tRNS_to_alpha(png); + } + if (!(colorType & PNG_COLOR_MASK_ALPHA)) { + png_set_add_alpha(png, 0xFF, PNG_FILLER_AFTER); + } + png_set_interlace_handling(png); + png_read_update_info(png, info); + + rowPointer = static_cast(UIMalloc(sizeof(png_bytep) * height)); + if (rowPointer == nullptr) { + srcType_ = IMG_SRC_UNKNOWN; + fclose(infile); + png_destroy_read_struct(&png, &info, nullptr); + return false; + } + for (uint16_t y = 0; y < height; y++) { + rowPointer[y] = static_cast(UIMalloc(png_get_rowbytes(png, info))); + if (rowPointer[y] == nullptr) { + for (uint16_t i = 0; i < y; i++) { + UIFree(rowPointer[i]); + rowPointer[i] = nullptr; + } + fclose(infile); + UIFree(rowPointer); + srcType_ = IMG_SRC_UNKNOWN; + png_destroy_read_struct(&png, &info, nullptr); + return false; + } + } + png_read_image(png, rowPointer); + fclose(infile); + png_destroy_read_struct(&png, &info, nullptr); + ImageInfo* imgInfo = static_cast(UIMalloc(sizeof(ImageInfo))); + if (imgInfo == nullptr) { + for (uint16_t i = 0; i < height; i++) { + UIFree(rowPointer[i]); + rowPointer[i] = nullptr; + } + UIFree(rowPointer); + srcType_ = IMG_SRC_UNKNOWN; + return false; + } + uint8_t* srcData = static_cast(UIMalloc(dataSize)); + if (srcData == nullptr) { + for (uint16_t i = 0; i < height; i++) { + UIFree(rowPointer[i]); + rowPointer[i] = nullptr; + } + UIFree(rowPointer); + UIFree(imgInfo); + srcType_ = IMG_SRC_UNKNOWN; + return false; + } + uint32_t n = 0; + for (uint16_t y = 0; y < height; y++) { + png_bytep row = rowPointer[y]; + for (uint16_t x = 0; x < width * pixelByteSize; x += pixelByteSize) { + srcData[n++] = row[x + 2]; // 2: B channel + srcData[n++] = row[x + 1]; // 1: G channel + srcData[n++] = row[x + 0]; // 0: R channel + srcData[n++] = row[x + 3]; // 3: Alpha channel + } + UIFree(row); + row = nullptr; + } + UIFree(rowPointer); + + imgInfo->header.width = width; + imgInfo->header.height = height; + imgInfo->header.colorMode = ARGB8888; + imgInfo->dataSize = dataSize; + imgInfo->data = srcData; + + if (imageInfo_ != nullptr) { + if (mallocFlag_) { + if (imageInfo_->data != nullptr) { + UIFree(reinterpret_cast(const_cast(imageInfo_->data))); + } + mallocFlag_ = false; + } + UIFree(reinterpret_cast(const_cast(imageInfo_))); + imageInfo_ = nullptr; + } + imageInfo_ = imgInfo; + mallocFlag_ = true; + srcType_ = IMG_SRC_VARIABLE; + return true; +} + +bool Image::SetJPEGSrc(const char* src) +{ + struct jpeg_decompress_struct cinfo; + struct jpeg_error_mgr jerr; + FILE* infile = nullptr; + + if ((infile = fopen(src, "rb")) == nullptr) { + GRAPHIC_LOGE("can't open %s\n", src); + srcType_ = IMG_SRC_UNKNOWN; + return false; + } + cinfo.err = jpeg_std_error(&jerr); + jpeg_create_decompress(&cinfo); + jpeg_stdio_src(&cinfo, infile); + jpeg_read_header(&cinfo, TRUE); + jpeg_start_decompress(&cinfo); + + uint8_t pixelByteSize = DrawUtils::GetPxSizeByColorMode(ARGB8888) >> 3; // 3: Shift right 3 bits + uint16_t width = cinfo.output_width; + uint16_t height = cinfo.output_height; + uint32_t dataSize = width * height * pixelByteSize; + uint16_t rowStride = cinfo.output_width * pixelByteSize; + JSAMPARRAY buffer = (*cinfo.mem->alloc_sarray)(reinterpret_cast(&cinfo), JPOOL_IMAGE, rowStride, + 1); // 1: one-row-high array + ImageInfo* imgInfo = static_cast(UIMalloc(sizeof(ImageInfo))); + if (imgInfo == nullptr) { + jpeg_finish_decompress(&cinfo); + jpeg_destroy_decompress(&cinfo); + fclose(infile); + srcType_ = IMG_SRC_UNKNOWN; + return false; + } + uint8_t* srcData = static_cast(UIMalloc(dataSize)); + if (srcData == nullptr) { + jpeg_finish_decompress(&cinfo); + jpeg_destroy_decompress(&cinfo); + fclose(infile); + UIFree(imgInfo); + srcType_ = IMG_SRC_UNKNOWN; + return false; + } + uint32_t n = 0; + while (cinfo.output_scanline < cinfo.output_height) { + jpeg_read_scanlines(&cinfo, buffer, 1); // 1: read one line each time + for (uint16_t x = 0; x < width * 3; x += 3) { // 3: color components per pixel + srcData[n++] = buffer[0][x + 2]; // 2: B channel + srcData[n++] = buffer[0][x + 1]; // 1: G channel + srcData[n++] = buffer[0][x + 0]; // 0: R channel + srcData[n++] = 255; // 255: set alpha channel + } + } + jpeg_finish_decompress(&cinfo); + jpeg_destroy_decompress(&cinfo); + fclose(infile); + + imgInfo->header.width = width; + imgInfo->header.height = height; + imgInfo->header.colorMode = ARGB8888; + imgInfo->dataSize = dataSize; + imgInfo->data = srcData; + + if (imageInfo_ != nullptr) { + if (mallocFlag_) { + if (imageInfo_->data != nullptr) { + UIFree(reinterpret_cast(const_cast(imageInfo_->data))); + } + mallocFlag_ = false; + } + UIFree(reinterpret_cast(const_cast(imageInfo_))); + imageInfo_ = nullptr; + } + imageInfo_ = imgInfo; + mallocFlag_ = true; + srcType_ = IMG_SRC_VARIABLE; + return true; +} +#endif +} // namespace OHOS \ No newline at end of file diff --git a/frameworks/common/input_device_manager.cpp b/frameworks/common/input_device_manager.cpp new file mode 100755 index 0000000..23923b0 --- /dev/null +++ b/frameworks/common/input_device_manager.cpp @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "common/input_device_manager.h" +#include "common/task_manager.h" +#include "graphic_log.h" + +namespace OHOS { +void InputDeviceManager::Init() +{ + if (INDEV_READ_PERIOD > 0) { + SetPeriod(INDEV_READ_PERIOD); + TaskManager::GetInstance()->Add(this); + } +} + +void InputDeviceManager::Add(InputDevice* device) +{ + if (device == nullptr) { + GRAPHIC_LOGE("InputDeviceManager::Add invalid param\n"); + return; + } + deviceList_.PushBack(device); +} + +void InputDeviceManager::Remove(InputDevice* device) +{ + if (device == nullptr) { + return; + } + ListNode* node = deviceList_.Begin(); + while (node != deviceList_.End()) { + if (node->data_ == device) { + deviceList_.Remove(node); + return; + } + node = node->next_; + } +} + +void InputDeviceManager::Callback() +{ + ListNode* node = deviceList_.Begin(); + while (node != deviceList_.End()) { + node->data_->ProcessEvent(); + node = node->next_; + } +} + +void InputDeviceManager::Clear() +{ + deviceList_.Clear(); +} +} // namespace OHOS \ No newline at end of file diff --git a/frameworks/common/screen.cpp b/frameworks/common/screen.cpp new file mode 100755 index 0000000..0e51e1f --- /dev/null +++ b/frameworks/common/screen.cpp @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "common/screen.h" +#include "dock/screen_device_proxy.h" + +namespace OHOS { +uint16_t Screen::GetWidth() +{ + return ScreenDeviceProxy::GetInstance()->GetScreenWidth(); +} + +uint16_t Screen::GetHeight() +{ + return ScreenDeviceProxy::GetInstance()->GetScreenHeight(); +} +} // namespace OHOS \ No newline at end of file diff --git a/frameworks/common/screen_device_proxy.cpp b/frameworks/common/screen_device_proxy.cpp new file mode 100644 index 0000000..b8d94bd --- /dev/null +++ b/frameworks/common/screen_device_proxy.cpp @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "dock/screen_device_proxy.h" +#include "draw/draw_utils.h" +#include "graphic_log.h" +#include "securec.h" + +namespace OHOS { +#if ENABLE_FRAME_BUFFER +void ScreenDeviceProxy::Flush() {} +#else +void ScreenDeviceProxy::Flush() +{ + flush_.Flushing(); + + if (device_ != nullptr) { +#if ENABLE_WINDOW + device_->Flush(bufferRect_.GetLeft(), bufferRect_.GetTop(), bufferRect_.GetRight(), bufferRect_.GetBottom(), + gfxAlloc_.virAddr, ARGB8888); +#else + device_->Flush(bufferRect_.GetLeft(), bufferRect_.GetTop(), bufferRect_.GetRight(), bufferRect_.GetBottom(), + buffer_, ARGB8888); +#endif + } +} +#endif + +void ScreenDeviceProxy::OnFlushReady() +{ + flush_.Notify(); +} + +void ScreenDeviceProxy::OnRenderFinish() +{ + if (device_ != nullptr) { + device_->RenderFinish(); + } +} + +void ScreenDeviceProxy::DrawAnimatorBuffer(const Rect& invalidatedArea) +{ + Rect invalidRect = curViewRect_; + transMap_.SetTransMapRect(curViewRect_); + invalidRect.Join(invalidRect, transMap_.GetBoxRect()); + + if (invalidRect.Intersect(invalidRect, invalidatedArea)) { + uint8_t pxSize = DrawUtils::GetPxSizeByColorMode(animatorImageInfo_.header.colorMode); + TransformDataInfo imageTranDataInfo = {animatorImageInfo_.header, animatorImageInfo_.data, pxSize, LEVEL0, + BILINEAR}; + DrawUtils::GetInstance()->DrawTransform(invalidRect, {0, 0}, Color::Black(), OPA_OPAQUE, transMap_, + imageTranDataInfo); + } +} + +void ScreenDeviceProxy::SetAnimatorRect(const Rect& rect) +{ + curViewRect_ = rect; + uint16_t bufferWidth = (width_ > curViewRect_.GetWidth()) ? curViewRect_.GetWidth() : width_; + uint16_t bufferHeight = (height_ > curViewRect_.GetHeight()) ? curViewRect_.GetHeight() : height_; + + animatorImageInfo_.header.colorMode = animatorBufferMode_; + animatorImageInfo_.dataSize = bufferWidth * bufferHeight * DrawUtils::GetByteSizeByColorMode(animatorBufferMode_); + animatorImageInfo_.header.width = bufferWidth; + animatorImageInfo_.header.height = bufferHeight; + animatorImageInfo_.header.reserved = 0; + animatorImageInfo_.data = reinterpret_cast(GetBuffer()); + if (animatorImageInfo_.data == nullptr) { + return; + } + + SetAnimatorbufferWidth(bufferWidth); + if (memset_s(reinterpret_cast(const_cast(animatorImageInfo_.data)), animatorImageInfo_.dataSize, 0, + animatorImageInfo_.dataSize) != EOK) { + GRAPHIC_LOGE("animator buffer memset failed."); + } +} + +void ScreenDeviceProxy::SetScreenSize(uint16_t width, uint16_t height) +{ + if ((width == 0) || (height == 0)) { + GRAPHIC_LOGE("screen size can not be zero."); + return; + } + width_ = width; + height_ = height; +#if !ENABLE_WINDOW && !ENABLE_FRAME_BUFFER + if (buffer_ != nullptr) { + UIFree(buffer_); + } + uint32_t bufSize = width * height * DrawUtils::GetByteSizeByColorMode(ARGB8888); + buffer_ = static_cast(UIMalloc(bufSize)); + if (buffer_ == nullptr) { + GRAPHIC_LOGE("screen buffer malloc failed."); + return; + } + if (memset_s(buffer_, bufSize, 0, bufSize) != EOK) { + GRAPHIC_LOGE("screen buffer memset failed."); + UIFree(reinterpret_cast(buffer_)); + buffer_ = nullptr; + } +#endif +} + +uint8_t* ScreenDeviceProxy::GetBuffer() +{ + flush_.Wait(); + if (useAnimatorBuff_) { + if (animatorBufferAddr_ == nullptr) { + GRAPHIC_LOGE("Invalid param animatorBufferAddr_."); + return nullptr; + } + int32_t offset = bufferRect_.GetTop() * animatorBufferWidth_ + bufferRect_.GetLeft(); + offset *= DrawUtils::GetByteSizeByColorMode(animatorBufferMode_); + return animatorBufferAddr_ + offset; + } +#if ENABLE_FRAME_BUFFER + if (frameBufferAddr_ == nullptr) { + GRAPHIC_LOGE("Invalid param frameBufferAddr_."); + return nullptr; + } + int32_t offset = bufferRect_.GetTop() * frameBufferWidth_ + bufferRect_.GetLeft(); + offset *= DrawUtils::GetByteSizeByColorMode(frameBufferMode_); + return frameBufferAddr_ + offset; +#elif ENABLE_WINDOW + return gfxAlloc_.virAddr; +#else + return buffer_; +#endif +} + +ColorMode ScreenDeviceProxy::GetBufferMode() +{ + if (useAnimatorBuff_) { + return animatorBufferMode_; + } +#if ENABLE_FRAME_BUFFER + return frameBufferMode_; +#else + return ARGB8888; +#endif +} +} // namespace OHOS diff --git a/frameworks/common/task.cpp b/frameworks/common/task.cpp new file mode 100755 index 0000000..a38827a --- /dev/null +++ b/frameworks/common/task.cpp @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "common/task.h" +#include "common/task_manager.h" +#include "hal_tick.h" + +namespace OHOS { +void Task::TaskExecute() +{ + uint32_t elp = HALTick::GetInstance().GetElapseTime(lastRun_); + if (elp >= period_) { + lastRun_ = HALTick::GetInstance().GetTime(); + Callback(); + } +} + +void Task::Init() +{ + SetPeriod(period_); + TaskManager::GetInstance()->Add(this); +} +} diff --git a/frameworks/common/text.cpp b/frameworks/common/text.cpp new file mode 100755 index 0000000..de1bb32 --- /dev/null +++ b/frameworks/common/text.cpp @@ -0,0 +1,368 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "common/text.h" +#include "common/typed_text.h" +#include "draw/draw_label.h" +#include "font/ui_font.h" +#include "graphic_log.h" +#include "securec.h" + +namespace OHOS { +Text::TextLine Text::textLine_[MAX_LINE_COUNT] = {{0}}; + +Text::Text() + : text_(nullptr), + fontId_(0), + fontSize_(0), + textSize_({0, 0}), + needRefresh_(false), + expandWidth_(false), + expandHeight_(false), + direct_(TEXT_DIRECT_LTR), + horizontalAlign_(TEXT_ALIGNMENT_LEFT), + verticalAlign_(TEXT_ALIGNMENT_TOP) +{ + SetFont(DEFAULT_VECTOR_FONT_FILENAME, DEFAULT_VECTOR_FONT_SIZE); +} + +Text::~Text() +{ + if (text_ != nullptr) { + UIFree(text_); + text_ = nullptr; + } +} + +void Text::SetText(const char* text) +{ + if (text == nullptr) { + return; + } + uint32_t textLen = static_cast(strlen(text)); + if (textLen > MAX_TEXT_LENGTH) { + return; + } + if (text_ != nullptr) { + if (strcmp(text, text_) == 0) { + return; + } + UIFree(text_); + text_ = nullptr; + } + text_ = static_cast(UIMalloc(++textLen)); + if (text_ == nullptr) { + return; + } + if (memcpy_s(text_, textLen, text, textLen) != EOK) { + UIFree(text_); + text_ = nullptr; + return; + } + needRefresh_ = true; +} + +void Text::SetFont(const char* name, uint8_t size) +{ + if (name == nullptr) { + return; + } + if (UIFont::GetInstance()->IsVectorFont()) { + uint8_t fontId = UIFont::GetInstance()->GetFontId(name); + if ((fontId != GetTotalFontId()) && ((fontId_ != fontId) || (fontSize_ != size))) { + fontId_ = fontId; + fontSize_ = size; + needRefresh_ = true; + } + } else { + uint8_t fontId = UIFont::GetInstance()->GetFontId(name, size); + SetFontId(fontId); + } +} + +void Text::SetFont(const char* name, uint8_t size, char*& destName, uint8_t& destSize) +{ + if (name == nullptr) { + return; + } + uint32_t nameLen = static_cast(strlen(name)); + if (nameLen > MAX_TEXT_LENGTH) { + return; + } + if (destName != nullptr) { + if (strcmp(destName, name) == 0) { + destSize = size; + return; + } + UIFree(destName); + destName = nullptr; + } + if (nameLen != 0) { + /* one more to store '\0' */ + destName = static_cast(UIMalloc(++nameLen)); + if (destName == nullptr) { + return; + } + if (memcpy_s(destName, nameLen, name, nameLen) != EOK) { + UIFree(destName); + destName = nullptr; + return; + } + destSize = size; + } +} + +void Text::SetFontId(uint8_t fontId) +{ + if ((fontId >= GetTotalFontId()) || (fontId_ == fontId)) { + GRAPHIC_LOGE("Text::SetFontId invalid fontId(%d)", fontId); + return; + } + UITextLanguageFontParam* fontParam = GetTextLangFontsTable(fontId); + if (fontParam == nullptr) { + return; + } + if (UIFont::GetInstance()->IsVectorFont()) { + uint8_t fontId = UIFont::GetInstance()->GetFontId(fontParam->ttfName); + if ((fontId != GetTotalFontId()) && ((fontId_ != fontId) || (fontSize_ != fontParam->size))) { + fontId_ = fontId; + fontSize_ = fontParam->size; + needRefresh_ = true; + } + } else { + fontId_ = fontId; + fontSize_ = fontParam->size; + needRefresh_ = true; + } +} + +void Text::ReMeasureTextSize(const Rect& textRect, const Style& style) +{ + if (fontSize_ == 0) { + return; + } + UIFont::GetInstance()->SetCurrentFontId(fontId_, fontSize_); + int16_t maxWidth = (expandWidth_ ? COORD_MAX : textRect.GetWidth()); + if (maxWidth > 0) { + textSize_ = TypedText::GetTextSize(text_, style.letterSpace_, style.lineSpace_, maxWidth); + FontHeader head; + if (UIFont::GetInstance()->GetCurrentFontHeader(head) != 0) { + return; + } + textSize_.y += fontSize_ - head.ascender; + } +} + +void Text::OnDraw(const Rect& invalidatedArea, + const Rect& viewOrigRect, + const Rect& textRect, + int16_t offsetX, + const Style& style, + uint16_t ellipsisIndex, + OpacityType opaScale) +{ + if ((text_ == nullptr) || (strlen(text_) == 0) || (fontSize_ == 0)) { + return; + } + UIFont::GetInstance()->SetCurrentFontId(fontId_, fontSize_); + Rect mask = invalidatedArea; + + if (mask.Intersect(mask, textRect)) { + Draw(mask, textRect, style, offsetX, ellipsisIndex, opaScale); + } +} + +void Text::Draw(const Rect& mask, + const Rect& coords, + const Style& style, + int16_t offsetX, + uint16_t ellipsisIndex, + OpacityType opaScale) +{ + Point offset = {offsetX, 0}; + int16_t lineMaxWidth = expandWidth_ ? textSize_.x : coords.GetWidth(); + int16_t lineHeight = UIFont::GetInstance()->GetHeight() + style.lineSpace_; + uint16_t lineBegin = 0; + uint32_t maxLineBytes = 0; + uint16_t lineCount = GetLine(lineMaxWidth, style.letterSpace_, ellipsisIndex, maxLineBytes); + Point pos; + pos.y = TextPositionY(coords, (lineCount * lineHeight - style.lineSpace_)); + OpacityType opa = DrawUtils::GetMixOpacity(opaScale, style.textOpa_); + for (int i = 0; i < lineCount; i++) { + if (pos.y > mask.GetBottom()) { + return; + } + int16_t nextLine = pos.y + lineHeight; + if (nextLine >= mask.GetTop()) { + pos.x = LineStartPos(coords, textLine_[i].linePixelWidth); + LabelLineInfo labelLine{pos, + offset, + mask, + lineHeight, + textLine_[i].lineBytes, + 0, + opa, + style, + &text_[lineBegin], + textLine_[i].lineBytes, + lineBegin, + fontId_, + fontSize_, + 0, + static_cast(direct_), + nullptr}; + DrawLabel::DrawTextOneLine(labelLine); + if ((i == (lineCount - 1)) && (ellipsisIndex != TEXT_ELLIPSIS_END_INV)) { + labelLine.offset.x = 0; + labelLine.text = TEXT_ELLIPSIS; + labelLine.lineLength = TEXT_ELLIPSIS_DOT_NUM; + labelLine.length = TEXT_ELLIPSIS_DOT_NUM; + DrawLabel::DrawTextOneLine(labelLine); + } + } + lineBegin += textLine_[i].lineBytes; + pos.y = nextLine; + } +} + +int16_t Text::TextPositionY(const Rect& textRect, int16_t textHeight) +{ + int16_t yOffset = 0; + if (!expandHeight_ && (verticalAlign_ != TEXT_ALIGNMENT_TOP) && (textRect.GetHeight() > textHeight)) { + if (verticalAlign_ == TEXT_ALIGNMENT_CENTER) { + yOffset = (textRect.GetHeight() - textHeight) >> 1; + } else if (verticalAlign_ == TEXT_ALIGNMENT_BOTTOM) { + yOffset = textRect.GetHeight() - textHeight; + } + } + return textRect.GetY() + yOffset; +} + +int16_t Text::LineStartPos(const Rect& textRect, uint16_t lineWidth) +{ + int16_t xOffset = 0; + int16_t rectWidth = textRect.GetWidth(); + if (horizontalAlign_ == TEXT_ALIGNMENT_CENTER) { + xOffset = (direct_ == TEXT_DIRECT_RTL) ? ((rectWidth + lineWidth + 1) >> 1) : ((rectWidth - lineWidth) >> 1); + } else if (horizontalAlign_ == TEXT_ALIGNMENT_RIGHT) { + xOffset = (direct_ == TEXT_DIRECT_RTL) ? rectWidth : (rectWidth - lineWidth); + } else { + xOffset = (direct_ == TEXT_DIRECT_RTL) ? lineWidth : 0; + } + return textRect.GetX() + xOffset; +} + +uint16_t Text::GetLine(int16_t width, uint8_t letterSpace, uint16_t ellipsisIndex, uint32_t& maxLineBytes) +{ + if (text_ == nullptr) { + return 0; + } + uint16_t lineNum = 0; + uint32_t textLen = GetTextStrLen(); + if ((ellipsisIndex != TEXT_ELLIPSIS_END_INV) && (ellipsisIndex < textLen)) { + textLen = ellipsisIndex; + } + uint32_t begin = 0; + while ((begin < textLen) && (text_[begin] != '\0') && (lineNum < MAX_LINE_COUNT)) { + begin += GetTextLine(begin, textLen, width, lineNum, letterSpace); + if (maxLineBytes < textLine_[lineNum].lineBytes) { + maxLineBytes = textLine_[lineNum].lineBytes; + } + lineNum++; + } + if ((lineNum != 0) && (ellipsisIndex != TEXT_ELLIPSIS_END_INV)) { + uint16_t ellipsisWidth = UIFont::GetInstance()->GetWidth('.', 0) + letterSpace; + textLine_[lineNum - 1].linePixelWidth += ellipsisWidth * TEXT_ELLIPSIS_DOT_NUM; + } + return lineNum; +} + +uint32_t Text::GetTextStrLen() +{ + return strlen(text_); +} + +uint32_t Text::GetTextLine(uint32_t begin, uint32_t textLen, int16_t width, uint16_t lineNum, uint8_t letterSpace) +{ + uint16_t nextLineBytes = TypedText::GetNextLine(&text_[begin], letterSpace, width); + if (nextLineBytes + begin > textLen) { + nextLineBytes = textLen - begin; + } + textLine_[lineNum].lineBytes = nextLineBytes; + textLine_[lineNum].linePixelWidth = + static_cast(TypedText::GetTextWidth(&text_[begin], nextLineBytes, letterSpace)); + return nextLineBytes; +} + +uint16_t Text::GetEllipsisIndex(const Rect& textRect, const Style& style) +{ + if ((textSize_.y <= textRect.GetHeight()) || (TypedText::GetUTF8CharacterSize(text_) <= TEXT_ELLIPSIS_DOT_NUM)) { + return TEXT_ELLIPSIS_END_INV; + } + UIFont* fontEngine = UIFont::GetInstance(); + fontEngine->SetCurrentFontId(fontId_, fontSize_); + int16_t letterWidth = fontEngine->GetWidth('.', 0) + style.letterSpace_; + Point p; + p.x = textRect.GetWidth() - letterWidth * TEXT_ELLIPSIS_DOT_NUM; + p.y = textRect.GetHeight(); + int16_t height = fontEngine->GetHeight() + style.lineSpace_; + if (height) { + p.y -= p.y % height; + } + + p.y -= style.lineSpace_; + return GetLetterIndexByPosition(textRect, style, p); +} + +uint16_t Text::GetLetterIndexByPosition(const Rect& textRect, const Style& style, const Point& pos) +{ + if (text_ == nullptr) { + return 0; + } + uint32_t lineStart = 0; + uint32_t nextLineStart = 0; + + uint16_t lineHeight = UIFont::GetInstance()->GetHeight(); + int16_t y = 0; + uint32_t textLen = static_cast(strlen(text_)); + while ((lineStart < textLen) && (text_[lineStart] != '\0')) { + nextLineStart += TypedText::GetNextLine(&text_[lineStart], style.letterSpace_, textRect.GetWidth()); + if (pos.y <= y + lineHeight) { + break; + } + y += lineHeight + style.lineSpace_; + lineStart = nextLineStart; + } + /* Calculate the x coordinate */ + int16_t x = 0; + uint32_t i = lineStart; + uint32_t pre = i; + while (i < nextLineStart - 1) { + uint32_t letter = TypedText::GetUTF8Next(text_, i, i); + x += UIFont::GetInstance()->GetWidth(letter, 0); + if (pos.x < x) { + i = pre; + break; + } + x += style.letterSpace_; + pre = i; + } + if (i >= (GetTextStrLen() - TEXT_ELLIPSIS_DOT_NUM)) { + return TEXT_ELLIPSIS_END_INV; + } else { + return static_cast(i); + } +} +} // namespace OHOS \ No newline at end of file diff --git a/frameworks/common/typed_text.cpp b/frameworks/common/typed_text.cpp new file mode 100755 index 0000000..63e3780 --- /dev/null +++ b/frameworks/common/typed_text.cpp @@ -0,0 +1,384 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "common/typed_text.h" +#include "font/ui_font.h" +#include "font/ui_font_adaptor.h" +#include "graphic_log.h" +#include "mem_api.h" +#include "transform.h" + +namespace OHOS { +#ifndef _FONT_TOOL +Point TypedText::GetTextSize(const char* text, int16_t letterSpace, int16_t lineSpace, int16_t maxWidth) +{ + Point size{0, 0}; + + if (text == nullptr) { + GRAPHIC_LOGE("TypedText::GetTextSize invalid parameter"); + return size; + } + + uint32_t lineBegin = 0; + uint32_t newLineBegin = 0; + uint16_t letterHeight = UIFont::GetInstance()->GetHeight(); + + while (text[lineBegin] != '\0') { + int16_t lineWidth = maxWidth; + newLineBegin += UIFontAdaptor::GetNextLineAndWidth(&text[lineBegin], letterSpace, lineWidth); + if (newLineBegin == lineBegin) { + break; + } + size.y += letterHeight + lineSpace; + size.x = MATH_MAX(lineWidth, size.x); + lineBegin = newLineBegin; + } + + if ((lineBegin != 0) && ((text[lineBegin - 1] == '\n') || (text[lineBegin - 1] == '\r'))) { + size.y += letterHeight + lineSpace; + } + + if (size.y == 0) { + size.y = letterHeight; + } else { + size.y -= lineSpace; + } + return size; +} + +Rect TypedText::GetArcTextRect(const char* text, + const Point& arcCenter, + int16_t letterSpace, + UIArcLabel::TextOrientation orientation, + const UIArcLabel::ArcTextInfo& arcTextInfo) +{ + if ((text == nullptr) || (arcTextInfo.lineStart == arcTextInfo.lineEnd) || (arcTextInfo.radius == 0)) { + GRAPHIC_LOGE("TypedText::GetArcTextRect invalid parameter\n"); + return Rect(); + } + + uint16_t letterHeight = UIFont::GetInstance()->GetHeight(); + bool xorFlag = (orientation == UIArcLabel::TextOrientation::INSIDE) ^ (arcTextInfo.direct == TEXT_DIRECT_LTR); + float posX = 0; + float posY = 0; + uint32_t i = arcTextInfo.lineStart; + float angle = arcTextInfo.startAngle; + Rect rect; + Rect rectLetter; + TransformMap transform; + while (i < arcTextInfo.lineEnd) { + uint32_t tmp = i; + uint32_t letter = GetUTF8Next(text, tmp, i); + if (letter == 0) { + continue; + } + if ((letter == '\r') || (letter == '\n')) { + break; + } + uint16_t letterWidth = UIFont::GetInstance()->GetWidth(letter, 0); + if (tmp == arcTextInfo.lineStart) { + angle += xorFlag ? GetAngleForArcLen(static_cast(letterWidth), letterHeight, arcTextInfo.radius, + arcTextInfo.direct, orientation) + : 0; + GetArcLetterPos(arcCenter, arcTextInfo.radius, angle, posX, posY); + rect.SetPosition(MATH_ROUND(posX), MATH_ROUND(posY)); + } + rectLetter.SetPosition(MATH_ROUND(posX), MATH_ROUND(posY)); + rectLetter.Resize(letterWidth, letterHeight); + transform.SetTransMapRect(rectLetter); + + uint16_t arcLen = letterWidth + letterSpace; + if (arcLen == 0) { + continue; + } + float incrementAngle = GetAngleForArcLen(static_cast(arcLen), letterHeight, arcTextInfo.radius, + arcTextInfo.direct, orientation); + float rotateAngle = + (orientation == UIArcLabel::TextOrientation::INSIDE) ? angle : (angle - SEMICIRCLE_IN_DEGREE); + // 2: letterWidth's half + float fineTuningAngle = incrementAngle * (static_cast(letterWidth) / (2 * arcLen)); + rotateAngle += (xorFlag ? -fineTuningAngle : fineTuningAngle); + + transform.Rotate(MATH_ROUND(rotateAngle), Vector2(0, 0)); + rect.Join(rect, transform.GetBoxRect()); + + angle += incrementAngle; + GetArcLetterPos(arcCenter, arcTextInfo.radius, angle, posX, posY); + } + return rect; +} + +float TypedText::GetAngleForArcLen(float len, + uint16_t height, + uint16_t radius, + UITextLanguageDirect direct, + UIArcLabel::TextOrientation orientation) +{ + float realRadius = + static_cast((orientation == UIArcLabel::TextOrientation::OUTSIDE) ? (radius + height) : radius); + float angle = static_cast(len * SEMICIRCLE_IN_DEGREE) / (UI_PI * realRadius); + return (direct == TEXT_DIRECT_LTR) ? angle : -angle; +} + +void TypedText::GetArcLetterPos(const Point& arcCenter, uint16_t radius, float angle, float& posX, float& posY) +{ + posX = arcCenter.x + (static_cast(radius) * Sin(MATH_ROUND(angle))); + posY = arcCenter.y - (static_cast(radius) * Sin(MATH_ROUND(angle + QUARTER_IN_DEGREE))); +} + +uint32_t TypedText::GetNextLine(const char* text, int16_t letterSpace, int16_t maxWidth) +{ + uint32_t lastBreakPos = 0; + int16_t curW; + uint32_t index = 0; + uint32_t tmp = 0; + if (GetWrapPoint(text, index)) { + return index; + } + while (true) { + curW = TypedText::GetTextWidth(text, index, letterSpace); + if (curW > maxWidth) { + index = lastBreakPos; + if (lastBreakPos == 0) { + curW = 0; + uint32_t i = 0; + uint32_t letter; + uint16_t letterWidth; + while (text[i] != '\0') { + tmp = i; + letter = TypedText::GetUTF8Next(text, tmp, i); + letterWidth = UIFont::GetInstance()->GetWidth(letter, 0); + curW += letterWidth; + if (letterWidth > 0) { + curW += letterSpace; + } + if (curW > maxWidth) { + index = lastBreakPos; + return index; + } + lastBreakPos = i; + } + } + break; + } + if ((index > 0) && (index < strlen(text)) && ((text[index - 1] == '\r') || (text[index - 1] == '\n'))) { + break; + } + lastBreakPos = index; + if (text[index] == '\0') { + break; + } + if (GetWrapPoint(text + index, tmp)) { + return index + tmp; + } + index += tmp; + if (lastBreakPos == index) { + break; + } + } + return index; +} + +bool TypedText::GetWrapPoint(const char* text, uint32_t& breakPoint) +{ + breakPoint = 0; + uint32_t j = 0; + uint32_t letter = 0; + if (text == nullptr) { + return true; + } + + while (text[breakPoint] != '\0') { + letter = GetUTF8Next(text, breakPoint, j); + breakPoint = j; + if ((letter == ' ') || (letter == '.') || (letter == ',') || (letter == '!') || (letter == '=') + || (letter == '?')) { + return false; + } + if (letter == '\n') { + return true; + } + if ((letter == '\r') && (GetUTF8Next(text, breakPoint, j) == '\n')) { + breakPoint = j; + return true; + } + } + return false; +} + +int16_t TypedText::GetTextWidth(const char* text, uint16_t length, int16_t letterSpace) +{ + if ((text == nullptr) || (length == 0) || (length > strlen(text))) { + GRAPHIC_LOGE("TypedText::GetTextWidth invalid parameter\n"); + return 0; + } + + uint32_t i = 0; + uint16_t width = 0; + uint32_t letter; + + while (i < length) { + letter = GetUTF8Next(text, i, i); + if ((letter == 0) || (letter == '\n') || (letter == '\r')) { + continue; + } + uint16_t charWidth = UIFont::GetInstance()->GetWidth(letter, 0); + width += charWidth + letterSpace; + } + if (width > 0) { + width -= letterSpace; + } + return width; +} +#endif // _FONT_TOOL + +uint8_t TypedText::GetUTF8OneCharacterSize(const char* str) +{ + if ((str[0] & 0x80) == 0) { + return 1; + } else if ((str[0] & 0xE0) == 0xC0) { + return 2; // 2: 2 bytes + } else if ((str[0] & 0xF0) == 0xE0) { + return 3; // 3: 3 bytes + } else if ((str[0] & 0xF8) == 0xF0) { + return 4; // 4: 4 bytes + } + return 0; +} + +uint32_t TypedText::GetUTF8Next(const char* text, uint32_t i, uint32_t& j) +{ + uint32_t unicode = 0; + if (text == nullptr) { + GRAPHIC_LOGE("text invalid parameter"); + return 0; + } + + j = i; + uint8_t lettetSize = GetUTF8OneCharacterSize(text + i); + switch (lettetSize) { + case 1: + unicode = text[j]; + break; + case 2: // 2: letter size + unicode = static_cast(text[j] & 0x1F) << UTF8_TO_UNICODE_SHIFT1; + j++; + if ((text[j] & 0xC0) != 0x80) { + return 0; + } + unicode += (text[j] & 0x3F); + break; + case 3: // 3: letter size + unicode = static_cast(text[j] & 0x0F) << UTF8_TO_UNICODE_SHIFT2; + unicode += static_cast(text[++j] & 0x3F) << UTF8_TO_UNICODE_SHIFT1; + unicode += (text[++j] & 0x3F); + break; + case 4: // 4: letter size + unicode = static_cast(text[j] & 0x07) << UTF8_TO_UNICODE_SHIFT3; + unicode += static_cast(text[++j] & 0x3F) << UTF8_TO_UNICODE_SHIFT2; + unicode += static_cast(text[++j] & 0x3F) << UTF8_TO_UNICODE_SHIFT1; + unicode += text[++j] & 0x3F; + break; + default: + break; + } + j++; + return unicode; +} + +uint32_t TypedText::GetByteIndexFromUTF8Id(const char* text, uint32_t utf8Id) +{ + if (text == nullptr) { + GRAPHIC_LOGE("TypedText::GetByteIndexFromUTF8Id text invalid parameter\n"); + return 0; + } + uint32_t byteIndex = 0; + for (uint32_t i = 0; i < utf8Id; i++) { + byteIndex += GetUTF8OneCharacterSize(&text[byteIndex]); + } + + return byteIndex; +} + +uint32_t TypedText::GetUTF8CharacterSize(const char* text, uint32_t byteIndex) +{ + uint32_t i = 0; + uint32_t size = 0; + + if (text == nullptr) { + GRAPHIC_LOGE("TypedText::GetUTF8CharacterSize text invalid parameter\n"); + return 0; + } + while ((text[i] != '\0') && (i < byteIndex)) { + GetUTF8Next(text, i, i); + size++; + } + + return size; +} + +void TypedText::Utf8ToUtf16(const char* utf8Str, uint16_t* utf16Str, uint32_t len) +{ + if ((utf8Str == nullptr) || (utf16Str == nullptr)) { + GRAPHIC_LOGE("utf8Str or u16Str is null"); + return; + } + + uint32_t i = 0; + uint32_t cnt = 0; + while (utf8Str[i] != '\0') { + uint32_t unicode = GetUTF8Next(utf8Str, i, i); + if (cnt < len) { + if (unicode <= MAX_UINT16_LOW_SCOPE) { + utf16Str[cnt] = (unicode & MAX_UINT16_LOW_SCOPE); + } else if (unicode <= MAX_UINT16_HIGH_SCOPE) { + if (cnt + 1 < len) { + utf16Str[cnt] = static_cast(UTF16_LOW_PARAM + (unicode & UTF16_LOW_MASK)); // low + cnt++; + utf16Str[cnt] = static_cast(UTF16_HIGH_PARAM1 + (unicode >> UTF16_HIGH_SHIFT) - + UTF16_HIGH_PARAM2); // high + } + } else { + GRAPHIC_LOGE("Invalid unicode"); + return; + } + cnt++; + } + } +} + +uint32_t TypedText::GetUtf16Cnt(const char* utf8Str) +{ + if (utf8Str == nullptr) { + GRAPHIC_LOGE("text invalid parameter"); + return 0; + } + uint32_t len = 0; + uint32_t i = 0; + + while (utf8Str[i] != '\0') { + uint32_t unicode = GetUTF8Next(utf8Str, i, i); + if (unicode <= MAX_UINT16_LOW_SCOPE) { + len++; + } else if (unicode <= MAX_UINT16_HIGH_SCOPE) { + len += 2; // 2: low and high, two uint16_t numbers + } else { + GRAPHIC_LOGE("Invalid unicode"); + return 0; + } + } + return len; +} +} // namespace OHOS \ No newline at end of file diff --git a/frameworks/common/typed_text.h b/frameworks/common/typed_text.h new file mode 100755 index 0000000..6dbba99 --- /dev/null +++ b/frameworks/common/typed_text.h @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GRAPHIC_LITE_TYPE_TEXT_H +#define GRAPHIC_LITE_TYPE_TEXT_H +#include "common/text.h" +#include "graphic_config.h" +#ifndef _FONT_TOOL +#include "components/ui_arc_label.h" +#include "font/ui_font_header.h" +#include "geometry2d.h" +#endif + +namespace OHOS { +#ifndef _FONT_TOOL +class TypedText : public HeapBase { +public: + static constexpr uint32_t MAX_UINT16_LOW_SCOPE = 0xFFFF; + static constexpr uint32_t MAX_UINT16_HIGH_SCOPE = 0xEFFFF; + static constexpr uint32_t UTF16_LOW_MASK = 0x03FF; + static constexpr uint32_t UTF16_LOW_PARAM = 56320; + static constexpr uint32_t UTF16_HIGH_PARAM1 = 55296; + static constexpr uint8_t UTF16_HIGH_PARAM2 = 64; + static constexpr uint8_t UTF16_HIGH_SHIFT = 10; + static constexpr uint8_t UTF8_TO_UNICODE_SHIFT1 = 6; + static constexpr uint8_t UTF8_TO_UNICODE_SHIFT2 = 12; + static constexpr uint8_t UTF8_TO_UNICODE_SHIFT3 = 18; + + static Point GetTextSize(const char* text, + int16_t letterSpace, + int16_t lineSpace, + int16_t maxWidth); + + static uint32_t GetNextLine(const char* text, + int16_t letterSpace, + int16_t maxWidth); + + static int16_t GetTextWidth(const char* text, + uint16_t length, + int16_t letterSpace); + + static Rect GetArcTextRect(const char* text, + const Point& arcCenter, + int16_t letterSpace, + UIArcLabel::TextOrientation orientation, + const UIArcLabel::ArcTextInfo& arcTextInfo); + + static float GetAngleForArcLen(float len, + uint16_t height, + uint16_t radius, + UITextLanguageDirect direct, + UIArcLabel::TextOrientation orientation); + + static void GetArcLetterPos(const Point& arcCenter, uint16_t radius, float angle, float& posX, float& posY); + +#else // _FONT_TOOL +class TypedText { +public: +#endif // _FONT_TOOL + static uint32_t GetUTF8Next(const char* text, uint32_t i, uint32_t& j); + static uint8_t GetUTF8OneCharacterSize(const char* str); + static uint32_t GetByteIndexFromUTF8Id(const char* text, uint32_t utf8Id); + static uint32_t GetUTF8CharacterSize(const char* text, uint32_t byteIndex = UINT32_MAX); + static void Utf8ToUtf16(const char* utf8Str, uint16_t* utf16Str, uint32_t len); + static uint32_t GetUtf16Cnt(const char* utf8Str); + +private: + static bool GetWrapPoint(const char* text, uint32_t& breakPoint); +}; +} // namespace OHOS +#endif // GRAPHIC_LITE_TYPE_TEXT_H diff --git a/frameworks/common/ui_font_header.cpp b/frameworks/common/ui_font_header.cpp new file mode 100755 index 0000000..df03018 --- /dev/null +++ b/frameworks/common/ui_font_header.cpp @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at. + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "font/ui_font.h" +#include "font/ui_font_header.h" + +namespace OHOS { +UITextLanguageFontParam* GetTextLangFontsTable(uint8_t langFontId) +{ + // Need to be implemented + return nullptr; +} + +uint8_t GetTotalLangId() +{ + // Need to be implemented + return 0; +} + +uint8_t GetBitmapFontIdMax() +{ + // Need to be implemented + return 0; +} + +uint8_t GetTotalFontId() +{ + // Need to be implemented + return 0xFF; +} + +uint16_t GetTotalTextId() +{ + // Need to be implemented + return 0; +} + +LangTextParam* GetLangTextDefaultParamTable() +{ + // Need to be implemented + return nullptr; +} +} // namespace OHOS \ No newline at end of file diff --git a/frameworks/components/root_view.cpp b/frameworks/components/root_view.cpp new file mode 100755 index 0000000..90199a4 --- /dev/null +++ b/frameworks/components/root_view.cpp @@ -0,0 +1,633 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "components/root_view.h" + +#include "common/screen.h" +#include "core/render_manager.h" +#include "dock/screen_device_proxy.h" +#include "graphic_log.h" +#if ENABLE_WINDOW +#include "window/window_impl.h" +#endif +namespace OHOS { +namespace { +#if LOCAL_RENDER +const constexpr uint8_t MAX_SPLIT_NUM = 32; // split at most 32 parts +// view along with its parents and siblings are at most 128 +const constexpr uint8_t VIEW_STACK_DEPTH = COMPONENT_NESTING_DEPTH * 2; +#else +const constexpr uint8_t VIEW_STACK_DEPTH = COMPONENT_NESTING_DEPTH; +#endif +static Rect g_maskStack[COMPONENT_NESTING_DEPTH]; +static UIView* g_viewStack[VIEW_STACK_DEPTH]; +} // namespace +RootView::RootView() +{ +#if defined __linux__ || defined __LITEOS__ || defined __APPLE__ + pthread_mutex_init(&lock_, nullptr); +#endif +} + +#if ENABLE_WINDOW +Window* RootView::GetBoundWindow() const +{ + return boundWindow_; +} +#endif + +inline bool RootView::IntersectScreenRect(Rect& rect) +{ +#if ENABLE_WINDOW + Rect screenRect = GetRect(); +#else + Rect screenRect(0, 0, Screen::GetInstance().GetWidth() - 1, Screen::GetInstance().GetHeight() - 1); +#endif + return rect.Intersect(rect, screenRect); +} + +#if LOCAL_RENDER +static void DivideInvalidateRect(const Rect& originRect, Rect& leftoverRect, Vector& splitRects) +{ + Rect mask; + if (!mask.Intersect(originRect, leftoverRect)) { + splitRects.PushBack(leftoverRect); + return; + } + + /* + * +---+---+---+ + * | | A | | originRect :A+B + * | +---+ | leftoverRect :A->0 + * | B | mask :A + * +-----------+ + */ + if (originRect.IsContains(leftoverRect)) { + return; + } + + int16_t reserveCnt = MAX_SPLIT_NUM - splitRects.Size(); + if (reserveCnt <= 0) { + splitRects.PushBack(leftoverRect); + return; + } + + if (mask.GetWidth() == leftoverRect.GetWidth()) { + /* + * +---+ + * | A | originRect :B+C + * +-----------+ leftoverRect :A+B->A + * | | B | | mask :B + * | +---+ | + * | C | + * +-----------+ + */ + if (mask.GetBottom() == leftoverRect.GetBottom()) { + leftoverRect.SetBottom(mask.GetTop() - 1); + } else if (mask.GetTop() == leftoverRect.GetTop()) { + leftoverRect.SetTop(mask.GetBottom() + 1); + } else { + splitRects.PushBack(leftoverRect); + splitRects.Back().SetBottom(mask.GetTop() - 1); + leftoverRect.SetTop(mask.GetBottom() + 1); + } + splitRects.PushBack(leftoverRect); + return; + } + if (mask.GetHeight() == leftoverRect.GetHeight()) { + /* + * +---------+ originRect :B+C + * +-------+ | leftoverRect :A+B->A + * | A | B | C | mask :B + * +-------+ | + * +---------+ + */ + if (mask.GetLeft() == leftoverRect.GetLeft()) { + leftoverRect.SetLeft(mask.GetRight() + 1); + } else if (mask.GetRight() == leftoverRect.GetRight()) { + leftoverRect.SetRight(mask.GetLeft() - 1); + } else { + splitRects.PushBack(leftoverRect); + splitRects.Back().SetRight(mask.GetLeft() - 1); + leftoverRect.SetLeft(mask.GetRight() + 1); + } + splitRects.PushBack(leftoverRect); + return; + } + + Vector copyRect(splitRects); + if (mask.GetLeft() != leftoverRect.GetLeft()) { + /* + * | + * +-------+ + * | +---+ mask :A + * | B | A | leftoverRect :A+B + * | +---+ + * +-------+ + * | + */ + if (reserveCnt-- <= 0) { + splitRects.Swap(copyRect); + splitRects.PushBack(leftoverRect); + return; + } + splitRects.PushBack(leftoverRect); + splitRects.Back().SetRight(mask.GetLeft() - 1); + leftoverRect.SetLeft(mask.GetLeft()); + } + + if (mask.GetTop() != leftoverRect.GetTop()) { + /* + * +-------+ + * | B | mask :A + * ---+---+--- leftoverRect :A+B + * | | A | | + * +-+---+-+ + */ + if (reserveCnt-- <= 0) { + splitRects.Swap(copyRect); + splitRects.PushBack(leftoverRect); + return; + } + splitRects.PushBack(leftoverRect); + splitRects.Back().SetBottom(mask.GetTop() - 1); + leftoverRect.SetTop(mask.GetTop()); + } + + if (mask.GetRight() != leftoverRect.GetRight()) { + /* + * | + * +-------+ + * +---+ | mask :A + * | A | B | leftoverRect :A+B + * +---+ | + * +-------+ + * | + */ + if (reserveCnt-- <= 0) { + splitRects.Swap(copyRect); + splitRects.PushBack(leftoverRect); + return; + } + splitRects.PushBack(leftoverRect); + splitRects.Back().SetLeft(mask.GetRight() + 1); + leftoverRect.SetRight(mask.GetRight()); + } + + if (mask.GetBottom() != leftoverRect.GetBottom()) { + /* + * +-+---+-+ + * | | A | | mask :A + * ---+---+--- leftoverRect :A+B + * | B | + * +-------+ + */ + if (reserveCnt-- <= 0) { + splitRects.Swap(copyRect); + splitRects.PushBack(leftoverRect); + return; + } + splitRects.PushBack(leftoverRect); + splitRects.Back().SetTop(mask.GetBottom() + 1); + leftoverRect.SetBottom(mask.GetBottom()); + } + return; +} + +static void AddRenderedRects(Rect& rect, List& renderedRects, ListNode* iter) +{ + /* Elements at front have larger area and more relevance */ + for (; iter != renderedRects.End(); iter = iter->next_) { + Rect& curRect = iter->data_; + if (!curRect.IsIntersect(rect)) { + continue; + } + + if (curRect.IsContains(rect)) { + return; + } + + /* Merge two rects */ + if (rect.IsContains(curRect)) { + } else if (((curRect.GetLeft() == rect.GetLeft()) && (curRect.GetRight() == rect.GetRight())) || + ((curRect.GetTop() == rect.GetTop()) && (curRect.GetBottom() == rect.GetBottom()))) { + rect.Join(curRect, rect); + } else { + continue; + } + iter = renderedRects.Remove(iter)->prev_; + break; + } + if (iter == renderedRects.End()) { // No merge rises + if (renderedRects.Size() == 128) { // record 128 rendered rects at most + renderedRects.PopBack(); + } + renderedRects.PushFront(rect); + } else { // merge rises, go over the rest nodes + AddRenderedRects(rect, renderedRects, iter); + } +} + +void RootView::RemoveViewFromInvalidMap(UIView* view) +{ +#if defined __linux__ || defined __LITEOS__ || defined __APPLE__ + pthread_mutex_lock(&lock_); +#endif + + int16_t stackCount = 0; + do { + while (view != nullptr) { + /* delete node itself */ + auto entry = invalidateMap_.find(view); + if (entry != invalidateMap_.end()) { + invalidateMap_.erase(entry); + } + /* delete node's children */ + if (view->IsViewGroup() && stackCount < COMPONENT_NESTING_DEPTH) { + g_viewStack[stackCount++] = view; + view = static_cast(view)->GetChildrenHead(); + continue; + } + /* only go to child's sibling */ + view = view->GetNextSibling(); + } + if (--stackCount >= 0) { + view = g_viewStack[stackCount]->GetNextSibling(); + } + } while (stackCount >= 0); + +#if defined __linux__ || defined __LITEOS__ || defined __APPLE__ + pthread_mutex_unlock(&lock_); +#endif +} + +void RootView::OptimizeInvalidView(UIView* curview, UIView* background, List& renderedRects) +{ + if (curview == nullptr) { + return; + } + auto mapEntry = invalidateMap_.find(curview); + if (mapEntry == invalidateMap_.end()) { + return; + } + + Rect& invalidRect = mapEntry->second.Front(); + /* Transparent views should draw from background */ + if (((curview->GetStyleConst().bgOpa_ != OPA_OPAQUE) || (curview->GetOpaScale() != OPA_OPAQUE) || + (!curview->IsTransInvalid())) && + (curview != this)) { + AddInvalidateRect(invalidRect, background); + invalidateMap_.erase(mapEntry); + return; + } + + /* Remove the rendered parts and split the origin rect into splitInvalidRects + * For performance reason, split numbers are strictly restrained. + */ + Vector splitInvalidRects(MAX_SPLIT_NUM << 1); + Rect invalidRectCopy(invalidRect); + /* Using forward order because entries at the front are closer to the current view and have larger Size */ + for (auto iter = renderedRects.Begin(); iter != renderedRects.End(); iter = iter->next_) { + for (int8_t i = 0; i < mapEntry->second.Size(); i++) { + DivideInvalidateRect(iter->data_, mapEntry->second[i], splitInvalidRects); + } + mapEntry->second.Swap(splitInvalidRects); + splitInvalidRects.Clear(); + } + + /* Add new opaque rects */ + Rect preDrawRect(invalidRectCopy); + if (!curview->OnPreDraw(preDrawRect)) { + AddInvalidateRect(invalidRectCopy, background); + } + AddRenderedRects(preDrawRect, renderedRects, renderedRects.Begin()); +} + +void RootView::OptimizeInvalidMap() +{ + UIView* curview = this; + int16_t stackCount = 0; + int16_t opaStackCount = 0; + UIView* background[VIEW_STACK_DEPTH]; + bool flags[VIEW_STACK_DEPTH]; // indicate whether stack go back from child + List renderedRects; // Record rendered areas to avoid rerendering + + do { + /* push stack */ + if (curview != nullptr) { + if (stackCount >= VIEW_STACK_DEPTH) { + return; + } + g_viewStack[stackCount] = curview; + flags[stackCount++] = false; + curview = curview->GetNextSibling(); + continue; + } + + curview = g_viewStack[--stackCount]; + Rect rect(curview->GetRect()); + if (!curview->IsVisible() || !IntersectScreenRect(rect)) { + curview = nullptr; + continue; + } + if (!flags[stackCount]) { // Back from sibling + if (curview->IsViewGroup()) { + /* Set background/topview */ + if (((curview->GetStyleConst().bgOpa_ == OPA_OPAQUE) && (curview->GetOpaScale() == OPA_OPAQUE) && + curview->IsTransInvalid()) || + (curview == this)) { + background[opaStackCount] = curview; + } else { + background[opaStackCount] = background[opaStackCount - 1]; + } + ++opaStackCount; + if (opaStackCount >= VIEW_STACK_DEPTH) { + return; + } + flags[stackCount++] = true; + curview = static_cast(curview)->GetChildrenHead(); + continue; + } + } else { // Back from child + opaStackCount--; + } + OptimizeInvalidView(curview, background[opaStackCount - 1], renderedRects); + curview = nullptr; + } while (stackCount > 0); +} + +void RootView::DrawInvalidMap(const Rect& buffRect) +{ + OptimizeInvalidMap(); + Rect rect; + for (auto& viewEntry : invalidateMap_) { + Vector& viewRenderRect = viewEntry.second; + for (uint16_t i = 0; i < viewRenderRect.Size(); i++) { + rect.Intersect(viewRenderRect[i], buffRect); + DrawTop(viewEntry.first, rect); + } + } +} +#endif + +void RootView::AddInvalidateRect(Rect& rect, UIView* view) +{ + Rect commonRect(rect); + if (IntersectScreenRect(commonRect)) { +#if LOCAL_RENDER + Vector& invalidRects = invalidateMap_[view]; + if (invalidRects.IsEmpty()) { + invalidRects.PushBack(commonRect); + } else { + invalidRects[0].Join(invalidRects[0], commonRect); + } +#else + invalidRect_.Join(invalidRect_, commonRect); + renderFlag_ = true; +#endif + } +} + +void RootView::AddInvalidateRectWithLock(Rect& rect, UIView* view) +{ +#if defined __linux__ || defined __LITEOS__ || defined __APPLE__ + pthread_mutex_lock(&lock_); +#endif + + AddInvalidateRect(rect, view); + +#if defined __linux__ || defined __LITEOS__ || defined __APPLE__ + pthread_mutex_unlock(&lock_); +#endif +} + +void RootView::Measure() +{ +#if LOCAL_RENDER + if (!invalidateMap_.empty()) { + MeasureView(childrenHead_); + } +#else + if (renderFlag_) { + MeasureView(childrenHead_); + } +#endif +} + +void RootView::MeasureView(UIView* view) +{ + int16_t stackCount = 0; + UIView* curView = view; + while (stackCount >= 0) { + while (curView != nullptr) { + if (curView->IsVisible()) { + curView->ReMeasure(); + if (curView->IsViewGroup() && stackCount < COMPONENT_NESTING_DEPTH) { + g_viewStack[stackCount++] = curView; + curView = static_cast(curView)->GetChildrenHead(); + continue; + } + } + curView = curView->GetNextSibling(); + } + if (--stackCount >= 0) { + curView = (g_viewStack[stackCount])->GetNextSibling(); + } + } +} + +void RootView::Render() +{ +#if defined __linux__ || defined __LITEOS__ || defined __APPLE__ + pthread_mutex_lock(&lock_); +#endif + +#if LOCAL_RENDER + if (!invalidateMap_.empty()) { + RenderManager::RenderRect(GetRect(), this); + invalidateMap_.clear(); +#else + if (renderFlag_) { + RenderManager::RenderRect(invalidRect_, this); + invalidRect_ = {0, 0, 0, 0}; + renderFlag_ = false; +#endif + +#if ENABLE_WINDOW + if (boundWindow_) { + boundWindow_->Flush(); + boundWindow_->Update(); + } +#endif + ScreenDeviceProxy::GetInstance()->OnRenderFinish(); + } + +#if defined __linux__ || defined __LITEOS__ || defined __APPLE__ + pthread_mutex_unlock(&lock_); +#endif +} + +void RootView::DrawTop(UIView* view, const Rect& rect) +{ + if (view == nullptr) { + return; + } + + int16_t stackCount = 0; + UIView* par = view->GetParent(); + if (par == nullptr) { + par = view; + } + UIView* curView = view; + UIView* transViewGroup = nullptr; + Rect curViewRect; + Rect mask = rect; + Rect OrigRect; + Rect RelativeRect; + while (par != nullptr) { + if (curView != nullptr) { + if (curView->IsVisible()) { + curViewRect = curView->GetMaskedRect(); + if (curViewRect.Intersect(curViewRect, mask) || enableAnimator_) { + if ((curView->GetViewType() != UI_IMAGE_VIEW) && (curView->GetViewType() != UI_TEXTURE_MAPPER) && + !curView->IsTransInvalid() && !enableAnimator_) { + OrigRect = curView->GetOrigRect(); + RelativeRect = curView->GetRelativeRect(); + curView->GetTransformMap().SetInvalid(true); + curView->SetPosition(RelativeRect.GetX() - OrigRect.GetX(), + RelativeRect.GetY() - OrigRect.GetY()); + ScreenDeviceProxy::GetInstance()->EnableAnimatorBuffer(true); + ScreenDeviceProxy::GetInstance()->SetAnimatorRect(OrigRect); + ScreenDeviceProxy::GetInstance()->SetAnimatorTransMap(curView->GetTransformMap()); + enableAnimator_ = true; + } + if (enableAnimator_) { + Rect invalidatedArea; + invalidatedArea.SetWidth(ScreenDeviceProxy::GetInstance()->GetScreenWidth()); + invalidatedArea.SetHeight(ScreenDeviceProxy::GetInstance()->GetScreenHeight()); + curView->OnDraw(invalidatedArea); + } else { + curView->OnDraw(curViewRect); + } + + if ((curView->IsViewGroup()) && (stackCount < COMPONENT_NESTING_DEPTH)) { + if (enableAnimator_ && (transViewGroup == nullptr)) { + transViewGroup = curView; + } + par = curView; + g_viewStack[stackCount] = curView; + g_maskStack[stackCount] = mask; + stackCount++; + curView = static_cast(curView)->GetChildrenHead(); + mask = par->GetContentRect(); + mask.Intersect(mask, curViewRect); + continue; + } + curView->OnPostDraw(curViewRect); + if (enableAnimator_ && (transViewGroup == nullptr)) { + ScreenDeviceProxy::GetInstance()->EnableAnimatorBuffer(false); + ScreenDeviceProxy::GetInstance()->DrawAnimatorBuffer(mask); + curView->GetTransformMap().SetInvalid(false); + enableAnimator_ = false; + curView->SetPosition(RelativeRect.GetX(), RelativeRect.GetY()); + } + } + } + curView = curView->GetNextSibling(); + continue; + } + if (--stackCount >= 0) { + curViewRect = par->GetMaskedRect(); + mask = g_maskStack[stackCount]; + if (curViewRect.Intersect(curViewRect, g_maskStack[stackCount])) { + par->OnPostDraw(curViewRect); + } + if (enableAnimator_ && transViewGroup == g_viewStack[stackCount]) { + ScreenDeviceProxy::GetInstance()->EnableAnimatorBuffer(false); + ScreenDeviceProxy::GetInstance()->DrawAnimatorBuffer(mask); + transViewGroup->GetTransformMap().SetInvalid(false); + enableAnimator_ = false; + transViewGroup->SetPosition(RelativeRect.GetX(), RelativeRect.GetY()); + transViewGroup = nullptr; + } + curView = g_viewStack[stackCount]->GetNextSibling(); + par = par->GetParent(); + continue; + } + stackCount = 0; + curView = par->GetNextSibling(); + par = par->GetParent(); + } +} + +UIView* RootView::GetTopUIView(const Rect& rect) +{ + int16_t stackCount = 0; + UIView* currentView = this; + UIView* topView = currentView; + Rect copyRect(rect); + while (stackCount >= 0) { + while (currentView != nullptr) { + if (currentView->GetOrigRect().IsContains(rect) && currentView->IsVisible()) { + if (currentView->GetStyleConst().bgOpa_ == OPA_OPAQUE && currentView->OnPreDraw(copyRect) && + currentView->GetOpaScale() == OPA_OPAQUE) { + topView = currentView; + } + if (currentView->IsViewGroup() && stackCount < COMPONENT_NESTING_DEPTH) { + g_viewStack[stackCount++] = currentView; + currentView = static_cast(currentView)->GetChildrenHead(); + continue; + } + } + currentView = currentView->GetNextSibling(); + } + if (--stackCount >= 0) { + currentView = (g_viewStack[stackCount])->GetNextSibling(); + } + } + return topView; +} + +bool RootView::FindSubView(const UIView& parentView, const UIView* subView) +{ + const UIView* root = &parentView; + if (root == subView) { + return true; + } else if (!root->IsViewGroup() || (subView == nullptr)) { + return false; + } + + UIView* currentView = static_cast(root)->GetChildrenHead(); + const UIView* parent = root; + int8_t deep = 1; + while (deep > 0) { + if (currentView == subView) { + return true; + } + if (currentView == nullptr) { + currentView = parent->GetNextSibling(); + parent = parent->GetParent(); + deep--; + } else if (currentView->IsViewGroup()) { + parent = currentView; + currentView = static_cast(currentView)->GetChildrenHead(); + deep++; + } else { + currentView = currentView->GetNextSibling(); + } + } + return false; +} +} // namespace OHOS \ No newline at end of file diff --git a/frameworks/components/text_adapter.cpp b/frameworks/components/text_adapter.cpp new file mode 100755 index 0000000..3df75ff --- /dev/null +++ b/frameworks/components/text_adapter.cpp @@ -0,0 +1,229 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "components/text_adapter.h" +#include +#include "securec.h" + +namespace OHOS { +bool TextFormatter::Format(int16_t value, char* outText, uint16_t textLen) +{ + if (sprintf_s(outText, textLen, "%d", value) < 0) { + return false; + } + return true; +} + +TextAdapter::TextAdapter() + : dataMode_(DYNAMIC_TEXT_MODE), + fontName_(nullptr), + fontSize_(0), + width_(0), + height_(0), + direct_(UITextLanguageDirect::TEXT_DIRECT_LTR), + lineBreakMode_(UILabel::LINE_BREAK_ADAPT), + integerTextStart_(0), + integerTextEnd_(0), + clickListener_(nullptr), + formatter_(nullptr) +{ + style_ = StyleDefault::GetBackgroundTransparentStyle(); + fontId_ = style_.font_; +} + +TextAdapter::~TextAdapter() +{ + ClearDynamicText(); + if (fontName_ != nullptr) { + UIFree(fontName_); + fontName_ = nullptr; + } +} + +void TextAdapter::SetFont(const char* name, uint8_t size) +{ + Text::SetFont(name, size, fontName_, fontSize_); +} + +UIView* TextAdapter::GetView(UIView* inView, int16_t index) +{ + UILabel* newView = GetTextView(inView, index); + if (newView == nullptr) { + return nullptr; + } + newView->SetLineBreakMode(lineBreakMode_); + newView->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER); + if (width_) { + newView->SetWidth(width_); + } + if (height_) { + newView->SetHeight(height_); + } + newView->SetViewIndex(index); + newView->UIView::SetStyle(style_); + newView->GetHeight(); + if (clickListener_) { + newView->SetOnClickListener(clickListener_); + newView->SetTouchable(true); + } + return newView; +} + +UILabel* TextAdapter::GetTextView(UIView* inView, int16_t index) +{ + switch (dataMode_) { + case DYNAMIC_TEXT_MODE: + return GetDynamicText(inView, index); + case CONTINUOUS_INTEGER_MODE: + return GetIntegerText(inView, index); + default: + return nullptr; + } +} + +UILabel* TextAdapter::GetDynamicText(UIView* inView, int16_t index) +{ + if (dynamicText_.IsEmpty() || (index > dynamicText_.Size() - 1) || (index < 0)) { + return nullptr; + } + + ListNode* node = dynamicText_.Begin(); + for (int16_t i = 0; i < index; i++) { + node = node->next_; + } + UILabel* newView = nullptr; + if (inView == nullptr) { + newView = new UILabel(); + } else { + newView = static_cast(inView); + } + + if (newView != nullptr) { + newView->SetText(node->data_); + if (fontName_ == nullptr) { + newView->SetFontId(fontId_); + } else { + newView->SetFont(fontName_, fontSize_); + } + newView->SetDirect(direct_); + } + return newView; +} + +UILabel* TextAdapter::GetIntegerText(UIView* inView, int16_t index) +{ + if ((index < 0) || ((integerTextEnd_ - integerTextStart_) < index)) { + return nullptr; + } + UILabel* newView = nullptr; + if (inView == nullptr) { + newView = new UILabel(); + } else { + newView = static_cast(inView); + } + + if (newView != nullptr) { + char buf[BUF_LEN] = {0}; + if (formatter_ != nullptr) { + if (!formatter_->Format(integerTextStart_ + index, buf, BUF_LEN)) { + if (inView == nullptr) { + delete newView; + newView = nullptr; + } + return nullptr; + } + } else { + if (sprintf_s(buf, sizeof(buf), "%02d", integerTextStart_ + index) < 0) { + if (inView == nullptr) { + delete newView; + newView = nullptr; + } + return nullptr; + } + } + + buf[BUF_LEN - 1] = '\0'; + newView->SetText(buf); + if (fontName_ == nullptr) { + newView->SetFontId(fontId_); + } else { + newView->SetFont(fontName_, fontSize_); + } + newView->SetDirect(direct_); + } + return newView; +} + +void TextAdapter::ClearDynamicText() +{ + ListNode* node = dynamicText_.Begin(); + while (node != dynamicText_.End()) { + if (node->data_) { + UIFree(reinterpret_cast(const_cast(node->data_))); + node->data_ = nullptr; + } + node = node->next_; + } + dynamicText_.Clear(); +} + +void TextAdapter::SetData(List* data) +{ + if (data == nullptr) { + return; + } + if (!dynamicText_.IsEmpty()) { + ClearDynamicText(); + } + ListNode* node = data->Begin(); + while (node != data->End()) { + uint32_t len = strlen(node->data_); + char* stringData = static_cast(UIMalloc(len + 1)); + if (stringData == nullptr) { + return; + } + if (memcpy_s(stringData, len + 1, node->data_, len) != EOK) { + UIFree(reinterpret_cast(stringData)); + stringData = nullptr; + return; + } + stringData[len] = '\0'; + dynamicText_.PushBack(stringData); + node = node->next_; + } + dataMode_ = DYNAMIC_TEXT_MODE; +} + +void TextAdapter::SetData(int16_t start, int16_t end) +{ + if (start <= end) { + integerTextStart_ = start; + integerTextEnd_ = end; + dataMode_ = CONTINUOUS_INTEGER_MODE; + } +} + +uint16_t TextAdapter::GetCount() +{ + switch (dataMode_) { + case DYNAMIC_TEXT_MODE: + return dynamicText_.Size(); + case CONTINUOUS_INTEGER_MODE: + return (integerTextStart_ <= integerTextEnd_) ? (integerTextEnd_ - integerTextStart_ + 1) : 0; + default: + return 0; + } +} +} // namespace OHOS \ No newline at end of file diff --git a/frameworks/components/ui_abstract_clock.cpp b/frameworks/components/ui_abstract_clock.cpp new file mode 100755 index 0000000..fa053da --- /dev/null +++ b/frameworks/components/ui_abstract_clock.cpp @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "components/ui_abstract_clock.h" + +namespace OHOS { +void UIAbstractClock::SetTime24Hour(uint8_t hour, uint8_t minute, uint8_t second) +{ + currentHour_ = hour % ONE_DAY_IN_HOUR; + currentMinute_ = minute % ONE_HOUR_IN_MINUTE; + currentSecond_ = second % ONE_MINUTE_IN_SECOND; + UpdateClock(false); +} + +void UIAbstractClock::SetTime12Hour(uint8_t hour, uint8_t minute, uint8_t second, bool am) +{ + SetTime24Hour((hour % HALF_DAY_IN_HOUR) + (am ? 0 : HALF_DAY_IN_HOUR), minute, second); +} + +void UIAbstractClock::IncOneSecond() +{ + currentSecond_++; + currentMinute_ += currentSecond_ / ONE_MINUTE_IN_SECOND; + currentSecond_ = currentSecond_ % ONE_MINUTE_IN_SECOND; + + currentHour_ += currentMinute_ / ONE_HOUR_IN_MINUTE; + currentMinute_ = currentMinute_ % ONE_HOUR_IN_MINUTE; + currentHour_ = currentHour_ % ONE_DAY_IN_HOUR; + + UpdateClock(false); +} + +void UIAbstractClock::SetWorkMode(WorkMode newMode) +{ + if (mode_ != newMode) { + mode_ = newMode; + Invalidate(); + } +} + +void UIAbstractClock::UpdateClock(bool clockInit) +{ + Invalidate(); +} +} // namespace OHOS \ No newline at end of file diff --git a/frameworks/components/ui_abstract_progress.cpp b/frameworks/components/ui_abstract_progress.cpp new file mode 100755 index 0000000..8f9e62c --- /dev/null +++ b/frameworks/components/ui_abstract_progress.cpp @@ -0,0 +1,230 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "components/ui_abstract_progress.h" +#include "common/image.h" +#include "draw/draw_utils.h" +#include "graphic_log.h" +#include "imgdecode/cache_manager.h" +#include "themes/theme_manager.h" + +namespace OHOS { +UIAbstractProgress::UIAbstractProgress() + : enableBackground_(true), + backgroundStyleAllocFlag_(false), + foregroundStyleAllocFlag_(false), + backgroundImage_(nullptr), + foregroundImage_(nullptr), + rangeMax_(MAX_PERCENT_VALUE), + rangeMin_(MIN_PERCENT_VALUE), + curValue_(0), + step_(1), + lastValue_(0) +{ + style_ = &(StyleDefault::GetBackgroundTransparentStyle()); + Theme* theme = ThemeManager::GetInstance().GetCurrent(); + if (theme != nullptr) { + backgroundStyle_ = &(theme->GetProgressBackgroundStyle()); + foregroundStyle_ = &(theme->GetProgressForegroundStyle()); + } else { + backgroundStyle_ = &(StyleDefault::GetProgressBackgroundStyle()); + foregroundStyle_ = &(StyleDefault::GetProgressForegroundStyle()); + } +} + +UIAbstractProgress::~UIAbstractProgress() +{ + if (backgroundImage_ != nullptr) { + delete backgroundImage_; + backgroundImage_ = nullptr; + } + + if (foregroundImage_ != nullptr) { + delete foregroundImage_; + foregroundImage_ = nullptr; + } + + if (backgroundStyleAllocFlag_) { + delete backgroundStyle_; + backgroundStyle_ = nullptr; + backgroundStyleAllocFlag_ = false; + } + + if (foregroundStyleAllocFlag_) { + delete foregroundStyle_; + foregroundStyle_ = nullptr; + foregroundStyleAllocFlag_ = false; + } +} + +void UIAbstractProgress::SetRange(int32_t rangeMax, int32_t rangeMin) +{ + if ((rangeMax_ == rangeMax) && (rangeMin_ == rangeMin)) { + return; + } + + if (rangeMax >= rangeMin) { + rangeMax_ = rangeMax; + rangeMin_ = rangeMin; + lastValue_ = rangeMin; + SetValue(curValue_); + } else { + GRAPHIC_LOGW("UIAbstractProgress::SetRange rangeMax less than rangeMin !\n"); + } +}; + +void UIAbstractProgress::SetValue(int32_t value) +{ + if (value < rangeMin_) { + curValue_ = rangeMin_; + } else if (value > rangeMax_) { + curValue_ = rangeMax_; + } else { + curValue_ = value; + } + + if ((curValue_ != lastValue_) && + ((curValue_ == rangeMin_) || (curValue_ == rangeMax_) || + (MATH_ABS(curValue_ - lastValue_) >= static_cast(step_)))) { + Invalidate(); + lastValue_ = curValue_; + } +} + +int16_t UIAbstractProgress::GetCurrentPos(int16_t distance) const +{ + uint32_t delta = lastValue_ - rangeMin_; + uint32_t rangeSize = GetRangeSize(); + if (rangeSize == 0) { + return distance; + } + int16_t result = static_cast(distance) * delta / rangeSize; + return result; +} + +uint32_t UIAbstractProgress::GetRangeSize() const +{ + return (rangeMax_ < rangeMin_) ? 0 : (rangeMax_ - rangeMin_); +} + +void UIAbstractProgress::SetImage(const char* foregroundImage, const char* backgroundImage) +{ + if (!InitImage()) { + return; + } + backgroundImage_->SetSrc(backgroundImage); + foregroundImage_->SetSrc(foregroundImage); +} + +void UIAbstractProgress::SetImage(const ImageInfo* foregroundImage, const ImageInfo* backgroundImage) +{ + if (!InitImage()) { + return; + } + backgroundImage_->SetSrc(backgroundImage); + foregroundImage_->SetSrc(foregroundImage); +} + +void UIAbstractProgress::SetBackgroundStyle(const Style& style) +{ + if (!backgroundStyleAllocFlag_) { + backgroundStyle_ = new Style(); + if (backgroundStyle_ == nullptr) { + GRAPHIC_LOGE("new Style1 fail"); + return; + } + backgroundStyleAllocFlag_ = true; + } + *backgroundStyle_ = style; +} + +void UIAbstractProgress::SetBackgroundStyle(uint8_t key, int64_t value) +{ + if (!backgroundStyleAllocFlag_) { + backgroundStyle_ = new Style(*backgroundStyle_); + if (backgroundStyle_ == nullptr) { + GRAPHIC_LOGE("new Style1 fail"); + return; + } + backgroundStyleAllocFlag_ = true; + } + backgroundStyle_->SetStyle(key, value); +} + +const Style& UIAbstractProgress::GetBackgroundStyle() const +{ + return *backgroundStyle_; +} + +int64_t UIAbstractProgress::GetBackgroundStyle(uint8_t key) const +{ + return backgroundStyle_->GetStyle(key); +} + +void UIAbstractProgress::SetForegroundStyle(const Style& style) +{ + if (!foregroundStyleAllocFlag_) { + foregroundStyle_ = new Style(); + if (foregroundStyle_ == nullptr) { + GRAPHIC_LOGE("new Style1 fail"); + return; + } + foregroundStyleAllocFlag_ = true; + } + *foregroundStyle_ = style; +} + +void UIAbstractProgress::SetForegroundStyle(uint8_t key, int64_t value) +{ + if (!foregroundStyleAllocFlag_) { + foregroundStyle_ = new Style(*foregroundStyle_); + if (foregroundStyle_ == nullptr) { + GRAPHIC_LOGE("new Style1 fail"); + return; + } + foregroundStyleAllocFlag_ = true; + } + foregroundStyle_->SetStyle(key, value); +} + +const Style& UIAbstractProgress::GetForegroundStyle() const +{ + return *foregroundStyle_; +} + +int64_t UIAbstractProgress::GetForegroundStyle(uint8_t key) const +{ + return foregroundStyle_->GetStyle(key); +} + +bool UIAbstractProgress::InitImage() +{ + if (backgroundImage_ == nullptr) { + backgroundImage_ = new Image(); + if (backgroundImage_ == nullptr) { + GRAPHIC_LOGE("new Image fail"); + return false; + } + } + if (foregroundImage_ == nullptr) { + foregroundImage_ = new Image(); + if (foregroundImage_ == nullptr) { + GRAPHIC_LOGE("new Image fail"); + return false; + } + } + return true; +} +} // namespace OHOS diff --git a/frameworks/components/ui_abstract_scroll.cpp b/frameworks/components/ui_abstract_scroll.cpp new file mode 100755 index 0000000..2c6d90a --- /dev/null +++ b/frameworks/components/ui_abstract_scroll.cpp @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "components/ui_abstract_scroll.h" + +namespace OHOS { +UIAbstractScroll::UIAbstractScroll() + : scrollBlankSize_(0), + reboundSize_(0), + maxScrollDistance_(0), + lastDeltaY_{0}, + dragAccCoefficient_(DRAG_ACC_FACTOR), + swipeAccCoefficient_(0), + direction_(VERTICAL), + deltaYIndex_(0), + reserve_(0), + throwDrag_(false), + easingFunc_(EasingEquation::CubicEaseOut), + scrollAnimator_(&animatorCallback_, this, 0, true) +{ +#if ENABLE_ROTATE_INPUT + rotateFactor_ = 1; + rotateThreshold_ = 1; +#endif + isViewGroup_ = true; + touchable_ = true; + draggable_ = true; + dragParentInstead_ = false; + AnimatorManager::GetInstance()->Add(&scrollAnimator_); +} + +UIAbstractScroll::~UIAbstractScroll() +{ + scrollAnimator_.Stop(); + AnimatorManager::GetInstance()->Remove(&scrollAnimator_); +} + +void UIAbstractScroll::MoveChildByOffset(int16_t offsetX, int16_t offsetY) +{ + if ((offsetX == 0) && (offsetY == 0)) { + return; + } + UIView* view = GetChildrenHead(); + int16_t x; + int16_t y; + while (view != nullptr) { + x = view->GetX() + offsetX; + y = view->GetY() + offsetY; + view->SetPosition(x, y); + view = view->GetNextSibling(); + } + Invalidate(); +} + +int16_t UIAbstractScroll::GetMaxDeltaY() const +{ + int16_t result = 0; + for (int16_t i = 0; i < MAX_DELTA_Y_SIZE; i++) { + if (result < MATH_ABS(lastDeltaY_[i])) { + result = MATH_ABS(lastDeltaY_[i]); + } + } + return result; +} + +void UIAbstractScroll::StopAnimator() +{ + scrollAnimator_.Stop(); + animatorCallback_.RsetCallback(); + isDragging_ = false; +} + +bool UIAbstractScroll::DragThrowAnimator(Point currentPos, Point lastPos) +{ + if (!throwDrag_ && (reboundSize_ == 0)) { + return false; + } + int16_t dragDistanceX = 0; + int16_t dragDistanceY = 0; + if (throwDrag_) { + CalculateDragDistance(currentPos, lastPos, dragDistanceX, dragDistanceY); + } + if (reboundSize_ != 0) { + CalculateReboundDistance(dragDistanceX, dragDistanceY); + } + StartAnimator(dragDistanceX, dragDistanceY); + return true; +} + +void UIAbstractScroll::StartAnimator(int16_t dragDistanceX, int16_t dragDistanceY) +{ + int16_t dragTimes = MATH_MAX(MATH_ABS(dragDistanceX), MATH_ABS(dragDistanceY)) / DRAG_TIMES_COEFFICIENT; + if (dragTimes < MIN_DRAG_TIMES) { + dragTimes = MIN_DRAG_TIMES; + } + animatorCallback_.SetDragStartValue(0, 0); + animatorCallback_.SetDragEndValue(dragDistanceX, dragDistanceY); + animatorCallback_.SetDragTimes(dragTimes * DRAG_ACC_FACTOR / GetDragACCLevel()); + scrollAnimator_.Start(); +} + +void UIAbstractScroll::CalculateDragDistance(Point currentPos, + Point lastPos, + int16_t& dragDistanceX, + int16_t& dragDistanceY) +{ + if ((direction_ == VERTICAL) || (direction_ == HORIZONTAL_AND_VERTICAL)) { + dragDistanceY = (currentPos.y - lastPos.y) * DRAG_DISTANCE_COEFFICIENT; + if (dragDistanceY > 0) { + dragDistanceY += GetMaxDeltaY() * GetSwipeACCLevel() / DRAG_ACC_FACTOR; + } else { + dragDistanceY -= GetMaxDeltaY() * GetSwipeACCLevel() / DRAG_ACC_FACTOR; + } + } + + if ((direction_ == HORIZONTAL) || (direction_ == HORIZONTAL_AND_VERTICAL)) { + dragDistanceX = (currentPos.x - lastPos.x) * DRAG_DISTANCE_COEFFICIENT; + } + + if (maxScrollDistance_ != 0) { + if (MATH_ABS(dragDistanceY) > maxScrollDistance_) { + int16_t calculatedValue = (dragDistanceY > 0) ? 1 : -1; + dragDistanceY = calculatedValue * maxScrollDistance_; + } + if (MATH_ABS(dragDistanceX) > maxScrollDistance_) { + int16_t calculatedValue = (dragDistanceX > 0) ? 1 : -1; + dragDistanceX = calculatedValue * maxScrollDistance_; + } + } +} + +void UIAbstractScroll::ListAnimatorCallback::Callback(UIView* view) +{ + if (view == nullptr) { + return; + } + curtTime_++; + + UIAbstractScroll* scrollView = static_cast(view); + scrollView->isDragging_ = true; + + if (curtTime_ <= dragTimes_) { + bool needStopX = false; + bool needStopY = false; + if (startValueY_ != endValueY_) { + int16_t actY = scrollView->easingFunc_(startValueY_, endValueY_, curtTime_, dragTimes_); + if (!scrollView->DragYInner(actY - previousValueY_)) { + needStopY = true; + } + previousValueY_ = actY; + } else { + needStopY = true; + } + if (startValueX_ != endValueX_) { + int16_t actX = scrollView->easingFunc_(startValueX_, endValueX_, curtTime_, dragTimes_); + if (!scrollView->DragXInner(actX - previousValueX_)) { + needStopX = true; + } + previousValueX_ = actX; + } else { + needStopX = true; + } + if (needStopX && needStopY) { + scrollView->StopAnimator(); + } + } else { + scrollView->StopAnimator(); + } +} +} // namespace OHOS \ No newline at end of file diff --git a/frameworks/components/ui_analog_clock.cpp b/frameworks/components/ui_analog_clock.cpp new file mode 100755 index 0000000..3780a87 --- /dev/null +++ b/frameworks/components/ui_analog_clock.cpp @@ -0,0 +1,336 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "components/ui_analog_clock.h" +#include "components/ui_image_view.h" +#include "draw/draw_image.h" +#include "draw/draw_line.h" +#include "draw/draw_rect.h" +#include "graphic_log.h" +#include "imgdecode/cache_manager.h" +#include "style.h" +#include "themes/theme.h" + +namespace OHOS { +UIAnalogClock::UIAnalogClock() +{ + touchable_ = true; +} + +void UIAnalogClock::SetHandImage(HandType type, const UIImageView& img, Point position, Point center) +{ + Hand *hand = nullptr; + if (type == HandType::HOUR_HAND) { + hand = &hourHand_; + } else if (type == HandType::MINUTE_HAND) { + hand = &minuteHand_; + } else { + hand = &secondHand_; + } + + hand->center_ = center; + hand->position_ = position; + hand->initAngle_ = 0; + hand->preAngle_ = 0; + hand->nextAngle_ = 0; + hand->drawtype_ = DrawType::DRAW_IMAGE; + + if (img.GetSrcType() == IMG_SRC_FILE) { + CacheEntry entry; + RetCode ret = CacheManager::GetInstance().Open(img.GetPath(), *style_, entry); + if (ret != RetCode::OK) { + return; + } + hand->imageInfo_ = entry.GetImageInfo(); + } else { + hand->imageInfo_ = *(img.GetImageInfo()); + } +} + +void UIAnalogClock::SetHandLine(HandType type, Point position, Point center, ColorType color, + uint16_t width, uint16_t height, OpacityType opacity) +{ + Hand* hand = nullptr; + if (type == HandType::HOUR_HAND) { + hand = &hourHand_; + } else if (type == HandType::MINUTE_HAND) { + hand = &minuteHand_; + } else { + hand = &secondHand_; + } + + hand->color_ = color; + hand->height_ = height; + hand->width_ = width; + hand->position_ = position; + hand->center_ = center; + hand->opacity_ = opacity; + hand->initAngle_ = 0; + hand->preAngle_ = 0; + hand->nextAngle_ = 0; + hand->drawtype_ = DrawType::DRAW_LINE; +} + +Point UIAnalogClock::GetHandRotateCenter(HandType type) const +{ + if (type == HandType::HOUR_HAND) { + return hourHand_.center_; + } else if (type == HandType::MINUTE_HAND) { + return minuteHand_.center_; + } else { + return secondHand_.center_; + } +} + +Point UIAnalogClock::GetHandPosition(HandType type) const +{ + if (type == HandType::HOUR_HAND) { + return hourHand_.position_; + } else if (type == HandType::MINUTE_HAND) { + return minuteHand_.position_; + } else { + return secondHand_.position_; + } +} + +uint16_t UIAnalogClock::GetHandInitAngle(HandType type) const +{ + if (type == HandType::HOUR_HAND) { + return hourHand_.initAngle_; + } else if (type == HandType::MINUTE_HAND) { + return minuteHand_.initAngle_; + } else { + return secondHand_.initAngle_; + } +} + +uint16_t UIAnalogClock::GetHandCurrentAngle(HandType type) const +{ + if (type == HandType::HOUR_HAND) { + return hourHand_.nextAngle_; + } else if (type == HandType::MINUTE_HAND) { + return minuteHand_.nextAngle_; + } else { + return secondHand_.nextAngle_; + } +} + +void UIAnalogClock::SetInitTime24Hour(uint8_t hour, uint8_t minute, uint8_t second) +{ + currentHour_ = hour % ONE_DAY_IN_HOUR; + currentMinute_ = minute % ONE_HOUR_IN_MINUTE; + currentSecond_ = second % ONE_MINUTE_IN_SECOND; + + hourHand_.initAngle_ = ConvertHandValueToAngle(currentHour_, + HALF_DAY_IN_HOUR, currentMinute_, ONE_HOUR_IN_MINUTE); + hourHand_.preAngle_ = hourHand_.initAngle_; + hourHand_.nextAngle_ = hourHand_.initAngle_; + + minuteHand_.initAngle_ = ConvertHandValueToAngle(currentMinute_, + ONE_HOUR_IN_MINUTE, currentSecond_, ONE_MINUTE_IN_SECOND); + minuteHand_.preAngle_ = minuteHand_.initAngle_; + minuteHand_.nextAngle_ = minuteHand_.initAngle_; + + secondHand_.initAngle_ = ConvertHandValueToAngle(currentSecond_, ONE_MINUTE_IN_SECOND); + secondHand_.preAngle_ = secondHand_.initAngle_; + secondHand_.nextAngle_ = secondHand_.initAngle_; + + UpdateClock(true); +} + +void UIAnalogClock::SetInitTime12Hour(uint8_t hour, uint8_t minute, uint8_t second, bool am) +{ + SetInitTime24Hour((hour % HALF_DAY_IN_HOUR) + (am ? 0 : HALF_DAY_IN_HOUR), minute, second); +} + +uint16_t UIAnalogClock::ConvertHandValueToAngle(uint8_t handValue, uint8_t range, + uint8_t secondHandValue, uint8_t ratio) const +{ + if ((range == 0) || (ratio == 0)) { + GRAPHIC_LOGW("UIAnalogClock::ConvertHandValueToAngle Invalid range or ratio\n"); + return 0; + } + /* + * Example: calculate the angle of hour hand + * Assume that the time is 5: 30, then range is 12, radio is 60 + * angle is [(5 * 60 + 30) / (12 * 60)] * 360 + */ + uint32_t degree = (static_cast(handValue) * ratio + secondHandValue); + degree = static_cast(CIRCLE_IN_DEGREE * degree / (static_cast(range) * ratio)); + + return static_cast(degree % CIRCLE_IN_DEGREE); +} + +uint16_t UIAnalogClock::ConvertHandValueToAngle(uint8_t handValue, uint8_t range) const +{ + if (range == 0) { + GRAPHIC_LOGW("UIAnalogClock::ConvertHandValueToAngle Invalid range or ratio\n"); + return 0; + } + /* + * Example: calculate the angle of second hand without millisecond handle + * Assume that the time is 5:30:30, then range is 60 + * angle is (30 / 60) * 360 + */ + return (static_cast(handValue) * CIRCLE_IN_DEGREE / range); +} + +void UIAnalogClock::UpdateClock(bool clockInit) +{ + Invalidate(); + hourHand_.nextAngle_ = ConvertHandValueToAngle(currentHour_, + HALF_DAY_IN_HOUR, currentMinute_, ONE_HOUR_IN_MINUTE); + + minuteHand_.nextAngle_ = ConvertHandValueToAngle(currentMinute_, + ONE_HOUR_IN_MINUTE, currentSecond_, ONE_MINUTE_IN_SECOND); + + secondHand_.nextAngle_ = ConvertHandValueToAngle(currentSecond_, ONE_MINUTE_IN_SECOND); + + Rect rect = GetRect(); + CalculateRedrawArea(rect, hourHand_, clockInit); + CalculateRedrawArea(rect, minuteHand_, clockInit); + if (GetWorkMode() == WorkMode::NORMAL) { + CalculateRedrawArea(rect, secondHand_, clockInit); + } +} + +void UIAnalogClock::OnDraw(const Rect& invalidatedArea) +{ + DrawRect::Draw(GetRect(), invalidatedArea, *style_, opaScale_); +} + +void UIAnalogClock::OnPostDraw(const Rect& invalidatedArea) +{ + Rect current = GetOrigRect(); + DrawHand(current, invalidatedArea, hourHand_); + DrawHand(current, invalidatedArea, minuteHand_); + if (GetWorkMode() == WorkMode::NORMAL) { + DrawHand(current, invalidatedArea, secondHand_); + } +} + +void UIAnalogClock::SetPosition(int16_t x, int16_t y) +{ + UIViewGroup::SetPosition(x, y); + UpdateClock(true); +} + +void UIAnalogClock::SetPosition(int16_t x, int16_t y, int16_t width, int16_t height) +{ + UIViewGroup::SetPosition(x, y, width, height); + UpdateClock(true); +} + +void UIAnalogClock::CalculateRedrawArea(const Rect& current, Hand& hand, bool clockInit) +{ + /* + * Use the current image as an independent rectangular area + * to calculate the coordinate conversion coefficient. + */ + int16_t imgWidth = hand.imageInfo_.header.width; + int16_t imgHeight = hand.imageInfo_.header.height; + + int16_t left = hand.position_.x + current.GetLeft(); + int16_t right = left + imgWidth; + int16_t top = hand.position_.y + current.GetTop(); + int16_t bottom = top + imgHeight; + Rect imgRect(left, top, right, bottom); + TransformMap backwardMap(imgRect); + Vector2 pivot; + pivot.x_ = hand.center_.x; + pivot.y_ = hand.center_.y; + + /* Rotate the specified angle, */ + backwardMap.Rotate(hand.nextAngle_ - hand.initAngle_, pivot); + Rect redraw = hand.target_; + hand.target_ = backwardMap.GetBoxRect(); + hand.trans_ = backwardMap; + hand.preAngle_ = hand.nextAngle_; + if (!clockInit) { + /* Prevent old images from being residued */ + redraw.Join(redraw, hand.target_); + InvalidateRect(redraw); + } +} + +void UIAnalogClock::DrawHand(const Rect& current, const Rect& invalidatedArea, Hand& hand) +{ + if (hand.drawtype_ == DrawType::DRAW_IMAGE) { + DrawHandImage(current, invalidatedArea, hand); + } else { + DrawHandLine(invalidatedArea, hand); + } +} + +void UIAnalogClock::DrawHandImage(const Rect& current, const Rect& invalidatedArea, Hand& hand) +{ + uint8_t pxSize = DrawUtils::GetPxSizeByColorMode(hand.imageInfo_.header.colorMode); + TransformDataInfo imageTranDataInfo = { + hand.imageInfo_.header, hand.imageInfo_.data, pxSize, + BlurLevel::LEVEL0, TransformAlgorithm::BILINEAR + }; + DrawUtils::GetInstance()->DrawTransform(invalidatedArea, { 0, 0 }, + Color::Black(), opaScale_, hand.trans_, imageTranDataInfo); +} + +void UIAnalogClock::DrawHandLine(const Rect& invalidatedArea, Hand& hand) +{ + float sinma = Sin(hand.nextAngle_); + float cosma = Sin(hand.nextAngle_ + THREE_QUARTER_IN_DEGREE); + int32_t handLength = hand.height_; + Rect rect = GetRect(); + Point start; + Point end; + Point curCenter; + curCenter.x = hand.position_.x + hand.center_.x + rect.GetLeft(); + curCenter.y = hand.position_.y + hand.center_.y + rect.GetTop(); + + int32_t startToCenterLength = hand.center_.y; + + int32_t xPointLength = static_cast(startToCenterLength * sinma); + int32_t yPointLength = static_cast(startToCenterLength * cosma); + + start.x = xPointLength + curCenter.x; + start.y = yPointLength + curCenter.y; + + /* + * @ startToCenterLength: means the length between StartPoint and CenterPoint. + * @ handlength: means the hand height. + * @ xlength: means X-axis length relative to the center point + * @ ylength: means Y-axis length relative to the center point + */ + int32_t xlength = static_cast((startToCenterLength - handLength) * sinma); + int32_t ylength = static_cast((startToCenterLength - handLength) * cosma); + end.x = xlength + curCenter.x; + end.y = ylength + curCenter.y; + + DrawLine::Draw(start, end, invalidatedArea, hand.width_, hand.color_, hand.opacity_); +} + +void UIAnalogClock::SetWorkMode(WorkMode newMode) +{ + WorkMode oldMode = mode_; + + if (oldMode != newMode) { + /* + * After entering the alwayson mode, all child controls are no longer drawn, + * making the simplest analog clock. + */ + isViewGroup_ = (newMode == ALWAYS_ON) ? false : true; + mode_ = newMode; + Invalidate(); + } +} +} // namespace OHOS diff --git a/frameworks/components/ui_arc_label.cpp b/frameworks/components/ui_arc_label.cpp new file mode 100755 index 0000000..c93d148 --- /dev/null +++ b/frameworks/components/ui_arc_label.cpp @@ -0,0 +1,219 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "components/ui_arc_label.h" +#include "common/typed_text.h" +#include "draw/draw_label.h" +#include "draw/draw_rect.h" +#include "font/ui_font.h" +#include "themes/theme_manager.h" + +namespace OHOS { +UIArcLabel::UIArcLabel() + : arcLabelText_(nullptr), + needRefresh_(false), + textSize_({0, 0}), + radius_(0), + startAngle_(0), + endAngle_(0), + arcCenter_({0, 0}), + orientation_(TextOrientation::INSIDE), + arcTextInfo_{0} +{ + Theme* theme = ThemeManager::GetInstance().GetCurrent(); + style_ = (theme != nullptr) ? &(theme->GetLabelStyle()) : &(StyleDefault::GetLabelStyle()); +} + +UIArcLabel::~UIArcLabel() +{ + if (arcLabelText_ != nullptr) { + delete arcLabelText_; + arcLabelText_ = nullptr; + } +} + +void UIArcLabel::SetStyle(uint8_t key, int64_t value) +{ + UIView::SetStyle(key, value); + RefreshArcLabel(); +} + +void UIArcLabel::SetText(const char* text) +{ + if (text == nullptr) { + return; + } + InitArcLabelText(); + arcLabelText_->SetText(text); + if (arcLabelText_->IsNeedRefresh()) { + RefreshArcLabel(); + } +} + +const char* UIArcLabel::GetText() const +{ + return (arcLabelText_ == nullptr) ? nullptr : arcLabelText_->GetText(); +} + +void UIArcLabel::SetAlign(UITextLanguageAlignment horizontalAlign) +{ + InitArcLabelText(); + arcLabelText_->SetAlign(horizontalAlign, TEXT_ALIGNMENT_TOP); + if (arcLabelText_->IsNeedRefresh()) { + RefreshArcLabel(); + } +} + +UITextLanguageAlignment UIArcLabel::GetHorAlign() +{ + InitArcLabelText(); + return arcLabelText_->GetHorAlign(); +} + +UITextLanguageDirect UIArcLabel::GetDirect() +{ + InitArcLabelText(); + return arcLabelText_->GetDirect(); +} + +void UIArcLabel::SetFontId(uint8_t fontId) +{ + InitArcLabelText(); + arcLabelText_->SetFontId(fontId); + if (arcLabelText_->IsNeedRefresh()) { + RefreshArcLabel(); + } +} + +uint8_t UIArcLabel::GetFontId() +{ + InitArcLabelText(); + return arcLabelText_->GetFontId(); +} + +void UIArcLabel::SetFont(const char* name, uint8_t size) +{ + if (name == nullptr) { + return; + } + InitArcLabelText(); + arcLabelText_->SetFont(name, size); + if (arcLabelText_->IsNeedRefresh()) { + RefreshArcLabel(); + } +} + +void UIArcLabel::OnDraw(const Rect& invalidatedArea) +{ + InitArcLabelText(); + const char* text = arcLabelText_->GetText(); + if ((text == nullptr) || (radius_ == 0)) { + return; + } + Rect trunc = invalidatedArea; + OpacityType opa = GetMixOpaScale(); + DrawRect::Draw(GetRect(), trunc, *style_, opa); + + Rect coords = GetContentRect(); + if (trunc.Intersect(trunc, coords)) { + DrawArcText(trunc, opa); + } +} + +void UIArcLabel::DrawArcText(const Rect& mask, OpacityType opaScale) +{ + Point center; + center.x = arcTextInfo_.arcCenter.x + GetRect().GetX(); + center.y = arcTextInfo_.arcCenter.y + GetRect().GetY(); + InitArcLabelText(); + UIFont::GetInstance()->SetCurrentFontId(arcLabelText_->GetFontId(), arcLabelText_->GetFontSize()); + DrawLabel::DrawArcText(mask, arcLabelText_->GetText(), center, arcLabelText_->GetFontId(), + arcTextInfo_, orientation_, *style_, opaScale); +} + +void UIArcLabel::RefreshArcLabel() +{ + Invalidate(); + if (!needRefresh_) { + needRefresh_ = true; + } +} + +void UIArcLabel::ReMeasure() +{ + if (!needRefresh_) { + return; + } + needRefresh_ = false; + InitArcLabelText(); + UIFont::GetInstance()->SetCurrentFontId(arcLabelText_->GetFontId(), arcLabelText_->GetFontSize()); + + MeasureArcTextInfo(); + Rect textRect = TypedText::GetArcTextRect(arcLabelText_->GetText(), + arcCenter_, + style_->letterSpace_, + orientation_, + arcTextInfo_); + int16_t arcTextWidth = textRect.GetWidth(); + int16_t arcTextHeight = textRect.GetHeight(); + + SetPosition(textRect.GetX(), textRect.GetY()); + Resize(arcTextWidth, arcTextHeight); + arcTextInfo_.arcCenter.x = arcCenter_.x - GetX(); + arcTextInfo_.arcCenter.y = arcCenter_.y - GetY(); + textSize_.x = arcTextWidth; + textSize_.y = arcTextHeight; + Invalidate(); +} + +void UIArcLabel::MeasureArcTextInfo() +{ + const char* text = arcLabelText_->GetText(); + if (text == nullptr) { + return; + } + uint16_t letterHeight = UIFont::GetInstance()->GetHeight(); + arcTextInfo_.radius = ((orientation_ == TextOrientation::INSIDE) ? radius_ : (radius_ - letterHeight)); + if (arcTextInfo_.radius == 0) { + return; + } + + uint16_t arcAngle; + if (startAngle_ < endAngle_) { + arcAngle = endAngle_ - startAngle_; + arcTextInfo_.direct = TEXT_DIRECT_LTR; // Clockwise + arcLabelText_->SetDirect(TEXT_DIRECT_LTR); + } else { + arcAngle = startAngle_ - endAngle_; + arcTextInfo_.direct = TEXT_DIRECT_RTL; // Counterclockwise + arcLabelText_->SetDirect(TEXT_DIRECT_RTL); + } + // calculate max arc length + float maxLength = static_cast((UI_PI * radius_ * arcAngle) / SEMICIRCLE_IN_DEGREE); + arcTextInfo_.lineStart = 0; + arcTextInfo_.lineEnd = TypedText::GetNextLine(&text[arcTextInfo_.lineStart], style_->letterSpace_, maxLength); + arcTextInfo_.startAngle = startAngle_ % CIRCLE_IN_DEGREE; + int16_t actLength = TypedText::GetTextWidth(&text[arcTextInfo_.lineStart], + arcTextInfo_.lineEnd - arcTextInfo_.lineStart, style_->letterSpace_); + if ((arcLabelText_->GetHorAlign() != TEXT_ALIGNMENT_LEFT) && (actLength < maxLength)) { + float gapLength = maxLength - actLength; + if (arcLabelText_->GetHorAlign() == TEXT_ALIGNMENT_CENTER) { + gapLength = gapLength / 2; // 2: half + } + arcTextInfo_.startAngle += TypedText::GetAngleForArcLen(gapLength, letterHeight, arcTextInfo_.radius, + arcTextInfo_.direct, orientation_); + } +} +} // namespace OHOS \ No newline at end of file diff --git a/frameworks/components/ui_axis.cpp b/frameworks/components/ui_axis.cpp new file mode 100755 index 0000000..9193eb8 --- /dev/null +++ b/frameworks/components/ui_axis.cpp @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "components/ui_axis.h" +#include "common/screen.h" +#include "draw/draw_line.h" + +namespace OHOS { +UIAxis::UIAxis() + : maxRange_(0), + minRange_(0), + start_({0, 0}), + end_({0, 0}), + markInterval_(0), + dataPerMark_(0), + dataInterval_(0), + markDataCount_(AXIS_DEFAULT_MARK_INTERVAL), + enableReverse_(false) +{ + SetStyle(STYLE_LINE_WIDTH, 1); + SetStyle(STYLE_LINE_COLOR, Color::White().full); +} + +void UIAxis::SetLineColor(const ColorType& color) +{ + SetStyle(STYLE_LINE_COLOR, color.full); +} + +void UIXAxis::SetMarkNum(uint16_t count) +{ + if ((count == 0) || (count > Screen::GetInstance().GetWidth())) { + return; + } + markDataCount_ = count; + UpdateAxis(); +} + +bool UIXAxis::SetDataRange(uint16_t min, uint16_t max) +{ + if (max <= min) { + return false; + } + maxRange_ = max; + minRange_ = min; + return UpdateAxis(); +} + +void UIXAxis::UpdateAxisPoints() +{ + Rect current = GetContentRect(); + start_.x = current.GetLeft(); + end_.x = current.GetRight(); + start_.y = enableReverse_ ? current.GetTop() : current.GetBottom(); + end_.y = start_.y; +} + +bool UIXAxis::UpdateAxis() +{ + UpdateAxisPoints(); + int16_t xAxisLength = end_.x - start_.x + 1; + if (xAxisLength <= 0) { + return false; + } + + if (markDataCount_ != 0) { + dataInterval_ = static_cast((maxRange_ - minRange_) / markDataCount_); + markInterval_ = static_cast(xAxisLength) / markDataCount_; + if (maxRange_ > minRange_) { + dataPerMark_ = markInterval_ / dataInterval_; + } + } + + return true; +} + +void UIXAxis::TranslateToPixel(int16_t& value) +{ + float minXStep = dataPerMark_ ? dataPerMark_ : markInterval_; + value = start_.x + static_cast((value - minRange_) * minXStep); +} + +void UIAxis::OnDraw(const Rect& invalidatedArea) +{ + DrawLine::Draw(start_, end_, invalidatedArea, style_->lineWidth_, style_->lineColor_, style_->lineOpa_); + DrawAxisMark(invalidatedArea); +} + +void UIXAxis::DrawAxisMark(const Rect& invalidatedArea) +{ + Point start; + Point end; + uint16_t index = 1; + while (index <= markDataCount_) { + start.y = start_.y; + start.x = start_.x + static_cast(index * markInterval_); + end.y = enableReverse_ ? (start.y + AXIS_DEFAULT_MARK_LENGTH) : (start.y - AXIS_DEFAULT_MARK_LENGTH); + end.x = start.x; + + DrawLine::Draw(start, end, invalidatedArea, style_->lineWidth_, style_->lineColor_, style_->lineOpa_); + index++; + } +} + +void UIYAxis::SetMarkNum(uint16_t count) +{ + if ((count == 0) || (count > Screen::GetInstance().GetHeight())) { + return; + } + markDataCount_ = count; + dataInterval_ = static_cast((maxRange_ - minRange_) / markDataCount_); +} + +bool UIYAxis::SetDataRange(uint16_t min, uint16_t max) +{ + if (max <= min) { + return false; + } + + maxRange_ = max; + minRange_ = min; + return UpdateAxis(); +} + +void UIYAxis::UpdateAxisPoints() +{ + Rect current = GetContentRect(); + int16_t top = current.GetTop(); + int16_t bottom = current.GetBottom(); + + start_.x = current.GetLeft(); + end_.x = start_.x; + if (enableReverse_) { + start_.y = top; + end_.y = bottom; + } else { + start_.y = bottom; + end_.y = top; + } +} + +void UIYAxis::TranslateToPixel(int16_t& value) +{ + float minYStep = dataPerMark_ ? dataPerMark_ : markInterval_; + if (enableReverse_) { + value = start_.y + static_cast((maxRange_ - value + minRange_) * minYStep); + } else { + value = start_.y - static_cast((value - minRange_) * minYStep); + } +} + +bool UIYAxis::UpdateAxis() +{ + UpdateAxisPoints(); + int16_t yAxisLength = enableReverse_ ? (end_.y - start_.y + 1) : (start_.y - end_.y + 1); + if (yAxisLength <= 0) { + return false; + } + + if (markDataCount_ != 0) { + dataInterval_ = static_cast((maxRange_ - minRange_) / markDataCount_); + markInterval_ = static_cast(yAxisLength) / markDataCount_; + if (dataInterval_ != 0) { + dataPerMark_ = markInterval_ / dataInterval_; + } + } + return true; +} + +void UIYAxis::DrawAxisMark(const Rect& invalidatedArea) +{ + uint16_t index = 1; + while (index <= markDataCount_) { + Point start; + Point end; + start.x = start_.x; + start.y = enableReverse_ ? (start_.y + static_cast(index * markInterval_)) + : (start_.y - static_cast(index * markInterval_)); + end.x = start.x + AXIS_DEFAULT_MARK_LENGTH; + end.y = start.y; + + DrawLine::Draw(start, end, invalidatedArea, style_->lineWidth_, style_->lineColor_, style_->lineOpa_); + index++; + } +} +} // namespace OHOS \ No newline at end of file diff --git a/frameworks/components/ui_box_progress.cpp b/frameworks/components/ui_box_progress.cpp new file mode 100755 index 0000000..27534d7 --- /dev/null +++ b/frameworks/components/ui_box_progress.cpp @@ -0,0 +1,237 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "components/ui_box_progress.h" +#include "draw/draw_arc.h" +#include "draw/draw_rect.h" +#include "graphic_log.h" + +namespace OHOS { +UIBoxProgress::UIBoxProgress() + : progressWidth_(0), progressHeight_(0), isValidWidthSet_(false), isValidHeightSet_(false) +{ + SetDirection(Direction::DIR_LEFT_TO_RIGHT); +} + +void UIBoxProgress::DrawValidRect(const Image* image, const Rect& rect, const Rect& invalidatedArea, + const Style& style, uint16_t radius) +{ + Rect cordsTmp; + if ((image != nullptr) && (image->GetSrcType() != IMG_SRC_UNKNOWN)) { + ImageHeader header = {0}; + image->GetHeader(header); + + Rect area(rect); + switch (direction_) { + case Direction::DIR_LEFT_TO_RIGHT: + cordsTmp.SetPosition(area.GetLeft() - radius, area.GetTop()); + break; + case Direction::DIR_TOP_TO_BOTTOM: + cordsTmp.SetPosition(area.GetLeft(), area.GetTop() - radius); + break; + case Direction::DIR_RIGHT_TO_LEFT: + cordsTmp.SetPosition(area.GetRight() + radius - header.width, area.GetTop()); + break; + case Direction::DIR_BOTTOM_TO_TOP: + cordsTmp.SetPosition(area.GetLeft(), area.GetBottom() + radius - header.height); + break; + default: + GRAPHIC_LOGE("UIBoxProgress: DrawValidRect direction Err!\n"); + break; + } + cordsTmp.SetHeight(header.height); + cordsTmp.SetWidth(header.width); + if (area.Intersect(area, invalidatedArea)) { + image->DrawImage(cordsTmp, area, style, opaScale_); + } + } else { + DrawRect::Draw(rect, invalidatedArea, style, opaScale_); + } + + if (style.lineCap_ == CapType::CAP_ROUND) { + DrawRoundCap(image, {cordsTmp.GetX(), cordsTmp.GetY()}, rect, invalidatedArea, radius, style); + } +} + +void UIBoxProgress::DrawRoundCap(const Image* image, const Point& imgPos, const Rect& rect, + const Rect& invalidatedArea, uint16_t radius, const Style& style) +{ + Point leftTop; + Point leftBottom; + Point rightTop; + Point rightBottom; + + switch (direction_) { + case Direction::DIR_LEFT_TO_RIGHT: + case Direction::DIR_RIGHT_TO_LEFT: { + leftTop.x = rect.GetLeft() - 1; + leftTop.y = rect.GetTop() + radius - 1; + leftBottom.x = leftTop.x; + leftBottom.y = rect.GetBottom() - radius + 1; + rightTop.x = rect.GetRight() + 1; + rightTop.y = leftTop.y; + rightBottom.x = rightTop.x; + rightBottom.y = leftBottom.y; + break; + } + + case Direction::DIR_TOP_TO_BOTTOM: + case Direction::DIR_BOTTOM_TO_TOP: { + leftTop.x = rect.GetLeft() + radius - 1; + leftTop.y = rect.GetTop() - 1; + rightTop.x = rect.GetRight() - radius + 1; + rightTop.y = leftTop.y; + leftBottom.x = leftTop.x; + leftBottom.y = rect.GetBottom() + 1; + rightBottom.x = rightTop.x; + rightBottom.y = leftBottom.y; + break; + } + default: + GRAPHIC_LOGE("UIBoxProgress: DrawRoundCap direction Err!\n"); + break; + } + + Style capStyle = style; + capStyle.lineWidth_ = radius; + capStyle.lineColor_ = style.bgColor_; + capStyle.lineOpa_ = style.bgOpa_; + ArcInfo arcInfo = {{0}}; + arcInfo.radius = radius; + arcInfo.imgPos = imgPos; + arcInfo.imgSrc = image; + + arcInfo.center = leftTop; + arcInfo.startAngle = THREE_QUARTER_IN_DEGREE; + arcInfo.endAngle = 0; + DrawArc::GetInstance()->Draw(arcInfo, invalidatedArea, capStyle, opaScale_, CapType::CAP_NONE); + + arcInfo.center = leftBottom; + arcInfo.startAngle = SEMICIRCLE_IN_DEGREE; + arcInfo.endAngle = THREE_QUARTER_IN_DEGREE; + DrawArc::GetInstance()->Draw(arcInfo, invalidatedArea, capStyle, opaScale_, CapType::CAP_NONE); + + arcInfo.center = rightTop; + arcInfo.startAngle = 0; + arcInfo.endAngle = THREE_QUARTER_IN_DEGREE; + DrawArc::GetInstance()->Draw(arcInfo, invalidatedArea, capStyle, opaScale_, CapType::CAP_NONE); + + arcInfo.center = rightBottom; + arcInfo.startAngle = THREE_QUARTER_IN_DEGREE; + arcInfo.endAngle = SEMICIRCLE_IN_DEGREE; + DrawArc::GetInstance()->Draw(arcInfo, invalidatedArea, capStyle, opaScale_, CapType::CAP_NONE); +} + +void UIBoxProgress::GetBackgroundParam(Point& startPoint, int16_t& width, int16_t& height, uint16_t& radius, + const Style& style) +{ + Rect rect = GetOrigRect(); + startPoint.x = rect.GetLeft() + style_->borderWidth_ + style_->paddingLeft_; + startPoint.y = rect.GetTop() + style_->borderWidth_ + style_->paddingTop_; + + radius = 0; + width = progressWidth_; + height = progressHeight_; + if (style.lineCap_ == CapType::CAP_ROUND) { + switch (direction_) { + case Direction::DIR_LEFT_TO_RIGHT: + case Direction::DIR_RIGHT_TO_LEFT: + radius = (progressHeight_ + 1) >> 1; + width -= radius << 1; + startPoint.x += radius; + break; + case Direction::DIR_TOP_TO_BOTTOM: + case Direction::DIR_BOTTOM_TO_TOP: + radius = (progressWidth_ + 1) >> 1; + height -= radius << 1; + startPoint.y += radius; + break; + default: + GRAPHIC_LOGE("UIBoxProgress: GetBackgroundParam direction Err!\n"); + return; + } + } +} + +void UIBoxProgress::DrawBackground(const Rect& invalidatedArea) +{ + Point startPoint; + int16_t progressWidth; + int16_t progressHeight; + uint16_t radius; + GetBackgroundParam(startPoint, progressWidth, progressHeight, radius, *backgroundStyle_); + + Rect coords(startPoint.x, startPoint.y, startPoint.x + progressWidth - 1, startPoint.y + progressHeight - 1); + + DrawValidRect(backgroundImage_, coords, invalidatedArea, *backgroundStyle_, radius); +} + +void UIBoxProgress::DrawForeground(const Rect& invalidatedArea, Rect& coords) +{ + Point startPoint; + int16_t progressWidth; + int16_t progressHeight; + uint16_t radius; + GetBackgroundParam(startPoint, progressWidth, progressHeight, radius, *foregroundStyle_); + int16_t length; + + switch (direction_) { + case Direction::DIR_LEFT_TO_RIGHT: { + length = GetCurrentPos(progressWidth - 1); + coords.SetRect(startPoint.x, startPoint.y, startPoint.x + length, startPoint.y + progressHeight - 1); + break; + } + case Direction::DIR_RIGHT_TO_LEFT: { + length = GetCurrentPos(progressWidth - 1); + coords.SetRect(startPoint.x + progressWidth - 1 - length, + startPoint.y, startPoint.x + progressWidth - 1, startPoint.y + progressHeight - 1); + break; + } + case Direction::DIR_TOP_TO_BOTTOM: { + length = GetCurrentPos(progressHeight - 1); + coords.SetRect(startPoint.x, startPoint.y, startPoint.x + progressWidth - 1, startPoint.y + length); + break; + } + case Direction::DIR_BOTTOM_TO_TOP: { + length = GetCurrentPos(progressHeight - 1); + coords.SetRect(startPoint.x, startPoint.y + progressHeight - 1 - length, + startPoint.x + progressWidth - 1, startPoint.y + progressHeight - 1); + break; + } + default: { + GRAPHIC_LOGE("UIBoxProgress: DrawForeground direction Err!\n"); + return; + } + } + + DrawValidRect(foregroundImage_, coords, invalidatedArea, *foregroundStyle_, radius); +} + +void UIBoxProgress::OnDraw(const Rect& invalidatedArea) +{ + DrawRect::Draw(GetOrigRect(), invalidatedArea, *style_, opaScale_); + Rect trunc(invalidatedArea); + if (trunc.Intersect(trunc, GetOrigRect())) { + if (enableBackground_) { + DrawBackground(trunc); + } + + if ((lastValue_ - rangeMin_ != 0) || (foregroundStyle_->lineCap_ == CapType::CAP_ROUND)) { + Rect coords; + DrawForeground(trunc, coords); + } + } +} +} // namespace OHOS \ No newline at end of file diff --git a/frameworks/components/ui_button.cpp b/frameworks/components/ui_button.cpp new file mode 100755 index 0000000..18d9d31 --- /dev/null +++ b/frameworks/components/ui_button.cpp @@ -0,0 +1,278 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "components/ui_button.h" +#include "common/image.h" +#include "draw/draw_image.h" +#include "draw/draw_rect.h" +#include "graphic_log.h" +#include "imgdecode/cache_manager.h" +#include "style.h" +#include "themes/theme_manager.h" + +namespace OHOS { +UIButton::UIButton() + : defaultImgSrc_(nullptr), + triggeredImgSrc_(nullptr), + currentImgSrc_(ButtonImageSrc::BTN_IMAGE_DEFAULT), + imgX_(0), + imgY_(0), + contentWidth_(0), + contentHeight_(0), + state_(RELEASED), + styleState_(RELEASED), + buttonStyleAllocFlag_(false) +{ + touchable_ = true; + SetupThemeStyles(); +} + +UIButton::~UIButton() +{ + if (defaultImgSrc_ != nullptr) { + delete defaultImgSrc_; + defaultImgSrc_ = nullptr; + } + + if (triggeredImgSrc_ != nullptr) { + delete triggeredImgSrc_; + triggeredImgSrc_ = nullptr; + } + + if (buttonStyleAllocFlag_) { + for (uint8_t i = 0; i < BTN_STATE_NUM; i++) { + delete buttonStyles_[i]; + buttonStyles_[i] = nullptr; + } + buttonStyleAllocFlag_ = false; + } +} + +void UIButton::DrawImg(const Rect& invalidatedArea, OpacityType opaScale) +{ + const Image* image = GetCurImageSrc(); + if (image == nullptr) { + return; + } + + ImageHeader header = {0}; + image->GetHeader(header); + Rect coords; + Rect viewRect = GetContentRect(); + coords.SetLeft(viewRect.GetLeft() + GetImageX()); + coords.SetTop(viewRect.GetTop() + GetImageY()); + coords.SetWidth(header.width); + coords.SetHeight(header.height); + + Rect trunc(invalidatedArea); + if (trunc.Intersect(trunc, viewRect)) { + image->DrawImage(coords, trunc, *buttonStyles_[state_], opaScale); + } +} + +void UIButton::OnDraw(const Rect& invalidatedArea) +{ + OpacityType opa = GetMixOpaScale(); + DrawRect::Draw(GetOrigRect(), invalidatedArea, *buttonStyles_[state_], opa); + DrawImg(invalidatedArea, opa); +} + +void UIButton::SetupThemeStyles() +{ + Theme* theme = ThemeManager::GetInstance().GetCurrent(); + + if (theme == nullptr) { + buttonStyles_[RELEASED] = &(StyleDefault::GetButtonReleasedStyle()); + buttonStyles_[PRESSED] = &(StyleDefault::GetButtonPressedStyle()); + buttonStyles_[INACTIVE] = &(StyleDefault::GetButtonInactiveStyle()); + } else { + buttonStyles_[RELEASED] = &(theme->GetButtonStyle().released); + buttonStyles_[PRESSED] = &(theme->GetButtonStyle().pressed); + buttonStyles_[INACTIVE] = &(theme->GetButtonStyle().inactive); + } +} + +int64_t UIButton::GetStyle(uint8_t key) const +{ + return GetStyleForState(key, styleState_); +} + +void UIButton::SetStyle(uint8_t key, int64_t value) +{ + SetStyleForState(key, value, styleState_); +} + +int64_t UIButton::GetStyleForState(uint8_t key, ButtonState state) const +{ + if (state < BTN_STATE_NUM) { + return (buttonStyles_[state])->GetStyle(key); + } + return 0; +} + +void UIButton::SetStyleForState(uint8_t key, int64_t value, ButtonState state) +{ + if (state < BTN_STATE_NUM) { + if (!buttonStyleAllocFlag_) { + for (uint8_t i = 0; i < BTN_STATE_NUM; i++) { + Style styleSaved = *buttonStyles_[i]; + buttonStyles_[i] = new Style; + if (buttonStyles_[i] == nullptr) { + GRAPHIC_LOGE("new Style fail"); + return; + } + *(buttonStyles_[i]) = styleSaved; + } + buttonStyleAllocFlag_ = true; + } + int16_t width = GetWidth(); + int16_t height = GetHeight(); + buttonStyles_[state]->SetStyle(key, value); + switch (key) { + case STYLE_BORDER_WIDTH: { + SetWidth(width); + SetHeight(height); + break; + } + case STYLE_PADDING_LEFT: + case STYLE_PADDING_RIGHT: { + SetWidth(width); + break; + } + case STYLE_PADDING_TOP: + case STYLE_PADDING_BOTTOM: { + SetHeight(height); + break; + } + default: + break; + } + } +} + +bool UIButton::OnPressEvent(const PressEvent& event) +{ + currentImgSrc_ = ButtonImageSrc::BTN_IMAGE_TRIGGERED; + SetState(PRESSED); + Resize(contentWidth_, contentHeight_); + Invalidate(); + return UIView::OnPressEvent(event); +} + +bool UIButton::OnReleaseEvent(const ReleaseEvent& event) +{ + currentImgSrc_ = ButtonImageSrc::BTN_IMAGE_DEFAULT; + SetState(RELEASED); + Resize(contentWidth_, contentHeight_); + Invalidate(); + return UIView::OnReleaseEvent(event); +} + +bool UIButton::OnCancelEvent(const CancelEvent& event) +{ + currentImgSrc_ = ButtonImageSrc::BTN_IMAGE_DEFAULT; + SetState(RELEASED); + Resize(contentWidth_, contentHeight_); + Invalidate(); + return UIView::OnCancelEvent(event); +} + +const Image* UIButton::GetCurImageSrc() const +{ + if (currentImgSrc_ == ButtonImageSrc::BTN_IMAGE_DEFAULT) { + return defaultImgSrc_; + } else if (currentImgSrc_ == ButtonImageSrc::BTN_IMAGE_TRIGGERED) { + return triggeredImgSrc_; + } else { + return nullptr; + } +} + +void UIButton::SetImageSrc(const char* defaultImgSrc, const char* triggeredImgSrc) +{ + if (!InitImage()) { + return; + } + defaultImgSrc_->SetSrc(defaultImgSrc); + triggeredImgSrc_->SetSrc(triggeredImgSrc); +} + +void UIButton::SetImageSrc(const ImageInfo* defaultImgSrc, const ImageInfo* triggeredImgSrc) +{ + if (!InitImage()) { + return; + } + defaultImgSrc_->SetSrc(defaultImgSrc); + triggeredImgSrc_->SetSrc(triggeredImgSrc); +} + +void UIButton::Disable() +{ + SetState(INACTIVE); + touchable_ = false; +} + +void UIButton::Enable() +{ + SetState(RELEASED); + touchable_ = true; +} + +void UIButton::SetState(ButtonState state) +{ + state_ = state; + Invalidate(); +} + +bool UIButton::InitImage() +{ + if (defaultImgSrc_ == nullptr) { + defaultImgSrc_ = new Image(); + if (defaultImgSrc_ == nullptr) { + GRAPHIC_LOGE("new Image fail"); + return false; + } + } + if (triggeredImgSrc_ == nullptr) { + triggeredImgSrc_ = new Image(); + if (triggeredImgSrc_ == nullptr) { + GRAPHIC_LOGE("new Image fail"); + return false; + } + } + return true; +} + +bool UIButton::OnPreDraw(Rect& invalidatedArea) const +{ + Rect rect(GetRect()); + int16_t r = buttonStyles_[styleState_]->borderRadius_; + if (r == COORD_MAX) { + return true; + } + if (r != 0) { + r = ((r & 0x1) == 0) ? (r >> 1) : ((r + 1) >> 1); + rect.SetLeft(rect.GetX() + r); + rect.SetWidth(rect.GetWidth() - r); + rect.SetTop(rect.GetY() + r); + rect.SetHeight(rect.GetHeight() - r); + } + if (rect.IsContains(invalidatedArea)) { + return true; + } + invalidatedArea.Intersect(invalidatedArea, rect); + return false; +} +} // namespace OHOS \ No newline at end of file diff --git a/frameworks/components/ui_canvas.cpp b/frameworks/components/ui_canvas.cpp new file mode 100755 index 0000000..e7b52b8 --- /dev/null +++ b/frameworks/components/ui_canvas.cpp @@ -0,0 +1,807 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "components/ui_canvas.h" +#include "common/image.h" +#include "draw/draw_arc.h" +#include "draw/draw_curve.h" +#include "draw/draw_image.h" +#include "draw/draw_line.h" +#include "draw/draw_rect.h" +#include "graphic_log.h" + +namespace OHOS { +UICanvas::UICanvasPath::~UICanvasPath() +{ + points_.Clear(); + cmd_.Clear(); + arcParam_.Clear(); +} + +void UICanvas::BeginPath() +{ + /* If the previous path is not added to the drawing linked list, it should be destroyed directly. */ + if (path_ != nullptr && path_->strokeCount_ == 0) { + delete path_; + path_ = nullptr; + } + + path_ = new UICanvasPath(); + if (path_ == nullptr) { + GRAPHIC_LOGE("new UICanvasPath fail"); + return; + } +} + +void UICanvas::MoveTo(const Point& point) +{ + if (path_ == nullptr) { + return; + } + + path_->startPos_ = point; + /* If the previous command is also CMD_MOVE_TO, the previous command is overwritten. */ + if ((path_->cmd_.Size() != 0) && (path_->cmd_.Tail()->data_ == CMD_MOVE_TO)) { + path_->points_.Tail()->data_ = point; + return; + } + path_->points_.PushBack(point); + path_->cmd_.PushBack(CMD_MOVE_TO); +} + +void UICanvas::LineTo(const Point& point) +{ + if (path_ == nullptr) { + return; + } + + path_->points_.PushBack(point); + if (path_->cmd_.Size() == 0) { + path_->startPos_ = point; + path_->cmd_.PushBack(CMD_MOVE_TO); + } else { + path_->cmd_.PushBack(CMD_LINE_TO); + } +} + +void UICanvas::ArcTo(const Point& center, uint16_t radius, int16_t startAngle, int16_t endAngle) +{ + if (path_ == nullptr) { + return; + } + + /* + * If there is no command before CMD_ARC, only the arc is drawn. If there is a command in front of + * CMD_ARC, the start point of arc must be connected to the end point of the path. + */ + float sinma = radius * Sin(startAngle); + float cosma = radius * Sin(QUARTER_IN_DEGREE - startAngle); + if (path_->cmd_.Size() != 0) { + path_->points_.PushBack({MATH_ROUND(center.x + sinma), MATH_ROUND(center.y - cosma)}); + path_->cmd_.PushBack(CMD_LINE_TO); + } else { + path_->startPos_ = {MATH_ROUND(center.x + sinma), MATH_ROUND(center.y - cosma)}; + } + + /* If the ARC scan range exceeds 360 degrees, the end point of the path is the position of the start angle. */ + if (MATH_ABS(startAngle - endAngle) < CIRCLE_IN_DEGREE) { + sinma = radius * Sin(endAngle); + cosma = radius * Sin(QUARTER_IN_DEGREE - endAngle); + } + path_->points_.PushBack({MATH_ROUND(center.x + sinma), MATH_ROUND(center.y - cosma)}); + path_->cmd_.PushBack(CMD_ARC); + + int16_t start; + int16_t end; + if (startAngle > endAngle) { + start = endAngle; + end = startAngle; + } else { + start = startAngle; + end = endAngle; + } + + DrawArc::GetInstance()->GetDrawRange(start, end); + ArcParam param; + param.center = center; + param.radius = radius; + param.startAngle = start; + param.endAngle = end; + path_->arcParam_.PushBack(param); +} + +void UICanvas::AddRect(const Point& point, int16_t height, int16_t width) +{ + if (path_ == nullptr) { + return; + } + + MoveTo(point); + LineTo({static_cast(point.x + width), point.y}); + LineTo({static_cast(point.x + width), static_cast(point.y + height)}); + LineTo({point.x, static_cast(point.y + height)}); + ClosePath(); +} + +void UICanvas::ClosePath() +{ + if ((path_ == nullptr) || (path_->cmd_.Size() == 0)) { + return; + } + + path_->points_.PushBack(path_->startPos_); + path_->cmd_.PushBack(CMD_CLOSE); +} + +UICanvas::~UICanvas() +{ + if ((path_ != nullptr) && (path_->strokeCount_ == 0)) { + delete path_; + path_ = nullptr; + } + + void* param = nullptr; + ListNode* curDraw = drawCmdList_.Begin(); + for (; curDraw != drawCmdList_.End(); curDraw = curDraw->next_) { + param = curDraw->data_.param; + curDraw->data_.DeleteParam(param); + curDraw->data_.param = nullptr; + } + drawCmdList_.Clear(); +} + +void UICanvas::Clear() +{ + if ((path_ != nullptr) && (path_->strokeCount_ == 0)) { + delete path_; + path_ = nullptr; + } + + void* param = nullptr; + ListNode* curDraw = drawCmdList_.Begin(); + for (; curDraw != drawCmdList_.End(); curDraw = curDraw->next_) { + param = curDraw->data_.param; + curDraw->data_.DeleteParam(param); + curDraw->data_.param = nullptr; + } + drawCmdList_.Clear(); + Invalidate(); +} + +void UICanvas::DrawLine(const Point& endPoint, const Paint& paint) +{ + DrawLine(startPoint_, endPoint, paint); +} + +void UICanvas::DrawLine(const Point& startPoint, const Point& endPoint, const Paint& paint) +{ + LineParam* lineParam = new LineParam; + if (lineParam == nullptr) { + GRAPHIC_LOGE("new LineParam fail"); + return; + } + lineParam->start = startPoint; + lineParam->end = endPoint; + + DrawCmd cmd; + cmd.paint = paint; + cmd.param = lineParam; + cmd.DeleteParam = DeleteLineParam; + cmd.DrawGraphics = DoDrawLine; + drawCmdList_.PushBack(cmd); + + Invalidate(); + SetStartPosition(endPoint); +} + +void UICanvas::DrawCurve(const Point& control1, const Point& control2, const Point& endPoint, const Paint& paint) +{ + DrawCurve(startPoint_, control1, control2, endPoint, paint); +} + +void UICanvas::DrawCurve(const Point& startPoint, + const Point& control1, + const Point& control2, + const Point& endPoint, + const Paint& paint) +{ + CurveParam* curveParam = new CurveParam; + if (curveParam == nullptr) { + GRAPHIC_LOGE("new CurveParam fail"); + return; + } + curveParam->start = startPoint; + curveParam->control1 = control1; + curveParam->control2 = control2; + curveParam->end = endPoint; + + DrawCmd cmd; + cmd.paint = paint; + if (paint.GetStrokeWidth() > MAX_CURVE_WIDTH) { + cmd.paint.SetStrokeWidth(MAX_CURVE_WIDTH); + } + cmd.param = curveParam; + cmd.DeleteParam = DeleteCurveParam; + cmd.DrawGraphics = DoDrawCurve; + drawCmdList_.PushBack(cmd); + + Invalidate(); + SetStartPosition(endPoint); +} + +void UICanvas::DrawRect(const Point& startPoint, int16_t height, int16_t width, const Paint& paint) +{ + if (static_cast(paint.GetStyle()) & Paint::PaintStyle::STROKE_STYLE) { + RectParam* rectParam = new RectParam; + if (rectParam == nullptr) { + GRAPHIC_LOGE("new RectParam fail"); + return; + } + rectParam->start = startPoint; + rectParam->height = height; + rectParam->width = width; + + DrawCmd cmd; + cmd.paint = paint; + cmd.param = rectParam; + cmd.DeleteParam = DeleteRectParam; + cmd.DrawGraphics = DoDrawRect; + drawCmdList_.PushBack(cmd); + } + + if (static_cast(paint.GetStyle()) & Paint::PaintStyle::FILL_STYLE) { + RectParam* rectParam = new RectParam; + if (rectParam == nullptr) { + GRAPHIC_LOGE("new RectParam fail"); + return; + } + rectParam->start = startPoint; + rectParam->height = height; + rectParam->width = width; + + DrawCmd cmd; + cmd.paint = paint; + cmd.param = rectParam; + cmd.DeleteParam = DeleteRectParam; + cmd.DrawGraphics = DoFillRect; + drawCmdList_.PushBack(cmd); + } + + Invalidate(); +} + +void UICanvas::DrawCircle(const Point& center, uint16_t radius, const Paint& paint) +{ + CircleParam* circleParam = new CircleParam; + if (circleParam == nullptr) { + GRAPHIC_LOGE("new CircleParam fail"); + return; + } + circleParam->center = center; + circleParam->radius = radius; + + DrawCmd cmd; + cmd.paint = paint; + cmd.param = circleParam; + cmd.DeleteParam = DeleteCircleParam; + cmd.DrawGraphics = DoDrawCircle; + drawCmdList_.PushBack(cmd); + + Invalidate(); +} + +void UICanvas::DrawSector(const Point& center, + uint16_t radius, + int16_t startAngle, + int16_t endAngle, + const Paint& paint) +{ + if (static_cast(paint.GetStyle()) & Paint::PaintStyle::FILL_STYLE) { + Paint innerPaint = paint; + innerPaint.SetStyle(Paint::PaintStyle::STROKE_STYLE); + innerPaint.SetStrokeWidth(radius); + innerPaint.SetStrokeColor(paint.GetFillColor()); + radius >>= 1; + DrawArc(center, radius, startAngle, endAngle, innerPaint); + } +} + +void UICanvas::DrawArc(const Point& center, uint16_t radius, int16_t startAngle, int16_t endAngle, const Paint& paint) +{ + if (static_cast(paint.GetStyle()) & Paint::PaintStyle::STROKE_STYLE) { + ArcParam* arcParam = new ArcParam; + if (arcParam == nullptr) { + GRAPHIC_LOGE("new ArcParam fail"); + return; + } + arcParam->center = center; + arcParam->radius = radius; + + int16_t start; + int16_t end; + if (startAngle > endAngle) { + start = endAngle; + end = startAngle; + } else { + start = startAngle; + end = endAngle; + } + + DrawArc::GetInstance()->GetDrawRange(start, end); + arcParam->startAngle = start; + arcParam->endAngle = end; + + DrawCmd cmd; + cmd.paint = paint; + cmd.param = arcParam; + cmd.DeleteParam = DeleteArcParam; + cmd.DrawGraphics = DoDrawArc; + drawCmdList_.PushBack(cmd); + + Invalidate(); + } +} + +void UICanvas::DrawLabel(const Point& startPoint, + const char* text, + uint16_t maxWidth, + const FontStyle& fontStyle, + const Paint& paint) +{ + if (text == nullptr) { + return; + } + if (static_cast(paint.GetStyle()) & Paint::PaintStyle::FILL_STYLE) { + UILabel* label = new UILabel(); + if (label == nullptr) { + GRAPHIC_LOGE("new UILabel fail"); + return; + } + label->SetLineBreakMode(UILabel::LINE_BREAK_CLIP); + label->SetPosition(startPoint.x, startPoint.y); + label->SetWidth(maxWidth); + label->SetHeight(GetHeight()); + label->SetText(text); + label->SetFont(fontStyle.fontName, fontStyle.fontSize); + label->SetAlign(fontStyle.align); + label->SetDirect(fontStyle.direct); + label->SetStyle(STYLE_LETTER_SPACE, fontStyle.letterSpace); + label->SetStyle(STYLE_TEXT_COLOR, paint.GetFillColor().full); + label->SetStyle(STYLE_TEXT_OPA, paint.GetOpacity()); + + DrawCmd cmd; + cmd.param = label; + cmd.DeleteParam = DeleteLabel; + cmd.DrawGraphics = DoDrawLabel; + drawCmdList_.PushBack(cmd); + + Invalidate(); + } +} + +void UICanvas::DrawImage(const Point& startPoint, const char* image, const Paint& paint) +{ + if (image == nullptr) { + return; + } + + ImageParam* imageParam = new ImageParam; + if (imageParam == nullptr) { + GRAPHIC_LOGE("new ImageParam fail"); + return; + } + imageParam->image = new Image(); + if (imageParam->image == nullptr) { + delete imageParam; + imageParam = nullptr; + return; + } + + imageParam->image->SetSrc(image); + ImageHeader header = {0}; + imageParam->image->GetHeader(header); + imageParam->start = startPoint; + imageParam->height = header.height; + imageParam->width = header.width; + + DrawCmd cmd; + cmd.paint = paint; + cmd.param = imageParam; + cmd.DeleteParam = DeleteImageParam; + cmd.DrawGraphics = DoDrawImage; + drawCmdList_.PushBack(cmd); + + Invalidate(); +} + +void UICanvas::DrawPath(const Paint& paint) +{ + if ((path_ == nullptr) || (path_->cmd_.Size() == 0)) { + return; + } + + path_->strokeCount_++; + PathParam* param = new PathParam; + if (param == nullptr) { + GRAPHIC_LOGE("new PathParam fail"); + return; + } + param->path = path_; + param->count = path_->cmd_.Size(); + + DrawCmd cmd; + cmd.paint = paint; + cmd.param = param; + cmd.DeleteParam = DeletePathParam; + cmd.DrawGraphics = DoDrawPath; + drawCmdList_.PushBack(cmd); + Invalidate(); +} + +void UICanvas::OnDraw(const Rect& invalidatedArea) +{ + Rect rect = GetOrigRect(); + DrawRect::Draw(rect, invalidatedArea, *style_, opaScale_); + + void* param = nullptr; + ListNode* curDraw = drawCmdList_.Begin(); + Rect coords = GetOrigRect(); + Rect trunc = invalidatedArea; + if (trunc.Intersect(trunc, coords)) { + for (; curDraw != drawCmdList_.End(); curDraw = curDraw->next_) { + param = curDraw->data_.param; + curDraw->data_.DrawGraphics(param, curDraw->data_.paint, rect, trunc, *style_); + } + } +} + +void UICanvas::GetAbsolutePosition(const Point& prePoint, const Rect& rect, const Style& style, Point& point) +{ + point.x = prePoint.x + rect.GetLeft() + style.paddingLeft_ + style.borderWidth_; + point.y = prePoint.y + rect.GetTop() + style.paddingTop_ + style.borderWidth_; +} + +void UICanvas::DoDrawLine(void* param, + const Paint& paint, + const Rect& rect, + const Rect& invalidatedArea, + const Style& style) +{ + if (param == nullptr) { + return; + } + LineParam* lineParam = static_cast(param); + Point start; + Point end; + GetAbsolutePosition(lineParam->start, rect, style, start); + GetAbsolutePosition(lineParam->end, rect, style, end); + + DrawLine::Draw(start, end, invalidatedArea, paint.GetStrokeWidth(), paint.GetStrokeColor(), paint.GetOpacity()); +} + +void UICanvas::DoDrawCurve(void* param, + const Paint& paint, + const Rect& rect, + const Rect& invalidatedArea, + const Style& style) +{ + if (param == nullptr) { + return; + } + CurveParam* curveParam = static_cast(param); + Point start; + Point end; + Point control1; + Point control2; + GetAbsolutePosition(curveParam->start, rect, style, start); + GetAbsolutePosition(curveParam->end, rect, style, end); + GetAbsolutePosition(curveParam->control1, rect, style, control1); + GetAbsolutePosition(curveParam->control2, rect, style, control2); + + DrawCurve::DrawCubicBezier(start, control1, control2, end, invalidatedArea, paint.GetStrokeWidth(), + paint.GetStrokeColor(), paint.GetOpacity()); +} + +void UICanvas::DoDrawRect(void* param, + const Paint& paint, + const Rect& rect, + const Rect& invalidatedArea, + const Style& style) +{ + if (param == nullptr) { + return; + } + RectParam* rectParam = static_cast(param); + Style drawStyle = StyleDefault::GetDefaultStyle(); + drawStyle.bgColor_ = paint.GetStrokeColor(); + drawStyle.bgOpa_ = paint.GetOpacity(); + drawStyle.borderRadius_ = 0; + + int16_t lineWidth = static_cast(paint.GetStrokeWidth()); + Point start; + GetAbsolutePosition(rectParam->start, rect, style, start); + + int16_t x = start.x - lineWidth / 2; // 2: half + int16_t y = start.y - lineWidth / 2; // 2: half + Rect coords; + if ((rectParam->height <= lineWidth) || (rectParam->width <= lineWidth)) { + coords.SetPosition(x, y); + coords.SetHeight(rectParam->height + lineWidth); + coords.SetWidth(rectParam->width + lineWidth); + DrawRect::Draw(coords, invalidatedArea, drawStyle, OPA_OPAQUE); + return; + } + + coords.SetPosition(x, y); + coords.SetHeight(lineWidth); + coords.SetWidth(rectParam->width); + DrawRect::Draw(coords, invalidatedArea, drawStyle, OPA_OPAQUE); + + coords.SetPosition(x + rectParam->width, y); + coords.SetHeight(rectParam->height); + coords.SetWidth(lineWidth); + DrawRect::Draw(coords, invalidatedArea, drawStyle, OPA_OPAQUE); + + coords.SetPosition(x, y + lineWidth); + coords.SetHeight(rectParam->height); + coords.SetWidth(lineWidth); + DrawRect::Draw(coords, invalidatedArea, drawStyle, OPA_OPAQUE); + + coords.SetPosition(x + lineWidth, y + rectParam->height); + coords.SetHeight(lineWidth); + coords.SetWidth(rectParam->width); + DrawRect::Draw(coords, invalidatedArea, drawStyle, OPA_OPAQUE); +} + +void UICanvas::DoFillRect(void* param, + const Paint& paint, + const Rect& rect, + const Rect& invalidatedArea, + const Style& style) +{ + if (param == nullptr) { + return; + } + RectParam* rectParam = static_cast(param); + uint8_t enableStroke = static_cast(paint.GetStyle()) & Paint::PaintStyle::STROKE_STYLE; + int16_t lineWidth = enableStroke ? paint.GetStrokeWidth() : 0; + if ((rectParam->height <= lineWidth) || (rectParam->width <= lineWidth)) { + return; + } + Point start; + GetAbsolutePosition(rectParam->start, rect, style, start); + + Rect coords; + coords.SetPosition(start.x + (lineWidth + 1) / 2, start.y + (lineWidth + 1) / 2); // 2: half + coords.SetHeight(rectParam->height - lineWidth); + coords.SetWidth(rectParam->width - lineWidth); + + Style drawStyle = StyleDefault::GetDefaultStyle(); + drawStyle.bgColor_ = paint.GetFillColor(); + drawStyle.bgOpa_ = paint.GetOpacity(); + drawStyle.borderRadius_ = 0; + DrawRect::Draw(coords, invalidatedArea, drawStyle, OPA_OPAQUE); +} + +void UICanvas::DoDrawCircle(void* param, + const Paint& paint, + const Rect& rect, + const Rect& invalidatedArea, + const Style& style) +{ + if (param == nullptr) { + return; + } + CircleParam* circleParam = static_cast(param); + + Style drawStyle = StyleDefault::GetDefaultStyle(); + drawStyle.lineOpa_ = paint.GetOpacity(); + + ArcInfo arcInfo = {{0}}; + arcInfo.imgPos = Point{0, 0}; + arcInfo.startAngle = 0; + arcInfo.endAngle = CIRCLE_IN_DEGREE; + GetAbsolutePosition(circleParam->center, rect, style, arcInfo.center); + uint8_t enableStroke = static_cast(paint.GetStyle()) & Paint::PaintStyle::STROKE_STYLE; + uint16_t halfLineWidth = enableStroke ? (paint.GetStrokeWidth() >> 1) : 0; + if (static_cast(paint.GetStyle()) & Paint::PaintStyle::FILL_STYLE) { + arcInfo.radius = circleParam->radius - halfLineWidth; + drawStyle.lineWidth_ = arcInfo.radius; + drawStyle.lineColor_ = paint.GetFillColor(); + DrawArc::GetInstance()->Draw(arcInfo, invalidatedArea, drawStyle, OPA_OPAQUE, CapType::CAP_NONE); + } + + if (enableStroke) { + arcInfo.radius = circleParam->radius + halfLineWidth - 1; + drawStyle.lineWidth_ = static_cast(paint.GetStrokeWidth()); + drawStyle.lineColor_ = paint.GetStrokeColor(); + DrawArc::GetInstance()->Draw(arcInfo, invalidatedArea, drawStyle, OPA_OPAQUE, CapType::CAP_NONE); + } +} + +void UICanvas::DoDrawArc(void* param, + const Paint& paint, + const Rect& rect, + const Rect& invalidatedArea, + const Style& style) +{ + if (param == nullptr) { + return; + } + ArcParam* arcParam = static_cast(param); + + ArcInfo arcInfo = {{0}}; + arcInfo.imgPos = Point{0, 0}; + arcInfo.startAngle = arcParam->startAngle; + arcInfo.endAngle = arcParam->endAngle; + Style drawStyle = StyleDefault::GetDefaultStyle(); + drawStyle.lineWidth_ = static_cast(paint.GetStrokeWidth()); + drawStyle.lineColor_ = paint.GetStrokeColor(); + drawStyle.lineOpa_ = paint.GetOpacity(); + arcInfo.radius = arcParam->radius + ((paint.GetStrokeWidth() + 1) >> 1); + + GetAbsolutePosition(arcParam->center, rect, style, arcInfo.center); + DrawArc::GetInstance()->Draw(arcInfo, invalidatedArea, drawStyle, OPA_OPAQUE, CapType::CAP_NONE); +} + +void UICanvas::DoDrawImage(void* param, + const Paint& paint, + const Rect& rect, + const Rect& invalidatedArea, + const Style& style) +{ + if (param == nullptr) { + return; + } + ImageParam* imageParam = static_cast(param); + + if (imageParam->image == nullptr) { + return; + } + + Point start; + GetAbsolutePosition(imageParam->start, rect, style, start); + + Rect cordsTmp; + cordsTmp.SetPosition(start.x, start.y); + cordsTmp.SetHeight(imageParam->height); + cordsTmp.SetWidth(imageParam->width); + DrawImage::DrawCommon(cordsTmp, invalidatedArea, imageParam->image->GetPath(), style, paint.GetOpacity()); +} + +void UICanvas::DoDrawLabel(void* param, + const Paint& paint, + const Rect& rect, + const Rect& invalidatedArea, + const Style& style) +{ + if (param == nullptr) { + return; + } + UILabel* label = static_cast(param); + Point startPos = {label->GetX(), label->GetY()}; + Point start; + GetAbsolutePosition({startPos.x, startPos.y}, rect, style, start); + label->SetPosition(start.x, start.y); + label->OnDraw(invalidatedArea); + label->SetPosition(startPos.x, startPos.y); +} + +void UICanvas::DoDrawLineJoin(const Point& center, const Rect& invalidatedArea, const Paint& paint) +{ + ArcInfo arcinfo = {{0}}; + arcinfo.center = center; + arcinfo.imgPos = Point{0, 0}; + arcinfo.radius = (paint.GetStrokeWidth() + 1) >> 1; + arcinfo.startAngle = 0; + arcinfo.endAngle = CIRCLE_IN_DEGREE; + + Style style; + style.lineColor_ = paint.GetStrokeColor(); + style.lineWidth_ = static_cast(paint.GetStrokeWidth()); + style.lineOpa_ = OPA_OPAQUE; + DrawArc::GetInstance()->Draw(arcinfo, invalidatedArea, style, OPA_OPAQUE, CapType::CAP_NONE); +} + +void UICanvas::DoDrawPath(void* param, + const Paint& paint, + const Rect& rect, + const Rect& invalidatedArea, + const Style& style) +{ + if (param == nullptr) { + return; + } + PathParam* pathParam = static_cast(param); + const UICanvasPath* path = pathParam->path; + if (path == nullptr) { + return; + } + Point pathEnd = {COORD_MIN, COORD_MIN}; + + ListNode* pointIter = path->points_.Begin(); + ListNode* arcIter = path->arcParam_.Begin(); + ListNode* iter = path->cmd_.Begin(); + for (uint16_t i = 0; (i < pathParam->count) && (iter != path->cmd_.End()); i++, iter = iter->next_) { + switch (iter->data_) { + case CMD_MOVE_TO: { + pointIter = pointIter->next_; + break; + } + case CMD_LINE_TO: { + Point start = pointIter->prev_->data_; + Point end = pointIter->data_; + pointIter = pointIter->next_; + if ((start.x == end.x) && (start.y == end.y)) { + break; + } + + GetAbsolutePosition(start, rect, style, start); + GetAbsolutePosition(end, rect, style, end); + DrawLine::Draw(start, end, invalidatedArea, paint.GetStrokeWidth(), paint.GetStrokeColor(), OPA_OPAQUE); + if ((pathEnd.x == start.x) && (pathEnd.y == start.y)) { + DoDrawLineJoin(start, invalidatedArea, paint); + } + pathEnd = end; + break; + } + case CMD_ARC: { + ArcInfo arcInfo = {{0}}; + arcInfo.imgPos = Point{0, 0}; + arcInfo.startAngle = arcIter->data_.startAngle; + arcInfo.endAngle = arcIter->data_.endAngle; + Style drawStyle = StyleDefault::GetDefaultStyle(); + drawStyle.lineWidth_ = static_cast(paint.GetStrokeWidth()); + drawStyle.lineColor_ = paint.GetStrokeColor(); + drawStyle.lineOpa_ = OPA_OPAQUE; + arcInfo.radius = arcIter->data_.radius + ((paint.GetStrokeWidth() + 1) >> 1); + + GetAbsolutePosition(arcIter->data_.center, rect, style, arcInfo.center); + DrawArc::GetInstance()->Draw(arcInfo, invalidatedArea, drawStyle, OPA_OPAQUE, CapType::CAP_NONE); + if (pointIter != path->points_.Begin()) { + DoDrawLineJoin(pathEnd, invalidatedArea, paint); + } + + GetAbsolutePosition(pointIter->data_, rect, style, pathEnd); + pointIter = pointIter->next_; + arcIter = arcIter->next_; + break; + } + case CMD_CLOSE: { + Point start = pointIter->prev_->data_; + Point end = pointIter->data_; + GetAbsolutePosition(start, rect, style, start); + GetAbsolutePosition(end, rect, style, end); + if ((start.x != end.x) || (start.y != end.y)) { + DrawLine::Draw(start, end, invalidatedArea, paint.GetStrokeWidth(), paint.GetStrokeColor(), + OPA_OPAQUE); + if ((pathEnd.x == start.x) && (pathEnd.y == start.y)) { + DoDrawLineJoin(start, invalidatedArea, paint); + } + pathEnd = end; + } + + if ((pathEnd.x == end.x) && (pathEnd.y == end.y)) { + DoDrawLineJoin(end, invalidatedArea, paint); + } + pointIter = pointIter->next_; + break; + } + default: + break; + } + } +} +} // namespace OHOS \ No newline at end of file diff --git a/frameworks/components/ui_chart.cpp b/frameworks/components/ui_chart.cpp new file mode 100755 index 0000000..7542645 --- /dev/null +++ b/frameworks/components/ui_chart.cpp @@ -0,0 +1,832 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "components/ui_chart.h" +#include "draw/draw_arc.h" +#include "draw/draw_line.h" +#include "securec.h" + +namespace OHOS { +UIChart::~UIChart() +{ + if (mixData_ != nullptr) { + UIFree(mixData_); + mixData_ = nullptr; + } + ClearDataSerial(); + Remove(&xAxis_); + Remove(&yAxis_); +} + +void UIChart::SetHeight(int16_t height) +{ + if (GetHeight() == height) { + return; + } + + if (height > 0) { + needRefresh_ = true; + } + + UIViewGroup::SetHeight(height); + xAxis_.SetHeight(height); + xAxis_.UpdateAxis(); + yAxis_.SetHeight(height); + yAxis_.UpdateAxis(); +} + +void UIChart::SetWidth(int16_t width) +{ + UIViewGroup::SetWidth(width); + xAxis_.SetWidth(width); + yAxis_.SetWidth(width); + xAxis_.UpdateAxis(); + yAxis_.UpdateAxis(); +} + +void UIChart::OnDraw(const Rect& invalidatedArea) +{ + UIViewGroup::OnDraw(invalidatedArea); + Rect rect = GetContentRect(); + if (rect.Intersect(rect, invalidatedArea)) { + DrawDataSerials(rect); + } +} + +bool UIChart::AddDataSerial(UIChartDataSerial* dataSerial) +{ + if (dataSerial == nullptr) { + return false; + } + + ListNode* serialNode = list_.Head(); + while (serialNode != list_.End()) { + if (serialNode->data_ == dataSerial) { + return false; + } + serialNode = serialNode->next_; + } + list_.PushBack(dataSerial); + dataSerial->BindToChart(this); + return true; +} + +bool UIChart::DeleteDataSerial(UIChartDataSerial* dataSerial) +{ + if ((dataSerial == nullptr) || list_.IsEmpty()) { + return false; + } + + bool findSerial = false; + ListNode* serialNode = list_.Head(); + while (serialNode != list_.End()) { + if (serialNode->data_ == dataSerial) { + dataSerial->BindToChart(nullptr); + list_.Remove(serialNode); + findSerial = true; + break; + } + serialNode = serialNode->next_; + } + + return findSerial; +} + +void UIChart::ClearDataSerial() +{ + if (list_.IsEmpty()) { + return; + } + + ListNode* serialNode = list_.Head(); + while (serialNode != list_.End()) { + serialNode->data_->BindToChart(nullptr); + ListNode* tempNode = serialNode; + serialNode = serialNode->next_; + list_.Remove(tempNode); + } + list_.Clear(); +} + +UIChartDataSerial::UIChartDataSerial() + : maxCount_(0), + pointArray_(nullptr), + serialColor_(Color::White()), + fillColor_(Color::White()), + dataCount_(0), + peakPointIndex_(0), + peakData_(0), + valleyData_(0), + valleyPointIndex_(0), + lastPointIndex_(0), + latestIndex_(0), + hideIndex_(0), + hideCount_(0), + smooth_(false), + enableGradient_(false), + enableHeadPoint_(false), + enableTopPoint_(false), + enableBottomPoint_(false), + chart_(nullptr), + invalidateRect_(0, 0, 0, 0) +{ + PointStyle style; + style.radius = DEFAULT_POINT_RADIUS; + style.strokeWidth = 1; + style.fillColor = Color::White(); + style.strokeColor = Color::White(); + topPointStyle_ = style; + bottomPointStyle_ = style; + headPointStyle_ = style; +} + +bool UIChartDataSerial::SetMaxDataCount(uint16_t maxCount) +{ + if (maxCount > MAX_POINTS_COUNT) { + maxCount = MAX_POINTS_COUNT; + } + + if (maxCount == maxCount_) { + return true; + } + + if (pointArray_ != nullptr) { + UIFree(pointArray_); + pointArray_ = nullptr; + } + + maxCount_ = maxCount; + if (maxCount_ == 0) { + return true; + } + + pointArray_ = static_cast(UIMalloc(sizeof(Point) * maxCount_)); + if (pointArray_ == nullptr) { + maxCount_ = 0; + return false; + } + return true; +} + +bool UIChartDataSerial::ModifyPoint(uint16_t index, const Point& point) +{ + if ((index >= maxCount_) || (pointArray_ == nullptr)) { + return false; + } + + pointArray_[index].x = point.x; + pointArray_[index].y = point.y; + if (point.y > peakData_) { + if (enableTopPoint_) { + RefreshInvalidateRect(peakPointIndex_, topPointStyle_); + } + peakPointIndex_ = index; + peakData_ = point.y; + } else if (point.y < valleyData_) { + if (enableBottomPoint_) { + RefreshInvalidateRect(valleyPointIndex_, bottomPointStyle_); + } + valleyPointIndex_ = index; + valleyData_ = point.y; + } else if ((index == peakPointIndex_) || (index == valleyPointIndex_)) { + UpdatePeakAndValley(0, dataCount_); + } + + latestIndex_ = index; + uint16_t startIndex = (index == 0) ? index : (index - 1); + RefreshInvalidateRect(startIndex, index + 1); + return true; +} + +bool UIChartDataSerial::GetPoint(uint16_t index, Point& point) +{ + if ((index >= dataCount_) || (pointArray_ == nullptr)) { + return false; + } + point = pointArray_[index]; + if (chart_ != nullptr) { + chart_->GetXAxis().TranslateToPixel(point.x); + chart_->GetYAxis().TranslateToPixel(point.y); + } + return true; +} + +void UIChartDataSerial::HidePoint(uint16_t index, uint16_t count) +{ + hideIndex_ = index; + hideCount_ = count; + RefreshInvalidateRect(hideIndex_, hideIndex_ + hideCount_); +} + +void UIChartDataSerial::RefreshInvalidateRect(uint16_t pointIndex, const PointStyle& style) +{ + Point point; + if (GetPoint(pointIndex, point)) { + uint16_t width = style.radius + style.strokeWidth; + Rect refresh(point.x - width, 0, point.x + width, 0); + if ((invalidateRect_.GetLeft() == 0) && (invalidateRect_.GetRight() == 0)) { + invalidateRect_ = refresh; + } else { + invalidateRect_.Join(invalidateRect_, refresh); + } + } +} + +void UIChartDataSerial::RefreshInvalidateRect(uint16_t startIndex, uint16_t endIndex) +{ + Point start; + GetPoint(startIndex, start); + Point end; + endIndex = (endIndex >= dataCount_) ? (dataCount_ - 1) : endIndex; + GetPoint(endIndex, end); + int16_t xMin = MATH_MIN(start.x, end.x); + int16_t xMax = MATH_MAX(start.x, end.x); + Rect refresh(xMin, 0, xMax, 0); + if ((invalidateRect_.GetLeft() == 0) && (invalidateRect_.GetRight() == 0)) { + invalidateRect_ = refresh; + return; + } + invalidateRect_.Join(invalidateRect_, refresh); +} + +bool UIChartDataSerial::UpdatePeakAndValley(uint16_t startPos, uint16_t endPos) +{ + if ((startPos >= endPos) || (endPos > dataCount_) || (pointArray_ == nullptr)) { + return false; + } + + if (startPos == 0) { + peakData_ = pointArray_[startPos].y; + valleyData_ = pointArray_[startPos].y; + } + + for (uint16_t i = startPos; i < endPos; i++) { + if (pointArray_[i].y > peakData_) { + if (enableTopPoint_) { + RefreshInvalidateRect(peakPointIndex_, topPointStyle_); + RefreshInvalidateRect(i, topPointStyle_); + } + peakPointIndex_ = i; + peakData_ = pointArray_[i].y; + } + + if (pointArray_[i].y < valleyData_) { + if (enableBottomPoint_) { + RefreshInvalidateRect(valleyPointIndex_, bottomPointStyle_); + RefreshInvalidateRect(i, bottomPointStyle_); + } + valleyPointIndex_ = i; + valleyData_ = pointArray_[i].y; + } + } + return true; +} + +bool UIChartDataSerial::AddPoints(const Point* data, uint16_t count) +{ + if ((maxCount_ <= dataCount_) || (count == 0) || (pointArray_ == nullptr) || (data == nullptr)) { + return false; + } + + if (count > (maxCount_ - dataCount_)) { + count = maxCount_ - dataCount_; + } + + Point* current = pointArray_ + dataCount_; + if (memcpy_s(current, (maxCount_ - dataCount_) * sizeof(Point), data, count * sizeof(Point)) != EOK) { + return false; + } + uint16_t i = dataCount_; + dataCount_ += count; + UpdatePeakAndValley(i, dataCount_); + latestIndex_ = dataCount_ - 1; + uint16_t startIndex = (i == 0) ? i : (i - 1); + RefreshInvalidateRect(startIndex, latestIndex_); + return true; +} + +void UIChartDataSerial::ClearData() +{ + RefreshInvalidateRect(0, dataCount_ - 1); + if (pointArray_ != nullptr) { + if (memset_s(pointArray_, maxCount_ * sizeof(Point), 0, maxCount_ * sizeof(Point)) != EOK) { + return; + } + } + dataCount_ = 0; + valleyPointIndex_ = 0; + peakPointIndex_ = 0; + latestIndex_ = 0; +} + +void UIChartDataSerial::DoDrawPoint(const Point& center, const PointStyle& style, const Rect& mask) +{ + Style drawStyle = StyleDefault::GetDefaultStyle(); + drawStyle.lineOpa_ = OPA_OPAQUE; + drawStyle.lineColor_ = style.fillColor; + + ArcInfo arcinfo = {{0}}; + arcinfo.center = center; + arcinfo.imgPos = Point{0, 0}; + arcinfo.radius = style.radius + style.strokeWidth; + arcinfo.startAngle = 0; + arcinfo.endAngle = CIRCLE_IN_DEGREE; + + if (style.fillColor.full == style.strokeColor.full) { + drawStyle.lineWidth_ = style.radius + style.strokeWidth; + DrawArc::GetInstance()->Draw(arcinfo, mask, drawStyle, OPA_OPAQUE, CapType::CAP_NONE); + return; + } + drawStyle.lineWidth_ = style.radius; + arcinfo.radius = style.radius; + DrawArc::GetInstance()->Draw(arcinfo, mask, drawStyle, OPA_OPAQUE, CapType::CAP_NONE); + + drawStyle.lineWidth_ = style.strokeWidth; + drawStyle.lineColor_ = style.strokeColor; + arcinfo.radius = style.radius + style.strokeWidth; + DrawArc::GetInstance()->Draw(arcinfo, mask, drawStyle, OPA_OPAQUE, CapType::CAP_NONE); +} + +void UIChartDataSerial::DrawPoint(const Rect& mask) +{ + Point center; + if (enableTopPoint_) { + if (GetPoint(peakPointIndex_, center)) { + DoDrawPoint(center, topPointStyle_, mask); + } + } + + if (enableBottomPoint_) { + if (GetPoint(valleyPointIndex_, center)) { + DoDrawPoint(center, bottomPointStyle_, mask); + } + } + + if (enableHeadPoint_) { + if (GetPoint(latestIndex_, center)) { + DoDrawPoint(center, headPointStyle_, mask); + lastPointIndex_ = latestIndex_; + } + } +} + +void UIChartDataSerial::Refresh() +{ + if (chart_ != nullptr) { + Rect refresh = chart_->GetContentRect(); + refresh.SetLeft(invalidateRect_.GetLeft() - headPointStyle_.radius - headPointStyle_.strokeWidth); + refresh.SetRight(invalidateRect_.GetRight() + headPointStyle_.radius + headPointStyle_.strokeWidth); + invalidateRect_.SetRect(0, 0, 0, 0); + chart_->InvalidateRect(refresh); + + if (enableHeadPoint_ && (lastPointIndex_ != latestIndex_)) { + RefreshInvalidateRect(lastPointIndex_, headPointStyle_); + refresh.SetLeft(invalidateRect_.GetLeft()); + refresh.SetRight(invalidateRect_.GetRight()); + chart_->InvalidateRect(refresh); + invalidateRect_.SetRect(0, 0, 0, 0); + } + } +} + +void UIChartPillar::RefreshChart() +{ + ListNode* iter = list_.Begin(); + Rect rect = GetContentRect(); + for (; iter != list_.End(); iter = iter->next_) { + UIChartDataSerial* data = iter->data_; + if (data == nullptr) { + break; + } + uint16_t dataCount = data->GetDataCount(); + if (dataCount <= 1) { + break; + } + + uint16_t index = data->GetLastPointIndex(); + if (index >= dataCount) { + break; + } + + Point current; + data->GetPoint(index, current); + Point last; + data->GetPoint(dataCount - 1, last); + Rect refresh(current.x, rect.GetTop(), last.x, rect.GetBottom()); + InvalidateRect(refresh); + data->SetLastPointIndex(dataCount - 1); + } +} + +void UIChartPillar::DrawDataSerials(const Rect& invalidatedArea) +{ + xAxis_.UpdateAxisPoints(); + yAxis_.UpdateAxisPoints(); + uint16_t minXStep = static_cast(xAxis_.GetMarkInterval()); + Point xStart = xAxis_.GetStartPoint(); + uint16_t dataSerialCount = list_.Size(); + if (dataSerialCount == 0) { + return; + } + uint16_t width = minXStep / dataSerialCount; + uint8_t dataSerialIndex = 0; + uint16_t barWidth = static_cast(width - DEFAULT_MARK_PERCENTAGE * (width << 1)); + + for (ListNode* iter = list_.Begin(); iter != list_.End(); iter = iter->next_) { + UIChartDataSerial* data = iter->data_; + uint16_t dataSerialWidth = width * dataSerialIndex; + int16_t x = dataSerialWidth + (width >> 1); + for (uint16_t index = 0; index < data->GetDataCount(); index++) { + Point current; + data->GetPoint(index, current); + if (current.y == xStart.y) { + continue; + } + current.x += x; + xStart.x = current.x; + DrawLine::Draw(current, xStart, invalidatedArea, barWidth, data->GetFillColor(), style_->lineOpa_); + } + dataSerialIndex++; + } +} + +void UIChartPolyline::RefreshChart() +{ + ListNode* iter = list_.Begin(); + for (; iter != list_.End(); iter = iter->next_) { + UIChartDataSerial* data = iter->data_; + uint16_t dataCount = data->GetDataCount(); + if (dataCount == 1) { + break; + } + data->Refresh(); + } +} + +void UIChartPolyline::ReMeasure() +{ + if (!needRefresh_) { + return; + } + needRefresh_ = false; + int16_t height = GetHeight(); + if (mixData_ != nullptr) { + UIFree(mixData_); + mixData_ = nullptr; + } + if (height <= 0) { + return; + } + if (height > COORD_MAX) { + height = COORD_MAX; + } + mixData_ = static_cast(UIMalloc(height)); + if (mixData_ == nullptr) { + return; + } + int16_t opa = maxOpa_ - minOpa_; + for (int16_t y = 0; y < height; y++) { + mixData_[y] = static_cast(y * opa / height + minOpa_); + } +} + +void UIChartPolyline::DrawDataSerials(const Rect& invalidatedArea) +{ + xAxis_.UpdateAxisPoints(); + yAxis_.UpdateAxisPoints(); + ListNode* iter = list_.Begin(); + for (; iter != list_.End(); iter = iter->next_) { + UIChartDataSerial* data = iter->data_; + uint16_t dataCount = data->GetDataCount(); + if (dataCount <= 1) { + continue; + } + if (data->IsGradient()) { + GradientColor(invalidatedArea, data); + } + if (data->GetHideCount() != 0) { + uint16_t hideIndex = data->GetHideIndex(); + DrawPolyLine(0, hideIndex, invalidatedArea, data); + DrawPolyLine(hideIndex + data->GetHideCount(), dataCount - 1, invalidatedArea, data); + } else { + DrawPolyLine(0, dataCount - 1, invalidatedArea, data); + } + + data->DrawPoint(invalidatedArea); + } +} + +void UIChartPolyline::DrawSmoothPolyLine(uint16_t startIndex, + uint16_t endIndex, + const Rect& invalidatedArea, + UIChartDataSerial* data) +{ + if (data == nullptr) { + return; + } + Point start; + Point end; + ColorType color = data->GetLineColor(); + Style style = *style_; + style.lineColor_ = color; + style.lineOpa_ = OPA_OPAQUE; + + uint16_t slope; + data->GetPoint(startIndex, start); + data->GetPoint(startIndex + 1, end); + uint16_t preSlope = (start.x == end.x) ? QUARTER_IN_DEGREE : FastAtan2(end.x - start.x, end.y - start.y); + Point current; + for (uint16_t i = startIndex; i < endIndex; i++) { + data->GetPoint(i + 1, current); + if (((end.y - start.y <= 0) && (current.y - end.y <= 0)) || + ((end.y - start.y >= 0) && (current.y - end.y >= 0))) { + slope = (current.x == start.x) ? QUARTER_IN_DEGREE : FastAtan2(current.x - start.x, current.y - start.y); + if (MATH_ABS(slope - preSlope) < SMOOTH_SLOPE_ANGLE) { + end = current; + continue; + } + } + preSlope = (current.x == end.x) ? QUARTER_IN_DEGREE : FastAtan2(current.x - end.x, current.y - end.y); + Rect rect; + rect.SetLeft(MATH_MIN(start.x, end.x) - style_->lineWidth_); + rect.SetRight(MATH_MAX(start.x, end.x) + style_->lineWidth_); + rect.SetTop(MATH_MIN(start.y, end.y) - style_->lineWidth_); + rect.SetBottom(MATH_MAX(start.y, end.y) + style_->lineWidth_); + if (!invalidatedArea.IsIntersect(rect)) { + start = end; + end = current; + continue; + } + + DrawLine::Draw(start, end, invalidatedArea, style_->lineWidth_, color, OPA_OPAQUE); + ArcInfo arcinfo = {{0}}; + arcinfo.center = end; + arcinfo.imgPos = Point{0, 0}; + arcinfo.radius = (style_->lineWidth_ + 1) >> 1; + arcinfo.startAngle = 0; + arcinfo.endAngle = CIRCLE_IN_DEGREE; + + DrawArc::GetInstance()->Draw(arcinfo, invalidatedArea, style, OPA_OPAQUE, CapType::CAP_NONE); + + start = end; + end = current; + } + DrawLine::Draw(start, end, invalidatedArea, style_->lineWidth_, color, OPA_OPAQUE); +} + +void UIChartPolyline::DrawPolyLine(uint16_t startIndex, + uint16_t endIndex, + const Rect& invalidatedArea, + UIChartDataSerial* data) +{ + if ((startIndex >= endIndex) || (data == nullptr)) { + return; + } + + if (data->IsSmooth()) { + DrawSmoothPolyLine(startIndex, endIndex, invalidatedArea, data); + return; + } + Point start; + Point end; + ColorType color = data->GetLineColor(); + Style style = *style_; + style.lineColor_ = color; + style.lineOpa_ = OPA_OPAQUE; + ArcInfo arcinfo = {{0}}; + arcinfo.imgPos = Point{0, 0}; + arcinfo.radius = (style_->lineWidth_ + 1) >> 1; + arcinfo.startAngle = 0; + arcinfo.endAngle = CIRCLE_IN_DEGREE; + for (uint16_t i = startIndex; i < endIndex - 1; i++) { + data->GetPoint(i, start); + data->GetPoint(i + 1, end); + Rect rect; + rect.SetLeft(MATH_MIN(start.x, end.x) - style_->lineWidth_); + rect.SetRight(MATH_MAX(start.x, end.x) + style_->lineWidth_); + rect.SetTop(MATH_MIN(start.y, end.y) - style_->lineWidth_); + rect.SetBottom(MATH_MAX(start.y, end.y) + style_->lineWidth_); + if (!invalidatedArea.IsIntersect(rect)) { + continue; + } + + DrawLine::Draw(start, end, invalidatedArea, style_->lineWidth_, color, OPA_OPAQUE); + if (style_->lineWidth_ >= LINE_JOIN_WIDTH) { + arcinfo.center = end; + DrawArc::GetInstance()->Draw(arcinfo, invalidatedArea, style, OPA_OPAQUE, CapType::CAP_NONE); + } + } + data->GetPoint(endIndex - 1, start); + data->GetPoint(endIndex, end); + DrawLine::Draw(start, end, invalidatedArea, style_->lineWidth_, color, OPA_OPAQUE); +} + +bool UIChartPolyline::GetLineCrossPoint(const Point& p1, + const Point& p2, + const Point& p3, + const Point& p4, + Point& cross) +{ + /* Rectangular ranges of line segments must intersect. */ + if ((MATH_MIN(p1.x, p2.x) <= MATH_MAX(p3.x, p4.x)) && (MATH_MIN(p3.x, p4.x) <= MATH_MAX(p1.x, p2.x)) && + (MATH_MIN(p1.y, p2.y) <= MATH_MAX(p3.y, p4.y)) && (MATH_MIN(p3.y, p4.y) <= MATH_MAX(p1.y, p2.y))) { + /* Check whether the lines are parallel. If the lines are collinear, there is no intersection point. */ + if ((p4.y - p3.y) * (p2.x - p1.x) - (p4.x - p3.x) * (p2.y - p1.y) != 0) { + /* + * (y1 - y2)x + (x2 - x1)y = x2y1 - x1y2 -> ax + by = c + * (y3 - y4)x + (x4 - x3)y = x4y3 - x3y4 -> dx + ey = f + */ + int64_t a = p1.y - p2.y; + int64_t b = p2.x - p1.x; + int64_t c = p2.x * p1.y - p1.x * p2.y; + int64_t d = p3.y - p4.y; + int64_t e = p4.x - p3.x; + int64_t f = p4.x * p3.y - p3.x * p4.y; + int64_t left = a * e - b * d; + int64_t right = c * e - b * f; + if (left == 0) { + return false; + } + cross.x = static_cast(right / left); + left = b * d - a * e; + right = c * d - a * f; + if (left == 0) { + return false; + } + cross.y = static_cast(right / left); + if ((cross.x >= MATH_MIN(p1.x, p2.x)) && (cross.x <= MATH_MAX(p1.x, p2.x)) && + (cross.x >= MATH_MIN(p3.x, p4.x)) && (cross.x <= MATH_MAX(p3.x, p4.x))) { + return true; + } + } + } + return false; +} + +void UIChartPolyline::FindCrossPoints(const ChartLine& line, const ChartLine& polyLine, CrossPointSet& cross) +{ + if (GetLineCrossPoint(line.start, line.end, polyLine.start, polyLine.end, cross.nextFirst)) { + if (!cross.firstFind) { + /* first corss must on the line like "/" */ + if (polyLine.start.y < polyLine.end.y) { + cross.first = cross.nextFirst; + cross.firstFind = true; + } + } else if (!cross.secondFind) { + /* second corss can't be same with first cross. */ + if ((cross.first.x != cross.nextFirst.x) || (cross.first.y != cross.nextFirst.y)) { + cross.second = cross.nextFirst; + cross.secondFind = true; + return; + } + /* second corss must on the line like "\", otherwise skip those crosss. */ + if (polyLine.start.y > polyLine.end.y) { + cross.firstFind = false; + } + } + } +} + +void UIChartPolyline::DrawGradientColor(const Rect& invalidatedArea, + UIChartDataSerial* data, + const ChartLine& linePoints, + const ChartLine& limitPoints, + int16_t startY) +{ + if (data == nullptr) { + return; + } + Rect currentRect = GetContentRect(); + CrossPointSet cross = {{0}}; + ChartLine polyLine = {{0}}; + uint16_t pointCount = data->GetDataCount() - 1; + int16_t y = enableReverse_ ? (linePoints.start.y + startY) : (startY - linePoints.start.y); + int16_t mixScale = !enableReverse_ ? (currentRect.GetBottom() - y) : (y - currentRect.GetTop()); + if ((mixScale < 0) || (mixScale >= currentRect.GetHeight())) { + return; + } + bool onVerticalLine = enableReverse_ ? (y <= limitPoints.start.y) : (y >= limitPoints.start.y); + if (onVerticalLine) { + cross.first.x = limitPoints.start.x; + cross.first.y = enableReverse_ ? (y - startY) : (startY - y); + cross.firstFind = true; + } + + Point start; + Point end; + for (uint16_t i = 0; i < pointCount; i++) { + data->GetPoint(i, start); + data->GetPoint(i + 1, end); + if (start.y == end.y) { + int16_t tmpY = enableReverse_ ? (start.y + startY) : (startY - start.y); + if (tmpY == linePoints.start.y) { + cross.firstFind = false; + cross.secondFind = false; + } + continue; + } + start.y = enableReverse_ ? (start.y - startY) : (startY - start.y); + end.y = enableReverse_ ? (end.y - startY) : (startY - end.y); + polyLine.start = start; + polyLine.end = end; + FindCrossPoints(linePoints, polyLine, cross); + if (cross.firstFind && cross.secondFind) { + cross.first.y = enableReverse_ ? (cross.first.y + startY) : (startY - cross.first.y); + cross.second.y = enableReverse_ ? (cross.second.y + startY) : (startY - cross.second.y); + DrawLine::Draw(cross.first, cross.second, invalidatedArea, 1, data->GetFillColor(), mixData_[mixScale]); + cross.firstFind = false; + cross.secondFind = false; + } + } + + if (cross.firstFind && !cross.secondFind) { + cross.second.x = limitPoints.end.x; + cross.second.y = y; + cross.first.y = y; + DrawLine::Draw(cross.first, cross.second, invalidatedArea, 1, data->GetFillColor(), mixData_[mixScale]); + } +} + +void UIChartPolyline::CalcVerticalInfo(int16_t top, + int16_t bottom, + int16_t start, + int16_t end, + int16_t& y, + int16_t& yHeight) +{ + if ((top < start) && (bottom > start)) { + y = start; + yHeight = top; + } else if ((bottom <= start) && (top >= end)) { + y = bottom; + yHeight = top; + } else if ((top < end) && (bottom > end)) { + y = bottom; + yHeight = end; + } +} + +void UIChartPolyline::GradientColor(const Rect& invalidatedArea, UIChartDataSerial* data) +{ + if (data == nullptr) { + return; + } + int16_t bottom = invalidatedArea.GetBottom(); + int16_t top = invalidatedArea.GetTop(); + Point yStart = yAxis_.GetStartPoint(); + yStart.y = enableReverse_ ? (yStart.y + gradientBottom_) : (yStart.y - gradientBottom_); + int16_t topY = enableReverse_ ? data->GetValleyData() : data->GetPeakData(); + int16_t bottomY = enableReverse_ ? data->GetPeakData() : data->GetValleyData(); + yAxis_.TranslateToPixel(topY); + yAxis_.TranslateToPixel(bottomY); + int16_t valleyY = enableReverse_ ? topY : bottomY; + int16_t startY = enableReverse_ ? topY : yStart.y; + int16_t endY = enableReverse_ ? yStart.y : topY; + if ((bottom < endY) || (top > startY)) { + return; + } + + int16_t y = 0; + int16_t yHeight = 0; + CalcVerticalInfo(top, bottom, startY, endY, y, yHeight); + + ChartLine limitPoints = {{0}}; + data->GetPoint(0, limitPoints.start); + data->GetPoint(data->GetDataCount() - 1, limitPoints.end); + ChartLine linePoints = {{0}}; + linePoints.start.x = limitPoints.start.x; + linePoints.end.x = limitPoints.end.x; + Rect currentRect = GetContentRect(); + while (y >= yHeight) { + linePoints.start.y = enableReverse_ ? (y - endY) : (startY - y); + linePoints.end.y = linePoints.start.y; + if (y <= valleyY) { + int16_t baseY = enableReverse_ ? endY : startY; + DrawGradientColor(invalidatedArea, data, linePoints, limitPoints, baseY); + } else { + int16_t mixScale = enableReverse_ ? (linePoints.start.y + endY - currentRect.GetTop()) : + (currentRect.GetBottom() - (startY - linePoints.start.y)); + if ((mixScale < 0) || (mixScale >= currentRect.GetHeight())) { + y--; + continue; + } + Point start = {limitPoints.start.x, y}; + Point end = {limitPoints.end.x, y}; + DrawLine::Draw(start, end, invalidatedArea, 1, data->GetFillColor(), mixData_[mixScale]); + } + y--; + } +} +} // namespace OHOS \ No newline at end of file diff --git a/frameworks/components/ui_checkbox.cpp b/frameworks/components/ui_checkbox.cpp new file mode 100755 index 0000000..aed4c14 --- /dev/null +++ b/frameworks/components/ui_checkbox.cpp @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "common/image.h" +#include "components/ui_checkbox.h" +#include "default_resource/check_box_res.h" +#include "draw/draw_image.h" +#include "draw/draw_rect.h" +#include "imgdecode/cache_manager.h" + +namespace OHOS { +UICheckBox::UICheckBox() + : state_(UNSELECTED), onStateChangeListener_(nullptr) +{ + touchable_ = true; + style_ = &(StyleDefault::GetBackgroundTransparentStyle()); + image_[UNSELECTED].SetSrc(GetCheckBoxOffInfo()); + image_[SELECTED].SetSrc(GetCheckBoxOnInfo()); + ImageHeader header = { 0 }; + image_[UNSELECTED].GetHeader(header); + Resize(header.width, header.height); +} + +UICheckBox::~UICheckBox() +{ +} + +void UICheckBox::SetState(UICheckBoxState state) +{ + if (state != state_) { + state_ = state; + if (onStateChangeListener_ != nullptr) { + onStateChangeListener_->OnChange(state); + } + Invalidate(); + } +} + +void UICheckBox::ReverseState() +{ + state_ = (state_ == SELECTED) ? UNSELECTED : SELECTED; +} + +bool UICheckBox::OnClickEvent(const ClickEvent& event) +{ + ReverseState(); + Invalidate(); + return UIView::OnClickEvent(event); +} + +void UICheckBox::SetImages(const char* selectedImageSrc, const char* unselectedImageSrc) +{ + image_[SELECTED].SetSrc(selectedImageSrc); + image_[UNSELECTED].SetSrc(unselectedImageSrc); +} + +void UICheckBox::SetImages(const ImageInfo* selectedImageSrc, const ImageInfo* unselectedImageSrc) +{ + image_[SELECTED].SetSrc(selectedImageSrc); + image_[UNSELECTED].SetSrc(unselectedImageSrc); +} + +void UICheckBox::OnDraw(const Rect& invalidatedArea) +{ + ImageHeader header = {0}; + image_[state_].GetHeader(header); + int16_t imgWidth = header.width; + int16_t imgHeight = header.height; + Rect coords = GetContentRect(); + coords.SetWidth(imgWidth); + coords.SetHeight(imgHeight); + DrawRect::Draw(GetRect(), invalidatedArea, *style_, opaScale_); + + int16_t offsetLeft = (GetWidth() - imgWidth) / 2; // 2 : half + int16_t offsetTop = (GetHeight() - imgHeight) / 2; // 2 : half + coords.SetX(coords.GetX() + offsetLeft); + coords.SetY(coords.GetY() + offsetTop); + Rect trunc = invalidatedArea; + if (trunc.Intersect(trunc, coords)) { + image_[state_].DrawImage(coords, trunc, *style_, opaScale_); + } +} +} // namespace OHOS diff --git a/frameworks/components/ui_circle_progress.cpp b/frameworks/components/ui_circle_progress.cpp new file mode 100755 index 0000000..59a2ec0 --- /dev/null +++ b/frameworks/components/ui_circle_progress.cpp @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "components/ui_circle_progress.h" +#include "draw/draw_arc.h" +#include "draw/draw_line.h" +#include "draw/draw_rect.h" + +namespace OHOS { +UICircleProgress::UICircleProgress() + : center_({0, 0}), + backgroundImagePos_({0, 0}), + progressImagePos_({0, 0}), + radius_(0), + startAngle_(MIN_ANGLE_VALUE), + endAngle_(MAX_ANGLE_VALUE) +{ +} + +void UICircleProgress::SetCenterPosition(int16_t x, int16_t y) +{ + center_.x = x; + center_.y = y; +} + +void UICircleProgress::SetStartAngle(int16_t startAngle) +{ + startAngle_ = startAngle; +} + +void UICircleProgress::SetEndAngle(int16_t endAngle) +{ + endAngle_ = endAngle; +} + +void UICircleProgress::GetStartEndAngle(int16_t& start, int16_t& end) const +{ + if (startAngle_ > endAngle_) { + start = endAngle_; + end = startAngle_; + } else { + start = startAngle_; + end = endAngle_; + } +} + +void UICircleProgress::GetAngleRange(int16_t& start, int16_t& end) const +{ + GetStartEndAngle(start, end); + DrawArc::GetInstance()->GetDrawRange(start, end); +} + +void UICircleProgress::GetRedrawAngle(int16_t& start, int16_t& end) const +{ + GetStartEndAngle(start, end); + + if (startAngle_ == endAngle_) { + return; + } + + int16_t angleRange = end - start; + angleRange = (angleRange > CIRCLE_IN_DEGREE) ? CIRCLE_IN_DEGREE : angleRange; + + int16_t angle = GetCurrentPos(angleRange); + if (startAngle_ > endAngle_) { + start = end - angle; + } else { + end = angle + start; + } + DrawArc::GetInstance()->GetDrawRange(start, end); +} + +void UICircleProgress::DrawCommonCircle(const Rect& invalidatedArea) +{ + ArcInfo arcinfo = {{0}}; + arcinfo.radius = radius_; + int16_t endAngle; + int16_t startAngle; + GetRedrawAngle(startAngle, endAngle); + + int16_t start; + int16_t end; + GetAngleRange(start, end); + Rect rect = GetOrigRect(); + arcinfo.center.x = center_.x + rect.GetLeft() + style_->paddingLeft_ + style_->borderWidth_; + arcinfo.center.y = center_.y + rect.GetTop() + style_->paddingTop_ + style_->borderWidth_; + + if (enableBackground_ && ((start != end) || (backgroundStyle_->lineCap_ == CapType::CAP_ROUND))) { + arcinfo.imgPos.x = backgroundImagePos_.x + rect.GetLeft(); + arcinfo.imgPos.y = backgroundImagePos_.y + rect.GetTop(); + arcinfo.startAngle = start; + arcinfo.endAngle = end; + arcinfo.imgSrc = backgroundImage_; + DrawArc::GetInstance()->Draw(arcinfo, invalidatedArea, *backgroundStyle_, opaScale_, + backgroundStyle_->lineCap_); + } + + if ((startAngle != endAngle) || (foregroundStyle_->lineCap_ == CapType::CAP_ROUND)) { + arcinfo.imgPos.x = progressImagePos_.x + rect.GetLeft(); + arcinfo.imgPos.y = progressImagePos_.y + rect.GetTop(); + arcinfo.startAngle = startAngle; + arcinfo.endAngle = endAngle; + arcinfo.imgSrc = foregroundImage_; + DrawArc::GetInstance()->Draw(arcinfo, invalidatedArea, *foregroundStyle_, opaScale_, + foregroundStyle_->lineCap_); + } +} + +void UICircleProgress::OnDraw(const Rect& invalidatedArea) +{ + if (GetRangeSize() == 0) { + return; + } + + DrawRect::Draw(GetOrigRect(), invalidatedArea, *style_, opaScale_); + + Rect trunc(invalidatedArea); + if (trunc.Intersect(trunc, GetOrigRect())) { + DrawCommonCircle(trunc); + } +} +} // namespace OHOS \ No newline at end of file diff --git a/frameworks/components/ui_dialog.cpp b/frameworks/components/ui_dialog.cpp new file mode 100755 index 0000000..8bb8d5b --- /dev/null +++ b/frameworks/components/ui_dialog.cpp @@ -0,0 +1,694 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "components/ui_dialog.h" +#include "common/screen.h" +#include "common/typed_text.h" +#if ENABLE_DEBUG +#include "graphic_assert.h" +#endif +#include "graphic_log.h" +#if ENABLE_WINDOW +#include "window/window.h" +#endif + +#if ENABLE_WINDOW +namespace OHOS { +class UIDialogLabelButton : public UILabelButton { +public: + UIDialogLabelButton(UIDialog* dialog) + { + dialog_ = dialog; + } + virtual ~UIDialogLabelButton() {} + virtual bool OnClickEvent(const ClickEvent& event) + { + bool ret = true; + if (dialog_ != nullptr) { + dialog_->DestoryWindow(); + dialog_->isShowing_ = false; + } + if (onClickListener_ != nullptr) { + ret = onClickListener_->OnClick(*this, event); + } + return ret; + } +private: + UIDialog* dialog_; +}; + +class UIDialogClickListener : public UIView::OnClickListener { +public: + UIDialogClickListener(UIDialog* dialog) + { + dialog_ = dialog; + } + virtual ~UIDialogClickListener() {} + bool OnClick(UIView& view, const ClickEvent& event) override + { + bool ret = true; + if ((dialog_ != nullptr) && (dialog_->dialogLayer_ != nullptr) && + !dialog_->dialogLayer_->GetRect().IsContains(event.GetCurrentPos())) { + if (dialog_->enableAutoCancel_) { + dialog_->DestoryWindow(); + dialog_->isShowing_ = false; + } + if (dialog_->onCancelListener_ != nullptr) { + ret = dialog_->onCancelListener_->OnClick(view, event); + } + } + return ret; + } +private: + UIDialog* dialog_; +}; + +UIDialog::UIDialog() + : isShowing_(false), + enableAutoCancel_(false), + buttonNum_(0), + title_(nullptr), + text_(nullptr), + button1_(nullptr), + button2_(nullptr), + button3_(nullptr), + dialogLayer_(nullptr), + windowRootView_(nullptr), + onCancelListener_(nullptr), + dialogClickListener_(nullptr), + window_(nullptr), + line1_(nullptr), + line2_(nullptr), + titleText_(nullptr), + textText_(nullptr) +{ + uint16_t screenWidth = Screen::GetInstance().GetWidth(); + uint16_t screenHeight = Screen::GetInstance().GetHeight(); + // 100: calculate percentage + widthMax_ = screenWidth * MAX_WIDTH_PERCENT / 100; + // 100: calculate percentage + heightMax_ = screenHeight * MAX_HEIGHT_PERCENT / 100; + colorType1_ = Color::White(); + colorType2_ = Color::White(); + colorType3_ = Color::White(); +} + +UIDialog::~UIDialog() +{ + onCancelListener_ = nullptr; + if (dialogLayer_ != nullptr) { + dialogLayer_->RemoveAll(); + delete dialogLayer_; + dialogLayer_ = nullptr; + } + if (title_ != nullptr) { + delete title_; + title_ = nullptr; + } + if (text_ != nullptr) { + delete text_; + text_ = nullptr; + } + if (button1_ != nullptr) { + delete button1_; + button1_ = nullptr; + } + if (button2_ != nullptr) { + delete button2_; + button2_ = nullptr; + } + if (button3_ != nullptr) { + delete button3_; + button3_ = nullptr; + } + if (line1_ != nullptr) { + delete line1_; + line1_ = nullptr; + } + if (line2_ != nullptr) { + delete line2_; + line2_ = nullptr; + } + if ((windowRootView_ != nullptr) && !RootView::DestoryWindowRootView(windowRootView_)) { + windowRootView_ = nullptr; + } + if (dialogClickListener_ != nullptr) { + delete dialogClickListener_; + dialogClickListener_ = nullptr; + } +} + +void UIDialog::SetTitle(const char* title) +{ + if (title == nullptr) { + return; + } + titleText_ = title; +} + +void UIDialog::SetText(const char* text) +{ + if (text == nullptr) { + return; + } + textText_ = text; +} + +void UIDialog::SetButton(DialogButtonType buttonType, const char* text, UIView::OnClickListener* listener) +{ + switch (buttonType) { + case DialogButtonType::BUTTON_LEFT: + if (button1_ == nullptr) { + button1_ = new UIDialogLabelButton(this); + if (button1_ == nullptr) { + GRAPHIC_LOGE("new UIDialogLabelButton fail"); + return; + } + button1_->SetViewId(BUTTON1_ID); + AddButton(buttonType, button1_, text, listener); + } else { + button1_->SetText(text); + button1_->SetOnClickListener(listener); + } + break; + case DialogButtonType::BUTTON_MID: + if (button2_ == nullptr) { + button2_ = new UIDialogLabelButton(this); + if (button2_ == nullptr) { + GRAPHIC_LOGE("new UIDialogLabelButton fail"); + return; + } + button2_->SetViewId(BUTTON2_ID); + AddButton(buttonType, button2_, text, listener); + } else { + button2_->SetText(text); + button2_->SetOnClickListener(listener); + } + break; + case DialogButtonType::BUTTON_RIGHT: + if (button3_ == nullptr) { + button3_ = new UIDialogLabelButton(this); + if (button3_ == nullptr) { + GRAPHIC_LOGE("new UIDialogLabelButton fail"); + return; + } + AddButton(buttonType, button3_, text, listener); + } else { + button3_->SetText(text); + button3_->SetOnClickListener(listener); + } + break; + default: + break; + } +} + +void UIDialog::AddButton(DialogButtonType buttonType, + UILabelButton* button, + const char* text, + UIView::OnClickListener* listener) +{ + buttonNum_++; + button->SetText(text); + button->SetOnClickListener(listener); + button->SetFont(DEFAULT_VECTOR_FONT_FILENAME, BUTTON_FONT_SIZE); + button->SetTextColor(Color::Blue()); + button->SetStyleForState(STYLE_BACKGROUND_COLOR, Color::White().full, UIButton::ButtonState::RELEASED); + button->SetStyleForState(STYLE_BACKGROUND_COLOR, Color::Black().full, UIButton::ButtonState::PRESSED); + button->SetStyleForState(STYLE_BACKGROUND_OPA, BUTTON_PRESS_OPA, UIButton::ButtonState::PRESSED); + button->SetStyleForState(STYLE_BORDER_WIDTH, 0, UIButton::ButtonState::RELEASED); + button->SetStyleForState(STYLE_BORDER_WIDTH, 0, UIButton::ButtonState::PRESSED); + button->SetHeight(BUTTON_HEIGHT); +} + +void UIDialog::Show() +{ + if (isShowing_) { + return; + } + SetTitleLabel(); + SetTextLabel(); + InitDialog(); + AddComponents(); + MeasureSize(); + Layout(); + CreateDialogWindow(); + window_->Show(); + isShowing_ = true; +} + +void UIDialog::SetTitleLabel() +{ + if (titleText_ == nullptr) { + return; + } + if (title_ == nullptr) { + title_ = new UILabel(); + if (title_ == nullptr) { + GRAPHIC_LOGE("new UILabel fail"); + return; + } + title_->SetViewId(TITLE_ID); + title_->SetFont(DEFAULT_VECTOR_FONT_FILENAME, TITLE_FONT_SIZE); + title_->SetAlign(UITextLanguageAlignment::TEXT_ALIGNMENT_LEFT); + title_->SetStyle(STYLE_TEXT_COLOR, Color::Black().full); + title_->SetStyle(STYLE_TEXT_OPA, TITLE_TEXT_OPA); + title_->SetStyle(STYLE_BACKGROUND_COLOR, Color::White().full); + } + title_->SetText(titleText_); +} + +void UIDialog::SetTextLabel() +{ + if (textText_ == nullptr) { + return; + } + if (text_ == nullptr) { + text_ = new UILabel(); + if (text_ == nullptr) { + GRAPHIC_LOGE("new UILabel fail"); + return; + } + text_->SetFont(DEFAULT_VECTOR_FONT_FILENAME, TEXT_FONT_SIZE); + text_->SetStyle(STYLE_TEXT_COLOR, Color::Black().full); + text_->SetStyle(STYLE_TEXT_OPA, TITLE_TEXT_OPA); + text_->SetStyle(STYLE_BACKGROUND_COLOR, Color::White().full); + } + text_->SetText(textText_); +} + +void UIDialog::InitDialog() +{ + if (dialogLayer_ == nullptr) { + dialogLayer_ = new UIViewGroup(); + if (dialogLayer_ == nullptr) { + GRAPHIC_LOGE("new UIViewGroup fail"); + return; + } + dialogLayer_->SetStyle(STYLE_BACKGROUND_COLOR, Color::White().full); + } + if (windowRootView_ == nullptr) { + windowRootView_ = RootView::GetWindowRootView(); + windowRootView_->SetWidth(Screen::GetInstance().GetWidth()); + windowRootView_->SetHeight(Screen::GetInstance().GetHeight()); + windowRootView_->SetTouchable(true); + windowRootView_->Add(dialogLayer_); + } + if (dialogClickListener_ == nullptr) { + dialogClickListener_ = new UIDialogClickListener(this); + if (dialogClickListener_ == nullptr) { + GRAPHIC_LOGE("new UIDialogClickListener fail"); + return; + } + windowRootView_->SetOnClickListener(dialogClickListener_); + } +} + +void UIDialog::AddComponents() +{ + if (title_ != nullptr) { + dialogLayer_->Add(title_); + } + if (text_ != nullptr) { + dialogLayer_->Add(text_); + } + if (button1_ != nullptr) { + dialogLayer_->Add(button1_); + } + if (button2_ != nullptr) { + dialogLayer_->Add(button2_); + } + if (button3_ != nullptr) { + dialogLayer_->Add(button3_); + } + if (buttonNum_ > 1) { + line1_ = new UIView(); + if (line1_ == nullptr) { + GRAPHIC_LOGE("new UIView fail"); + return; + } + line1_->SetHeight(LINE_HEIGHT); + line1_->SetWidth(LINE_WIDTH); + line1_->SetStyle(STYLE_BACKGROUND_COLOR, Color::Black().full); + line1_->SetStyle(STYLE_BACKGROUND_OPA, LINE_OPA); + dialogLayer_->Add(line1_); + } + if (buttonNum_ == 3) { // 3: three buttons + line2_ = new UIView(); + if (line2_ == nullptr) { + GRAPHIC_LOGE("new UIView fail"); + return; + } + line2_->SetHeight(LINE_HEIGHT); + line2_->SetWidth(LINE_WIDTH); + line2_->SetStyle(STYLE_BACKGROUND_COLOR, Color::Black().full); + line2_->SetStyle(STYLE_BACKGROUND_OPA, LINE_OPA); + dialogLayer_->Add(line2_); + } +} + +void UIDialog::MeasureSize() +{ + uint16_t width = MeasureMaxWidth(); + uint16_t height = 0; + + if (buttonNum_ > 0) { + // 2: there are 2 paddings horizontally + uint16_t buttonWidth = (width - 2 * BUTTON_PADDING - (buttonNum_ - 1) * BUTTON_MID_PADDING) / buttonNum_; + // 2: there are 2 paddings horizontally + width = static_cast(buttonWidth) * buttonNum_ + (buttonNum_ - 1) * BUTTON_MID_PADDING + + 2 * BUTTON_PADDING; + height += BUTTON_TOTAL_HEIGHT; + height += TEXT_BUTTON_PADDING; + if (button1_ != nullptr) { + button1_->SetWidth(buttonWidth); + } + if (button2_ != nullptr) { + button2_->SetWidth(buttonWidth); + } + if (button3_ != nullptr) { + button3_->SetWidth(buttonWidth); + } + } else { + height += PADDING; + } + + if (title_ != nullptr) { + title_->SetWidth(width - 2 * PADDING); // 2: there are 2 paddings horizontally + height += TITLE_TOTAL_HEIGHT; + } else { + height += PADDING; + } + + if (text_ != nullptr) { + text_->SetWidth(width - 2 * PADDING); // 2: there are 2 paddings horizontally + uint16_t textHightMax = heightMax_ - height; + if (text_->GetTextHeight() < textHightMax) { + text_->SetHeight(text_->GetTextHeight()); + } else { + text_->SetHeight(textHightMax); + text_->SetLineBreakMode(UILabel::LINE_BREAK_ELLIPSIS); + } + height += text_->GetHeight(); + } + + uint16_t dialogHeight = height; + dialogLayer_->SetHeight(dialogHeight); + dialogLayer_->SetWidth(width); +} + +uint16_t UIDialog::MeasureMaxWidth() +{ + uint16_t titleWidth = 0; + uint16_t textWidth = 0; + uint16_t buttonTotalWidth = 0; + + if (title_ != nullptr) { + titleWidth = MeasureTitleWidth(); + titleWidth += 2 * PADDING; // 2: there are 2 paddings horizontally + } + if (text_ != nullptr) { + textWidth = MeasureTextWidth(); + textWidth += 2 * PADDING; // 2: there are 2 paddings horizontally + } + if (buttonNum_ > 0) { + buttonTotalWidth = + static_cast(MeasureButtonWidth()) * buttonNum_ + (buttonNum_ - 1) * BUTTON_MID_PADDING; + buttonTotalWidth += 2 * BUTTON_PADDING; // 2: there are 2 paddings horizontally + } + return MATH_MAX(titleWidth, MATH_MAX(textWidth, buttonTotalWidth)); +} + +uint16_t UIDialog::MeasureTitleWidth() +{ + uint16_t titleWidth = 0; + uint16_t widthMaxNoPadding = widthMax_ - 2 * PADDING; // 2: there are 2 paddings horizontally + title_->SetLineBreakMode(UILabel::LINE_BREAK_ADAPT); + if (title_->GetTextWidth() > widthMaxNoPadding) { + titleWidth = widthMaxNoPadding; + title_->SetLineBreakMode(UILabel::LINE_BREAK_ELLIPSIS); + } else { + titleWidth = title_->GetTextWidth(); + } + title_->SetHeight(title_->GetTextHeight()); + return titleWidth; +} + +uint16_t UIDialog::MeasureTextWidth() +{ + uint16_t textWidth = 0; + uint16_t widthMaxNoPadding = widthMax_ - 2 * PADDING; // 2: there are 2 paddings horizontally + if (title_ != nullptr) { + text_->SetAlign(UITextLanguageAlignment::TEXT_ALIGNMENT_LEFT); + } else { + text_->SetAlign(UITextLanguageAlignment::TEXT_ALIGNMENT_CENTER); + } + text_->SetLineBreakMode(UILabel::LINE_BREAK_ADAPT); + textWidth = text_->GetTextWidth(); + if (text_->GetTextWidth() > widthMaxNoPadding) { + text_->SetAlign(UITextLanguageAlignment::TEXT_ALIGNMENT_LEFT); + textWidth = widthMaxNoPadding; + text_->SetLineBreakMode(UILabel::LINE_BREAK_WRAP); + } + return textWidth; +} + +uint16_t UIDialog::MeasureButtonWidth() +{ + if (buttonNum_ == 0) { + return 0; + } + + uint16_t buttonTextWidth = 0; + // 2: there are 2 paddings horizontally + uint16_t buttonMaxWidth = (widthMax_ - 2 * BUTTON_PADDING - (buttonNum_ - 1) * BUTTON_MID_PADDING) / buttonNum_; + + if (button1_ != nullptr) { + const char* text1 = button1_->GetText(); + buttonTextWidth = MATH_MAX(buttonTextWidth, TypedText::GetTextSize(text1, + button1_->GetStyleConst().letterSpace_, button1_->GetStyleConst().lineSpace_, widthMax_).x); + } + if (button2_ != nullptr) { + const char* text2 = button2_->GetText(); + buttonTextWidth = MATH_MAX(buttonTextWidth, TypedText::GetTextSize(text2, + button2_->GetStyleConst().letterSpace_, button2_->GetStyleConst().lineSpace_, widthMax_).x); + } + if (button3_ != nullptr) { + const char* text3 = button3_->GetText(); + buttonTextWidth = MATH_MAX(buttonTextWidth, TypedText::GetTextSize(text3, + button3_->GetStyleConst().letterSpace_, button3_->GetStyleConst().lineSpace_, widthMax_).x); + } + return (buttonTextWidth + BUTTON_HEIGHT) > buttonMaxWidth ? buttonMaxWidth : (buttonTextWidth + BUTTON_HEIGHT); +} + +void UIDialog::Layout() +{ + if (title_ != nullptr) { + // 2: there are 2 paddings vertically + uint8_t padding = (TITLE_TOTAL_HEIGHT - title_->GetHeight()) / 2; + title_->LayoutLeftOfParent(PADDING); + title_->LayoutTopOfParent(padding); + if (text_ != nullptr) { + text_->LayoutLeftOfParent(PADDING); + text_->LayoutBottomToSibling(TITLE_ID, padding); + } + } else { + if (text_ != nullptr) { + text_->LayoutLeftOfParent(PADDING); + text_->LayoutTopOfParent(PADDING); + } + } + LayoutButton(); +} + +void UIDialog::LayoutButton() +{ + if (button1_ != nullptr) { + button1_->LayoutLeftOfParent(BUTTON_PADDING); + button1_->LayoutBottomOfParent(BUTTON_PADDING); + if (button2_ != nullptr) { + button2_->LayoutRightToSibling(BUTTON1_ID, BUTTON_MID_PADDING); + button2_->LayoutBottomOfParent(BUTTON_PADDING); + } + } else if (button2_ != nullptr) { + button2_->LayoutLeftOfParent(BUTTON_PADDING); + button2_->LayoutBottomOfParent(BUTTON_PADDING); + } + if (button3_ != nullptr) { + button3_->LayoutRightOfParent(BUTTON_PADDING); + button3_->LayoutBottomOfParent(BUTTON_PADDING); + } + + if (buttonNum_ == 3) { // 3: three buttons + line1_->LayoutBottomOfParent(LINE_BOTTOM_PADDING); + line1_->LayoutRightToSibling(BUTTON1_ID, LINE_BUTTON_PADDING); + line2_->LayoutBottomOfParent(LINE_BOTTOM_PADDING); + line2_->LayoutRightToSibling(BUTTON2_ID, LINE_BUTTON_PADDING); + } else if (buttonNum_ == 2) { // 2: two buttons + if (button1_ != nullptr) { + line1_->LayoutBottomOfParent(LINE_BOTTOM_PADDING); + line1_->LayoutRightToSibling(BUTTON1_ID, LINE_BUTTON_PADDING); + } else { + line1_->LayoutBottomOfParent(LINE_BOTTOM_PADDING); + line1_->LayoutRightToSibling(BUTTON2_ID, LINE_BUTTON_PADDING); + } + } +} + +void UIDialog::CreateDialogWindow() +{ + dialogLayer_->LayoutCenterOfParent(); + Rect rect = dialogLayer_->GetRect(); + int16_t offset_x = dialogLayer_->GetX(); + int16_t offset_y = dialogLayer_->GetY(); + + windowRootView_->SetPosition(-offset_x, -offset_y); + windowRootView_->Invalidate(); + + WindowConfig config = {}; + config.rect = rect; + config.rect.SetPosition(offset_x, offset_y); + config.isModal = true; + window_ = Window::CreateWindow(config); + if (window_ != nullptr) { + window_->BindRootView(windowRootView_); + } else { + GRAPHIC_LOGE("Create window false!"); + } +} + +void UIDialog::SetButtonColor(DialogButtonType buttonType, ColorType color) +{ + switch (buttonType) { + case DialogButtonType::BUTTON_LEFT: + if (button1_ != nullptr) { + button1_->SetStyleForState(STYLE_BACKGROUND_COLOR, color.full, UIButton::ButtonState::RELEASED); + button1_->SetStyleForState(STYLE_BACKGROUND_OPA, color.alpha, UIButton::ButtonState::RELEASED); + colorType1_ = color; + } + break; + case DialogButtonType::BUTTON_MID: + if (button2_ != nullptr) { + button2_->SetStyleForState(STYLE_BACKGROUND_COLOR, color.full, UIButton::ButtonState::RELEASED); + button2_->SetStyleForState(STYLE_BACKGROUND_OPA, color.alpha, UIButton::ButtonState::RELEASED); + colorType2_ = color; + } + break; + case DialogButtonType::BUTTON_RIGHT: + if (button3_ != nullptr) { + button3_->SetStyleForState(STYLE_BACKGROUND_COLOR, color.full, UIButton::ButtonState::RELEASED); + button3_->SetStyleForState(STYLE_BACKGROUND_COLOR, color.alpha, UIButton::ButtonState::RELEASED); + colorType3_ = color; + } + break; + default: + break; + } +} + +void UIDialog::SetOnCancelListener(UIView::OnClickListener* onCancelListener) +{ + if (onCancelListener == nullptr) { + return; + } + onCancelListener_ = onCancelListener; +} + +void UIDialog::EnableAutoCancel(bool enable) +{ + enableAutoCancel_ = enable; +} + +void UIDialog::DestoryWindow() +{ + if (window_ != nullptr) { + Window::DestoryWindow(window_); + window_ = nullptr; + } +} + +#ifdef ENABLE_DEBUG +const char* UIDialog::GetButtonText(DialogButtonType buttonType) const +{ + switch (buttonType) { + case DialogButtonType::BUTTON_LEFT: + if (button1_ != nullptr) { + return button1_->GetText(); + } + return nullptr; + case DialogButtonType::BUTTON_MID: + if (button2_ != nullptr) { + return button2_->GetText(); + } + return nullptr; + case DialogButtonType::BUTTON_RIGHT: + if (button3_ != nullptr) { + return button3_->GetText(); + } + return nullptr; + default: + return nullptr; + } +} + +UIView::OnClickListener* UIDialog::GetButtonListener(DialogButtonType buttonType) const +{ + switch (buttonType) { + case DialogButtonType::BUTTON_LEFT: + if (button1_ != nullptr) { + return button1_->GetOnClickListener(); + } + return nullptr; + case DialogButtonType::BUTTON_MID: + if (button2_ != nullptr) { + return button2_->GetOnClickListener(); + } + return nullptr; + case DialogButtonType::BUTTON_RIGHT: + if (button3_ != nullptr) { + return button3_->GetOnClickListener(); + } + return nullptr; + default: + return nullptr; + } +} + +ColorType UIDialog::GetButtonColor(DialogButtonType buttonType) const +{ + switch (buttonType) { + case DialogButtonType::BUTTON_LEFT: + if (button1_ != nullptr) { + return colorType1_; + } + break; + case DialogButtonType::BUTTON_MID: + if (button2_ != nullptr) { + return colorType2_; + } + break; + case DialogButtonType::BUTTON_RIGHT: + if (button3_ != nullptr) { + return colorType3_; + } + break; + default: + break; + } + ASSERT(0); +} +#endif // ENABLE_DEBUG +} // namespace OHOS +#endif // ENABLE_WINDOW \ No newline at end of file diff --git a/frameworks/components/ui_digital_clock.cpp b/frameworks/components/ui_digital_clock.cpp new file mode 100755 index 0000000..406c5f7 --- /dev/null +++ b/frameworks/components/ui_digital_clock.cpp @@ -0,0 +1,249 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "components/ui_digital_clock.h" +#include +#include "components/ui_view_group.h" +#include "font/ui_font.h" +#include "graphic_log.h" +#include "securec.h" + +namespace OHOS { +UIDigitalClock::UIDigitalClock() + : timeLabels_{0}, + displayMode_(DISPLAY_24_HOUR), + leadingZero_(true), + color_(Color::White()), + prevHour_(0), + prevMinute_(0), + prevSecond_(0), + verticalShow_(false) +{ + style_ = &(StyleDefault::GetBackgroundTransparentStyle()); +} + +void UIDigitalClock::InitTimeLabels() +{ + for (uint8_t i = 0; i < TIME_ELEMENT_COUNT; i++) { + if (timeLabels_[i] == nullptr) { + timeLabels_[i] = new UILabel; + if (timeLabels_[i] == nullptr) { + GRAPHIC_LOGE("new UILabel fail"); + return; + } + timeLabels_[i]->SetLineBreakMode(UILabel::LINE_BREAK_ADAPT); + timeLabels_[i]->SetStyle(STYLE_BACKGROUND_OPA, OPA_TRANSPARENT); + Add(timeLabels_[i]); + } + } +} + +void UIDigitalClock::DisplayLeadingZero(bool displayLeadingZero) +{ + leadingZero_ = displayLeadingZero; + UpdateClock(false); +} + +void UIDigitalClock::SetOpacity(uint8_t opacity) +{ + opaScale_ = opacity; + InitTimeLabels(); + for (uint8_t i = 0; i < TIME_ELEMENT_COUNT; i++) { + timeLabels_[i]->SetStyle(STYLE_TEXT_OPA, opacity); + } + RefreshTime(); +} + +uint8_t UIDigitalClock::GetOpacity() const +{ + return opaScale_; +} + +void UIDigitalClock::SetFontId(uint8_t fontId) +{ + SetStyle(STYLE_TEXT_FONT, fontId); + InitTimeLabels(); + for (uint8_t i = 0; i < TIME_ELEMENT_COUNT; i++) { + timeLabels_[i]->SetFontId(fontId); + } + UpdateClock(false); +} + +void UIDigitalClock::SetFont(const char* name, uint8_t size) +{ + InitTimeLabels(); + for (uint8_t i = 0; i < TIME_ELEMENT_COUNT; i++) { + timeLabels_[i]->SetFont(name, size); + } + UpdateClock(false); +} + +void UIDigitalClock::SetColor(ColorType color) +{ + color_ = color; + InitTimeLabels(); + for (uint8_t i = 0; i < TIME_ELEMENT_COUNT; i++) { + timeLabels_[i]->SetStyle(STYLE_TEXT_COLOR, color.full); + } + RefreshTime(); +} + +void UIDigitalClock::TimeElementRefresh() +{ + InitTimeLabels(); + if (currentHour_ != prevHour_) { + prevHour_ = currentHour_; + timeLabels_[HOUR_ELEMENT]->Invalidate(); + } + + if (currentMinute_ != prevMinute_) { + prevMinute_ = currentMinute_; + timeLabels_[MINUTE_ELEMENT]->Invalidate(); + } + + if (currentSecond_ != prevSecond_) { + prevSecond_ = currentSecond_; + timeLabels_[SECOND_ELEMENT]->Invalidate(); + } +} + +void UIDigitalClock::RefreshTime() +{ + InitTimeLabels(); + for (uint8_t i = 0; i < TIME_ELEMENT_COUNT; i++) { + timeLabels_[i]->Invalidate(); + } +} + +void UIDigitalClock::UpdateClock(bool clockInit) +{ + char buf[TIME_ELEMENT_COUNT][BUFFER_SIZE] = {{0}}; + const char* formatWithColon = leadingZero_ ? "%02d:" : "%d:"; + const char* formatWithoutColon = leadingZero_ ? "%02d" : "%d"; + const char* format = verticalShow_ ? formatWithoutColon : formatWithColon; + const char* formatForMinute = verticalShow_ ? "%02d" : "%02d:"; + switch (displayMode_) { + case DISPLAY_24_HOUR_NO_SECONDS: { + if (sprintf_s(buf[HOUR_ELEMENT], BUFFER_SIZE, format, currentHour_) < 0) { + return; + } + if (sprintf_s(buf[MINUTE_ELEMENT], BUFFER_SIZE, "%02d", currentMinute_) < 0) { + return; + } + break; + } + case DISPLAY_12_HOUR_NO_SECONDS: { + if (sprintf_s(buf[HOUR_ELEMENT], BUFFER_SIZE, format, currentHour_ % HALF_DAY_IN_HOUR) < 0) { + return; + } + if (sprintf_s(buf[MINUTE_ELEMENT], BUFFER_SIZE, "%02d", currentMinute_) < 0) { + return; + } + break; + } + case DISPLAY_12_HOUR: { + if (sprintf_s(buf[HOUR_ELEMENT], BUFFER_SIZE, format, currentHour_ % HALF_DAY_IN_HOUR) < 0) { + return; + } + if (sprintf_s(buf[MINUTE_ELEMENT], BUFFER_SIZE, formatForMinute, currentMinute_) < 0) { + return; + } + if (sprintf_s(buf[SECOND_ELEMENT], BUFFER_SIZE, "%02d", currentSecond_) < 0) { + return; + } + break; + } + case DISPLAY_24_HOUR: { + if (sprintf_s(buf[HOUR_ELEMENT], BUFFER_SIZE, format, currentHour_) < 0) { + return; + } + if (sprintf_s(buf[MINUTE_ELEMENT], BUFFER_SIZE, formatForMinute, currentMinute_) < 0) { + return; + } + if (sprintf_s(buf[SECOND_ELEMENT], BUFFER_SIZE, "%02d", currentSecond_) < 0) { + return; + } + break; + } + default: { + break; + } + } + SetTimeLabels(buf); +} + +void UIDigitalClock::SetTimeLabels(const char buf[TIME_ELEMENT_COUNT][BUFFER_SIZE]) +{ + InitTimeLabels(); + for (uint8_t i = 0; i < TIME_ELEMENT_COUNT; i++) { + timeLabels_[i]->SetText(buf[i]); + } + + SetTimeLabelsPosition(); + TimeElementRefresh(); +} + +void UIDigitalClock::SetHorizontal() +{ + InitTimeLabels(); + uint16_t totalWidth = timeLabels_[HOUR_ELEMENT]->GetWidth() + timeLabels_[MINUTE_ELEMENT]->GetWidth() + + timeLabels_[SECOND_ELEMENT]->GetWidth(); + UITextLanguageAlignment align = timeLabels_[HOUR_ELEMENT]->GetHorAlign(); + int16_t x = 0; + Rect rect = GetContentRect(); + if (align == TEXT_ALIGNMENT_CENTER) { + x = (rect.GetWidth() >> 1) - (totalWidth >> 1); + } else if (align == TEXT_ALIGNMENT_RIGHT) { + x = rect.GetRight() - totalWidth; + } + timeLabels_[HOUR_ELEMENT]->SetPosition(x, 0); + int16_t width = timeLabels_[HOUR_ELEMENT]->GetWidth(); + for (uint8_t i = 1; i < TIME_ELEMENT_COUNT; i++) { + timeLabels_[i]->SetPosition(x + width, 0); + width += timeLabels_[i]->GetWidth(); + } +} + +void UIDigitalClock::SetTimeLabelsPosition() +{ + if (verticalShow_) { + SetVertical(); + } else { + SetHorizontal(); + } +} + +void UIDigitalClock::SetVertical() +{ + InitTimeLabels(); + int16_t fontHeight = timeLabels_[HOUR_ELEMENT]->GetHeight(); + timeLabels_[HOUR_ELEMENT]->SetPosition(0, 0); + int16_t y = fontHeight; + for (uint8_t i = 1; i < TIME_ELEMENT_COUNT; i++) { + timeLabels_[i]->SetPosition(0, y); + y += fontHeight; + } +} + +UIDigitalClock::~UIDigitalClock() +{ + for (uint8_t i = 0; i < TIME_ELEMENT_COUNT; i++) { + if (timeLabels_[i] != nullptr) { + delete timeLabels_[i]; + timeLabels_[i] = nullptr; + } + } +} +} // namespace OHOS diff --git a/frameworks/components/ui_image_animator.cpp b/frameworks/components/ui_image_animator.cpp new file mode 100755 index 0000000..2882e41 --- /dev/null +++ b/frameworks/components/ui_image_animator.cpp @@ -0,0 +1,204 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "components/ui_image_animator.h" + +namespace OHOS { +UIImageAnimatorView::UIImageAnimatorView() + : imageSrc_(nullptr), imageNum_(0), tickOfUpdate_(1), timeOfUpdate_(DEFAULT_TASK_PERIOD), + timeOfPause_(0), tickOfPause_(0), repeatTimes_(1), imageAnimator_(&imageAnimatorCallback_, this, 0, true), + listener_(nullptr), reverse_(false), repeat_(true), sizeFixed_(false), fillMode_(true) +{ + AnimatorManager::GetInstance()->Add(&imageAnimator_); +} + +UIImageAnimatorView::~UIImageAnimatorView() +{ + AnimatorManager::GetInstance()->Remove(&imageAnimator_); +} + +void UIImageAnimatorView::ImageAnimatorCallback::Callback(UIView* view) +{ + if (view == nullptr) { + return; + } + UIImageAnimatorView* imageAnimatorView = static_cast(view); + + imageSrc_ = imageAnimatorView->GetImageAnimatorSrc(); + imageNum_ = imageAnimatorView->GetImageAnimatorImageNum(); + if ((imageSrc_ == nullptr) || (imageNum_ == 0) || (imageAnimatorView->tickOfUpdate_ == 0)) { + return; + } + + if (!imageAnimatorView->IsRepeat() && (repeat_ == imageAnimatorView->GetRepeatTimes())) { + imageAnimatorView->Stop(); + return; + } + + tickNum_++; + + if (loop_ != imageNum_) { + if (tickNum_ < imageAnimatorView->tickOfUpdate_) { + return; + } + } else { + if (imageAnimatorView->tickOfPause_ != 0) { + if (tickNum_ < imageAnimatorView->tickOfPause_) { + return; + } + } else { + if (tickNum_ < imageAnimatorView->tickOfUpdate_) { + return; + } + } + repeat_++; + loop_ = 0; + if (!imageAnimatorView->IsRepeat() && (repeat_ == imageAnimatorView->GetRepeatTimes())) { + imageAnimatorView->Stop(); + return; + } + } + imageAnimatorView->UpdateImage(drawingImage_, loop_); + tickNum_ = 0; +} + +void UIImageAnimatorView::UpdateImage(uint8_t& drawingImage, uint8_t& loop) +{ + Invalidate(); + drawingImage = reverse_ ? (imageNum_ - loop - 1) : loop; + if (drawingImage >= imageNum_) { + return; + } + + ImageAnimatorInfo* imageAnimatorInfo = &(imageSrc_[drawingImage]); + if (imageAnimatorInfo->imageType == IMG_SRC_FILE_PATH) { + SetSrc(imageAnimatorInfo->imagePath); + } else if (imageAnimatorInfo->imageType == IMG_SRC_IMAGE_INFO) { + SetSrc(imageAnimatorInfo->imageInfo); + } + if (!sizeFixed_) { + SetPosition(imageAnimatorInfo->pos.x, imageAnimatorInfo->pos.y); + SetWidth(imageAnimatorInfo->width); + SetHeight(imageAnimatorInfo->height); + } + Invalidate(); + loop++; +} + +void UIImageAnimatorView::SetImageAnimatorSrc(const ImageAnimatorInfo imageAnimatorInfoSrc[], uint8_t imageNum) +{ + SetImageAnimatorSrc(imageAnimatorInfoSrc, imageNum, timeOfUpdate_); +} + +void UIImageAnimatorView::SetImageAnimatorSrc(const ImageAnimatorInfo imageAnimatorInfoSrc[], + uint8_t imageNum, uint16_t timeOfUpdate) +{ + imageSrc_ = const_cast(imageAnimatorInfoSrc); + imageNum_ = imageNum; + timeOfUpdate_ = timeOfUpdate; + tickOfUpdate_ = GetTickByTime(timeOfUpdate); + return; +} + +const ImageAnimatorInfo* UIImageAnimatorView::GetImageAnimatorSrc() const +{ + return imageSrc_; +} + +uint8_t UIImageAnimatorView::GetImageAnimatorImageNum() const +{ + return imageNum_; +} + +void UIImageAnimatorView::SetTimeOfUpdate(uint16_t timeOfUpdate) +{ + timeOfUpdate_ = timeOfUpdate; + tickOfUpdate_ = GetTickByTime(timeOfUpdate); +} + +uint16_t UIImageAnimatorView::GetTimeOfUpdate() const +{ + return timeOfUpdate_; +} + +void UIImageAnimatorView::SetTimeOfPause(uint16_t timeOfPause) +{ + timeOfPause_ = timeOfPause; + tickOfPause_ = GetTickByTime(timeOfPause); +} + +uint16_t UIImageAnimatorView::GetTimeOfPause() const +{ + return timeOfPause_; +} + +void UIImageAnimatorView::Start() +{ + Reset(false); + imageAnimator_.SetState(Animator::START); +} + +void UIImageAnimatorView::Reset(bool fillMode) +{ + if ((imageSrc_ == nullptr) || (imageNum_ == 0)) { + return; + } + + Invalidate(); + uint8_t drawingImage; + if (fillMode) { + drawingImage = reverse_ ? 0 : (imageNum_ - 1); + } else { + drawingImage = reverse_ ? (imageNum_ - 1) : 0; + } + ImageAnimatorInfo* imageAnimatorInfo = &(imageSrc_[drawingImage]); + if (imageAnimatorInfo->imageType == IMG_SRC_FILE_PATH) { + SetSrc(imageAnimatorInfo->imagePath); + } else if (imageAnimatorInfo->imageType == IMG_SRC_IMAGE_INFO) { + SetSrc(imageAnimatorInfo->imageInfo); + } + if (!sizeFixed_) { + SetPosition(imageAnimatorInfo->pos.x, imageAnimatorInfo->pos.y); + SetWidth(imageAnimatorInfo->width); + SetHeight(imageAnimatorInfo->height); + } + Invalidate(); + imageAnimatorCallback_.Reset(); +} + +void UIImageAnimatorView::Stop() +{ + if (imageAnimator_.GetState() == Animator::STOP) { + return; + } + + imageAnimator_.SetState(Animator::STOP); + Reset(fillMode_); + if (listener_ != nullptr) { + listener_->OnAnimatorStop(*this); + } +} + +uint8_t UIImageAnimatorView::GetTickByTime(uint16_t time) const +{ + uint8_t tick; + if ((time > 0) && (time <= DEFAULT_TASK_PERIOD)) { + tick = 1; + } else { + tick = time / DEFAULT_TASK_PERIOD; + } + return tick; +} +} diff --git a/frameworks/components/ui_image_view.cpp b/frameworks/components/ui_image_view.cpp new file mode 100755 index 0000000..2fdd403 --- /dev/null +++ b/frameworks/components/ui_image_view.cpp @@ -0,0 +1,424 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "components/ui_image_view.h" +#include "common/image.h" +#include "common/typed_text.h" +#include "draw/draw_image.h" +#include "draw/draw_label.h" +#include "draw/draw_rect.h" +#include "file.h" +#include "image_info.h" +#include "imgdecode/cache_manager.h" +#include "mem_api.h" +#ifndef VERSION_LITE +#include "gif_lib.h" +#endif + +namespace OHOS { +#ifndef VERSION_LITE +class GifImageAnimator : public Animator, public AnimatorCallback { +public: + GifImageAnimator(UIView* view, const char* src) + : Animator(this, view, 0, true), + gifFileType_(nullptr), + imageIndex_(0), + delayTime_(0), + lastRunTime_(0), + deltaTime_(0), + gifDataSize_(0), + src_(src) + { + } + + virtual ~GifImageAnimator() + { + CloseGifFile(); + } + + void Callback(UIView* view) override; + + void SetGifFileType(GifFileType* gifFileType) + { + gifFileType_ = gifFileType; + } + + uint32_t SetGifFrame(GifFileType* gifFileType, int32_t imageIndex, UIImageView* imageView) const; + void DealGifImageData(const GifFileType* gifFileType, + const GifImageDesc* gifImageDesc, + const SavedImage* savedImage, + GraphicsControlBlock gcb, + const ColorMapObject* colorMap) const; + const void OpenGifFile(const char* src); + void CloseGifFile(); + +private: + GifFileType* GetGifFileType() + { + if (gifFileType_ == nullptr) { + OpenGifFile(src_); + } + return gifFileType_; + } + + GifFileType* gifFileType_; + int32_t imageIndex_; + uint32_t delayTime_; + uint32_t lastRunTime_; + uint32_t deltaTime_; + uint32_t gifDataSize_; + uint8_t* gifImageData_ = nullptr; + const char* src_; +}; + +const void GifImageAnimator::OpenGifFile(const char* src) +{ + int32_t error = D_GIF_SUCCEEDED; + GifFileType* gifFileType = DGifOpenFileName(src, &error); + if (error != D_GIF_SUCCEEDED) { + return; + } + DGifSlurp(gifFileType); + /* 3 : when change single pixel to byte, the buffer should divided by 8, equal to shift right 3 bits. */ + uint8_t pixelByteSize = DrawUtils::GetPxSizeByColorMode(ARGB8888) >> 3; + gifDataSize_ = gifFileType->SWidth * gifFileType->SHeight * pixelByteSize; + gifImageData_ = static_cast(UIMalloc(gifDataSize_)); + if (gifImageData_ == nullptr) { + CloseGifFile(); + return; + } + SetGifFileType(gifFileType); +} + +void GifImageAnimator::CloseGifFile() +{ + GifFileType* gifFileType = GetGifFileType(); + if (gifFileType != nullptr) { + DGifCloseFile(gifFileType, nullptr); + } + if (gifImageData_ != nullptr) { + UIFree(reinterpret_cast(const_cast(gifImageData_))); + gifImageData_ = nullptr; + } +} + +void GifImageAnimator::Callback(UIView* view) +{ + if (view == nullptr) { + return; + } + UIImageView* imageView = static_cast(view); + uint32_t curTime = GetRunTime(); + if (curTime != 0) { + if (curTime + deltaTime_ - lastRunTime_ >= delayTime_) { + deltaTime_ = curTime + deltaTime_ - lastRunTime_ - delayTime_; + lastRunTime_ = curTime; + } else { + return; + } + } + GifFileType* gifFileType = GetGifFileType(); + if (gifFileType != nullptr) { + delayTime_ = SetGifFrame(gifFileType, imageIndex_, imageView); + imageIndex_ = (imageIndex_ < gifFileType->ImageCount - 1) ? (imageIndex_ + 1) : 0; + } +} + +uint32_t GifImageAnimator::SetGifFrame(GifFileType* gifFileType, int32_t imageIndex, UIImageView* imageView) const +{ + SavedImage* savedImage = &(gifFileType->SavedImages[imageIndex]); + if (savedImage == nullptr) { + return 0; + } + GifImageDesc* gifImageDesc = &(savedImage->ImageDesc); + if (gifImageDesc == nullptr) { + return 0; + } + GraphicsControlBlock gcb; + int32_t ret = DGifSavedExtensionToGCB(gifFileType, imageIndex, &gcb); + if (ret != GIF_OK) { + return 0; + } + ColorMapObject* colorMap = nullptr; + if (gifImageDesc->ColorMap != nullptr) { + colorMap = gifImageDesc->ColorMap; + } else { + colorMap = gifFileType->SColorMap; + } + + DealGifImageData(gifFileType, gifImageDesc, savedImage, gcb, colorMap); + if (gifImageData_ == nullptr) { + return 0; + } + imageView->gifFrameFlag_ = true; + ImageInfo gifFrame; + gifFrame.header.width = gifFileType->SWidth; + gifFrame.header.height = gifFileType->SHeight; + gifFrame.header.colorMode = ARGB8888; + gifFrame.dataSize = gifDataSize_; + gifFrame.data = gifImageData_; + imageView->SetSrc(&gifFrame); + + if (gcb.DelayTime >= 0) { + return static_cast(gcb.DelayTime) * 10; // 10: change hundredths (1/100) of a second to millisecond + } else { + return 0; + } +} + +void GifImageAnimator::DealGifImageData(const GifFileType* gifFileType, + const GifImageDesc* gifImageDesc, + const SavedImage* savedImage, + GraphicsControlBlock gcb, + const ColorMapObject* colorMap) const +{ + if ((gifFileType == nullptr) || (gifImageDesc == nullptr) || (savedImage == nullptr) || + (savedImage->RasterBits == nullptr) || (colorMap == nullptr) || (colorMap->Colors == nullptr)) { + return; + } + uint8_t colorIndex = 0; + GifColorType* gifColorType = nullptr; + uint32_t index = 0; + bool transparentColor = true; + int32_t loc = 0; + for (int32_t x = 0; x < gifFileType->SHeight; x++) { + for (int32_t y = 0; y < gifFileType->SWidth; y++) { + transparentColor = true; + if ((x >= gifImageDesc->Top) && (x < gifImageDesc->Top + gifImageDesc->Height) && + (y >= gifImageDesc->Left) && (y < gifImageDesc->Left + gifImageDesc->Width)) { + loc = (x - gifImageDesc->Top) * gifImageDesc->Width + (y - gifImageDesc->Left); + colorIndex = savedImage->RasterBits[loc]; + + if ((gcb.DisposalMode != DISPOSE_DO_NOT) || (gcb.TransparentColor == NO_TRANSPARENT_COLOR) || + (colorIndex != gcb.TransparentColor)) { + transparentColor = false; + } + } + if (transparentColor) { + index += 4; // 4: skip color index, keep last frame color + } else { + gifColorType = &colorMap->Colors[colorIndex]; + gifImageData_[index++] = gifColorType->Blue; + gifImageData_[index++] = gifColorType->Green; + gifImageData_[index++] = gifColorType->Red; + gifImageData_[index++] = OPA_OPAQUE; + } + } + } +} +#endif + +UIImageView::UIImageView() + : imageWidth_(0), + imageHeight_(0), + autoEnable_(true), + needRefresh_(false), + colorFormat_(UNKNOW), + blurLevel_(BlurLevel::LEVEL0), + algorithm_(TransformAlgorithm::BILINEAR), + reserve_(0) +{ + style_ = &(StyleDefault::GetBackgroundTransparentStyle()); +#ifndef VERSION_LITE + gifImageAnimator_ = nullptr; + gifFrameFlag_ = false; +#endif +} + +UIImageView::~UIImageView() +{ +#ifndef VERSION_LITE + RemoveAndStopGifAnimator(); +#endif +} + +bool UIImageView::OnPreDraw(Rect& invalidatedArea) const +{ + if ((image_.GetSrcType() == IMG_SRC_UNKNOWN)) { + return true; + } + + if ((colorFormat_ == RGB565) || (colorFormat_ == RGB888)) { + if (GetRect().IsContains(invalidatedArea)) { + return true; + } + invalidatedArea.Intersect(invalidatedArea, GetRect()); + } + + return false; +} + +void UIImageView::OnDraw(const Rect& invalidatedArea) +{ + OpacityType opa = GetMixOpaScale(); + DrawRect::Draw(GetRect(), invalidatedArea, *style_, opa); + if ((imageHeight_ == 0) || (imageWidth_ == 0)) { + return; + } + + Rect viewRect = GetContentRect(); + Rect trunc(invalidatedArea); + if (trunc.Intersect(trunc, viewRect)) { + uint8_t srcType = image_.GetSrcType(); + if ((srcType == IMG_SRC_FILE) || (srcType == IMG_SRC_VARIABLE)) { + Rect cordsTmp; + cordsTmp.SetTop(viewRect.GetY()); + cordsTmp.SetBottom(viewRect.GetY() + imageHeight_ - 1); + + if ((transMap_ == nullptr) || transMap_->IsInvalid()) { + while (cordsTmp.GetTop() <= viewRect.GetBottom()) { + cordsTmp.SetLeft(viewRect.GetX()); + cordsTmp.SetRight(viewRect.GetX() + imageWidth_ - 1); + while (cordsTmp.GetLeft() <= viewRect.GetRight()) { + image_.DrawImage(cordsTmp, trunc, *style_, opa); + cordsTmp.SetLeft(cordsTmp.GetLeft() + imageWidth_); + cordsTmp.SetRight(cordsTmp.GetRight() + imageWidth_); + } + cordsTmp.SetTop(cordsTmp.GetTop() + imageHeight_); + cordsTmp.SetBottom(cordsTmp.GetBottom() + imageHeight_); + } + } else if ((transMap_ != nullptr) && !transMap_->IsInvalid()) { + ImageInfo imgInfo; + if (srcType == IMG_SRC_FILE) { + CacheEntry entry; + RetCode ret = CacheManager::GetInstance().Open(GetPath(), *style_, entry); + if (ret != RetCode::OK) { + return; + } + imgInfo = entry.GetImageInfo(); + } else { + imgInfo = *(GetImageInfo()); + } + uint8_t pxSize = DrawUtils::GetPxSizeByColorMode(imgInfo.header.colorMode); + TransformDataInfo imageTranDataInfo = {imgInfo.header, imgInfo.data, pxSize, + static_cast(blurLevel_), + static_cast(algorithm_)}; + + Rect origRect = GetOrigRect(); + transMap_->SetTransMapRect(origRect); + OpacityType opaScale = DrawUtils::GetMixOpacity(opa, style_->imageOpa_); + DrawUtils::GetInstance()->DrawTransform(invalidatedArea, {0, 0}, Color::Black(), opaScale, *transMap_, + imageTranDataInfo); + } + } + } +} + +void UIImageView::SetSrc(const char* src) +{ +#ifndef VERSION_LITE + if (src == nullptr) { + return; + } + const static uint8_t IMG_BYTES_TO_CHECK = 4; // 4: check 4 bytes of image file + char buf[IMG_BYTES_TO_CHECK] = {0}; + int32_t fd = open(src, O_RDONLY); + if (fd < 0) { + return; + } + if (read(fd, buf, IMG_BYTES_TO_CHECK) != IMG_BYTES_TO_CHECK) { + close(fd); + return; + } + close(fd); + bool updated = false; + RemoveAndStopGifAnimator(); + // 0x47 0x49 0x46: GIF file's header + if ((static_cast(buf[0]) == 0x47) && (static_cast(buf[1]) == 0x49) && + (static_cast(buf[2]) == 0x46)) { // 2: array index of GIF file's header + if (gifImageAnimator_ == nullptr) { + gifImageAnimator_ = new GifImageAnimator(this, src); + if (gifImageAnimator_ == nullptr) { + GRAPHIC_LOGE("new GifImageAnimator fail"); + return; + } + } + AddAndStartGifAnimator(); + updated = true; + } else { + updated = image_.SetSrc(src); + } +#else + bool updated = image_.SetSrc(src); +#endif + if (!updated) { + return; + } + needRefresh_ = true; + if (autoEnable_) { + UIImageView::ReMeasure(); + } + Invalidate(); +} + +void UIImageView::ReMeasure() +{ + if (!needRefresh_) { + return; + } + needRefresh_ = false; + + ImageHeader header = {0}; + image_.GetHeader(header); + + imageWidth_ = header.width; + imageHeight_ = header.height; + colorFormat_ = header.colorMode; + + if (autoEnable_) { + Resize(imageWidth_, imageHeight_); + Invalidate(); + } +} + +void UIImageView::SetSrc(const ImageInfo* src) +{ +#ifndef VERSION_LITE + if (!gifFrameFlag_ && (gifImageAnimator_ != nullptr)) { + RemoveAndStopGifAnimator(); + } + gifFrameFlag_ = false; +#endif + bool updated = image_.SetSrc(src); + if (!updated) { + return; + } + needRefresh_ = true; + if (autoEnable_) { + UIImageView::ReMeasure(); + } + Invalidate(); +} + +#ifndef VERSION_LITE +void UIImageView::AddAndStartGifAnimator() +{ + if (gifImageAnimator_ != nullptr) { + AnimatorManager::GetInstance()->Add(gifImageAnimator_); + gifImageAnimator_->Start(); + } +} + +void UIImageView::RemoveAndStopGifAnimator() +{ + if (gifImageAnimator_ != nullptr) { + gifImageAnimator_->Stop(); + AnimatorManager::GetInstance()->Remove(gifImageAnimator_); + delete gifImageAnimator_; + gifImageAnimator_ = nullptr; + } +} +#endif +} // namespace OHOS \ No newline at end of file diff --git a/frameworks/components/ui_label.cpp b/frameworks/components/ui_label.cpp new file mode 100644 index 0000000..f56f053 --- /dev/null +++ b/frameworks/components/ui_label.cpp @@ -0,0 +1,365 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "components/ui_label.h" +#include "font/ui_font.h" +#include "graphic_log.h" +#include "themes/theme_manager.h" + +namespace OHOS { +class LabelAnimator : public Animator, public AnimatorCallback { +public: + LabelAnimator(uint16_t textX, uint16_t labelX, int16_t startPos, UIView* view) + : Animator(this, view, 0, true), + startPos_(startPos), + textX_(textX), + labelX_(labelX), + offsetX_(startPos), + waitCount_(ANIM_WAIT_COUNT), + speed_(0), + preRunTime_(0), + decimal_(0) + { + AnimatorManager::GetInstance()->Add(this); + } + + virtual ~LabelAnimator() + { + AnimatorManager::GetInstance()->Remove(this); + } + + int16_t GetStartPos() const + { + return startPos_; + } + + void SetStartPos(int16_t pos) + { + startPos_ = pos; + } + + void UpdateWidth(uint16_t textWidth, uint16_t labelWidth) + { + textX_ = textWidth; + labelX_ = labelWidth; + waitCount_ = ANIM_WAIT_COUNT; + preRunTime_ = 0; + decimal_ = 0; + offsetX_ = startPos_; + static_cast(view_)->offsetX_ = offsetX_; + view_->Invalidate(); + } + + void Callback(UIView* view) override + { + if (view == nullptr) { + return; + } + + uint32_t curTime = GetRunTime(); + if (waitCount_ > 0) { + waitCount_--; + preRunTime_ = curTime; + return; + } + uint32_t time = (curTime > preRunTime_) ? (curTime - preRunTime_) : (UINT32_MAX - preRunTime_ + curTime); + // 1000: 1000 milliseconds is 1 second + float floatStep = (static_cast(time * speed_) / 1000) + decimal_; + uint16_t integerStep = static_cast(floatStep); + decimal_ = floatStep - integerStep; + preRunTime_ = curTime; + + if (integerStep != 0) { + offsetX_ -= integerStep; + } else { + return; + } + offsetX_ = ((offsetX_ - labelX_) % (textX_ + labelX_)) + labelX_; + static_cast(view)->offsetX_ = offsetX_; + view->Invalidate(); + } + + void SetAnimatorSpeed(uint16_t animSpeed) + { + speed_ = animSpeed; + decimal_ = 0; + } + +private: + static constexpr uint8_t ANIM_WAIT_COUNT = 50; + int16_t startPos_; + uint16_t textX_; + uint16_t labelX_; + int16_t offsetX_; + uint16_t waitCount_; + uint16_t speed_; + uint32_t preRunTime_; + float decimal_; +}; + +UILabel::UILabel() + : labelText_(nullptr), + needRefresh_(false), + useTextColor_(false), + hasAnimator_(false), + lineBreakMode_(LINE_BREAK_ELLIPSIS), + ellipsisIndex_(Text::TEXT_ELLIPSIS_END_INV), + offsetX_(0), + textColor_(Color::White()), + animator_{nullptr} +{ + Theme* theme = ThemeManager::GetInstance().GetCurrent(); + Style& style = (theme != nullptr) ? (theme->GetLabelStyle()) : (StyleDefault::GetLabelStyle()); + UIView::SetStyle(style); + animator_.speed = DEFAULT_ANIMATOR_SPEED; +} + +UILabel::~UILabel() +{ + if (hasAnimator_) { + animator_.animator->Stop(); + delete animator_.animator; + hasAnimator_ = false; + } + if (labelText_ != nullptr) { + delete labelText_; + labelText_ = nullptr; + } +} + +int16_t UILabel::GetWidth() +{ + InitLabelText(); + if (needRefresh_ && labelText_->IsExpandWidth()) { + ReMeasure(); + } + return UIView::GetWidth(); +} + +int16_t UILabel::GetHeight() +{ + InitLabelText(); + if (needRefresh_ && labelText_->IsExpandHeight()) { + ReMeasure(); + } + return UIView::GetHeight(); +} + +void UILabel::SetStyle(uint8_t key, int64_t value) +{ + UIView::SetStyle(key, value); + RefreshLabel(); +} + +void UILabel::SetText(const char* text) +{ + InitLabelText(); + labelText_->SetText(text); + if (labelText_->IsNeedRefresh()) { + RefreshLabel(); + } +} + +void UILabel::SetLineBreakMode(const uint8_t lineBreakMode) +{ + InitLabelText(); + if ((lineBreakMode >= LINE_BREAK_MAX) || (lineBreakMode_ == lineBreakMode)) { + return; + } + lineBreakMode_ = lineBreakMode; + if ((lineBreakMode_ == LINE_BREAK_ADAPT) || (lineBreakMode_ == LINE_BREAK_STRETCH) || + (lineBreakMode_ == LINE_BREAK_MARQUEE)) { + labelText_->SetExpandWidth(true); + } else { + labelText_->SetExpandWidth(false); + } + if ((lineBreakMode_ == LINE_BREAK_ADAPT) || (lineBreakMode_ == LINE_BREAK_WRAP)) { + labelText_->SetExpandHeight(true); + } else { + labelText_->SetExpandHeight(false); + } + RefreshLabel(); +} + +void UILabel::SetAlign(UITextLanguageAlignment horizontalAlign, UITextLanguageAlignment verticalAlign) +{ + InitLabelText(); + labelText_->SetAlign(horizontalAlign, verticalAlign); + if (labelText_->IsNeedRefresh()) { + RefreshLabel(); + } +} + +void UILabel::SetFontId(uint8_t fontId) +{ + InitLabelText(); + labelText_->SetFontId(fontId); + if (labelText_->IsNeedRefresh()) { + RefreshLabel(); + } +} + +void UILabel::SetFont(const char* name, uint8_t size) +{ + InitLabelText(); + labelText_->SetFont(name, size); + if (labelText_->IsNeedRefresh()) { + RefreshLabel(); + } +} + +uint16_t UILabel::GetTextWidth() +{ + InitLabelText(); + if (labelText_->IsNeedRefresh()) { + Style style = GetStyleConst(); + style.textColor_ = GetTextColor(); + labelText_->ReMeasureTextSize(GetContentRect(), style); + } + return labelText_->GetTextSize().x; +} + +uint16_t UILabel::GetTextHeight() +{ + InitLabelText(); + if (labelText_->IsNeedRefresh()) { + Style style = GetStyleConst(); + style.textColor_ = GetTextColor(); + labelText_->ReMeasureTextSize(GetContentRect(), style); + } + return labelText_->GetTextSize().y; +} + +void UILabel::SetWidth(int16_t width) +{ + if (GetWidth() != width) { + UIView::SetWidth(width); + RefreshLabel(); + } +} + +void UILabel::SetHeight(int16_t height) +{ + if (GetHeight() != height) { + UIView::SetHeight(height); + RefreshLabel(); + } +} + +void UILabel::RefreshLabel() +{ + Invalidate(); + ellipsisIndex_ = Text::TEXT_ELLIPSIS_END_INV; + if (!needRefresh_) { + needRefresh_ = true; + } +} + +void UILabel::ReMeasure() +{ + if (!needRefresh_) { + return; + } + needRefresh_ = false; + InitLabelText(); + Style style = GetStyleConst(); + style.textColor_ = GetTextColor(); + labelText_->ReMeasureTextSize(GetContentRect(), style); + Point textSize = labelText_->GetTextSize(); + switch (lineBreakMode_) { + case LINE_BREAK_ADAPT: + Resize(textSize.x, textSize.y); + break; + case LINE_BREAK_STRETCH: + SetWidth(textSize.x); + break; + case LINE_BREAK_WRAP: + SetHeight(textSize.y); + break; + case LINE_BREAK_ELLIPSIS: + ellipsisIndex_ = labelText_->GetEllipsisIndex(GetContentRect(), style); + break; + case LINE_BREAK_MARQUEE: + RemeasureForMarquee(textSize.x); + break; + default: + break; + } +} + +void UILabel::RemeasureForMarquee(int16_t textWidth) +{ + int16_t rectWidth = GetWidth(); + if (textWidth > rectWidth) { + offsetX_ = GetRollStartPos(); + if (labelText_->GetDirect() == TEXT_DIRECT_RTL) { + labelText_->SetAlign(TEXT_ALIGNMENT_RIGHT); + } else { + labelText_->SetAlign(TEXT_ALIGNMENT_LEFT); + } + if (hasAnimator_) { + static_cast(animator_.animator)->UpdateWidth(textWidth, rectWidth); + } else { + LabelAnimator* animator = new LabelAnimator(textWidth, rectWidth, offsetX_, this); + if (animator == nullptr) { + GRAPHIC_LOGE("new LabelAnimator fail"); + return; + } + animator->SetAnimatorSpeed(animator_.speed); + animator_.animator = animator; + hasAnimator_ = true; + } + animator_.animator->Start(); + } else { + offsetX_ = 0; + if (hasAnimator_) { + animator_.animator->Stop(); + } + } +} + +void UILabel::SetRollStartPos(int16_t pos) +{ + if (hasAnimator_) { + static_cast(animator_.animator)->SetStartPos(pos); + } else { + animator_.pos = pos; + } +} + +int16_t UILabel::GetRollStartPos() const +{ + return hasAnimator_ ? static_cast(animator_.animator)->GetStartPos() : animator_.pos; +} + +void UILabel::SetRollSpeed(uint16_t speed) +{ + if (hasAnimator_) { + static_cast(animator_.animator)->SetAnimatorSpeed(speed); + } else { + animator_.speed = speed; + } +} + +void UILabel::OnDraw(const Rect& invalidatedArea) +{ + InitLabelText(); + UIView::OnDraw(invalidatedArea); + Style style = GetStyleConst(); + style.textColor_ = GetTextColor(); + OpacityType opa = GetMixOpaScale(); + labelText_->OnDraw(invalidatedArea, GetOrigRect(), GetContentRect(), offsetX_, style, ellipsisIndex_, opa); +} +} // namespace OHOS \ No newline at end of file diff --git a/frameworks/components/ui_label_button.cpp b/frameworks/components/ui_label_button.cpp new file mode 100755 index 0000000..2839bdc --- /dev/null +++ b/frameworks/components/ui_label_button.cpp @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "components/ui_label_button.h" +#include "common/typed_text.h" +#include "draw/draw_label.h" +#include "font/ui_font.h" + +namespace OHOS { +UILabelButton::UILabelButton() : labelButtonText_(nullptr), offset_({ 0, 0 }) +{ + labelStyle_ = StyleDefault::GetDefaultStyle(); +} + +void UILabelButton::OnDraw(const Rect& invalidatedArea) +{ + UIButton::OnDraw(invalidatedArea); + + Rect textRect = GetContentRect(); + textRect.SetLeft(textRect.GetLeft() + offset_.x); + textRect.SetTop(textRect.GetTop() + offset_.y); + InitLabelButtonText(); + labelButtonText_->ReMeasureTextSize(textRect, labelStyle_); + OpacityType opa = GetMixOpaScale(); + uint16_t ellipsisIndex = labelButtonText_->GetEllipsisIndex(textRect, labelStyle_); + labelButtonText_->OnDraw(invalidatedArea, GetOrigRect(), textRect, 0, labelStyle_, ellipsisIndex, opa); +} + +UILabelButton::~UILabelButton() +{ + if (labelButtonText_ != nullptr) { + delete labelButtonText_; + labelButtonText_ = nullptr; + } +} +} // namespace OHOS diff --git a/frameworks/components/ui_list.cpp b/frameworks/components/ui_list.cpp new file mode 100755 index 0000000..815d435 --- /dev/null +++ b/frameworks/components/ui_list.cpp @@ -0,0 +1,763 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "components/ui_list.h" + +namespace OHOS { +UIList::Recycle::~Recycle() +{ + ListNode* node = scrapView_.Begin(); + while (node != scrapView_.End()) { + if (node->data_) { + UIView* deleteView = node->data_; + if (deleteView != nullptr) { + delete deleteView; + deleteView = nullptr; + node->data_ = nullptr; + } + } + node = node->next_; + } + scrapView_.Clear(); +} + +void UIList::Recycle::InitRecycle() +{ + if ((adapter_ == nullptr) || (listView_ == nullptr)) { + return; + } + FillActiveView(); + listView_->Invalidate(); +} + +UIView* UIList::Recycle::GetView(int16_t index) +{ + if (adapter_ == nullptr) { + return nullptr; + } + UIView* inView = nullptr; + UIView* retView = nullptr; + + if (scrapView_.Size() != 0) { + inView = scrapView_.Back(); + } + + retView = adapter_->GetView(inView, index); + if (retView != nullptr) { + retView->SetViewIndex(index); + scrapView_.PopBack(); + } + return retView; +} + +void UIList::Recycle::FillActiveView() +{ + if ((adapter_ == nullptr) || (listView_ == nullptr)) { + return; + } + uint16_t index = listView_->GetStartIndex(); + if (listView_->GetDirection() == UIList::VERTICAL) { + int16_t childBottom = 0; + while ((index < adapter_->GetCount()) && (childBottom < listView_->GetHeight())) { + UIView* view = GetView(index); + if (view == nullptr) { + break; + } + listView_->PushBack(view); + if (listView_->childrenTail_) { + childBottom = + listView_->childrenTail_->GetY() + listView_->childrenTail_->GetRelativeRect().GetHeight(); + } else { + break; + } + index++; + } + } else { + int16_t childRight = 0; + while ((index < adapter_->GetCount()) && (childRight < listView_->GetWidth())) { + UIView* view = GetView(index); + listView_->PushBack(view); + if (listView_->childrenTail_) { + childRight = listView_->childrenTail_->GetX() + listView_->childrenTail_->GetRelativeRect().GetWidth(); + } else { + break; + } + index++; + } + } +} + +UIList::UIList() + : onSelectedView_(nullptr), + isLoopList_(false), + isReCalculateDragEnd_(true), + autoAlign_(false), + startIndex_(0), + topIndex_(0), + bottomIndex_(0), + selectPosition_(0), + onSelectedIndex_(0), + recycle_(this), + scrollListener_(nullptr) +{ +#if ENABLE_ROTATE_INPUT + rotateFactor_ = 1; + rotateThreshold_ = 4; // 4: which means 25% of half view size +#endif +#if ENABLE_MOTOR + motorType_ = MotorType::MOTOR_TYPE_ONE; +#endif + direction_ = VERTICAL; + touchable_ = true; + draggable_ = true; + dragParentInstead_ = false; +} + +UIList::UIList(uint8_t direction) + : onSelectedView_(nullptr), + isLoopList_(false), + isReCalculateDragEnd_(true), + autoAlign_(false), + startIndex_(0), + topIndex_(0), + bottomIndex_(0), + selectPosition_(0), + onSelectedIndex_(0), + recycle_(this), + scrollListener_(nullptr) +{ +#if ENABLE_ROTATE_INPUT + rotateFactor_ = 1; + rotateThreshold_ = 4; // 4: which means 25% of half view size +#endif +#if ENABLE_MOTOR + motorType_ = MotorType::MOTOR_TYPE_ONE; +#endif + direction_ = direction; + touchable_ = true; + draggable_ = true; + dragParentInstead_ = false; +} + +UIList::~UIList() +{ + UIView* view = GetChildrenHead(); + while (view != nullptr) { + UIView* tmp = view->GetNextSibling(); + delete view; + view = tmp; + } +} + +bool UIList::OnDragEvent(const DragEvent& event) +{ + if (scrollAnimator_.GetState() != Animator::STOP) { + UIAbstractScroll::StopAnimator(); + } + int16_t xDistance = event.GetDeltaX(); + int16_t yDistance = event.GetDeltaY(); + isReCalculateDragEnd_ = true; + if (direction_ == VERTICAL) { + RefreshDeltaY(yDistance); + DragYInner(yDistance); + } else { + DragXInner(xDistance); + } + return UIView::OnDragEvent(event); +} + +bool UIList::OnDragEndEvent(const DragEvent& event) +{ + Point last = event.GetPreLastPoint(); + Point current = event.GetLastPoint(); + if ((last.x == current.x) && (last.y == current.y)) { + last = current; + current = event.GetCurrentPos(); + } + isReCalculateDragEnd_ = false; + if (!DragThrowAnimator(current, last)) { + if (scrollListener_ && (scrollListener_->GetScrollState() == ListScrollListener::SCROLL_STATE_MOVE)) { + scrollListener_->SetScrollState(ListScrollListener::SCROLL_STATE_STOP); + scrollListener_->OnScrollEnd(onSelectedIndex_, onSelectedView_); + } + } + return UIView::OnDragEndEvent(event); +} + +bool UIList::OnPressEvent(const PressEvent& event) +{ + StopAnimator(); + return UIView::OnPressEvent(event); +} + +#if ENABLE_ROTATE_INPUT +bool UIList::OnRotateEvent(const RotateEvent& event) +{ + int16_t tmpRotateLen = static_cast(event.GetRotate() * rotateFactor_); + int16_t midPointX = static_cast(GetWidth() / 2); // 2 : Get the middle point X coord of the view + int16_t midPointY = static_cast(GetHeight() / 2); // 2 : Get the middle point Y coord of the view + Point last, current; +#if ENABLE_MOTOR + MotorFunc motorFunc = FocusManager::GetInstance()->GetMotorFunc(); +#endif + + if (!throwDrag_ || ((MATH_ABS(tmpRotateLen) < (midPointX / rotateThreshold_)) && + (MATH_ABS(tmpRotateLen) < (midPointY / rotateThreshold_)))) { + ScrollBy(tmpRotateLen); + if (event.GetRotate() == 0) { + isReCalculateDragEnd_ = false; + DragThrowAnimator(Point{0, 0}, Point{0, 0}); + } + } else { + tmpRotateLen += tmpRotateLen; + last = Point{midPointX, midPointY}; + if (direction_ == VERTICAL) { + current = Point{midPointX, static_cast(midPointY + tmpRotateLen)}; + } else { + current = Point{static_cast(midPointX + tmpRotateLen), midPointY}; + } + isReCalculateDragEnd_ = false; + DragThrowAnimator(current, last); +#if ENABLE_MOTOR + if (motorFunc != nullptr && motorType_ == MotorType::MOTOR_TYPE_TWO) { + motorFunc(MotorType::MOTOR_TYPE_TWO); + } +#endif + } + +#if ENABLE_MOTOR + if (motorFunc != nullptr && motorType_ == MotorType::MOTOR_TYPE_ONE) { + motorFunc(MotorType::MOTOR_TYPE_ONE); + } +#endif + + return UIView::OnRotateEvent(event); +} +#endif + +void UIList::ScrollBy(int16_t distance) +{ + if (direction_ == VERTICAL) { + DragYInner(distance); + } else { + DragXInner(distance); + } + if (scrollListener_ && (scrollListener_->GetScrollState() == ListScrollListener::SCROLL_STATE_MOVE)) { + scrollListener_->SetScrollState(ListScrollListener::SCROLL_STATE_STOP); + scrollListener_->OnScrollEnd(onSelectedIndex_, onSelectedView_); + } +} + +bool UIList::DragXInner(int16_t distance) +{ + if (IsNeedReCalculateDragEnd()) { + return false; + } + int16_t listWidth = GetWidth(); + if (distance == 0) { + return true; + } + int16_t reboundSize = reboundSize_; + if (isLoopList_ || (scrollAnimator_.GetState() != Animator::STOP)) { + reboundSize = 0; + } + bool ret = 0; + do { + ret = MoveChildStep(distance); + } while (ret); + + if (isLoopList_) { + return MoveOffset(distance); + } + if (distance > 0) { + if (childrenHead_ && (childrenHead_->GetX() + distance > scrollBlankSize_ + reboundSize)) { + distance = scrollBlankSize_ + reboundSize - childrenHead_->GetX(); + } + } else { + if (childrenTail_) { + if (childrenTail_->GetRelativeRect().GetRight() <= listWidth - scrollBlankSize_ - reboundSize) { + distance = 0; + } else if (listWidth - (childrenTail_->GetX() + childrenTail_->GetRelativeRect().GetWidth() + distance) > + scrollBlankSize_ + reboundSize) { + distance = listWidth - scrollBlankSize_ - reboundSize - childrenTail_->GetX() - + childrenTail_->GetRelativeRect().GetWidth(); + } + } + } + return MoveOffset(distance); +} + +bool UIList::DragYInner(int16_t distance) +{ + if (IsNeedReCalculateDragEnd()) { + return false; + } + int16_t listHeigh = GetHeight(); + if (distance == 0) { + return true; + } + int16_t reboundSize = reboundSize_; + if (isLoopList_ || (scrollAnimator_.GetState() != Animator::STOP)) { + reboundSize = 0; + } + bool ret = 0; + do { + ret = MoveChildStep(distance); + } while (ret); + + if (isLoopList_) { + return MoveOffset(distance); + } + if (distance > 0) { + if (childrenHead_ && (childrenHead_->GetY() + distance > scrollBlankSize_ + reboundSize)) { + distance = scrollBlankSize_ + reboundSize - childrenHead_->GetY(); + } + } else { + if (childrenTail_) { + if (childrenTail_->GetRelativeRect().GetBottom() <= listHeigh - scrollBlankSize_ - reboundSize) { + distance = 0; + } else if (listHeigh - (childrenTail_->GetY() + childrenTail_->GetRelativeRect().GetHeight() + distance) > + scrollBlankSize_ + reboundSize) { + distance = listHeigh - scrollBlankSize_ - reboundSize - childrenTail_->GetY() - + childrenTail_->GetRelativeRect().GetHeight(); + } + } + } + return MoveOffset(distance); +} + +bool UIList::MoveOffset(int16_t offset) +{ + if (offset == 0) { + return false; + } + if (direction_ == VERTICAL) { + MoveChildByOffset(0, offset); + } else { + MoveChildByOffset(offset, 0); + } + Invalidate(); + if (scrollListener_ && (scrollListener_->GetScrollState() == ListScrollListener::SCROLL_STATE_STOP)) { + scrollListener_->SetScrollState(ListScrollListener::SCROLL_STATE_MOVE); + scrollListener_->OnScrollStart(onSelectedIndex_, onSelectedView_); + } + + return true; +} + +bool UIList::IsNeedReCalculateDragEnd() +{ + if (!autoAlign_ || isReCalculateDragEnd_ || (onSelectedView_ == nullptr)) { + return false; + } + int16_t animationLess = 0; + if (direction_ == VERTICAL) { + animationLess = animatorCallback_.endValueY_ - animatorCallback_.previousValueY_; + } else { + animationLess = animatorCallback_.endValueX_ - animatorCallback_.previousValueX_; + } + if (!isDragging_ || (MATH_ABS(animationLess) > RECALCULATE_DRAG_DISTANCE)) { + return false; + } + return true; +} +bool UIList::ReCalculateDragEnd() +{ + if ((onSelectedView_ == nullptr) || isReCalculateDragEnd_ || !autoAlign_) { + return false; + } + + int16_t offsetX = 0; + int16_t offsetY = 0; + if (direction_ == VERTICAL) { + // 2: half + offsetY = selectPosition_ - (onSelectedView_->GetY() + (onSelectedView_->GetRelativeRect().GetHeight() / 2)); + } else { + // 2: half + offsetX = selectPosition_ - (onSelectedView_->GetX() + (onSelectedView_->GetRelativeRect().GetWidth() / 2)); + } + animatorCallback_.SetDragStartValue(0, 0); + animatorCallback_.SetDragEndValue(offsetX, offsetY); + animatorCallback_.SetDragTimes(RECALCULATE_DRAG_TIMES * DRAG_ACC_FACTOR / GetDragACCLevel()); + scrollAnimator_.Start(); + isReCalculateDragEnd_ = true; + return true; +} + +bool UIList::MoveChildStepInner(int16_t distance, + int16_t (UIView::*pfnGetXOrY)() const, + int16_t (Rect::*pfnGetWidthOrHeight)() const) +{ + bool popRet = false; + bool pushRet = false; + if (distance > 0) { + if ((childrenHead_ == nullptr) || ((childrenHead_->*pfnGetXOrY)() + distance > 0)) { + uint16_t index = GetIndexDec(topIndex_); + if (index == topIndex_) { + return false; + } + UIView* newView = recycle_.GetView(index); + if (newView == nullptr) { + return false; + } + PushFront(newView); + pushRet = true; + } + if (childrenTail_ != nullptr && + ((childrenTail_->*pfnGetXOrY)() + distance > (this->GetRelativeRect().*pfnGetWidthOrHeight)())) { + PopItem(childrenTail_); + popRet = true; + } + } else { + if ((childrenTail_ == nullptr) || + ((childrenTail_->*pfnGetXOrY)() + (childrenTail_->GetRelativeRect().*pfnGetWidthOrHeight)() + distance < + (this->GetRelativeRect().*pfnGetWidthOrHeight)())) { + UIView* newView = recycle_.GetView(GetIndexInc(bottomIndex_)); + if (newView == nullptr) { + return false; + } + PushBack(newView); + pushRet = true; + } + if (childrenHead_ && + (childrenHead_->*pfnGetXOrY)() + distance + (childrenHead_->GetRelativeRect().*pfnGetWidthOrHeight)() < 0) { + PopItem(childrenHead_); + popRet = true; + } + } + return (popRet || pushRet); +} + +bool UIList::MoveChildStep(int16_t distance) +{ + if (direction_ == VERTICAL) { + return MoveChildStepInner(distance, &UIView::GetY, &Rect::GetHeight); + } else { + return MoveChildStepInner(distance, &UIView::GetX, &Rect::GetWidth); + } +} + +void UIList::SetAdapter(AbstractAdapter* adapter) +{ + recycle_.SetAdapter(adapter); + recycle_.InitRecycle(); +} + +UIView* UIList::GetSelectView() +{ + if (onSelectedView_ != nullptr) { + return onSelectedView_; + } + if ((childrenHead_ == nullptr) || (selectPosition_ == 0)) { + return nullptr; + } + UIView* child = childrenHead_; + while (child != nullptr) { + if (direction_ == VERTICAL) { + if ((child->GetY() <= selectPosition_) && + (child->GetY() + child->GetRelativeRect().GetHeight() >= selectPosition_)) { + if (scrollListener_ != nullptr) { + scrollListener_->OnItemSelected(child->GetViewIndex(), child); + } + return child; + } + } else { + if ((child->GetX() <= selectPosition_) && + (child->GetX() + child->GetRelativeRect().GetWidth() >= selectPosition_)) { + if (scrollListener_ != nullptr) { + scrollListener_->OnItemSelected(child->GetViewIndex(), child); + } + return child; + } + } + child = child->GetNextSibling(); + } + return nullptr; +} + +void UIList::PushBack(UIView* view) +{ + if (view == nullptr) { + return; + } + if (childrenTail_ == nullptr) { + SetHead(view); + } else { + if (direction_ == VERTICAL) { + view->SetPosition(0, childrenTail_->GetY() + childrenTail_->GetRelativeRect().GetHeight()); + } else { + view->SetPosition(childrenTail_->GetX() + childrenTail_->GetRelativeRect().GetWidth(), 0); + } + bottomIndex_ = GetIndexInc(bottomIndex_); + } + + view->SetDragParentInstead(true); + UIViewGroup::Add(view); +} + +void UIList::PushFront(UIView* view) +{ + if (view == nullptr) { + return; + } + if (GetChildrenHead() == nullptr) { + SetHead(view); + } else { + if (direction_ == VERTICAL) { + view->SetPosition(0, GetChildrenHead()->GetY() - view->GetRelativeRect().GetHeight()); + } else { + view->SetPosition(GetChildrenHead()->GetX() - view->GetRelativeRect().GetWidth(), 0); + } + topIndex_ = GetIndexDec(topIndex_); + } + view->SetDragParentInstead(true); + UIViewGroup::Insert(nullptr, view); +} + +void UIList::PopItem(UIView* view) +{ + if (view == nullptr) { + return; + } + recycle_.AddScrapView(view); + if (view == GetChildrenHead()) { + topIndex_ = GetIndexInc(topIndex_); + } + + if (view == childrenTail_) { + bottomIndex_ = GetIndexDec(bottomIndex_); + } + UIViewGroup::Remove(view); +} + +void UIList::SetHead(UIView* view) +{ + if (view != nullptr) { + view->SetPosition(0, 0); + topIndex_ = startIndex_; + bottomIndex_ = startIndex_; + } +} + +void UIList::MoveChildByOffset(int16_t xOffset, int16_t yOffset) +{ + UIView* view = GetChildrenHead(); + if (view == nullptr) { + return; + } + int16_t x; + int16_t y; + int16_t height; + int16_t width; + + if ((onSelectedIndex_ != NULL_SELECT_INDEX) && (selectPosition_ != 0)) { + if (direction_ == VERTICAL) { + height = view->GetRelativeRect().GetHeight(); + if ((GetChildrenHead()->GetY() + yOffset > selectPosition_) || + (childrenTail_->GetY() + height + yOffset < selectPosition_)) { + onSelectedIndex_ = NULL_SELECT_INDEX; + onSelectedView_ = nullptr; + if (scrollListener_ != nullptr) { + scrollListener_->OnItemSelected(onSelectedIndex_, onSelectedView_); + } + } + } else { + width = view->GetRelativeRect().GetWidth(); + if ((GetChildrenHead()->GetX() + xOffset > selectPosition_) || + (childrenTail_->GetX() + width < selectPosition_)) { + onSelectedIndex_ = NULL_SELECT_INDEX; + onSelectedView_ = nullptr; + if (scrollListener_ != nullptr) { + scrollListener_->OnItemSelected(onSelectedIndex_, onSelectedView_); + } + } + } + } + bool isSelectViewFind = false; + while (view != nullptr) { + x = view->GetX() + xOffset; + y = view->GetY() + yOffset; + view->SetPosition(x, y); + if ((selectPosition_ != 0) && !isSelectViewFind) { + if (direction_ == VERTICAL) { + height = view->GetRelativeRect().GetHeight(); + /* Views may be the same but have different indexes because of view recycling. */ + if ((y <= selectPosition_) && (y + height >= selectPosition_) && + ((onSelectedView_ != view) || (onSelectedIndex_ != view->GetViewIndex()))) { + onSelectedIndex_ = view->GetViewIndex(); + onSelectedView_ = view; + if (scrollListener_ != nullptr) { + scrollListener_->OnItemSelected(onSelectedIndex_, onSelectedView_); + } + isSelectViewFind = true; + } + } else { + width = view->GetRelativeRect().GetWidth(); + if ((x <= selectPosition_) && (x + width >= selectPosition_) && + ((onSelectedView_ != view) || (onSelectedIndex_ != view->GetViewIndex()))) { + onSelectedIndex_ = view->GetViewIndex(); + onSelectedView_ = view; + if (scrollListener_ != nullptr) { + scrollListener_->OnItemSelected(onSelectedIndex_, onSelectedView_); + } + isSelectViewFind = true; + } + } + } + view = view->GetNextSibling(); + } +} + +void UIList::StopAnimator() +{ + UIAbstractScroll::StopAnimator(); + if (!ReCalculateDragEnd()) { + if ((scrollListener_ != nullptr) && + (scrollListener_->GetScrollState() == ListScrollListener::SCROLL_STATE_MOVE)) { + scrollListener_->SetScrollState(ListScrollListener::SCROLL_STATE_STOP); + scrollListener_->OnScrollEnd(onSelectedIndex_, onSelectedView_); + } + } +} + +uint16_t UIList::GetIndexInc(uint16_t index) +{ + uint16_t ret = index + 1; + if (isLoopList_ && (recycle_.GetAdapterItemCount() != 0)) { + ret = ret % recycle_.GetAdapterItemCount(); + } + return ret; +} + +uint16_t UIList::GetIndexDec(uint16_t index) +{ + if (index == 0) { + if (isLoopList_) { + return recycle_.GetAdapterItemCount() - 1; + } else { + return 0; + } + } else { + return index - 1; + } +} + +void UIList::ScrollTo(uint16_t index) +{ + UIView* child = GetChildrenHead(); + UIView* tmp = nullptr; + while (child != nullptr) { + tmp = child; + child = child->GetNextSibling(); + PopItem(tmp); + } + onSelectedView_ = nullptr; + SetStartIndex(index); + recycle_.InitRecycle(); +} + +void UIList::RefreshList() +{ + int16_t topIndex = topIndex_; + UIView* child = GetChildrenHead(); + UIView* tmp = nullptr; + int16_t offset = 0; + if (child != nullptr) { + if (direction_ == VERTICAL) { + offset = child->GetY(); + } else { + offset = child->GetX(); + } + } + + while (child != nullptr) { + tmp = child; + child = child->GetNextSibling(); + PopItem(tmp); + } + onSelectedView_ = nullptr; + + uint16_t tmpStartIndex = startIndex_; + if (topIndex > recycle_.GetAdapterItemCount() - 1) { + startIndex_ = 0; + offset = 0; + } else { + startIndex_ = topIndex; + } + recycle_.InitRecycle(); + startIndex_ = tmpStartIndex; + + if (direction_ == VERTICAL) { + DragYInner(offset); + } else { + DragXInner(offset); + } + Invalidate(); +} + +void UIList::RemoveAll() +{ + UIViewGroup::RemoveAll(); + recycle_.ClearScrapView(); +} + +void UIList::CalculateReboundDistance(int16_t& dragDistanceX, int16_t& dragDistanceY) +{ + if (isLoopList_) { + return; + } + Rect rect = GetAllChildRelativeRect(); + int16_t top = rect.GetTop(); + int16_t bottom = rect.GetBottom(); + int16_t scrollHeight = GetHeight(); + int16_t left = rect.GetLeft(); + int16_t right = rect.GetRight(); + int16_t scrollWidth = GetWidth(); + if ((direction_ == VERTICAL) || (direction_ == HORIZONTAL_AND_VERTICAL)) { + if (top > scrollBlankSize_) { + if ((dragDistanceY + top) > (scrollBlankSize_ + reboundSize_)) { + dragDistanceY = 0; + } + dragDistanceY += scrollBlankSize_ - (top + dragDistanceY); + } + if (bottom < (scrollHeight - scrollBlankSize_ - 1)) { + if ((dragDistanceY + bottom) < (scrollHeight - scrollBlankSize_ - reboundSize_ - 1)) { + dragDistanceY = 0; + } + dragDistanceY += scrollHeight - scrollBlankSize_ - 1 - (bottom + dragDistanceY); + } + } else { + if (left > scrollBlankSize_) { + if ((dragDistanceX + left) > (scrollBlankSize_ + reboundSize_)) { + dragDistanceX = 0; + } + dragDistanceX += scrollBlankSize_ - (left + dragDistanceX); + } + if (right < (scrollWidth - scrollBlankSize_ - 1)) { + if ((dragDistanceX + right) < (scrollWidth - scrollBlankSize_ - reboundSize_ - 1)) { + dragDistanceX = 0; + } + dragDistanceX += scrollWidth - scrollBlankSize_ - 1 - (right + dragDistanceX); + } + } +} + +#if ENABLE_MOTOR +void UIList::SetMotorType(MotorType motorType) +{ + motorType_ = motorType; +} +#endif +} // namespace OHOS diff --git a/frameworks/components/ui_picker.cpp b/frameworks/components/ui_picker.cpp new file mode 100755 index 0000000..5380931 --- /dev/null +++ b/frameworks/components/ui_picker.cpp @@ -0,0 +1,462 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "components/ui_picker.h" +#include "draw/draw_line.h" +#include "draw/draw_rect.h" +#include "themes/theme_manager.h" + +namespace OHOS { +class PickerListScrollListener : public ListScrollListener { +public: + PickerListScrollListener(UIPicker* picker, UIList* list) + : listView_(list), pickerView_(picker), selectView_(nullptr), lastSelectView_(nullptr), + selectIndex_(0), isInitted_(false){} + + virtual ~PickerListScrollListener() {} + + void OnItemSelected(int16_t index, UIView* view) override + { + if (!isInitted_) { + return; + } + + if ((lastSelectView_ != nullptr) && (listView_ != nullptr) && (pickerView_ != nullptr) && (view != nullptr)) { + lastSelectView_->SetStyle(STYLE_TEXT_COLOR, pickerView_->GetBackgroundTextColor().full); + if (pickerView_->backgroundFontName_ == nullptr) { + static_cast(lastSelectView_)->SetFontId(pickerView_->backgroundFontId_); + } else { + static_cast(lastSelectView_) + ->SetFont(pickerView_->backgroundFontName_, pickerView_->backgroundFontSize_); + } + view->SetStyle(STYLE_TEXT_COLOR, pickerView_->GetHighlightTextColor().full); + if (pickerView_->highlightFontName_ == nullptr) { + static_cast(view)->SetFontId(pickerView_->highlightFontId_); + } else { + static_cast(view) + ->SetFont(pickerView_->highlightFontName_, pickerView_->highlightFontSize_); + } + lastSelectView_ = view; + selectIndex_ = index; + listView_->Invalidate(); + } + } + + void OnScrollEnd(int16_t index, UIView* view) override + { + if ((view == nullptr) || (listView_ == nullptr) || (pickerView_ == nullptr)) { + return; + } + + if (lastSelectView_ != nullptr) { + lastSelectView_->SetStyle(STYLE_TEXT_COLOR, pickerView_->GetBackgroundTextColor().full); + if (pickerView_->backgroundFontName_ == nullptr) { + static_cast(lastSelectView_)->SetFontId(pickerView_->backgroundFontId_); + } else { + static_cast(lastSelectView_) + ->SetFont(pickerView_->backgroundFontName_, pickerView_->backgroundFontSize_); + } + lastSelectView_ = view; + } + + view->SetStyle(STYLE_TEXT_COLOR, pickerView_->GetHighlightTextColor().full); + if (pickerView_->highlightFontName_ == nullptr) { + static_cast(view)->SetFontId(pickerView_->highlightFontId_); + } else { + static_cast(view) + ->SetFont(pickerView_->highlightFontName_, pickerView_->highlightFontSize_); + } + + listView_->Invalidate(); + selectView_ = view; + selectIndex_ = index; + + if (pickerView_->pickerListener_) { + pickerView_->pickerListener_->OnPickerStoped(*pickerView_); + } + } + + void SetSelectView(UIView* view) + { + selectView_ = view; + lastSelectView_ = view; + } + + const UIView* GetSelectView() const + { + return selectView_; + } + + void SetSelectIndex(uint16_t index) + { + selectIndex_ = index; + } + + uint16_t GetSelectIndex() const + { + return selectIndex_; + } + + void SetInitStatus(bool status) + { + isInitted_ = status; + } + +private: + UIList* listView_; + UIPicker* pickerView_; + UIView* selectView_; + UIView* lastSelectView_; + uint16_t selectIndex_; + bool isInitted_; +}; + +UIPicker::UIPicker() + : isWidthSet_(false), + isHeightSet_(false), + textAdapter_(nullptr), + maxCount_(0), + setSelectedIndex_(0), + backgroundFontSize_(0), + highlightFontSize_(0), + backgroundFontName_(nullptr), + highlightFontName_(nullptr), + itemsWidth_(0), + itemsHeight_(0), + rangeValue_(nullptr), + rangeValueCount_(0), + startValue_(0), + endValue_(0), + isSetAdaptered_(false), + pickerListener_(nullptr) +{ + Theme* theme = ThemeManager::GetInstance().GetCurrent(); + if (theme != nullptr) { + style_ = &(theme->GetPickerBackgroundStyle()); + } else { + style_ = &(StyleDefault::GetPickerBackgroundStyle()); + } + backgroundFontId_ = style_->font_; + backgroundColor_ = style_->textColor_; + direct_ = UITextLanguageDirect::TEXT_DIRECT_LTR; + + if (theme != nullptr) { + style_ = &(theme->GetPickerHighlightStyle()); + } else { + style_ = &(StyleDefault::GetPickerHighlightStyle()); + } + highlightFontId_ = style_->font_; + highlightColor_ = style_->textColor_; + + list_.SetThrowDrag(true); +#if ENABLE_ROTATE_INPUT + list_.rotateFactor_ = 5; // 5: need to fit device to change + list_.rotateThreshold_ = 20; // 20: which means 20% of half view size +#endif +#if ENABLE_MOTOR + list_.SetMotorType(MotorType::MOTOR_TYPE_TWO); +#endif + list_.SetLoopState(false); + list_.EnableAutoAlign(true); + list_.SetIntercept(false); + PickerListScrollListener* listener = new PickerListScrollListener(this, &list_); + list_.SetScrollStateListener(listener); + listListener_ = static_cast(listener); + Add(&list_); +} + +UIPicker::~UIPicker() +{ + ClearValues(); + Remove(&list_); + if (listListener_ != nullptr) { + delete static_cast(listListener_); + listListener_ = nullptr; + } + + if (backgroundFontName_ != nullptr) { + UIFree(backgroundFontName_); + backgroundFontName_ = nullptr; + } + + if (highlightFontName_ != nullptr) { + UIFree(highlightFontName_); + highlightFontName_ = nullptr; + } + + if (textAdapter_ != nullptr) { + delete textAdapter_; + textAdapter_ = nullptr; + } +} + +bool UIPicker::SetValues(int16_t start, int16_t end) +{ + if (start > end) { + return false; + } + + startValue_ = start; + endValue_ = end; + return RefreshValues(start, end); +} + +bool UIPicker::SetValues(const char* value[], uint16_t count) +{ + if (value == nullptr) { + return false; + } + + rangeValue_ = value; + rangeValueCount_ = count; + return RefreshValues(value, count); +} + +void UIPicker::Refresh() +{ + if (rangeValue_) { + RefreshValues(rangeValue_, rangeValueCount_); + } else if ((startValue_ != 0) || (endValue_ != 0)) { + RefreshValues(startValue_, endValue_); + } +} + +bool UIPicker::RefreshValues(int16_t start, int16_t end) +{ + if (!isWidthSet_ || !isHeightSet_ || !itemsHeight_ || ((start == 0) && (end == 0))) { + return false; + } + + ClearList(); + InitTextAdapter(); + textAdapter_->SetData(start, end); + maxCount_ = end - start + 1; + RefreshList(); + if (setSelectedIndex_) { + RefreshSelected(setSelectedIndex_); + } + return true; +} + +bool UIPicker::RefreshValues(const char* value[], uint16_t count) +{ + if (value == nullptr || !isWidthSet_ || !isHeightSet_ || !itemsHeight_) { + return false; + } + + ClearList(); + for (uint16_t i = 0; i < count; i++) { + dataList_.PushBack(value[i]); + } + InitTextAdapter(); + textAdapter_->SetData(&dataList_); + maxCount_ = count; + RefreshList(); + if (setSelectedIndex_) { + RefreshSelected(setSelectedIndex_); + } + + return true; +} + +void UIPicker::RefreshList() +{ + int16_t height = GetHeight(); + itemsWidth_ = GetWidth(); + textAdapter_->SetWidth(itemsWidth_); + textAdapter_->SetHeight(itemsHeight_); + textAdapter_->SetLineBreakMode(UILabel::LINE_BREAK_CLIP); + if (backgroundFontName_ == nullptr) { + textAdapter_->SetFontId(backgroundFontId_); + } else { + textAdapter_->SetFont(backgroundFontName_, backgroundFontSize_); + } + textAdapter_->GetStyle().textColor_ = backgroundColor_; + textAdapter_->SetDirect(direct_); + list_.SetHeight(height); + list_.SetWidth(itemsWidth_); + list_.SetPosition(GetWidth() / 2 - list_.GetWidth() / 2, 0); // 2: half + list_.SetScrollBlankSize((height - itemsHeight_) / 2); // 2: half + list_.SetSelectPosition(height / 2); // 2: half + list_.SetStyle(*style_); + list_.SetStyle(STYLE_BORDER_WIDTH, 0); + list_.SetStyle(STYLE_BACKGROUND_OPA, 0); + if (!isSetAdaptered_) { + list_.SetAdapter(textAdapter_); + isSetAdaptered_ = true; + } + + list_.RefreshList(); + RefreshSelected(0); +} + +void UIPicker::ClearValues() +{ + rangeValue_ = nullptr; + rangeValueCount_ = 0; + setSelectedIndex_ = 0; + ClearList(); +} + +void UIPicker::ClearList() +{ + maxCount_ = 0; + itemsWidth_ = 0; + if (listListener_) { + PickerListScrollListener* listListener = static_cast(listListener_); + listListener->SetSelectView(nullptr); + listListener->SetSelectIndex(0); + listListener->SetInitStatus(false); + } + dataList_.Clear(); +} + +bool UIPicker::SetSelected(uint16_t index) +{ + setSelectedIndex_ = index; + return RefreshSelected(index); +} + +bool UIPicker::RefreshSelected(uint16_t index) +{ + if (itemsHeight_ && (maxCount_ > index) && (list_.GetChildrenHead() != nullptr) && isWidthSet_ && isHeightSet_) { + PickerListScrollListener* listListener = static_cast(listListener_); + listListener->SetInitStatus(false); + // 2: half + int16_t yOffset = (list_.GetHeight() - itemsHeight_) / 2 - + itemsHeight_ * (index - list_.GetChildrenHead()->GetViewIndex()); + list_.SetScrollStateListener(nullptr); + list_.ScrollBy(yOffset - list_.GetChildrenHead()->GetY()); + list_.SetScrollStateListener(listListener); + listListener->SetScrollState(ListScrollListener::SCROLL_STATE_STOP); + UIView* childView = static_cast(list_.GetChildrenHead()); + uint16_t lastSelectIndex = listListener->GetSelectIndex(); + + int16_t viewIndex; + while (childView != nullptr) { + viewIndex = childView->GetViewIndex(); + if (viewIndex == lastSelectIndex) { + childView->SetStyle(STYLE_TEXT_COLOR, GetBackgroundTextColor().full); + if (backgroundFontName_ == nullptr) { + static_cast(childView)->SetFontId(backgroundFontId_); + } else { + static_cast(childView)->SetFont(backgroundFontName_, backgroundFontSize_); + } + } + if (viewIndex == index) { + childView->SetStyle(STYLE_TEXT_COLOR, GetHighlightTextColor().full); + if (highlightFontName_ == nullptr) { + static_cast(childView)->SetFontId(highlightFontId_); + } else { + static_cast(childView)->SetFont(highlightFontName_, highlightFontSize_); + } + listListener->SetSelectView(childView); + listListener->SetSelectIndex(index); + listListener->SetInitStatus(true); + } + childView = childView->GetNextSibling(); + } + list_.Invalidate(); + return true; + } + return false; +} + +uint16_t UIPicker::GetSelected() const +{ + PickerListScrollListener* listListener = static_cast(listListener_); + return listListener->GetSelectIndex(); +} + +void UIPicker::SetFontId(uint8_t backgroundFontId, uint8_t highlightFontId) +{ + backgroundFontId_ = backgroundFontId; + if (backgroundFontName_ != nullptr) { + UIFree(backgroundFontName_); + backgroundFontName_ = nullptr; + } + + highlightFontId_ = highlightFontId; + if (highlightFontName_ != nullptr) { + UIFree(highlightFontName_); + highlightFontName_ = nullptr; + } + + Refresh(); +} + +void UIPicker::SetBackgroundFont(const char* name, uint8_t size) +{ + Text::SetFont(name, size, backgroundFontName_, backgroundFontSize_); + Refresh(); +} + +void UIPicker::SetHighlightFont(const char* name, uint8_t size) +{ + Text::SetFont(name, size, highlightFontName_, highlightFontSize_); + Refresh(); +} + +void UIPicker::SetTextColor(ColorType backgroundColor, ColorType highlightColor) +{ + backgroundColor_ = backgroundColor; + highlightColor_ = highlightColor; + Refresh(); +} + +void UIPicker::SetItemHeight(int16_t height) +{ + if (height > 0) { + itemsHeight_ = height; + Refresh(); + } +} + +void UIPicker::SetWidth(int16_t width) +{ + if (width > 0) { + UIView::SetWidth(width); + isWidthSet_ = true; + Refresh(); + } +} + +void UIPicker::SetHeight(int16_t height) +{ + if (height > 0) { + UIView::SetHeight(height); + isHeightSet_ = true; + Refresh(); + } +} + +void UIPicker::SetLoopState(bool state) +{ + list_.SetLoopState(state); +} + +void UIPicker::SetDirect(UITextLanguageDirect direct) +{ + direct_ = direct; + Refresh(); +} + +void UIPicker::SetTextFormatter(TextFormatter* formatter) +{ + InitTextAdapter(); + textAdapter_->SetTextFormatter(formatter); + Refresh(); +} +} diff --git a/frameworks/components/ui_qrcode.cpp b/frameworks/components/ui_qrcode.cpp new file mode 100755 index 0000000..7011816 --- /dev/null +++ b/frameworks/components/ui_qrcode.cpp @@ -0,0 +1,215 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "components/ui_qrcode.h" +#include "QrCode.hpp" +#include "graphic_log.h" +#include "securec.h" + +using qrcodegen::QrCode; +namespace OHOS { +UIQrcode::UIQrcode() + : width_(0), needDraw_(false), backgroundColor_(Color::White()), qrColor_(Color::Black()), qrcodeVal_(nullptr) +{ + style_ = &(StyleDefault::GetBackgroundTransparentStyle()); + imageInfo_ = {{0}}; +} + +UIQrcode::~UIQrcode() +{ + if (qrcodeVal_ != nullptr) { + UIFree(qrcodeVal_); + qrcodeVal_ = nullptr; + } + + if (imageInfo_.data != nullptr) { + ImageCacheFree(imageInfo_); + imageInfo_.data = nullptr; + } +} + +void UIQrcode::SetQrcodeInfo(const char* val, ColorType backgroundColor, ColorType qrColor) +{ + if (val == nullptr) { + GRAPHIC_LOGE("UIQrcode::SetQrcodeInfo val is null!\n"); + return; + } + uint32_t length = static_cast(strlen(val)); + if ((length > QRCODE_VAL_MAX) || (length == 0)) { + GRAPHIC_LOGE("UIQrcode::SetQrcodeInfo val length is equal 0 or greater than QRCODE_VAL_MAX!\n"); + return; + } + backgroundColor_ = backgroundColor; + qrColor_ = qrColor; + SetQrcodeVal(val, length); + RefreshQrcode(); +} + +void UIQrcode::RefreshQrcode() +{ + Invalidate(); + if (!needDraw_) { + needDraw_ = true; + } +} + +void UIQrcode::SetWidth(int16_t width) +{ + if (GetWidth() != width) { + UIView::SetWidth(width); + RefreshQrcode(); + } +} + +void UIQrcode::SetHeight(int16_t height) +{ + if (GetHeight() != height) { + UIView::SetHeight(height); + RefreshQrcode(); + } +} + +void UIQrcode::ReMeasure() +{ + if (!needDraw_) { + return; + } + needDraw_ = false; + if (qrcodeVal_ == nullptr) { + GRAPHIC_LOGE("UIQrcode::ReMeasure qrcodeVal_ is null!\n"); + return; + } + QrCode qr = QrCode::encodeText(qrcodeVal_, QrCode::Ecc::LOW); + SetImageInfo(qr); + SetSrc(&imageInfo_); +} + +void UIQrcode::SetQrcodeVal(const char* qrcodeVal, uint32_t length) +{ + if (qrcodeVal_ != nullptr) { + UIFree(qrcodeVal_); + qrcodeVal_ = nullptr; + } + + uint32_t len = static_cast(length + 1); + qrcodeVal_ = static_cast(UIMalloc(len)); + if (qrcodeVal_ != nullptr) { + if (memcpy_s(qrcodeVal_, len, qrcodeVal, len) != EOK) { + UIFree(reinterpret_cast(qrcodeVal_)); + qrcodeVal_ = nullptr; + } + } +} + +void UIQrcode::SetImageInfo(qrcodegen::QrCode& qrcode) +{ + int16_t width = GetWidth(); + int16_t height = GetHeight(); + width_ = (width >= height) ? height : width; + if (width_ < qrcode.getSize()) { + GRAPHIC_LOGE("UIQrcode::SetImageInfo width is less than the minimum qrcode width!\n"); + return; + } + imageInfo_.header.width = width; + imageInfo_.header.height = height; + imageInfo_.header.colorMode = ARGB8888; + imageInfo_.dataSize = imageInfo_.header.width * imageInfo_.header.height * QRCODE_FACTOR_NUM; + if (imageInfo_.data != nullptr) { + ImageCacheFree(imageInfo_); + imageInfo_.data = nullptr; + } + imageInfo_.data = reinterpret_cast(ImageCacheMalloc(imageInfo_)); + if (imageInfo_.data == nullptr) { + GRAPHIC_LOGE("UIQrcode::SetImageInfo imageInfo_.data is null!\n"); + return; + } + GenerateQrCode(qrcode); +} + +void UIQrcode::GenerateQrCode(qrcodegen::QrCode& qrcode) +{ + FillQrCodeBackgroundColor(); + + FillQrCodeColor(qrcode); +} + +void UIQrcode::FillQrCodeColor(qrcodegen::QrCode& qrcode) +{ + int32_t qrWidth = qrcode.getSize(); + if (qrWidth <= 0) { + GRAPHIC_LOGE("UIQrcode::FillQrCodeColor generated qrcode size is less or equal 0!\n"); + return; + } + int16_t width = imageInfo_.header.width; + int16_t height = imageInfo_.header.height; + uint16_t outFilePixelPrescaler = width_ / qrWidth; + int32_t offsetX = (width - outFilePixelPrescaler * qrWidth) / 2; // 2: half + int32_t offsetY = (height - outFilePixelPrescaler * qrWidth) / 2; // 2: half + + uint8_t* destData = nullptr; + int64_t oneLinePixel = width * QRCODE_FACTOR_NUM * outFilePixelPrescaler; + int64_t oneLineOffsetPixel = (offsetY * width * QRCODE_FACTOR_NUM) + (offsetX * QRCODE_FACTOR_NUM); + for (int32_t y = 0; y < qrWidth; ++y) { + destData = const_cast(imageInfo_.data) + (oneLinePixel * y) + oneLineOffsetPixel; + for (int32_t x = 0; x < qrWidth; ++x) { + if (qrcode.getModule(x, y)) { + GetDestData(destData, outFilePixelPrescaler); + } + destData += QRCODE_FACTOR_NUM * outFilePixelPrescaler; + } + } +} + +void UIQrcode::FillQrCodeBackgroundColor() +{ + uint8_t* initColorData = const_cast(imageInfo_.data); + *(initColorData + 0) = backgroundColor_.blue; // 0: B channel + *(initColorData + 1) = backgroundColor_.green; // 1: G channel + *(initColorData + 2) = backgroundColor_.red; // 2: R channel + *(initColorData + 3) = OPA_OPAQUE; // 3: Alpha channel + + uint8_t* tempColorData = initColorData; + for (int16_t col = 1; col < imageInfo_.header.width; ++col) { + initColorData += QRCODE_FACTOR_NUM; + if (memcpy_s(initColorData, QRCODE_FACTOR_NUM, tempColorData, QRCODE_FACTOR_NUM) != EOK) { + GRAPHIC_LOGE("UIQrcode::FillQrCodeBackgroundColor memcpy_s failed!\n"); + return; + } + } + initColorData = tempColorData; + int32_t deltaWidth = QRCODE_FACTOR_NUM * imageInfo_.header.width; + for (int16_t row = 1; row < imageInfo_.header.height; ++row) { + initColorData += deltaWidth; + if (memcpy_s(initColorData, deltaWidth, tempColorData, deltaWidth) != EOK) { + GRAPHIC_LOGE("UIQrcode::FillQrCodeBackgroundColor memcpy_s failed!\n"); + return; + } + } +} + +void UIQrcode::GetDestData(uint8_t* destData, int32_t outFilePixelPrescaler) +{ + for (int32_t x = 0; x < outFilePixelPrescaler; ++x) { + uint8_t* tempData = destData + imageInfo_.header.width * QRCODE_FACTOR_NUM * x; + for (int32_t y = 0; y < outFilePixelPrescaler; ++y) { + *(tempData + 0) = qrColor_.blue; // 0: B channel + *(tempData + 1) = qrColor_.green; // 1: G channel + *(tempData + 2) = qrColor_.red; // 2: R channel + *(tempData + 3) = OPA_OPAQUE; // 3: Alpha channel + tempData += QRCODE_FACTOR_NUM; + } + } +} +} // namespace OHOS diff --git a/frameworks/components/ui_radio_button.cpp b/frameworks/components/ui_radio_button.cpp new file mode 100755 index 0000000..6ff60c9 --- /dev/null +++ b/frameworks/components/ui_radio_button.cpp @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "components/ui_radio_button.h" +#include "common/image.h" +#include "components/root_view.h" +#include "components/ui_view_group.h" +#include "draw/draw_arc.h" +#include "draw/draw_image.h" +#include "draw/draw_rect.h" +#include "graphic_log.h" +#include "imgdecode/cache_manager.h" +#include "securec.h" + +namespace OHOS { +UIRadioButton::UIRadioButton() : name_(nullptr), width_(DEFAULT_HOT_WIDTH), height_(DEFAULT_HOT_HEIGHT), + radiusBig_(DEFAULT_RADIUS_BIG), radiusSmall_(DEFAULT_RADIUS_SMALL), + lineWidth_(DEFAULT_LINE_WIDTH) +{ + image_[UNSELECTED].SetSrc(""); + image_[SELECTED].SetSrc(""); + Resize(width_, height_); +} + +UIRadioButton::UIRadioButton(const char* name) : name_(nullptr), width_(DEFAULT_HOT_WIDTH), + height_(DEFAULT_HOT_HEIGHT), radiusBig_(DEFAULT_RADIUS_BIG), + radiusSmall_(DEFAULT_RADIUS_SMALL), lineWidth_(DEFAULT_LINE_WIDTH) +{ + SetName(name); + image_[UNSELECTED].SetSrc(""); + image_[SELECTED].SetSrc(""); + Resize(width_, height_); +} + +bool UIRadioButton::OnClickEvent(const ClickEvent& event) +{ + SetState(SELECTED); + Invalidate(); + UIView* view = this; + while ((view != nullptr) && (view->GetParent() != nullptr)) { + view = view->GetParent(); + } + FindRadioButtonAndChangeState(view); + return UIView::OnClickEvent(event); +} + +void UIRadioButton::CalculateSize() +{ + int16_t width = GetWidth(); + int16_t height = GetHeight(); + if ((width_ == width) && (height_ == height)) { + return; + } + width_ = width; + height_ = height; + int16_t minValue = (width_ > height_) ? height_ : width_; + radiusBig_ = DEFAULT_RADIUS_BIG * minValue / DEFAULT_HOT_WIDTH; + radiusSmall_ = DEFAULT_RADIUS_SMALL * minValue / DEFAULT_HOT_WIDTH; + if (minValue >= DEFAULT_HOT_WIDTH) { + lineWidth_ = DEFAULT_LINE_WIDTH * minValue / DEFAULT_HOT_WIDTH; + } +} + +void UIRadioButton::OnDraw(const Rect& invalidatedArea) +{ + if ((image_[SELECTED].GetSrcType() != IMG_SRC_UNKNOWN) && (image_[UNSELECTED].GetSrcType() != IMG_SRC_UNKNOWN)) { + UICheckBox::OnDraw(invalidatedArea); + } else { + CalculateSize(); + DrawRect::Draw(GetRect(), invalidatedArea, *style_, opaScale_); + Rect contentRect = GetContentRect(); + int16_t dx = width_ >> 1; + int16_t dy = height_ >> 1; + int16_t x = contentRect.GetX() + dx; + int16_t y = contentRect.GetY() + dy; + ArcInfo arcInfoBig = { { x, y }, { 0 }, radiusBig_, 0, CIRCLE_IN_DEGREE, nullptr }; + ArcInfo arcInfoSmall = { { x, y }, { 0 }, radiusSmall_, 0, CIRCLE_IN_DEGREE, nullptr }; + Rect trunc = invalidatedArea; + bool isIntersect = trunc.Intersect(trunc, contentRect); + switch (state_) { + case SELECTED: { + Style styleSelect = StyleDefault::GetBackgroundTransparentStyle(); + styleSelect.lineWidth_ = arcInfoBig.radius; + styleSelect.lineColor_ = Color::GetColorFromRGB(0x1F, 0x71, 0xFF); + if (isIntersect) { + DrawArc::GetInstance()->Draw(arcInfoBig, trunc, styleSelect, OPA_OPAQUE, CapType::CAP_NONE); + } + styleSelect.lineWidth_ = arcInfoSmall.radius; + styleSelect.lineColor_ = Color::White(); + if (isIntersect) { + DrawArc::GetInstance()->Draw(arcInfoSmall, trunc, styleSelect, OPA_OPAQUE, CapType::CAP_NONE); + } + break; + } + case UNSELECTED: { + Style styleUnSelect = StyleDefault::GetBackgroundTransparentStyle(); + styleUnSelect.lineColor_ = Color::White(); + styleUnSelect.lineWidth_ = lineWidth_; + if (isIntersect) { + // 0xa8 : opacity of drawing unselected button arc edge. + DrawArc::GetInstance()->Draw(arcInfoBig, trunc, styleUnSelect, 0xa8, CapType::CAP_NONE); + } + break; + } + default: + break; + } + } +} + +void UIRadioButton::SetName(const char* name) +{ + if (name == nullptr) { + return; + } + if (name_ != nullptr) { + UIFree(name_); + name_ = nullptr; + } + uint32_t nameLen = static_cast(strlen(name) + 1); + if (nameLen > MAX_TEXT_LENGTH) { + return; + } + name_ = static_cast(UIMalloc(nameLen)); + if (name_ != nullptr) { + if (memcpy_s(name_, nameLen, name, nameLen) != EOK) { + UIFree(name_); + name_ = nullptr; + return; + } + } +} + +void UIRadioButton::FindRadioButtonAndChangeState(UIView* view) +{ + if ((view == nullptr) || (name_ == nullptr)) { + return; + } + if (view->IsViewGroup()) { + UIView* childView = static_cast(view)->GetChildrenHead(); + while (childView != nullptr) { + FindRadioButtonAndChangeState(childView); + childView = childView->GetNextSibling(); + } + } + if ((view == this) || (view->GetViewType() != UI_RADIO_BUTTON)) { + return; + } + UIRadioButton* uiRadioButtonInfo = static_cast(view); + if ((uiRadioButtonInfo->GetName() != nullptr) && (strcmp(uiRadioButtonInfo->GetName(), name_) == 0)) { + uiRadioButtonInfo->SetState(UNSELECTED); + } +} +} // namespace OHOS diff --git a/frameworks/components/ui_repeat_button.cpp b/frameworks/components/ui_repeat_button.cpp new file mode 100755 index 0000000..ca2a1d2 --- /dev/null +++ b/frameworks/components/ui_repeat_button.cpp @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "components/ui_repeat_button.h" +#include "components/ui_button.h" + +namespace OHOS { +UIRepeatButton* UIRepeatButton::repeatButton_ = nullptr; + +/** default tick interval 200ms. */ +UIRepeatButton::UIRepeatButton() : ticksInterval_(200), event_({ 0, 0 }), + longPressed_(false), timerRepeatID_(0) {} + +UIRepeatButton::~UIRepeatButton() {} + +void UIRepeatButton::OnDraw(const Rect& invalidatedArea) +{ + UIButton::OnDraw(invalidatedArea); +} + +bool UIRepeatButton::OnReleaseEvent(const ReleaseEvent& event) +{ + if (longPressed_ && (timerRepeatID_ != 0)) { + longPressed_ = false; + } + return UIButton::OnReleaseEvent(event); +} + +bool UIRepeatButton::OnClickEvent(const ClickEvent& event) +{ + SetEvent(event); + return UIButton::OnClickEvent(event); +} + +void UIRepeatButton::HandleTickEvent() +{ + UIButton::OnClickEvent(event_); +} + +bool UIRepeatButton::OnLongPressEvent(const LongPressEvent& event) +{ + longPressed_ = true; + repeatButton_ = this; + return UIButton::OnLongPressEvent(event); +} + +void UIRepeatButton::SetEvent(const ClickEvent& event) +{ + event_ = event; +} +} // namespace OHOS \ No newline at end of file diff --git a/frameworks/components/ui_scroll_view.cpp b/frameworks/components/ui_scroll_view.cpp new file mode 100755 index 0000000..aa16030 --- /dev/null +++ b/frameworks/components/ui_scroll_view.cpp @@ -0,0 +1,417 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "components/ui_scroll_view.h" +#include "dock/focus_manager.h" +#include "draw/draw_rect.h" + +namespace OHOS { +UIScrollView::UIScrollView() + : xSliderPos_({0, 0}), + ySliderPos_({0, 0}), + scrollBarWidth_(DEFAULT_BAR_WIDTH), + xScrollable_(true), + yScrollable_(true), + minScrollBarLen_(DEFAULT_MIN_BAR_LEN), + scrollListener_(nullptr) +{ +#if ENABLE_ROTATE_INPUT + rotateFactor_ = 1; + rotateThreshold_ = 4; // 4: which means 25% of half view size +#endif + direction_ = HORIZONTAL_AND_VERTICAL; + xSlider_.SetVisible(false); + ySlider_.SetVisible(false); + xSlider_.SetStyle(StyleDefault::GetBrightStyle()); + ySlider_.SetStyle(StyleDefault::GetBrightStyle()); +} + +void UIScrollView::OnPostDraw(const Rect& invalidatedArea) +{ + if (!xSlider_.IsVisible() && !ySlider_.IsVisible()) { + return; + } + Rect scrollRect = GetRect(); + if (xSlider_.IsVisible()) { + xSlider_.SetPosition(scrollRect.GetX() + xSliderPos_.x, scrollRect.GetY() + xSliderPos_.y); + xSlider_.OnDraw(invalidatedArea); + } + if (ySlider_.IsVisible()) { + ySlider_.SetPosition(scrollRect.GetX() + ySliderPos_.x, scrollRect.GetY() + ySliderPos_.y); + ySlider_.OnDraw(invalidatedArea); + } +} + +bool UIScrollView::OnDragEvent(const DragEvent& event) +{ + if (scrollAnimator_.GetState() != Animator::STOP) { + UIAbstractScroll::StopAnimator(); + } + Drag(event); + return UIView::OnDragEvent(event); +} + +bool UIScrollView::OnDragEndEvent(const DragEvent& event) +{ + Point last = event.GetPreLastPoint(); + Point current = event.GetLastPoint(); + if ((last.x == current.x) && (last.y == current.y)) { + last = current; + current = event.GetCurrentPos(); + } + + if (!DragThrowAnimator(current, last)) { + if (scrollListener_ && (scrollListener_->GetScrollState() == OnScrollListener::SCROLL_STATE_MOVE)) { + scrollListener_->OnScrollEnd(); + scrollListener_->SetScrollState(OnScrollListener::SCROLL_STATE_STOP); + } + } + return UIView::OnDragEndEvent(event); +} + +void UIScrollView::Drag(const DragEvent& event) +{ + int16_t xDistance = event.GetDeltaX(); + int16_t yDistance = event.GetDeltaY(); + + if (xScrollable_ && xDistance != 0) { + DragXInner(xDistance); + } + if (yScrollable_ && yDistance != 0) { + DragYInner(yDistance); + } +} + +bool UIScrollView::OnPressEvent(const PressEvent& event) +{ + StopAnimator(); + return UIView::OnPressEvent(event); +} + +#if ENABLE_ROTATE_INPUT +bool UIScrollView::OnRotateEvent(const RotateEvent& event) +{ + int16_t tmpRotateLen = static_cast(event.GetRotate() * rotateFactor_); + int16_t midPointX = static_cast(GetWidth() / 2); // 2 : Get the middle point X coord of the view + int16_t midPointY = static_cast(GetHeight() / 2); // 2 : Get the middle point Y coord of the view + Point last, current; + if (!throwDrag_ || ((MATH_ABS(tmpRotateLen) < (midPointX / rotateThreshold_)) && + (MATH_ABS(tmpRotateLen) < (midPointY / rotateThreshold_)))) { + yScrollable_ ? ScrollBy(0, tmpRotateLen) : ScrollBy(tmpRotateLen, 0); + if (event.GetRotate() == 0) { + DragThrowAnimator(Point {0, 0}, Point {0, 0}); + } + } else { + last = Point {midPointX, midPointY}; + yScrollable_ ? (current = Point {midPointX, static_cast(midPointY + tmpRotateLen)}) + : (current = Point {static_cast(midPointX + tmpRotateLen), midPointY}); + DragThrowAnimator(current, last); + } +#if ENABLE_MOTOR + MotorFunc motorFunc = FocusManager::GetInstance()->GetMotorFunc(); + if (motorFunc != nullptr) { + motorFunc(MotorType::MOTOR_TYPE_ONE); + } +#endif + return UIView::OnRotateEvent(event); +} +#endif + +void UIScrollView::ScrollBy(int16_t xDistance, int16_t yDistance) +{ + if (xScrollable_ && xDistance != 0) { + DragXInner(xDistance); + } + if (yScrollable_ && yDistance != 0) { + DragYInner(yDistance); + } + if ((scrollListener_ != nullptr) && (scrollListener_->GetScrollState() == OnScrollListener::SCROLL_STATE_MOVE)) { + scrollListener_->OnScrollEnd(); + scrollListener_->SetScrollState(OnScrollListener::SCROLL_STATE_STOP); + } +} + +bool UIScrollView::DragXInner(int16_t distance) +{ + Rect childRect = GetAllChildRelativeRect(); + int16_t reboundSize = reboundSize_; + if (scrollAnimator_.GetState() != Animator::STOP) { + reboundSize = 0; + } + + if (childRect.GetWidth() <= (GetWidth() - (scrollBlankSize_ << 1))) { + return false; + } + + if (distance > 0) { + if (childRect.GetLeft() > scrollBlankSize_ + reboundSize) { + distance = 0; + } else if ((childRect.GetLeft() + distance) > scrollBlankSize_ + reboundSize) { + distance = scrollBlankSize_ - childRect.GetLeft() + reboundSize; + } + } else { + int16_t childRight = childRect.GetRight(); + int16_t scrollWidth = GetWidth(); + if (yScrollable_ && ySlider_.IsVisible()) { + if (childRight < scrollWidth - ySlider_.GetWidth() - (scrollBlankSize_ + reboundSize)) { + distance = 0; + } else if (childRight + distance < scrollWidth - ySlider_.GetWidth() - (scrollBlankSize_ + reboundSize)) { + distance = scrollWidth - ySlider_.GetWidth() - (scrollBlankSize_ + reboundSize) - childRight; + } + } else { + if (childRight < scrollWidth - (scrollBlankSize_ + reboundSize)) { + distance = 0; + } else if (childRight + distance < scrollWidth - (scrollBlankSize_ + reboundSize)) { + distance = scrollWidth - (scrollBlankSize_ + reboundSize) - childRight - 1; + } + } + } + + return MoveOffset(distance, 0); +} + +bool UIScrollView::DragYInner(int16_t distance) +{ + Rect childRect = GetAllChildRelativeRect(); + int16_t reboundSize = reboundSize_; + if (scrollAnimator_.GetState() != Animator::STOP) { + reboundSize = 0; + } + + if (childRect.GetHeight() <= (GetHeight() - (scrollBlankSize_ << 1))) { + return false; + } + + if (distance > 0) { + if (childRect.GetTop() > scrollBlankSize_ + reboundSize) { + distance = 0; + } else if ((childRect.GetTop() + distance) > scrollBlankSize_ + reboundSize) { + distance = scrollBlankSize_ - childRect.GetTop() + reboundSize; + } + } else { + int16_t childBottom = childRect.GetBottom(); + int16_t scrollHeight = GetHeight(); + if (xScrollable_ && xSlider_.IsVisible()) { + if (childBottom < scrollHeight - xSlider_.GetHeight() - (scrollBlankSize_ + reboundSize)) { + distance = 0; + } else if (childBottom + distance < + scrollHeight - xSlider_.GetHeight() - (scrollBlankSize_ + reboundSize)) { + distance = scrollHeight - xSlider_.GetHeight() - (scrollBlankSize_ + reboundSize) - childBottom; + } + } else { + if (childBottom < scrollHeight - (scrollBlankSize_ + reboundSize)) { + distance = 0; + } else if (childBottom + distance < scrollHeight - (scrollBlankSize_ + reboundSize)) { + distance = scrollHeight - (scrollBlankSize_ + reboundSize) - childBottom - 1; + } + } + } + + return MoveOffset(0, distance); +} + +bool UIScrollView::MoveOffset(int16_t offsetX, int16_t offsetY) +{ + if ((offsetX != 0) || (offsetY != 0)) { + if ((scrollListener_ != nullptr) && + (scrollListener_->GetScrollState() == OnScrollListener::SCROLL_STATE_STOP)) { + scrollListener_->OnScrollStart(); + scrollListener_->SetScrollState(OnScrollListener::SCROLL_STATE_MOVE); + } + UIAbstractScroll::MoveChildByOffset(offsetX, offsetY); + Invalidate(); + RefreshScrollBarPosition(); + return true; + } + return false; +} + +void UIScrollView::RefreshScrollBar() +{ + Rect childRect = GetAllChildRelativeRect(); + if (xScrollable_ && (childRect.GetWidth() <= GetWidth())) { + xSliderPos_.y = GetHeight() - scrollBarWidth_; + xSlider_.SetHeight(scrollBarWidth_); + // y scroll bar is on, x scroll bar width should be group width - scroll bar width + if (yScrollable_) { + xSlider_.SetWidth(GetWidth() - scrollBarWidth_); + } else { + xSlider_.SetWidth(GetWidth()); + } + } + + if (yScrollable_ && (childRect.GetHeight() <= GetHeight())) { + ySliderPos_.x = GetWidth() - scrollBarWidth_; + ySliderPos_.y = 0; + ySlider_.SetWidth(scrollBarWidth_); + // x scroll bar is on, y scroll bar height should be group height - scroll bar width + if (xScrollable_) { + ySlider_.SetHeight(GetHeight() - scrollBarWidth_); + } else { + ySlider_.SetHeight(GetHeight()); + } + } + float multiple; + + // child width is larger than group width, resize the x scroll bar width + if (xScrollable_ && (childRect.GetWidth() > GetWidth()) && (childRect.GetWidth() != 0)) { + int16_t groupWidth = GetWidth(); + int16_t xWidth; + if (yScrollable_) { + multiple = static_cast(groupWidth - scrollBarWidth_) / childRect.GetWidth(); + xWidth = static_cast((groupWidth - scrollBarWidth_) * multiple); + } else { + multiple = static_cast(groupWidth) / childRect.GetWidth(); + xWidth = static_cast(groupWidth * multiple); + } + if (xWidth < minScrollBarLen_) { + xWidth = minScrollBarLen_; + } + + xSliderPos_.x = GetXScrollOffset(childRect); + xSliderPos_.y = GetHeight() - scrollBarWidth_; + xSlider_.SetWidth(xWidth); + xSlider_.SetHeight(scrollBarWidth_); + } + + // child height is larger than group height, resize the y scroll height + if (yScrollable_ && (childRect.GetHeight() > GetHeight()) && (childRect.GetHeight() != 0)) { + int16_t groupHeight = GetHeight(); + int16_t yHeight; + if (xScrollable_) { + multiple = static_cast(groupHeight - scrollBarWidth_) / childRect.GetHeight(); + yHeight = static_cast((groupHeight - scrollBarWidth_) * multiple); + } else { + multiple = static_cast(groupHeight) / childRect.GetHeight(); + yHeight = static_cast(groupHeight * multiple); + } + + // scroll bar may be too small, keep it min size + if (yHeight < minScrollBarLen_) { + yHeight = minScrollBarLen_; + } + + ySliderPos_.x = GetWidth() - scrollBarWidth_; + ySliderPos_.y = GetYScrollOffset(childRect); + ySlider_.SetHeight(yHeight); + ySlider_.SetWidth(scrollBarWidth_); + } +} + +void UIScrollView::RefreshScrollBarPosition() +{ + if (!xSlider_.IsVisible() && !ySlider_.IsVisible()) { + return; + } + Rect childRect = GetAllChildRelativeRect(); + if ((childRect.GetWidth() == 0) || (childRect.GetHeight() == 0)) { + return; + } + if (xScrollable_) { + int16_t xOffset = GetXScrollOffset(childRect); + xSliderPos_.x = xOffset; + Invalidate(); + } + if (yScrollable_) { + int16_t yOffset = GetYScrollOffset(childRect); + ySliderPos_.y = yOffset; + Invalidate(); + } +} + +int16_t UIScrollView::GetXScrollOffset(const Rect& childRect) +{ + Rect scrollRect = GetRelativeRect(); + int16_t xOffset; + int16_t scrollBarOffset = 0; + + if (yScrollable_) { + scrollBarOffset = scrollBarWidth_; + } + + int16_t childRectLeft = childRect.GetLeft(); + int16_t childRectWidth = childRect.GetWidth(); + int16_t scrollRectWidth = scrollRect.GetWidth(); + int16_t xSliderWidth = xSlider_.GetWidth(); + if ((childRectLeft >= 0) || (childRectWidth - scrollRectWidth + scrollBarOffset == 0)) { + xOffset = 0; + } else { + float multiple = static_cast(scrollRectWidth - scrollBarOffset - xSliderWidth) / + (childRectWidth - scrollRectWidth + scrollBarOffset); + xOffset = static_cast(-childRectLeft * multiple); + } + return xOffset; +} + +int16_t UIScrollView::GetYScrollOffset(const Rect& childRect) +{ + Rect scrollRect = GetRelativeRect(); + int16_t yOffset; + int16_t scrollBarOffset = 0; + + if (xScrollable_) { + scrollBarOffset = scrollBarWidth_; + } + + int16_t childRectTop = childRect.GetTop(); + int16_t childRectHeight = childRect.GetHeight(); + int16_t scrollRectHeight = scrollRect.GetHeight(); + int16_t ySliderHeight = ySlider_.GetHeight(); + if ((childRectTop >= 0) || (childRectHeight - scrollRectHeight + scrollBarOffset == 0)) { + yOffset = 0; + } else { + float multiple = static_cast(scrollRectHeight - scrollBarOffset - ySliderHeight) / + (childRectHeight - scrollRectHeight + scrollBarOffset); + yOffset = static_cast(-childRectTop * multiple); + } + return yOffset; +} + +void UIScrollView::OnChildChanged() +{ + RefreshScrollBar(); +} + +void UIScrollView::CalculateReboundDistance(int16_t& dragDistanceX, int16_t& dragDistanceY) +{ + Rect rect = GetAllChildRelativeRect(); + int16_t top = rect.GetTop(); + int16_t bottom = rect.GetBottom(); + int16_t scrollHeight = GetHeight(); + int16_t left = rect.GetLeft(); + int16_t right = rect.GetRight(); + int16_t scrollWidth = GetWidth(); + if (scrollBlankSize_ < top) { + dragDistanceY = scrollBlankSize_ - top; + } else if (bottom < scrollHeight - 1) { + dragDistanceY = scrollHeight - scrollBlankSize_ - bottom - 1; + } + + if (scrollBlankSize_ < left) { + dragDistanceX = scrollBlankSize_ - left; + } else if (right < scrollWidth - 1) { + dragDistanceX = scrollWidth - scrollBlankSize_ - right - 1; + } +} + +void UIScrollView::StopAnimator() +{ + if ((scrollListener_ != nullptr) && (scrollListener_->GetScrollState() == OnScrollListener::SCROLL_STATE_MOVE)) { + scrollListener_->OnScrollEnd(); + scrollListener_->SetScrollState(OnScrollListener::SCROLL_STATE_STOP); + } + UIAbstractScroll::StopAnimator(); +} +} // namespace OHOS \ No newline at end of file diff --git a/frameworks/components/ui_slider.cpp b/frameworks/components/ui_slider.cpp new file mode 100755 index 0000000..3e1224d --- /dev/null +++ b/frameworks/components/ui_slider.cpp @@ -0,0 +1,299 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "components/ui_slider.h" +#include "common/image.h" +#include "dock/focus_manager.h" +#include "draw/draw_image.h" +#include "draw/draw_rect.h" +#include "graphic_log.h" +#include "imgdecode/cache_manager.h" +#include "themes/theme_manager.h" + +namespace OHOS { +UISlider::UISlider() + : knobWidth_(0), knobWidthSetFlag_(false), knobStyleAllocFlag_(false), knobImage_(nullptr), listener_(nullptr) +{ + touchable_ = true; + draggable_ = true; + dragParentInstead_ = false; + Theme* theme = ThemeManager::GetInstance().GetCurrent(); + if (theme != nullptr) { + knobStyle_ = &(theme->GetSliderKnobStyle()); + } else { + knobStyle_ = &(StyleDefault::GetSliderKnobStyle()); + } +} + +UISlider::~UISlider() +{ + if (knobImage_ != nullptr) { + delete knobImage_; + knobImage_ = nullptr; + } + + if (knobStyleAllocFlag_) { + delete knobStyle_; + knobStyle_ = nullptr; + knobStyleAllocFlag_ = false; + } +} + +void UISlider::SetKnobStyle(const Style& style) +{ + if (!knobStyleAllocFlag_) { + knobStyle_ = new Style; + if (knobStyle_ == nullptr) { + GRAPHIC_LOGE("new Style fail"); + return; + } + knobStyleAllocFlag_ = true; + } + *knobStyle_ = style; +} + +void UISlider::SetKnobStyle(uint8_t key, int64_t value) +{ + if (!knobStyleAllocFlag_) { + knobStyle_ = new Style(*knobStyle_); + if (knobStyle_ == nullptr) { + GRAPHIC_LOGE("new Style fail"); + return; + } + knobStyleAllocFlag_ = true; + } + knobStyle_->SetStyle(key, value); +} + +const Style& UISlider::GetKnobStyle() const +{ + return *knobStyle_; +} + +int64_t UISlider::GetKnobStyle(uint8_t key) const +{ + return knobStyle_->GetStyle(key); +} + +int32_t UISlider::CalculateCurrentValue(int16_t length, int16_t totalLength) +{ + if (totalLength != 0) { + return static_cast(rangeMin_ + (static_cast(rangeMax_) - rangeMin_) * length / totalLength); + } + return 0; +} + +int32_t UISlider::UpdateCurrentValue(const Point& knobPosition) +{ + Point startPoint; + Rect rect = GetOrigRect(); + startPoint.x = rect.GetLeft() + style_->borderWidth_ + style_->paddingLeft_; + startPoint.y = rect.GetTop() + style_->borderWidth_ + style_->paddingTop_; + + int32_t value = curValue_; + switch (direction_) { + case Direction::DIR_LEFT_TO_RIGHT: + if (knobPosition.x <= startPoint.x) { + value = rangeMin_; + } else if (knobPosition.x >= startPoint.x + progressWidth_) { + value = rangeMax_; + } else { + value = CalculateCurrentValue(knobPosition.x - startPoint.x, progressWidth_); + } + break; + case Direction::DIR_RIGHT_TO_LEFT: + if (knobPosition.x <= startPoint.x) { + value = rangeMax_; + } else if (knobPosition.x >= startPoint.x + progressWidth_) { + value = rangeMin_; + } else { + value = CalculateCurrentValue(startPoint.x + progressWidth_ - knobPosition.x, progressWidth_); + } + break; + case Direction::DIR_BOTTOM_TO_TOP: + if (knobPosition.y <= startPoint.y) { + value = rangeMax_; + } else if (knobPosition.y >= startPoint.y + progressHeight_) { + value = rangeMin_; + } else { + value = CalculateCurrentValue(startPoint.y + progressHeight_ - knobPosition.y, progressHeight_); + } + break; + case Direction::DIR_TOP_TO_BOTTOM: + if (knobPosition.y <= startPoint.y) { + value = rangeMin_; + } else if (knobPosition.y >= startPoint.y + progressHeight_) { + value = rangeMax_; + } else { + value = CalculateCurrentValue(knobPosition.y - startPoint.y, progressHeight_); + } + break; + default: + GRAPHIC_LOGW("UISlider::UpdateCurrentValue Direction error!\n"); + } + SetValue(value); + return value; +} + +bool UISlider::OnClickEvent(const ClickEvent& event) +{ + Point knobPosition = event.GetCurrentPos(); + int32_t value = UpdateCurrentValue(knobPosition); + if (listener_ != nullptr) { + listener_->OnChange(value); + } + bool ret = UIView::OnClickEvent(event); + Invalidate(); + return ret; +} + +bool UISlider::OnDragEvent(const DragEvent& event) +{ + Point knobPosition = event.GetCurrentPos(); + int32_t value = UpdateCurrentValue(knobPosition); + if (listener_ != nullptr) { + listener_->OnChange(value); + } + Invalidate(); + return UIView::OnDragEvent(event); +} + +bool UISlider::OnDragEndEvent(const DragEvent& event) +{ + Point knobPosition = event.GetCurrentPos(); + int32_t value = UpdateCurrentValue(knobPosition); + if (listener_ != nullptr) { + listener_->OnChange(value); + listener_->OnRelease(value); + } + Invalidate(); + return UIView::OnDragEndEvent(event); +} + +#if ENABLE_ROTATE_INPUT +bool UISlider::OnRotateEvent(const RotateEvent& event) +{ + if (event.GetRotate() == 0) { + return false; + } + int32_t tmp = event.GetRotate() * rotateFactor_; + SetValue(curValue_ + tmp); +#if ENABLE_MOTOR + MotorFunc motorFunc = FocusManager::GetInstance()->GetMotorFunc(); + if (motorFunc != nullptr) { + motorFunc(MotorType::MOTOR_TYPE_TWO); + } +#endif + return UIView::OnRotateEvent(event); +} +#endif + +int16_t UISlider::GetKnobWidth() +{ + if (!knobWidthSetFlag_) { + if ((direction_ == Direction::DIR_LEFT_TO_RIGHT) || (direction_ == Direction::DIR_RIGHT_TO_LEFT)) { + knobWidth_ = progressHeight_; + } else { + knobWidth_ = progressWidth_; + } + } + return knobWidth_; +} + +void UISlider::SetImage(const ImageInfo* backgroundImage, const ImageInfo* foregroundImage, const ImageInfo* knobImage) +{ + if (!InitImage()) { + return; + } + backgroundImage_->SetSrc(backgroundImage); + foregroundImage_->SetSrc(foregroundImage); + knobImage_->SetSrc(knobImage); +} + +void UISlider::SetImage(const char* backgroundImage, const char* foregroundImage, const char* knobImage) +{ + if (!InitImage()) { + return; + } + backgroundImage_->SetSrc(backgroundImage); + foregroundImage_->SetSrc(foregroundImage); + knobImage_->SetSrc(knobImage); +} + +void UISlider::DrawKnob(const Rect& invalidatedArea, const Rect& foregroundRect) +{ + int16_t halfKnobWidth = GetKnobWidth() >> 1; + int16_t offset; + Rect knobBar; + switch (direction_) { + case Direction::DIR_LEFT_TO_RIGHT: { + offset = (knobWidth_ - progressHeight_) >> 1; + knobBar.SetRect(foregroundRect.GetRight() - halfKnobWidth, foregroundRect.GetTop() - offset, + foregroundRect.GetRight() + halfKnobWidth, foregroundRect.GetBottom() + offset); + break; + } + case Direction::DIR_RIGHT_TO_LEFT: { + offset = (knobWidth_ - progressHeight_) >> 1; + knobBar.SetRect(foregroundRect.GetLeft() - halfKnobWidth, foregroundRect.GetTop() - offset, + foregroundRect.GetLeft() + halfKnobWidth, foregroundRect.GetBottom() + offset); + break; + } + case Direction::DIR_BOTTOM_TO_TOP: { + offset = (knobWidth_ - progressWidth_) >> 1; + knobBar.SetRect(foregroundRect.GetLeft() - offset, foregroundRect.GetTop() - halfKnobWidth, + foregroundRect.GetRight() + offset, foregroundRect.GetTop() + halfKnobWidth); + break; + } + case Direction::DIR_TOP_TO_BOTTOM: { + offset = (knobWidth_ - progressWidth_) >> 1; + knobBar.SetRect(foregroundRect.GetLeft() - offset, foregroundRect.GetBottom() - halfKnobWidth, + foregroundRect.GetRight() + offset, foregroundRect.GetBottom() + halfKnobWidth); + break; + } + default: { + GRAPHIC_LOGW("UISlider::DrawKnob Direction error!\n"); + } + } + DrawValidRect(knobImage_, knobBar, invalidatedArea, *knobStyle_, 0); +} + +void UISlider::OnDraw(const Rect& invalidatedArea) +{ + DrawRect::Draw(GetOrigRect(), invalidatedArea, *style_, opaScale_); + + Rect trunc(invalidatedArea); + if (trunc.Intersect(trunc, GetOrigRect())) { + DrawBackground(trunc); + Rect foregroundRect; + DrawForeground(trunc, foregroundRect); + DrawKnob(trunc, foregroundRect); + } +} + +bool UISlider::InitImage() +{ + if (!UIAbstractProgress::InitImage()) { + return false; + } + if (knobImage_ == nullptr) { + knobImage_ = new Image(); + if (knobImage_ == nullptr) { + GRAPHIC_LOGE("new Image fail"); + return false; + } + } + return true; +} +} // namespace OHOS \ No newline at end of file diff --git a/frameworks/components/ui_surface_view.cpp b/frameworks/components/ui_surface_view.cpp new file mode 100755 index 0000000..2b6abc3 --- /dev/null +++ b/frameworks/components/ui_surface_view.cpp @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "components/ui_surface_view.h" + +#include + +#include "draw/draw_rect.h" +#include "draw/draw_utils.h" +#include "graphic_log.h" +#include "surface_buffer.h" + +namespace OHOS { +UISurfaceView::UISurfaceView() +{ + surface_ = Surface::CreateSurface(); + if (surface_ == nullptr) { + GRAPHIC_LOGE("UISurfaceView::UISurfaceView surface create failed\n"); + return; + } + surface_->SetWidthAndHeight(GetWidth(), GetHeight()); + surface_->SetQueueSize(DEFAULT_QUEUE_SIZE); + surface_->SetFormat(IMAGE_PIXEL_FORMAT_ARGB8888); +} + +UISurfaceView::~UISurfaceView() +{ + if (surface_ != nullptr) { + delete surface_; + surface_ = nullptr; + } +} + +void UISurfaceView::SetPosition(int16_t x, int16_t y) +{ + UIView::SetPosition(x, y); + if (surface_ == nullptr) { + GRAPHIC_LOGE("UISurfaceView::SetPosition surface is null\n"); + return; + } + x = GetRect().GetLeft(); + y = GetRect().GetTop(); + surface_->SetUserData(REGION_POSITION_X, std::to_string(x)); + surface_->SetUserData(REGION_POSITION_Y, std::to_string(y)); +} + +void UISurfaceView::SetPosition(int16_t x, int16_t y, int16_t width, int16_t height) +{ + UIView::SetPosition(x, y, width, height); + if (surface_ == nullptr) { + GRAPHIC_LOGE("UISurfaceView::SetPosition surface is null\n"); + return; + } + x = GetRect().GetLeft(); + y = GetRect().GetTop(); + surface_->SetUserData(REGION_POSITION_X, std::to_string(x)); + surface_->SetUserData(REGION_POSITION_Y, std::to_string(y)); + surface_->SetUserData(REGION_WIDTH, std::to_string(width)); + surface_->SetUserData(REGION_HEIGHT, std::to_string(height)); +} + +void UISurfaceView::Resize(int16_t width, int16_t height) +{ + UIView::Resize(width, height); + if (surface_ == nullptr) { + GRAPHIC_LOGE("UISurfaceView::Resize surface is null\n"); + return; + } + surface_->SetUserData(REGION_WIDTH, std::to_string(width)); + surface_->SetUserData(REGION_HEIGHT, std::to_string(height)); +} + +void UISurfaceView::SetWidth(int16_t width) +{ + UIView::SetWidth(width); + if (surface_ == nullptr) { + GRAPHIC_LOGE("UISurfaceView::SetWidth surface is null\n"); + return; + } + surface_->SetUserData(REGION_WIDTH, std::to_string(width)); +} + +void UISurfaceView::SetHeight(int16_t height) +{ + UIView::SetHeight(height); + if (surface_ == nullptr) { + GRAPHIC_LOGE("UISurfaceView::SetHeight surface is null\n"); + return; + } + surface_->SetUserData(REGION_HEIGHT, std::to_string(height)); +} + +void UISurfaceView::SetX(int16_t x) +{ + UIView::SetX(x); + if (surface_ == nullptr) { + GRAPHIC_LOGE("UISurfaceView::SetX surface is null\n"); + return; + } + x = GetRect().GetLeft(); + surface_->SetUserData(REGION_POSITION_X, std::to_string(x)); +} + +void UISurfaceView::SetY(int16_t y) +{ + UIView::SetY(y); + if (surface_ == nullptr) { + GRAPHIC_LOGE("UISurfaceView::SetY surface is null\n"); + return; + } + y = GetRect().GetTop(); + surface_->SetUserData(REGION_POSITION_Y, std::to_string(y)); +} + +Surface* UISurfaceView::GetSurface() const +{ + return surface_; +} + +bool UISurfaceView::OnPreDraw(Rect& invalidatedArea) const +{ + // need fill transpant color + return false; +} + +void UISurfaceView::OnDraw(const Rect& invalidatedArea) +{ + Draw(invalidatedArea); +} + +void UISurfaceView::Draw(const Rect& invalidatedArea) +{ + SurfaceBuffer* acquireBuffer = (surface_ != nullptr) ? surface_->AcquireBuffer() : nullptr; + if (acquireBuffer != nullptr) { + GRAPHIC_LOGE("UISurfaceView::Draw acquireBufferVirAddr=%p \n", acquireBuffer->GetVirAddr()); + // fill with buffer + DrawUtils::GetInstance()->DrawWithBuffer(GetRect(), invalidatedArea, + reinterpret_cast(acquireBuffer->GetVirAddr())); + surface_->ReleaseBuffer(acquireBuffer); + } else { + // fill with transpant color + DrawUtils::GetInstance()->DrawTranspantArea(GetRect(), invalidatedArea); + } +} +} // namespace OHOS diff --git a/frameworks/components/ui_swipe_view.cpp b/frameworks/components/ui_swipe_view.cpp new file mode 100755 index 0000000..e94e174 --- /dev/null +++ b/frameworks/components/ui_swipe_view.cpp @@ -0,0 +1,480 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "components/ui_swipe_view.h" +#include "dock/focus_manager.h" + +namespace OHOS { +UISwipeView::UISwipeView(uint8_t direction) + : swipeListener_(nullptr), + curIndex_(0), + blankSize_(DEFAULT_BLANK_SIZE), + curView_(nullptr), + loop_(false) +{ +#if ENABLE_ROTATE_INPUT + rotateFactor_ = 1; +#endif + direction_ = direction; + AnimatorManager::GetInstance()->Add(&scrollAnimator_); + tickTime_ = ANIMATOR_TIME; + swipeAccCoefficient_ = DRAG_ACC_FACTOR; +} + +UISwipeView::~UISwipeView() +{ + AnimatorManager::GetInstance()->Remove(&scrollAnimator_); +} + +void UISwipeView::Add(UIView* view) +{ + if (view == nullptr) { + return; + } + view->SetDragParentInstead(true); + UIViewGroup::Add(view); + SortChild(); + Invalidate(); +} + +void UISwipeView::Insert(UIView* prevView, UIView* insertView) +{ + if (insertView == nullptr) { + return; + } + insertView->SetDragParentInstead(true); + UIViewGroup::Insert(prevView, insertView); + SortChild(); + Invalidate(); +} + +void UISwipeView::Remove(UIView* view) +{ + if (view == nullptr) { + return; + } + UIViewGroup::Remove(view); + SortChild(); + Invalidate(); +} + +void UISwipeView::SetCurrentPage(uint16_t index, bool needAnimator) +{ + SwitchToPage(index, needAnimator); + Invalidate(); +} + +bool UISwipeView::DragXInner(int16_t distance) +{ + if (distance == 0) { + return true; + } + if (!loop_) { + if ((distance > 0) && (childrenHead_ != nullptr)) { + if (childrenHead_->GetX() >= blankSize_) { + distance = 0; + } else if (childrenHead_ && (childrenHead_->GetX() + distance > blankSize_)) { + distance = blankSize_ - childrenHead_->GetX(); + } + } else if (childrenTail_ != nullptr) { + int16_t width = GetWidth(); + if (childrenTail_->GetRelativeRect().GetRight() < width - blankSize_) { + distance = 0; + } else if (width - (childrenTail_->GetX() + childrenTail_->GetWidth() + distance) > blankSize_) { + distance = width - blankSize_ - childrenTail_->GetX() - childrenTail_->GetWidth(); + } + } + } + CalculateInvalidate(); + MoveChildByOffset(distance, 0); + CalculateInvalidate(); + return true; +} + +bool UISwipeView::DragYInner(int16_t distance) +{ + if (distance == 0) { + return true; + } + if (!loop_) { + if ((distance > 0) && (childrenHead_ != nullptr)) { + if (childrenHead_->GetY() >= blankSize_) { + distance = 0; + } else if ((childrenHead_ != nullptr) && (childrenHead_->GetY() + distance > blankSize_)) { + distance = blankSize_ - childrenHead_->GetY(); + } + } else if (childrenTail_ != nullptr) { + int16_t height = GetHeight(); + if (childrenTail_->GetRelativeRect().GetBottom() < height - blankSize_) { + distance = 0; + } else if (height - (childrenTail_->GetY() + childrenTail_->GetHeight() + distance) > blankSize_) { + distance = height - blankSize_ - childrenTail_->GetY() - childrenTail_->GetHeight(); + } + } + } + CalculateInvalidate(); + MoveChildByOffset(0, distance); + CalculateInvalidate(); + return true; +} + +bool UISwipeView::OnDragEvent(const DragEvent& event) +{ + UIView* currentView = GetViewByIndex(curIndex_); + if (currentView == nullptr) { + return UIView::OnDragEvent(event); + } + if (scrollAnimator_.GetState() != Animator::STOP) { + UIAbstractScroll::StopAnimator(); + } + + if (direction_ == HORIZONTAL) { + DragXInner(event.GetDeltaX()); + RefreshDeltaY(event.GetDeltaX()); + } else { + DragYInner(event.GetDeltaY()); + RefreshDeltaY(event.GetDeltaY()); + } + return UIView::OnDragEvent(event); +} + +bool UISwipeView::OnDragEndEvent(const DragEvent& event) +{ + int16_t distance = 0; + if (direction_ == HORIZONTAL) { + distance = event.GetCurrentPos().x - event.GetPreLastPoint().x; + } else { + distance = event.GetCurrentPos().y - event.GetPreLastPoint().y; + } + RefreshCurrentView(distance); + + if (curView_ == nullptr) { + return UIView::OnDragEndEvent(event); + } + + SwitchToPage(curIndex_); + + Invalidate(); + return UIView::OnDragEndEvent(event); +} + +#if ENABLE_ROTATE_INPUT +bool UISwipeView::OnRotateEvent(const RotateEvent& event) +{ + if (event.GetRotate() != 0) { + // 4 : need to fit for the device + if (MATH_ABS(event.GetRotate()) > blankSize_ / (4 * static_cast(rotateFactor_))) { + SwitchToPage(curIndex_ - event.GetRotate()); + } else { + int16_t tmp = event.GetRotate() * rotateFactor_; + DragXInner(tmp); + RefreshCurrentView(tmp); + } + } else { + SwitchToPage(curIndex_); +#if ENABLE_MOTOR + MotorFunc motorFunc = FocusManager::GetInstance()->GetMotorFunc(); + if (motorFunc != nullptr) { + motorFunc(MotorType::MOTOR_TYPE_ONE); + } +#endif + } + return UIView::OnRotateEvent(event); +} +#endif + +UIView* UISwipeView::GetViewByIndex(uint16_t index) const +{ + UIView* child = childrenHead_; + while (child != nullptr) { + if (child->GetViewIndex() == index) { + return child; + } + child = child->GetNextSibling(); + } + return nullptr; +} + +void UISwipeView::SetAnimatorTime(uint16_t time) +{ + tickTime_ = time / DEFAULT_TASK_PERIOD; + if (tickTime_ == 0) { + tickTime_ = 1; + } + animatorCallback_.SetDragTimes(tickTime_); +} + +void UISwipeView::SwitchToPage(int16_t dst, bool needAnimator) +{ + if (isNeedLoop()) { + dst = (dst + childrenNum_) % childrenNum_; + } else if (dst < 0) { + dst = 0; + } else if (dst >= childrenNum_) { + dst = childrenNum_ - 1; + } + + UIView* dstView = GetViewByIndex(dst); + if (dstView == nullptr) { + return; + } + curIndex_ = dst; + int16_t xOffset = 0; + int16_t yOffset = 0; + + if (direction_ == HORIZONTAL) { + if (alignMode_ == ALIGN_LEFT) { + xOffset = -dstView->GetX(); + } else if (alignMode_ == ALIGN_RIGHT) { + xOffset = GetWidth() - (dstView->GetX() + dstView->GetWidth()); + } else { + xOffset = (GetWidth() >> 1) - (dstView->GetX() + (dstView->GetWidth() >> 1)); + } + } else { + yOffset = (GetHeight() >> 1) - (dstView->GetY() + (dstView->GetHeight() >> 1)); + } + + if ((xOffset != 0) || (yOffset != 0)) { + if (scrollAnimator_.GetState() != Animator::STOP) { + scrollAnimator_.Stop(); + } + if (needAnimator) { + animatorCallback_.SetDragTimes(tickTime_); + animatorCallback_.SetDragStartValue(0, 0); + animatorCallback_.SetDragEndValue(xOffset, yOffset); + scrollAnimator_.Start(); + } else { + MoveChildByOffset(xOffset, yOffset); + } + } +} + +void UISwipeView::StopAnimator() +{ + UIAbstractScroll::StopAnimator(); + if (swipeListener_ != nullptr) { + swipeListener_->OnSwipe(*this); + } +} + +void UISwipeView::SortChild() +{ + if (childrenHead_ == nullptr) { + return; + } + int16_t index = 0; + UIView* pre = childrenHead_; + UIView* next = childrenHead_->GetNextSibling(); + if (direction_ == HORIZONTAL) { + pre->SetX(0); + } else { + pre->SetY(0); + } + pre->SetViewIndex(index); + index++; + + while (next != nullptr) { + if (direction_ == HORIZONTAL) { + next->SetX(pre->GetX() + pre->GetWidth()); + } else { + next->SetY(pre->GetY() + pre->GetHeight()); + } + pre = next; + next->SetViewIndex(index); + next = next->GetNextSibling(); + index++; + } + bool tmpLoop = loop_; + loop_ = false; + SwitchToPage(curIndex_, false); + loop_ = tmpLoop; +} + +void UISwipeView::RefreshCurrentViewInner(int16_t distance, int16_t (UIView::*pfnGetXOrY)() const, + int16_t(UIView::*pfnGetWidthOrHeight)()) +{ + if (childrenHead_ == nullptr) { + curIndex_ = 0; + curView_ = nullptr; + return; + } + + curIndex_ = 0; + curView_ = nullptr; + + uint16_t swipeMid; + if (alignMode_ == ALIGN_LEFT) { + swipeMid = 0; + } else if (alignMode_ == ALIGN_RIGHT) { + swipeMid = (this->*pfnGetWidthOrHeight)(); + } else { + swipeMid = (this->*pfnGetWidthOrHeight)() >> 1; + } + UIView* view = childrenHead_; + + if ((childrenHead_->*pfnGetXOrY)() > swipeMid) { + curIndex_ = childrenHead_->GetViewIndex(); + curView_ = childrenHead_; + } else if ((childrenTail_->*pfnGetXOrY)() + (childrenHead_->*pfnGetWidthOrHeight)() < swipeMid) { + curIndex_ = childrenTail_->GetViewIndex(); + curView_ = childrenTail_; + } else { + while (view != nullptr) { + if ((swipeMid >= (view->*pfnGetXOrY)()) && + (swipeMid <= (view->*pfnGetXOrY)() + (view->*pfnGetWidthOrHeight)())) { + curIndex_ = view->GetViewIndex(); + curView_ = view; + break; + } + view = view->GetNextSibling(); + } + } + if (curView_ == nullptr) { + return; + } + + int16_t accelerationOffset = GetMaxDeltaY() * GetSwipeACCLevel() / DRAG_ACC_FACTOR; + if (distance < 0) { + /* + * 7, 10 : Check whether the current view is dragged by more than 1/5, + * that is, the x or y coordinate plus 7/10 width or height. + */ + if (((curView_->*pfnGetXOrY)() + ((curView_->*pfnGetWidthOrHeight)() >> 1) < swipeMid) && + ((curView_->*pfnGetXOrY)() + ((curView_->*pfnGetWidthOrHeight)() * 7 / 10) - + accelerationOffset < swipeMid)) { + curIndex_++; + } + } else if (distance > 0) { + /* + * 3, 10 : Check whether the current view is dragged by more than 1/5, + * that is, the x or y coordinate plus 3/10 width or height. + */ + if (((curView_->*pfnGetXOrY)() + ((curView_->*pfnGetWidthOrHeight)() >> 1) > swipeMid) && + ((curView_->*pfnGetXOrY)() + ((curView_->*pfnGetWidthOrHeight)() * 3 / 10) + + accelerationOffset > swipeMid)) { + curIndex_--; + } + } else { + if (alignMode_ == ALIGN_LEFT) { + if (((curView_->*pfnGetXOrY)() + ((curView_->*pfnGetWidthOrHeight)() >> 1) < swipeMid)) { + curIndex_++; + } + } else if (alignMode_ == ALIGN_RIGHT) { + if ((curView_->*pfnGetXOrY)() + ((curView_->*pfnGetWidthOrHeight)() >> 1) > swipeMid) { + curIndex_--; + } + } + } +} + +void UISwipeView::RefreshCurrentView(int16_t distance) +{ + if (direction_ == HORIZONTAL) { + RefreshCurrentViewInner(distance, &UIView::GetX, &UIView::GetWidth); + } else { + RefreshCurrentViewInner(distance, &UIView::GetY, &UIView::GetHeight); + } +} + +void UISwipeView::MoveChildByOffset(int16_t xOffset, int16_t yOffset) +{ + UIViewGroup::MoveChildByOffset(xOffset, yOffset); + if (direction_ == HORIZONTAL) { + while (isNeedLoop() && (childrenHead_->GetX() > 0)) { + MoveLastChildToFirst(); + } + while (isNeedLoop() && (childrenTail_->GetX() + childrenTail_->GetWidth() < GetWidth())) { + MoveFirstChildToLast(); + } + } else { + while (isNeedLoop() && (childrenHead_->GetY() > 0)) { + MoveLastChildToFirst(); + } + while (isNeedLoop() && (childrenTail_->GetY() + childrenTail_->GetHeight() < GetHeight())) { + MoveFirstChildToLast(); + } + } +} + +bool UISwipeView::isNeedLoop() +{ + if (!loop_ || (childrenHead_ == nullptr) || (childrenTail_ == nullptr)) { + return false; + } + Rect childRect = GetAllChildRelativeRect(); + if (direction_ == HORIZONTAL) { + if ((childRect.GetWidth() - childrenHead_->GetWidth() >= GetWidth()) && + (childRect.GetWidth() - childrenTail_->GetWidth() >= GetWidth())) { + return true; + } + } else { + if ((childRect.GetHeight() - childrenHead_->GetHeight() >= GetHeight()) && + (childRect.GetHeight() - childrenTail_->GetHeight() >= GetHeight())) { + return true; + } + } + return false; +} + +void UISwipeView::MoveFirstChildToLast() +{ + if ((childrenTail_ == nullptr) || (childrenHead_ == nullptr)) { + return; + } + if (direction_ == HORIZONTAL) { + childrenHead_->SetX(childrenTail_->GetX() + childrenTail_->GetWidth()); + } else { + childrenHead_->SetY(childrenTail_->GetY() + childrenTail_->GetHeight()); + } + UIView* head = childrenHead_; + UIViewGroup::Remove(childrenHead_); + UIViewGroup::Add(head); +} + +void UISwipeView::MoveLastChildToFirst() +{ + if ((childrenTail_ == nullptr) || (childrenHead_ == nullptr)) { + return; + } + if (direction_ == HORIZONTAL) { + childrenTail_->SetX(childrenHead_->GetX() - childrenTail_->GetWidth()); + } else { + childrenTail_->SetY(childrenHead_->GetY() - childrenTail_->GetHeight()); + } + UIView* last = childrenTail_; + UIViewGroup::Remove(childrenTail_); + UIViewGroup::Insert(nullptr, last); +} + +void UISwipeView::CalculateInvalidate() +{ + Rect swipeRect(0, 0, GetRelativeRect().GetWidth() - 1, GetRelativeRect().GetHeight() - 1); + UIView* view = childrenHead_; + bool isFound = false; + while (view != nullptr) { + Rect rect = view->GetRelativeRect(); + if (rect.IsIntersect(swipeRect)) { + if (view->IsVisible() && (view->GetOpaScale() != OPA_TRANSPARENT)) { + view->Invalidate(); + } + isFound = true; + } else if (isFound) { + return; + } + + view = view->GetNextSibling(); + } +} +} // namespace OHOS diff --git a/frameworks/components/ui_texture_mapper.cpp b/frameworks/components/ui_texture_mapper.cpp new file mode 100755 index 0000000..636ecee --- /dev/null +++ b/frameworks/components/ui_texture_mapper.cpp @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "components/ui_texture_mapper.h" + +namespace OHOS { +void UITextureMapper::TextureMapperAnimatorCallback::Callback(UIView* view) +{ + if (view == nullptr) { + return; + } + UITextureMapper* mapper = static_cast(view); + mapper->Callback(); +} + +void UITextureMapper::TextureMapperAnimatorCallback::OnStop(UIView& view) +{ + UITextureMapper& mapper = static_cast(view); + if (mapper.listener_ != nullptr) { + mapper.listener_->OnAnimatorStop(view); + } +} + +UITextureMapper::UITextureMapper() + : animator_(&animatorCallback_, this, 0, false), + listener_(nullptr), + pivot_(0, 0), + rotateCur_(0), + rotateStart_(0), + rotateEnd_(0), + scaleCur_(SCALE_CONVERTION), + scaleStart_(SCALE_CONVERTION), + scaleEnd_(SCALE_CONVERTION), + delayTime_(0), + easingFunc_(EasingEquation::LinearEaseNone) +{ + AnimatorManager::GetInstance()->Add(&animator_); +} + +UITextureMapper::~UITextureMapper() +{ + AnimatorManager::GetInstance()->Remove(&animator_); +} + +void UITextureMapper::Start() +{ + rotateStart_ = rotateCur_; + scaleStart_ = scaleCur_; + animator_.Start(); +} + +void UITextureMapper::Cancel() +{ + animator_.Stop(); +} + +void UITextureMapper::Reset() +{ + Invalidate(); + ResetTransParameter(); + Invalidate(); +} + +void UITextureMapper::Callback() +{ + uint16_t curTime = animator_.GetRunTime(); + if (curTime >= delayTime_) { + uint16_t actualTime = curTime - delayTime_; + uint16_t durationTime = animator_.GetTime() - delayTime_; + + if (scaleStart_ != scaleEnd_) { + scaleCur_ = easingFunc_(scaleStart_, scaleEnd_, actualTime, durationTime); + } + float scale = static_cast(scaleCur_) / SCALE_CONVERTION; + Scale(Vector2(scale, scale), pivot_); + + if (rotateStart_ != rotateEnd_) { + rotateCur_ = easingFunc_(rotateStart_, rotateEnd_, actualTime, durationTime); + } + Rotate(rotateCur_, pivot_); + } +} +} \ No newline at end of file diff --git a/frameworks/components/ui_time_picker.cpp b/frameworks/components/ui_time_picker.cpp new file mode 100755 index 0000000..a7542ba --- /dev/null +++ b/frameworks/components/ui_time_picker.cpp @@ -0,0 +1,291 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "components/ui_time_picker.h" +#include +#include +#include "securec.h" +#include "draw/draw_rect.h" +#include "graphic_log.h" +#include "themes/theme_manager.h" + +namespace OHOS { +UITimePicker::UITimePicker() + : selectedValue_{0}, + selectedHour_{0}, + selectedMinute_{0}, + selectedSecond_{0}, + secVisible_(false), + setSelectedTime_(nullptr), + pickerWidth_(0), + itemsHeight_(0), + xPos_(0), + backgroundFontSize_(0), + highlightFontSize_(0), + backgroundFontName_(nullptr), + highlightFontName_(nullptr), + pickerListener_(this), + timePickerListener_(nullptr) +{ + Theme* theme = ThemeManager::GetInstance().GetCurrent(); + if (theme != nullptr) { + style_ = &(theme->GetPickerBackgroundStyle()); + } else { + style_ = &(StyleDefault::GetPickerBackgroundStyle()); + } + backgroundFontId_ = style_->font_; + backgroundColor_ = style_->textColor_; + if (theme != nullptr) { + style_ = &(theme->GetPickerHighlightStyle()); + } else { + style_ = &(StyleDefault::GetPickerHighlightStyle()); + } + highlightFontId_ = style_->font_; + highlightColor_ = style_->textColor_; + + hourPicker_ = nullptr; + minutePicker_ = nullptr; + secondPicker_ = nullptr; +} + +UITimePicker::~UITimePicker() +{ + DeInitTimePicker(); + if (backgroundFontName_ != nullptr) { + UIFree(backgroundFontName_); + backgroundFontName_ = nullptr; + } + + if (highlightFontName_ != nullptr) { + UIFree(highlightFontName_); + highlightFontName_ = nullptr; + } +} + +void UITimePicker::InitTimePicker() +{ + xPos_ = 0; + if (secVisible_) { + pickerWidth_ = GetWidth() / SEC_VISIBLE_COUNT; + InitPicker(hourPicker_, TIME_START, HOUR_END); + xPos_ = pickerWidth_; + InitPicker(minutePicker_, TIME_START, MIN_END); + xPos_ *= (SEC_VISIBLE_COUNT - 1); + InitPicker(secondPicker_, TIME_START, SEC_END); + } else { + pickerWidth_ = GetWidth() / SEC_INVISIBLE_COUNT; + InitPicker(hourPicker_, TIME_START, HOUR_END); + xPos_ = pickerWidth_; + InitPicker(minutePicker_, TIME_START, MIN_END); + } + + if (setSelectedTime_ == nullptr) { + const char* curTime = secVisible_ ? "00:00:00" : "00:00"; + RefreshSelected(curTime); + } else { + RefreshSelected(setSelectedTime_); + } +} + +void UITimePicker::DeInitTimePicker() +{ + DeInitPicker(secondPicker_); + DeInitPicker(minutePicker_); + DeInitPicker(hourPicker_); +} + +void UITimePicker::RefreshTimePicker() +{ + DeInitTimePicker(); + InitTimePicker(); +} + +void UITimePicker::InitPicker(UIPicker*& picker, int16_t start, int16_t end) +{ + picker = new UIPicker(); + if (picker == nullptr) { + GRAPHIC_LOGE("new UIPicker fail"); + return; + } + picker->SetPosition(xPos_, 0, pickerWidth_, GetHeight()); + picker->SetItemHeight(itemsHeight_); + picker->SetFontId(backgroundFontId_, highlightFontId_); + if ((backgroundFontName_ == nullptr) || (highlightFontName_ == nullptr)) { + picker->SetFontId(backgroundFontId_, highlightFontId_); + } else { + picker->SetBackgroundFont(backgroundFontName_, backgroundFontSize_); + picker->SetHighlightFont(highlightFontName_, highlightFontSize_); + } + picker->SetTextColor(backgroundColor_, highlightColor_); + picker->SetValues(start, end); + picker->RegisterSelectedListener(&pickerListener_); + picker->SetIntercept(false); + Add(picker); + +#if ENABLE_ROTATE_INPUT + if (end == HOUR_END) { + picker->GetChildrenHead()->SetViewId(HOUR_LIST_NAME); + return; + } else if (end == MIN_END) { + if (minutePicker_->GetChildById(MIN_LIST_NAME) == nullptr) { + picker->GetChildrenHead()->SetViewId(MIN_LIST_NAME); + return; + } + } + picker->GetChildrenHead()->SetViewId(SEC_LIST_NAME); + return; +#endif +} + +void UITimePicker::DeInitPicker(UIPicker*& picker) +{ + if (picker != nullptr) { + Remove(picker); + picker->ClearValues(); + delete picker; + picker = nullptr; + } +} + +void UITimePicker::TimeSelectedCallback() +{ + uint16_t hourSelect = hourPicker_->GetSelected(); + uint16_t minSelect = minutePicker_->GetSelected(); + GetValueByIndex(selectedHour_, BUF_SIZE, hourSelect, TIME_START, HOUR_END); + GetValueByIndex(selectedMinute_, BUF_SIZE, minSelect, TIME_START, MIN_END); + + if (secVisible_) { + uint16_t secSelect = secondPicker_->GetSelected(); + GetValueByIndex(selectedSecond_, BUF_SIZE, secSelect, TIME_START, SEC_END); + if (sprintf_s(selectedValue_, SELECTED_VALUE_SIZE, "%s:%s:%s", + selectedHour_, selectedMinute_, selectedSecond_) < 0) { + return; + } + } else { + if (sprintf_s(selectedValue_, SELECTED_VALUE_SIZE, "%s:%s", selectedHour_, selectedMinute_) < 0) { + return; + } + } + + if (timePickerListener_ != nullptr) { + timePickerListener_->OnTimePickerStoped(*this); + } +} + +void UITimePicker::GetValueByIndex(char* value, uint8_t len, uint16_t index, int16_t start, int16_t end) +{ + if ((value != nullptr) && (index < end - start + 1)) { + if (sprintf_s(value, len, "%02d", index) < 0) { + return; + } + } +} + +bool UITimePicker::SetSelected(const char* value) +{ + setSelectedTime_ = value; + return RefreshSelected(value); +} + +bool UITimePicker::RefreshSelected(const char* value) +{ + uint32_t hourSelect; + uint32_t minSelect; + + if (value == nullptr) { + return false; + } + + if (secVisible_) { + uint32_t secSelect; + // 3: three variables + if (sscanf_s(value, "%d:%d:%d", &hourSelect, &minSelect, &secSelect) < 3) { + return false; + } + secondPicker_->SetSelected(secSelect); + } else { + if (sscanf_s(value, "%d:%d", &hourSelect, &minSelect) < 2) { // 2: two variables + return false; + } + } + + hourPicker_->SetSelected(hourSelect); + minutePicker_->SetSelected(minSelect); + return true; +} + +void UITimePicker::SetItemHeight(int16_t height) +{ + itemsHeight_ = height; + RefreshTimePicker(); +} + +void UITimePicker::EnableSecond(bool state) +{ + secVisible_ = state; + RefreshTimePicker(); +} + +void UITimePicker::SetTextStyle(uint8_t backgroundFontId, uint8_t highlightFontId, + ColorType backgroundColor, ColorType highlightColor) +{ + highlightFontId_ = highlightFontId; + if (highlightFontName_ != nullptr) { + UIFree(highlightFontName_); + highlightFontName_ = nullptr; + } + + backgroundFontId_ = backgroundFontId; + if (backgroundFontName_ != nullptr) { + UIFree(backgroundFontName_); + backgroundFontName_ = nullptr; + } + + highlightColor_ = highlightColor; + backgroundColor_ = backgroundColor; + RefreshTimePicker(); +} + +void UITimePicker::SetTextColor(ColorType backgroundColor, ColorType highlightColor) +{ + backgroundColor_ = backgroundColor; + highlightColor_ = highlightColor; + RefreshTimePicker(); +} + +void UITimePicker::SetBackgroundFont(const char* name, uint8_t size) +{ + Text::SetFont(name, size, backgroundFontName_, backgroundFontSize_); + RefreshTimePicker(); +} + +void UITimePicker::SetHighlightFont(const char* name, uint8_t size) +{ + Text::SetFont(name, size, highlightFontName_, highlightFontSize_); + RefreshTimePicker(); +} + +void UITimePicker::SetWidth(int16_t width) +{ + UIView::SetWidth(width); + RefreshTimePicker(); +} + +void UITimePicker::SetHeight(int16_t height) +{ + UIView::SetHeight(height); + RefreshTimePicker(); +} +} diff --git a/frameworks/components/ui_toggle_button.cpp b/frameworks/components/ui_toggle_button.cpp new file mode 100755 index 0000000..ae0b029 --- /dev/null +++ b/frameworks/components/ui_toggle_button.cpp @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "components/ui_toggle_button.h" +#include "common/image.h" +#include "draw/draw_arc.h" +#include "draw/draw_rect.h" +#include "imgdecode/cache_manager.h" + +namespace OHOS { +UIToggleButton::UIToggleButton() : width_(DEFAULT_HOT_WIDTH), height_(DEFAULT_HOT_WIDTH), + corner_(DEFAULT_CORNER_RADIUS), radius_(DEFAULT_CORNER_RADIUS - DEAFULT_RADIUS_DIFF), + rectWidth_(DEFAULT_WIDTH) +{ + image_[UNSELECTED].SetSrc(""); + image_[SELECTED].SetSrc(""); + Resize(width_, height_); +} + +void UIToggleButton::SetState(bool state) +{ + if (state) { + UICheckBox::SetState(SELECTED); + } else { + UICheckBox::SetState(UNSELECTED); + } + Invalidate(); +} + +void UIToggleButton::CalculateSize() +{ + int16_t width = GetWidth(); + int16_t height = GetHeight(); + if ((width_ == width) && (height_ == height)) { + return; + } + width_ = width; + height_ = height; + int16_t minValue = (width_ > height_) ? height_ : width_; + corner_ = DEFAULT_CORNER_RADIUS * minValue / DEFAULT_HOT_HEIGHT; + int16_t radiusDiff = DEAFULT_RADIUS_DIFF * minValue / DEFAULT_HOT_WIDTH; + radius_ = corner_ - radiusDiff; + rectWidth_ = DEFAULT_WIDTH * minValue / DEFAULT_HOT_WIDTH; +} + +void UIToggleButton::OnDraw(const Rect& invalidatedArea) +{ + if ((image_[SELECTED].GetSrcType() != IMG_SRC_UNKNOWN) && (image_[UNSELECTED].GetSrcType() != IMG_SRC_UNKNOWN)) { + UICheckBox::OnDraw(invalidatedArea); + } else { + CalculateSize(); + DrawRect::Draw(GetRect(), invalidatedArea, *style_, opaScale_); + Rect contentRect = GetContentRect(); + int16_t dx = (width_ - rectWidth_) >> 1; + int16_t dy = (height_ >> 1) - corner_; + int16_t x = contentRect.GetX() + dx; + int16_t y = contentRect.GetY() + dy; + Rect rectMid; + rectMid.SetRect(x, y, x + rectWidth_, y + (corner_ << 1) + 1); + Rect trunc = invalidatedArea; + bool isIntersect = trunc.Intersect(trunc, contentRect); + switch (state_) { + case SELECTED: { + Style styleSelect = StyleDefault::GetBackgroundTransparentStyle(); + styleSelect.borderRadius_ = corner_; + styleSelect.bgColor_ = Color::GetColorFromRGB(DEFAULT_BG_RED, DEFAULT_BG_GREEN, DEFAULT_BG_BLUE); + styleSelect.bgOpa_ = OPA_OPAQUE; + if (isIntersect) { + DrawRect::Draw(rectMid, trunc, styleSelect, opaScale_); + } + ArcInfo arcInfoRight = { + { static_cast(x + rectWidth_ - corner_), static_cast(y + corner_) }, { 0 }, + radius_, 0, CIRCLE_IN_DEGREE, nullptr + }; + styleSelect.bgColor_ = Color::White(); + styleSelect.lineWidth_ = radius_; + styleSelect.lineColor_ = Color::White(); + if (isIntersect) { + DrawArc::GetInstance()->Draw(arcInfoRight, trunc, styleSelect, OPA_OPAQUE, CapType::CAP_NONE); + } + break; + } + case UNSELECTED: { + Style styleUnSelect = StyleDefault::GetBackgroundTransparentStyle(); + styleUnSelect.bgColor_ = Color::White(); + styleUnSelect.bgOpa_ = DEFAULT_UNSELECTED_OPA; + styleUnSelect.borderRadius_ = corner_; + if (isIntersect) { + DrawRect::Draw(rectMid, trunc, styleUnSelect, opaScale_); + } + ArcInfo arcInfoLeft = { + { static_cast(x + corner_), static_cast(y + corner_) }, { 0 }, radius_, 0, + CIRCLE_IN_DEGREE, nullptr + }; + styleUnSelect.lineColor_ = Color::White(); + styleUnSelect.lineWidth_ = radius_; + if (isIntersect) { + DrawArc::GetInstance()->Draw(arcInfoLeft, trunc, styleUnSelect, OPA_OPAQUE, CapType::CAP_NONE); + } + break; + } + default: + break; + } + } +} +} // namespace OHOS \ No newline at end of file diff --git a/frameworks/components/ui_video.cpp b/frameworks/components/ui_video.cpp new file mode 100755 index 0000000..bbf8549 --- /dev/null +++ b/frameworks/components/ui_video.cpp @@ -0,0 +1,719 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "components/ui_video.h" +#include "securec.h" + +#ifndef VERSION_LITE +namespace OHOS { +UIVideo::UIVideo() +{ + SetTouchable(true); + SetOnTouchListener(this); +} + +UIVideo::~UIVideo() +{ + if (videoPlayer_ != nullptr) { + videoPlayer_->Stop(); + videoPlayer_->Reset(); + videoPlayer_->Release(); + } + if (sliderAnimator_ != nullptr) { + AnimatorManager::GetInstance()->Remove(sliderAnimator_); + delete sliderAnimator_; + sliderAnimator_ = nullptr; + } + if (surfaceView_ != nullptr) { + delete surfaceView_; + surfaceView_ = nullptr; + } + DeleteController(); +} + +bool UIVideo::SetSrc(const char* source) +{ + if (source == nullptr) { + return false; + } + + InitVideo(); + src_ = source; + std::string uri(source); + std::map header; + Source videoSource(uri, header); + if (videoPlayer_ != nullptr) { + int32_t ret = videoPlayer_->SetSource(videoSource); + if (ret == 0) { + return true; + } + } + return false; +} + +bool UIVideo::Prepare() +{ + if (src_ == nullptr) { + return false; + } + if (videoPlayer_ == nullptr) { + return false; + } + + int32_t ret = videoPlayer_->Prepare(); + if (ret != 0) { + return false; + } + SetVolume(DEFAULT_VOLUME, DEFAULT_VOLUME); + videoPlayer_->GetDuration(duration_); + if (totalTimeLabel_ != nullptr) { + char timer[10] = {0}; // 10:timer length + if (!GetTimerFromMSecond(duration_, timer, sizeof(timer))) { + return false; + } + totalTimeLabel_->SetText(&timer[0]); + } + + if (titleLabel_ != nullptr) { + const char* fileName = nullptr; + fileName = strrchr(src_, '/'); + if (fileName != nullptr) { + titleLabel_->SetText(fileName + 1); + } + } + if (sliderAnimatorCallback_) { + sliderAnimatorCallback_->SetDuration(duration_); + } + SetSurfaceInfo(); + + playerListener_ = std::make_shared(); + playerListener_->SetVideoPlayer(this); + videoPlayer_->SetPlayerCallback(playerListener_); + return true; +} + +bool UIVideo::Play() +{ + if (videoPlayer_ != nullptr) { + int32_t ret = videoPlayer_->Play(); + if (ret == 0) { + if (sliderAnimator_ != nullptr) { + sliderAnimator_->Start(); + } + if (pauseButton_ != nullptr) { + pauseButton_->SetVisible(false); + } + if (playButton_ != nullptr) { + playButton_->SetState(true); + } + if (surfaceView_ != nullptr) { + surfaceView_->SetVisible(true); + } + if (videoPlayerListener_ != nullptr) { + videoPlayerListener_->OnPlaybackPlay(); + } + return true; + } + } + return false; +} + +bool UIVideo::IsPlaying() +{ + if (videoPlayer_ != nullptr) { + return videoPlayer_->IsPlaying(); + } + return false; +} + +bool UIVideo::Pause() +{ + if (videoPlayer_ != nullptr) { + int32_t ret = videoPlayer_->Pause(); + if (ret == 0) { + if (sliderAnimator_ != nullptr) { + sliderAnimator_->Pause(); + } + if (playButton_ != nullptr) { + playButton_->SetState(false); + } + if (pauseButton_ != nullptr) { + pauseButton_->SetVisible(true); + } + if (videoPlayerListener_ != nullptr) { + videoPlayerListener_->OnPlaybackPause(); + } + return true; + } + } + return false; +} + +bool UIVideo::Stop() +{ + if (videoPlayer_ != nullptr) { + int32_t ret = videoPlayer_->Stop(); + if (ret == 0) { + if (sliderAnimator_ != nullptr) { + sliderAnimator_->Stop(); + } + if (playSlider_ != nullptr) { + playSlider_->SetValue(0); + } + if (currentTimeLabel_ != nullptr) { + currentTimeLabel_->SetText("00:00:00"); + } + if (totalTimeLabel_ != nullptr) { + totalTimeLabel_->SetText("00:00:00"); + } + if (playButton_ != nullptr) { + playButton_->SetState(false); + } + if (pauseButton_ != nullptr) { + pauseButton_->SetVisible(true); + } + if (surfaceView_ != nullptr) { + surfaceView_->SetVisible(false); + } + if (videoPlayerListener_ != nullptr) { + videoPlayerListener_->OnPlaybackStop(); + } + return true; + } + } + return false; +} + +bool UIVideo::Rewind(int64_t mSeconds) +{ + if (videoPlayer_ != nullptr) { + int32_t ret = videoPlayer_->Rewind(mSeconds, PLAYER_SEEK_PREVIOUS_SYNC); + if (ret == 0) { + return true; + } + } + return false; +} + +bool UIVideo::SetVolume(float leftVolume, float rightVolume) +{ + if (videoPlayer_ != nullptr) { + int32_t ret = videoPlayer_->SetVolume(leftVolume * MAX_VOLUME, rightVolume * MAX_VOLUME); + if (ret == 0) { + leftVolumeValue_ = leftVolume; + rightVolumeValue_ = rightVolume; + if (volumeButton_ != nullptr) { + if ((leftVolumeValue_ == 0) && (rightVolumeValue_ == 0)) { + volumeButton_->SetState(true); + } else { + volumeButton_->SetState(false); + } + } + return true; + } + } + return false; +} + +bool UIVideo::IsSingleLooping() +{ + if (videoPlayer_ != nullptr) { + return videoPlayer_->IsSingleLooping(); + } + return false; +} + +bool UIVideo::GetCurrentTime(int64_t& time) +{ + if (videoPlayer_ != nullptr) { + int32_t ret = videoPlayer_->GetCurrentTime(time); + if (ret == 0) { + return true; + } + } + return false; +} + +bool UIVideo::GetDuration(int64_t& durationMs) +{ + if (videoPlayer_ != nullptr) { + int32_t ret = videoPlayer_->GetDuration(durationMs); + if (ret == 0) { + return true; + } + } + return false; +} + +void UIVideo::SetVideoPlayerListener(VideoPlayerListener* listener) +{ + videoPlayerListener_ = listener; + + if (playerListener_ != nullptr) { + playerListener_->SetVideoPlayerListerner(videoPlayerListener_); + } +} + +bool UIVideo::Reset() +{ + if (videoPlayer_ != nullptr) { + if (sliderAnimator_ != nullptr) { + sliderAnimator_->Stop(); + } + if (playSlider_ != nullptr) { + playSlider_->SetValue(0); + } + if (currentTimeLabel_ != nullptr) { + currentTimeLabel_->SetText("00:00"); + } + if (totalTimeLabel_ != nullptr) { + totalTimeLabel_->SetText("00:00"); + } + if (pauseButton_ != nullptr) { + pauseButton_->SetVisible(true); + } + if (playButton_ != nullptr) { + playButton_->SetState(false); + } + int32_t ret = videoPlayer_->Reset(); + if (ret == 0) { + videoPlayer_->Release(); + return true; + } + } + return false; +} + +bool UIVideo::EnableSingleLooping(bool loop) +{ + if (videoPlayer_ != nullptr) { + int32_t ret = videoPlayer_->EnableSingleLooping(loop); + if (ret == 0) { + return true; + } + } + return false; +} + +void UIVideo::ShowController(bool show) +{ + if (controllerGroup_ != nullptr) { + if (controllerGroup_->IsVisible() != show) { + controllerGroup_->SetVisible(show); + Invalidate(); + } + } +} + +void UIVideo::InitVideo() +{ + if (videoPlayer_ == nullptr) { + videoPlayer_ = std::make_shared(); + } + + InitControllerLabel(); + InitControllerButton(); + InitControllerSlider(); + + if (surfaceView_ == nullptr) { + surfaceView_ = new UISurfaceView(); + if (surfaceView_ == nullptr) { + GRAPHIC_LOGE("new UISurfaceView fail"); + return; + } + surfaceView_->SetPosition(0, 0); + surfaceView_->SetWidth(DEFAULT_VIEW_WIDTH); + surfaceView_->SetHeight(DEFAULT_VIEW_HEIGHT); + Add(surfaceView_); + } + if (controllerGroup_ == nullptr) { + controllerGroup_ = new UIViewGroup(); + if (controllerGroup_ == nullptr) { + GRAPHIC_LOGE("new UIViewGroup fail"); + return; + } + controllerGroup_->SetPosition(0, 0, DEFAULT_VIEW_WIDTH, DEFAULT_VIEW_HEIGHT); + controllerGroup_->SetStyle(STYLE_BACKGROUND_OPA, 0); + controllerGroup_->Add(titleLabel_); + controllerGroup_->Add(totalTimeLabel_); + controllerGroup_->Add(currentTimeLabel_); + controllerGroup_->Add(playSlider_); + controllerGroup_->Add(volumeButton_); + controllerGroup_->Add(playButton_); + controllerGroup_->Add(pauseButton_); + Add(controllerGroup_); + } + if (sliderAnimatorCallback_ == nullptr) { + sliderAnimatorCallback_ = new SliderAnimatorCallback(this, playSlider_, currentTimeLabel_); + if (sliderAnimatorCallback_ == nullptr) { + GRAPHIC_LOGE("new SliderAnimatorCallback fail"); + return; + } + sliderAnimatorCallback_->SetPlayButton(playButton_); + } + if (sliderAnimator_ == nullptr) { + sliderAnimator_ = new Animator(sliderAnimatorCallback_, this, 0, true); + if (sliderAnimator_ == nullptr) { + GRAPHIC_LOGE("new Animator fail"); + return; + } + AnimatorManager::GetInstance()->Add(sliderAnimator_); + sliderAnimatorCallback_->SetSliderAnimator(sliderAnimator_); + } +} + +void UIVideo::InitControllerLabel() +{ + if (titleLabel_ == nullptr) { + titleLabel_ = new UILabel(); + if (titleLabel_ == nullptr) { + GRAPHIC_LOGE("new UILabel fail"); + return; + } + titleLabel_->SetPosition(322, 28, 316, 29); // 322:pos x, 28:pos y, 316:width, 29:height + titleLabel_->SetAlign(UITextLanguageAlignment::TEXT_ALIGNMENT_CENTER, + UITextLanguageAlignment::TEXT_ALIGNMENT_CENTER); + titleLabel_->SetFont(DEFAULT_VECTOR_FONT_FILENAME, 20); // 20:font size + titleLabel_->SetText("title title"); + titleLabel_->SetTextColor(Color::White()); + } + + if (totalTimeLabel_ == nullptr) { + totalTimeLabel_ = new UILabel(); + if (totalTimeLabel_ == nullptr) { + GRAPHIC_LOGE("new UILabel fail"); + return; + } + // 580:pos x, 56:pos y offset + totalTimeLabel_->SetPosition(580, DEFAULT_VIEW_HEIGHT - 56, TIME_LABEL_WIDTH, TIME_LABEL_HEIGHT); + totalTimeLabel_->SetAlign(UITextLanguageAlignment::TEXT_ALIGNMENT_RIGHT, + UITextLanguageAlignment::TEXT_ALIGNMENT_CENTER); + totalTimeLabel_->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + totalTimeLabel_->SetTextColor(Color::White()); + totalTimeLabel_->SetText("00:00:00"); + } + + if (currentTimeLabel_ == nullptr) { + currentTimeLabel_ = new UILabel(); + if (currentTimeLabel_ == nullptr) { + GRAPHIC_LOGE("new UILabel fail"); + return; + } + // 80:pos x, 56:pos y offset + currentTimeLabel_->SetPosition(80, DEFAULT_VIEW_HEIGHT - 56, TIME_LABEL_WIDTH, TIME_LABEL_HEIGHT); + currentTimeLabel_->SetAlign(UITextLanguageAlignment::TEXT_ALIGNMENT_LEFT, + UITextLanguageAlignment::TEXT_ALIGNMENT_CENTER); + currentTimeLabel_->SetStyle(STYLE_BACKGROUND_COLOR, Color::Red().full); + currentTimeLabel_->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + currentTimeLabel_->SetText("00:00:00"); + currentTimeLabel_->SetTextColor(Color::White()); + } +} + +void UIVideo::InitControllerButton() +{ + if (playButton_ == nullptr) { + playButton_ = new UIToggleButton(); + if (playButton_ == nullptr) { + GRAPHIC_LOGE("new UIToggleButton fail"); + return; + } + // 24:pox x, 88:pos y offset + playButton_->SetPosition(24, DEFAULT_VIEW_HEIGHT - 88, TOGGLE_BUTTON_WIDTH, TOGGLE_BUTTON_HEIGHT); + playButton_->SetImages(MEDIA_IMAGE_PAUSE, MEDIA_IMAGE_PLAY); + playButton_->SetState(true); + playButton_->SetOnClickListener(this); + } + + if (pauseButton_ == nullptr) { + pauseButton_ = new UIToggleButton(); + if (pauseButton_ == nullptr) { + GRAPHIC_LOGE("new UIToggleButton fail"); + return; + } + // 448:pox x, 208:pos y 2:double width 2:double height + pauseButton_->SetPosition(448, 208, 2 * TOGGLE_BUTTON_WIDTH, 2 * TOGGLE_BUTTON_HEIGHT); + pauseButton_->SetImages(MEDIA_IMAGE_PLAY_CENTER, MEDIA_IMAGE_PLAY_CENTER); + pauseButton_->SetVisible(false); + pauseButton_->SetOnClickListener(this); + } + + if (volumeButton_ == nullptr) { + volumeButton_ = new UIToggleButton(); + if (volumeButton_ == nullptr) { + GRAPHIC_LOGE("new UIToggleButton fail"); + return; + } + // 56:pox x offset, 88:pos y offset + volumeButton_->SetPosition(DEFAULT_VIEW_WIDTH - 56, DEFAULT_VIEW_HEIGHT - 88, TOGGLE_BUTTON_WIDTH, + TOGGLE_BUTTON_HEIGHT); + volumeButton_->SetImages(MEDIA_IMAGE_MUTE, MEDIA_IMAGE_VOLUME); + volumeButton_->SetOnClickListener(this); + } +} + +void UIVideo::OnVideoComplete() +{ + if (videoPlayer_ != nullptr) { + videoPlayer_->Stop(); + completeFlag_ = true; + + if (sliderAnimator_ != nullptr) { + sliderAnimator_->Stop(); + } + if (playSlider_ != nullptr) { + playSlider_->SetValue(playSlider_->GetRangeMax()); + } + if (playButton_ != nullptr) { + playButton_->SetState(false); + } + if (pauseButton_ != nullptr) { + pauseButton_->SetVisible(true); + } + if (controllerGroup_ != nullptr) { + controllerGroup_->SetVisible(true); + } + Invalidate(); + } +} + +void UIVideo::InitControllerSlider() +{ + if (playSlider_ == nullptr) { + playSlider_ = new UISlider(); + if (playSlider_ == nullptr) { + GRAPHIC_LOGE("new UISlider fail"); + return; + } + // 80:pos x, 74:width offset, 5:multiply, 6:divide, 4:height + playSlider_->SetPosition(80, DEFAULT_VIEW_HEIGHT - 74, DEFAULT_VIEW_WIDTH * 5 / 6, 4); + playSlider_->SetValidHeight(4); // 4:height + playSlider_->SetValidWidth(DEFAULT_VIEW_WIDTH * 5 / 6); // 5:multiply, 6:divide + playSlider_->SetRange(100, 0); // 100:range max + playSlider_->SetValue(0); + playSlider_->SetKnobWidth(KNOB_WIDTH); + playSlider_->SetSliderRadius(5, 5, KNOB_WIDTH / 2); // 5:background radius, 5:foreground radius, 2:half + playSlider_->SetKnobStyle(STYLE_BACKGROUND_COLOR, Color::White().full); + playSlider_->SetBackgroundStyle(STYLE_BACKGROUND_COLOR, 0x1A888888); // 0x1A888888:slider background color + playSlider_->SetBackgroundStyle(STYLE_BACKGROUND_OPA, 90); // 90:background opa + playSlider_->SetDirection(UISlider::Direction::DIR_LEFT_TO_RIGHT); + playSlider_->SetSliderEventListener(this); + } +} + +void UIVideo::DeleteController() +{ + if (controllerGroup_ != nullptr) { + controllerGroup_->RemoveAll(); + delete controllerGroup_; + controllerGroup_ = nullptr; + } + if (playButton_ != nullptr) { + delete playButton_; + playButton_ = nullptr; + } + if (pauseButton_ != nullptr) { + delete pauseButton_; + pauseButton_ = nullptr; + } + if (volumeButton_ != nullptr) { + delete volumeButton_; + volumeButton_ = nullptr; + } + if (playSlider_ != nullptr) { + delete playSlider_; + playSlider_ = nullptr; + } + if (sliderAnimatorCallback_ != nullptr) { + delete sliderAnimatorCallback_; + sliderAnimatorCallback_ = nullptr; + } + if (titleLabel_ != nullptr) { + delete titleLabel_; + titleLabel_ = nullptr; + } + if (totalTimeLabel_ != nullptr) { + delete totalTimeLabel_; + totalTimeLabel_ = nullptr; + } + if (currentTimeLabel_ != nullptr) { + delete currentTimeLabel_; + currentTimeLabel_ = nullptr; + } +} + +bool UIVideo::OnClick(UIView& view, const ClickEvent& event) +{ + if (videoPlayer_ == nullptr) { + return true; + } + if (&view == playButton_) { + if (completeFlag_) { + completeFlag_ = false; + if (pauseButton_ != nullptr) { + pauseButton_->SetVisible(true); + } + if (controllerGroup_ != nullptr) { + controllerGroup_->SetVisible(true); + } + return true; + } + + if ((sliderAnimatorCallback_ != nullptr) && (pauseButton_ != nullptr) && (playButton_ != nullptr)) { + sliderAnimatorCallback_->ResetTickTime(); + if (playButton_->GetState()) { + Play(); + pauseButton_->SetVisible(false); + } else { + Pause(); + pauseButton_->SetVisible(true); + } + pauseButton_->Invalidate(); + } + } else if (&view == pauseButton_) { + if ((pauseButton_ != nullptr) && pauseButton_->IsVisible()) { + pauseButton_->SetVisible(false); + Play(); + } + } else if (&view == volumeButton_) { + if ((volumeButton_ != nullptr) && volumeButton_->GetState()) { + videoPlayer_->SetVolume(0, 0); + } else { + SetVolume(leftVolumeValue_, rightVolumeValue_); + } + } + return true; +} + +bool UIVideo::OnPress(UIView& view, const PressEvent& event) +{ + if (sliderAnimatorCallback_ != nullptr) { + sliderAnimatorCallback_->ResetTickTime(); + if (controllerGroup_ != nullptr) { + controllerGroup_->SetVisible(true); + } + Invalidate(); + } + return true; +} + +void UIVideo::OnChange(int32_t progress) +{ + if (videoPlayer_ != nullptr) { + if (sliderAnimatorCallback_ != nullptr) { + sliderAnimatorCallback_->ResetTickTime(); + } + int64_t currentValue = progress * duration_ / 100; // 100:percent + if (currentTimeLabel_ != nullptr) { + char timer[10] = {0}; // 10:timer length + if (!GetTimerFromMSecond(currentValue, timer, sizeof(timer))) { + return; + } + currentTimeLabel_->SetText(&timer[0]); + } + videoPlayer_->Rewind(currentValue, PLAYER_SEEK_PREVIOUS_SYNC); + } +} + +void UIVideo::SetSurfaceInfo() +{ + if (videoPlayer_ == nullptr) { + return; + } + int32_t width = 0; + int32_t height = 0; + videoPlayer_->GetVideoWidth(width); + videoPlayer_->GetVideoHeight(height); + + if ((width <= 0) || (height <= 0)) { + videoPlayer_->Reset(); + return; + } + + int16_t viewWidth = GetWidth(); + int16_t viewHeight = GetHeight(); + if ((viewWidth <= 0) || (viewHeight <= 0)) { + videoPlayer_->Reset(); + return; + } + float ratioX = static_cast(width) / viewWidth; + float ratioY = static_cast(height) / viewHeight; + uint16_t surfaceViewWidth; + uint16_t surfaceViewHeight; + uint16_t surfaceViewPositionX = 0; + uint16_t surfaceViewPositionY = 0; + if (ratioX > ratioY) { + surfaceViewWidth = viewWidth; + surfaceViewHeight = height / ratioX; + surfaceViewPositionY = (viewHeight - surfaceViewHeight) / 2; // 2:half + } else { + surfaceViewWidth = width / ratioY; + surfaceViewHeight = viewHeight; + surfaceViewPositionX = (viewWidth - surfaceViewWidth) / 2; // 2:half + } + if (surfaceView_ != nullptr) { + surfaceView_->SetPosition(surfaceViewPositionX, surfaceViewPositionY); + surfaceView_->SetWidth(surfaceViewWidth - 1); + surfaceView_->SetHeight(surfaceViewHeight); + videoPlayer_->SetVideoSurface(surfaceView_->GetSurface()); + } +} + +bool UIVideo::GetTimerFromMSecond(int64_t currentTime, char* timer, int32_t len) +{ + int64_t currentSecond = currentTime / 1000; // 1000:millisecond + int32_t second = currentSecond % 60; // 60:second + int32_t minute = (currentSecond / 60) % 60; // 60:minute 60:second + int32_t hour = (currentSecond / 60) / 60; // 60:minute 60:second + int32_t ret = 0; + ret = sprintf_s(timer, len, "%02d:%02d:%02d", hour, minute, second); + if (ret < 0) { + return false; + } + return true; +} + +void UIVideo::SliderAnimatorCallback::Callback(UIView* view) +{ + if (video_ != nullptr) { + int64_t currentTime = 0; + video_->GetCurrentTime(currentTime); + if (timeLabel_ != nullptr) { + char timer[10] = {0}; // 10:timer length + if (!video_->GetTimerFromMSecond(currentTime, timer, sizeof(timer))) { + return; + } + timeLabel_->SetText(&timer[0]); + timeLabel_->Invalidate(); + } + if (slider_ != nullptr) { + int64_t curPosition = currentTime * slider_->GetRangeMax() / duration_; + slider_->SetValue(curPosition); + slider_->Invalidate(); + } + + uint32_t runTime = 0; + if (sliderAnimator_ != nullptr) { + runTime = sliderAnimator_->GetRunTime(); + } + if ((runTime > tickCount_) && (runTime - tickCount_ >= HIDE_MILLI_SECOND)) { + video_->ShowController(false); + tickCount_ = runTime; + } + } +} +} // namespace OHOS + +#endif // VERSION_LITE \ No newline at end of file diff --git a/frameworks/components/ui_view.cpp b/frameworks/components/ui_view.cpp new file mode 100755 index 0000000..5838a4b --- /dev/null +++ b/frameworks/components/ui_view.cpp @@ -0,0 +1,785 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "components/ui_view.h" +#include "components/root_view.h" +#include "core/render_manager.h" +#include "dock/focus_manager.h" +#include "draw/draw_rect.h" +#include "graphic_log.h" +#include "themes/theme_manager.h" + +namespace OHOS { +UIView::UIView() + : touchable_(false), + visible_(true), + draggable_(false), + dragParentInstead_(true), + isViewGroup_(false), + needRedraw_(false), + styleAllocFlag_(false), + isIntercept_(true), + opaScale_(OPA_OPAQUE), + index_(0), + id_(nullptr), + parent_(nullptr), + nextSibling_(nullptr), + style_(nullptr), + transMap_(nullptr), + onClickListener_(nullptr), + onLongPressListener_(nullptr), + onDragListener_(nullptr), + onTouchListener_(nullptr), +#if ENABLE_ROTATE_INPUT + onRotateListener_(nullptr), +#endif + viewExtraMsg_(nullptr), + rect_(0, 0, 0, 0), + visibleRect_(nullptr) +{ + SetupThemeStyles(); +} + +UIView::~UIView() +{ + if (transMap_ != nullptr) { + delete transMap_; + transMap_ = nullptr; + } + if (visibleRect_ != nullptr) { + delete visibleRect_; + visibleRect_ = nullptr; + } + if (styleAllocFlag_) { + delete style_; + style_ = nullptr; + styleAllocFlag_ = false; + } +} + +bool UIView::OnPreDraw(Rect& invalidatedArea) const +{ + Rect rect(GetRect()); + int16_t r = style_->borderRadius_; + if (r == COORD_MAX) { + return true; + } + if (r != 0) { + r = ((r & 0x1) == 0) ? (r >> 1) : ((r + 1) >> 1); + rect.SetLeft(rect.GetX() + r); + rect.SetWidth(rect.GetWidth() - r); + rect.SetTop(rect.GetY() + r); + rect.SetHeight(rect.GetHeight() - r); + } + if (rect.IsContains(invalidatedArea)) { + return true; + } + invalidatedArea.Intersect(invalidatedArea, rect); + return false; +} + +void UIView::OnDraw(const Rect& invalidatedArea) +{ + uint8_t opa = GetMixOpaScale(); + DrawRect::Draw(GetOrigRect(), invalidatedArea, *style_, opa); +} + +void UIView::SetupThemeStyles() +{ + Theme* theme = ThemeManager::GetInstance().GetCurrent(); + if (theme != nullptr) { + style_ = &(theme->GetMainStyle()); + } else { + style_ = &(StyleDefault::GetDefaultStyle()); + } +} + +void UIView::SetStyle(Style& style) +{ + if (styleAllocFlag_) { + delete style_; + styleAllocFlag_ = false; + } + style_ = &style; +} + +void UIView::SetStyle(uint8_t key, int64_t value) +{ + if (!styleAllocFlag_) { + style_ = new Style(*style_); + if (style_ == nullptr) { + GRAPHIC_LOGE("new Style fail"); + return; + } + styleAllocFlag_ = true; + } + int16_t width = GetWidth(); + int16_t height = GetHeight(); + style_->SetStyle(key, value); + switch (key) { + case STYLE_BORDER_WIDTH: { + SetWidth(width); + SetHeight(height); + break; + } + case STYLE_PADDING_LEFT: + case STYLE_PADDING_RIGHT: { + SetWidth(width); + break; + } + case STYLE_PADDING_TOP: + case STYLE_PADDING_BOTTOM: { + SetHeight(height); + break; + } + default: + break; + } +} + +void UIView::Rotate(int16_t angle, const Vector2& pivot) +{ + if (transMap_ == nullptr) { + ReMeasure(); + transMap_ = new TransformMap(); + if (transMap_ == nullptr) { + GRAPHIC_LOGE("new TransformMap fail"); + return; + } + } + Rect joinRect = transMap_->GetBoxRect(); + transMap_->SetTransMapRect(GetOrigRect()); + transMap_->Rotate(angle, pivot); + joinRect.Join(joinRect, transMap_->GetBoxRect()); + joinRect.Join(joinRect, GetOrigRect()); + InvalidateRect(joinRect); +} + +void UIView::Scale(const Vector2& scale, const Vector2& pivot) +{ + if (transMap_ == nullptr) { + ReMeasure(); + transMap_ = new TransformMap(); + if (transMap_ == nullptr) { + GRAPHIC_LOGE("new TransformMap fail"); + return; + } + } + Rect joinRect = transMap_->GetBoxRect(); + transMap_->SetTransMapRect(GetOrigRect()); + transMap_->Scale(scale, pivot); + joinRect.Join(joinRect, transMap_->GetBoxRect()); + joinRect.Join(joinRect, GetOrigRect()); + InvalidateRect(joinRect); +} + +void UIView::Translate(const Vector2& trans) +{ + if (transMap_ == nullptr) { + ReMeasure(); + transMap_ = new TransformMap(GetOrigRect()); + if (transMap_ == nullptr) { + GRAPHIC_LOGE("new TransformMap fail"); + return; + } + } + transMap_->Translate(trans); + + Rect prevRect = GetRect(); + Rect mapRect = transMap_->GetBoxRect(); + + Rect joinRect; + joinRect.Join(prevRect, mapRect); + InvalidateRect(joinRect); +} + +bool UIView::IsTransInvalid() +{ + if (transMap_ == nullptr) { + return true; + } + return transMap_->IsInvalid(); +} + +void UIView::ResetTransParameter() +{ + if (transMap_ != nullptr) { + delete transMap_; + transMap_ = nullptr; + } +} + +#if ENABLE_ROTATE_INPUT +void UIView::RequestFocus() +{ + FocusManager::GetInstance()->RequestFocus(this); +} + +void UIView::ClearFocus() +{ + FocusManager::GetInstance()->ClearFocus(); +} +#endif + +void UIView::Invalidate() +{ + InvalidateRect(GetOrigRect()); +} + +void UIView::InvalidateRect(const Rect& invalidatedArea) +{ + if (!visible_) { + if (needRedraw_) { + needRedraw_ = false; + } else { + return; + } + } + + Rect trunc(invalidatedArea); + bool isIntersect = true; + UIView* par = parent_; + UIView* cur = this; + + while (par != nullptr) { + if (!par->visible_) { + return; + } + + isIntersect = trunc.Intersect(par->GetContentRect(), trunc); + if (!isIntersect) { + break; + } + + cur = par; + par = par->parent_; + } + + if (isIntersect && (cur->GetViewType() == UI_ROOT_VIEW)) { + RootView* rootView = reinterpret_cast(cur); + rootView->AddInvalidateRectWithLock(trunc, this); + } +} + +bool UIView::OnLongPressEvent(const LongPressEvent& event) +{ + if (onLongPressListener_ != nullptr) { + /* To ensure version compatibility, the listeners of both versions are invoked. */ + bool isConsumed = onLongPressListener_->OnLongPress(*this, event); + return isConsumed; + } + return false; +} + +bool UIView::OnDragStartEvent(const DragEvent& event) +{ + if (onDragListener_ != nullptr) { + /* To ensure version compatibility, the listeners of both versions are invoked. */ + bool isConsumed = onDragListener_->OnDragStart(*this, event); + return isConsumed; + } + return false; +} + +bool UIView::OnDragEvent(const DragEvent& event) +{ + if (onDragListener_ != nullptr) { + /* To ensure version compatibility, the listeners of both versions are invoked. */ + bool isConsumed = onDragListener_->OnDrag(*this, event); + return isConsumed; + } + return false; +} + +bool UIView::OnDragEndEvent(const DragEvent& event) +{ + if (onDragListener_ != nullptr) { + /* To ensure version compatibility, the listeners of both versions are invoked. */ + bool isConsumed = onDragListener_->OnDragEnd(*this, event); + return isConsumed; + } + return false; +} + +bool UIView::OnClickEvent(const ClickEvent& event) +{ + if (onClickListener_ != nullptr) { + /* To ensure version compatibility, the listeners of both versions are invoked. */ + bool isConsumed = onClickListener_->OnClick(*this, event); + return isConsumed; + } + return false; +} + +bool UIView::OnPressEvent(const PressEvent& event) +{ + if (onTouchListener_ != nullptr) { + /* To ensure version compatibility, the listeners of both versions are invoked. */ + bool isConsumed = onTouchListener_->OnPress(*this, event); + return isConsumed; + } + return false; +} + +bool UIView::OnReleaseEvent(const ReleaseEvent& event) +{ + if (onTouchListener_ != nullptr) { + /* To ensure version compatibility, the listeners of both versions are invoked. */ + bool isConsumed = onTouchListener_->OnRelease(*this, event); + return isConsumed; + } + return false; +} + +bool UIView::OnCancelEvent(const CancelEvent& event) +{ + if (onTouchListener_ != nullptr) { + /* To ensure version compatibility, the listeners of both versions are invoked. */ + bool isConsumed = onTouchListener_->OnCancel(*this, event); + return isConsumed; + } + return false; +} + +#if ENABLE_ROTATE_INPUT +bool UIView::OnRotateEvent(const RotateEvent& event) +{ + if (onRotateListener_ != nullptr) { + return onRotateListener_->OnRotate(*this, event); + } + return false; +} +#endif + +void UIView::GetTargetView(const Point& point, UIView** last) +{ + if (last == nullptr) { + return; + } + UIView* par = parent_; + Rect rect = GetRect(); + + if (par != nullptr) { + rect.Intersect(par->GetContentRect(), rect); + } + + if (visible_ && touchable_ && rect.IsContains(point)) { + *last = this; + } +} + +void UIView::GetTargetView(const Point& point, UIView** current, UIView** target) +{ + if (current == nullptr) { + return; + } + UIView* par = parent_; + Rect rect = GetRect(); + + if (par != nullptr) { + rect.Intersect(par->GetContentRect(), rect); + } + + if (visible_ && rect.IsContains(point)) { + if (touchable_) { + *current = this; + } + *target = this; + } +} + +Rect UIView::GetRect() const +{ + if ((transMap_ != nullptr) && !transMap_->IsInvalid()) { + Rect r = transMap_->GetBoxRect(); + Rect origRect = GetOrigRect(); + r.SetX(r.GetX() + origRect.GetX() - transMap_->GetTransMapRect().GetX()); + r.SetY(r.GetY() + origRect.GetY() - transMap_->GetTransMapRect().GetY()); + return r; + } + return GetOrigRect(); +} + +Rect UIView::GetContentRect() +{ + if ((transMap_ != nullptr) && !transMap_->IsInvalid()) { + Rect r = transMap_->GetBoxRect(); + Rect origRect = GetOrigRect(); + r.SetX(r.GetX() + origRect.GetX() - transMap_->GetTransMapRect().GetX()); + r.SetY(r.GetY() + origRect.GetY() - transMap_->GetTransMapRect().GetY()); + return r; + } + + Rect contentRect = GetRect(); + contentRect.SetX(contentRect.GetX() + style_->paddingLeft_ + style_->borderWidth_); + contentRect.SetY(contentRect.GetY() + style_->paddingTop_ + style_->borderWidth_); + contentRect.SetWidth(GetWidth()); + contentRect.SetHeight(GetHeight()); + return contentRect; +} + +Rect UIView::GetOrigRect() const +{ + int16_t x = rect_.GetX(); + int16_t y = rect_.GetY(); + UIView* par = parent_; + while (par != nullptr) { + x += par->GetX() + par->style_->paddingLeft_ + par->style_->borderWidth_; + y += par->GetY() + par->style_->paddingTop_ + par->style_->borderWidth_; + par = par->parent_; + } + return Rect(x, y, x + rect_.GetWidth() - 1, y + rect_.GetHeight() - 1); +} + +Rect UIView::GetMaskedRect() const +{ + Rect mask; + if (visibleRect_ != nullptr) { + mask.Intersect(GetRect(), GetVisibleRect()); + } else { + mask = GetRect(); + } + return mask; +} + +Rect UIView::GetVisibleRect() const +{ + if (visibleRect_ == nullptr) { + return GetRect(); + } + Rect absoluteRect; + int16_t x = visibleRect_->GetX(); + int16_t y = visibleRect_->GetY(); + UIView* par = parent_; + while (par != nullptr) { + x += par->GetX(); + y += par->GetY(); + par = par->parent_; + } + absoluteRect.SetX(x); + absoluteRect.SetY(y); + absoluteRect.SetWidth(visibleRect_->GetWidth()); + absoluteRect.SetHeight(visibleRect_->GetHeight()); + return absoluteRect; +} + +void UIView::SetTransformMap(const TransformMap& transMap) +{ + if (transMap.IsInvalid()) { + return; + } + + if ((transMap_ != nullptr) && (*transMap_ == transMap)) { + return; + } + + Rect prevRect = GetRect(); + Rect mapRect = transMap.GetBoxRect(); + + Rect joinRect; + joinRect.Join(prevRect, mapRect); + + InvalidateRect(joinRect); + + if (transMap_ == nullptr) { + transMap_ = new TransformMap(); + } + + *transMap_ = transMap; +} + +void UIView::SetWidthPercent(float widthPercent) +{ + if (IsInvalid(widthPercent)) { + return; + } + if ((GetParent() != nullptr) && (GetParent()->GetWidth() > 1)) { + int16_t newWidth = static_cast(GetParent()->GetWidth() * widthPercent); + SetWidth(newWidth); + } +} + +void UIView::SetHeightPercent(float heightPercent) +{ + if (IsInvalid(heightPercent)) { + return; + } + if ((GetParent() != nullptr) && (GetParent()->GetHeight() > 1)) { + int16_t newHeight = static_cast(GetParent()->GetHeight() * heightPercent); + SetHeight(newHeight); + } +} + +void UIView::ResizePercent(float widthPercent, float heightPercent) +{ + if (IsInvalid(widthPercent) || IsInvalid(heightPercent)) { + return; + } + if ((GetParent() != nullptr) && (GetParent()->GetWidth() > 1) && (GetParent()->GetHeight() > 1)) { + int16_t newWidth = static_cast(GetParent()->GetWidth() * widthPercent); + int16_t newHeight = static_cast(GetParent()->GetHeight() * heightPercent); + Resize(newWidth, newHeight); + } +} + +void UIView::SetXPercent(float xPercent) +{ + if (IsInvalid(xPercent)) { + return; + } + if ((GetParent() != nullptr) && (GetParent()->GetWidth() > 1)) { + int16_t newX = static_cast(GetParent()->GetWidth() * xPercent); + SetX(newX); + } +} + +void UIView::SetYPercent(float yPercent) +{ + if (IsInvalid(yPercent)) { + return; + } + if ((GetParent() != nullptr) && (GetParent()->GetHeight() > 1)) { + int16_t newY = static_cast(GetParent()->GetHeight() * yPercent); + SetY(newY); + } +} + +void UIView::SetPositionPercent(float xPercent, float yPercent) +{ + if (IsInvalid(xPercent) || IsInvalid(yPercent)) { + return; + } + if ((GetParent() != nullptr) && (GetParent()->GetWidth() > 1) && (GetParent()->GetHeight() > 1)) { + int16_t newX = static_cast(GetParent()->GetWidth() * xPercent); + int16_t newY = static_cast(GetParent()->GetHeight() * yPercent); + SetPosition(newX, newY); + } +} + +void UIView::SetPositionPercent(float xPercent, float yPercent, float widthPercent, float heightPercent) +{ + if (IsInvalid(xPercent) || IsInvalid(yPercent) || IsInvalid(widthPercent) || IsInvalid(heightPercent)) { + return; + } + if ((GetParent() != nullptr) && (GetParent()->GetWidth() > 1) && (GetParent()->GetHeight() > 1)) { + int16_t newX = static_cast(GetParent()->GetWidth() * xPercent); + int16_t newY = static_cast(GetParent()->GetHeight() * yPercent); + int16_t newWidth = static_cast(GetParent()->GetWidth() * widthPercent); + int16_t newHeight = static_cast(GetParent()->GetHeight() * heightPercent); + SetPosition(newX, newY, newWidth, newHeight); + } +} + +bool UIView::IsInvalid(float percent) +{ + if ((percent < 1) && (percent > 0)) { + return false; + } + return true; +} + +void UIView::LayoutCenterOfParent(int16_t xOffset, int16_t yOffset) +{ + if (parent_ == nullptr) { + return; + } + + int16_t topMargin = style_->marginTop_; + int16_t leftMargin = style_->marginLeft_; + int16_t rightMargin = style_->marginRight_; + int16_t bottomMargin = style_->marginBottom_; + // 2: half + int16_t posX = parent_->GetWidth() / 2 - (rect_.GetWidth() - leftMargin + rightMargin) / 2 + xOffset; + int16_t posY = parent_->GetHeight() / 2 - (rect_.GetHeight() - topMargin + bottomMargin) / 2 + yOffset; + SetPosition(posX, posY); +} + +void UIView::LayoutLeftOfParent(int16_t offset) +{ + if (parent_ == nullptr) { + return; + } + + int16_t leftMargin = style_->marginLeft_; + SetPosition(leftMargin + offset, GetY()); +} + +void UIView::LayoutRightOfParent(int16_t offset) +{ + if (parent_ == nullptr) { + return; + } + + int16_t rightMargin = style_->marginRight_; + SetPosition(parent_->GetWidth() - offset - rect_.GetWidth() - rightMargin, GetY()); +} + +void UIView::LayoutTopOfParent(int16_t offset) +{ + if (parent_ == nullptr) { + return; + } + + int16_t topMargin = style_->marginTop_; + SetPosition(GetX(), topMargin + offset); +} + +void UIView::LayoutBottomOfParent(int16_t offset) +{ + if (parent_ == nullptr) { + return; + } + + int16_t bottomMargin = style_->marginBottom_; + SetPosition(GetX(), parent_->GetHeight() - offset - rect_.GetHeight() - bottomMargin); +} + +void UIView::AlignLeftToSibling(const char* id, int16_t offset) +{ + if (parent_ == nullptr) { + return; + } + UIView* sib = parent_->GetChildById(id); + if (sib != nullptr) { + int16_t margin = sib->style_->marginLeft_ - style_->marginLeft_; + SetPosition(sib->GetX() - margin + offset, GetY()); + } +} + +void UIView::AlignRightToSibling(const char* id, int16_t offset) +{ + if (parent_ == nullptr) { + return; + } + UIView* sib = parent_->GetChildById(id); + if (sib != nullptr) { + int16_t margin = sib->style_->marginRight_ - style_->marginRight_; + SetPosition(sib->GetX() + sib->rect_.GetWidth() - rect_.GetWidth() - offset + margin, GetY()); + } +} + +void UIView::AlignTopToSibling(const char* id, int16_t offset) +{ + if (parent_ == nullptr) { + return; + } + UIView* sib = parent_->GetChildById(id); + if (sib != nullptr) { + int16_t margin = sib->style_->marginTop_ - style_->marginTop_; + SetPosition(GetX(), sib->GetY() + offset - margin); + } +} + +void UIView::AlignBottomToSibling(const char* id, int16_t offset) +{ + if (parent_ == nullptr) { + return; + } + UIView* sib = parent_->GetChildById(id); + if (sib != nullptr) { + int16_t margin = sib->style_->marginBottom_ - style_->marginBottom_; + SetPosition(GetX(), sib->GetY() + sib->rect_.GetHeight() - rect_.GetHeight() - offset + margin); + } +} + +void UIView::AlignHorCenterToSibling(const char* id, int16_t offset) +{ + if (parent_ == nullptr) { + return; + } + UIView* sib = parent_->GetChildById(id); + if (sib != nullptr) { + int16_t margin = + (sib->style_->marginRight_ - sib->style_->marginLeft_ - style_->marginRight_ + style_->marginLeft_) / + 2; // 2 : half + SetPosition(sib->GetX() + sib->rect_.GetWidth() / 2 - rect_.GetWidth() / 2 + margin + offset, GetY()); + } +} +void UIView::AlignVerCenterToSibling(const char* id, int16_t offset) +{ + if (parent_ == nullptr) { + return; + } + UIView* sib = parent_->GetChildById(id); + if (sib != nullptr) { + int16_t margin = + (sib->style_->marginBottom_ - sib->style_->marginTop_ - style_->marginBottom_ + style_->marginTop_) / + 2; // 2 : half + SetPosition(GetX(), sib->GetY() + sib->rect_.GetHeight() / 2 - rect_.GetHeight() / 2 + margin + offset); + } +} + +void UIView::LayoutLeftToSibling(const char* id, int16_t offset) +{ + if (parent_ == nullptr) { + return; + } + UIView* sib = parent_->GetChildById(id); + if (sib != nullptr) { + int16_t margin = sib->style_->marginLeft_ + style_->marginRight_; + SetPosition(sib->GetX() - offset - rect_.GetWidth() - margin, GetY()); + } +} + +void UIView::LayoutRightToSibling(const char* id, int16_t offset) +{ + if (parent_ == nullptr) { + return; + } + UIView* sib = parent_->GetChildById(id); + if (sib != nullptr) { + int16_t margin = sib->style_->marginRight_ + style_->marginLeft_; + SetPosition(sib->GetX() + sib->rect_.GetWidth() + offset + margin, GetY()); + } +} + +void UIView::LayoutTopToSibling(const char* id, int16_t offset) +{ + if (parent_ == nullptr) { + return; + } + UIView* sib = parent_->GetChildById(id); + if (sib != nullptr) { + int16_t margin = sib->style_->marginTop_ + style_->marginBottom_; + SetPosition(GetX(), sib->GetY() - offset - rect_.GetHeight() - margin); + } +} + +void UIView::LayoutBottomToSibling(const char* id, int16_t offset) +{ + if (parent_ == nullptr) { + return; + } + UIView* sib = parent_->GetChildById(id); + if (sib != nullptr) { + int16_t margin = sib->style_->marginBottom_ + style_->marginTop_; + SetPosition(GetX(), sib->GetY() + sib->rect_.GetHeight() + offset + margin); + } +} + +uint8_t UIView::GetMixOpaScale() +{ + uint8_t opaMix = opaScale_; + UIView* parent = parent_; + uint8_t opaParent; + while (parent != nullptr) { + opaParent = parent->GetOpaScale(); + // 8: Shift right 8 bits + opaMix = (opaParent == OPA_OPAQUE) ? opaMix : ((static_cast(opaParent) * opaMix) >> 8); + parent = parent->GetParent(); + } + return opaMix; +} +} // namespace OHOS diff --git a/frameworks/components/ui_view_group.cpp b/frameworks/components/ui_view_group.cpp new file mode 100755 index 0000000..705bf13 --- /dev/null +++ b/frameworks/components/ui_view_group.cpp @@ -0,0 +1,299 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "components/ui_view_group.h" + +#include +#include "components/root_view.h" + +namespace OHOS { +UIViewGroup::UIViewGroup() + : childrenHead_(nullptr), childrenTail_(nullptr), childrenNum_(0), + isDragging_(false), disallowIntercept_(false), isAutoSize_(false) +{ + isViewGroup_ = true; +} + +UIViewGroup::~UIViewGroup() {} + +void UIViewGroup::Add(UIView* view) +{ + if ((view == this) || (view == nullptr)) { + return; + } + if (childrenHead_ == nullptr) { + childrenHead_ = view; + } else { + UIView* head = childrenHead_; + while (head != nullptr) { + if ((view == head) || + ((view->GetViewId() != nullptr) && + (head->GetViewId() != nullptr) && + !strcmp(view->GetViewId(), head->GetViewId()))) { + return; + } + head = head->GetNextSibling(); + } + + if (childrenTail_ == nullptr) { + return; + } + childrenTail_->SetNextSibling(view); + } + view->SetParent(this); + view->SetNextSibling(nullptr); + childrenTail_ = view; + childrenNum_++; + if (isAutoSize_) { + AutoResize(); + } + OnChildChanged(); +} + +void UIViewGroup::Insert(UIView* prevView, UIView* insertView) +{ + if (insertView == nullptr) { + return; + } + if (childrenHead_ == nullptr) { + Add(insertView); + return; + } + UIView* head = childrenHead_; + while (head != nullptr) { + if ((insertView == head) || + ((insertView->GetViewId() != nullptr) && + (head->GetViewId() != nullptr) && + !strcmp(insertView->GetViewId(), head->GetViewId()))) { + return; + } + head = head->GetNextSibling(); + } + if (prevView == nullptr) { + insertView->SetNextSibling(childrenHead_); + insertView->SetParent(this); + childrenHead_ = insertView; + } else { + UIView* nextView = prevView->GetNextSibling(); + prevView->SetNextSibling(insertView); + insertView->SetNextSibling(nextView); + insertView->SetParent(this); + } + if (childrenTail_ == prevView) { + childrenTail_ = insertView; + } + childrenNum_++; + if (isAutoSize_) { + AutoResize(); + } + OnChildChanged(); +} + +void UIViewGroup::Remove(UIView* view) +{ + if ((childrenHead_ == nullptr) || (view == nullptr)) { + return; + } + +#if LOCAL_RENDER + RootView::GetInstance()->RemoveViewFromInvalidMap(view); + InvalidateRect(view->GetRect()); +#endif + if (childrenHead_ == view) { + childrenHead_ = childrenHead_->GetNextSibling(); + view->SetParent(nullptr); + view->SetNextSibling(nullptr); + if (childrenTail_ == view) { + childrenTail_ = nullptr; + } + childrenNum_--; + OnChildChanged(); + return; + } + UIView* node = childrenHead_; + while (node->GetNextSibling() != nullptr) { + if (node->GetNextSibling() == view) { + node->SetNextSibling(view->GetNextSibling()); + view->SetParent(nullptr); + view->SetNextSibling(nullptr); + if (childrenTail_ == view) { + childrenTail_ = node; + } + childrenNum_--; + OnChildChanged(); + return; + } + node = node->GetNextSibling(); + } +} + +void UIViewGroup::RemoveAll() +{ + UIView* node = childrenHead_; + childrenHead_ = nullptr; + childrenTail_ = nullptr; + childrenNum_ = 0; + UIView* tmp = nullptr; + while (node != nullptr) { + tmp = node; + node = node->GetNextSibling(); + tmp->SetParent(nullptr); + tmp->SetNextSibling(nullptr); + } + OnChildChanged(); +} + +void UIViewGroup::GetTargetView(const Point& point, UIView** last) +{ + if (last == nullptr) { + return; + } + + Rect rect = GetRect(); + if (disallowIntercept_) { + *last = nullptr; + return; + } + if (!rect.IsContains(point)) { + return; + } + if (!visible_) { + return; + } + if (touchable_) { + *last = this; + } + if (isDragging_) { + return; + } + UIView* view = childrenHead_; + while (view != nullptr) { + if (!view->IsViewGroup()) { + rect = view->GetRect(); + if (rect.IsContains(point)) { + view->GetTargetView(point, last); + } + } else { + UIViewGroup* viewGroup = static_cast(view); + viewGroup->GetTargetView(point, last); + } + view = view->GetNextSibling(); + } +} + +void UIViewGroup::GetTargetView(const Point& point, UIView** current, UIView** target) +{ + if ((current == nullptr) || (target == nullptr)) { + return; + } + + Rect rect = GetRect(); + if (disallowIntercept_) { + *current = nullptr; + *target = nullptr; + return; + } + if (!rect.IsContains(point)) { + return; + } + if (!visible_) { + return; + } + *target = this; + if (touchable_) { + *current = this; + } + if (isDragging_) { + return; + } + UIView* view = childrenHead_; + while (view != nullptr) { + if (!view->IsViewGroup()) { + rect = view->GetRect(); + if (rect.IsContains(point)) { + view->GetTargetView(point, current, target); + } + } else { + UIViewGroup* viewGroup = static_cast(view); + viewGroup->GetTargetView(point, current, target); + } + view = view->GetNextSibling(); + } +} + +Rect UIViewGroup::GetAllChildRelativeRect() const +{ + Rect rect; + UIView* view = childrenHead_; + if (view != nullptr) { + rect = view->GetRelativeRect(); + view = view->GetNextSibling(); + } + while (view != nullptr) { + Rect rectChild = view->GetRelativeRect(); + rect.Join(rect, rectChild); + view = view->GetNextSibling(); + } + return rect; +} + +UIView* UIViewGroup::GetChildById(const char* id) const +{ + if (id == nullptr || childrenHead_ == nullptr) { + return nullptr; + } + UIView* child = childrenHead_; + while (child != nullptr) { + if ((child->GetViewId() != nullptr) && !strcmp(child->GetViewId(), id)) { + return child; + } else if (child->IsViewGroup() && static_cast(child)->GetChildrenHead() != nullptr) { + child = static_cast(child)->GetChildrenHead(); + continue; + } else if (child->GetNextSibling() != nullptr) { + child = child->GetNextSibling(); + continue; + } + while (child->GetParent() != this && child->GetParent()->GetNextSibling() == nullptr) { + child = child->GetParent(); + } + if (child->GetParent() != this) { + child = child->GetParent()->GetNextSibling(); + continue; + } + break; + } + return nullptr; +} + +void UIViewGroup::MoveChildByOffset(int16_t xOffset, int16_t yOffset) +{ + UIView* view = childrenHead_; + int16_t x; + int16_t y; + while (view != nullptr) { + x = view->GetX() + xOffset; + y = view->GetY() + yOffset; + view->SetPosition(x, y); + view = view->GetNextSibling(); + } +} + +void UIViewGroup::AutoResize() +{ + Rect rect = GetAllChildRelativeRect(); + SetWidth(rect.GetWidth() + rect.GetLeft()); + SetHeight(rect.GetHeight() + rect.GetTop()); +} +} // namespace OHOS diff --git a/frameworks/core/render_manager.cpp b/frameworks/core/render_manager.cpp new file mode 100755 index 0000000..9714517 --- /dev/null +++ b/frameworks/core/render_manager.cpp @@ -0,0 +1,230 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "core/render_manager.h" + +#include "components/root_view.h" +#include "dock/screen_device_proxy.h" +#include "graphic_log.h" +#include "hal_tick.h" +#include "securec.h" +#if ENABLE_WINDOW +#include "window/window_impl.h" +#endif + +namespace OHOS { +RenderManager::RenderManager() : fps_(0.f), needResetFPS_(true), onFPSChangedListener_(nullptr) {} + +RenderManager::~RenderManager() {} + +void RenderManager::Init() +{ + Task::Init(); +} + +void RenderManager::RenderTask() +{ +#if ENABLE_WINDOW + ListNode* winNode = winList_.Begin(); + while (winNode != winList_.End()) { + WindowImpl* windowImpl = reinterpret_cast(winNode->data_); + windowImpl->Render(); + winNode = winNode->next_; + } +#else + RootView::GetInstance()->Measure(); + RootView::GetInstance()->Render(); +#endif + +#if ENABLE_FPS_SUPPORT + UpdateFPS(); +#endif +} + +#if ENABLE_FPS_SUPPORT +void RenderManager::UpdateFPS() +{ + SysInfo::FPSCalculateType type = SysInfo::FPS_CT_FIXED_TIME; + if (onFPSChangedListener_) { + type = onFPSChangedListener_->GetFPSCalculateType(); + } + if (type == SysInfo::FPS_CT_FIXED_TIME) { + UpdateFPSByFixedTimeMethod(); + } else if (type == SysInfo::FPS_CT_AVERAGE_SAMPLING) { + UpdateFPSByAverageSamplingMethod(); + } else if (type == SysInfo::FPS_CT_PRECISE_SAMPLING) { + UpdateFPSByPreciseSamplingMethod(); + } + OnFPSChanged(fps_); +} + +void RenderManager::UpdateFPSByFixedTimeMethod() +{ + static uint16_t frameCount = 0; + static uint32_t lastTime = HALTick::GetInstance().GetTime(); + if (needResetFPS_) { + frameCount = 0; + lastTime = HALTick::GetInstance().GetTime(); + needResetFPS_ = false; + fps_ = 0.f; + return; + } + + frameCount++; + + uint32_t curTime = HALTick::GetInstance().GetTime(); + if (curTime - lastTime > MILLISECONDS_PER_SECOND) { + fps_ = 1.f * frameCount / (curTime - lastTime) * MILLISECONDS_PER_SECOND; + fps_ = (fps_ > MAX_FPS) ? MAX_FPS : fps_; + frameCount = 0; + lastTime = curTime; + } +} + +void RenderManager::UpdateFPSByAverageSamplingMethod() +{ + static float avgDuration = 0.f; + static float alpha = 1.f / SAMPLE_NUMBER; + static bool firstFrame = true; + static uint32_t lastTime = HALTick::GetInstance().GetTime(); + if (needResetFPS_) { + avgDuration = 0.f; + alpha = 1.f / SAMPLE_NUMBER; + firstFrame = true; + lastTime = HALTick::GetInstance().GetTime(); + needResetFPS_ = false; + fps_ = 0.f; + return; + } + uint32_t curTime = HALTick::GetInstance().GetTime(); + int deltaTime = curTime - lastTime; + lastTime = curTime; + + if (firstFrame) { + avgDuration = static_cast(deltaTime); + firstFrame = false; + } else { + avgDuration = avgDuration * (1 - alpha) + deltaTime * alpha; + } + fps_ = 1.f / avgDuration * MILLISECONDS_PER_SECOND; +} + +void RenderManager::UpdateFPSByPreciseSamplingMethod() +{ + static int deltaTimeQueue[SAMPLE_NUMBER] = {0}; + static int frameCount = 0; + static int sumDuration = 0; + static bool isQueueFull = false; + static uint32_t lastTime = HALTick::GetInstance().GetTime(); + if (needResetFPS_) { + if (memset_s(deltaTimeQueue, sizeof(deltaTimeQueue), 0, sizeof(deltaTimeQueue)) != EOK) { + return; + } + frameCount = 0; + sumDuration = 0; + isQueueFull = false; + lastTime = HALTick::GetInstance().GetTime(); + needResetFPS_ = false; + fps_ = 0.f; + return; + } + uint32_t curTime = HALTick::GetInstance().GetTime(); + int deltaTime = curTime - lastTime; + lastTime = curTime; + + if (!isQueueFull && (frameCount == SAMPLE_NUMBER)) { + isQueueFull = true; + } + frameCount %= SAMPLE_NUMBER; + sumDuration -= deltaTimeQueue[frameCount]; + sumDuration += deltaTime; + deltaTimeQueue[frameCount++] = deltaTime; + if (isQueueFull) { + fps_ = 1.f * SAMPLE_NUMBER / sumDuration * MILLISECONDS_PER_SECOND; + } else { + fps_ = 1.f * frameCount / sumDuration * MILLISECONDS_PER_SECOND; + } +} +#endif + +void RenderManager::RenderRect(const Rect& rect, RootView* rootView) +{ + if (rootView == nullptr) { + return; + } + + Rect mask = rect; +#if ENABLE_WINDOW + if (rootView->GetBoundWindow()) { + Rect winRect = rootView->GetBoundWindow()->GetRect(); + winRect.SetPosition(0, 0); + mask.Intersect(rect, winRect); + } +#endif + int32_t bufferHeight = ScreenDeviceProxy::GetInstance()->GetScreenArea() / mask.GetWidth(); + if (bufferHeight > mask.GetHeight()) { + bufferHeight = mask.GetHeight(); + } + + Rect& bufferRect = ScreenDeviceProxy::GetInstance()->GetBufferRect(); + bufferRect.SetLeft(mask.GetLeft()); + bufferRect.SetRight(mask.GetRight()); + + int16_t bottom = mask.GetBottom(); + for (int16_t bufferTop = mask.GetTop(); bufferTop <= bottom; bufferTop += bufferHeight) { + bufferRect.SetTop(bufferTop); + int16_t bufferBottom = bufferTop + bufferHeight - 1; + if (bufferBottom >= bottom) { + bufferRect.SetBottom(bottom); + } else { + bufferRect.SetBottom(bufferBottom); + } +#if LOCAL_RENDER + rootView->DrawInvalidMap(bufferRect); +#else + UIView* topView = rootView->GetTopUIView(bufferRect); + rootView->DrawTop(topView, bufferRect); +#endif +#if !ENABLE_WINDOW + ScreenDeviceProxy::GetInstance()->Flush(); +#endif + } +} + +#if ENABLE_WINDOW +void RenderManager::AddToDisplay(Window* window) +{ + if (window == nullptr) { + return; + } + winList_.PushBack(window); +} + +void RenderManager::RemoveFromDisplay(Window* window) +{ + if (window == nullptr) { + return; + } + ListNode* winNode = winList_.Begin(); + while (winNode != winList_.End()) { + if (winNode->data_ == window) { + winList_.Remove(winNode); + return; + } + winNode = winNode->next_; + } +} +#endif +} // namespace OHOS diff --git a/frameworks/core/render_manager.h b/frameworks/core/render_manager.h new file mode 100755 index 0000000..23ac038 --- /dev/null +++ b/frameworks/core/render_manager.h @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GRAPHIC_LITE_RENDER_MANAGER_H +#define GRAPHIC_LITE_RENDER_MANAGER_H + +#include "components/ui_view.h" +#include "components/root_view.h" +#include "common/task_manager.h" +#include "geometry2d.h" +#include "list.h" +#include "sys_info.h" +#if ENABLE_WINDOW +#include "window/window.h" +#endif + +namespace OHOS { +static constexpr float MAX_FPS = 90.f; + +static constexpr int16_t SAMPLE_NUMBER = 100; + +static constexpr uint16_t MILLISECONDS_PER_SECOND = 1000; + +class RenderManager : public Task { +public: + static RenderManager& GetInstance() + { + static RenderManager instance; + return instance; + } + + virtual void Init() override; + +#if ENABLE_WINDOW + Window* GetWindowById(int32_t id) + { + auto node = winList_.Begin(); + while (node != winList_.End()) { + if (node->data_->GetWindowId() == id) { + return node->data_; + } + node = node->next_; + } + return nullptr; + } +#endif + + virtual void Callback() override + { + RenderTask(); + } + + float GetFPS() const + { + return fps_; + } + + void RegisterFPSChangedListener(SysInfo::OnFPSChangedListener* onFPSChangedListener) + { + onFPSChangedListener_ = onFPSChangedListener; + needResetFPS_ = true; + } + +#if ENABLE_WINDOW + void AddToDisplay(Window* window); + + void RemoveFromDisplay(Window* window); +#endif + +private: + friend class RootView; + + RenderManager(); + + ~RenderManager(); + + void RenderTask(); + + static void RenderRect(const Rect& rect, RootView* rootView); + +#if ENABLE_FPS_SUPPORT + void UpdateFPS(); + + void UpdateFPSByFixedTimeMethod(); + + void UpdateFPSByAverageSamplingMethod(); + + void UpdateFPSByPreciseSamplingMethod(); + + void OnFPSChanged(float newFPS) + { + if (onFPSChangedListener_) { + onFPSChangedListener_->OnFPSChanged(newFPS); + } + } +#endif + + float fps_; + + bool needResetFPS_; + + SysInfo::OnFPSChangedListener* onFPSChangedListener_; + +#if ENABLE_WINDOW + List winList_; +#endif +}; +} // namespace OHOS +#endif // GRAPHIC_LITE_RENDER_MANAGER_H diff --git a/frameworks/core/task_manager.cpp b/frameworks/core/task_manager.cpp new file mode 100755 index 0000000..2035f40 --- /dev/null +++ b/frameworks/core/task_manager.cpp @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "common/task_manager.h" +#include +#include "hal_tick.h" + +namespace OHOS { +void TaskManager::Add(Task* task) +{ + if (task == nullptr) { + return; + } + + list_.PushBack(task); +} + +void TaskManager::Remove(Task* task) +{ + if (task == nullptr) { + return; + } + ListNode* pos = list_.Begin(); + while (pos != list_.End()) { + if (pos->data_ == task) { + list_.Remove(pos); + return; + } + pos = pos->next_; + } +} + +void TaskManager::TaskHandler() +{ + if (!canTaskRun_) { + return; + } + + if (isHandlerRunning_) { + return; + } + isHandlerRunning_ = true; + + ListNode* node = list_.Begin(); + + while (node != list_.End()) { + Task* currentTask = node->data_; + currentTask->TaskExecute(); + + node = node->next_; + } + + isHandlerRunning_ = false; +} +} diff --git a/frameworks/default_resource/check_box_res.cpp b/frameworks/default_resource/check_box_res.cpp new file mode 100755 index 0000000..ae5be9a --- /dev/null +++ b/frameworks/default_resource/check_box_res.cpp @@ -0,0 +1,1379 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "default_resource/check_box_res.h" +namespace OHOS { +const uint8_t CHECK_BOX_ON_MAP[] = { + /* Pixel format: ARGB8888 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x74, 0x23, + 0x2c, 0xff, 0x71, 0x20, 0x90, 0xff, 0x72, 0x1f, 0xd5, 0xff, 0x71, 0x1f, 0xf3, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xf3, 0xff, 0x72, 0x1f, 0xd5, + 0xff, 0x71, 0x20, 0x90, 0xff, 0x74, 0x23, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xff, 0x80, 0x2b, 0x06, 0xff, 0x73, 0x21, 0x95, 0xff, 0x72, 0x1f, 0xfd, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x72, 0x1f, 0xfd, 0xff, 0x72, 0x1f, 0x94, 0xff, 0x80, 0x2b, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x80, 0x2b, 0x06, + 0xff, 0x71, 0x1f, 0xbd, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xbd, 0xff, 0x80, 0x2b, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x73, 0x21, 0x95, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x72, 0x1f, 0x94, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xff, 0x74, 0x23, 0x2c, 0xff, 0x72, 0x1f, 0xfd, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x72, 0x1f, 0xfd, 0xff, 0x74, 0x23, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, + 0x71, 0x20, 0x90, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x20, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x72, 0x1f, 0xd5, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x72, 0x1f, 0xd5, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x71, 0x1f, 0xf3, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xf3, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x83, 0x3b, 0xff, 0xff, 0x78, 0x2a, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x79, 0x2c, 0xff, 0xff, 0xdb, 0xc7, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xf7, 0xf3, 0xff, 0xff, 0x99, 0x5f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x76, 0x26, 0xff, 0xff, 0xdd, 0xc9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xe6, 0xd8, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x73, 0x23, 0xff, 0xff, + 0xd5, 0xbd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xe7, 0xd9, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x72, 0x20, 0xff, 0xff, 0xcd, 0xb1, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xff, 0xff, 0x9e, 0x66, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0xc5, 0xa3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xac, 0x7c, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0xbc, 0x95, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xb6, 0x8b, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0xb2, 0x85, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xbf, 0x9a, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x9e, 0x66, 0xff, 0xff, 0xd9, + 0xc4, 0xff, 0xff, 0xcb, 0xae, 0xff, 0xff, 0x82, 0x3a, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0xa9, 0x77, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xc8, 0xa8, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x87, 0x42, 0xff, 0xff, 0xfd, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xf1, 0xe9, 0xff, 0xff, 0x8a, 0x46, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0xa0, 0x6a, 0xff, 0xff, 0xfd, 0xfd, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xd1, 0xb6, 0xff, 0xff, 0x72, + 0x21, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0xa1, 0x6b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xf5, 0xf0, 0xff, 0xff, 0x90, 0x50, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x99, 0x5e, 0xff, 0xff, 0xfc, 0xfa, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xd8, 0xc2, 0xff, 0xff, 0x74, 0x24, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x7e, 0x34, + 0xff, 0xff, 0xf5, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xf8, 0xf5, 0xff, 0xff, 0x96, 0x5a, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x91, 0x52, 0xff, 0xff, 0xf9, 0xf6, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xdf, 0xcd, + 0xff, 0xff, 0x77, 0x28, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x90, 0x51, + 0xff, 0xff, 0xf5, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xfb, 0xf9, 0xff, 0xff, 0x9e, 0x67, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x8b, 0x48, 0xff, 0xff, 0xf6, 0xf1, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe5, 0xd7, 0xff, 0xff, 0x7a, 0x2d, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x8a, 0x46, + 0xff, 0xff, 0xf1, 0xe9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xfd, 0xfc, 0xff, 0xff, 0xa7, 0x74, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x85, 0x3f, 0xff, 0xff, + 0xf2, 0xeb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xeb, 0xe0, 0xff, 0xff, 0x7e, 0x34, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x84, 0x3d, + 0xff, 0xff, 0xed, 0xe2, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xff, 0xff, 0xb9, 0x91, 0xff, 0xff, 0xed, 0xe3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0xe8, 0xff, 0xff, 0x83, 0x3b, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x80, 0x36, + 0xff, 0xff, 0xe7, 0xda, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xf4, 0xee, 0xff, 0xff, 0x88, 0x44, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x7b, 0x2f, + 0xff, 0xff, 0xe1, 0xd0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xf4, 0xff, 0xff, + 0x8f, 0x4e, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x78, 0x2a, + 0xff, 0xff, 0xda, 0xc5, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfb, 0xf8, 0xff, 0xff, 0x95, 0x58, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x74, 0x25, + 0xff, 0xff, 0xd3, 0xb9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, + 0xfc, 0xff, 0xff, 0x9c, 0x64, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x73, 0x22, + 0xff, 0xff, 0xc3, 0xa1, 0xff, 0xff, 0xfd, 0xfd, 0xff, 0xff, 0xf6, 0xf0, 0xff, 0xff, 0xa1, 0x6b, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x76, 0x26, 0xff, 0xff, 0x72, 0x21, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x71, 0x1f, 0xf3, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xf3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xff, 0x72, 0x1f, 0xd5, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x72, 0x1f, 0xd5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0x71, 0x20, 0x90, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x20, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x74, 0x23, 0x2c, + 0xff, 0x72, 0x1f, 0xfd, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x72, 0x1f, 0xfd, 0xff, 0x74, 0x23, + 0x2c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x73, 0x21, 0x95, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x72, 0x1f, 0x94, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x80, 0x2b, 0x06, 0xff, 0x71, 0x1f, 0xbd, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xbd, 0xff, 0x80, 0x2b, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x80, 0x2b, 0x06, 0xff, 0x73, 0x21, 0x95, + 0xff, 0x72, 0x1f, 0xfd, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x72, 0x1f, 0xfd, 0xff, 0x72, 0x1f, 0x94, 0xff, 0x80, 0x2b, 0x06, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x74, 0x23, 0x2c, + 0xff, 0x71, 0x20, 0x90, 0xff, 0x72, 0x1f, 0xd5, 0xff, 0x71, 0x1f, 0xf3, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, + 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, + 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, + 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, + 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xff, 0xff, 0x71, 0x1f, 0xf3, 0xff, 0x72, 0x1f, 0xd5, 0xff, + 0x71, 0x20, 0x90, 0xff, 0x74, 0x23, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, +}; + +const uint8_t CHECK_BOX_OFF_MAP[] = { + /* Pixel format: ARGB8888 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, + 0x02, 0xff, 0xff, 0xff, 0x38, 0xff, 0xff, 0xff, 0x6f, 0xff, 0xff, 0xff, 0x93, 0xff, 0xff, 0xff, 0xa3, 0xff, 0xff, + 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, + 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, + 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, + 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, + 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, + 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa3, 0xff, 0xff, 0xff, 0x93, 0xff, 0xff, 0xff, 0x6f, + 0xff, 0xff, 0xff, 0x38, 0xff, 0xff, 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x24, 0xff, 0xff, 0xff, 0x8b, 0xff, 0xff, 0xff, + 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, + 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, + 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, + 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, + 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, + 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, + 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, + 0xff, 0xff, 0xff, 0x8b, 0xff, 0xff, 0xff, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, 0x37, 0xff, 0xff, 0xff, 0xa3, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, + 0x8a, 0xff, 0xff, 0xff, 0x50, 0xff, 0xff, 0xff, 0x31, 0xff, 0xff, 0xff, 0x23, 0xff, 0xff, 0xff, 0x22, 0xff, 0xff, + 0xff, 0x22, 0xff, 0xff, 0xff, 0x22, 0xff, 0xff, 0xff, 0x22, 0xff, 0xff, 0xff, 0x22, 0xff, 0xff, 0xff, 0x22, 0xff, + 0xff, 0xff, 0x22, 0xff, 0xff, 0xff, 0x22, 0xff, 0xff, 0xff, 0x22, 0xff, 0xff, 0xff, 0x22, 0xff, 0xff, 0xff, 0x22, + 0xff, 0xff, 0xff, 0x22, 0xff, 0xff, 0xff, 0x22, 0xff, 0xff, 0xff, 0x22, 0xff, 0xff, 0xff, 0x22, 0xff, 0xff, 0xff, + 0x22, 0xff, 0xff, 0xff, 0x22, 0xff, 0xff, 0xff, 0x22, 0xff, 0xff, 0xff, 0x22, 0xff, 0xff, 0xff, 0x22, 0xff, 0xff, + 0xff, 0x22, 0xff, 0xff, 0xff, 0x22, 0xff, 0xff, 0xff, 0x22, 0xff, 0xff, 0xff, 0x22, 0xff, 0xff, 0xff, 0x23, 0xff, + 0xff, 0xff, 0x31, 0xff, 0xff, 0xff, 0x50, 0xff, 0xff, 0xff, 0x8a, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, + 0xff, 0xff, 0xff, 0xa3, 0xff, 0xff, 0xff, 0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x24, 0xff, 0xff, 0xff, 0xa3, + 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa1, 0xff, 0xff, 0xff, 0x3d, 0xff, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x01, 0xff, 0xff, 0xff, 0x3d, 0xff, 0xff, 0xff, 0xa1, 0xff, 0xff, 0xff, 0xa8, + 0xff, 0xff, 0xff, 0xa3, 0xff, 0xff, 0xff, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x02, 0xff, 0xff, 0xff, 0x8b, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa1, + 0xff, 0xff, 0xff, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x21, 0xff, 0xff, 0xff, 0xa1, 0xff, 0xff, 0xff, 0xa8, + 0xff, 0xff, 0xff, 0x8b, 0xff, 0xff, 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, + 0xff, 0xff, 0x38, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0x3c, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x3d, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, + 0xff, 0xff, 0xff, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x6f, 0xff, + 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0x8a, 0xff, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x01, 0xff, 0xff, 0xff, 0x8a, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0x6f, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x93, 0xff, 0xff, 0xff, 0xa8, 0xff, + 0xff, 0xff, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x50, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0x93, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xa3, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0x31, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, + 0xff, 0xff, 0x31, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x23, 0xff, + 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, + 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x22, 0xff, 0xff, 0xff, 0xa8, 0xff, + 0xff, 0xff, 0xa8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, + 0xff, 0xa8, 0xff, 0xff, 0xff, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x22, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, + 0xff, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xff, 0xff, 0xff, 0x22, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0x22, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, + 0xff, 0x22, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x22, 0xff, 0xff, + 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, + 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x22, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, + 0xff, 0xa8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, + 0xa8, 0xff, 0xff, 0xff, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x22, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, + 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xff, 0xff, 0xff, 0x22, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0x22, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, + 0x22, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x22, 0xff, 0xff, 0xff, + 0xa8, 0xff, 0xff, 0xff, 0xa8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xa8, + 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x22, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, + 0xa8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, + 0xff, 0xff, 0xff, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x22, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0x22, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, 0x22, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0x22, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x22, + 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, + 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x22, 0xff, 0xff, 0xff, 0xa8, + 0xff, 0xff, 0xff, 0xa8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xa8, 0xff, + 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x22, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, + 0xff, 0xff, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x22, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0x22, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, + 0xff, 0xff, 0x22, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x22, 0xff, + 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, + 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x22, 0xff, 0xff, 0xff, 0xa8, 0xff, + 0xff, 0xff, 0xa8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, + 0xff, 0xa8, 0xff, 0xff, 0xff, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x22, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, + 0xff, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xff, 0xff, 0xff, 0x22, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0x22, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, + 0xff, 0x22, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x22, 0xff, 0xff, + 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, + 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x23, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, + 0xff, 0xa8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xa3, 0xff, 0xff, 0xff, + 0xa8, 0xff, 0xff, 0xff, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x31, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa3, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x93, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, + 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xff, 0xff, 0xff, 0x50, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0x93, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x6f, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0x8a, 0xff, 0xff, 0xff, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x01, 0xff, 0xff, 0xff, + 0x8a, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0x6f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, 0x38, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0x3d, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x3d, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, + 0xa8, 0xff, 0xff, 0xff, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x02, + 0xff, 0xff, 0xff, 0x8b, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa1, 0xff, 0xff, 0xff, 0x21, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, 0x21, 0xff, 0xff, 0xff, 0xa1, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0x8b, 0xff, 0xff, 0xff, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x24, + 0xff, 0xff, 0xff, 0xa3, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa1, 0xff, 0xff, 0xff, 0x3c, 0xff, 0xff, 0xff, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x01, 0xff, 0xff, 0xff, 0x3d, 0xff, 0xff, 0xff, 0xa1, + 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa3, 0xff, 0xff, 0xff, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x37, + 0xff, 0xff, 0xff, 0xa3, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0x8a, 0xff, 0xff, 0xff, + 0x50, 0xff, 0xff, 0xff, 0x31, 0xff, 0xff, 0xff, 0x23, 0xff, 0xff, 0xff, 0x22, 0xff, 0xff, 0xff, 0x22, 0xff, 0xff, + 0xff, 0x22, 0xff, 0xff, 0xff, 0x22, 0xff, 0xff, 0xff, 0x22, 0xff, 0xff, 0xff, 0x22, 0xff, 0xff, 0xff, 0x22, 0xff, + 0xff, 0xff, 0x22, 0xff, 0xff, 0xff, 0x22, 0xff, 0xff, 0xff, 0x22, 0xff, 0xff, 0xff, 0x22, 0xff, 0xff, 0xff, 0x22, + 0xff, 0xff, 0xff, 0x22, 0xff, 0xff, 0xff, 0x22, 0xff, 0xff, 0xff, 0x22, 0xff, 0xff, 0xff, 0x22, 0xff, 0xff, 0xff, + 0x22, 0xff, 0xff, 0xff, 0x22, 0xff, 0xff, 0xff, 0x22, 0xff, 0xff, 0xff, 0x22, 0xff, 0xff, 0xff, 0x22, 0xff, 0xff, + 0xff, 0x22, 0xff, 0xff, 0xff, 0x22, 0xff, 0xff, 0xff, 0x22, 0xff, 0xff, 0xff, 0x23, 0xff, 0xff, 0xff, 0x31, 0xff, + 0xff, 0xff, 0x50, 0xff, 0xff, 0xff, 0x8a, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa3, + 0xff, 0xff, 0xff, 0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x24, + 0xff, 0xff, 0xff, 0x8b, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, + 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, + 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, + 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, + 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, + 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, + 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, + 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0x8b, 0xff, 0xff, 0xff, 0x24, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x02, + 0xff, 0xff, 0xff, 0x38, 0xff, 0xff, 0xff, 0x6f, 0xff, 0xff, 0xff, 0x93, 0xff, 0xff, 0xff, 0xa3, 0xff, 0xff, 0xff, + 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, + 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, + 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, + 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, + 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, + 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa8, 0xff, 0xff, 0xff, 0xa3, 0xff, 0xff, 0xff, 0x93, 0xff, 0xff, 0xff, 0x6f, 0xff, + 0xff, 0xff, 0x38, 0xff, 0xff, 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, +}; + +ImageInfo g_checkBoxOn = {{0}}; + +ImageInfo* GetCheckBoxOnInfo() +{ + /* 56 : check box width */ + g_checkBoxOn.header.width = 56; + /* 56 : check box heigh */ + g_checkBoxOn.header.height = 56; + /* 5 : check box cf format */ + g_checkBoxOn.header.colorMode = ARGB8888; + /* 12544 : check box size */ + g_checkBoxOn.dataSize = 12544; + g_checkBoxOn.data = CHECK_BOX_ON_MAP; + return &g_checkBoxOn; +} + +ImageInfo g_checkBoxOff = {{0}}; + +ImageInfo* GetCheckBoxOffInfo() +{ + /* 56 : check box width */ + g_checkBoxOff.header.width = 56; + /* 56 : check box heigh */ + g_checkBoxOff.header.height = 56; + /* 5 : check box cf format */ + g_checkBoxOff.header.colorMode = ARGB8888; + /* 12544 : check box size */ + g_checkBoxOff.dataSize = 12544; + g_checkBoxOff.data = CHECK_BOX_OFF_MAP; + return &g_checkBoxOff; +} +} \ No newline at end of file diff --git a/frameworks/default_resource/check_box_res.h b/frameworks/default_resource/check_box_res.h new file mode 100755 index 0000000..233e46d --- /dev/null +++ b/frameworks/default_resource/check_box_res.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GRAPHIC_LITE_CHECK_BOX_RES_H +#define GRAPHIC_LITE_CHECK_BOX_RES_H + +#include "image_info.h" +#include "graphic_types.h" +namespace OHOS { +ImageInfo* GetCheckBoxOnInfo(); +ImageInfo* GetCheckBoxOffInfo(); +} // namespace OHOS +#endif // GRAPHIC_LITE_CHECK_BOX_RES_H diff --git a/frameworks/dfx/event_injector.cpp b/frameworks/dfx/event_injector.cpp new file mode 100755 index 0000000..01d44bc --- /dev/null +++ b/frameworks/dfx/event_injector.cpp @@ -0,0 +1,269 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "dfx/event_injector.h" +#if ENABLE_DEBUG +#include "common/input_device_manager.h" +#include "dfx/key_event_injector.h" +#include "dfx/point_event_injector.h" +#include "graphic_log.h" + +namespace OHOS { +EventInjector::~EventInjector() +{ + if (pointEventInjector_ != nullptr) { + InputDeviceManager::GetInstance()->Remove(pointEventInjector_); + delete pointEventInjector_; + pointEventInjector_ = nullptr; + } + if (keyEventInjector_ != nullptr) { + InputDeviceManager::GetInstance()->Remove(keyEventInjector_); + delete keyEventInjector_; + keyEventInjector_ = nullptr; + } +} + +bool EventInjector::RegisterEventInjector(EventDataType type) +{ + switch (type) { + case EventDataType::POINT_TYPE: + if (pointEventInjector_ == nullptr) { + pointEventInjector_ = new PointEventInjector(); + if (pointEventInjector_ == nullptr) { + GRAPHIC_LOGE("EventInjector::RegisterEventInjector register pointEventInjector failed Err!\n"); + return false; + } + InputDeviceManager::GetInstance()->Add(pointEventInjector_); + } + return true; + case EventDataType::KEY_TYPE: + if (keyEventInjector_ == nullptr) { + keyEventInjector_ = new KeyEventInjector(); + if (keyEventInjector_ == nullptr) { + GRAPHIC_LOGE("EventInjector::RegisterEventInjector register keyEventInjector failed Err!\n"); + return false; + } + InputDeviceManager::GetInstance()->Add(keyEventInjector_); + } + return true; + default: + break; + } + return false; +} + +void EventInjector::UnregisterEventInjector(EventDataType type) +{ + switch (type) { + case EventDataType::POINT_TYPE: + if (pointEventInjector_ != nullptr) { + InputDeviceManager::GetInstance()->Remove(pointEventInjector_); + delete pointEventInjector_; + pointEventInjector_ = nullptr; + } + break; + case EventDataType::KEY_TYPE: + if (keyEventInjector_ != nullptr) { + InputDeviceManager::GetInstance()->Remove(keyEventInjector_); + delete keyEventInjector_; + keyEventInjector_ = nullptr; + } + break; + default: + break; + } +} + +bool EventInjector::IsEventInjectorRegistered(EventDataType type) const +{ + switch (type) { + case EventDataType::POINT_TYPE: + if (pointEventInjector_ != nullptr) { + return true; + } + break; + case EventDataType::KEY_TYPE: + if (keyEventInjector_ != nullptr) { + return true; + } + break; + default: + break; + } + return false; +} + +bool EventInjector::SetInjectEvent(const DeviceData* dataArray, uint16_t arrayLength, EventDataType type) +{ + if (dataArray == nullptr) { + return false; + } + switch (type) { + case EventDataType::POINT_TYPE: + if (pointEventInjector_ == nullptr) { + return false; + } + for (uint16_t i = 0; i < arrayLength; i++) { + if (!pointEventInjector_->SetPointEvent(dataArray[i])) { + return false; + } + } + break; + case EventDataType::KEY_TYPE: + if (keyEventInjector_ == nullptr) { + return false; + } + for (uint16_t i = 0; i < arrayLength; i++) { + if (!keyEventInjector_->SetKey(dataArray[i])) { + return false; + } + } + break; + default: + return false; + } + return true; +} + +bool EventInjector::SetClickEvent(const Point& clickPoint) +{ + uint16_t clickArrayLen = 2; /* 2:click event point */ + if (clickArrayLen > pointEventInjector_->GetLeftSize()) { + GRAPHIC_LOGE("front points need to be read.(left size in pointer queue is not enough)"); + return false; + } + bool setResult = true; + DeviceData* dataArray = new DeviceData[clickArrayLen]; + if (dataArray == nullptr) { + return false; + } + dataArray[0].point = clickPoint; + dataArray[0].state = InputDevice::STATE_PRESS; + dataArray[1].point = clickPoint; + dataArray[1].state = InputDevice::STATE_RELEASE; + if (!SetInjectEvent(dataArray, clickArrayLen, EventDataType::POINT_TYPE)) { + setResult = false; + } + delete [] dataArray; + return setResult; +} + +bool EventInjector::SetLongPressEvent(const Point& longPressPoint) +{ + uint16_t pointCount = INDEV_LONG_PRESS_TIME / INDEV_READ_PERIOD + 1; + if (pointCount > pointEventInjector_->GetLeftSize()) { + GRAPHIC_LOGE("front points need to be read.(left size in pointer queue is not enough)"); + return false; + } + bool setResult = true; + DeviceData* dataArray = new DeviceData[pointCount]; + if (dataArray == nullptr) { + return false; + } + for (uint16_t i = 0; i < pointCount; i++) { + dataArray[i].point = longPressPoint; + dataArray[i].state = InputDevice::STATE_PRESS; + } + dataArray[pointCount - 1].state = InputDevice::STATE_RELEASE; + if (!SetInjectEvent(dataArray, pointCount, EventDataType::POINT_TYPE)) { + setResult = false; + } + delete [] dataArray; + return setResult; +} + +bool EventInjector::SetDragEvent(const Point& startPoint, const Point& endPoint, uint32_t dragTime) +{ + uint16_t pointCount = (dragTime / INDEV_READ_PERIOD) + 1; + /* 3: at least 3 points in drag event */ + if (pointCount < 3) { + GRAPHIC_LOGE("dragTime is too short.(drag event needs at least 3 points)"); + return false; + } + if (pointCount > pointEventInjector_->GetLeftSize()) { + GRAPHIC_LOGE("dragTime is too long or front points need to be read.(left size in pointer queue is not enough)"); + return false; + } + bool setResult = true; + int16_t negativeFlag = 1; /* 1:represent the coordinate (x, y) of endPoint is larger than startPoint. */ + DeviceData* dataArray = new DeviceData[pointCount]; + if (dataArray == nullptr) { + return false; + } + if (startPoint.x == endPoint.x) { + float pointStep = static_cast(MATH_ABS(endPoint.y - startPoint.y)) / (pointCount - 1); + if (endPoint.y < startPoint.y) { + negativeFlag = -1; /* -1:represent the coordinate y of endPoint is smaller than startPoint. */ + } + for (uint16_t i = 0; i < pointCount; i++) { + dataArray[i].point.x = startPoint.x; + dataArray[i].point.y = startPoint.y + (i * negativeFlag * pointStep); + dataArray[i].state = InputDevice::STATE_PRESS; + } + } else { + float slope = static_cast(endPoint.y - startPoint.y) / (endPoint.x - startPoint.x); + int16_t constPara = startPoint.y - (slope * startPoint.x); + float pointStep = static_cast(MATH_ABS(endPoint.x - startPoint.x)) / (pointCount - 1); + if (endPoint.x < startPoint.x) { + negativeFlag = -1; /* -1:represent the coordinate x of endPoint is smaller than startPoint. */ + } + for (uint16_t i = 0; i < pointCount; i++) { + dataArray[i].point.x = startPoint.x + (i * negativeFlag * pointStep); + dataArray[i].point.y = slope * (dataArray[i].point.x) + constPara; + dataArray[i].state = InputDevice::STATE_PRESS; + } + } + dataArray[pointCount - 1].point = endPoint; + dataArray[pointCount - 1].state = InputDevice::STATE_RELEASE; + if (!SetInjectEvent(dataArray, pointCount, EventDataType::POINT_TYPE)) { + setResult = false; + } + delete [] dataArray; + return setResult; +} + +bool EventInjector::SetKeyEvent(uint16_t keyId, uint16_t state) +{ + uint16_t kevArrayLen = 1; + if (kevArrayLen > keyEventInjector_->GetLeftSize()) { + GRAPHIC_LOGE("front key event need to be read.(left size in key event queue is not enough)"); + return false; + } + bool setResult = true; + DeviceData* dataArray = new DeviceData[kevArrayLen]; + if (dataArray == nullptr) { + return false; + } + for (uint16_t i = 0; i < kevArrayLen; i++) { + dataArray[i].keyId = keyId; + dataArray[i].state = state; + } + if (!SetInjectEvent(dataArray, kevArrayLen, EventDataType::KEY_TYPE)) { + setResult = false; + } + delete [] dataArray; + return setResult; +} + +#if ENABLE_WINDOW +void EventInjector::SetWindowId(uint8_t windowId) +{ + if (pointEventInjector_ != nullptr) { + pointEventInjector_->SetWindowId(windowId); + } +} +#endif +} +#endif // ENABLE_DEBUG \ No newline at end of file diff --git a/frameworks/dfx/key_event_injector.cpp b/frameworks/dfx/key_event_injector.cpp new file mode 100755 index 0000000..4986641 --- /dev/null +++ b/frameworks/dfx/key_event_injector.cpp @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "dfx/key_event_injector.h" +#if ENABLE_DEBUG +#include "graphic_log.h" + +namespace { +const uint8_t MAX_LIST_SIZE = 100; +} + +namespace OHOS { +KeyEventInjector::~KeyEventInjector() +{ + ListNode* node = dataList_.Begin(); + while (node != dataList_.End()) { + if (node->data_ != nullptr) { + DeviceData* deleteData = node->data_; + delete deleteData; + } + node = node->next_; + } + dataList_.Clear(); +} + +bool KeyEventInjector::Read(DeviceData& data) +{ + if (dataList_.IsEmpty()) { + lastState_ = INVALID_KEY_STATE; + } + if (dataList_.Size() > 0) { + lastKeyId_ = dataList_.Front()->keyId; + lastState_ = dataList_.Front()->state; + + ListNode* node = dataList_.Begin(); + if (node->data_ != nullptr) { + DeviceData* deleteData = node->data_; + delete deleteData; + } + dataList_.PopFront(); + } + data.keyId = lastKeyId_; + data.state = lastState_; + return false; +} + +bool KeyEventInjector::SetKey(const DeviceData& data) +{ + if (dataList_.Size() >= MAX_LIST_SIZE) { + GRAPHIC_LOGI("PointEventInjector::SetPointEvent data list is full."); + return false; + } + + DeviceData* tmpData = new DeviceData; + if (tmpData == nullptr) { + GRAPHIC_LOGE("PointEventInjector::SetPointEvent memory allocation failed Err!"); + return false; + } + tmpData->keyId = data.keyId; + tmpData->state = data.state; + dataList_.PushBack(tmpData); + return true; +} + +uint8_t KeyEventInjector::GetLeftSize() const +{ + return MAX_LIST_SIZE - dataList_.Size(); +} +} +#endif // ENABLE_DEBUG \ No newline at end of file diff --git a/frameworks/dfx/key_event_injector.h b/frameworks/dfx/key_event_injector.h new file mode 100755 index 0000000..60f1821 --- /dev/null +++ b/frameworks/dfx/key_event_injector.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GRAPHIC_LITE_KEY_EVENT_INJECTOR +#define GRAPHIC_LITE_KEY_EVENT_INJECTOR + +#include "graphic_config.h" +#if ENABLE_DEBUG +#include "dock/key_input_device.h" +#include "events/key_event.h" +#include "list.h" + +namespace OHOS { +class KeyEventInjector : public KeyInputDevice { +public: + KeyEventInjector() : lastKeyId_(0), lastState_(INVALID_KEY_STATE) {} + ~KeyEventInjector(); + bool Read(DeviceData& data) override; + bool SetKey(const DeviceData& data); + uint8_t GetLeftSize() const; +private: + List dataList_; + uint16_t lastKeyId_; + uint16_t lastState_; +}; +} + +#endif // ENABLE_DEBUG +#endif // GRAPHIC_LITE_KEY_EVENT_INJECTOR \ No newline at end of file diff --git a/frameworks/dfx/performance_task.h b/frameworks/dfx/performance_task.h new file mode 100755 index 0000000..cbc62aa --- /dev/null +++ b/frameworks/dfx/performance_task.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GRAPHIC_LITE_PERFORMANCE_TASK_H +#define GRAPHIC_LITE_PERFORMANCE_TASK_H + +#include "graphic_config.h" +#if ENABLE_DEBUG && ENABLE_DEBUG_PERFORMANCE_TRACE +#include "common/task.h" +#include "graphic_performance.h" + +namespace OHOS { +class PerformanceTask : public Task { +public: + static PerformanceTask* GetInstance() + { + static PerformanceTask instance; + return &instance; + } + + void Init() override + { + Performance::GetInstance()->RegisterSIGUSR1(); + Task::Init(); + } + + void Callback() override + { + Performance::GetInstance()->PrintResult(); + } +private: + PerformanceTask() {} + virtual ~PerformanceTask() {} + PerformanceTask(const PerformanceTask&) = delete; + PerformanceTask& operator=(const PerformanceTask&) = delete; + PerformanceTask(PerformanceTask&&) = delete; + PerformanceTask& operator=(PerformanceTask&&) = delete; +}; +} +#define DEBUG_PERFORMANCE_TASK_INIT() OHOS::PerformanceTask::GetInstance()->Init() +#else +#define DEBUG_PERFORMANCE_TASK_INIT() +#endif +#endif \ No newline at end of file diff --git a/frameworks/dfx/point_event_injector.cpp b/frameworks/dfx/point_event_injector.cpp new file mode 100755 index 0000000..b03375e --- /dev/null +++ b/frameworks/dfx/point_event_injector.cpp @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "dfx/point_event_injector.h" +#if ENABLE_DEBUG +#include "graphic_log.h" + +namespace { +const uint16_t MAX_LIST_SIZE = 500; +} + +namespace OHOS { +PointEventInjector::~PointEventInjector() +{ + ListNode* node = dataList_.Begin(); + while (node != dataList_.End()) { + if (node->data_ != nullptr) { + DeviceData* deleteData = node->data_; + delete deleteData; + } + node = node->next_; + } + dataList_.Clear(); +} + +bool PointEventInjector::SetPointEvent(const DeviceData& data) +{ + if (dataList_.Size() >= MAX_LIST_SIZE) { + GRAPHIC_LOGI("PointEventInjector::SetPointEvent data list is full."); + return false; + } + + DeviceData* tmpData = new DeviceData; + if (tmpData == nullptr) { + GRAPHIC_LOGE("PointEventInjector::SetPointEvent memory allocation failed Err!"); + return false; + } + tmpData->point = data.point; + tmpData->state = data.state; + dataList_.PushBack(tmpData); + return true; +} + +bool PointEventInjector::Read(DeviceData& data) +{ + if (dataList_.Size() > 0) { + lastX_ = dataList_.Front()->point.x; + lastY_ = dataList_.Front()->point.y; + lastState_ = dataList_.Front()->state; + + ListNode* node = dataList_.Begin(); + if (node->data_ != nullptr) { + DeviceData* deleteData = node->data_; + delete deleteData; + } + dataList_.PopFront(); + } + data.point = {lastX_, lastY_}; + data.state = lastState_; +#if ENABLE_WINDOW + if (windowId_ >= 0) { + data.winId = windowId_; + } +#endif + return false; +} + +uint16_t PointEventInjector::GetLeftSize() const +{ + return MAX_LIST_SIZE - dataList_.Size(); +} + +#if ENABLE_WINDOW +void PointEventInjector::SetWindowId(int32_t windowId) +{ + windowId_ = windowId; +} +#endif +} +#endif // ENABLE_DEBUG \ No newline at end of file diff --git a/frameworks/dfx/point_event_injector.h b/frameworks/dfx/point_event_injector.h new file mode 100755 index 0000000..6058455 --- /dev/null +++ b/frameworks/dfx/point_event_injector.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GRAPHIC_LITE_POINT_EVENT_INJECTOR_H +#define GRAPHIC_LITE_POINT_EVENT_INJECTOR_H + +#include "graphic_config.h" +#if ENABLE_DEBUG +#include "dock/pointer_input_device.h" +#include "list.h" + +namespace OHOS { +class PointEventInjector : public PointerInputDevice { +public: + PointEventInjector() : lastX_(0), lastY_(0), lastState_(0) {} + ~PointEventInjector(); + bool SetPointEvent(const DeviceData& data); + bool Read(DeviceData& data) override; + uint16_t GetLeftSize() const; +#if ENABLE_WINDOW + void SetWindowId(int32_t windowId); +#endif + +private: + List dataList_; + int16_t lastX_; + int16_t lastY_; + uint16_t lastState_; +#if ENABLE_WINDOW + int32_t windowId_ = -1; +#endif +}; +} +#endif // ENABLE_DEBUG +#endif // GRAPHIC_LITE_POINT_EVENT_INJECTOR_H \ No newline at end of file diff --git a/frameworks/dfx/ui_dump_dom_tree.cpp b/frameworks/dfx/ui_dump_dom_tree.cpp new file mode 100755 index 0000000..74d4fb0 --- /dev/null +++ b/frameworks/dfx/ui_dump_dom_tree.cpp @@ -0,0 +1,386 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "dfx/ui_dump_dom_tree.h" + +#if ENABLE_DEBUG +#include "components/root_view.h" +#include "components/ui_view.h" +#include "components/ui_label.h" +#include "components/ui_label_button.h" +#include "components/ui_checkbox.h" +#include "components/ui_toggle_button.h" +#include "components/ui_image_view.h" +#include "components/ui_abstract_progress.h" +#include "components/ui_scroll_view.h" +#include "components/ui_list.h" +#include "components/ui_abstract_clock.h" +#include "components/ui_picker.h" +#include "components/ui_swipe_view.h" +#include "components/ui_time_picker.h" +#include "draw/draw_image.h" +#include "file.h" +#include "graphic_log.h" +#endif // ENABLE_DEBUG +namespace OHOS { +#if ENABLE_DEBUG +void UIDumpDomTree::AddNameField(UIViewType type, cJSON* usr) const +{ + if (usr == nullptr) { + return; + } + + if (type < UI_NUMBER_MAX) { + cJSON_AddStringToObject(usr, "name", VIEW_TYPE_STRING[type]); + } else { + cJSON_AddStringToObject(usr, "name", "UnknownType"); + } +} + +void UIDumpDomTree::AddImageViewSpecialField(const UIView* view, cJSON* usr) const +{ + if ((view == nullptr) || (usr == nullptr)) { + return; + } + const UIImageView* tmpImageView = static_cast(view); + ImageSrcType srcType = tmpImageView->GetSrcType(); + if (srcType == IMG_SRC_FILE) { + cJSON_AddStringToObject(usr, "src", reinterpret_cast(tmpImageView->GetPath())); + } else if (srcType == IMG_SRC_VARIABLE) { + const ImageInfo* imageInfo = reinterpret_cast(tmpImageView->GetImageInfo()); + if ((imageInfo == nullptr) || (imageInfo->userData == nullptr)) { + cJSON_AddStringToObject(usr, "src", ""); + return; + } + uintptr_t userData = reinterpret_cast(imageInfo->userData); + cJSON_AddNumberToObject(usr, "src", static_cast(userData)); + } else { + cJSON_AddStringToObject(usr, "src", ""); + } +} + +void UIDumpDomTree::AddSpecialField(const UIView* view, cJSON* usr) const +{ + if ((view == nullptr) || (usr == nullptr)) { + return; + } + switch (view->GetViewType()) { + case UI_LABEL: + case UI_ARC_LABEL: { + const UILabel* tmpLabel = static_cast(view); + cJSON_AddStringToObject(usr, "text", tmpLabel->GetText()); + tmpLabel = nullptr; + break; + } + case UI_LABEL_BUTTON: { + const UILabelButton* tmpLabelButton = static_cast(view); + cJSON_AddStringToObject(usr, "text", tmpLabelButton->GetText()); + tmpLabelButton = nullptr; + break; + } + case UI_CHECK_BOX: + case UI_RADIO_BUTTON: { + const UICheckBox* tmpCheckBox = static_cast(view); + if (tmpCheckBox->GetState()) { + cJSON_AddStringToObject(usr, "state", "UNSELECTED"); + } else { + cJSON_AddStringToObject(usr, "state", "SELECTED"); + } + tmpCheckBox = nullptr; + break; + } + case UI_TOGGLE_BUTTON: { + const UIToggleButton* tmpToggleButton = static_cast(view); + cJSON_AddBoolToObject(usr, "state", tmpToggleButton->GetState()); + tmpToggleButton = nullptr; + break; + } + case UI_IMAGE_VIEW: { + AddImageViewSpecialField(view, usr); + break; + } + // case below are all progress, thus has same attr. + case UI_BOX_PROGRESS: + case UI_SLIDER: + case UI_CIRCLE_PROGRESS: { + const UIAbstractProgress* tmpAbstractProgress = static_cast(view); + cJSON_AddNumberToObject(usr, "currValue", static_cast(tmpAbstractProgress->GetValue())); + cJSON_AddNumberToObject(usr, "rangeMin", static_cast(tmpAbstractProgress->GetRangeMin())); + cJSON_AddNumberToObject(usr, "rangeMax", static_cast(tmpAbstractProgress->GetRangeMax())); + tmpAbstractProgress = nullptr; + break; + } + case UI_SCROLL_VIEW: { + const UIScrollView* tmpScrollView = static_cast(view); + cJSON_AddBoolToObject(usr, "xScrollable", tmpScrollView->GetHorizontalScrollState()); + cJSON_AddBoolToObject(usr, "yScrollable", tmpScrollView->GetVerticalScrollState()); + tmpScrollView = nullptr; + break; + } + case UI_LIST: { + UIList* tmpList = static_cast(const_cast(view)); + cJSON_AddBoolToObject(usr, "isLoopList", tmpList->GetLoopState()); + UIView* selectView = tmpList->GetSelectView(); + if (selectView != nullptr) { + cJSON_AddNumberToObject(usr, "selectedIndex", static_cast(selectView->GetViewIndex())); + selectView = nullptr; + } + tmpList = nullptr; + break; + } + case UI_DIGITAL_CLOCK: + case UI_ANALOG_CLOCK: { + const UIAbstractClock* tmpAbstractClock = static_cast(view); + cJSON_AddNumberToObject(usr, "currentHour", static_cast(tmpAbstractClock->GetCurrentHour())); + cJSON_AddNumberToObject(usr, "currentMinute", static_cast(tmpAbstractClock->GetCurrentMinute())); + cJSON_AddNumberToObject(usr, "currentSecond", static_cast(tmpAbstractClock->GetCurrentSecond())); + tmpAbstractClock = nullptr; + break; + } + case UI_PICKER: { + const UIPicker* tmpPicker = static_cast(view); + cJSON_AddNumberToObject(usr, "selectedIndex", static_cast(tmpPicker->GetSelected())); + tmpPicker = nullptr; + break; + } + case UI_SWIPE_VIEW: { + const UISwipeView* tmpSwipeView = static_cast(view); + cJSON_AddNumberToObject(usr, "currentIndex", static_cast(tmpSwipeView->GetCurrentPage())); + cJSON_AddNumberToObject(usr, "direction", static_cast(tmpSwipeView->GetDirection())); + tmpSwipeView = nullptr; + break; + } + case UI_TIME_PICKER: { + const UITimePicker* tmpTimePicker = static_cast(view); + cJSON_AddStringToObject(usr, "selectedHour", tmpTimePicker->GetSelectHour()); + cJSON_AddStringToObject(usr, "selectedMinute", tmpTimePicker->GetSelectMinute()); + cJSON_AddStringToObject(usr, "selectedSecond", tmpTimePicker->GetSelectSecond()); + tmpTimePicker = nullptr; + break; + } + default: + break; + } +} + +void UIDumpDomTree::AddCommonField(UIView* view, cJSON* usr) const +{ + if ((view == nullptr) || (usr == nullptr)) { + return; + } + cJSON_AddNumberToObject(usr, "x", static_cast(view->GetOrigRect().GetX())); + cJSON_AddNumberToObject(usr, "y", static_cast(view->GetOrigRect().GetY())); + cJSON_AddNumberToObject(usr, "width", static_cast(view->GetWidth())); + cJSON_AddNumberToObject(usr, "height", static_cast(view->GetHeight())); + cJSON_AddStringToObject(usr, "id", view->GetViewId()); + cJSON_AddBoolToObject(usr, "visible", view->IsVisible()); + cJSON_AddBoolToObject(usr, "touchable", view->IsTouchable()); + cJSON_AddBoolToObject(usr, "draggable", view->IsDraggable()); + cJSON_AddBoolToObject(usr, "onClickListener", (view->GetOnClickListener() != nullptr)); + cJSON_AddBoolToObject(usr, "onDragListener", (view->GetOnDragListener() != nullptr)); + cJSON_AddBoolToObject(usr, "onLongPressListener", (view->GetOnLongPressListener() != nullptr)); +} + +void UIDumpDomTree::OutputDomNode(UIView* view) +{ + if (view == nullptr) { + return; + } + /* Output current view's info into cJSON structure. */ + cJSON* dumpUsr = cJSON_CreateObject(); + if (dumpUsr == nullptr) { + GRAPHIC_LOGE("UIDumpDomTree::OutputDomNode cJSON create object failed Err!\n"); + return; + } + + AddNameField(view->GetViewType(), dumpUsr); + AddCommonField(view, dumpUsr); + AddSpecialField(view, dumpUsr); + + pJson_ = cJSON_Print(dumpUsr); + cJSON_Delete(dumpUsr); +} + +bool UIDumpDomTree::WriteDumpFile() const +{ + unlink(path_); + int32_t fd = open(path_, O_CREAT | O_RDWR, DEFAULT_FILE_PERMISSION); + if (fd < 0) { + GRAPHIC_LOGE("UIDumpDomTree::WriteDumpFile open file failed Err!\n"); + return false; + } + + if (pJson_ == nullptr) { + close(fd); + return false; + } + + uint32_t length = strlen(pJson_); + if (static_cast(write(fd, pJson_, length)) != length) { + GRAPHIC_LOGE("UIDumpDomTree::WriteDumpFile write file failed Err!\n"); + close(fd); + return false; + } + + if (close(fd) < 0) { + return false; + } + return true; +} + +void UIDumpDomTree::OutputDomTree(UIView* view, cJSON* usr) +{ + if (allocErrorFlag_) { + return; + } + + cJSON* dumpUsr = usr; + if (dumpUsr != rootJson_) { + dumpUsr = cJSON_CreateObject(); + if (dumpUsr == nullptr) { + allocErrorFlag_ = true; + GRAPHIC_LOGE("UIDumpDomTree::OutputDomTree cJSON create object failed Err!\n"); + return; + } + /* usr must be a array. */ + cJSON_AddItemToArray(usr, dumpUsr); + } + + AddNameField(view->GetViewType(), dumpUsr); + AddCommonField(view, dumpUsr); + AddSpecialField(view, dumpUsr); + + if (view->IsViewGroup()) { + cJSON* arrayJson = cJSON_CreateArray(); + if (arrayJson == nullptr) { + allocErrorFlag_ = true; + GRAPHIC_LOGE("UIDumpDomTree::OutputDomTree cJSON create object failed Err!\n"); + return; + } + + cJSON_AddItemToObject(dumpUsr, "child", arrayJson); + UIViewGroup* tmpViewGroup = static_cast(view); + UIView* childView = tmpViewGroup->GetChildrenHead(); + while (childView != nullptr) { + OutputDomTree(childView, arrayJson); + childView = childView->GetNextSibling(); + } + } +} + +void UIDumpDomTree::DumpJsonById(UIView* view, const char* id, DumpMode mode) +{ + if (searchFlag_) { + return; + } + + /* Check whether current view is the view we are looking for. */ + if ((view->GetViewId() != nullptr) && !strcmp(view->GetViewId(), id)) { + if (mode == DUMP_NODE) { + /* Find the view with right id, output its info. */ + OutputDomNode(view); + } else if (mode == DUMP_TREE) { + /* Find the view with right id, output its dom tree's info. */ + OutputDomTree(view, rootJson_); + } + /* Set flag to stop the tranversion. */ + searchFlag_ = true; + } else { + /* Look through all childrens of the current viewGroup. */ + if (view->IsViewGroup()) { + UIViewGroup* tmpViewGroup = static_cast(view); + UIView* childView = tmpViewGroup->GetChildrenHead(); + while (childView != nullptr) { + DumpJsonById(childView, id, mode); + childView = childView->GetNextSibling(); + } + } + } +} +#endif // ENABLE_DEBUG + +char* UIDumpDomTree::DumpDomNode(const char* id) +{ +#if ENABLE_DEBUG + if (id == nullptr) { + return nullptr; + } + /* Reset dump info */ + pJson_ = nullptr; + + RootView* rootView = RootView::GetInstance(); + UIView* currView = static_cast(rootView); + /* Search through all views from rootView. */ + DumpJsonById(currView, id, DUMP_NODE); + + /* Reset the search flag and pJson for next search */ + searchFlag_ = false; + return pJson_; +#else + return nullptr; +#endif // ENABLE_DEBUG +} + +bool UIDumpDomTree::DumpDomTree(const char* id, const char* path) +{ +#if ENABLE_DEBUG + path_ = (path == nullptr) ? DEFAULT_DUMP_DOM_TREE_PATH : path; + + RootView* rootView = RootView::GetInstance(); + UIView* currView = static_cast(rootView); + rootJson_ = cJSON_CreateObject(); + if (rootJson_ == nullptr) { + GRAPHIC_LOGE("UIDumpDomTree::DumpDomTree cJSON create object failed Err!\n"); + return false; + } + + if (id == nullptr) { + OutputDomTree(currView, rootJson_); + } else { + DumpJsonById(currView, id, DUMP_TREE); + if (!searchFlag_) { + cJSON_Delete(rootJson_); + rootJson_ = nullptr; + GRAPHIC_LOGI("UIDumpDomTree::DumpDomTree can not find the node \n"); + return false; + } + /* Reset the search flag. */ + searchFlag_ = false; + } + + pJson_ = cJSON_Print(rootJson_); + cJSON_Delete(rootJson_); + rootJson_ = nullptr; + if (pJson_ == nullptr) { + GRAPHIC_LOGE("UIDumpDomTree::DumpDomTree convert cJSON object to string failed Err!\n"); + return false; + } + + if (!WriteDumpFile()) { + cJSON_free(pJson_); + pJson_ = nullptr; + GRAPHIC_LOGE("UIDumpDomTree::DumpDomTree file operation failed Err!\n"); + return false; + } + + cJSON_free(pJson_); + pJson_ = nullptr; + allocErrorFlag_ = false; + return true; +#else + return false; +#endif // ENABLE_DEBUG +} +} diff --git a/frameworks/dfx/ui_screenshot.cpp b/frameworks/dfx/ui_screenshot.cpp new file mode 100755 index 0000000..431533a --- /dev/null +++ b/frameworks/dfx/ui_screenshot.cpp @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "dfx/ui_screenshot.h" + +#if ENABLE_DEBUG +#include "iwindows_manager.h" +#include "color.h" +#include "common/screen.h" +#include "draw/draw_utils.h" +#include "file.h" +#include "graphic_log.h" +#include "image_info.h" +#include "securec.h" + +namespace OHOS { +class UIScreenshotListener : public IWindowsManager::ScreenshotListener { +public: + UIScreenshotListener() : filePath_(nullptr) {} + + virtual ~UIScreenshotListener() + { + if (filePath_ != nullptr) { + UIFree(reinterpret_cast(filePath_)); + filePath_ = nullptr; + } + } + + void OnScreenshotEnd(uint8_t* virAddr, uint32_t width, uint32_t height, + ImagePixelFormat format, uint32_t stride) override + { + if ((virAddr == nullptr) || ((format != IMAGE_PIXEL_FORMAT_ARGB1555) && + (format != IMAGE_PIXEL_FORMAT_ARGB8888))) { + return; + } + + ImageHeader header = {0}; + header.colorMode = ARGB8888; + header.width = width; + header.height = height; + + unlink(filePath_); + int32_t fd = open(filePath_, O_RDWR | O_CREAT, DEFAULT_FILE_PERMISSION); + UIFree(reinterpret_cast(filePath_)); + filePath_ = nullptr; + if (fd < 0) { + GRAPHIC_LOGE("UIScreenshotListener::OnScreenshotEnd open file failed Err!\n"); + return; + } + + if (write(fd, &header, sizeof(ImageHeader)) != sizeof(ImageHeader)) { + GRAPHIC_LOGE("UIScreenshotListener::OnScreenshotEnd write image header failed Err!\n"); + close(fd); + return; + } + + uint32_t row = MAX_MALLOC_SIZE / width; + row = (row == 0) ? 1 : row; + uint32_t size = row * width * sizeof(uint32_t); + uint32_t* argb8888Addr = static_cast(UIMalloc(size)); + if (argb8888Addr == nullptr) { + GRAPHIC_LOGE("UIScreenshotListener::OnScreenshotEnd memory allocation failed Err!"); + close(fd); + return; + } + + while (height >= row) { + WriteBlockToFile(fd, argb8888Addr, virAddr, row, width, format, stride); + height -= row; + } + if (height != 0) { + WriteBlockToFile(fd, argb8888Addr, virAddr, height, width, format, stride); + } + UIFree(reinterpret_cast(argb8888Addr)); + close(fd); + } + + void SetFilePath(char* path) + { + if (filePath_ != nullptr) { + UIFree(reinterpret_cast(filePath_)); + } + filePath_ = path; + } + +private: + static constexpr uint8_t DEFAULT_COLOR_SIZE = 4; + static constexpr uint16_t MAX_MALLOC_SIZE = 2048; // unit: 4 bytes + char* filePath_; + + bool WriteBlockToFile(int32_t fd, uint32_t* buffer, uint8_t*& startAddr, uint32_t row, + uint32_t width, ImagePixelFormat format, uint32_t stride) const + { + uint32_t* argb8888Addr = buffer; + for (uint32_t r = 0; r < row; ++r) { + if (format == IMAGE_PIXEL_FORMAT_ARGB1555) { + uint16_t* temp = reinterpret_cast(startAddr); + for (uint32_t i = 0; i < width; ++i) { + buffer[i] = PixelFormatUtils::ARGB1555ToARGB8888(*temp++); + } + } else if (format == IMAGE_PIXEL_FORMAT_ARGB8888) { + if (memcpy_s(buffer, width * DEFAULT_COLOR_SIZE, startAddr, width * DEFAULT_COLOR_SIZE) != EOK) { + GRAPHIC_LOGE("memcpy_s error!"); + } + } + startAddr += stride; + buffer += width; + } + + uint32_t blockSize = row * width * sizeof(uint32_t); + if (static_cast(write(fd, argb8888Addr, blockSize)) != blockSize) { + GRAPHIC_LOGE("UIScreenshotListener::WriteBlockToFile wrong amount of written data Err!"); + return false; + } + return true; + } +}; + +UIScreenshot::~UIScreenshot() +{ + if (screenshotListener_ != nullptr) { + delete screenshotListener_; + screenshotListener_ = nullptr; + } +} + +bool UIScreenshot::ScreenshotToFile(const char* path) +{ + if (screenshotListener_ == nullptr) { + screenshotListener_ = new UIScreenshotListener(); + if (screenshotListener_ == nullptr) { + GRAPHIC_LOGE("UIScreenshot::ScreenshotToFile register screenshot listener failed Err!\n"); + return false; + } + IWindowsManager::GetInstance()->SetScreenshotListener(screenshotListener_); + } + + const char* srcPath = (path == nullptr) ? DEFAULT_SCREENSHOT_PATH : path; + uint32_t pathLength = strlen(srcPath); + char* destPath = static_cast(UIMalloc(pathLength + 1)); + if (destPath == nullptr) { + return false; + } + + if (memcpy_s(destPath, pathLength + 1, srcPath, pathLength) != EOK) { + UIFree(reinterpret_cast(destPath)); + return false; + } + destPath[pathLength] = '\0'; + screenshotListener_->SetFilePath(destPath); + IWindowsManager::GetInstance()->Screenshot(); + return true; +} +} // namespace OHOS +#endif // ENABLE_DEBUG \ No newline at end of file diff --git a/frameworks/dock/input_device.cpp b/frameworks/dock/input_device.cpp new file mode 100755 index 0000000..a6c4c5a --- /dev/null +++ b/frameworks/dock/input_device.cpp @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "dock/input_device.h" + +namespace OHOS { +void InputDevice::ProcessEvent() +{ + DeviceData data; + bool moreToRead = false; + do { + moreToRead = Read(data); + DispatchEvent(data); + } while (moreToRead); +} +} // namespace OHOS diff --git a/frameworks/dock/input_device.h b/frameworks/dock/input_device.h new file mode 100755 index 0000000..fcc0326 --- /dev/null +++ b/frameworks/dock/input_device.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GRAPHIC_LITE_INPUT_DEVICE_H +#define GRAPHIC_LITE_INPUT_DEVICE_H + +#include "input_event_info.h" +#include "heap_base.h" + +namespace OHOS { +/** + * @brief Input device base class. + */ +class InputDevice : public HeapBase { +public: + InputDevice() : rawDataState_(STATE_RELEASE) {} + virtual ~InputDevice() {} + + /** + * @brief Process event from input device driver. + */ + void ProcessEvent(); + + /** + * @brief Read data from hardware.User should override this to set data + * + * @param [out] input device data. + * + * @returns no more data to read if true. + */ + virtual bool Read(DeviceData& data) = 0; + + constexpr static uint8_t STATE_RELEASE = 0; + constexpr static uint8_t STATE_PRESS = 1; + +protected: + uint16_t rawDataState_; + + /** + * @brief Dispatch event to ui component. + * @param [in] data data received from hardware + */ + virtual void DispatchEvent(const DeviceData& data) = 0; +}; +} // namespace OHOS +#endif // GRAPHIC_LITE_INPUT_DEVICE_H diff --git a/frameworks/dock/key_input_device.cpp b/frameworks/dock/key_input_device.cpp new file mode 100755 index 0000000..79ee9dc --- /dev/null +++ b/frameworks/dock/key_input_device.cpp @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "dock/key_input_device.h" +#include "components/root_view.h" +#include "graphic_math.h" +#include "hal_tick.h" + +namespace OHOS { +void KeyInputDevice::DispatchEvent(const DeviceData& data) +{ + if (data.state != INVALID_KEY_STATE) { + KeyEvent event(data.keyId, data.state); + RootView::GetInstance()->OnKeyEvent(event); + } +} +} // namespace OHOS diff --git a/frameworks/dock/key_input_device.h b/frameworks/dock/key_input_device.h new file mode 100755 index 0000000..46ed95e --- /dev/null +++ b/frameworks/dock/key_input_device.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GRAPHIC_LITE_KEY_INPUT_DEVICE_H +#define GRAPHIC_LITE_KEY_INPUT_DEVICE_H + +#include "dock/input_device.h" + +namespace OHOS { +/** @brief A key input device. */ +class KeyInputDevice : public InputDevice { +public: + /** + * @brief Constructor + * + * @param [in] read If non-null, the read. + * @param [in] userData If non-null, information describing the user. + */ + KeyInputDevice() {} + /** @brief Destructor */ + virtual ~KeyInputDevice() {} + +private: + void DispatchEvent(const DeviceData& data) override; +}; +} // namespace OHOS +#endif // UI_INCLUDE_CORE_PHYKEY_INPUT_DEVICE_H diff --git a/frameworks/dock/ohos/ohos_input_device.cpp b/frameworks/dock/ohos/ohos_input_device.cpp new file mode 100755 index 0000000..76832d7 --- /dev/null +++ b/frameworks/dock/ohos/ohos_input_device.cpp @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "dock/ohos/ohos_input_device.h" +#include "input_event_info.h" +#include "iwindows_manager.h" + +namespace OHOS { +bool OHOSInputDevice::Read(DeviceData& data) +{ + IWindowsManager::GetInstance()->GetEventData(&data); + return false; +} +} diff --git a/frameworks/dock/ohos/ohos_input_device.h b/frameworks/dock/ohos/ohos_input_device.h new file mode 100755 index 0000000..85428e1 --- /dev/null +++ b/frameworks/dock/ohos/ohos_input_device.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GRAPHIC_LITE_OHOS_INPUT_DEVICE_H +#define GRAPHIC_LITE_OHOS_INPUT_DEVICE_H + +#include "dock/pointer_input_device.h" + +namespace OHOS { +class OHOSInputDevice : public PointerInputDevice { +public: + OHOSInputDevice() {} + + virtual ~OHOSInputDevice() {} + + bool Read(DeviceData& data) override; +}; +} +#endif diff --git a/frameworks/dock/pointer_input_device.cpp b/frameworks/dock/pointer_input_device.cpp new file mode 100755 index 0000000..df90071 --- /dev/null +++ b/frameworks/dock/pointer_input_device.cpp @@ -0,0 +1,324 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "dock/pointer_input_device.h" + +#include "components/root_view.h" +#include "core/render_manager.h" +#if ENABLE_AOD +#include "events/aod_callback.h" +#endif +#include "graphic_log.h" +#include "graphic_math.h" +#if ENABLE_WINDOW +#include "window/window.h" +#endif + +namespace OHOS { +void PointerInputDevice::DispatchEvent(const DeviceData& data) +{ + curPos_ = data.point; +#if ENABLE_WINDOW + Window* window = RenderManager::GetInstance().GetWindowById(data.winId); + if (window == nullptr) { + return; + } + curPos_.x = curPos_.x - window->GetRect().GetLeft(); + curPos_.y = curPos_.y - window->GetRect().GetTop(); + UIViewGroup* rootView = window->GetRootView(); + if (rootView == nullptr) { + return; + } +#else + UIViewGroup* rootView = RootView::GetInstance(); +#endif + // invalid touchable and draggable view will be reset to nullptr + if ((touchableView_ != nullptr) && !RootView::FindSubView(*rootView, touchableView_)) { + touchableView_ = nullptr; + lastPos_ = curPos_; + } + if ((draggableView_ != nullptr) && !RootView::FindSubView(*rootView, draggableView_)) { + draggableView_ = nullptr; + lastPos_ = curPos_; + dragLastPos_ = curPos_; + dragLen_ = { 0, 0 }; + dragStep_ = { 0, 0 }; + isDragging_ = false; + } + + if (data.state == STATE_PRESS) { + DispatchPressEvent(rootView); + } else { + DispatchReleaseEvent(rootView); + } + dragLastPos_ = lastPos_; + lastPos_ = curPos_; +} + +void PointerInputDevice::DispatchPressEvent(UIViewGroup* rootView) +{ + if (rootView == nullptr) { + return; + } + // first time to press + if (!pressState_) { + rootView->GetTargetView(curPos_, &touchableView_, &targetView_); + if (touchableView_ == nullptr) { + GRAPHIC_LOGD("PointerInputDevice::DispatchPressEvent cannot find target view!\n"); + return; + } + draggableView_ = GetDraggableView(touchableView_); + pressState_ = true; + pressTimeStamp_ = HALTick::GetInstance().GetTime(); + lastPos_ = curPos_; + dragLastPos_ = lastPos_; + return; + } + uint32_t elapse = HALTick::GetInstance().GetElapseTime(pressTimeStamp_); + DispatchDragStartEvent(); + DispatchDragEvent(); + if (!isDragging_ && (touchableView_ != nullptr) && !cancelSent_) { + UIView* tempView = nullptr; + rootView->GetTargetView(curPos_, &tempView, &targetView_); + if (tempView != touchableView_) { + DispatchCancelEvent(); + } else { + if (!pressSent_ && (elapse > INDEV_PRESS_TIME_IN_DRAG)) { + PressEvent evt(curPos_); + UIView* parent = touchableView_->GetParent(); + if (!touchableView_->OnPressEvent(evt)) { + while (parent != nullptr) { + PressEvent evtParent(curPos_); + if (parent->UIView::OnPressEvent(evtParent)) { + break; + } + parent = parent->GetParent(); + } + } + pressSent_ = true; + } + DispatchLongPressEvent(elapse); + } + } +} + +bool PointerInputDevice::ProcessReleaseEvent() +{ + UIView* parent = touchableView_->GetParent(); + // reissue press event. + if (!pressSent_) { + PressEvent evtPress(curPos_); + if (!touchableView_->OnPressEvent(evtPress)) { + while (parent != nullptr) { + PressEvent evtPressParent(curPos_); + if (parent->UIView::OnPressEvent(evtPressParent)) { + break; + } + parent = parent->GetParent(); + } + } + pressSent_ = true; + return false; + } else { + ReleaseEvent evtRelease(curPos_); + if (!touchableView_->OnReleaseEvent(evtRelease)) { + while (parent != nullptr) { + ReleaseEvent evtReleaseParent(curPos_); + if (parent->UIView::OnReleaseEvent(evtReleaseParent)) { + break; + } + parent = parent->GetParent(); + } + } + + if (pressSent_ && needClick_) { + ClickEvent evt(curPos_); + parent = touchableView_->GetParent(); + if (!touchableView_->OnClickEvent(evt)) { + while (parent != nullptr) { +#if ENABLE_AOD + OnClickEventHappen(parent); +#endif + ClickEvent evtParent(curPos_); + if (parent->UIView::OnClickEvent(evtParent)) { + break; + } + parent = parent->GetParent(); + } + } +#if ENABLE_AOD + OnClickEventHappen(*touchableView_); +#endif + } + } + return true; +} + +void PointerInputDevice::DispatchReleaseEvent(UIViewGroup* rootView) +{ + if (!pressState_ || (rootView == nullptr)) { + return; + } + + DispatchDragStartEvent(); + DispatchDragEndEvent(); + if (!isDragging_ && (touchableView_ != nullptr) && !cancelSent_) { + UIView* tempView = nullptr; + rootView->GetTargetView(curPos_, &tempView, &targetView_); + if (tempView != touchableView_) { + DispatchCancelEvent(); + } else { + if (!ProcessReleaseEvent()) { + return; + } + } + } + isDragging_ = false; + pressState_ = false; + pressSent_ = false; + cancelSent_ = false; + longPressSent_ = false; + needClick_ = true; + touchableView_ = nullptr; +} + +void PointerInputDevice::DispatchDragStartEvent() +{ + if (draggableView_ == nullptr) { + return; + } + dragStep_.x = curPos_.x - lastPos_.x; + dragStep_.y = curPos_.y - lastPos_.y; + dragLen_.x += dragStep_.x; + dragLen_.y += dragStep_.y; + if (!isDragging_) { + if ((MATH_ABS(dragLen_.x) >= INDEV_DRAG_LIMIT) || (MATH_ABS(dragLen_.y) >= INDEV_DRAG_LIMIT)) { + if ((touchableView_ != nullptr) && !cancelSent_) { + DispatchCancelEvent(); + } + // Send Drag Begin Event. + DragEvent evt(curPos_, lastPos_, dragLen_); + UIView* parent = draggableView_->GetParent(); + if (!draggableView_->OnDragStartEvent(evt)) { + while (parent != nullptr) { + DragEvent evtParent(curPos_, lastPos_, dragLen_); + if (parent->UIView::OnDragStartEvent(evtParent)) { + break; + } + parent = parent->GetParent(); + } + } + dragLastPos_ = lastPos_; + isDragging_ = true; + } + } +} + +void PointerInputDevice::DispatchDragEvent() +{ + if ((draggableView_ == nullptr) || !isDragging_) { + return; + } + if ((dragStep_.x != 0) || (dragStep_.y != 0)) { + DragEvent evt(curPos_, lastPos_, dragLen_); + UIView* parent = draggableView_->GetParent(); + if (!draggableView_->OnDragEvent(evt)) { + while (parent != nullptr) { + DragEvent evtParent(curPos_, lastPos_, dragLen_); + if (parent->UIView::OnDragEvent(evtParent)) { + break; + } + parent = parent->GetParent(); + } + } + } +} + +void PointerInputDevice::DispatchDragEndEvent() +{ + if (draggableView_ == nullptr) { + return; + } + + if (isDragging_) { + DragEvent evt(curPos_, lastPos_, dragLen_); + UIView* parent = draggableView_->GetParent(); + evt.SetPreLastPoint(dragLastPos_); + if (!draggableView_->OnDragEndEvent(evt)) { + while (parent != nullptr) { + DragEvent evtParent(curPos_, lastPos_, dragLen_); + if (parent->UIView::OnDragEndEvent(evtParent)) { + break; + } + parent = parent->GetParent(); + } + } +#if ENABLE_AOD + OnDragEndEventHappen(*draggableView_); +#endif + } + dragLen_ = { 0, 0 }; + dragStep_ = { 0, 0 }; + draggableView_ = nullptr; +} + +void PointerInputDevice::DispatchLongPressEvent(uint32_t elapse) +{ + if (!longPressSent_ && (elapse > INDEV_LONG_PRESS_TIME)) { + longPressSent_ = true; + LongPressEvent evt(curPos_, pressTimeStamp_); + bool isConsumed = touchableView_->OnLongPressEvent(evt); + if (touchableView_->GetOnLongPressListener() != nullptr) { + needClick_ = false; + } + UIView* parent = touchableView_->GetParent(); +#if ENABLE_AOD + OnLongPressEventHappen(*touchableView_); +#endif + if (!isConsumed) { + while (parent != nullptr) { + LongPressEvent evtParent(curPos_, pressTimeStamp_); + isConsumed = parent->UIView::OnLongPressEvent(evtParent); + if (needClick_ && (parent->GetOnLongPressListener() != nullptr)) { + needClick_ = false; + } +#if ENABLE_AOD + OnLongPressEventHappen(*parent); +#endif + if (isConsumed) { + break; + } + parent = parent->GetParent(); + } + } + } +} + +void PointerInputDevice::DispatchCancelEvent() +{ + CancelEvent evt(lastPos_); + UIView* parent = touchableView_->GetParent(); + if (!touchableView_->OnCancelEvent(evt)) { + while (parent != nullptr) { + CancelEvent evtParent(lastPos_); + if (parent->UIView::OnCancelEvent(evtParent)) { + break; + } + parent = parent->GetParent(); + } + } + cancelSent_ = true; +} +} // namespace OHOS diff --git a/frameworks/dock/pointer_input_device.h b/frameworks/dock/pointer_input_device.h new file mode 100755 index 0000000..295fb93 --- /dev/null +++ b/frameworks/dock/pointer_input_device.h @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GRAPHIC_LITE_POINTER_INPUT_DEVICE_H +#define GRAPHIC_LITE_POINTER_INPUT_DEVICE_H + +#include "hal_tick.h" +#include "dock/input_device.h" +#include "components/ui_view_group.h" + +namespace OHOS { +/** @brief A pointer input device. */ +class PointerInputDevice : public InputDevice { +public: + PointerInputDevice() + : touchableView_(nullptr), draggableView_(nullptr), targetView_(nullptr), lastPos_({0, 0}), + dragLastPos_({0, 0}), curPos_({ 0, 0 }), dragStep_({ 0, 0 }), dragLen_({ 0, 0 }), pressState_(false), + pressSent_(false), longPressSent_(false), cancelSent_(false), isDragging_(false), needClick_(true), + pressTimeStamp_(0) + {} + virtual ~PointerInputDevice() {} + +protected: + void DispatchEvent(const DeviceData& data) override; + +private: + UIView* touchableView_; + UIView* draggableView_; + UIView* targetView_; + Point lastPos_; + Point dragLastPos_; + Point curPos_; + Point dragStep_; + Point dragLen_; + bool pressState_; + bool pressSent_; + bool longPressSent_; + bool cancelSent_; + bool isDragging_; + bool needClick_; + uint32_t pressTimeStamp_; + + void DispatchPressEvent(UIViewGroup* rootView); + void DispatchReleaseEvent(UIViewGroup* rootView); + void DispatchDragStartEvent(); + void DispatchDragEndEvent(); + void DispatchDragEvent(); + void DispatchLongPressEvent(uint32_t elapse); + void DispatchCancelEvent(); + bool ProcessReleaseEvent(); + UIView* GetDraggableView(UIView* targetView) const + { + UIView* tempView = targetView; + while ((tempView != nullptr) && tempView->IsDragParentInstead()) { + tempView = tempView->GetParent(); + } + if ((tempView == nullptr) || !tempView->IsDraggable()) { + return nullptr; + } + return tempView; + } +}; +}; // namespace OHOS +#endif // GRAPHIC_LITE_POINTER_INPUT_DEVICE_H diff --git a/frameworks/dock/rotate_input_device.cpp b/frameworks/dock/rotate_input_device.cpp new file mode 100755 index 0000000..6322572 --- /dev/null +++ b/frameworks/dock/rotate_input_device.cpp @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "dock/rotate_input_device.h" +#include "dock/focus_manager.h" + +#if ENABLE_ROTATE_INPUT +namespace OHOS { +static int8_t g_rotateCtrl = INT8_MIN; +void RotateInputDevice::DispatchEvent(const DeviceData& data) +{ + UIView *view_ = FocusManager::GetInstance()->GetFocusedView(); + if (view_ == nullptr) { + return; + } else if (g_rotateCtrl == INT8_MIN) { + g_rotateCtrl = INT8_MAX; + return; + } else if (data.rotate == 0 && g_rotateCtrl == NULL) { + view_->OnRotateEvent(0); + g_rotateCtrl = INT8_MAX; + return; + } else if (data.rotate == 0) { + return; + } else { + g_rotateCtrl = data.rotate; + view_->OnRotateEvent(g_rotateCtrl); + g_rotateCtrl = NULL; + } +} +} // namespace OHOS +#endif diff --git a/frameworks/dock/screen_device.h b/frameworks/dock/screen_device.h new file mode 100755 index 0000000..a43db8b --- /dev/null +++ b/frameworks/dock/screen_device.h @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GRAPHIC_LITE_SCREEN_DEVICE_H +#define GRAPHIC_LITE_SCREEN_DEVICE_H + +#include "color.h" +#include "graphic_semaphore.h" +#include "graphic_assert.h" +#include "rect.h" +#if ENABLE_WINDOW +#include "pixel_format_utils.h" +#endif + +namespace OHOS { +#if ENABLE_WINDOW +struct AllocationInfo { + uint8_t* virAddr; + uint8_t* phyAddr; + uint16_t width; + uint16_t height; + uint32_t stride; + ImagePixelFormat pixelFormat; +}; +#endif + +struct TransformOption { + TransformAlgorithm algorithm; +}; + +/** @brief A semaphore for display buffer flushing. */ +class FlushSem : public HeapBase { +public: + /** + * Constructor + * + * @param [in] isFlushing (Optional) True if is flushing, false if not. + */ + FlushSem(bool isFlushing = false) : sem_(1, 1), isFlushing_(isFlushing) {} + + /** + * @brief Destructor + */ + virtual ~FlushSem() {} + + /** Notifies the buffer is flushing end */ + void Notify() + { + isFlushing_ = false; + sem_.Notify(); + } + + /** Waits the buffer is flushing */ + void Wait() + { + while (isFlushing_) { + sem_.Wait(); + } + } + + /** set the flag as flashing */ + void Flushing() + { + isFlushing_ = true; + } + +private: + GraphicSemaphore sem_; + bool isFlushing_; +}; + +/** @brief A display device. */ +class ScreenDevice : public HeapBase { +public: + /** + * @brief Constructor + */ + ScreenDevice() {} + + /** + * @brief Destructor + */ + virtual ~ScreenDevice() {} + + virtual void Flush(int16_t x1, int16_t y1, int16_t x2, int16_t y2, const uint8_t* buffer, ColorMode mode) {} + + virtual void Fill(int16_t x1, int16_t y1, int16_t x2, int16_t y2, const ColorType& color) {} + + /** + * @brief Hardware accelerated filling interface implemented by the product platform + * @param fillArea Indicates the area to be filled + * @param color Indicates the color to be filled + * @param opa Indicates the transparency + * @param dst Indicates the start address of destination memory + * @param dstStride Indicates the number of bytes in a single row of destination memory + * @param dstColorMode Indicates the color format of destination memory + * @return Return true on success, false on failure + * @since 5.0 + * @version 3.0 + */ + virtual bool HardwareFill(const Rect& fillArea, + uint32_t color, + OpacityType opa, + uint8_t* dst, + uint32_t dstStride, + ColorMode dstColorMode) + { + return false; + } + + /** + * @brief Hardware accelerated blending interface implemented by the product platform + * @param src Indicates the start address of source memory + * @param srcRect Indicates the area of ​​the source memory for color blending + * @param srcStride Indicates the number of bytes in a single row of source memory + * @param srcLineNumber Indicates the number of source memory rows + * @param srcColorMode Indicates the source memory color format + * @param color 32-bit XRGB8888 value + * (valid when the source memory is in a format with only alph information such as A1) + * @param opa Indicates the transparency + * @param dst Indicates the start address of destination memory + * @param dstStride Indicates the number of bytes in a single row of destination memory + * @param dstColorMode Indicates the color format of destination memory + * @param x The x coordinate of the upper left vertex of the destination memory for color blending + * @param y The y coordinate of the upper left vertex of the destination memory for color blending + * @return Return true on success, false on failure + * @since 5.0 + * @version 3.0 + */ + virtual bool HardwareBlend(const uint8_t* src, + const Rect& srcRect, + uint32_t srcStride, + uint32_t srcLineNumber, + ColorMode srcColorMode, + uint32_t color, + OpacityType opa, + uint8_t* dst, + uint32_t dstStride, + ColorMode dstColorMode, + uint32_t x, + uint32_t y) + { + return false; + } + + /** + * @brief Hardware accelerated transformation interface implemented by the product platform + * @param src Indicates the start address of the source image + * @param srcColorMode Indicates the color format of the source image + * @param srcRect Indicates the position of the source image in the destination memory and its width and height + * @param transformMatrix Indicates the transformation matrix + * @param opa Indicates the transparency + * @param color 32-bit XRGB8888 value + * (valid when the source memory is in a format with only alph information such as A1) + * @param mask Indicates the masking rectangle, and the content beyond the rectangle is not drawn + * @param dst Indicates the start address of destination memory + * @param dstStride Indicates the number of bytes in a single row of destination memory + * @param dstColorMode Indicates the color format of destination memory + * @param option Indicates the optional setting items for transformation operation + * @return Return true on success, false on failure + * @since 5.0 + * @version 3.0 + */ + virtual bool HardwareTransform(const uint8_t* src, + ColorMode srcColorMode, + const Rect& srcRect, + const Matrix3& transformMatrix, + OpacityType opa, + uint32_t color, + const Rect& mask, + uint8_t* dst, + uint32_t dstStride, + ColorMode dstColorMode, + const TransformOption& option) + { + return false; + } + + virtual void SnapShot(uint32_t len, + bool justCopy, + uint8_t* dest, + const Rect& rect, + bool justRender) + { + } + + virtual void RenderFinish() {} +}; +} // namespace OHOS +#endif // GRAPHIC_LITE_SCREEN_DEVICE_H \ No newline at end of file diff --git a/frameworks/dock/screen_device_proxy.h b/frameworks/dock/screen_device_proxy.h new file mode 100755 index 0000000..01f5452 --- /dev/null +++ b/frameworks/dock/screen_device_proxy.h @@ -0,0 +1,232 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GRAPHIC_LITE_SCREEN_DEVICE_PROXY_H +#define GRAPHIC_LITE_SCREEN_DEVICE_PROXY_H + +#include "color.h" +#include "dock/screen_device.h" +#include "graphic_assert.h" +#include "graphic_semaphore.h" +#include "image_info.h" +#include "rect.h" +#include "transform.h" +#if ENABLE_WINDOW +#include "surface.h" +#endif + +namespace OHOS { +/** @brief A display device proxy */ +class ScreenDeviceProxy : public HeapBase { +public: + static ScreenDeviceProxy* GetInstance() + { + static ScreenDeviceProxy instance; + return &instance; + } + + void SetDevice(ScreenDevice* device) + { + device_ = device; + } + + void Flush(); + + void OnFlushReady(); + + void OnRenderFinish(); + + bool HardwareFill(const Rect& fillArea, + uint32_t color, + OpacityType opa, + uint8_t* dst, + uint32_t dstStride, + ColorMode dstColorMode) + { + if (device_ != nullptr) { + return device_->HardwareFill(fillArea, color, opa, dst, dstStride, dstColorMode); + } + return false; + } + + bool HardwareBlend(const uint8_t* src, + const Rect& srcRect, + uint32_t srcStride, + uint32_t srcLineNumber, + ColorMode srcColorMode, + uint32_t color, + OpacityType opa, + uint8_t* dst, + uint32_t dstStride, + ColorMode dstColorMode, + uint32_t x, + uint32_t y) + { + if (device_ != nullptr) { + return device_->HardwareBlend(src, srcRect, srcStride, srcLineNumber, srcColorMode, color, opa, dst, + dstStride, dstColorMode, x, y); + } + return false; + } + + bool HardwareTransform(const uint8_t* src, + ColorMode srcColorMode, + const Rect& srcRect, + const Matrix3& transformMatrix, + OpacityType opa, + uint32_t color, + const Rect& mask, + uint8_t* dst, + uint32_t dstStride, + ColorMode dstColorMode, + const TransformOption& option) + { + if (device_ != nullptr) { + return device_->HardwareTransform(src, srcColorMode, srcRect, transformMatrix, opa, color, mask, dst, + dstStride, dstColorMode, option); + } + return false; + } + + void SnapShot(uint32_t len, bool justCopy, uint8_t* dest, const Rect& rect, bool justRender) + { + if (device_ != nullptr) { + device_->SnapShot(len, justCopy, dest, rect, justRender); + } + } + +#if ENABLE_FRAME_BUFFER + void SetFramebuffer(uint8_t* addr, ColorMode mode, uint16_t width) + { + frameBufferAddr_ = addr; + frameBufferWidth_ = width; + frameBufferMode_ = mode; + } +#endif + + void SetAnimatorbuffer(uint8_t* addr, ColorMode mode, uint16_t width) + { + animatorBufferAddr_ = addr; + animatorBufferWidth_ = width; + animatorBufferMode_ = mode; + } + + void SetAnimatorbufferWidth(uint16_t width) + { + animatorBufferWidth_ = width; + } + + void EnableAnimatorBuffer(bool status) + { + useAnimatorBuff_ = status; + } + + void SetAnimatorRect(const Rect& rect); + + void SetAnimatorTransMap(TransformMap& transMap) + { + transMap_ = transMap; + } + + void DrawAnimatorBuffer(const Rect& invalidatedArea); + + bool GetAnimatorBufferStatus() + { + return useAnimatorBuff_; + } + +#if ENABLE_WINDOW + AllocationInfo& GetAllocationInfo() + { + return gfxAlloc_; + } +#endif + + uint16_t GetBufferWidth() const + { + if (useAnimatorBuff_) { + return animatorBufferWidth_; + } +#if ENABLE_FRAME_BUFFER + return frameBufferWidth_; +#elif ENABLE_WINDOW + return gfxAlloc_.stride / sizeof(ColorType); +#else + return bufferRect_.GetWidth(); +#endif + } + + Rect& GetBufferRect() + { + return bufferRect_; + } + + void SetScreenSize(uint16_t width, uint16_t height); + + uint16_t GetScreenWidth() const + { + return width_; + } + + uint16_t GetScreenHeight() const + { + return height_; + } + + uint32_t GetScreenArea() const + { + return width_ * height_; + } + + uint8_t* GetBuffer(); + + ColorMode GetBufferMode(); + +private: + ScreenDeviceProxy() {} + virtual ~ScreenDeviceProxy() {} + + ScreenDeviceProxy(const ScreenDeviceProxy&) = delete; + ScreenDeviceProxy& operator=(const ScreenDeviceProxy&) = delete; + ScreenDeviceProxy(ScreenDeviceProxy&&) = delete; + ScreenDeviceProxy& operator=(ScreenDeviceProxy&&) = delete; + + ScreenDevice* device_ = nullptr; + FlushSem flush_ = FlushSem(false); + uint16_t width_ = HORIZONTAL_RESOLUTION; + uint16_t height_ = VERTICAL_RESOLUTION; + uint8_t* buffer_ = nullptr; + Rect bufferRect_; + +#if ENABLE_FRAME_BUFFER + uint8_t* frameBufferAddr_ = nullptr; + uint16_t frameBufferWidth_ = 0; + ColorMode frameBufferMode_ = ARGB8888; +#endif + + uint8_t* animatorBufferAddr_ = nullptr; + uint16_t animatorBufferWidth_ = 0; + ColorMode animatorBufferMode_ = ARGB8888; + Rect curViewRect_; + TransformMap transMap_; + bool useAnimatorBuff_ = false; + ImageInfo animatorImageInfo_ = {{0}}; + +#if ENABLE_WINDOW + AllocationInfo gfxAlloc_ = {0}; +#endif +}; +} // namespace OHOS +#endif // GRAPHIC_LITE_SCREEN_DEVICE_PROXY_H diff --git a/frameworks/dock/virtual_input_device.cpp b/frameworks/dock/virtual_input_device.cpp new file mode 100755 index 0000000..710c4b0 --- /dev/null +++ b/frameworks/dock/virtual_input_device.cpp @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "dock/virtual_input_device.h" +#include "components/root_view.h" +#include "events/virtual_device_event.h" + +#if USE_VIRTUAL_DEVICE +namespace OHOS { +void VirtualInputDevice::DispatchEvent(const DeviceData& data) +{ + if (data.state != INVALID_VIRTUAL_INPUT_STATE) { + VirtualDeviceEvent event(data.type, data.state); + RootView::GetInstance()->OnVirtualDeviceEvent(event); + } +} +} // namespace OHOS +#endif diff --git a/frameworks/dock/virtual_input_device.h b/frameworks/dock/virtual_input_device.h new file mode 100755 index 0000000..060e73d --- /dev/null +++ b/frameworks/dock/virtual_input_device.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GRAPHIC_LITE_VIRTUAL_INPUT_DEVICE_H +#define GRAPHIC_LITE_VIRTUAL_INPUT_DEVICE_H + +#include "dock/input_device.h" +#include "graphic_config.h" + +#if USE_VIRTUAL_DEVICE +namespace OHOS { +/** @brief virtual input device. */ +class VirtualInputDevice : public InputDevice { +public: + /** @brief Constructor */ + VirtualInputDevice() {} + + /** @brief Destructor */ + virtual ~VirtualInputDevice() {} + +private: + void DispatchEvent(const DeviceData& data) override; + + VirtualInputDevice(const VirtualInputDevice&) = delete; + VirtualInputDevice& operator=(const VirtualInputDevice&) = delete; + VirtualInputDevice(VirtualInputDevice&&) = delete; + VirtualInputDevice& operator=(VirtualInputDevice&&) = delete; +}; +} // namespace OHOS +#endif +#endif // GRAPHIC_LITE_VIRTUAL_INPUT_DEVICE_H diff --git a/frameworks/draw/draw_arc.cpp b/frameworks/draw/draw_arc.cpp new file mode 100755 index 0000000..31ceaf3 --- /dev/null +++ b/frameworks/draw/draw_arc.cpp @@ -0,0 +1,490 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "draw/draw_arc.h" +#include "common/image.h" +#include "graphic_math.h" + +namespace OHOS { +#define IS_IN_DEGREERANE(d, s, e) ((s) <= (e)) ? (((d) >= (s)) && ((d) <= (e))) : (((d) >= (s)) || ((d) <= (e))) + +void DrawArc::DrawImg(const Point& imgPos, + Rect& area, + const Rect& invalidatedArea, + const Style& style, + uint8_t opaScale, + const Image* image) +{ + if (image == nullptr) { + return; + } + ImageHeader header = {0}; + image->GetHeader(header); + + Rect cordsTmp; + cordsTmp.SetPosition(imgPos.x, imgPos.y); + cordsTmp.SetHeight(header.height); + cordsTmp.SetWidth(header.width); + if (area.Intersect(area, invalidatedArea)) { + image->DrawImage(cordsTmp, area, style, opaScale); + } +} + +void DrawArc::DrawVerLine(const Point& begin, + const Point& imgPos, + const Rect& mask, + int16_t len, + const Style& style, + uint8_t opaScale, + const Image* image) +{ + Rect rect(begin.x, begin.y, begin.x, begin.y + len); + if ((image != nullptr) && (image->GetSrcType() != IMG_SRC_UNKNOWN)) { + DrawImg(imgPos, rect, mask, style, opaScale, image); + } else { + DrawUtils::GetInstance()->DrawColorArea(rect, mask, style.lineColor_, opaScale); + } +} + +void DrawArc::DrawHorLine(const Point& begin, + const Point& imgPos, + const Rect& mask, + int16_t len, + const Style& style, + uint8_t opaScale, + const Image* image) +{ + if ((image != nullptr) && (image->GetSrcType() != IMG_SRC_UNKNOWN)) { + Rect rect(begin.x, begin.y, begin.x + len, begin.y); + DrawImg(imgPos, rect, mask, style, opaScale, image); + } else { + if (len == 0) { + DrawUtils::GetInstance()->DrawPixel(begin.x, begin.y, mask, style.lineColor_, opaScale); + } else { + Rect rect(begin.x, begin.y, begin.x + len, begin.y); + DrawUtils::GetInstance()->DrawColorArea(rect, mask, style.lineColor_, opaScale); + } + } +} + +int16_t DrawArc::GetDrawAngle(int16_t angle) +{ + if (angle < 0) { + angle = (angle % CIRCLE_IN_DEGREE) + CIRCLE_IN_DEGREE; + } else if (angle > CIRCLE_IN_DEGREE) { + angle = angle % CIRCLE_IN_DEGREE; + } + return angle; +} + +void DrawArc::GetDrawRange(int16_t& start, int16_t& end) +{ + int16_t tempAngle = GetDrawAngle(start); + if (start == end) { + start = tempAngle; + end = tempAngle; + } else if (end - start >= CIRCLE_IN_DEGREE) { + // draw circle + start = 0; + end = CIRCLE_IN_DEGREE; + } else { + start = tempAngle; + end = GetDrawAngle(end); + } +} + +uint16_t DrawArc::CalculateTanDegree(uint16_t x, uint16_t y) +{ + uint16_t degree = FastAtan2(x, y); + if ((degree == QUARTER_IN_DEGREE) && (y != 0)) { + degree--; + } + if ((degree == 0) && (x != 0)) { + degree++; + } + return degree; +} + +void DrawArc::DrawCircleNoEndpoint(ArcInfo& arcInfo, const Rect& mask, const Style& style, uint8_t opa, bool anti) +{ + DrawAxisLine(arcInfo, mask, style, opa); + + int16_t yStart = mask.GetTop() - arcInfo.center.y; + int16_t yEnd = mask.GetBottom() - arcInfo.center.y; + if ((yStart >= 0) && (yEnd >= 0)) { + int16_t tmp = yStart; + yStart = -yEnd; + yEnd = -tmp; + } else if ((yStart < 0) && (yEnd > 0)) { + yStart = MATH_MIN(yStart, -yEnd); + yEnd = -1; + } + yStart = MATH_MAX(yStart, -outRadius_) - 1; + yEnd = MATH_MIN(yEnd, -1); + + int16_t xi; + int16_t xLineStart = -outRadius_; + int16_t xLineStart2 = xLineStart - 1; + int16_t xLineStart3 = COORD_MIN; + + for (y_ = yEnd; y_ > yStart; y_--) { + ySqr_ = static_cast(y_) * y_; + bool isSetStartPot = false; + for (xi = xLineStart2; xi < 0; xi++) { + uint32_t currentSqr = static_cast(xi) * xi + ySqr_; + if (currentSqr > outRadiusSqr_) { + continue; + } + if (!isSetStartPot) { + xLineStart2 = xi; + lineStart_ = xi; + if (xLineStart3 != COORD_MIN) { + xi = xLineStart3; + } + isSetStartPot = true; + } + if (y_ <= -inRadius_) { + lineEnd_ = -1; + xi = -1; + break; + } + if (currentSqr < inRadiusSqr_) { + xLineStart3 = xi - 1; + lineEnd_ = xi - 1; + break; + } + } + if (!isSetStartPot) { + continue; + } +#if ENABLE_ANTIALIAS + if (anti) { + DrawLineAnti(arcInfo, mask, style, opa); + } +#endif + DrawLineWithDegree(arcInfo, -lineEnd_, -lineStart_, y_, mask, style, opa, ARC_QUADRANT_ONE); + + DrawLineWithDegree(arcInfo, -lineEnd_, -lineStart_, -y_, mask, style, opa, ARC_QUADRANT_TWO); + + DrawLineWithDegree(arcInfo, lineStart_, lineEnd_, -y_, mask, style, opa, ARC_QUADRANT_THREE); + + DrawLineWithDegree(arcInfo, lineStart_, lineEnd_, y_, mask, style, opa, ARC_QUADRANT_FOUR); + } +} + +void DrawArc::DrawAxisLine(ArcInfo& arcInfo, const Rect& mask, const Style& style, uint8_t opa) +{ + int16_t lineWidth = 0; + int16_t outRadius = outRadius_ - 1; + int16_t inRadius = inRadius_; + if (inRadius <= 0) { + inRadius = 1; + DrawHorLine(arcInfo.center, arcInfo.imgPos, mask, 0, style, opa, arcInfo.imgSrc); + } + lineWidth = outRadius - inRadius; + + if (isCircle_ || (IS_IN_DEGREERANE(THREE_QUARTER_IN_DEGREE, arcInfo.startAngle, arcInfo.endAngle))) { + DrawHorLine(Point{static_cast(arcInfo.center.x - outRadius), arcInfo.center.y}, arcInfo.imgPos, mask, + lineWidth, style, opa, arcInfo.imgSrc); + } + + if (isCircle_ || (IS_IN_DEGREERANE(QUARTER_IN_DEGREE, arcInfo.startAngle, arcInfo.endAngle))) { + DrawHorLine(Point{static_cast(arcInfo.center.x + inRadius), arcInfo.center.y}, arcInfo.imgPos, mask, + lineWidth, style, opa, arcInfo.imgSrc); + } + + if (isCircle_ || (IS_IN_DEGREERANE(0, arcInfo.startAngle, arcInfo.endAngle))) { + DrawVerLine(Point{arcInfo.center.x, static_cast(arcInfo.center.y - outRadius)}, arcInfo.imgPos, mask, + lineWidth, style, opa, arcInfo.imgSrc); + } + + if (isCircle_ || (IS_IN_DEGREERANE(SEMICIRCLE_IN_DEGREE, arcInfo.startAngle, arcInfo.endAngle))) { + DrawVerLine(Point{arcInfo.center.x, static_cast(arcInfo.center.y + inRadius)}, arcInfo.imgPos, mask, + lineWidth, style, opa, arcInfo.imgSrc); + } +} + +void DrawArc::DrawLineWithDegree(ArcInfo& arcInfo, + int16_t start, + int16_t end, + int16_t y, + const Rect& mask, + const Style& style, + uint8_t opaScale, + uint8_t quadrant) +{ + if (isCircle_) { + DrawHorLine(Point{static_cast(arcInfo.center.x + start), static_cast(arcInfo.center.y + y)}, + arcInfo.imgPos, mask, end - start, style, opaScale, arcInfo.imgSrc); + return; + } + uint16_t degreeStart = GetDegreeInQuadrant(CalculateTanDegree(MATH_ABS(start), MATH_ABS(y)), quadrant); + uint16_t degreeEnd = GetDegreeInQuadrant(CalculateTanDegree(MATH_ABS(end), MATH_ABS(y)), quadrant); + if (degreeStart > degreeEnd) { + uint16_t tmp = degreeStart; + degreeStart = degreeEnd; + degreeEnd = tmp; + } + + int16_t lineDegreeRet = GetDegreeRangeIntersectState(degreeStart, degreeEnd, arcInfo.startAngle, arcInfo.endAngle); + int16_t drawEnd = 0; + switch (lineDegreeRet) { + case OUT_DEGREE_RANG: + return; + case IN_DEGREE_RANG: + DrawHorLine( + Point{static_cast(arcInfo.center.x + start), static_cast(arcInfo.center.y + y)}, + arcInfo.imgPos, mask, end - start, style, opaScale, arcInfo.imgSrc); + return; + case INTERSECT: + DrawLineWithDegreeInner(arcInfo, start, end, y, mask, style, opaScale, quadrant); + return; + case DOUBLE_INTERSECT: + drawEnd = DrawLineWithDegreeInner(arcInfo, start, end, y, mask, style, opaScale, quadrant); + DrawLineWithDegreeInner(arcInfo, drawEnd + 1, end, y, mask, style, opaScale, quadrant); + return; + default: + return; + } +} + +int16_t DrawArc::DrawLineWithDegreeInner(ArcInfo& arcInfo, + int16_t start, + int16_t end, + int16_t y, + const Rect& mask, + const Style& style, + uint8_t opaScale, + uint8_t quadrant) +{ + int16_t drawStart = COORD_MIN; + int16_t drawEnd = COORD_MIN; + for (int16_t xi = start; xi <= end; xi++) { + uint16_t degreeBase = CalculateTanDegree(MATH_ABS(xi), MATH_ABS(y)); + uint16_t degree = GetDegreeInQuadrant(degreeBase, quadrant); + if (IS_IN_DEGREERANE(degree, arcInfo.startAngle, arcInfo.endAngle)) { + if (drawStart == COORD_MIN) { + drawStart = xi; + } + } else { + if ((drawStart != COORD_MIN) && (drawEnd == COORD_MIN)) { + drawEnd = xi - 1; + break; + } + } + } + if (drawEnd == COORD_MIN) { + drawEnd = end; + } + if ((drawStart != COORD_MIN) && (drawEnd != COORD_MIN)) { + DrawHorLine( + Point{static_cast(arcInfo.center.x + drawStart), static_cast(arcInfo.center.y + y)}, + arcInfo.imgPos, mask, drawEnd - drawStart, style, opaScale, arcInfo.imgSrc); + } + return drawEnd; +} + +#if ENABLE_ANTIALIAS +void DrawArc::DrawLineAnti(ArcInfo& arcInfo, const Rect& mask, const Style& style, uint8_t opa) +{ + outAntiStart_ = lineStart_; + outAntiEnd_ = lineStart_; + inAntiStart_ = lineEnd_ + 1; + inAntiEnd_ = COORD_MIN; + + for (int16_t xAnti = lineStart_; xAnti <= lineEnd_; xAnti++) { + uint32_t currentSqr = static_cast(xAnti) * xAnti + ySqr_; + if ((currentSqr <= antiOutRadiusSqr_) || (xAnti == lineEnd_)) { + lineStart_ = xAnti; + outAntiEnd_ = xAnti - 1; + break; + } + } + + for (int16_t xAnti = lineEnd_ + 1; xAnti <= -1; xAnti++) { + uint32_t currentSqr = static_cast(xAnti) * xAnti + ySqr_; + if ((currentSqr <= antiInRadiusSqr_) || (xAnti == -1)) { + inAntiEnd_ = xAnti; + break; + } + } + + for (int16_t xAnti = outAntiStart_; xAnti <= outAntiEnd_; xAnti++) { + uint32_t currentSqr = static_cast(xAnti) * xAnti + ySqr_; + uint8_t antiOpa = + (((static_cast(outRadius_) << 1) - 1 - (currentSqr - antiOutRadiusSqr_)) * OPA_OPAQUE) / + ((outRadius_ << 1) - 1); + antiOpa = (opa == OPA_OPAQUE) ? antiOpa : (static_cast(antiOpa) * opa) >> SHIFT_8; + DrawPointAnti(arcInfo, xAnti, mask, style, antiOpa); + } + + for (int16_t xAnti = inAntiStart_; xAnti <= inAntiEnd_; xAnti++) { + uint32_t currentSqr = static_cast(xAnti) * xAnti + ySqr_; + if (currentSqr <= antiInRadiusSqr_) { + break; + } + uint8_t antiOpa = (static_cast(currentSqr - antiInRadiusSqr_) * OPA_OPAQUE) / ((inRadius_ << 1) - 1); + antiOpa = (opa == OPA_OPAQUE) ? antiOpa : (static_cast(antiOpa) * opa) >> SHIFT_8; + DrawPointAnti(arcInfo, xAnti, mask, style, antiOpa); + } +} + +void DrawArc::DrawPointAnti(ArcInfo& arcInfo, int16_t x, const Rect& mask, const Style& style, uint8_t antiOpa) +{ + int16_t startX; + int16_t starty; + uint16_t degreeBase = CalculateTanDegree(MATH_ABS(x), MATH_ABS(y_)); + if (isCircle_ || (IS_IN_DEGREERANE(CIRCLE_IN_DEGREE - degreeBase, arcInfo.startAngle, arcInfo.endAngle))) { + startX = arcInfo.center.x + x; + starty = arcInfo.center.y + y_; + DrawHorLine(Point{startX, starty}, arcInfo.imgPos, mask, 0, style, antiOpa, arcInfo.imgSrc); + } + if (isCircle_ || (IS_IN_DEGREERANE(SEMICIRCLE_IN_DEGREE + degreeBase, arcInfo.startAngle, arcInfo.endAngle))) { + startX = arcInfo.center.x + x; + starty = arcInfo.center.y - y_; + DrawHorLine(Point{startX, starty}, arcInfo.imgPos, mask, 0, style, antiOpa, arcInfo.imgSrc); + } + if (isCircle_ || (IS_IN_DEGREERANE(degreeBase, arcInfo.startAngle, arcInfo.endAngle))) { + startX = arcInfo.center.x - x; + starty = arcInfo.center.y + y_; + DrawHorLine(Point{startX, starty}, arcInfo.imgPos, mask, 0, style, antiOpa, arcInfo.imgSrc); + } + if (isCircle_ || (IS_IN_DEGREERANE(SEMICIRCLE_IN_DEGREE - degreeBase, arcInfo.startAngle, arcInfo.endAngle))) { + startX = arcInfo.center.x - x; + starty = arcInfo.center.y - y_; + DrawHorLine(Point{startX, starty}, arcInfo.imgPos, mask, 0, style, antiOpa, arcInfo.imgSrc); + } +} +#endif + +uint16_t DrawArc::GetDegreeInQuadrant(uint16_t degree, uint8_t quadrant) +{ + switch (quadrant) { + case ARC_QUADRANT_ONE: + return degree; + case ARC_QUADRANT_TWO: + return SEMICIRCLE_IN_DEGREE - degree; + case ARC_QUADRANT_THREE: + return SEMICIRCLE_IN_DEGREE + degree; + case ARC_QUADRANT_FOUR: + return CIRCLE_IN_DEGREE - degree; + default: + return degree; + } +} + +void DrawArc::Draw(ArcInfo& arcInfo, const Rect& mask, const Style& style, uint8_t opaScale, uint8_t cap) +{ + OpacityType opa = DrawUtils::GetMixOpacity(opaScale, style.lineOpa_); + if ((opa == OPA_TRANSPARENT) || (style.lineWidth_ < 1) || (arcInfo.startAngle == arcInfo.endAngle)) { + return; + } + + SetArcInfo(arcInfo, style); + + if ((arcInfo.imgSrc != nullptr) && (arcInfo.imgSrc->GetSrcType() != IMG_SRC_UNKNOWN)) { + DrawCircleNoEndpoint(arcInfo, mask, style, opa, false); + } else { + DrawCircleNoEndpoint(arcInfo, mask, style, opa, true); + } + + if (!isCircle_ && (cap == CapType::CAP_ROUND)) { + int16_t lineWidth = style.lineWidth_; + if (lineWidth > arcInfo.radius) { + lineWidth = arcInfo.radius; + } + + ArcInfo endArcInfo = arcInfo; + endArcInfo.startAngle = 0; + endArcInfo.endAngle = CIRCLE_IN_DEGREE; + + int16_t outRadius = arcInfo.radius - 1; + lineWidth--; + /* the arc radius of the round cap should be half the line width */ + endArcInfo.radius = (static_cast(lineWidth + 1) >> 1) + 1; + + /* 0.5: round up */ + float temp = (outRadius - endArcInfo.radius + 1) * Sin(arcInfo.startAngle); + int16_t startCapX = static_cast((temp > 0) ? (temp + 0.5f) : (temp - 0.5f)); + + temp = (outRadius - endArcInfo.radius + 1) * Sin(QUARTER_IN_DEGREE - arcInfo.startAngle); + int16_t startCapY = static_cast((temp > 0) ? (temp + 0.5f) : (temp - 0.5f)); + + endArcInfo.center.x += startCapX; + endArcInfo.center.y -= startCapY; + SetArcInfo(endArcInfo, style); + DrawCircleNoEndpoint(endArcInfo, mask, style, opa, true); + + temp = (outRadius - endArcInfo.radius + 1) * Sin(arcInfo.endAngle); + int16_t endCapX = static_cast((temp > 0) ? (temp + 0.5f) : (temp - 0.5f)); + + temp = (outRadius - endArcInfo.radius + 1) * Sin(QUARTER_IN_DEGREE - arcInfo.endAngle); + int16_t endCapY = static_cast((temp > 0) ? (temp + 0.5f) : (temp - 0.5f)); + + endArcInfo.center = arcInfo.center; + endArcInfo.center.x += endCapX; + endArcInfo.center.y -= endCapY; + SetArcInfo(endArcInfo, style); + DrawCircleNoEndpoint(endArcInfo, mask, style, opa, true); + } +} + +int16_t DrawArc::GetDegreeRangeIntersectState(uint16_t degreeStart, uint16_t degreeEnd, uint16_t start, uint16_t end) +{ + if (start <= end) { + if ((degreeStart >= start) && (degreeStart <= end) && (degreeEnd >= start) && (degreeEnd <= end)) { + return IN_DEGREE_RANG; + } else if ((degreeEnd < start) || (degreeStart > end)) { + return OUT_DEGREE_RANG; + } else { + return INTERSECT; + } + } else { + if (((degreeStart >= start) && (degreeEnd >= start)) || ((degreeStart <= end) && (degreeEnd <= end))) { + return IN_DEGREE_RANG; + } else if ((degreeStart > end) && (degreeEnd < start)) { + return OUT_DEGREE_RANG; + } else if ((degreeStart <= end) && (degreeEnd >= start)) { + return DOUBLE_INTERSECT; + } else { + return INTERSECT; + } + } +} +void DrawArc::SetArcInfo(ArcInfo& arcInfo, const Style& style) +{ + outRadius_ = arcInfo.radius; + inRadius_ = outRadius_ - style.lineWidth_; + if (inRadius_ < 0) { + inRadius_ = 0; + } + outRadiusSqr_ = outRadius_ * outRadius_; + inRadiusSqr_ = inRadius_ * inRadius_; + + if ((arcInfo.startAngle == 0) && (arcInfo.endAngle == CIRCLE_IN_DEGREE)) { + isCircle_ = true; + } else { + isCircle_ = false; + } +#if ENABLE_ANTIALIAS + antiOutRadiusSqr_ = (outRadius_ - 1) * (outRadius_ - 1); + if (inRadius_ == 0) { + antiInRadiusSqr_ = 0; + } else { + antiInRadiusSqr_ = (inRadius_ - 1) * (inRadius_ - 1); + } +#endif +} +} // namespace OHOS \ No newline at end of file diff --git a/frameworks/draw/draw_arc.h b/frameworks/draw/draw_arc.h new file mode 100755 index 0000000..8b77635 --- /dev/null +++ b/frameworks/draw/draw_arc.h @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GRAPHIC_LITE_DRAW_ARC_H +#define GRAPHIC_LITE_DRAW_ARC_H +#include "common/image.h" +#include "draw_image.h" +#include "draw_utils.h" +#include "graphic_math.h" +#include "heap_base.h" +#include "imgdecode/cache_manager.h" +#include "style.h" + +namespace OHOS { +struct ArcInfo { + Point center; + Point imgPos; + uint16_t radius; + int16_t startAngle; + int16_t endAngle; + const Image* imgSrc; +}; + +class DrawArc : public HeapBase { +public: + static DrawArc* GetInstance() + { + static DrawArc drawArc; + return &drawArc; + } + + void GetDrawRange(int16_t& start, int16_t& end); + + void Draw(ArcInfo& arcInfo, const Rect& mask, const Style& style, uint8_t opaScale, uint8_t cap); + +private: + static constexpr uint8_t DRAW_ARC_QUADRANT_NUM = 4; + static constexpr uint8_t ARC_QUADRANT_ONE = 0; + static constexpr uint8_t ARC_QUADRANT_TWO = 1; + static constexpr uint8_t ARC_QUADRANT_THREE = 2; + static constexpr uint8_t ARC_QUADRANT_FOUR = 3; + static constexpr int16_t IN_DEGREE_RANG = 0; + static constexpr int16_t OUT_DEGREE_RANG = 1; + static constexpr int16_t INTERSECT = 2; + static constexpr int16_t DOUBLE_INTERSECT = 3; + + int16_t lineStart_; + int16_t lineEnd_; + int16_t outAntiStart_; + int16_t outAntiEnd_; + int16_t inAntiStart_; + int16_t inAntiEnd_; + int16_t y_; + int16_t outRadius_; + int16_t inRadius_; + uint32_t ySqr_; + uint32_t outRadiusSqr_; + uint32_t inRadiusSqr_; + bool isCircle_; + uint32_t antiOutRadiusSqr_; + uint32_t antiInRadiusSqr_; + + DrawArc() + : lineStart_(0), + lineEnd_(0), + outAntiStart_(0), + outAntiEnd_(0), + inAntiStart_(0), + inAntiEnd_(0), + y_(0), + outRadius_(0), + inRadius_(0), + ySqr_(0), + outRadiusSqr_(0), + inRadiusSqr_(0), + isCircle_(false), + antiOutRadiusSqr_(0), + antiInRadiusSqr_(0) + { + } + ~DrawArc() {} + void DrawVerLine(const Point& begin, + const Point& imgPos, + const Rect& mask, + int16_t len, + const Style& style, + uint8_t opaScale, + const Image* imgSrc); + + void DrawHorLine(const Point& begin, + const Point& imgPos, + const Rect& mask, + int16_t len, + const Style& style, + uint8_t opaScale, + const Image* imgSrc); + + void DrawImg(const Point& imgPos, + Rect& area, + const Rect& invalidatedArea, + const Style& style, + uint8_t opaScale, + const Image* imgSrc); + + int16_t GetDegreeRangeIntersectState(uint16_t degreeStart, uint16_t degreeEnd, uint16_t start, uint16_t end); + uint16_t CalculateTanDegree(uint16_t x, uint16_t y); + int16_t GetDrawAngle(int16_t angle); + void DrawCircleNoEndpoint(ArcInfo& arcInfo, const Rect& mask, const Style& style, uint8_t opa, bool anti); + void DrawAxisLine(ArcInfo& arcInfo, const Rect& mask, const Style& style, uint8_t opa); + void DrawLineWithDegree(ArcInfo& arcInfo, + int16_t start, + int16_t end, + int16_t y, + const Rect& mask, + const Style& style, + uint8_t opaScale, + uint8_t quadrant); + int16_t DrawLineWithDegreeInner(ArcInfo& arcInfo, + int16_t start, + int16_t end, + int16_t y, + const Rect& mask, + const Style& style, + uint8_t opaScale, + uint8_t quadrant); +#if ENABLE_ANTIALIAS + void DrawLineAnti(ArcInfo& arcInfo, const Rect& mask, const Style& style, uint8_t opa); + void DrawPointAnti(ArcInfo& arcInfo, int16_t x, const Rect& mask, const Style& style, uint8_t antiOpa); +#endif + uint16_t GetDegreeInQuadrant(uint16_t degree, uint8_t quadrant); + void SetArcInfo(ArcInfo& arcInfo, const Style& style); +}; +} // namespace OHOS +#endif // GRAPHIC_LITE_DRAW_ARC_H diff --git a/frameworks/draw/draw_curve.cpp b/frameworks/draw/draw_curve.cpp new file mode 100755 index 0000000..ef18ffa --- /dev/null +++ b/frameworks/draw/draw_curve.cpp @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "draw/draw_curve.h" +#include "animator/interpolation.h" +#include "draw/draw_line.h" + +namespace OHOS { +void DrawCurve::DrawCubicBezier(const Point& start, const Point& control1, const Point& control2, const Point& end, + const Rect& mask, int16_t width, const ColorType& color, OpacityType opacity) +{ + if ((width == 0) || (opacity == OPA_TRANSPARENT)) { + return; + } + + Point prePoint = start; + for (int16_t t = 1; t <= INTERPOLATION_RANGE; t++) { + Point point; + point.x = Interpolation::GetBezierInterpolation(t, start.x, control1.x, control2.x, end.x); + point.y = Interpolation::GetBezierInterpolation(t, start.y, control1.y, control2.y, end.y); + if ((prePoint.x == point.x) && (prePoint.y == point.y)) { + continue; + } + DrawLine::Draw(prePoint, point, mask, width, color, opacity); + prePoint = point; + } +} +} diff --git a/frameworks/draw/draw_curve.h b/frameworks/draw/draw_curve.h new file mode 100755 index 0000000..3ac0958 --- /dev/null +++ b/frameworks/draw/draw_curve.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GRAPHIC_LITE_DRAW_CURVE_H +#define GRAPHIC_LITE_DRAW_CURVE_H + +#include "color.h" +#include "geometry2d.h" + +namespace OHOS { +class DrawCurve : public HeapBase { +public: + static void DrawCubicBezier(const Point& start, const Point& control1, const Point& control2, const Point& end, + const Rect& mask, int16_t width, const ColorType& color, OpacityType opacity); +private: + static constexpr uint16_t INTERPOLATION_RANGE = 1024; +}; +} // namespace OHOS +#endif // GRAPHIC_LITE_DRAW_CURVE_H \ No newline at end of file diff --git a/frameworks/draw/draw_image.cpp b/frameworks/draw/draw_image.cpp new file mode 100755 index 0000000..40ce6de --- /dev/null +++ b/frameworks/draw/draw_image.cpp @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "draw/draw_image.h" + +#include "color.h" +#include "graphic_log.h" +#include "imgdecode/cache_manager.h" + +namespace OHOS { +void DrawImage::DrawCommon(const Rect& coords, const Rect& mask, + const ImageInfo* img, const Style& style, uint8_t opaScale) +{ + if (img == nullptr) { + return; + } + OpacityType opa = DrawUtils::GetMixOpacity(opaScale, style.imageOpa_); + + /* 3 : when single pixel change bit to byte, the buffer should divide by 8, equal to shift right 3 bits. */ + uint8_t pxByteSize = DrawUtils::GetPxSizeByImageInfo(*img) >> 3; + DrawUtils::GetInstance()->DrawImage(coords, mask, img->data, opa, pxByteSize); +} + +void DrawImage::DrawCommon(const Rect& coords, const Rect& mask, + const char* path, const Style& style, uint8_t opaScale) +{ + if (path == nullptr) { + return; + } + OpacityType opa = DrawUtils::GetMixOpacity(opaScale, style.imageOpa_); + + CacheEntry entry; + if (CacheManager::GetInstance().Open(path, style, entry) != RetCode::OK) { + return; + } + + /* 3 : when single pixel change bit to byte, the buffer should divide by 8, equal to shift right 3 bits. */ + uint8_t pxByteSize = DrawUtils::GetPxSizeByImageInfo(entry.GetImageInfo()) >> 3; + if (entry.InCache()) { + DrawUtils::GetInstance()->DrawImage(coords, mask, entry.GetImgData(), opa, pxByteSize); + } else { + Rect valid = coords; + if (!valid.Intersect(valid, mask)) { + return; + } + + int16_t width = valid.GetWidth(); + if (width <= 0) { + return; + } + uint8_t* buf = static_cast(UIMalloc(static_cast(width) * ((COLOR_DEPTH >> SHIFT_3) + 1))); + if (buf == nullptr) { + return; + } + + Rect line = valid; + line.SetHeight(1); + Point start; + start.x = valid.GetLeft() - coords.GetLeft(); + start.y = valid.GetTop() - coords.GetTop(); + int16_t row; + for (row = valid.GetTop(); row <= valid.GetBottom(); row++) { + if (entry.ReadLine(start, width, buf) != RetCode::OK) { + CacheManager::GetInstance().Close(path); + UIFree(buf); + return; + } + DrawUtils::GetInstance()->DrawImage(line, mask, buf, opa, pxByteSize); + line.SetTop(line.GetTop() + 1); + line.SetBottom(line.GetBottom() + 1); + start.y++; + } + UIFree(buf); + } +} +} // namespace OHOS diff --git a/frameworks/draw/draw_image.h b/frameworks/draw/draw_image.h new file mode 100755 index 0000000..537548d --- /dev/null +++ b/frameworks/draw/draw_image.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GRAPHIC_LITE_DRAW_IMAGE_H +#define GRAPHIC_LITE_DRAW_IMAGE_H + +#include +#include +#include "components/ui_view.h" +#include "draw_utils.h" +#include "geometry2d.h" + +namespace OHOS { +class CacheEntry; + +class DrawImage : public HeapBase { +public: + DrawImage() {} + ~DrawImage() {} + + static void DrawCommon(const Rect& coords, const Rect& mask, + const ImageInfo* img, const Style& style, uint8_t opaScale); + + static void DrawCommon(const Rect& coords, const Rect& mask, + const char* path, const Style& style, uint8_t opaScale); +}; +} // namespace OHOS + +#endif // GRAPHIC_LITE_DRAW_IMAGE_H diff --git a/frameworks/draw/draw_label.cpp b/frameworks/draw/draw_label.cpp new file mode 100755 index 0000000..8311e88 --- /dev/null +++ b/frameworks/draw/draw_label.cpp @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "draw/draw_label.h" +#include +#include "common/typed_text.h" +#include "draw/draw_utils.h" +#include "font/ui_font.h" +#include "font/ui_font_header.h" +#include "graphic_log.h" + +namespace OHOS { +void DrawLabel::DrawTextOneLine(const LabelLineInfo& labelLine) +{ + UIFont* fontEngine = UIFont::GetInstance(); + if (labelLine.direct == TEXT_DIRECT_RTL) { + labelLine.pos.x -= labelLine.offset.x; + } else { + labelLine.pos.x += labelLine.offset.x; + } + + uint32_t i = 0; + uint32_t letter; + uint16_t letterWidth; + while (i < labelLine.lineLength) { + letter = TypedText::GetUTF8Next(labelLine.text, i, i); + + LabelLetterInfo letterInfo{labelLine.pos, + labelLine.mask, + labelLine.style.textColor_, + labelLine.opaScale, + 0, + 0, + letter, + labelLine.direct, + labelLine.fontId, + 0, + labelLine.fontSize}; + DrawUtils::GetInstance()->DrawLetter(letterInfo); + letterWidth = fontEngine->GetWidth(letter, 0); + if (labelLine.direct == TEXT_DIRECT_RTL) { + labelLine.pos.x -= (letterWidth + labelLine.style.letterSpace_); + } else { + labelLine.pos.x += (letterWidth + labelLine.style.letterSpace_); + } + } +} + +void DrawLabel::DrawArcText(const Rect& mask, + const char* text, + const Point& arcCenter, + uint8_t fontId, + const UIArcLabel::ArcTextInfo arcTextInfo, + UIArcLabel::TextOrientation orientation, + const Style& style, + OpacityType opaScale) +{ + if ((text == nullptr) || (arcTextInfo.lineStart == arcTextInfo.lineEnd) || (arcTextInfo.radius == 0)) { + GRAPHIC_LOGE("DrawLabel::DrawArcText invalid parameter\n"); + return; + } + OpacityType opa = DrawUtils::GetMixOpacity(opaScale, style.textOpa_); + if (opa == OPA_TRANSPARENT) { + return; + } + uint16_t letterWidth; + uint16_t letterHeight = UIFont::GetInstance()->GetHeight(); + uint32_t i = arcTextInfo.lineStart; + float angle = arcTextInfo.startAngle; + float posX; + float posY; + float rotateAngle; + bool xorFlag = (orientation == UIArcLabel::TextOrientation::INSIDE) ^ (arcTextInfo.direct == TEXT_DIRECT_LTR); + + while (i < arcTextInfo.lineEnd) { + uint32_t tmp = i; + uint32_t letter = TypedText::GetUTF8Next(text, tmp, i); + if (letter == 0) { + continue; + } + if ((letter == '\r') || (letter == '\n')) { + break; + } + letterWidth = UIFont::GetInstance()->GetWidth(letter, 0); + if ((tmp == arcTextInfo.lineStart) && xorFlag) { + angle += TypedText::GetAngleForArcLen(static_cast(letterWidth), letterHeight, arcTextInfo.radius, + arcTextInfo.direct, orientation); + } + uint16_t arcLen = letterWidth + style.letterSpace_; + if (arcLen == 0) { + continue; + } + float incrementAngle = TypedText::GetAngleForArcLen(static_cast(arcLen), letterHeight, + arcTextInfo.radius, arcTextInfo.direct, orientation); + + rotateAngle = (orientation == UIArcLabel::TextOrientation::INSIDE) ? angle : (angle - SEMICIRCLE_IN_DEGREE); + + // 2: half + float fineTuningAngle = incrementAngle * (static_cast(letterWidth) / (2 * arcLen)); + rotateAngle += (xorFlag ? -fineTuningAngle : fineTuningAngle); + TypedText::GetArcLetterPos(arcCenter, arcTextInfo.radius, angle, posX, posY); + angle += incrementAngle; + + DrawLetterWithRotate(mask, fontId, letter, Point{MATH_ROUND(posX), MATH_ROUND(posY)}, + static_cast(rotateAngle), style.textColor_, opaScale); + } +} + +void DrawLabel::DrawLetterWithRotate(const Rect& mask, + uint8_t fontId, + uint32_t letter, + const Point& pos, + int16_t rotateAngle, + const ColorType& color, + OpacityType opaScale) +{ + UIFont* fontEngine = UIFont::GetInstance(); + FontHeader head; + GlyphNode node; + if (fontEngine->GetCurrentFontHeader(head) != 0) { + return; + } + + const uint8_t* fontMap = fontEngine->GetBitmap(letter, node, 0); + if (fontMap == nullptr) { + return; + } + uint8_t fontWeight = fontEngine->GetFontWeight(fontId); + ColorMode colorMode; + switch (fontWeight) { + case FONT_WEIGHT_1: + colorMode = A1; + break; + case FONT_WEIGHT_2: + colorMode = A2; + break; + case FONT_WEIGHT_4: + colorMode = A4; + break; + case FONT_WEIGHT_8: + colorMode = A8; + break; + default: + return; + } + Rect rectLetter; + rectLetter.SetPosition(pos.x + node.left, pos.y + head.ascender - node.top); + rectLetter.Resize(node.cols, node.rows); + TransformMap transMap(rectLetter); + transMap.Rotate(rotateAngle, Vector2(-node.left, node.top - head.ascender)); + TransformDataInfo letterTranDataInfo = {ImageHeader{colorMode, 0, 0, 0, node.cols, node.rows}, fontMap, fontWeight, + BlurLevel::LEVEL0}; + DrawUtils::GetInstance()->DrawTransform(mask, Point{0, 0}, color, opaScale, transMap, letterTranDataInfo); +} +} // namespace OHOS diff --git a/frameworks/draw/draw_label.h b/frameworks/draw/draw_label.h new file mode 100755 index 0000000..c72741c --- /dev/null +++ b/frameworks/draw/draw_label.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GRAPHIC_LITE_DRAW_LABEL_H +#define GRAPHIC_LITE_DRAW_LABEL_H + +#include "common/typed_text.h" +#include "draw/draw_utils.h" +#include "geometry2d.h" +#include "heap_base.h" +#include "style.h" + +namespace OHOS { +class DrawLabel : public HeapBase { +public: + static void DrawTextOneLine(const LabelLineInfo& labelLine); + + static void DrawArcText(const Rect& mask, const char* text, const Point& arcCenter, uint8_t fontId, + const UIArcLabel::ArcTextInfo arcTextInfo, UIArcLabel::TextOrientation orientation, const Style& style, + uint8_t opaScale); + + static void DrawLetterWithRotate(const Rect& mask, uint8_t fontId, uint32_t letter, const Point& pos, + int16_t rotateAngle, const ColorType& color, OpacityType opaScale); +}; +} // namespace OHOS +#endif // GRAPHIC_LITE_DRAW_LABEL_H diff --git a/frameworks/draw/draw_line.cpp b/frameworks/draw/draw_line.cpp new file mode 100755 index 0000000..ffb26a0 --- /dev/null +++ b/frameworks/draw/draw_line.cpp @@ -0,0 +1,472 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "draw/draw_line.h" +#include "draw/draw_utils.h" +#include "graphic_math.h" + +namespace OHOS { +#define INCREASE_ACC(acc, accTemp, adj, step, dir) \ + do { \ + (accTemp) = (acc); \ + (acc) += (adj); \ + if ((acc) <= (accTemp)) { \ + (step) += (dir); \ + } \ + } while (0) + +#define SWAP_START_END(sx, sy, ex, ey, dx, dy, dir) \ + do { \ + if ((dy) >= (dx)) { \ + if ((sy) > (ey)) { \ + SWAP_POINTS((sx), (ex), (sy), (ey)); \ + } \ + if ((ex) < (sx)) { \ + (dir) = -1; \ + } \ + } else { \ + if ((sx) < (ex)) { \ + SWAP_POINTS((sx), (ex), (sy), (ey)); \ + } \ + if ((ey) < (sy)) { \ + (dir) = -1; \ + } \ + } \ + } while (0) + +#define SWAP_IF_Y_LARGER(x1, x2, y1, y2) \ + if ((y1) > (y2)) { \ + SWAP_POINTS((x1), (x2), (y1), (y2)); \ + } + +#define SWAP_IF_X_SMALLER(x1, x2, y1, y2) \ + if ((x1) < (x2)) { \ + SWAP_POINTS((x1), (x2), (y1), (y2)); \ + } + +void DrawLine::Draw(const Point& start, + const Point& end, + const Rect& mask, + int16_t width, + const ColorType& color, + OpacityType opacity) +{ + if ((width == 0) || (opacity == OPA_TRANSPARENT)) { + return; + } + + int16_t yTop; + int16_t yBottom; + + if (start.y < end.y) { + yTop = start.y - width / 2; // 2: half + yBottom = end.y + width / 2; // 2: half + } else { + yTop = end.y - width / 2; // 2: half + yBottom = start.y + width / 2; // 2: half + } + + if ((yBottom < mask.GetTop()) || (yTop > mask.GetBottom())) { + return; + } + + if (start.y == end.y) { + DrawHorizontalLine(start, end, mask, width, color, opacity); + } else if (start.x == end.x) { + DrawVerticalLine(start, end, mask, width, color, opacity); + } else { + DrawWuLine(start, end, mask, width, color, opacity); + } +} + +void DrawLine::DrawVerticalLine(const Point& start, + const Point& end, + const Rect& mask, + int16_t width, + const ColorType& color, + OpacityType opacity) +{ + Rect rect; + + if (start.y < end.y) { + rect.SetX(start.x - width / 2); // 2: half + rect.SetY(start.y); + rect.SetWidth(width); + rect.SetHeight(end.y - start.y + 1); + } else { + rect.SetX(end.x - width / 2); // 2: half + rect.SetY(end.y); + rect.SetWidth(width); + rect.SetHeight(start.y - end.y + 1); + } + + DrawUtils::GetInstance()->DrawColorArea(rect, mask, color, opacity); +} + +void DrawLine::DrawHorizontalLine(const Point& start, + const Point& end, + const Rect& mask, + int16_t width, + const ColorType& color, + OpacityType opacity) +{ + Rect rect; + + if (start.x < end.x) { + rect.SetX(start.x); + rect.SetY(start.y - width / 2); // 2: half + rect.SetWidth(end.x - start.x + 1); + rect.SetHeight(width); + } else { + rect.SetX(end.x); + rect.SetY(end.y - width / 2); // 2: half + rect.SetWidth(start.x - end.x + 1); + rect.SetHeight(width); + } + + DrawUtils::GetInstance()->DrawColorArea(rect, mask, color, opacity); +} + +void DrawLine::DrawWuLine(const Point& start, const Point& end, const Rect& mask, + int16_t width, const ColorType& color, OpacityType opacity) +{ + if (width <= 2) { // 2 : thin line width + DrawThinWuLine(start, end, mask, width, color, opacity); + return; + } + + int16_t sx = start.x; + int16_t sy = start.y; + int16_t ex = end.x; + int16_t ey = end.y; + uint16_t dx = MATH_ABS(ex - sx); + uint16_t dy = MATH_ABS(ey - sy); + int8_t dir = 1; + SWAP_START_END(sx, sy, ex, ey, dx, dy, dir); + + // calculate four vertex ordered according to dy and dx + float plot = -static_cast(ex - sx) / static_cast(ey - sy); + float offset = 1 / (1 + plot * plot); + offset = Sqrt(offset) * width / 2; // 2: half + float x0 = sx + offset; + float y0 = sy + (x0 - sx) * plot; + float x1 = sx - offset; + float y1 = sy + (x1 - sx) * plot; + float x2 = ex + offset; + float y2 = ey + (x2 - ex) * plot; + float x3 = ex - offset; + float y3 = ey + (x3 - ex) * plot; + int16_t x0Int = MATH_ROUND(x0); + int16_t y0Int = MATH_ROUND(y0); + int16_t x1Int = MATH_ROUND(x1); + int16_t y1Int = MATH_ROUND(y1); + int16_t x2Int = MATH_ROUND(x2); + int16_t y2Int = MATH_ROUND(y2); + int16_t x3Int = MATH_ROUND(x3); + int16_t y3Int = MATH_ROUND(y3); + // width is longer than distance between start point and end point, need swap direction of line. + if (dx * dx + dy * dy < width * width) { + if ((dx == 1) && (dy == 1)) { + DrawThinWuLine({ x0Int, y0Int }, { x3Int, y3Int }, mask, 2, color, opacity); // 2 : width of line + return; + } + dx = MATH_ABS(x0Int - x1Int); + dy = MATH_ABS(y0Int - y1Int); + if (dy == dx) { + dir = -dir; + } + } + if (dy >= dx) { + SWAP_IF_Y_LARGER(x0Int, x1Int, y0Int, y1Int); + SWAP_IF_Y_LARGER(x1Int, x2Int, y1Int, y2Int); + SWAP_IF_Y_LARGER(x2Int, x3Int, y2Int, y3Int); + SWAP_IF_Y_LARGER(x0Int, x1Int, y0Int, y1Int); + SWAP_IF_Y_LARGER(x1Int, x2Int, y1Int, y2Int); + SWAP_IF_Y_LARGER(x0Int, x1Int, y0Int, y1Int); + if (dir == -1) { + SWAP_IF_X_SMALLER(x1Int, x0Int, y1Int, y0Int); + SWAP_IF_X_SMALLER(x3Int, x2Int, y3Int, y2Int); + } else { + SWAP_IF_X_SMALLER(x0Int, x1Int, y0Int, y1Int); + SWAP_IF_X_SMALLER(x2Int, x3Int, y2Int, y3Int); + } + } else { + SWAP_IF_X_SMALLER(x0Int, x1Int, y0Int, y1Int); + SWAP_IF_X_SMALLER(x1Int, x2Int, y1Int, y2Int); + SWAP_IF_X_SMALLER(x2Int, x3Int, y2Int, y3Int); + SWAP_IF_X_SMALLER(x0Int, x1Int, y0Int, y1Int); + SWAP_IF_X_SMALLER(x1Int, x2Int, y1Int, y2Int); + SWAP_IF_X_SMALLER(x0Int, x1Int, y0Int, y1Int); + if (dir == 1) { + SWAP_IF_Y_LARGER(x1Int, x0Int, y1Int, y0Int); + SWAP_IF_Y_LARGER(x3Int, x2Int, y3Int, y2Int); + } else { + SWAP_IF_Y_LARGER(x0Int, x1Int, y0Int, y1Int); + SWAP_IF_Y_LARGER(x2Int, x3Int, y2Int, y3Int); + } + } + + uint64_t adj0; + uint16_t accTemp0; + uint16_t acc0 = 0; + uint64_t adj1; + uint16_t accTemp1; + uint16_t acc1 = 0; + uint16_t accTemp2; + uint16_t acc2 = 0; + + int16_t endPoints0[MAX_LINE_WIDTH] = { 0 }; + int16_t endPoints1[MAX_LINE_WIDTH] = { 0 }; + int16_t temp0 = 0; + int16_t temp1 = 0; + int16_t edge0 = 0; + int16_t edge1 = 0; + Rect rect; + + // sort points + if (dy >= dx) { + adj0 = static_cast(dx << SHIFT_16) / static_cast(dy); + adj1 = static_cast(MATH_ABS(y1Int - y0Int) << SHIFT_16) / + static_cast(MATH_ABS(x1Int - x0Int)); + if (adj1 != 0) { + // draw top line + dx = MATH_ABS(x1Int - x0Int); + sx = x0Int; + sy = y0Int; + DrawUtils::GetInstance()->DrawPixel(x0Int, y0Int, mask, color, opacity); + while (--dx) { + accTemp1 = acc1; + acc1 += adj1; + if (acc1 <= accTemp1) { + if (sy - y0Int < MAX_LINE_WIDTH) { + endPoints0[sy - y0Int] = sx; + } + sy++; + } + sx -= dir; + DrawUtils::GetInstance()->DrawPixelInLine(sx, sy, mask, color, opacity, + (acc1 >> SHIFT_8) ^ OPA_OPAQUE); + } + if (sy - y0Int < MAX_LINE_WIDTH) { + endPoints0[sy - y0Int] = sx - dir; + } + + // draw botttom line + acc1 = 0; + dx = MATH_ABS(x3Int - x2Int); + sy = y3Int; + sx = x3Int; + DrawUtils::GetInstance()->DrawPixel(x3Int, y3Int, mask, color, opacity); + while (--dx) { + accTemp1 = acc1; + acc1 += adj1; + if (acc1 <= accTemp1) { + if (temp1 < MAX_LINE_WIDTH) { + endPoints1[temp1++] = sx; + } + sy--; + } + sx += dir; + DrawUtils::GetInstance()->DrawPixelInLine(sx, sy, mask, color, opacity, (acc1 >> SHIFT_8) ^ OPA_OPAQUE); + } + if (temp1 < MAX_LINE_WIDTH) { + endPoints1[temp1++] = sx + dir; + } + } else { + /* If y0 is equal to y1, draw two horizontal lines as the top line and bottom line. */ + rect.SetRect(MATH_MIN(x0Int, x1Int), y0Int, MATH_MAX(x0Int, x1Int), y1Int); + DrawUtils::GetInstance()->DrawColorArea(rect, mask, color, opacity); + rect.SetRect(MATH_MIN(x2Int, x3Int), y3Int, MATH_MAX(x2Int, x3Int), y2Int); + DrawUtils::GetInstance()->DrawColorArea(rect, mask, color, opacity); + } + + sx = x0Int; + sy = y0Int + 1; + dy = MATH_ABS(y3Int - y0Int); + if (dy == 0) { + return; + } + int16_t sxTemp = x1Int; + while (--dy) { + if (sy <= y1Int) { + INCREASE_ACC(acc0, accTemp0, adj0, sx, dir); + DrawUtils::GetInstance()->DrawPixelInLine(sx + dir, sy, mask, color, opacity, acc0 >> SHIFT_8); + if (temp0 < MAX_LINE_WIDTH) { + edge0 = endPoints0[temp0++]; + } + edge1 = sx; + } else if (sy < y2Int) { + INCREASE_ACC(acc0, accTemp0, adj0, sx, dir); + INCREASE_ACC(acc2, accTemp2, adj0, sxTemp, dir); + DrawUtils::GetInstance()->DrawPixelInLine(sx + dir, sy, mask, color, opacity, acc0 >> SHIFT_8); + DrawUtils::GetInstance()->DrawPixelInLine(sxTemp, sy, mask, color, opacity, + (acc2 >> SHIFT_8) ^ OPA_OPAQUE); + edge0 = sxTemp + dir; + edge1 = sx; + } else if (sy < y3Int) { + INCREASE_ACC(acc2, accTemp2, adj0, sxTemp, dir); + DrawUtils::GetInstance()->DrawPixelInLine(sxTemp, sy, mask, color, opacity, + (acc2 >> SHIFT_8) ^ OPA_OPAQUE); + edge0 = sxTemp + dir; + if (temp1 > 0) { + edge1 = endPoints1[--temp1]; + } + } + if ((dir < 0) && (edge0 > edge1)) { + SWAP_INT16(edge0, edge1); + } + rect.SetRect(edge0, sy, edge1, sy); + DrawUtils::GetInstance()->DrawColorArea(rect, mask, color, opacity); + sy++; + } + } else { + adj0 = static_cast(dy << SHIFT_16) / static_cast(dx); + adj1 = static_cast(MATH_ABS(x1Int - x0Int) << SHIFT_16) / + static_cast(MATH_ABS(y1Int - y0Int)); + if (adj1 != 0) { + // draw top line + dy = MATH_ABS(y1Int - y0Int); + sx = x0Int; + sy = y0Int; + DrawUtils::GetInstance()->DrawPixel(sx, sy, mask, color, opacity); + while (--dy) { + accTemp1 = acc1; + acc1 += adj1; + if (acc1 <= accTemp1) { + if (x0Int - sx < MAX_LINE_WIDTH) { + endPoints0[x0Int - sx] = sy; + } + sx--; + } + sy -= dir; + DrawUtils::GetInstance()->DrawPixelInLine(sx, sy, mask, color, opacity, + (acc1 >> SHIFT_8) ^ OPA_OPAQUE); + } + if (x0Int - sx < MAX_LINE_WIDTH) { + endPoints0[x0Int - sx] = sy - dir; + } + + // draw botttom line + acc1 = 0; + dy = MATH_ABS(y3Int - y2Int); + sy = y3Int; + sx = x3Int; + while (--dy) { + accTemp1 = acc1; + acc1 += adj1; + if (acc1 <= accTemp1) { + if (temp1 < MAX_LINE_WIDTH) { + endPoints1[temp1++] = sy; + } + sx++; + } + sy += dir; + DrawUtils::GetInstance()->DrawPixelInLine(sx, sy, mask, color, opacity, (acc1 >> SHIFT_8) ^ OPA_OPAQUE); + } + DrawUtils::GetInstance()->DrawPixel(x3Int, y3Int, mask, color, opacity); + if (temp1 < MAX_LINE_WIDTH) { + endPoints1[temp1++] = sy + dir; + } + } else { + /* If x0 is equal to x1, draw two vertical lines as the top line and bottom line. */ + rect.SetRect(x1Int, MATH_MIN(y0Int, y1Int), x0Int, MATH_MAX(y0Int, y1Int)); + DrawUtils::GetInstance()->DrawColorArea(rect, mask, color, opacity); + rect.SetRect(x3Int, MATH_MIN(y2Int, y3Int), x2Int, MATH_MAX(y2Int, y3Int)); + DrawUtils::GetInstance()->DrawColorArea(rect, mask, color, opacity); + } + + sx = x0Int - 1; + sy = y0Int; + dx = MATH_ABS(x3Int - x0Int); + int16_t syTemp = y1Int; + if (dx == 0) { + return; + } + while (--dx) { + if (sx >= x1Int) { + INCREASE_ACC(acc0, accTemp0, adj0, sy, dir); + DrawUtils::GetInstance()->DrawPixelInLine(sx, sy + dir, mask, color, opacity, acc0 >> SHIFT_8); + if (temp0 < MAX_LINE_WIDTH) { + edge0 = endPoints0[temp0++]; + } + edge1 = sy; + } else if (sx > x2Int) { + INCREASE_ACC(acc0, accTemp0, adj0, sy, dir); + INCREASE_ACC(acc2, accTemp2, adj0, syTemp, dir); + DrawUtils::GetInstance()->DrawPixelInLine(sx, sy + dir, mask, color, opacity, acc0 >> SHIFT_8); + DrawUtils::GetInstance()->DrawPixelInLine(sx, syTemp, mask, color, opacity, + (acc2 >> SHIFT_8) ^ OPA_OPAQUE); + edge0 = syTemp + dir; + edge1 = sy; + } else if (sx > x3Int) { + INCREASE_ACC(acc2, accTemp2, adj0, syTemp, dir); + DrawUtils::GetInstance()->DrawPixelInLine(sx, syTemp, mask, color, opacity, + (acc2 >> SHIFT_8) ^ OPA_OPAQUE); + edge0 = syTemp + dir; + if (temp1 > 0) { + edge1 = endPoints1[--temp1]; + } + } + if ((dir < 0) && (edge0 > edge1)) { + SWAP_INT16(edge0, edge1); + } + rect.SetRect(sx, edge0, sx, edge1); + DrawUtils::GetInstance()->DrawColorArea(rect, mask, color, opacity); + sx--; + } + } +} + +void DrawLine::DrawThinWuLine(const Point& start, const Point& end, const Rect& mask, + int16_t width, const ColorType& color, OpacityType opacity) +{ + int16_t sx = start.x; + int16_t sy = start.y; + int16_t ex = end.x; + int16_t ey = end.y; + uint16_t dx = MATH_ABS(ex - sx); + uint16_t dy = MATH_ABS(ey - sy); + uint64_t adj; + uint16_t accTemp; + uint16_t acc = 0; + int8_t dir = 1; + SWAP_START_END(sx, sy, ex, ey, dx, dy, dir); + if (dy >= dx) { + adj = static_cast(dx << SHIFT_16) / static_cast(dy); + while (dy--) { + INCREASE_ACC(acc, accTemp, adj, sx, dir); + sy++; + if (width == 1) { + DrawUtils::GetInstance()->DrawAdjPixelInLine(sx, sy, sx + dir, sy, mask, color, opacity, + acc >> SHIFT_8); + } else { + DrawUtils::GetInstance()->DrawVerPixelInLine(sx, sy, dir, mask, color, opacity, acc >> SHIFT_8); + } + } + } else { + adj = static_cast(dy << SHIFT_16) / static_cast(dx); + while (dx--) { + INCREASE_ACC(acc, accTemp, adj, sy, dir); + sx--; + if (width == 1) { + DrawUtils::GetInstance()->DrawAdjPixelInLine(sx, sy, sx, sy + dir, mask, color, opacity, + acc >> SHIFT_8); + } else { + DrawUtils::GetInstance()->DrawHorPixelInLine(sx, sy, dir, mask, color, opacity, acc >> SHIFT_8); + } + } + } +} +} // namespace OHOS \ No newline at end of file diff --git a/frameworks/draw/draw_line.h b/frameworks/draw/draw_line.h new file mode 100755 index 0000000..a53d361 --- /dev/null +++ b/frameworks/draw/draw_line.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GRAPHIC_LITE_DRAW_LINE_H +#define GRAPHIC_LITE_DRAW_LINE_H + +#include "color.h" +#include "geometry2d.h" + +namespace OHOS { +class DrawLine : public HeapBase { +public: + static void Draw(const Point& start, const Point& end, const Rect& mask, + int16_t width, const ColorType& color, OpacityType opacity); + +private: + static void DrawVerticalLine(const Point& start, const Point& end, const Rect& mask, + int16_t width, const ColorType& color, OpacityType opacity); + static void DrawHorizontalLine(const Point& start, const Point& end, const Rect& mask, + int16_t width, const ColorType& color, OpacityType opacity); + static void DrawWuLine(const Point& start, const Point& end, const Rect& mask, + int16_t width, const ColorType& color, OpacityType opacity); + static void DrawThinWuLine(const Point& start, const Point& end, const Rect& mask, + int16_t width, const ColorType& color, OpacityType opacity); +}; +} // namespace OHOS +#endif // GRAPHIC_LITE_DRAW_LINE_H diff --git a/frameworks/draw/draw_rect.cpp b/frameworks/draw/draw_rect.cpp new file mode 100755 index 0000000..4594e34 --- /dev/null +++ b/frameworks/draw/draw_rect.cpp @@ -0,0 +1,728 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "draw/draw_rect.h" +#include "draw/draw_arc.h" +#include "draw/draw_utils.h" +#include "graphic_log.h" +#include "graphic_math.h" +#include "style.h" + +namespace OHOS { +void DrawRect::Draw(const Rect& rect, const Rect& dirtyRect, const Style& style, OpacityType opaScale) +{ + if ((rect.GetWidth() <= 0) || (rect.GetHeight() <= 0)) { + GRAPHIC_LOGD("DrawRect::Draw width or height is zero\n"); + return; + } + + /** + * no border + * radius = 0 (1/4) + * radius > 0 (2/4) + * rect width > rect height + * radius >= rect height / 2 + * radius < rect height / 2 + * rect width <= rect height + * radius >= rect width / 2 + * radius < rect width / 2 + * have border + * radius = 0 (3/4) + * radius > 0 (4/4) + * radius < border width (4.1/4) + * radius = border width (4.2/4) + * radius > border width (4.3/4) + * rect width <= rect height + * radius >= border width + rect height / 2 + * radius < border width + rect height / 2 + * rect width > rect height + * radius >= border width + rect height / 2 + * radius < border width + rect height / 2 + */ + if (style.borderWidth_ == 0) { + if (style.borderRadius_ == 0) { + /* no border no radius (1/4) */ + OpacityType opa = DrawUtils::GetMixOpacity(opaScale, style.bgOpa_); + DrawUtils::GetInstance()->DrawColorArea(rect, dirtyRect, style.bgColor_, opa); + return; + } else { + /* [2/4] no border with radius (2/4) */ + DrawRectRadiusWithoutBorder(rect, dirtyRect, style, opaScale); + } + } else { + if (style.borderRadius_ == 0) { + /* [3/4] border without radius (3/4) */ + DrawRectBorderWithoutRadius(rect, dirtyRect, style, opaScale); + } else if (style.borderRadius_ < style.borderWidth_) { + /* [4.1/4] radius < border width */ + DrawRectRadiusSmallThanBorder(rect, dirtyRect, style, opaScale); + } else if (style.borderRadius_ == style.borderWidth_) { + /* [4.2/4] radius = border width */ + DrawRectRadiusEqualBorder(rect, dirtyRect, style, opaScale); + } else { + /* [4.3/4] radius >= border width + rect height_or_width / 2 */ + DrawRectRadiusBiggerThanBorder(rect, dirtyRect, style, opaScale); + } + } +} + +void DrawRect::DrawRectRadiusWithoutBorder(const Rect& rect, const Rect& dirtyRect, const Style& style, + OpacityType opaScale) +{ + // 2 : half + if ((rect.GetWidth() > rect.GetHeight()) && (style.borderRadius_ >= rect.GetHeight() / 2)) { + DrawRectRadiusWithoutBorderCon1(rect, dirtyRect, style, opaScale); + } else if ((rect.GetWidth() < rect.GetHeight()) && (style.borderRadius_ >= rect.GetWidth() / 2)) { + DrawRectRadiusWithoutBorderCon2(rect, dirtyRect, style, opaScale); + } else if ((rect.GetWidth() == rect.GetHeight()) && (style.borderRadius_ >= rect.GetWidth() / 2)) { + DrawRectRadiusWithoutBorderCon3(rect, dirtyRect, style, opaScale); + } else { + DrawRectRadiusWithoutBorderCon4(rect, dirtyRect, style, opaScale); + } +} + +void DrawRect::DrawRectRadiusWithoutBorderCon1(const Rect& rect, const Rect& dirtyRect, const Style& style, + OpacityType opaScale) +{ + int16_t radius = rect.GetHeight() / 2; + int16_t col2X = rect.GetLeft() + radius - 1; + int16_t col3X = rect.GetRight() - radius + 1; + + int16_t row1Y = rect.GetTop(); + int16_t row2Y = rect.GetTop() + radius - 1; + int16_t row3Y = rect.GetBottom(); + + Style arcStyle = style; + arcStyle.lineWidth_ = radius; + arcStyle.lineColor_ = style.bgColor_; + arcStyle.lineOpa_ = style.bgOpa_; + // draw left sector + ArcInfo arcInfo; + arcInfo.center = {col2X, row2Y}; + arcInfo.radius = radius; + arcInfo.startAngle = SEMICIRCLE_IN_DEGREE; + arcInfo.endAngle = CIRCLE_IN_DEGREE; + arcInfo.imgPos = {0, 0}; + arcInfo.imgSrc = nullptr; + DrawArc::GetInstance()->Draw(arcInfo, dirtyRect, arcStyle, opaScale, CapType::CAP_NONE); + + // draw right sector + arcInfo.center = {col3X, row2Y}; + arcInfo.startAngle = 0; + arcInfo.endAngle = SEMICIRCLE_IN_DEGREE; + DrawArc::GetInstance()->Draw(arcInfo, dirtyRect, arcStyle, opaScale, CapType::CAP_NONE); + + // draw top rectangle + Rect topRect(col2X, row1Y, col3X - 1, row2Y - 1); + OpacityType opa = DrawUtils::GetMixOpacity(opaScale, style.bgOpa_); + DrawUtils::GetInstance()->DrawColorArea(topRect, dirtyRect, style.bgColor_, opa); + + // draw bottom rectangle + Rect bottomRect(col2X + 1, row2Y, col3X - 1, row3Y); + DrawUtils::GetInstance()->DrawColorArea(bottomRect, dirtyRect, style.bgColor_, opa); +} + +void DrawRect::DrawRectRadiusWithoutBorderCon2(const Rect& rect, const Rect& dirtyRect, const Style& style, + OpacityType opaScale) +{ + int16_t radius = rect.GetWidth() / 2; + int16_t col1X = rect.GetLeft(); + int16_t col2X = rect.GetLeft() + radius - 1; + int16_t col3X = rect.GetRight(); + + int16_t row2Y = rect.GetTop() + radius - 1; + int16_t row3Y = rect.GetBottom() - radius + 1; + + Style arcStyle = style; + arcStyle.lineWidth_ = radius; + arcStyle.lineColor_ = style.bgColor_; + arcStyle.lineOpa_ = style.bgOpa_; + // draw top sector + ArcInfo arcInfo; + arcInfo.center = {col2X, row2Y}; + arcInfo.startAngle = THREE_QUARTER_IN_DEGREE; + arcInfo.endAngle = QUARTER_IN_DEGREE; + arcInfo.radius = radius; + arcInfo.imgPos = {0, 0}; + arcInfo.imgSrc = nullptr; + DrawArc::GetInstance()->Draw(arcInfo, dirtyRect, arcStyle, opaScale, CapType::CAP_NONE); + + // draw bottom sector + arcInfo.center = {col2X, row3Y}; + arcInfo.startAngle = QUARTER_IN_DEGREE; + arcInfo.endAngle = THREE_QUARTER_IN_DEGREE; + DrawArc::GetInstance()->Draw(arcInfo, dirtyRect, arcStyle, opaScale, CapType::CAP_NONE); + + // draw middle rectangle + Rect middleRect(col1X, row2Y + 1, col3X, row3Y - 1); + OpacityType opa = DrawUtils::GetMixOpacity(opaScale, style.bgOpa_); + DrawUtils::GetInstance()->DrawColorArea(middleRect, dirtyRect, style.bgColor_, opa); +} + +void DrawRect::DrawRectRadiusWithoutBorderCon3(const Rect& rect, const Rect& dirtyRect, const Style& style, + OpacityType opaScale) +{ + int16_t radius = rect.GetWidth() / 2; + int16_t col1X = rect.GetLeft() + radius - 1; + int16_t row1Y = rect.GetTop() + radius - 1; + + Style arcStyle = style; + arcStyle.lineWidth_ = radius; + arcStyle.lineColor_ = style.bgColor_; + arcStyle.lineOpa_ = style.bgOpa_; + // draw circle + ArcInfo arcInfo; + arcInfo.center = {col1X, row1Y}; + arcInfo.startAngle = 0; + arcInfo.endAngle = CIRCLE_IN_DEGREE; + arcInfo.radius = radius; + arcInfo.imgPos = {0, 0}; + arcInfo.imgSrc = nullptr; + DrawArc::GetInstance()->Draw(arcInfo, dirtyRect, arcStyle, opaScale, CapType::CAP_NONE); +} + +void DrawRect::DrawRectRadiusWithoutBorderCon4(const Rect& rect, const Rect& dirtyRect, const Style& style, + OpacityType opaScale) +{ + int16_t radius = style.borderRadius_; + int16_t col1X = rect.GetLeft(); + int16_t col2X = rect.GetLeft() + radius - 1; + int16_t col3X = rect.GetRight() - radius + 1; + int16_t col4X = rect.GetRight(); + + int16_t row1Y = rect.GetTop(); + int16_t row2Y = rect.GetTop() + radius - 1; + int16_t row3Y = rect.GetBottom() - radius + 1; + int16_t row4Y = rect.GetBottom(); + + OpacityType opa = DrawUtils::GetMixOpacity(opaScale, style.bgOpa_); + // draw top rectangle + Rect topRect(col2X, row1Y, col3X - 1, row2Y); + DrawUtils::GetInstance()->DrawColorArea(topRect, dirtyRect, style.bgColor_, opa); + + // draw middle rectangle + Rect middleRect(col1X, row2Y + 1, col4X, row3Y - 1); + DrawUtils::GetInstance()->DrawColorArea(middleRect, dirtyRect, style.bgColor_, opa); + + // draw bottom rectangle + Rect bottomRect(col2X + 1, row3Y, col3X - 1, row4Y); + DrawUtils::GetInstance()->DrawColorArea(bottomRect, dirtyRect, style.bgColor_, opa); + + Style arcStyle = style; + arcStyle.lineWidth_ = radius; + arcStyle.lineColor_ = style.bgColor_; + arcStyle.lineOpa_ = style.bgOpa_; + // top left sector + ArcInfo arcInfo; + arcInfo.center = {col2X, row2Y}; + arcInfo.startAngle = THREE_QUARTER_IN_DEGREE; + arcInfo.endAngle = CIRCLE_IN_DEGREE; + arcInfo.radius = radius; + arcInfo.imgPos = {0, 0}; + arcInfo.imgSrc = nullptr; + DrawArc::GetInstance()->Draw(arcInfo, dirtyRect, arcStyle, opaScale, CapType::CAP_NONE); + + // top right sector + arcInfo.center = {col3X, row2Y}; + arcInfo.startAngle = 0; + arcInfo.endAngle = QUARTER_IN_DEGREE; + DrawArc::GetInstance()->Draw(arcInfo, dirtyRect, arcStyle, opaScale, CapType::CAP_NONE); + + // bottom left sector + arcInfo.center = {col2X, row3Y}; + arcInfo.startAngle = SEMICIRCLE_IN_DEGREE; + arcInfo.endAngle = THREE_QUARTER_IN_DEGREE; + DrawArc::GetInstance()->Draw(arcInfo, dirtyRect, arcStyle, opaScale, CapType::CAP_NONE); + + // bottom right sector + arcInfo.center = {col3X, row3Y}; + arcInfo.startAngle = QUARTER_IN_DEGREE; + arcInfo.endAngle = SEMICIRCLE_IN_DEGREE; + DrawArc::GetInstance()->Draw(arcInfo, dirtyRect, arcStyle, opaScale, CapType::CAP_NONE); +} + +void DrawRect::DrawRectBorderWithoutRadius(const Rect& rect, const Rect& dirtyRect, const Style& style, + OpacityType opaScale) +{ + int16_t col1X = rect.GetLeft(); + int16_t col2X = rect.GetLeft() + style.borderWidth_ - 1; + int16_t col3X = rect.GetRight() - style.borderWidth_ + 1; + int16_t col4X = rect.GetRight(); + + int16_t row1Y = rect.GetTop(); + int16_t row2Y = rect.GetTop() + style.borderWidth_ - 1; + int16_t row3Y = rect.GetBottom() - style.borderWidth_ + 1; + int16_t row4Y = rect.GetBottom(); + + OpacityType opa = DrawUtils::GetMixOpacity(opaScale, style.borderOpa_); + // draw top border + Rect topRect(col1X, row1Y, col4X, row2Y); + DrawUtils::GetInstance()->DrawColorArea(topRect, dirtyRect, style.borderColor_, opa); + + // draw left border + Rect leftRect(col1X, row2Y + 1, col2X, row3Y - 1); + DrawUtils::GetInstance()->DrawColorArea(leftRect, dirtyRect, style.borderColor_, opa); + + OpacityType opaBg = DrawUtils::GetMixOpacity(opaScale, style.bgOpa_); + // draw middle rectangle + Rect middleRect(col2X + 1, row2Y + 1, col3X - 1, row3Y - 1); + DrawUtils::GetInstance()->DrawColorArea(middleRect, dirtyRect, style.bgColor_, opaBg); + + // draw right border + Rect rightRect(col3X, row2Y + 1, col4X, row3Y - 1); + DrawUtils::GetInstance()->DrawColorArea(rightRect, dirtyRect, style.borderColor_, opa); + + // draw bottom border + Rect bottomRect(col1X, row3Y, col4X, row4Y); + DrawUtils::GetInstance()->DrawColorArea(bottomRect, dirtyRect, style.borderColor_, opa); +} + +void DrawRect::DrawRectRadiusEqualBorder(const Rect& rect, const Rect& dirtyRect, const Style& style, + OpacityType opaScale) +{ + int16_t col1X = rect.GetLeft(); + int16_t col2X = rect.GetLeft() + style.borderRadius_ - 1; + int16_t col3X = rect.GetRight() - style.borderRadius_ + 1; + int16_t col4X = rect.GetRight(); + + int16_t row1Y = rect.GetTop(); + int16_t row2Y = rect.GetTop() + style.borderRadius_ - 1; + int16_t row3Y = rect.GetBottom() - style.borderRadius_ + 1; + int16_t row4Y = rect.GetBottom(); + + Style arcStyle = style; + arcStyle.lineWidth_ = style.borderWidth_; + arcStyle.lineColor_ = style.borderColor_; + arcStyle.lineOpa_ = style.borderOpa_; + // draw top left sector in border + ArcInfo arcInfo; + arcInfo.center = {col2X, row2Y}; + arcInfo.startAngle = THREE_QUARTER_IN_DEGREE; + arcInfo.endAngle = CIRCLE_IN_DEGREE; + arcInfo.radius = style.borderRadius_; + arcInfo.imgPos = {0, 0}; + arcInfo.imgSrc = nullptr; + DrawArc::GetInstance()->Draw(arcInfo, dirtyRect, arcStyle, opaScale, CapType::CAP_NONE); + + // draw top right sector in border + arcInfo.center = {col3X, row2Y}; + arcInfo.startAngle = 0; + arcInfo.endAngle = QUARTER_IN_DEGREE; + DrawArc::GetInstance()->Draw(arcInfo, dirtyRect, arcStyle, opaScale, CapType::CAP_NONE); + + // draw bottom left sector in border + arcInfo.center = {col2X, row3Y}; + arcInfo.startAngle = SEMICIRCLE_IN_DEGREE; + arcInfo.endAngle = THREE_QUARTER_IN_DEGREE; + DrawArc::GetInstance()->Draw(arcInfo, dirtyRect, arcStyle, opaScale, CapType::CAP_NONE); + + // draw bottom right sector in border + arcInfo.center = {col3X, row3Y}; + arcInfo.startAngle = QUARTER_IN_DEGREE; + arcInfo.endAngle = SEMICIRCLE_IN_DEGREE; + DrawArc::GetInstance()->Draw(arcInfo, dirtyRect, arcStyle, opaScale, CapType::CAP_NONE); + + OpacityType opa = DrawUtils::GetMixOpacity(opaScale, style.borderOpa_); + // draw top rectangle in border + Rect topRect(col2X, row1Y, col3X - 1, row2Y); + DrawUtils::GetInstance()->DrawColorArea(topRect, dirtyRect, style.borderColor_, opa); + + // draw left rectangle in border + Rect leftRect(col1X, row2Y + 1, col2X, row3Y - 1); + DrawUtils::GetInstance()->DrawColorArea(leftRect, dirtyRect, style.borderColor_, opa); + + OpacityType opaBg = DrawUtils::GetMixOpacity(opaScale, style.bgOpa_); + // draw middle rectangle + Rect middleRect(col2X + 1, row2Y + 1, col3X - 1, row3Y - 1); + DrawUtils::GetInstance()->DrawColorArea(middleRect, dirtyRect, style.bgColor_, opaBg); + + // draw right rectangle in border + Rect rightRect(col3X, row2Y + 1, col4X, row3Y - 1); + DrawUtils::GetInstance()->DrawColorArea(rightRect, dirtyRect, style.borderColor_, opa); + + // draw bottom rectangle in border + Rect bottomRect(col2X + 1, row3Y, col3X - 1, row4Y); + DrawUtils::GetInstance()->DrawColorArea(bottomRect, dirtyRect, style.borderColor_, opa); +} + +void DrawRect::DrawRectRadiusSmallThanBorder(const Rect& rect, const Rect& dirtyRect, const Style& style, + OpacityType opaScale) +{ + int16_t radiusCol1X = rect.GetLeft(); + int16_t radiusCol2X = rect.GetLeft() + style.borderRadius_ - 1; + int16_t radiusCol3X = rect.GetRight() - style.borderRadius_ + 1; + int16_t radiusCol4X = rect.GetRight(); + + int16_t radiusRow1Y = rect.GetTop(); + int16_t radiusRow2Y = rect.GetTop() + style.borderRadius_ - 1; + int16_t radiusRow3Y = rect.GetBottom() - style.borderRadius_ + 1; + int16_t radiusRow4Y = rect.GetBottom(); + + int16_t rectCol1X = radiusCol1X; + int16_t rectCol2X = rect.GetLeft() + style.borderWidth_ - 1; + int16_t rectCol3X = rect.GetRight() - style.borderWidth_ + 1; + int16_t rectCol4X = radiusCol4X; + + int16_t rectRow1Y = radiusRow2Y; + int16_t rectRow2Y = rect.GetTop() + style.borderWidth_ - 1; + int16_t rectRow3Y = rect.GetBottom() - style.borderWidth_ + 1; + + Style arcStyle = style; + arcStyle.lineWidth_ = style.borderWidth_; + arcStyle.lineColor_ = style.borderColor_; + arcStyle.lineOpa_ = style.borderOpa_; + // draw top left sector in border + ArcInfo arcInfo; + arcInfo.center = {radiusCol2X, radiusRow2Y}; + arcInfo.startAngle = THREE_QUARTER_IN_DEGREE; + arcInfo.endAngle = CIRCLE_IN_DEGREE; + arcInfo.radius = style.borderRadius_; + arcInfo.imgPos = {0, 0}; + arcInfo.imgSrc = nullptr; + DrawArc::GetInstance()->Draw(arcInfo, dirtyRect, arcStyle, opaScale, CapType::CAP_NONE); + + // draw top right sector in border + arcInfo.center = {radiusCol3X, radiusRow2Y}; + arcInfo.startAngle = 0; + arcInfo.endAngle = QUARTER_IN_DEGREE; + DrawArc::GetInstance()->Draw(arcInfo, dirtyRect, arcStyle, opaScale, CapType::CAP_NONE); + + // draw bottom left sector in border + arcInfo.center = {radiusCol2X, radiusRow3Y}; + arcInfo.startAngle = SEMICIRCLE_IN_DEGREE; + arcInfo.endAngle = THREE_QUARTER_IN_DEGREE; + DrawArc::GetInstance()->Draw(arcInfo, dirtyRect, arcStyle, opaScale, CapType::CAP_NONE); + + // draw bottom right sector in border + arcInfo.center = {radiusCol3X, radiusRow3Y}; + arcInfo.startAngle = QUARTER_IN_DEGREE; + arcInfo.endAngle = SEMICIRCLE_IN_DEGREE; + DrawArc::GetInstance()->Draw(arcInfo, dirtyRect, arcStyle, opaScale, CapType::CAP_NONE); + + OpacityType opa = DrawUtils::GetMixOpacity(opaScale, style.borderOpa_); + // draw top rectangle in border + Rect topRect(radiusCol2X, radiusRow1Y, radiusCol3X - 1, radiusRow2Y); + DrawUtils::GetInstance()->DrawColorArea(topRect, dirtyRect, style.borderColor_, opa); + Rect topRect2(rectCol1X, rectRow1Y + 1, rectCol4X, rectRow2Y); + DrawUtils::GetInstance()->DrawColorArea(topRect2, dirtyRect, style.borderColor_, opa); + + // draw left rectangle in border + Rect leftRect(rectCol1X, rectRow2Y + 1, rectCol2X, rectRow3Y - 1); + DrawUtils::GetInstance()->DrawColorArea(leftRect, dirtyRect, style.borderColor_, opa); + + OpacityType opaBg = DrawUtils::GetMixOpacity(opaScale, style.bgOpa_); + // draw middle rectangle + Rect middleRect(rectCol2X + 1, rectRow2Y + 1, rectCol3X - 1, rectRow3Y - 1); + DrawUtils::GetInstance()->DrawColorArea(middleRect, dirtyRect, style.bgColor_, opaBg); + + // draw right rectangle in border + Rect rightRect(rectCol3X, rectRow2Y + 1, rectCol4X, rectRow3Y - 1); + DrawUtils::GetInstance()->DrawColorArea(rightRect, dirtyRect, style.borderColor_, opa); + + // draw bottom rectangle in border + Rect bottomRect(radiusCol2X + 1, radiusRow3Y, radiusCol3X - 1, radiusRow4Y); + DrawUtils::GetInstance()->DrawColorArea(bottomRect, dirtyRect, style.borderColor_, opa); + Rect bottomRect2(rectCol1X, rectRow3Y, rectCol4X, radiusRow3Y - 1); + DrawUtils::GetInstance()->DrawColorArea(bottomRect2, dirtyRect, style.borderColor_, opa); +} + +void DrawRect::DrawRectRadiusBiggerThanBorder(const Rect& rect, const Rect& dirtyRect, const Style& style, + OpacityType opaScale) +{ + // 2 : half + if ((rect.GetWidth() > rect.GetHeight()) && (style.borderRadius_ >= rect.GetHeight() / 2)) { + DrawRectRadiusBiggerThanBorderCon1(rect, dirtyRect, style, opaScale); + } else if ((rect.GetWidth() < rect.GetHeight()) && (style.borderRadius_ >= rect.GetWidth() / 2)) { + DrawRectRadiusBiggerThanBorderCon2(rect, dirtyRect, style, opaScale); + } else if ((rect.GetWidth() == rect.GetHeight()) && (style.borderRadius_ >= rect.GetWidth() / 2)) { + DrawRectRadiusBiggerThanBorderCon3(rect, dirtyRect, style, opaScale); + } else { + DrawRectRadiusBiggerThanBorderCon4(rect, dirtyRect, style, opaScale); + } +} + +void DrawRect::DrawRectRadiusBiggerThanBorderCon1(const Rect& rect, const Rect& dirtyRect, const Style& style, + OpacityType opaScale) +{ + int16_t radius = rect.GetHeight() / 2; + int16_t borderWidth = style.borderWidth_; + int16_t col2X = rect.GetLeft() + radius - 1; + int16_t col3X = rect.GetRight() - radius + 1; + + int16_t row1Y = rect.GetTop(); + int16_t row2Y = rect.GetTop() + borderWidth - 1; + int16_t row3Y = rect.GetTop() + radius - 1; + int16_t row4Y = rect.GetBottom() - borderWidth + 1; + int16_t row5Y = rect.GetBottom(); + + Style arcStyle = style; + arcStyle.lineWidth_ = borderWidth; + arcStyle.lineColor_ = style.borderColor_; + arcStyle.lineOpa_ = style.borderOpa_; + // draw left arc in border + ArcInfo arcInfo; + arcInfo.center = {col2X, row3Y}; + arcInfo.startAngle = SEMICIRCLE_IN_DEGREE; + arcInfo.endAngle = CIRCLE_IN_DEGREE; + arcInfo.radius = radius; + arcInfo.imgPos = {0, 0}; + arcInfo.imgSrc = nullptr; + DrawArc::GetInstance()->Draw(arcInfo, dirtyRect, arcStyle, opaScale, CapType::CAP_NONE); + // draw right arc in border + arcInfo.center = {col3X, row3Y}; + arcInfo.startAngle = 0; + arcInfo.endAngle = SEMICIRCLE_IN_DEGREE; + DrawArc::GetInstance()->Draw(arcInfo, dirtyRect, arcStyle, opaScale, CapType::CAP_NONE); + + radius = radius - borderWidth; + arcStyle.lineWidth_ = radius; + arcStyle.lineColor_ = style.bgColor_; + arcStyle.lineOpa_ = style.bgOpa_; + + // draw left sector in rectangle + arcInfo.center = {col2X, row3Y}; + arcInfo.startAngle = SEMICIRCLE_IN_DEGREE; + arcInfo.endAngle = CIRCLE_IN_DEGREE; + arcInfo.radius = radius; + DrawArc::GetInstance()->Draw(arcInfo, dirtyRect, arcStyle, opaScale, CapType::CAP_NONE); + // draw right sector in rectangle + arcInfo.center = {col3X, row3Y}; + arcInfo.startAngle = 0; + arcInfo.endAngle = SEMICIRCLE_IN_DEGREE; + DrawArc::GetInstance()->Draw(arcInfo, dirtyRect, arcStyle, opaScale, CapType::CAP_NONE); + + OpacityType opa = DrawUtils::GetMixOpacity(opaScale, style.borderOpa_); + // top rectangle in border + Rect topBorderRect(col2X, row1Y, col3X - 1, row2Y); + DrawUtils::GetInstance()->DrawColorArea(topBorderRect, dirtyRect, style.borderColor_, opa); + OpacityType opaBg = DrawUtils::GetMixOpacity(opaScale, style.bgOpa_); + // middle rectangle inner + Rect middleInnerRect(col2X, row2Y + 1, col3X - 1, row3Y); + DrawUtils::GetInstance()->DrawColorArea(middleInnerRect, dirtyRect, style.bgColor_, opaBg); + Rect middleInnerRect2(col2X + 1, row3Y + 1, col3X - 1, row4Y - 1); + DrawUtils::GetInstance()->DrawColorArea(middleInnerRect2, dirtyRect, style.bgColor_, opaBg); + + // bottom rectangle in border + Rect bottomBorderRect(col2X + 1, row4Y, col3X - 1, row5Y); + DrawUtils::GetInstance()->DrawColorArea(bottomBorderRect, dirtyRect, style.borderColor_, opa); +} + +void DrawRect::DrawRectRadiusBiggerThanBorderCon2(const Rect& rect, const Rect& dirtyRect, const Style& style, + OpacityType opaScale) +{ + int16_t radius = rect.GetWidth() / 2; + int16_t borderWidth = style.borderWidth_; + int16_t col1X = rect.GetLeft(); + int16_t col2X = rect.GetLeft() + borderWidth - 1; + int16_t col3X = rect.GetLeft() + radius - 1; + int16_t col4X = rect.GetRight() - borderWidth + 1; + int16_t col5X = rect.GetRight(); + + int16_t row2Y = rect.GetTop() + radius - 1; + int16_t row3Y = rect.GetBottom() - radius + 1; + + Style arcStyle = style; + arcStyle.lineWidth_ = borderWidth; + arcStyle.lineColor_ = style.borderColor_; + arcStyle.lineOpa_ = style.borderOpa_; + // draw top arc in border + ArcInfo arcInfo; + arcInfo.center = {col3X, row2Y}; + arcInfo.startAngle = THREE_QUARTER_IN_DEGREE; + arcInfo.endAngle = QUARTER_IN_DEGREE; + arcInfo.radius = radius; + arcInfo.imgPos = {0, 0}; + arcInfo.imgSrc = nullptr; + DrawArc::GetInstance()->Draw(arcInfo, dirtyRect, arcStyle, opaScale, CapType::CAP_NONE); + // draw bottom arc in border + arcInfo.center = {col3X, row3Y}; + arcInfo.startAngle = QUARTER_IN_DEGREE; + arcInfo.endAngle = THREE_QUARTER_IN_DEGREE; + DrawArc::GetInstance()->Draw(arcInfo, dirtyRect, arcStyle, opaScale, CapType::CAP_NONE); + + radius = radius - borderWidth; + arcStyle.lineWidth_ = radius; + arcStyle.lineColor_ = style.bgColor_; + arcStyle.lineOpa_ = style.bgOpa_; + + // draw top sector in rectangle + arcInfo.center = {col3X, row2Y}; + arcInfo.startAngle = THREE_QUARTER_IN_DEGREE; + arcInfo.endAngle = QUARTER_IN_DEGREE; + arcInfo.radius = radius; + DrawArc::GetInstance()->Draw(arcInfo, dirtyRect, arcStyle, opaScale, CapType::CAP_NONE); + // draw bottom sector in rectangle + arcInfo.center = {col3X, row3Y}; + arcInfo.startAngle = QUARTER_IN_DEGREE; + arcInfo.endAngle = THREE_QUARTER_IN_DEGREE; + DrawArc::GetInstance()->Draw(arcInfo, dirtyRect, arcStyle, opaScale, CapType::CAP_NONE); + + OpacityType opa = DrawUtils::GetMixOpacity(opaScale, style.borderOpa_); + // left rectangle in border + Rect topBorderRect(col1X, row2Y + 1, col2X, row3Y - 1); + DrawUtils::GetInstance()->DrawColorArea(topBorderRect, dirtyRect, style.borderColor_, opa); + + OpacityType opaBg = DrawUtils::GetMixOpacity(opaScale, style.bgOpa_); + // middle rectangle inner + Rect middleInnerRect(col2X + 1, row2Y + 1, col4X - 1, row3Y - 1); + DrawUtils::GetInstance()->DrawColorArea(middleInnerRect, dirtyRect, style.bgColor_, opaBg); + + // right rectangle in border + Rect bottomBorderRect(col4X, row2Y + 1, col5X, row3Y - 1); + DrawUtils::GetInstance()->DrawColorArea(bottomBorderRect, dirtyRect, style.borderColor_, opa); +} + +void DrawRect::DrawRectRadiusBiggerThanBorderCon3(const Rect& rect, const Rect& dirtyRect, const Style& style, + OpacityType opaScale) +{ + int16_t radius = rect.GetWidth() / 2; + int16_t borderWidth = style.borderWidth_; + int16_t col2X = rect.GetLeft() + radius - 1; + int16_t row2Y = rect.GetTop() + radius - 1; + + Style arcStyle = style; + arcStyle.lineWidth_ = borderWidth; + arcStyle.lineColor_ = style.borderColor_; + arcStyle.lineOpa_ = style.borderOpa_; + // draw circle in border + ArcInfo arcInfo; + arcInfo.center = {col2X, row2Y}; + arcInfo.startAngle = 0; + arcInfo.endAngle = CIRCLE_IN_DEGREE; + arcInfo.radius = radius; + arcInfo.imgPos = {0, 0}; + arcInfo.imgSrc = nullptr; + DrawArc::GetInstance()->Draw(arcInfo, dirtyRect, arcStyle, opaScale, CapType::CAP_NONE); + + radius = radius - borderWidth; + arcStyle.lineWidth_ = radius; + arcStyle.lineColor_ = style.bgColor_; + arcStyle.lineOpa_ = style.bgOpa_; + + // draw circle in rectangle + arcInfo.center = {col2X, row2Y}; + arcInfo.startAngle = 0; + arcInfo.endAngle = CIRCLE_IN_DEGREE; + arcInfo.radius = radius; + DrawArc::GetInstance()->Draw(arcInfo, dirtyRect, arcStyle, opaScale, CapType::CAP_NONE); +} + +void DrawRect::DrawRectRadiusBiggerThanBorderCon4(const Rect& rect, const Rect& dirtyRect, const Style& style, + OpacityType opaScale) +{ + int16_t radius = style.borderRadius_; + int16_t borderWidth = style.borderWidth_; + int16_t col1X = rect.GetLeft(); + int16_t col2X = rect.GetLeft() + borderWidth - 1; + int16_t col3X = rect.GetLeft() + radius - 1; + int16_t col4X = rect.GetRight() - radius + 1; + int16_t col5X = rect.GetRight() - borderWidth + 1; + int16_t col6X = rect.GetRight(); + + int16_t row1Y = rect.GetTop(); + int16_t row2Y = rect.GetTop() + borderWidth - 1; + int16_t row3Y = rect.GetTop() + radius - 1; + int16_t row4Y = rect.GetBottom() - radius + 1; + int16_t row5Y = rect.GetBottom() - borderWidth + 1; + int16_t row6Y = rect.GetBottom(); + + Style arcStyle = style; + arcStyle.lineWidth_ = borderWidth; + arcStyle.lineColor_ = style.borderColor_; + arcStyle.lineOpa_ = style.borderOpa_; + + // draw top left arc in border + ArcInfo arcInfo; + arcInfo.center = {col3X, row3Y}; + arcInfo.startAngle = THREE_QUARTER_IN_DEGREE; + arcInfo.endAngle = CIRCLE_IN_DEGREE; + arcInfo.radius = radius; + arcInfo.imgPos = {0, 0}; + arcInfo.imgSrc = nullptr; + DrawArc::GetInstance()->Draw(arcInfo, dirtyRect, arcStyle, opaScale, CapType::CAP_NONE); + // draw top right arc in border + arcInfo.center = {col4X, row3Y}; + arcInfo.startAngle = 0; + arcInfo.endAngle = QUARTER_IN_DEGREE; + DrawArc::GetInstance()->Draw(arcInfo, dirtyRect, arcStyle, opaScale, CapType::CAP_NONE); + // draw bottom left arc in border + arcInfo.center = {col3X, row4Y}; + arcInfo.startAngle = SEMICIRCLE_IN_DEGREE; + arcInfo.endAngle = THREE_QUARTER_IN_DEGREE; + DrawArc::GetInstance()->Draw(arcInfo, dirtyRect, arcStyle, opaScale, CapType::CAP_NONE); + // draw bottom right arc in border + arcInfo.center = {col4X, row4Y}; + arcInfo.startAngle = QUARTER_IN_DEGREE; + arcInfo.endAngle = SEMICIRCLE_IN_DEGREE; + DrawArc::GetInstance()->Draw(arcInfo, dirtyRect, arcStyle, opaScale, CapType::CAP_NONE); + + radius = radius - borderWidth; + arcStyle.lineWidth_ = radius; + arcStyle.lineColor_ = style.bgColor_; + arcStyle.lineOpa_ = style.bgOpa_; + + // draw top left sector in rectangle + arcInfo.center = {col3X, row3Y}; + arcInfo.startAngle = THREE_QUARTER_IN_DEGREE; + arcInfo.endAngle = CIRCLE_IN_DEGREE; + arcInfo.radius = radius; + DrawArc::GetInstance()->Draw(arcInfo, dirtyRect, arcStyle, opaScale, CapType::CAP_NONE); + // draw top right sector in rectangle + arcInfo.center = {col4X, row3Y}; + arcInfo.startAngle = 0; + arcInfo.endAngle = QUARTER_IN_DEGREE; + DrawArc::GetInstance()->Draw(arcInfo, dirtyRect, arcStyle, opaScale, CapType::CAP_NONE); + // draw bottom left sector in rectangle + arcInfo.center = {col3X, row4Y}; + arcInfo.startAngle = SEMICIRCLE_IN_DEGREE; + arcInfo.endAngle = THREE_QUARTER_IN_DEGREE; + DrawArc::GetInstance()->Draw(arcInfo, dirtyRect, arcStyle, opaScale, CapType::CAP_NONE); + // draw bottom right sector in rectangle + arcInfo.center = {col4X, row4Y}; + arcInfo.startAngle = QUARTER_IN_DEGREE; + arcInfo.endAngle = SEMICIRCLE_IN_DEGREE; + DrawArc::GetInstance()->Draw(arcInfo, dirtyRect, arcStyle, opaScale, CapType::CAP_NONE); + + OpacityType opa = DrawUtils::GetMixOpacity(opaScale, style.borderOpa_); + // top rectangle in border + Rect topBorderRect(col3X, row1Y, col4X - 1, row2Y); + DrawUtils::GetInstance()->DrawColorArea(topBorderRect, dirtyRect, style.borderColor_, opa); + + OpacityType opaBg = DrawUtils::GetMixOpacity(opaScale, style.bgOpa_); + // top rectangle inner + Rect topInnerRect(col3X, row2Y + 1, col4X - 1, row3Y); + DrawUtils::GetInstance()->DrawColorArea(topInnerRect, dirtyRect, style.bgColor_, opaBg); + + // left rectangle in border + Rect leftBorderRect(col1X, row3Y + 1, col2X, row4Y - 1); + DrawUtils::GetInstance()->DrawColorArea(leftBorderRect, dirtyRect, style.borderColor_, opa); + + // middle rectangle inner + Rect middleInnerRect(col2X + 1, row3Y + 1, col5X - 1, row4Y - 1); + DrawUtils::GetInstance()->DrawColorArea(middleInnerRect, dirtyRect, style.bgColor_, opaBg); + + // right rectangle in border + Rect rightBorderRect(col5X, row3Y + 1, col6X, row4Y - 1); + DrawUtils::GetInstance()->DrawColorArea(rightBorderRect, dirtyRect, style.borderColor_, opa); + + // bottom rectangle inner + Rect bottomInnerRect(col3X + 1, row4Y, col4X - 1, row5Y - 1); + DrawUtils::GetInstance()->DrawColorArea(bottomInnerRect, dirtyRect, style.bgColor_, opaBg); + + // bottom rectangle in border + Rect bottomBorderRect(col3X + 1, row5Y, col4X - 1, row6Y); + DrawUtils::GetInstance()->DrawColorArea(bottomBorderRect, dirtyRect, style.borderColor_, opa); +} +} // namespace OHOS \ No newline at end of file diff --git a/frameworks/draw/draw_rect.h b/frameworks/draw/draw_rect.h new file mode 100755 index 0000000..8ee480b --- /dev/null +++ b/frameworks/draw/draw_rect.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GRAPHIC_LITE_DRAW_RECT_H +#define GRAPHIC_LITE_DRAW_RECT_H + +#include "components/ui_view.h" + +namespace OHOS { +/** @brief Class for drawing rectangle. */ +class DrawRect : public HeapBase { +public: + DrawRect() {}; + ~DrawRect() {}; + + static void Draw(const Rect& rect, const Rect& dirtyRect, const Style& style, OpacityType opaScale); + +private: + static void DrawRectRadiusWithoutBorder(const Rect& rect, const Rect& dirtyRect, const Style& style, + OpacityType opaScale); + static void DrawRectBorderWithoutRadius(const Rect& rect, const Rect& dirtyRect, const Style& style, + OpacityType opaScale); + static void DrawRectRadiusEqualBorder(const Rect& rect, const Rect& dirtyRect, const Style& style, + OpacityType opaScale); + static void DrawRectRadiusSmallThanBorder(const Rect& rect, const Rect& dirtyRect, const Style& style, + OpacityType opaScale); + static void DrawRectRadiusBiggerThanBorder(const Rect& rect, const Rect& dirtyRect, const Style& style, + OpacityType opaScale); + + static void DrawRectRadiusWithoutBorderCon1(const Rect& rect, const Rect& dirtyRect, const Style& style, + OpacityType opaScale); + static void DrawRectRadiusWithoutBorderCon2(const Rect& rect, const Rect& dirtyRect, const Style& style, + OpacityType opaScale); + static void DrawRectRadiusWithoutBorderCon3(const Rect& rect, const Rect& dirtyRect, const Style& style, + OpacityType opaScale); + static void DrawRectRadiusWithoutBorderCon4(const Rect& rect, const Rect& dirtyRect, const Style& style, + OpacityType opaScale); + + static void DrawRectRadiusBiggerThanBorderCon1(const Rect& rect, const Rect& dirtyRect, const Style& style, + OpacityType opaScale); + static void DrawRectRadiusBiggerThanBorderCon2(const Rect& rect, const Rect& dirtyRect, const Style& style, + OpacityType opaScale); + static void DrawRectRadiusBiggerThanBorderCon3(const Rect& rect, const Rect& dirtyRect, const Style& style, + OpacityType opaScale); + static void DrawRectRadiusBiggerThanBorderCon4(const Rect& rect, const Rect& dirtyRect, const Style& style, + OpacityType opaScale); +}; +} // namespace OHOS +#endif // GRAPHIC_LITE_DRAW_RECT_H diff --git a/frameworks/draw/draw_triangle.cpp b/frameworks/draw/draw_triangle.cpp new file mode 100755 index 0000000..950cb5f --- /dev/null +++ b/frameworks/draw/draw_triangle.cpp @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "draw/draw_triangle.h" +#include "draw/draw_utils.h" + +namespace OHOS { +void DrawTriangle::Draw(const Point* points, uint8_t count, const Rect& mask, const ColorType& color, OpacityType opa) +{ + if ((points == nullptr) || (count != VERTEX_NUM)) { + return; + } + // sort vertex according to y axis + Point p1 = points[0]; // 0: point index + Point p2 = points[1]; // 1: point index + Point p3 = points[2]; // 2: point index + // return if vertexs are invalid. + if ((p1.x == p2.x) && ((p1.y == p2.y) || (p1.x == p3.x))) { + return; + } + if ((p2.x == p3.x) && (p2.y == p3.y)) { + return; + } + if (((p1.x == p3.x) || (p1.y == p2.y)) && (p1.y == p3.y)) { + return; + } + SortVertexs(p1, p2, p3); + Edge edge1 = InitEdge(p1, p2); + Edge edge2 = InitEdge(p1, p3); + Rect area; + int16_t lastY = p1.y; + + while (edge1.curPoint.y <= p3.y) { + // change edge1 from p1-p2 to p2-p3 + if (edge1.curPoint.y == p2.y) { + edge1 = InitEdge(p2, p3); + if (edge1.dPoint.y == 0) { + return; + } + } + + area.SetLeft(MATH_MIN(edge1.curPoint.x, edge2.curPoint.x)); + area.SetRight(MATH_MAX(edge1.curPoint.x, edge2.curPoint.x)); + area.SetTop(MATH_MIN(edge1.curPoint.y, edge2.curPoint.y)); + area.SetBottom(MATH_MAX(edge1.curPoint.y, edge2.curPoint.y)); + DrawUtils::GetInstance()->DrawColorArea(area, mask, color, opa); + + while (edge1.curPoint.y == lastY) { + // use Bresenham algorithm to get next point on edge1 + StepToNextPointOnEdge(edge1); + } + while (edge2.curPoint.y == lastY) { + // use Bresenham algorithm to get next point on edge2 + StepToNextPointOnEdge(edge2); + } + lastY = edge1.curPoint.y; + } +} + +void DrawTriangle::SortVertexs(Point& p1, Point& p2, Point& p3) +{ + SortPoint(p1, p2); + SortPoint(p2, p3); + SortPoint(p1, p2); +} + +void DrawTriangle::SortPoint(Point& p1, Point& p2) +{ + Point temp; + if (p1.y > p2.y) { + temp = p1; + p1 = p2; + p2 = temp; + } +} + +void DrawTriangle::StepToNextPointOnEdge(Edge& edge) +{ + if (edge.dPoint.x > edge.dPoint.y) { + edge.curPoint.x += edge.uPoint.x; + edge.eps += edge.dPoint.y; + if ((edge.eps << 1) >= edge.dPoint.x) { + edge.curPoint.y += edge.uPoint.y; + edge.eps -= edge.dPoint.x; + } + } else { + edge.curPoint.y += edge.uPoint.y; + edge.eps += edge.dPoint.x; + if ((edge.eps << 1) >= edge.dPoint.y) { + edge.curPoint.x += edge.uPoint.x; + edge.eps -= edge.dPoint.y; + } + } +} + +DrawTriangle::Edge DrawTriangle::InitEdge(const Point& startP, const Point& endP) +{ + Edge edge = { {0, 0}, {0, 0}, {0, 0}, 0 }; + edge.curPoint = startP; + edge.dPoint.x = startP.x - endP.x; + edge.dPoint.y = startP.y - endP.y; + edge.uPoint.x = (edge.dPoint.x < 0) ? 1 : -1; + edge.uPoint.y = (edge.dPoint.y < 0) ? 1 : -1; + edge.eps = 0; + edge.dPoint.x = MATH_ABS(edge.dPoint.x); + edge.dPoint.y = MATH_ABS(edge.dPoint.y); + return edge; +} +} // namespace OHOS \ No newline at end of file diff --git a/frameworks/draw/draw_triangle.h b/frameworks/draw/draw_triangle.h new file mode 100755 index 0000000..e00cbb9 --- /dev/null +++ b/frameworks/draw/draw_triangle.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GRAPHIC_LITE_DRAW_TRIANGLE_H +#define GRAPHIC_LITE_DRAW_TRIANGLE_H + +#include "color.h" +#include "geometry2d.h" + +namespace OHOS { +class DrawTriangle : public HeapBase { +public: + static void Draw(const Point* points, uint8_t count, const Rect& mask, const ColorType& color, OpacityType opa); + + struct Edge { + Point curPoint; + Point dPoint; + Point uPoint; + int16_t eps; + }; + + static void SortVertexs(Point& p1, Point& p2, Point& p3); + + static void SortPoint(Point& p1, Point& p2); + + static void StepToNextPointOnEdge(Edge& edge); + + static Edge InitEdge(const Point& startP, const Point& endP); +private: + static constexpr uint8_t VERTEX_NUM = 3; +}; +} // namespace OHOS +#endif // GRAPHIC_LITE_DRAW_TRIANGLE_H diff --git a/frameworks/draw/draw_utils.cpp b/frameworks/draw/draw_utils.cpp new file mode 100644 index 0000000..b19a59d --- /dev/null +++ b/frameworks/draw/draw_utils.cpp @@ -0,0 +1,1643 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "draw/draw_utils.h" +#include "color.h" +#include "draw/draw_triangle.h" +#include "font/ui_font.h" +#include "font/ui_font_header.h" +#include "graphic_log.h" +#include "graphic_math.h" +#include "graphic_performance.h" +#include "securec.h" + +#ifdef ARM_NEON_OPT +#include "graphic_neon_pipeline.h" +#include "graphic_neon_utils.h" +#endif + +#if ENABLE_GFX_ENGINES +#include "hals/gfx_engines.h" +#endif + +#ifdef _LITEOS +#include "arm_math.h" +#endif + +namespace OHOS { +// Preprocess operation for draw +#define DRAW_UTILS_PREPROCESS(opa) \ + if ((opa) == OPA_TRANSPARENT) { \ + return; \ + } \ + uint8_t* screenBuffer = ScreenDeviceProxy::GetInstance()->GetBuffer(); \ + if (screenBuffer == nullptr) { \ + return; \ + } \ + ColorMode bufferMode = ScreenDeviceProxy::GetInstance()->GetBufferMode(); \ + uint8_t bufferPxSize = GetByteSizeByColorMode(bufferMode); \ + uint16_t screenBufferWidth = ScreenDeviceProxy::GetInstance()->GetBufferWidth(); \ + Rect bufferRect = ScreenDeviceProxy::GetInstance()->GetBufferRect(); + +/* cover mode, src alpha is 255 */ +#define COLOR_FILL_COVER(d, dm, r2, g2, b2, sm) \ + if ((dm) == ARGB8888) { \ + reinterpret_cast(d)->alpha = OPA_OPAQUE; \ + if (sm == RGB565) { \ + reinterpret_cast(d)->red = (r2) << 3; \ + reinterpret_cast(d)->green = (g2) << 2; \ + reinterpret_cast(d)->blue = (b2) << 3; \ + } else { \ + reinterpret_cast(d)->red = (r2); \ + reinterpret_cast(d)->green = (g2); \ + reinterpret_cast(d)->blue = (b2); \ + } \ + } else if ((dm) == RGB888) { \ + if (sm == RGB565) { \ + reinterpret_cast(d)->red = (r2) << 3; \ + reinterpret_cast(d)->green = (g2) << 2; \ + reinterpret_cast(d)->blue = (b2) << 3; \ + } else { \ + reinterpret_cast(d)->red = (r2); \ + reinterpret_cast(d)->green = (g2); \ + reinterpret_cast(d)->blue = (b2); \ + } \ + } else if ((dm) == RGB565) { \ + if ((sm) == ARGB8888 || (sm) == RGB888) { \ + reinterpret_cast(d)->red = (r2) >> 3; \ + reinterpret_cast(d)->green = (g2) >> 2; \ + reinterpret_cast(d)->blue = (b2) >> 3; \ + } else { \ + reinterpret_cast(d)->red = (r2); \ + reinterpret_cast(d)->green = (g2); \ + reinterpret_cast(d)->blue = (b2); \ + } \ + } else { \ + ASSERT(0); \ + } + +#define COLOR_BLEND_RGBA(r1, g1, b1, a1, r2, g2, b2, a2) \ + const float A1 = static_cast(a1) / OPA_OPAQUE; \ + const float A2 = static_cast(a2) / OPA_OPAQUE; \ + const float a = 1 - (1 - A1) * (1 - A2); \ + (r1) = (A2 * (r2) + (1 - A2) * A1 * (r1)) / a; \ + (g1) = (A2 * (g2) + (1 - A2) * A1 * (g1)) / a; \ + (b1) = (A2 * (b2) + (1 - A2) * A1 * (b1)) / a; \ + (a1) = a * OPA_OPAQUE; + +#define COLOR_BLEND_RGB(r1, g1, b1, r2, g2, b2, a2) \ + (r1) = (((r2) * (a2)) / OPA_OPAQUE) + (((r1) * (OPA_OPAQUE - (a2))) / OPA_OPAQUE); \ + (g1) = (((g2) * (a2)) / OPA_OPAQUE) + (((g1) * (OPA_OPAQUE - (a2))) / OPA_OPAQUE); \ + (b1) = (((b2) * (a2)) / OPA_OPAQUE) + (((b1) * (OPA_OPAQUE - (a2))) / OPA_OPAQUE); + +// 565 +#define COLOR_FILL_BLEND(d, dm, s, sm, a) \ + if ((dm) == ARGB8888) { \ + Color32* p = reinterpret_cast(d); \ + if ((sm) == ARGB8888) { \ + Color32* sTmp = reinterpret_cast(s); \ + uint8_t alpha = (sTmp->alpha * (a)) / OPA_OPAQUE; \ + COLOR_BLEND_RGBA(p->red, p->green, p->blue, p->alpha, sTmp->red, sTmp->green, sTmp->blue, alpha); \ + } else if ((sm) == RGB888) { \ + Color24* sTmp = reinterpret_cast(s); \ + COLOR_BLEND_RGBA(p->red, p->green, p->blue, p->alpha, sTmp->red, sTmp->green, sTmp->blue, a); \ + } else if ((sm) == RGB565) { \ + Color16* sTmp = reinterpret_cast(s); \ + COLOR_BLEND_RGBA(p->red, p->green, p->blue, p->alpha, (sTmp->red) << 3, (sTmp->green) << 2, \ + (sTmp->blue) << 3, a); \ + } \ + } else if ((dm) == RGB888) { \ + Color24* p = reinterpret_cast(d); \ + if ((sm) == ARGB8888) { \ + Color32* sTmp = reinterpret_cast(s); \ + uint8_t alpha = (sTmp->alpha * (a)) / OPA_OPAQUE; \ + COLOR_BLEND_RGB(p->red, p->green, p->blue, sTmp->red, sTmp->green, sTmp->blue, alpha); \ + } else if ((sm) == RGB888) { \ + Color24* sTmp = reinterpret_cast(s); \ + COLOR_BLEND_RGB(p->red, p->green, p->blue, sTmp->red, sTmp->green, sTmp->blue, a); \ + } else if ((sm) == RGB565) { \ + Color16* sTmp = reinterpret_cast(s); \ + COLOR_BLEND_RGB(p->red, p->green, p->blue, (sTmp->red) << 3, (sTmp->green) << 2, (sTmp->blue) << 3, a); \ + } \ + } else if ((dm) == RGB565) { \ + Color16* p = reinterpret_cast(d); \ + if ((sm) == ARGB8888) { \ + Color32* sTmp = reinterpret_cast(s); \ + uint8_t alpha = (sTmp->alpha * (a)) / OPA_OPAQUE; \ + COLOR_BLEND_RGB(p->red, p->green, p->blue, (sTmp->red) >> 3, (sTmp->green) >> 2, (sTmp->blue) >> 3, \ + alpha); \ + } else if ((sm) == RGB888) { \ + Color24* sTmp = reinterpret_cast(s); \ + COLOR_BLEND_RGB(p->red, p->green, p->blue, (sTmp->red) >> 3, (sTmp->green) >> 2, (sTmp->blue) >> 3, a); \ + } else if ((sm) == RGB565) { \ + Color16* sTmp = reinterpret_cast(s); \ + COLOR_BLEND_RGB(p->red, p->green, p->blue, sTmp->red, sTmp->green, sTmp->blue, a); \ + } \ + } else { \ + ASSERT(0); \ + } + +#ifdef VERSION_STANDARD +const int16_t HARDWARE_ACC_SIZE_LIMIT = 50 * 50; +#endif + +namespace { +static constexpr uint8_t OPACITY_STEP_A1 = 255; +static constexpr uint8_t OPACITY_STEP_A2 = 85; +static constexpr uint8_t OPACITY_STEP_A4 = 17; +} // namespace + +TriangleEdge::TriangleEdge(int16_t x1, int16_t y1, int16_t x2, int16_t y2) +{ + curX = static_cast(x1); + curY = static_cast(y1); + du = static_cast(x2 - x1); + dv = static_cast(y2 - y1); +} + +TriangleEdge::~TriangleEdge() {} + +void DrawUtils::DrawColorAreaBySides(const Rect& mask, + const ColorType& color, + OpacityType opa, + const EdgeSides& sides) const +{ + Rect area(sides.left, sides.top, sides.right, sides.bottom); + DrawUtils::GetInstance()->DrawColorArea(area, mask, color, opa); +} + +void DrawUtils::DrawColorArea(const Rect& area, const Rect& mask, const ColorType& color, OpacityType opa) const +{ + DRAW_UTILS_PREPROCESS(opa); + Rect maskedArea; + if (!maskedArea.Intersect(area, mask)) { + return; + } +#if !ENABLE_WINDOW + maskedArea.SetRect(maskedArea.GetLeft() - bufferRect.GetLeft(), maskedArea.GetTop() - bufferRect.GetTop(), + maskedArea.GetRight() - bufferRect.GetLeft(), maskedArea.GetBottom() - bufferRect.GetTop()); +#endif +#if ENABLE_HARDWARE_ACCELERATION + if (ScreenDeviceProxy::GetInstance()->HardwareFill(maskedArea, Color::ColorTo32(color), opa, screenBuffer, + screenBufferWidth * bufferPxSize, bufferMode)) { + return; + } +#endif +#if ENABLE_GFX_ENGINES + if (FillAreaWithHardware(maskedArea, color, opa)) { + return; + } +#endif + FillAreaWithSoftWare(maskedArea, screenBuffer, bufferMode, bufferPxSize, screenBufferWidth, color, opa); +} + +uint8_t DrawUtils::GetPxSizeByColorMode(uint8_t colorMode) +{ + switch (colorMode) { + case ARGB8888: + return 32; // 32: 32 bit + case RGB888: + return 24; // 24: 24 bit + case RGB565: + case ARGB1555: + case ARGB4444: + return 16; // 16: 16 bit + case L1: + case A1: + return 1; // 1: 1 bit + case L2: + case A2: + return 2; // 2: 2 bit + case L4: + case A4: + return 4; // 4: 4 bit + case L8: + case A8: + return 8; // 8: 8 bit + default: + return 0; + } +} + +uint8_t DrawUtils::GetByteSizeByColorMode(uint8_t colorMode) +{ + switch (colorMode) { + case ARGB8888: + return 4; // 4: 4 Byte + case RGB888: + return 3; // 3: 3 Byte + case RGB565: + case ARGB1555: + case ARGB4444: + return 2; // 2: 2 Byte + default: + return 0; + } +} + +uint8_t DrawUtils::GetPxSizeByImageInfo(ImageInfo imageInfo) +{ + if ((imageInfo.header.width == 0) || (imageInfo.header.height == 0)) { + return 0; + } + /* 3 : when change byte to single pixel, the buffer should multiply by 8, equal to shift left 3 bits. */ + uint8_t pxSize = (imageInfo.dataSize / (imageInfo.header.width * imageInfo.header.height)) << 3; + return pxSize; +} + +void DrawUtils::DrawPixel(int16_t x, int16_t y, const Rect& mask, const ColorType& color, OpacityType opa) const +{ + if ((x < mask.GetLeft()) || (x > mask.GetRight()) || (y < mask.GetTop()) || (y > mask.GetBottom())) { + return; + } + + DRAW_UTILS_PREPROCESS(opa); + +#if !ENABLE_WINDOW + x -= bufferRect.GetLeft(); + y -= bufferRect.GetTop(); +#endif + + Color32 fillColor; + fillColor.full = Color::ColorTo32(color); + + screenBuffer += (y * screenBufferWidth + x) * bufferPxSize; + COLOR_FILL_BLEND(screenBuffer, bufferMode, &fillColor, ARGB8888, opa); +} + +void DrawUtils::DrawLetter(const LabelLetterInfo& letterInfo) const +{ + OpacityType opa = letterInfo.opa; + Color32 fillColor; + fillColor.full = Color::ColorTo32(letterInfo.color); + + DRAW_UTILS_PREPROCESS(opa); + UIFont* fontEngine = UIFont::GetInstance(); + FontHeader head; + GlyphNode node; + if (fontEngine->GetCurrentFontHeader(head) != 0) { + return; + } + + const uint8_t* fontMap = fontEngine->GetBitmap(letterInfo.letter, node, letterInfo.shapingId); + if (fontMap == nullptr) { + return; + } + uint16_t letterW = node.cols; + uint16_t letterH = node.rows; + uint8_t opacityMask; + int16_t posX; + int16_t posY = letterInfo.pos.y + letterInfo.fontSize - node.top - letterInfo.offsetY; + uint8_t fontWeight = fontEngine->GetFontWeight(letterInfo.fontId); + uint8_t colorMode = 0; + uint8_t opacityStep = 1; + switch (fontWeight) { + case FONT_WEIGHT_1: + opacityStep = OPACITY_STEP_A1; + opacityMask = 0x01; + colorMode = A1; + break; + case FONT_WEIGHT_2: + opacityStep = OPACITY_STEP_A2; + opacityMask = 0x03; + colorMode = A2; + break; + case FONT_WEIGHT_4: + opacityStep = OPACITY_STEP_A4; + opacityMask = 0x0F; + colorMode = A4; + break; + case FONT_WEIGHT_8: + opacityMask = 0xFF; + colorMode = A8; + break; + default: + return; + } + + if (letterInfo.direct == TEXT_DIRECT_RTL) { + /* RTL */ + posX = letterInfo.pos.x - node.advance + node.left + letterInfo.offsetX; + } else { + /* LTR */ + posX = letterInfo.pos.x + node.left + letterInfo.offsetX; + } + + if ((posX + letterW < letterInfo.mask.GetLeft()) || (posX > letterInfo.mask.GetRight()) || + (posY + letterH < letterInfo.mask.GetTop()) || (posY > letterInfo.mask.GetBottom())) { + return; + } + + uint16_t rowStart = (posY >= letterInfo.mask.GetTop()) ? 0 : (letterInfo.mask.GetTop() - posY); + uint16_t rowEnd = + (posY + letterH <= letterInfo.mask.GetBottom()) ? letterH : (letterInfo.mask.GetBottom() - posY + 1); + uint16_t colStart = (posX >= letterInfo.mask.GetLeft()) ? 0 : (letterInfo.mask.GetLeft() - posX); + uint16_t colEnd = + (posX + letterW <= letterInfo.mask.GetRight()) ? letterW : (letterInfo.mask.GetRight() - posX + 1); + + uint8_t letterWidthInByte = (letterW * fontWeight) >> SHIFT_3; + if ((letterW * fontWeight) & 0x7) { + letterWidthInByte++; + } + +#if ENABLE_WINDOW + int16_t dstPosX = posX + colStart; + int16_t dstPosY = posY + rowStart; +#else + int16_t dstPosX = posX + colStart - bufferRect.GetLeft(); + int16_t dstPosY = posY + rowStart - bufferRect.GetTop(); +#endif + +#if ENABLE_HARDWARE_ACCELERATION && ENABLE_HARDWARE_ACCELERATION_FOR_TEXT + Rect srcRect(colStart, rowStart, colEnd - 1, rowEnd - 1); + if (ScreenDeviceProxy::GetInstance()->HardwareBlend(fontMap, srcRect, letterWidthInByte, letterH, + static_cast(colorMode), + Color::ColorTo32(letterInfo.color), opa, reinterpret_cast(screenBuffer), + screenBufferWidth * bufferPxSize, bufferMode, dstPosX, dstPosY)) { + return; + } +#endif + + screenBuffer += ((dstPosY * screenBufferWidth) + dstPosX) * bufferPxSize; + fontMap += (rowStart * letterWidthInByte) + ((colStart * fontWeight) >> SHIFT_3); + + uint8_t offsetInFont = (colStart * fontWeight) % FONT_WEIGHT_8; + int16_t temp = (colEnd - colStart) * fontWeight - FONT_WEIGHT_8 + offsetInFont; + if (temp < 0) { + temp = 0; + } + int16_t validWidthInByte = temp / FONT_WEIGHT_8 + 1; + if (temp % FONT_WEIGHT_8 != 0) { + validWidthInByte++; + } + for (int16_t i = rowStart; i < rowEnd; i++) { + int16_t col = colStart; + uint8_t tempOffset = offsetInFont; + uint8_t tempFontByte = (*fontMap++) >> offsetInFont; + while (col < colEnd) { + while ((tempOffset < FONT_WEIGHT_8) && (col < colEnd)) { + uint8_t validOpacity = tempFontByte & opacityMask; + if (validOpacity != 0) { + validOpacity *= opacityStep; + if (opa != OPA_OPAQUE) { + validOpacity = + static_cast((static_cast(validOpacity) * opa) >> FONT_WEIGHT_8); + } + COLOR_FILL_BLEND(screenBuffer, bufferMode, &fillColor, ARGB8888, validOpacity); + } + screenBuffer += bufferPxSize; + tempFontByte = tempFontByte >> fontWeight; + tempOffset += fontWeight; + col++; + } + tempOffset = 0; + tempFontByte = *(fontMap++); + } + fontMap += (letterWidthInByte)-validWidthInByte - 1; + screenBuffer += (screenBufferWidth - (colEnd - colStart)) * bufferPxSize; + } +} + +void DrawUtils::DrawImage(const Rect& area, + const Rect& mask, + const uint8_t* image, + OpacityType opa, + uint8_t pxByteSize) const +{ + if (image == nullptr) { + return; + } + DRAW_UTILS_PREPROCESS(opa); + Rect maskedArea; + Rect originMaskedArea; + if (!originMaskedArea.Intersect(area, mask)) { + return; + } +#if ENABLE_WINDOW + maskedArea = originMaskedArea; +#else + maskedArea.SetRect( + originMaskedArea.GetLeft() - bufferRect.GetLeft(), originMaskedArea.GetTop() - bufferRect.GetTop(), + originMaskedArea.GetRight() - bufferRect.GetLeft(), originMaskedArea.GetBottom() - bufferRect.GetTop()); +#endif + int16_t mapWidth = area.GetWidth(); + int16_t imageX = originMaskedArea.GetLeft() - area.GetLeft(); + int16_t imageY = originMaskedArea.GetTop() - area.GetTop(); + + ColorMode srcMode; + if (pxByteSize == static_cast(PixelType::IMG_RGB888)) { + srcMode = RGB888; + } else if (pxByteSize == static_cast(PixelType::IMG_RGB565)) { + srcMode = RGB565; + } else if (pxByteSize == static_cast(PixelType::IMG_ARGB8888)) { + srcMode = ARGB8888; + } else { + GRAPHIC_LOGE("DrawUtils::DrawImage image format err\n"); + return; + } +#if ENABLE_HARDWARE_ACCELERATION + Rect srcRect(imageX, imageY, imageX + maskedArea.GetWidth() - 1, imageY + maskedArea.GetHeight() - 1); + if (ScreenDeviceProxy::GetInstance()->HardwareBlend(image, srcRect, mapWidth * pxByteSize, area.GetHeight(), + srcMode, 0, opa, screenBuffer, screenBufferWidth * bufferPxSize, bufferMode, maskedArea.GetLeft(), + maskedArea.GetTop())) { + return; + } +#endif + screenBuffer += static_cast(screenBufferWidth) * maskedArea.GetTop() * bufferPxSize; + screenBuffer += static_cast(maskedArea.GetLeft()) * bufferPxSize; + + image += (static_cast(mapWidth) * imageY + imageX) * pxByteSize; + /* RGB565 RGB888 color mode, image src don't have alpha */ + BlendWithSoftWare(image, mapWidth * pxByteSize, srcMode, screenBuffer, screenBufferWidth * bufferPxSize, bufferMode, + maskedArea.GetWidth(), maskedArea.GetHeight(), opa); +} + +#if ENABLE_GFX_ENGINES +bool DrawUtils::FillAreaWithHardware(const Rect& fillArea, const ColorType& color, const OpacityType& opa) const +{ + if ((opa != OPA_OPAQUE) && (fillArea.GetSize() >= HARDWARE_ACC_SIZE_LIMIT)) { + AllocationInfo gfxAlloc = ScreenDeviceProxy::GetInstance()->GetAllocationInfo(); + LiteSurfaceData data; + data.phyAddr = gfxAlloc.phyAddr; + data.width = gfxAlloc.width; + data.height = gfxAlloc.height; + data.stride = gfxAlloc.stride; + data.pixelFormat = gfxAlloc.pixelFormat; + if (GfxEngines::GetInstance()->GfxFillArea(data, fillArea, color, opa)) { + return true; + } + } + return false; +} +#endif + +void DrawUtils::FillAreaWithSoftWare(const Rect& fillArea, + uint8_t* dest, + ColorMode mode, + uint8_t destByteSize, + int16_t destWidth, + const ColorType& color, + const OpacityType& opa) const +{ + int32_t halBufferDeltaByteLen = static_cast(destWidth) * destByteSize; + int16_t width = fillArea.GetWidth(); + int16_t height = fillArea.GetHeight(); + int32_t offset = static_cast(fillArea.GetTop()) * destWidth + fillArea.GetLeft(); + dest += offset * destByteSize; + int32_t dstMaxSize = (ScreenDeviceProxy::GetInstance()->GetScreenArea() - offset) * destByteSize; + Color32 fillColor; + fillColor.full = Color::ColorTo32(color); + uint8_t* dstTmp = nullptr; + + if ((fillColor.alpha == OPA_TRANSPARENT) || (opa == OPA_TRANSPARENT)) { + return; + } + /* cover mode */ + if ((opa == OPA_OPAQUE) && (fillColor.alpha == OPA_OPAQUE)) { + for (int16_t col = 0; col < width; ++col) { + dstTmp = dest + (col * destByteSize); + COLOR_FILL_COVER(dstTmp, mode, fillColor.red, fillColor.green, fillColor.blue, ARGB8888); + } + uint8_t* memStart = dest; + int32_t memSize = static_cast(width) * destByteSize; + dest += destWidth * destByteSize; + dstMaxSize -= halBufferDeltaByteLen; + for (int16_t row = 1; row < height; ++row) { +#ifdef ARM_NEON_OPT + { + DEBUG_PERFORMANCE_TRACE("memcpy_neon"); + NeonMemcpy(dest, dstMaxSize, memStart, memSize); + } +#else + { + DEBUG_PERFORMANCE_TRACE("memcpy"); + if (memcpy_s(dest, dstMaxSize, memStart, memSize) != EOK) { + GRAPHIC_LOGE("DrawUtils::FillAreaWithSoftWare memcpy failed!\n"); + return; + } + } +#endif + dest += destWidth * destByteSize; + dstMaxSize -= halBufferDeltaByteLen; + } + } else { +#ifdef ARM_NEON_OPT + { + DEBUG_PERFORMANCE_TRACE("FillAreaWithSoftWare_neon"); + NeonBlendPipeLine pipeLine; + pipeLine.Construct(mode, ARGB8888, &fillColor, opa); + int16_t step = NEON_STEP_8 * GetByteSizeByColorMode(mode); + for (int16_t row = 0; row < height; ++row) { + uint8_t* buf = dest; + int16_t tmpWidth = width; + while (tmpWidth >= NEON_STEP_8) { + pipeLine.Invoke(buf); + buf += step; + tmpWidth -= NEON_STEP_8; + } + for (int16_t i = 0; i < tmpWidth; ++i) { + COLOR_FILL_BLEND(buf, mode, &fillColor, ARGB8888, opa); + buf += destByteSize; + } + dest += halBufferDeltaByteLen; + } + } +#else + { + DEBUG_PERFORMANCE_TRACE("FillAreaWithSoftWare"); + for (int16_t row = 0; row < height; row++) { + for (int16_t col = 0; col < width; col++) { + dstTmp = dest + (col * destByteSize); + COLOR_FILL_BLEND(dstTmp, mode, &fillColor, ARGB8888, opa); + } + dest += destWidth * destByteSize; + } + } +#endif + } +} + +void DrawUtils::BlendWithSoftWare(const uint8_t* src, + uint32_t srcStride, + ColorMode srcMode, + uint8_t* dest, + uint32_t destStride, + ColorMode destMode, + uint32_t width, + uint32_t height, + OpacityType opa) const +{ + uint8_t destByteSize = GetByteSizeByColorMode(destMode); + uint8_t srcByteSize = GetByteSizeByColorMode(srcMode); +#ifdef ARM_NEON_OPT + { + DEBUG_PERFORMANCE_TRACE("BlendWithSoftWare_neon"); + NeonBlendPipeLine pipeLine; + pipeLine.Construct(destMode, srcMode); + int16_t dstStep = NEON_STEP_8 * GetByteSizeByColorMode(destMode); + int16_t srcStep = NEON_STEP_8 * GetByteSizeByColorMode(srcMode); + for (uint32_t row = 0; row < height; ++row) { + uint8_t* dstBuf = dest; + uint8_t* srcBuf = const_cast(src); + int16_t tmpWidth = width; + while (tmpWidth >= NEON_STEP_8) { + pipeLine.Invoke(dstBuf, srcBuf, opa); + dstBuf += dstStep; + srcBuf += srcStep; + tmpWidth -= NEON_STEP_8; + } + for (int16_t i = 0; i < tmpWidth; ++i) { + COLOR_FILL_BLEND(dstBuf, destMode, srcBuf, srcMode, opa); + dstBuf += destByteSize; + srcBuf += srcByteSize; + } + dest += destStride; + src += srcStride; + } + } +#else + { + DEBUG_PERFORMANCE_TRACE("BlendWithSoftWare"); + for (uint32_t row = 0; row < height; ++row) { + uint8_t* destTmp = dest; + uint8_t* srcTmp = const_cast(src); + for (uint32_t col = 0; col < width; ++col) { + COLOR_FILL_BLEND(destTmp, destMode, srcTmp, srcMode, opa); + destTmp += destByteSize; + srcTmp += srcByteSize; + } + dest += destStride; + src += srcStride; + } + } +#endif +} + +void DrawUtils::GetXAxisErrForJunctionLine(bool ignoreJunctionPoint, + bool isRightPart, + int32_t& xMinErr, + int32_t& xMaxErr) +{ + xMinErr = 0; + xMaxErr = 0; + if (ignoreJunctionPoint) { + if (isRightPart) { + xMinErr = 1; + } else { + xMaxErr = -1; + } + } +} + +void DrawUtils::GetTransformInitState(const TransformMap& transMap, + const Point& position, + const Rect& trans, + TransformInitState& init) +{ + int16_t x = trans.GetLeft(); + int16_t y = trans.GetTop(); + + init.duHorizon = transMap.invMatrix_.GetData()[0]; + init.dvHorizon = transMap.invMatrix_.GetData()[1]; + init.duVertical = transMap.invMatrix_.GetData()[3]; // 3:RSxy + init.dvVertical = transMap.invMatrix_.GetData()[4]; // 4:RSyy + + init.verticalU = (x - position.x) * init.duHorizon + (y - position.y) * init.duVertical + + transMap.invMatrix_.GetData()[6]; // 6:TRSx + init.verticalV = (x - position.x) * init.dvHorizon + (y - position.y) * init.dvVertical + + transMap.invMatrix_.GetData()[7]; // 7:TRSy +} + +inline void DrawUtils::StepToNextLine(TriangleEdge& edge1, TriangleEdge& edge2) +{ + edge1.curY++; + edge2.curY++; + edge1.curX += edge1.du / edge1.dv; + edge2.curX += edge2.du / edge2.dv; +} + +void DrawUtils::DrawTriangleAlphaBilinear(const TriangleScanInfo& in) +{ + int32_t maskLeft = in.mask.GetLeft(); + int32_t maskRight = in.mask.GetRight(); + ColorMode bufferMode = ScreenDeviceProxy::GetInstance()->GetBufferMode(); + for (int16_t y = in.yMin; y <= in.yMax; y++) { + int16_t xMin = MATH_MAX(static_cast(in.edge1.curX), maskLeft); + int16_t xMax = MATH_MIN(static_cast(in.edge2.curX), maskRight); + int16_t diffX = (xMin - static_cast(in.edge1.curX)); + in.init.verticalU += in.init.duHorizon * diffX; + in.init.verticalV += in.init.dvHorizon * diffX; +#if ENABLE_WINDOW + uint8_t* screenBuffer = in.screenBuffer + (y * in.screenBufferWidth + xMin) * in.bufferPxSize; +#else + uint8_t* screenBuffer = + in.screenBuffer + + (((y - in.screenBufferRect.GetTop()) * in.screenBufferWidth + (xMin - in.screenBufferRect.GetLeft())) * + in.bufferPxSize); +#endif + float u = in.init.verticalU; + float v = in.init.verticalV; + for (int16_t x = xMin; x <= xMax; x++) { + const int16_t intU = static_cast(u); + const int16_t intV = static_cast(v); + if ((u >= 0) && (intU < in.info.header.width - 1) && (v >= 0) && (intV < in.info.header.height - 1)) { + const int16_t intUPlus1 = intU + 1; + const int16_t intVPlus1 = intV + 1; + OpacityType p1 = GetPxAlphaForAlphaImg(in.info, {intU, intV}); + OpacityType p2 = GetPxAlphaForAlphaImg(in.info, {intUPlus1, intV}); + OpacityType p3 = GetPxAlphaForAlphaImg(in.info, {intU, intVPlus1}); + OpacityType p4 = GetPxAlphaForAlphaImg(in.info, {intUPlus1, intVPlus1}); + + const float decU = u - intU; + const float decV = v - intV; + const float decUMinus1 = 1.0f - decU; + const float decVMinus1 = 1.0f - decV; + + const int32_t w1 = static_cast(decUMinus1 * decVMinus1 * 256.0f); // 256:shift 8 bit left + const int32_t w2 = static_cast(decU * decVMinus1 * 256.0f); // 256:shift 8 bit left + const int32_t w3 = static_cast(decUMinus1 * decV * 256.0f); // 256:shift 8 bit left + const int32_t w4 = static_cast(decU * decV * 256.0f); // 256:shift 8 bit left + +#ifndef _LITEOS + const int32_t outA = p1 * w1 + p2 * w2 + p3 * w3 + p4 * w4; +#else + const int32_t outA = __SMUAD(p1, w1) + __SMUAD(p2, w2) + __SMUAD(p3, w3) + __SMUAD(p4, w4); +#endif + Color32 result; + result.full = Color::ColorTo32(in.color); + result.alpha = static_cast(outA >> 8); // 8:shift 8 bit right + COLOR_FILL_BLEND(screenBuffer, bufferMode, &result, ARGB8888, in.opaScale); + } + u += in.init.duHorizon; + v += in.init.dvHorizon; + screenBuffer += in.bufferPxSize; + } + StepToNextLine(in.edge1, in.edge2); + in.init.verticalU += in.init.duVertical; + in.init.verticalV += in.init.dvVertical; + int16_t deltaX = static_cast(in.edge1.curX) - xMin; + in.init.verticalU += in.init.duHorizon * deltaX; + in.init.verticalV += in.init.dvHorizon * deltaX; + } +} + +void DrawUtils::DrawTriangleTrueColorBilinear565(const TriangleScanInfo& in) +{ + ColorMode bufferMode = ScreenDeviceProxy::GetInstance()->GetBufferMode(); + for (int16_t y = in.yMin; y <= in.yMax; y++) { + int16_t xMin = static_cast(in.edge1.curX); + int16_t xMax = static_cast(in.edge2.curX); +#if ENABLE_WINDOW + uint8_t* screenBuffer = in.screenBuffer + (y * in.screenBufferWidth + xMin) * in.bufferPxSize; +#else + uint8_t* screenBuffer = + in.screenBuffer + + (((y - in.screenBufferRect.GetTop()) * in.screenBufferWidth + (xMin - in.screenBufferRect.GetLeft())) * + in.bufferPxSize); +#endif + float u = in.init.verticalU; + float v = in.init.verticalV; + for (int16_t x = xMin; x <= xMax; x++) { + const int16_t intU = static_cast(u); + const int16_t intV = static_cast(v); + if ((u >= 0) && (intU < in.info.header.width - 1) && (v >= 0) && (intV < in.info.header.height - 1)) { +#ifndef _LITEOS + uint32_t px1 = intV * in.srcLineWidth + intU * in.pixelSize; +#else + uint32_t val1 = __SMUAD(intV, in.srcLineWidth); + uint32_t val2 = __SMUAD(intU, in.pixelSize); + uint32_t px1 = val1 + val2; +#endif + uint8_t* imgHead = const_cast(in.info.data); + const Color16 p1 = *(reinterpret_cast(&imgHead[px1])); + const Color16 p2 = *(reinterpret_cast(&imgHead[px1 + in.pixelSize])); + const Color16 p3 = *(reinterpret_cast(&imgHead[px1 + in.srcLineWidth])); + const Color16 p4 = *(reinterpret_cast(&imgHead[px1 + in.srcLineWidth + in.pixelSize])); + + const float decU = u - intU; + const float decV = v - intV; + const float decUMinus1 = 1 - decU; + const float decVMinus1 = 1 - decV; + + const int32_t w1 = static_cast(decUMinus1 * decVMinus1 * 256.0f); // 256:shift 8 bit left + const int32_t w2 = static_cast(decU * decVMinus1 * 256.0f); // 256:shift 8 bit left + const int32_t w3 = static_cast(decUMinus1 * decV * 256.0f); // 256:shift 8 bit left + const int32_t w4 = static_cast(decU * decV * 256.0f); // 256:shift 8 bit left +#ifndef _LITEOS + const int32_t outR = p1.red * w1 + p2.red * w2 + p3.red * w3 + p4.red * w4; + const int32_t outG = p1.green * w1 + p2.green * w2 + p3.green * w3 + p4.green * w4; + const int32_t outB = p1.blue * w1 + p2.blue * w2 + p3.blue * w3 + p4.blue * w4; +#else + const int32_t outR = + __SMUAD(p1.red, w1) + __SMUAD(p2.red, w2) + __SMUAD(p3.red, w3) + __SMUAD(p4.red, w4); + const int32_t outG = + __SMUAD(p1.green, w1) + __SMUAD(p2.green, w2) + __SMUAD(p3.green, w3) + __SMUAD(p4.green, w4); + const int32_t outB = + __SMUAD(p1.blue, w1) + __SMUAD(p2.blue, w2) + __SMUAD(p3.blue, w3) + __SMUAD(p4.blue, w4); +#endif + + Color16 result; + result.red = static_cast(outR >> 5); // 5:shift 5 bit right + result.green = static_cast(outG >> 6); // 6:shift 6 bit right + result.blue = static_cast(outB >> 5); // 5:shift 5 bit right + if (in.opaScale == OPA_OPAQUE) { + COLOR_FILL_COVER(screenBuffer, bufferMode, result.red, result.green, result.blue, RGB565); + } else { + COLOR_FILL_BLEND(screenBuffer, bufferMode, &result, RGB565, in.opaScale); + } + } + u += in.init.duHorizon; + v += in.init.dvHorizon; + screenBuffer += in.bufferPxSize; + } + StepToNextLine(in.edge1, in.edge2); + in.init.verticalU += in.init.duVertical; + in.init.verticalV += in.init.dvVertical; + int16_t deltaX = static_cast(in.edge1.curX) - xMin; + in.init.verticalU += in.init.duHorizon * deltaX; + in.init.verticalV += in.init.dvHorizon * deltaX; + } +} + +void DrawUtils::DrawTriangleTrueColorBilinear888(const TriangleScanInfo& in) +{ + ColorMode bufferMode = ScreenDeviceProxy::GetInstance()->GetBufferMode(); + for (int16_t y = in.yMin; y <= in.yMax; y++) { + int16_t xMin = static_cast(in.edge1.curX); + int16_t xMax = static_cast(in.edge2.curX); +#if ENABLE_WINDOW + uint8_t* screenBuffer = in.screenBuffer + (y * in.screenBufferWidth + xMin) * in.bufferPxSize; +#else + uint8_t* screenBuffer = + in.screenBuffer + + (((y - in.screenBufferRect.GetTop()) * in.screenBufferWidth + (xMin - in.screenBufferRect.GetLeft())) * + in.bufferPxSize); +#endif + float u = in.init.verticalU; + float v = in.init.verticalV; + for (int16_t x = xMin; x <= xMax; x++) { + const int16_t intU = static_cast(u); + const int16_t intV = static_cast(v); + if ((u >= 0) && (intU < in.info.header.width - 1) && (v >= 0) && (intV < in.info.header.height - 1)) { +#ifndef _LITEOS + uint32_t px1 = intV * in.srcLineWidth + intU * in.pixelSize; +#else + uint32_t val1 = __SMUAD(intV, in.srcLineWidth); + uint32_t val2 = __SMUAD(intU, in.pixelSize); + uint32_t px1 = val1 + val2; +#endif + uint8_t* imgHead = const_cast(in.info.data); + const Color24 p1 = *(reinterpret_cast(&imgHead[px1])); + const Color24 p2 = *(reinterpret_cast(&imgHead[px1 + in.pixelSize])); + const Color24 p3 = *(reinterpret_cast(&imgHead[px1 + in.srcLineWidth])); + const Color24 p4 = *(reinterpret_cast(&imgHead[px1 + in.srcLineWidth + in.pixelSize])); + + const float decU = u - intU; + const float decV = v - intV; + const float decUMinus1 = 1 - decU; + const float decVMinus1 = 1 - decV; + + const int32_t w1 = static_cast(decUMinus1 * decVMinus1 * 256.0f); // 256:shift 8 bit left + const int32_t w2 = static_cast(decU * decVMinus1 * 256.0f); // 256:shift 8 bit left + const int32_t w3 = static_cast(decUMinus1 * decV * 256.0f); // 256:shift 8 bit left + const int32_t w4 = static_cast(decU * decV * 256.0f); // 256:shift 8 bit left +#ifndef _LITEOS + const int32_t outR = p1.red * w1 + p2.red * w2 + p3.red * w3 + p4.red * w4; + const int32_t outG = p1.green * w1 + p2.green * w2 + p3.green * w3 + p4.green * w4; + const int32_t outB = p1.blue * w1 + p2.blue * w2 + p3.blue * w3 + p4.blue * w4; +#else + const int32_t outR = + __SMUAD(p1.red, w1) + __SMUAD(p2.red, w2) + __SMUAD(p3.red, w3) + __SMUAD(p4.red, w4); + const int32_t outG = + __SMUAD(p1.green, w1) + __SMUAD(p2.green, w2) + __SMUAD(p3.green, w3) + __SMUAD(p4.green, w4); + const int32_t outB = + __SMUAD(p1.blue, w1) + __SMUAD(p2.blue, w2) + __SMUAD(p3.blue, w3) + __SMUAD(p4.blue, w4); +#endif + + Color24 result; + result.red = static_cast(outR >> 8); // 8:shift 8 bit right + result.green = static_cast(outG >> 8); // 8:shift 8 bit right + result.blue = static_cast(outB >> 8); // 8:shift 8 bit right + if (in.opaScale == OPA_OPAQUE) { + COLOR_FILL_COVER(screenBuffer, bufferMode, result.red, result.green, result.blue, RGB888); + } else { + COLOR_FILL_BLEND(screenBuffer, bufferMode, &result, RGB888, in.opaScale); + } + } + u += in.init.duHorizon; + v += in.init.dvHorizon; + screenBuffer += in.bufferPxSize; + } + StepToNextLine(in.edge1, in.edge2); + in.init.verticalU += in.init.duVertical; + in.init.verticalV += in.init.dvVertical; + int16_t deltaX = static_cast(in.edge1.curX) - xMin; + in.init.verticalU += in.init.duHorizon * deltaX; + in.init.verticalV += in.init.dvHorizon * deltaX; + } +} + +static void DrawTriangleTrueColorBilinear8888Inner(const TriangleScanInfo& in, + uint8_t* screenBuffer, + int16_t len, + float u, + float v) +{ + ColorMode bufferMode = ScreenDeviceProxy::GetInstance()->GetBufferMode(); + for (int16_t x = 0; x < len; ++x) { + const int16_t intU = static_cast(u); + const int16_t intV = static_cast(v); + if ((u >= 0) && (intU < in.info.header.width - 1) && (v >= 0) && (intV < in.info.header.height - 1)) { +#ifndef _LITEOS + uint32_t px1 = intV * in.srcLineWidth + intU * in.pixelSize; +#else + uint32_t val1 = __SMUAD(intV, in.srcLineWidth); + uint32_t val2 = __SMUAD(intU, in.pixelSize); + uint32_t px1 = val1 + val2; +#endif + uint8_t* imgHead = const_cast(in.info.data); + const ColorType p1 = *(reinterpret_cast(&imgHead[px1])); + const ColorType p2 = *(reinterpret_cast(&imgHead[px1 + in.pixelSize])); + const ColorType p3 = *(reinterpret_cast(&imgHead[px1 + in.srcLineWidth])); + const ColorType p4 = *(reinterpret_cast(&imgHead[px1 + in.srcLineWidth + in.pixelSize])); + + const float decU = u - intU; + const float decV = v - intV; + const float decUMinus1 = 1 - decU; + const float decVMinus1 = 1 - decV; + + const int32_t w1 = static_cast(decUMinus1 * decVMinus1 * 256.0f); // 256:shift 8 bit left + const int32_t w2 = static_cast(decU * decVMinus1 * 256.0f); // 256:shift 8 bit left + const int32_t w3 = static_cast(decUMinus1 * decV * 256.0f); // 256:shift 8 bit left + const int32_t w4 = static_cast(decU * decV * 256.0f); // 256:shift 8 bit left +#ifndef _LITEOS + const int32_t outR = p1.red * w1 + p2.red * w2 + p3.red * w3 + p4.red * w4; + const int32_t outG = p1.green * w1 + p2.green * w2 + p3.green * w3 + p4.green * w4; + const int32_t outB = p1.blue * w1 + p2.blue * w2 + p3.blue * w3 + p4.blue * w4; + const int32_t outA = p1.alpha * w1 + p2.alpha * w2 + p3.alpha * w3 + p4.alpha * w4; +#else + const int32_t outR = __SMUAD(p1.red, w1) + __SMUAD(p2.red, w2) + __SMUAD(p3.red, w3) + __SMUAD(p4.red, w4); + const int32_t outG = + __SMUAD(p1.green, w1) + __SMUAD(p2.green, w2) + __SMUAD(p3.green, w3) + __SMUAD(p4.green, w4); + const int32_t outB = + __SMUAD(p1.blue, w1) + __SMUAD(p2.blue, w2) + __SMUAD(p3.blue, w3) + __SMUAD(p4.blue, w4); + const int32_t outA = + __SMUAD(p1.alpha, w1) + __SMUAD(p2.alpha, w2) + __SMUAD(p3.alpha, w3) + __SMUAD(p4.alpha, w4); +#endif + + Color32 result; + result.red = static_cast(outR >> 8); // 8:shift 8 bit right + result.green = static_cast(outG >> 8); // 8:shift 8 bit right + result.blue = static_cast(outB >> 8); // 8:shift 8 bit right + result.alpha = static_cast(outA >> 8); // 8:shift 8 bit right + if ((in.opaScale == OPA_OPAQUE) && (result.alpha == OPA_OPAQUE)) { + COLOR_FILL_COVER(screenBuffer, bufferMode, result.red, result.green, result.blue, ARGB8888); + } else { + COLOR_FILL_BLEND(screenBuffer, bufferMode, &result, ARGB8888, in.opaScale); + } + } + u += in.init.duHorizon; + v += in.init.dvHorizon; + screenBuffer += in.bufferPxSize; + } +} + +#ifdef ARM_NEON_OPT +static void DrawTriangleTrueColorBilinear8888InnerNeon(const TriangleScanInfo& in, + uint8_t* screenBuffer, + int16_t len, + float u, + float v, + NeonBlendPipeLine& pipeLine) +{ + ColorType arrayp1[NEON_STEP_8] = {0}; + ColorType arrayp2[NEON_STEP_8] = {0}; + ColorType arrayp3[NEON_STEP_8] = {0}; + ColorType arrayp4[NEON_STEP_8] = {0}; + float arrayU[NEON_STEP_8] = {0}; + float arrayV[NEON_STEP_8] = {0}; + int32_t arrayPx1[NEON_STEP_8] = {0}; + int16_t step = in.bufferPxSize * NEON_STEP_8; + while (len >= NEON_STEP_8) { + for (uint32_t i = 0; i < NEON_STEP_8; ++i) { + arrayU[i] = u; + arrayV[i] = v; + u += in.init.duHorizon; + v += in.init.dvHorizon; + } + // Monotonically increasing or decreasing, so only judge the beginning and end. + if ((arrayU[0] >= 0) && (arrayU[0] < in.info.header.width - 1) && + (arrayV[0] >= 0) && (arrayV[0] < in.info.header.height - 1) && + (arrayU[NEON_STEP_8 - 1] >= 0) && (arrayU[NEON_STEP_8 - 1] < in.info.header.width - 1) && + (arrayV[NEON_STEP_8 - 1] >= 0) && (arrayV[NEON_STEP_8 - 1] < in.info.header.height - 1)) { + // Process the lower half of arrayU and arrayV + float32x4_t vU = vld1q_f32(arrayU); + float32x4_t vV = vld1q_f32(arrayV); + int32x4_t vIntU = vcvtq_s32_f32(vU); + int32x4_t vIntV = vcvtq_s32_f32(vV); + int32x4_t vPx1 = + vaddq_s32(vmulq_s32(vIntV, vdupq_n_s32(in.srcLineWidth)), vmulq_s32(vIntU, vdupq_n_s32(in.pixelSize))); + vst1q_s32(arrayPx1, vPx1); + float32x4_t vDecU = vsubq_f32(vU, vcvtq_f32_s32(vIntU)); + float32x4_t vDecV = vsubq_f32(vV, vcvtq_f32_s32(vIntV)); + float32x4_t vDecUMinus1 = vsubq_f32(vdupq_n_f32(1.0), vDecU); + float32x4_t vDecVMinus1 = vsubq_f32(vdupq_n_f32(1.0), vDecV); + // 256:shift 8 bit left + uint32x4_t vLowW1 = vcvtq_u32_f32(vmulq_f32(vmulq_f32(vDecUMinus1, vDecVMinus1), vdupq_n_f32(256.0))); + uint32x4_t vLowW2 = vcvtq_u32_f32(vmulq_f32(vmulq_f32(vDecU, vDecVMinus1), vdupq_n_f32(256.0))); + uint32x4_t vLowW3 = vcvtq_u32_f32(vmulq_f32(vmulq_f32(vDecUMinus1, vDecV), vdupq_n_f32(256.0))); + uint32x4_t vLowW4 = vcvtq_u32_f32(vmulq_f32(vmulq_f32(vDecU, vDecV), vdupq_n_f32(256.0))); + // Process the higher half of arrayU and arrayV + vU = vld1q_f32(arrayU + NEON_STEP_4); + vV = vld1q_f32(arrayV + NEON_STEP_4); + vIntU = vcvtq_s32_f32(vU); + vIntV = vcvtq_s32_f32(vV); + vPx1 = + vaddq_s32(vmulq_s32(vIntV, vdupq_n_s32(in.srcLineWidth)), vmulq_s32(vIntU, vdupq_n_s32(in.pixelSize))); + vst1q_s32(arrayPx1 + NEON_STEP_4, vPx1); + vDecU = vsubq_f32(vU, vcvtq_f32_s32(vIntU)); + vDecV = vsubq_f32(vV, vcvtq_f32_s32(vIntV)); + vDecUMinus1 = vsubq_f32(vdupq_n_f32(1.0), vDecU); + vDecVMinus1 = vsubq_f32(vdupq_n_f32(1.0), vDecV); + // 256:shift 8 bit left + uint32x4_t vHighW1 = vcvtq_u32_f32(vmulq_f32(vmulq_f32(vDecUMinus1, vDecVMinus1), vdupq_n_f32(256.0))); + uint32x4_t vHighW2 = vcvtq_u32_f32(vmulq_f32(vmulq_f32(vDecU, vDecVMinus1), vdupq_n_f32(256.0))); + uint32x4_t vHighW3 = vcvtq_u32_f32(vmulq_f32(vmulq_f32(vDecUMinus1, vDecV), vdupq_n_f32(256.0))); + uint32x4_t vHighW4 = vcvtq_u32_f32(vmulq_f32(vmulq_f32(vDecU, vDecV), vdupq_n_f32(256.0))); + + // joins two uint32x4_t vectors into a uint16x8_t vector + uint16x8_t vW1 = vcombine_u16(vmovn_u32(vLowW1), vmovn_u32(vHighW1)); + uint16x8_t vW2 = vcombine_u16(vmovn_u32(vLowW2), vmovn_u32(vHighW2)); + uint16x8_t vW3 = vcombine_u16(vmovn_u32(vLowW3), vmovn_u32(vHighW3)); + uint16x8_t vW4 = vcombine_u16(vmovn_u32(vLowW4), vmovn_u32(vHighW4)); + + uint8_t* imgHead = const_cast(in.info.data); + for (uint32_t i = 0; i < NEON_STEP_8; ++i) { + int32_t px1 = arrayPx1[i]; + arrayp1[i] = *(reinterpret_cast(&imgHead[px1])); + arrayp2[i] = *(reinterpret_cast(&imgHead[px1 + in.pixelSize])); + arrayp3[i] = *(reinterpret_cast(&imgHead[px1 + in.srcLineWidth])); + arrayp4[i] = *(reinterpret_cast(&imgHead[px1 + in.srcLineWidth + in.pixelSize])); + } + + uint8x8x4_t v4p1 = vld4_u8(reinterpret_cast(arrayp1)); + uint8x8x4_t v4p2 = vld4_u8(reinterpret_cast(arrayp2)); + uint8x8x4_t v4p3 = vld4_u8(reinterpret_cast(arrayp3)); + uint8x8x4_t v4p4 = vld4_u8(reinterpret_cast(arrayp4)); + uint8x8_t vOutB = + vshrn_n_u16(vmulq_u16(vmovl_u8(v4p1.val[NEON_B]), vW1) + vmulq_u16(vmovl_u8(v4p2.val[NEON_B]), vW2) + + vmulq_u16(vmovl_u8(v4p3.val[NEON_B]), vW3) + vmulq_u16(vmovl_u8(v4p4.val[NEON_B]), vW4), + 8); // 8:shift 8 bit right + uint8x8_t vOutG = + vshrn_n_u16(vmulq_u16(vmovl_u8(v4p1.val[NEON_G]), vW1) + vmulq_u16(vmovl_u8(v4p2.val[NEON_G]), vW2) + + vmulq_u16(vmovl_u8(v4p3.val[NEON_G]), vW3) + vmulq_u16(vmovl_u8(v4p4.val[NEON_G]), vW4), + 8); // 8:shift 8 bit right + uint8x8_t vOutR = + vshrn_n_u16(vmulq_u16(vmovl_u8(v4p1.val[NEON_R]), vW1) + vmulq_u16(vmovl_u8(v4p2.val[NEON_R]), vW2) + + vmulq_u16(vmovl_u8(v4p3.val[NEON_R]), vW3) + vmulq_u16(vmovl_u8(v4p4.val[NEON_R]), vW4), + 8); // 8:shift 8 bit right + uint8x8_t vOutA = + vshrn_n_u16(vmulq_u16(vmovl_u8(v4p1.val[NEON_A]), vW1) + vmulq_u16(vmovl_u8(v4p2.val[NEON_A]), vW2) + + vmulq_u16(vmovl_u8(v4p3.val[NEON_A]), vW3) + vmulq_u16(vmovl_u8(v4p4.val[NEON_A]), vW4), + 8); // 8:shift 8 bit right + vOutA = NeonMulDiv255(vdup_n_u8(in.opaScale), vOutA); + pipeLine.Invoke(screenBuffer, vOutR, vOutG, vOutB, vOutA); + } else { + DrawTriangleTrueColorBilinear8888Inner(in, screenBuffer, NEON_STEP_8, arrayU[0], arrayV[0]); + } + screenBuffer += step; + len -= NEON_STEP_8; + } + if (len > 0) { + DrawTriangleTrueColorBilinear8888Inner(in, screenBuffer, len, u, v); + } +} +#endif + +void DrawUtils::DrawTriangleTrueColorBilinear8888(const TriangleScanInfo& in) +{ +#ifdef ARM_NEON_OPT + ColorMode bufferMode = ScreenDeviceProxy::GetInstance()->GetBufferMode(); +#endif + int16_t maskLeft = in.mask.GetLeft(); + int16_t maskRight = in.mask.GetRight(); + int32_t xMinErr = 0; + int32_t xMaxErr = 0; + GetXAxisErrForJunctionLine(in.ignoreJunctionPoint, in.isRightPart, xMinErr, xMaxErr); +#ifdef ARM_NEON_OPT + NeonBlendPipeLine pipeLine; + pipeLine.Construct(bufferMode, ARGB8888); +#endif + for (int16_t y = in.yMin; y <= in.yMax; ++y) { + int16_t xMin = MATH_MAX(static_cast(in.edge1.curX + xMinErr), maskLeft); + int16_t xMax = MATH_MIN(static_cast(in.edge2.curX + xMaxErr), maskRight); + int16_t diffX = (xMin - static_cast(in.edge1.curX)); + in.init.verticalU += in.init.duHorizon * diffX; + in.init.verticalV += in.init.dvHorizon * diffX; + +#if ENABLE_WINDOW + uint8_t* screenBuffer = in.screenBuffer + (y * in.screenBufferWidth + xMin) * in.bufferPxSize; +#else + uint8_t* screenBuffer = + in.screenBuffer + + (((y - in.screenBufferRect.GetTop()) * in.screenBufferWidth + (xMin - in.screenBufferRect.GetLeft())) * + in.bufferPxSize); +#endif + float u = in.init.verticalU; + float v = in.init.verticalV; +#ifdef ARM_NEON_OPT + { + DEBUG_PERFORMANCE_TRACE("DrawTriangleTrueColorBilinear8888_neon"); + DrawTriangleTrueColorBilinear8888InnerNeon(in, screenBuffer, xMax - xMin + 1, u, v, pipeLine); + } +#else + { + DEBUG_PERFORMANCE_TRACE("DrawTriangleTrueColorBilinear8888"); + DrawTriangleTrueColorBilinear8888Inner(in, screenBuffer, xMax - xMin + 1, u, v); + } +#endif + StepToNextLine(in.edge1, in.edge2); + in.init.verticalU += in.init.duVertical; + in.init.verticalV += in.init.dvVertical; + int16_t deltaX = static_cast(in.edge1.curX) - xMin; + in.init.verticalU += in.init.duHorizon * deltaX; + in.init.verticalV += in.init.dvHorizon * deltaX; + } +} + +void DrawUtils::DrawTriangleTrueColorNearest(const TriangleScanInfo& in) +{ + int16_t maskLeft = in.mask.GetLeft(); + int16_t maskRight = in.mask.GetRight(); + int32_t xMinErr = 0; + int32_t xMaxErr = 0; + ColorMode bufferMode = ScreenDeviceProxy::GetInstance()->GetBufferMode(); + GetXAxisErrForJunctionLine(in.ignoreJunctionPoint, in.isRightPart, xMinErr, xMaxErr); + for (int16_t y = in.yMin; y <= in.yMax; y++) { + int16_t xMin = MATH_MAX(static_cast(in.edge1.curX + xMinErr), maskLeft); + int16_t xMax = MATH_MIN(static_cast(in.edge2.curX + xMaxErr), maskRight); + int16_t diffX = (xMin - static_cast(in.edge1.curX)); + in.init.verticalU += in.init.duHorizon * diffX; + in.init.verticalV += in.init.dvHorizon * diffX; +#if ENABLE_WINDOW + uint8_t* screenBuffer = in.screenBuffer + (y * in.screenBufferWidth + xMin) * in.bufferPxSize; +#else + uint8_t* screenBuffer = + in.screenBuffer + + (((y - in.screenBufferRect.GetTop()) * in.screenBufferWidth + (xMin - in.screenBufferRect.GetLeft())) * + in.bufferPxSize); +#endif + float u = in.init.verticalU; + float v = in.init.verticalV; + for (int16_t x = xMin; x <= xMax; x++) { + const int16_t intU = static_cast(u); + const int16_t intV = static_cast(v); + if ((u >= 0) && (intU < in.info.header.width - 1) && (v >= 0) && (intV < in.info.header.height - 1)) { +#ifndef _LITEOS + uint32_t px1 = intV * in.srcLineWidth + intU * in.pixelSize; +#else + uint32_t val1 = __SMUAD(intV, in.srcLineWidth); + uint32_t val2 = __SMUAD(intU, in.pixelSize); + uint32_t px1 = val1 + val2; +#endif + uint8_t* imgHead = const_cast(in.info.data); + OpacityType opa = in.opaScale; + + switch (in.info.header.colorMode) { + case RGB888: { + Color24 p24 = *(reinterpret_cast(&imgHead[px1])); + if (opa == OPA_OPAQUE) { + COLOR_FILL_COVER(screenBuffer, bufferMode, p24.red, p24.green, p24.blue, RGB888); + } else { + COLOR_FILL_BLEND(screenBuffer, bufferMode, &p24, RGB888, opa); + } + break; + } + case RGB565: { + Color16 p16 = *(reinterpret_cast(&imgHead[px1])); + if (opa == OPA_OPAQUE) { + COLOR_FILL_COVER(screenBuffer, bufferMode, p16.red, p16.green, p16.blue, RGB565); + } else { + COLOR_FILL_BLEND(screenBuffer, bufferMode, &p16, RGB565, opa); + } + break; + } + case ARGB8888: { + Color32 p32 = *(reinterpret_cast(&imgHead[px1])); + if ((in.opaScale == OPA_OPAQUE) && (p32.alpha == OPA_OPAQUE)) { + COLOR_FILL_COVER(screenBuffer, bufferMode, p32.red, p32.green, p32.blue, ARGB8888); + } else { + COLOR_FILL_BLEND(screenBuffer, bufferMode, &p32, ARGB8888, in.opaScale); + } + break; + } + default: + return; + } + } + u += in.init.duHorizon; + v += in.init.dvHorizon; + screenBuffer += in.bufferPxSize; + } + StepToNextLine(in.edge1, in.edge2); + in.init.verticalU += in.init.duVertical; + in.init.verticalV += in.init.dvVertical; + int16_t deltaX = static_cast(in.edge1.curX) - xMin; + in.init.verticalU += in.init.duHorizon * deltaX; + in.init.verticalV += in.init.dvHorizon * deltaX; + } +} + +void DrawUtils::DrawTriangleTransformPart(const TrianglePartInfo& part) +{ + part.edge1.curX += part.edge1.du * (part.yMin - part.edge1.curY) / part.edge1.dv; + part.edge1.curY = part.yMin; + part.edge2.curX += part.edge2.du * (part.yMin - part.edge2.curY) / part.edge2.dv; + part.edge2.curY = part.yMin; + + Rect line; + line.SetLeft(static_cast(part.edge1.curX)); + line.SetRight(static_cast(part.edge1.curX)); + line.SetTop(static_cast(part.edge1.curY)); + line.SetBottom(static_cast(part.edge1.curY)); + TransformInitState init; + GetTransformInitState(part.transMap, part.position, line, init); + + uint16_t screenBufferWidth = ScreenDeviceProxy::GetInstance()->GetBufferWidth(); + uint8_t* screenBuffer = ScreenDeviceProxy::GetInstance()->GetBuffer(); + ColorMode bufferMode = ScreenDeviceProxy::GetInstance()->GetBufferMode(); + uint8_t bufferPxSize = GetByteSizeByColorMode(bufferMode); + if (screenBuffer == nullptr) { + return; + } + Rect bufferRect = ScreenDeviceProxy::GetInstance()->GetBufferRect(); + uint8_t pixelSize; + DrawTriangleTransformFuc fuc; + bool isTrueColor = (part.info.header.colorMode == ARGB8888) || (part.info.header.colorMode == RGB888) || + (part.info.header.colorMode == RGB565); + if (isTrueColor) { + pixelSize = part.info.pxSize >> SHIFT_3; + if (part.info.algorithm == TransformAlgorithm::NEAREST_NEIGHBOR) { + fuc = DrawTriangleTrueColorNearest; + } else if (part.info.header.colorMode == ARGB8888) { + fuc = DrawTriangleTrueColorBilinear8888; + } else if (part.info.header.colorMode == RGB888) { + fuc = DrawTriangleTrueColorBilinear888; + } else { + fuc = DrawTriangleTrueColorBilinear565; + } + } else { + pixelSize = part.info.pxSize; + fuc = DrawTriangleAlphaBilinear; + } + const int32_t srcLineWidth = part.info.header.width * pixelSize; + TriangleScanInfo input{part.yMin, + part.yMax, + part.edge1, + part.edge2, + screenBuffer, + bufferPxSize, + bufferRect, + part.color, + part.opaScale, + init, + screenBufferWidth, + pixelSize, + srcLineWidth, + part.info, + part.mask, + part.isRightPart, + part.ignoreJunctionPoint}; + fuc(input); +} + +void DrawUtils::DrawTriangleTransform(const Rect& mask, + const Point& position, + const ColorType& color, + OpacityType opaScale, + const TransformMap& transMap, + const TriangleTransformDataInfo& triangleInfo) +{ + bool p3IsInRight = ((triangleInfo.p1.y - triangleInfo.p2.y) * triangleInfo.p3.x + + (triangleInfo.p2.x - triangleInfo.p1.x) * triangleInfo.p3.y + + triangleInfo.p1.x * triangleInfo.p2.y - triangleInfo.p2.x * triangleInfo.p1.y) < 0; + TriangleEdge edge1; + TriangleEdge edge2; + TrianglePartInfo part{ + mask, + transMap, + position, + edge1, + edge2, + 0, + 0, + triangleInfo.info, + color, + opaScale, + triangleInfo.isRightPart, + triangleInfo.ignoreJunctionPoint, + }; + + uint8_t yErr = 1; + if (triangleInfo.p2.y == triangleInfo.p1.y) { + yErr = 0; + goto BottomHalf; + } + if (p3IsInRight) { + edge1 = TriangleEdge(triangleInfo.p1.x, triangleInfo.p1.y, triangleInfo.p2.x, triangleInfo.p2.y); + edge2 = TriangleEdge(triangleInfo.p1.x, triangleInfo.p1.y, triangleInfo.p3.x, triangleInfo.p3.y); + } else { + edge2 = TriangleEdge(triangleInfo.p1.x, triangleInfo.p1.y, triangleInfo.p2.x, triangleInfo.p2.y); + edge1 = TriangleEdge(triangleInfo.p1.x, triangleInfo.p1.y, triangleInfo.p3.x, triangleInfo.p3.y); + } + + part.yMin = MATH_MAX(mask.GetTop(), triangleInfo.p1.y); + part.yMax = MATH_MIN(mask.GetBottom(), triangleInfo.p2.y); + part.edge1 = edge1; + part.edge2 = edge2; + DrawTriangleTransformPart(part); +BottomHalf: + if (triangleInfo.p2.y == triangleInfo.p3.y) { + return; + } + + if (triangleInfo.p2.y == triangleInfo.p1.y) { + if (triangleInfo.p1.x < triangleInfo.p2.x) { + edge1 = TriangleEdge(triangleInfo.p1.x, triangleInfo.p1.y, triangleInfo.p3.x, triangleInfo.p3.y); + edge2 = TriangleEdge(triangleInfo.p2.x, triangleInfo.p2.y, triangleInfo.p3.x, triangleInfo.p3.y); + } else { + edge2 = TriangleEdge(triangleInfo.p1.x, triangleInfo.p1.y, triangleInfo.p3.x, triangleInfo.p3.y); + edge1 = TriangleEdge(triangleInfo.p2.x, triangleInfo.p2.y, triangleInfo.p3.x, triangleInfo.p3.y); + } + } else { + if (p3IsInRight) { + edge1 = TriangleEdge(triangleInfo.p2.x, triangleInfo.p2.y, triangleInfo.p3.x, triangleInfo.p3.y); + } else { + edge2 = TriangleEdge(triangleInfo.p2.x, triangleInfo.p2.y, triangleInfo.p3.x, triangleInfo.p3.y); + } + } + + part.yMin = MATH_MAX(mask.GetTop(), triangleInfo.p2.y + yErr); + part.yMax = MATH_MIN(mask.GetBottom(), triangleInfo.p3.y); + part.edge1 = edge1; + part.edge2 = edge2; + DrawTriangleTransformPart(part); +} + +void DrawUtils::DrawTransform(const Rect& mask, + const Point& position, + const ColorType& color, + OpacityType opaScale, + const TransformMap& transMap, + const TransformDataInfo& dataInfo) const +{ + if (opaScale == OPA_TRANSPARENT) { + return; + } + Rect trans = transMap.GetBoxRect(); + trans.SetX(trans.GetX() + position.x); + trans.SetY(trans.GetY() + position.y); + if (!trans.Intersect(trans, mask)) { + return; + } +#if ENABLE_HARDWARE_ACCELERATION + DRAW_UTILS_PREPROCESS(opaScale); + TransformOption option; + option.algorithm = dataInfo.algorithm; + if (ScreenDeviceProxy::GetInstance()->HardwareTransform(dataInfo.data, + static_cast(dataInfo.header.colorMode), transMap.GetTransMapRect(), transMap.GetTransformMatrix(), + opaScale, Color::ColorTo32(color), mask, screenBuffer, screenBufferWidth * bufferPxSize, bufferMode, option)) { + return; + } +#endif + + TriangleTransformDataInfo triangleInfo{ + dataInfo, + }; + Polygon polygon = transMap.GetPolygon(); + Point p1; + p1.x = polygon[0].x_ + position.x; // 0:first point + p1.y = polygon[0].y_ + position.y; // 0:first point + Point p2; + p2.x = polygon[1].x_ + position.x; // 1:second point + p2.y = polygon[1].y_ + position.y; // 1:second point + Point p3; + p3.x = polygon[2].x_ + position.x; // 2:third point + p3.y = polygon[2].y_ + position.y; // 2:third point + triangleInfo.isRightPart = ((p1.y - p3.y) * p2.x + (p3.x - p1.x) * p2.y + p1.x * p3.y - p3.x * p1.y) < 0; + triangleInfo.isRightPart = (p1.y < p3.y) ? triangleInfo.isRightPart : !triangleInfo.isRightPart; + DrawTriangle::SortVertexs(p1, p2, p3); + triangleInfo.ignoreJunctionPoint = false; + triangleInfo.p1 = p1; + triangleInfo.p2 = p2; + triangleInfo.p3 = p3; + if ((triangleInfo.p1.y <= mask.GetBottom()) && (triangleInfo.p3.y >= mask.GetTop())) { + DrawTriangleTransform(mask, position, color, opaScale, transMap, triangleInfo); + } + + triangleInfo.ignoreJunctionPoint = true; + triangleInfo.isRightPart = !triangleInfo.isRightPart; + p1.x = polygon[0].x_ + position.x; // 0:first point + p1.y = polygon[0].y_ + position.y; // 0:first point + p3.x = polygon[2].x_ + position.x; // 2:third point + p3.y = polygon[2].y_ + position.y; // 2:third point + Point p4; + p4.x = polygon[3].x_ + position.x; // 3:fourth point + p4.y = polygon[3].y_ + position.y; // 3:fourth point + DrawTriangle::SortVertexs(p1, p3, p4); + triangleInfo.p1 = p1; + triangleInfo.p2 = p3; + triangleInfo.p3 = p4; + if ((triangleInfo.p1.y <= mask.GetBottom()) && (triangleInfo.p3.y >= mask.GetTop())) { + DrawTriangleTransform(mask, position, color, opaScale, transMap, triangleInfo); + } +} + +OpacityType DrawUtils::GetPxAlphaForAlphaImg(const TransformDataInfo& dataInfo, const Point& point) +{ + Point tmpPoint = point; + const uint8_t* bufU8 = const_cast(dataInfo.data); +#if ENABLE_SPEC_FONT + if (dataInfo.header.colorMode == A1) { + uint8_t bit = tmpPoint.x & 0x7; // 0x7: 1 byte is 8 bit, + tmpPoint.x = tmpPoint.x >> SHIFT_3; + + uint32_t px = (dataInfo.header.width >> SHIFT_3) * tmpPoint.y + tmpPoint.x; + // 1: A1 means 1 bit, 7: maximum offset in bytes + uint8_t pxOpa = (bufU8[px] & (1 << (7 - bit))) >> (7 - bit); + return pxOpa ? OPA_TRANSPARENT : OPA_OPAQUE; + } else if (dataInfo.header.colorMode == A2) { + uint8_t bit = (tmpPoint.x & 0x3) * 2; // 0x3: 0b0011, 2: A2 color mode + tmpPoint.x = tmpPoint.x >> SHIFT_2; + + uint32_t px = (dataInfo.header.width >> SHIFT_2) * tmpPoint.y + tmpPoint.x; + // 3: the value of 0b0011 + uint8_t pxOpa = (bufU8[px] & (3 << (SHIFT_6 - bit))) >> (SHIFT_6 - bit); + return pxOpa * OPACITY_STEP_A2; + } else if (dataInfo.header.colorMode == A8) { + uint32_t px = dataInfo.header.width * tmpPoint.y + tmpPoint.x; + return bufU8[px]; + } +#else + uint8_t letterWidthInByte = (dataInfo.header.width * dataInfo.pxSize) >> SHIFT_3; + // 0x7: for rounding + if ((dataInfo.header.width * dataInfo.pxSize) & 0x7) { + letterWidthInByte++; + } + uint8_t bit = (tmpPoint.x & 0x1) << SHIFT_2; + bufU8 += (tmpPoint.y * letterWidthInByte) + ((tmpPoint.x * dataInfo.pxSize) >> SHIFT_3); + // 0xF: 0b1111, get the data of the A4 color mode + uint8_t pxOpa = (*bufU8 & (0xF << bit)) >> (bit); + return pxOpa * OPACITY_STEP_A4; +#endif // ENABLE_SPEC_FONT +} + +void DrawUtils::DrawTranspantArea(const Rect& rect, const Rect& mask) +{ + FillArea(rect, mask, true, nullptr); +} + +void DrawUtils::DrawWithBuffer(const Rect& rect, const Rect& mask, const ColorType* colorBuf) +{ + FillArea(rect, mask, false, colorBuf); +} + +void DrawUtils::FillArea(const Rect& rect, const Rect& mask, bool isTransparent, const ColorType* colorBuf) +{ + Rect maskedArea; + if (!maskedArea.Intersect(rect, mask)) { + return; + } + + int16_t left = maskedArea.GetLeft(); + int16_t right = maskedArea.GetRight(); + int16_t top = maskedArea.GetTop(); + int16_t bottom = maskedArea.GetBottom(); + +#if !ENABLE_WINDOW + Rect bufferRect = ScreenDeviceProxy::GetInstance()->GetBufferRect(); + maskedArea.SetLeft(left - bufferRect.GetLeft()); + maskedArea.SetRight(right - bufferRect.GetLeft()); + maskedArea.SetTop(top - bufferRect.GetTop()); + maskedArea.SetBottom(bottom - bufferRect.GetTop()); +#endif + + uint16_t screenBufferWidth = ScreenDeviceProxy::GetInstance()->GetBufferWidth(); + uint8_t* mem = ScreenDeviceProxy::GetInstance()->GetBuffer(); + ColorMode bufferMode = ScreenDeviceProxy::GetInstance()->GetBufferMode(); + uint8_t bufferPxSize = GetByteSizeByColorMode(bufferMode); + + mem += top * screenBufferWidth * bufferPxSize; + if (isTransparent) { + uint16_t sz = (right - left + 1) * bufferPxSize; + for (int16_t row = top; row <= bottom; row++) { + if (memset_s(mem + (left * bufferPxSize), sz, 0, sz) != EOK) { + return; + } + mem += screenBufferWidth * bufferPxSize; + } + } else { + if (colorBuf == nullptr) { + return; + } + for (int16_t row = top; row <= bottom; row++) { + for (int16_t col = left; col <= right; col++) { +#if COLOR_DEPTH == 32 + COLOR_FILL_COVER(mem[col * bufferPxSize], bufferMode, colorBuf[row * screenBufferWidth + col].red, + colorBuf[row * screenBufferWidth + col].green, + colorBuf[row * screenBufferWidth + col].blue, ARGB8888); +#else + COLOR_FILL_COVER(mem[col * bufferPxSize], bufferMode, colorBuf[row * screenBufferWidth + col].red, + colorBuf[row * screenBufferWidth + col].green, + colorBuf[row * screenBufferWidth + col].blue, RGB565); +#endif + } + mem += screenBufferWidth * bufferPxSize; + } + } +} + +void DrawUtils::DrawAdjPixelInLine(int16_t x1, + int16_t y1, + int16_t x2, + int16_t y2, + const Rect& mask, + const ColorType& color, + OpacityType opa, + uint16_t weight) const +{ + DRAW_UTILS_PREPROCESS(opa); + Color32 result; + result.full = Color::ColorTo32(color); + if ((x1 >= mask.GetLeft()) && (x1 <= mask.GetRight()) && (y1 >= mask.GetTop()) && (y1 <= mask.GetBottom())) { + x1 -= bufferRect.GetLeft(); + y1 -= bufferRect.GetTop(); + screenBuffer += (y1 * screenBufferWidth + x1) * bufferPxSize; + OpacityType fillOpa = (weight ^ OPA_OPAQUE) * opa / OPA_OPAQUE; + COLOR_FILL_BLEND(screenBuffer, bufferMode, &result, ARGB8888, fillOpa); + } + if ((x2 >= mask.GetLeft()) && (x2 <= mask.GetRight()) && (y2 >= mask.GetTop()) && (y2 <= mask.GetBottom())) { + x2 -= bufferRect.GetLeft(); + y2 -= bufferRect.GetTop(); + screenBuffer = ScreenDeviceProxy::GetInstance()->GetBuffer(); + screenBuffer += (y2 * screenBufferWidth + x2) * bufferPxSize; + OpacityType fillOpa = weight * opa / OPA_OPAQUE; + COLOR_FILL_BLEND(screenBuffer, bufferMode, &result, ARGB8888, fillOpa); + } +} + +void DrawUtils::DrawPixelInLine(int16_t x, + int16_t y, + const Rect& mask, + const ColorType& color, + OpacityType opa, + uint16_t weight) const +{ + DRAW_UTILS_PREPROCESS(opa); + Color32 result; + result.full = Color::ColorTo32(color); + if ((x >= mask.GetLeft()) && (x <= mask.GetRight()) && (y >= mask.GetTop()) && (y <= mask.GetBottom())) { + x -= bufferRect.GetLeft(); + y -= bufferRect.GetTop(); + screenBuffer += (y * screenBufferWidth + x) * bufferPxSize; + OpacityType fillOpa = weight * opa / OPA_OPAQUE; + COLOR_FILL_BLEND(screenBuffer, bufferMode, &result, ARGB8888, fillOpa); + } +} + +void DrawUtils::DrawVerPixelInLine(int16_t x, + int16_t y, + int8_t dir, + const Rect& mask, + const ColorType& color, + OpacityType opa, + uint16_t weight) const +{ + DRAW_UTILS_PREPROCESS(opa); + if ((y < mask.GetTop()) || (y > mask.GetBottom())) { + return; + } + Color32 result; + result.full = Color::ColorTo32(color); + int16_t x0 = x + dir; + int16_t x1 = x - dir; + y -= bufferRect.GetTop(); + if ((x0 >= mask.GetLeft()) && (x0 <= mask.GetRight())) { + x0 -= bufferRect.GetLeft(); + screenBuffer += (y * screenBufferWidth + x0) * bufferPxSize; + OpacityType fillOpa = weight * opa / OPA_OPAQUE; + COLOR_FILL_BLEND(screenBuffer, bufferMode, &result, ARGB8888, fillOpa); + } + if ((x >= mask.GetLeft()) && (x <= mask.GetRight())) { + x -= bufferRect.GetLeft(); + screenBuffer = ScreenDeviceProxy::GetInstance()->GetBuffer(); + screenBuffer += (y * screenBufferWidth + x) * bufferPxSize; + if (opa == OPA_OPAQUE) { + COLOR_FILL_COVER(screenBuffer, bufferMode, result.red, result.green, result.blue, ARGB8888); + } else { + COLOR_FILL_BLEND(screenBuffer, bufferMode, &result, ARGB8888, opa); + } + } + if ((x1 >= mask.GetLeft()) && (x1 <= mask.GetRight())) { + x1 -= bufferRect.GetLeft(); + screenBuffer = ScreenDeviceProxy::GetInstance()->GetBuffer(); + screenBuffer += (y * screenBufferWidth + x1) * bufferPxSize; + OpacityType fillOpa = (weight ^ OPA_OPAQUE) * opa / OPA_OPAQUE; + COLOR_FILL_BLEND(screenBuffer, bufferMode, &result, ARGB8888, fillOpa); + } +} + +void DrawUtils::DrawHorPixelInLine(int16_t x, + int16_t y, + int8_t dir, + const Rect& mask, + const ColorType& color, + OpacityType opa, + uint16_t weight) const +{ + DRAW_UTILS_PREPROCESS(opa); + if ((x < mask.GetLeft()) || (x > mask.GetRight())) { + return; + } + Color32 result; + result.full = Color::ColorTo32(color); + int16_t y0 = y + dir; + int16_t y1 = y - dir; + x -= bufferRect.GetLeft(); + if ((y0 >= mask.GetTop()) && (y0 <= mask.GetBottom())) { + y0 -= bufferRect.GetTop(); + screenBuffer += (y0 * screenBufferWidth + x) * bufferPxSize; + OpacityType fillOpa = weight * opa / OPA_OPAQUE; + COLOR_FILL_BLEND(screenBuffer, bufferMode, &result, ARGB8888, fillOpa); + } + if ((y >= mask.GetTop()) && (y <= mask.GetBottom())) { + y -= bufferRect.GetTop(); + screenBuffer = ScreenDeviceProxy::GetInstance()->GetBuffer(); + screenBuffer += (y * screenBufferWidth + x) * bufferPxSize; + if (opa == OPA_OPAQUE) { + COLOR_FILL_COVER(screenBuffer, bufferMode, result.red, result.green, result.blue, ARGB8888); + } else { + COLOR_FILL_BLEND(screenBuffer, bufferMode, &result, ARGB8888, opa); + } + } + if ((y1 >= mask.GetTop()) && (y1 <= mask.GetBottom())) { + y1 -= bufferRect.GetTop(); + screenBuffer = ScreenDeviceProxy::GetInstance()->GetBuffer(); + screenBuffer += (y1 * screenBufferWidth + x) * bufferPxSize; + OpacityType fillOpa = (weight ^ OPA_OPAQUE) * opa / OPA_OPAQUE; + COLOR_FILL_BLEND(screenBuffer, bufferMode, &result, ARGB8888, fillOpa); + } +} +} // namespace OHOS \ No newline at end of file diff --git a/frameworks/draw/draw_utils.h b/frameworks/draw/draw_utils.h new file mode 100644 index 0000000..9c1fb1f --- /dev/null +++ b/frameworks/draw/draw_utils.h @@ -0,0 +1,291 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GRAPHIC_LITE_DRAW_UTILS_H +#define GRAPHIC_LITE_DRAW_UTILS_H + +#include "color.h" +#include "dock/screen_device_proxy.h" +#include "common/text.h" +#include "geometry2d.h" +#include "graphic_types.h" +#include "style.h" +#include "transform.h" + +namespace OHOS { +#define SWAP_INT16(x, y) \ + do { \ + int16_t temp = (x); \ + (x) = (y); \ + (y) = temp; \ + } while (0) + +#define SWAP_POINTS(x1, x2, y1, y2) \ + SWAP_INT16(x1, x2); \ + SWAP_INT16(y1, y2); + +struct EdgeSides { + int16_t left; + int16_t right; + int16_t top; + int16_t bottom; +}; + +struct LabelLineInfo { + Point& pos; + Point& offset; + const Rect& mask; + int16_t lineHeight; + uint16_t lineLength; + uint8_t shapingId; + uint8_t opaScale; + const Style& style; + + const char* text; + uint16_t length; + uint16_t start; + uint8_t fontId; + uint8_t fontSize; + uint8_t txtFlag; + UITextLanguageDirect direct; + uint32_t* codePoints; +}; + +struct LabelLetterInfo { + const Point& pos; + Rect mask; + const ColorType& color; + OpacityType opa; + int8_t offsetX; + int8_t offsetY; + + const uint32_t& letter; + UITextLanguageDirect direct; + uint8_t fontId; + uint8_t shapingId; + uint8_t fontSize; +}; + +struct TransformInitState { + float verticalU; + float verticalV; + float duHorizon; + float dvHorizon; + float duVertical; + float dvVertical; +}; + +struct TriangleEdge { + TriangleEdge() {} + TriangleEdge(int16_t x1, int16_t y1, int16_t duInt, int16_t dvInt); + ~TriangleEdge(); + float curX = 0.0f; + float curY = 0.0f; + float du = 0.0f; + float dv = 0.0f; +}; +struct TransformDataInfo { + ImageHeader header; + const uint8_t* data; + uint8_t pxSize; + BlurLevel blurLevel; + TransformAlgorithm algorithm; +}; + +struct TriangleTransformDataInfo { + const TransformDataInfo& info; + Point p1; + Point p2; + Point p3; + bool isRightPart; + bool ignoreJunctionPoint; +}; + +struct TriangleScanInfo { + int16_t yMin; + int16_t yMax; + TriangleEdge& edge1; + TriangleEdge& edge2; + uint8_t* screenBuffer; + uint8_t bufferPxSize; + Rect screenBufferRect; + const ColorType& color; + const OpacityType opaScale; + TransformInitState& init; + uint16_t screenBufferWidth; + uint8_t pixelSize; + const int32_t srcLineWidth; + const TransformDataInfo& info; + const Rect& mask; + bool isRightPart; + bool ignoreJunctionPoint; +}; + +struct TrianglePartInfo { + const Rect& mask; + const TransformMap& transMap; + const Point& position; + TriangleEdge& edge1; + TriangleEdge& edge2; + int16_t yMin; + int16_t yMax; + const TransformDataInfo& info; + const ColorType& color; + const OpacityType opaScale; + bool isRightPart; + bool ignoreJunctionPoint; +}; + +enum { + IMG_SRC_VARIABLE, + IMG_SRC_FILE, + IMG_SRC_UNKNOWN, +}; + +enum PixelType { + IMG_RGB565 = 2, + IMG_RGB888 = 3, + IMG_ARGB8888 = 4, +}; + +class DrawUtils : public HeapBase { +public: + static DrawUtils* GetInstance() + { + static DrawUtils instance; + return &instance; + } + + void DrawColorArea(const Rect& area, const Rect& mask, const ColorType& color, OpacityType opa) const; + + void DrawColorAreaBySides(const Rect& mask, const ColorType& color, OpacityType opa, const EdgeSides& sides) const; + + void DrawPixel(int16_t x, int16_t y, const Rect& mask, const ColorType& color, OpacityType opa) const; + + void DrawLetter(const LabelLetterInfo& letterInfo) const; + + void DrawImage(const Rect& area, const Rect& mask, const uint8_t* image, OpacityType opa, uint8_t pxByteSize) const; + + static void + GetXAxisErrForJunctionLine(bool ignoreJunctionPoint, bool isRightPart, int32_t& xMinErr, int32_t& xMaxErr); + + static void GetTransformInitState(const TransformMap& transMap, + const Point& position, + const Rect& trans, + TransformInitState& init); + + static void DrawTriangleTransform(const Rect& mask, + const Point& position, + const ColorType& color, + OpacityType opaScale, + const TransformMap& transMap, + const TriangleTransformDataInfo& dataInfo); + + void DrawTransform(const Rect& mask, + const Point& position, + const ColorType& color, + OpacityType opaScale, + const TransformMap& transMap, + const TransformDataInfo& dataInfo) const; + + void DrawTranspantArea(const Rect& rect, const Rect& mask); + + void DrawWithBuffer(const Rect& rect, const Rect& mask, const ColorType* colorBuf); + + static uint8_t GetPxSizeByColorMode(uint8_t colorMode); + + static uint8_t GetByteSizeByColorMode(uint8_t colorMode); + + static uint8_t GetPxSizeByImageInfo(ImageInfo imageInfo); + + static OpacityType GetMixOpacity(OpacityType opa1, OpacityType opa2) + { + // 8: Shift right 8 bits + OpacityType opaMix = (opa1 == OPA_OPAQUE) ? opa2 : ((static_cast(opa1) * opa2) >> 8); + return opaMix; + } + + void DrawAdjPixelInLine(int16_t x1, + int16_t y1, + int16_t x2, + int16_t y2, + const Rect& mask, + const ColorType& color, + OpacityType opa, + uint16_t w) const; + + void DrawPixelInLine(int16_t x, int16_t y, const Rect& mask, const ColorType& color, OpacityType opa, uint16_t w) + const; + + void DrawVerPixelInLine(int16_t x, + int16_t y, + int8_t dir, + const Rect& mask, + const ColorType& color, + OpacityType opa, + uint16_t weight) const; + + void DrawHorPixelInLine(int16_t x, + int16_t y, + int8_t dir, + const Rect& mask, + const ColorType& color, + OpacityType opa, + uint16_t weight) const; + +private: +#if ENABLE_GFX_ENGINES + bool FillAreaWithHardware(const Rect& fillArea, const ColorType& color, const OpacityType& opa) const; +#endif + void FillAreaWithSoftWare(const Rect& fillArea, + uint8_t* mem, + ColorMode mode, + uint8_t destByteSize, + int16_t bufWidth, + const ColorType& color, + const OpacityType& opa) const; + + void BlendWithSoftWare(const uint8_t* src, + uint32_t srcStride, + ColorMode srcMode, + uint8_t* dest, + uint32_t destStride, + ColorMode destMode, + uint32_t width, + uint32_t height, + OpacityType opa) const; + + using DrawTriangleTransformFuc = void (*)(const TriangleScanInfo& triangle); + + static void DrawTriangleTrueColorNearest(const TriangleScanInfo& triangle); + + static void DrawTriangleAlphaBilinear(const TriangleScanInfo& triangle); + + static void DrawTriangleTrueColorBilinear565(const TriangleScanInfo& triangle); + + static void DrawTriangleTrueColorBilinear888(const TriangleScanInfo& triangle); + + static void DrawTriangleTrueColorBilinear8888(const TriangleScanInfo& triangle); + + inline static void StepToNextLine(TriangleEdge& edg1, TriangleEdge& edg2); + + static void DrawTriangleTransformPart(const TrianglePartInfo& part); + + static OpacityType GetPxAlphaForAlphaImg(const TransformDataInfo& dataInfo, const Point& point); + + void FillArea(const Rect& rect, const Rect& mask, bool isTransparent, const ColorType* colorBuf); +}; +} // namespace OHOS +#endif // GRAPHIC_LITE_DRAW_UTILS_H diff --git a/frameworks/events/event.cpp b/frameworks/events/event.cpp new file mode 100755 index 0000000..ed4b35e --- /dev/null +++ b/frameworks/events/event.cpp @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "events/event.h" +#include "hal_tick.h" + +namespace OHOS { +Event::Event() : timeStamp_(HALTick::GetInstance().GetTime()), curPos_({ 0, 0 }) {} + +Event::Event(const Point& curPos) : timeStamp_(HALTick::GetInstance().GetTime()), curPos_(curPos) {} +} // namespace OHOS diff --git a/frameworks/font/base_font.cpp b/frameworks/font/base_font.cpp new file mode 100755 index 0000000..4d99d3c --- /dev/null +++ b/frameworks/font/base_font.cpp @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "font/base_font.h" +namespace OHOS { +uint8_t BaseFont::GetBaseFontId() +{ + return fontId_; +} +void BaseFont::SetBaseFontId(uint8_t fontId) +{ + fontId_ = fontId; +} + +uintptr_t BaseFont::GetRamAddr() +{ + return ramAddr_; +} +void BaseFont::SetRamAddr(uintptr_t ramAddr) +{ + ramAddr_ = ramAddr; +} +uint32_t BaseFont::GetRamLen() +{ + return ramLen_; +} +void BaseFont::SetRamLen(uint32_t ramLen) +{ + ramLen_ = ramLen; +} + +void BaseFont::SetPsramMemory(uintptr_t psramAddr, uint32_t psramLen) +{ + SetRamAddr(psramAddr); + SetRamLen(psramLen); +} + +int8_t BaseFont::GetDefaultParamByLangId(uint8_t langId, LangTextParam** pParam) const +{ + if ((langId >= GetTotalLangId()) || (pParam == nullptr)) { + return INVALID_RET_VALUE; + } + + LangTextParam *pTable = GetLangTextDefaultParamTable(); + if (pTable == nullptr) { + return INVALID_RET_VALUE; + } + + *pParam = (pTable + langId); + return RET_VALUE_OK; +} +} // namespace OHOS diff --git a/frameworks/font/glyphs_manager.cpp b/frameworks/font/glyphs_manager.cpp new file mode 100755 index 0000000..61da98e --- /dev/null +++ b/frameworks/font/glyphs_manager.cpp @@ -0,0 +1,383 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "font/glyphs_manager.h" +#include "file.h" +#include "securec.h" + +namespace OHOS { +GlyphsManager::GlyphsManager() + : binHeader_{{0}}, + fontNum_(0), + start_(0), + fontHeaderSectionStart_(0), + fontIndexSectionStart_(0), + curFontIndexSectionStart_(0), + glyphNodeSectionStart_(0), + curGlyphNodeSectionStart_(0), + bitMapSectionStart_(0), + curBitMapSectionStart_(0), + ramAddr_(nullptr), + ramUsedLen_(0), + fontHeaderCache_(nullptr), + indexCache_(nullptr), + curIndexCache_(nullptr), + nodeCache_(nullptr), + cacheStatus_(nullptr), + fp_(-1), + curFontHeader_(nullptr), + curGlyphNode_(nullptr), + isRamSet_(false), + isFileSet_(false), + isFontIdSet_(false) +{ + fontId_ = GetBitmapFontIdMax(); +} +GlyphsManager::~GlyphsManager() {} + +int8_t GlyphsManager::GlyphNodeCacheInit() +{ + uint32_t size = 0; + for (int32_t i = 0; i < fontNum_; i++) { + size += fontHeaderCache_[i].indexLen; + } + + indexCache_ = ramAddr_ + ramUsedLen_; + ramUsedLen_ += size; + /* align up to 4 byte, power of 2 */ + ramUsedLen_ = AlignUp(ramUsedLen_, 2); + + int32_t ret = read(fp_, indexCache_, size); + if (ret != static_cast(size)) { + return INVALID_RET_VALUE; + } + + cacheStatus_ = reinterpret_cast(ramAddr_ + ramUsedLen_); + ramUsedLen_ += sizeof(CacheState); + /* align up to 4 byte, power of 2 */ + ramUsedLen_ = AlignUp(ramUsedLen_, 2); + for (int32_t font = 0; font < FONT_HASH_NR; font++) { + for (int32_t uc = 0; uc < UNICODE_HASH_NR; uc++) { + (*cacheStatus_)[font][uc] = 0; + } + } + + nodeCache_ = reinterpret_cast(ramAddr_ + ramUsedLen_); + ramUsedLen_ += sizeof(CacheType); + /* align up to 4 byte, power of 2 */ + ramUsedLen_ = AlignUp(ramUsedLen_, 2); + for (int32_t font = 0; font < FONT_HASH_NR; font++) { + for (int32_t uc = 0; uc < UNICODE_HASH_NR; uc++) { + for (int32_t node = 0; node < NODE_HASH_NR; node++) { + (*nodeCache_)[font][uc][node].unicode = 0; + } + } + } + + return RET_VALUE_OK; +} + +GlyphNode* GlyphsManager::GetNodeFromCache(uint32_t unicode) +{ + GlyphNode* node = nullptr; + + uint8_t font = fontId_ & FONT_HASH_MASK; + uint8_t uc = unicode & UNICODE_HASH_MASK; + for (uint8_t i = 0; i < NODE_HASH_NR; i++) { + GlyphNode* p = &((*nodeCache_)[font][uc][i]); + if ((p->unicode == unicode) && (p->reserve == fontId_)) { + node = p; + break; + } + } + return node; +} + +GlyphNode* GlyphsManager::GetNodeCacheSpace(uint32_t unicode) +{ + uint8_t font, uc, i; + GlyphNode* node = nullptr; + + font = fontId_ & FONT_HASH_MASK; + uc = unicode & UNICODE_HASH_MASK; + i = (*cacheStatus_)[font][uc]; + node = &((*nodeCache_)[font][uc][i]); + + i++; + if (i >= NODE_HASH_NR) { + i = 0; + } + (*cacheStatus_)[font][uc] = i; + + return node; +} + +GlyphNode* GlyphsManager::GetNodeFromFile(uint32_t unicode) +{ + uint16_t idx = 0; + uint8_t key; + uint32_t offset; + + for (int32_t i = RADIX_SHIFT_START; i >= 0; i -= RADIX_TREE_BITS) { + offset = idx * sizeof(IndexNode); + key = static_cast((unicode >> static_cast(i)) & RADIX_TREE_MASK); + offset += key * sizeof(uint16_t); + idx = *(reinterpret_cast(curIndexCache_ + offset)); + if (idx == 0) { + return nullptr; + } + } + + offset = curGlyphNodeSectionStart_ + (idx - 1) * sizeof(GlyphNode); + int32_t ret = lseek(fp_, offset, SEEK_SET); + if (ret != static_cast(offset)) { + return nullptr; + } + GlyphNode* node = GetNodeCacheSpace(unicode); + ret = read(fp_, node, sizeof(GlyphNode)); + if (ret < 0) { + return nullptr; + } + + return node; +} + +void GlyphsManager::SetRamBuffer(uintptr_t ramAddr) +{ + ramAddr_ = reinterpret_cast(ramAddr); + isRamSet_ = true; +} + +int8_t GlyphsManager::SetFile(int32_t fp, uint32_t start) +{ + if (!isRamSet_) { + return INVALID_RET_VALUE; + } + + fp_ = fp; + start_ = start; + int32_t ret = lseek(fp_, start_, SEEK_SET); + if (ret < 0) { + return INVALID_RET_VALUE; + } + ret = read(fp_, &binHeader_, sizeof(binHeader_)); + if (ret != sizeof(binHeader_)) { + return INVALID_RET_VALUE; + } + if (strncmp(binHeader_.fontMagic, FONT_MAGIC_NUMBER, FONT_MAGIC_NUM_LEN) != 0) { + return INVALID_RET_VALUE; + } + if (binHeader_.fontNum > GetBitmapFontIdMax()) { + return INVALID_RET_VALUE; + } + + fontNum_ = binHeader_.fontNum; + fontHeaderSectionStart_ = start_ + sizeof(binHeader_); + int32_t size = sizeof(FontHeader) * fontNum_; + fontIndexSectionStart_ = fontHeaderSectionStart_ + size; + + fontHeaderCache_ = reinterpret_cast(ramAddr_); + /* align up to 4 byte, power of 2 */ + ramUsedLen_ = AlignUp(size, 2); + + ret = read(fp_, fontHeaderCache_, size); + if (ret != size) { + return INVALID_RET_VALUE; + } + + FontHeader* last = fontHeaderCache_ + fontNum_ - 1; + size = last->indexOffset + last->indexLen; + glyphNodeSectionStart_ = fontIndexSectionStart_ + size; + + size = 0; + for (uint32_t i = 0; i < fontNum_; i++) { + size += fontHeaderCache_[i].glyphNum * sizeof(GlyphNode); + } + bitMapSectionStart_ = glyphNodeSectionStart_ + size; + ret = GlyphNodeCacheInit(); + if (ret == RET_VALUE_OK) { + isFileSet_ = true; + } + + fontId_ = GetBitmapFontIdMax(); + return ret; +} + +int8_t GlyphsManager::SetCurrentFontId(uint8_t fontId) +{ + uint16_t fontIdx = 0; + if (!isFileSet_) { + return INVALID_RET_VALUE; + } + if (fontId > GetBitmapFontIdMax()) { + return INVALID_RET_VALUE; + } + if (fontId_ == fontId) { + return RET_VALUE_OK; + } + + int32_t low = 0; + int32_t high = binHeader_.fontNum - 1; + bool found = false; + + while (low <= high) { + int32_t mid = (low + high) / 2; // 2 means half + if (fontHeaderCache_[mid].fontId == fontId) { + fontIdx = mid; + found = true; + break; + } else if (fontHeaderCache_[mid].fontId > fontId) { + high = mid - 1; + } else if (fontHeaderCache_[mid].fontId < fontId) { + low = mid + 1; + } + } + if (!found) { + isFontIdSet_ = false; + curFontHeader_ = nullptr; + fontId_ = GetBitmapFontIdMax(); + return INVALID_RET_VALUE; + } + + uint32_t size = 0; + fontId_ = fontId; + curFontHeader_ = fontHeaderCache_ + fontIdx; + curGlyphNode_ = nullptr; + curFontIndexSectionStart_ = fontIndexSectionStart_ + curFontHeader_->indexOffset; + for (uint32_t i = 0; i < fontIdx; i++) { + size += fontHeaderCache_[i].glyphNum * sizeof(GlyphNode); + } + curGlyphNodeSectionStart_ = glyphNodeSectionStart_ + size; + curBitMapSectionStart_ = bitMapSectionStart_ + curFontHeader_->glyphOffset; + curIndexCache_ = indexCache_ + curFontHeader_->indexOffset; + isFontIdSet_ = true; + + return RET_VALUE_OK; +} + +int32_t GlyphsManager::GetRamUsedLen() const +{ + if (!isFileSet_) { + return INVALID_RET_VALUE; + } + return ramUsedLen_; +} + +int8_t GlyphsManager::GetFontVersion(char* version, uint8_t len) const +{ + if (!isFileSet_ || (version == nullptr) || (len > FONT_VERSION_LEN)) { + return INVALID_RET_VALUE; + } + if (memset_s(version, len, 0, len) != EOK) { + return INVALID_RET_VALUE; + } + if (strcpy_s(version, len, binHeader_.fontVersion) != EOK) { + return INVALID_RET_VALUE; + } + return RET_VALUE_OK; +} + +const FontHeader* GlyphsManager::GetCurrentFontHeader() const +{ + if (!isFontIdSet_) { + return nullptr; + } + + if (curFontHeader_ == nullptr) { + return nullptr; + } + + return curFontHeader_; +} + +const GlyphNode* GlyphsManager::GetGlyphNode(uint32_t unicode) +{ + if (!isFontIdSet_) { + return nullptr; + } + + if (curGlyphNode_ != nullptr) { + if ((curGlyphNode_->unicode == unicode) && (curGlyphNode_->reserve == fontId_)) { + return curGlyphNode_; + } + } + GlyphNode* node = GetNodeFromCache(unicode); + if (node == nullptr) { + node = GetNodeFromFile(unicode); + if (node != nullptr) { + node->reserve = fontId_; + } + } + + curGlyphNode_ = node; + return node; +} + +int16_t GlyphsManager::GetFontHeight() const +{ + if (!isFontIdSet_) { + return INVALID_RET_VALUE; + } + + if (curFontHeader_ == nullptr) { + return INVALID_RET_VALUE; + } + + return curFontHeader_->fontHeight; +} + +int16_t GlyphsManager::GetFontWidth(uint32_t unicode) +{ + const GlyphNode* node = nullptr; + + if (!isFontIdSet_) { + return INVALID_RET_VALUE; + } + node = GetGlyphNode(unicode); + if (node == nullptr) { + return INVALID_RET_VALUE; + } + return node->advance; +} + +int8_t GlyphsManager::GetBitmap(uint32_t unicode, uint8_t* bitmap) +{ + if (bitmap == nullptr) { + return INVALID_RET_VALUE; + } + if (!isFontIdSet_) { + return INVALID_RET_VALUE; + } + + const GlyphNode* node = GetGlyphNode(unicode); + if (node == nullptr) { + return INVALID_RET_VALUE; + } + + uint32_t offset = curBitMapSectionStart_ + node->dataOff; + uint32_t size = node->kernOff - node->dataOff; + int32_t ret = lseek(fp_, offset, SEEK_SET); + if (ret != static_cast(offset)) { + return INVALID_RET_VALUE; + } + + int32_t readSize = read(fp_, bitmap, size); + if (readSize != static_cast(size)) { + return INVALID_RET_VALUE; + } + + return RET_VALUE_OK; +} +} // namespace OHOS diff --git a/frameworks/font/glyphs_manager.h b/frameworks/font/glyphs_manager.h new file mode 100755 index 0000000..d13d0eb --- /dev/null +++ b/frameworks/font/glyphs_manager.h @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GLYPHS_MANAGER_FONT_H +#define GLYPHS_MANAGER_FONT_H + +#include "font/ui_font_header.h" + +namespace OHOS { +class GlyphsManager { +public: + GlyphsManager(); + + GlyphsManager(const GlyphsManager&) = delete; + + GlyphsManager& operator=(const GlyphsManager&) = delete; + + ~GlyphsManager(); + + int32_t GetRamUsedLen() const; + + int8_t GetFontVersion(char* version, uint8_t len) const; + + int16_t GetFontHeight() const; + + int16_t GetFontWidth(uint32_t unicode); + + const FontHeader* GetCurrentFontHeader() const; + + const GlyphNode* GetGlyphNode(uint32_t unicode); + + int8_t GetBitmap(uint32_t unicode, uint8_t* bitmap); + + void SetRamBuffer(uintptr_t ramAddr); + + int8_t SetFile(int32_t fp, uint32_t start); + + int8_t SetCurrentFontId(uint8_t fontId); + +private: + static constexpr uint8_t RADIX_TREE_BITS = 4; + static constexpr uint8_t RADIX_SHIFT_START = 32 - RADIX_TREE_BITS; + static constexpr uint32_t RADIX_TREE_SLOT_NUM = 1 << RADIX_TREE_BITS; + static constexpr uint32_t RADIX_TREE_MASK = RADIX_TREE_SLOT_NUM - 1; + + static constexpr uint8_t FONT_HASH_SHIFT = 3; + static constexpr uint8_t FONT_HASH_NR = 1 << FONT_HASH_SHIFT; + static constexpr uint32_t FONT_HASH_MASK = FONT_HASH_NR - 1; + static constexpr uint8_t UNICODE_HASH_SHIFT = 6; + static constexpr uint8_t UNICODE_HASH_NR = 1 << UNICODE_HASH_SHIFT; + static constexpr uint32_t UNICODE_HASH_MASK = UNICODE_HASH_NR - 1; + static constexpr uint8_t NODE_HASH_SHIFT = 4; + static constexpr uint8_t NODE_HASH_NR = 1 << NODE_HASH_SHIFT; + + using CacheType = GlyphNode[FONT_HASH_NR][UNICODE_HASH_NR][NODE_HASH_NR]; + using CacheState = uint8_t[FONT_HASH_NR][UNICODE_HASH_NR]; + + struct IndexNode { + uint16_t stubs[RADIX_TREE_SLOT_NUM]; + }; + + int8_t GlyphNodeCacheInit(); + GlyphNode* GetNodeFromCache(uint32_t unicode); + GlyphNode* GetNodeCacheSpace(uint32_t unicode); + GlyphNode* GetNodeFromFile(uint32_t unicode); + uint32_t AlignUp(uint32_t addr, uint32_t align) + { + return (((addr + (1 << align)) >> align) << align); + } + + BinHeader binHeader_; + uint8_t fontNum_; + uint32_t start_; + uint32_t fontHeaderSectionStart_; + uint32_t fontIndexSectionStart_; + uint32_t curFontIndexSectionStart_; + uint32_t glyphNodeSectionStart_; + uint32_t curGlyphNodeSectionStart_; + uint32_t bitMapSectionStart_; + uint32_t curBitMapSectionStart_; + + uint8_t* ramAddr_; + uint32_t ramUsedLen_; + FontHeader* fontHeaderCache_; + uint8_t* indexCache_; + uint8_t* curIndexCache_; + + CacheType* nodeCache_; + CacheState* cacheStatus_; + + int32_t fp_; + uint8_t fontId_; + FontHeader* curFontHeader_; + GlyphNode* curGlyphNode_; + + bool isRamSet_; + bool isFileSet_; + bool isFontIdSet_; +}; +} // namespace OHOS +#endif /* GLYPHS_MANAGER_FONT_H */ diff --git a/frameworks/font/ui_font.cpp b/frameworks/font/ui_font.cpp new file mode 100755 index 0000000..15c039f --- /dev/null +++ b/frameworks/font/ui_font.cpp @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "font/ui_font.h" +#include "common/text.h" +#include "font/ui_font_cache.h" +#if ENABLE_VECTOR_FONT +#include "font/ui_font_vector.h" +#endif // ENABLE_VECTOR_FONT +#include "graphic_config.h" +#if ENABLE_MULTI_FONT +#include "font/ui_multi_font_manager.h" +#endif + +namespace OHOS { +UIFont::UIFont() : instance_(nullptr), defaultInstance_(nullptr){}; + +UIFont::~UIFont(){}; + +UIFont* UIFont::GetInstance() +{ + static UIFont instance; +#if ENABLE_VECTOR_FONT + if (instance.instance_ == nullptr) { + instance.defaultInstance_ = new UIFontVector(); + instance.instance_ = instance.defaultInstance_; + } +#endif + return &instance; +} + +void UIFont::SetFont(BaseFont* font) +{ + if (font != nullptr) { + if (defaultInstance_ != nullptr) { + delete defaultInstance_; + defaultInstance_ = nullptr; + } + defaultInstance_ = font; + instance_ = font; + } +} + +uint8_t* UIFont::GetBitmap(uint32_t unicode, GlyphNode& glyphNode, uint8_t shapingFont) +{ + uint8_t* bitmap = nullptr; + uint8_t currentFontId = GetCurrentFontId(); +#if ENABLE_MULTI_FONT + // shaping font is in search list, search shaping font first + if (shapingFont > 1) { + bitmap = instance_->GetBitmap(unicode, glyphNode, shapingFont); + if (bitmap != nullptr) { + return bitmap; + } + } +#endif + bitmap = instance_->GetBitmap(unicode, glyphNode, currentFontId); + if (bitmap != nullptr) { + return bitmap; + } +#if ENABLE_MULTI_FONT + uint8_t* searchLists = nullptr; + int8_t listSize = UIMultiFontManager::GetInstance()->GetSearchFontList(currentFontId, &searchLists); + int8_t currentIndex = 0; + if ((searchLists == nullptr) || (listSize == 0)) { + return nullptr; + } + do { + SetCurrentFontId(searchLists[currentIndex], 0); + bitmap = instance_->GetBitmap(unicode, glyphNode, GetCurrentFontId()); + if (bitmap != nullptr) { + SetCurrentFontId(currentFontId, 0); + return bitmap; + } + // switch to next search List + currentIndex++; + } while ((currentIndex < listSize) && (searchLists != nullptr)); + SetCurrentFontId(currentFontId); +#endif + return nullptr; +} + +uint16_t UIFont::GetWidth(uint32_t unicode, uint8_t shapingId) +{ + int16_t result; + uint8_t currentFontId = GetCurrentFontId(); +#if ENABLE_MULTI_FONT + if (shapingId > 1) { + result = instance_->GetWidth(unicode, shapingId); + if (result >= 0) { + return result; + } + } +#endif + result = instance_->GetWidth(unicode, currentFontId); + if (result >= 0) { + return result; + } + +#if ENABLE_MULTI_FONT + uint8_t* searchLists = nullptr; + int8_t listSize = UIMultiFontManager::GetInstance()->GetSearchFontList(currentFontId, &searchLists); + if ((searchLists == nullptr) || (listSize == 0)) { + return 0; + } + int8_t currentIndex = 0; + do { + SetCurrentFontId(searchLists[currentIndex], 0); + result = instance_->GetWidth(unicode, GetCurrentFontId()); + if (result >= 0) { + SetCurrentFontId(currentFontId, 0); + return result; + } + currentIndex++; + } while ((currentIndex < listSize) && (searchLists != nullptr)); + SetCurrentFontId(currentFontId); +#endif + return 0; +} +} // namespace OHOS diff --git a/frameworks/font/ui_font_adaptor.cpp b/frameworks/font/ui_font_adaptor.cpp new file mode 100755 index 0000000..bb8912d --- /dev/null +++ b/frameworks/font/ui_font_adaptor.cpp @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "font/ui_font_adaptor.h" +#include "common/typed_text.h" +#include "graphic_config.h" +#if ENABLE_ICU +#include "font/ui_line_break.h" +#endif +#if ENABLE_SHAPING +#include "font/ui_text_shaping.h" +#endif +namespace OHOS { +uint32_t UIFontAdaptor::GetNextLineAndWidth(const char* txt, int16_t letterSpace, int16_t& maxWidth, bool allBreak) +{ +#if ENABLE_ICU + return UILineBreakEngine::GetInstance().GetNextLineAndWidth(txt, letterSpace, allBreak, maxWidth); +#else + uint32_t index = TypedText::GetNextLine(txt, letterSpace, maxWidth); + maxWidth = TypedText::GetTextWidth(txt, index, letterSpace); + return index; +#endif +} + +bool UIFontAdaptor::IsSameTTFId(uint8_t fontId, uint32_t unicode) +{ +#if ENABLE_SHAPING + return UITextShaping::IsSameTTFId(fontId, unicode); +#else + return true; +#endif +} +} // namespace OHOS + diff --git a/frameworks/font/ui_font_adaptor.h b/frameworks/font/ui_font_adaptor.h new file mode 100755 index 0000000..0bc1681 --- /dev/null +++ b/frameworks/font/ui_font_adaptor.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef UI_FONT_ADAPTOR +#define UI_FONT_ADAPTOR + +#include "common/typed_text.h" +#include "graphic_config.h" + +namespace OHOS { +class UIFontAdaptor : public HeapBase { +public: + static uint32_t GetNextLineAndWidth(const char* txt, int16_t letterSpace, int16_t& maxWidth, + bool allBreak = false); + + static bool IsSameTTFId(uint8_t fontId, uint32_t unicode); +}; +} // namespace OHOS +#endif // UI_FONT_ADAPTOR diff --git a/frameworks/font/ui_font_allocator.cpp b/frameworks/font/ui_font_allocator.cpp new file mode 100755 index 0000000..2e6a463 --- /dev/null +++ b/frameworks/font/ui_font_allocator.cpp @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "font/ui_font_allocator.h" + +namespace OHOS { +UIFontAllocator::UIFontAllocator() : ram_(nullptr), ramSize_(0), freeSize_(0), + minSize_(0), end_(nullptr), free_(nullptr) {} + +UIFontAllocator::~UIFontAllocator() {} + +void UIFontAllocator::SetRamAddr(uint8_t* ram, uint32_t size) +{ + struct Chunk* chunk = nullptr; + + ram_ = ram; + ramSize_ = size - sizeof(struct Chunk) * 2; // head and tail two chunk + chunk = reinterpret_cast(ram_); + chunk->next = size - sizeof(struct Chunk); + chunk->prev = 0; + chunk->used = false; + + end_ = reinterpret_cast(ram_ + size - sizeof(struct Chunk)); + end_->next = size - sizeof(struct Chunk); + end_->prev = size - sizeof(struct Chunk); + end_->used = true; + + free_ = chunk; + freeSize_ = size - sizeof(struct Chunk); +} + +uint32_t UIFontAllocator::GetSize(void *addr) +{ + struct Chunk *chunk = reinterpret_cast(static_cast(addr) - sizeof(struct Chunk)); + return chunk->next - (reinterpret_cast(addr) - reinterpret_cast(ram_)); +} + +void UIFontAllocator::SetMinChunkSize(uint32_t size) +{ + minSize_ = AlignSize(size); +} + +void* UIFontAllocator::Allocate(uint32_t size) +{ + uint32_t free, left; + uint32_t ptr, ptr2; + struct Chunk* chunk = nullptr; + struct Chunk* chunk2 = nullptr; + struct Chunk* chunk3 = nullptr; + + size = AlignSize(size); + if (size < minSize_) { + size = minSize_; + } + + for (ptr = reinterpret_cast(free_) - ram_; ptr < ramSize_; + ptr = reinterpret_cast(ram_ + ptr)->next) { + chunk = reinterpret_cast(ram_ + ptr); + if (chunk->used) { + continue; + } + free = chunk->next - ptr; + if (free < sizeof(struct Chunk) + size) { + continue; + } + left = free - sizeof(struct Chunk) - size; + if (left >= sizeof(struct Chunk) + minSize_) { + ptr2 = ptr + sizeof(struct Chunk) + size; + chunk2 = reinterpret_cast(ram_ + ptr2); + chunk2->used = false; + chunk2->next = chunk->next; + chunk2->prev = ptr; + chunk->next = ptr2; + chunk->used = true; + if (chunk2->next != end_->next) { + chunk3 = reinterpret_cast(ram_ + chunk2->next); + chunk3->prev = ptr2; + } + freeSize_ -= size + sizeof(struct Chunk); + } else { + chunk->used = true; + freeSize_ -= chunk->next - ptr; + } + + if (chunk == free_) { + struct Chunk* cur = free_; + while (cur->used && (cur != end_)) { + cur = reinterpret_cast(ram_ + cur->next); + } + free_ = cur; + } + + return reinterpret_cast(chunk) + sizeof(struct Chunk); + } + + return nullptr; +} + +void UIFontAllocator::CombineFree(struct Chunk* chunk) +{ + if (chunk == nullptr) { + return; + } + struct Chunk* nextChunk = nullptr; + struct Chunk* prevChunk = nullptr; + + nextChunk = reinterpret_cast(ram_ + chunk->next); + if (((nextChunk != chunk) && !(nextChunk->used)) && (nextChunk != end_)) { + if (free_ == nextChunk) { + free_ = chunk; + } + chunk->next = nextChunk->next; + reinterpret_cast(ram_ + nextChunk->next)->prev = reinterpret_cast(chunk) - ram_; + } + + prevChunk = reinterpret_cast(ram_ + chunk->prev); + if ((prevChunk != chunk) && !(prevChunk->used)) { + if (free_ == chunk) { + free_ = prevChunk; + } + prevChunk->next = chunk->next; + reinterpret_cast(ram_ + chunk->next)->prev = reinterpret_cast(prevChunk) - ram_; + } +} + +void UIFontAllocator::Free(void* addr) +{ + struct Chunk* chunk = nullptr; + + if (addr == nullptr) { + return; + } + + if ((reinterpret_cast(addr) < ram_) || + (reinterpret_cast(addr) > reinterpret_cast(end_))) { + return; + } + + chunk = reinterpret_cast(static_cast(addr) - sizeof(struct Chunk)); + chunk->used = false; + if (chunk < free_) { + free_ = chunk; + } + + freeSize_ += chunk->next - (reinterpret_cast(chunk) - ram_); + CombineFree(chunk); +} +} // namespace OHOS diff --git a/frameworks/font/ui_font_allocator.h b/frameworks/font/ui_font_allocator.h new file mode 100755 index 0000000..a846759 --- /dev/null +++ b/frameworks/font/ui_font_allocator.h @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef UI_FONT_ALLOCATOR_H +#define UI_FONT_ALLOCATOR_H + +#include + +namespace OHOS { +class UIFontAllocator { + static constexpr uint8_t MEM_ALIGNMENT = 4; + + struct Chunk { + uint32_t next; + uint32_t prev; + bool used; + }; + +public: + UIFontAllocator(); + + ~UIFontAllocator(); + + void SetRamAddr(uint8_t* ram, uint32_t size); + + void SetMinChunkSize(uint32_t size); + + uint32_t GetSize(void *addr); + + /** + * @brief allocate memory + * + * @param size + * @return memory address + */ + void *Allocate(uint32_t size); + + void Free(void* addr); + +private: + uint32_t AlignSize(uint32_t size) + { + return (size + MEM_ALIGNMENT - 1U) & ~(MEM_ALIGNMENT - 1U); + } + void CombineFree(Chunk* cache); + + uint8_t* ram_; + uint32_t ramSize_; + uint32_t freeSize_; + uint32_t minSize_; + struct Chunk* end_; + struct Chunk* free_; +}; +} // namespace OHOS +#endif /* UI_FONT_ALLOCATOR_H */ \ No newline at end of file diff --git a/frameworks/font/ui_font_cache.cpp b/frameworks/font/ui_font_cache.cpp new file mode 100755 index 0000000..decc66e --- /dev/null +++ b/frameworks/font/ui_font_cache.cpp @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "font/ui_font_cache.h" +#include + +namespace OHOS { +UIFontCache::UIFontCache(uint8_t* ram, uint32_t size) +{ + if (ram == nullptr) { + return; + } + allocator_.SetRamAddr(ram, size); + allocator_.SetMinChunkSize(FONT_CACHE_MIN_SIZE + sizeof(Bitmap)); + + uint32_t hashTableSize = sizeof(ListHead) * FONT_CACHE_HASH_NR; + hashTable_ = reinterpret_cast(allocator_.Allocate(hashTableSize)); + for (uint8_t i = 0; i < FONT_CACHE_HASH_NR; i++) { + ListInit(hashTable_ + i); + } + + ListInit(&lruList_); +} + +UIFontCache::~UIFontCache() {} + +void UIFontCache::UpdateLru(Bitmap* bitmap) +{ + ListDel(&bitmap->lruHead); + ListInit(&bitmap->lruHead); + ListAdd(&bitmap->lruHead, &lruList_); +} + +uint8_t* UIFontCache::GetSpace(uint8_t fontId, uint32_t unicode, uint32_t size) +{ + Bitmap* bitmap = nullptr; + + uint32_t allocSize = sizeof(Bitmap) + size; + while (bitmap == nullptr) { + bitmap = reinterpret_cast(allocator_.Allocate(allocSize)); + if (bitmap == nullptr) { + // nothing to free, return null + if ((lruList_.prev == &lruList_) && (lruList_.next == &lruList_)) { + return nullptr; + } + Bitmap* toFree = reinterpret_cast(reinterpret_cast(lruList_.prev) - + offsetof(struct Bitmap, lruHead)); + ListDel(&toFree->hashHead); + ListDel(&toFree->lruHead); + allocator_.Free(toFree); + } + } + + ListInit(&bitmap->hashHead); + ListInit(&bitmap->lruHead); + ListAdd(&bitmap->hashHead, hashTable_ + fontId % FONT_CACHE_HASH_NR); + ListAdd(&bitmap->lruHead, &lruList_); + + bitmap->fontId = fontId; + bitmap->unicode = unicode; + + return reinterpret_cast(bitmap->data); +} + +void UIFontCache::PutSpace(uint8_t* addr) +{ + if (addr == nullptr) { + return; + } + Bitmap* bitmap = reinterpret_cast(addr - sizeof(Bitmap)); + + ListDel(&bitmap->hashHead); + ListDel(&bitmap->lruHead); + + allocator_.Free(bitmap); +} + +uint8_t* UIFontCache::GetBitmap(uint8_t fontId, uint32_t unicode) +{ + Bitmap* bitmap = nullptr; + ListHead* node = nullptr; + ListHead* head = hashTable_ + fontId % FONT_CACHE_HASH_NR; + for (node = head->next; node != head; node = node->next) { + bitmap = reinterpret_cast(reinterpret_cast(node) - + offsetof(struct Bitmap, hashHead)); + if ((bitmap->fontId == fontId) && (bitmap->unicode == unicode)) { + UpdateLru(bitmap); + return reinterpret_cast(bitmap->data); + } + } + + return nullptr; +} +} // namespace OHOS diff --git a/frameworks/font/ui_font_cache.h b/frameworks/font/ui_font_cache.h new file mode 100755 index 0000000..fc9b2b0 --- /dev/null +++ b/frameworks/font/ui_font_cache.h @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef UI_FONT_CACHE_H +#define UI_FONT_CACHE_H + +#include "ui_font_allocator.h" + +namespace OHOS { +class UIFontCache { +public: + static constexpr uint8_t FONT_CACHE_HASH_NR = 32; + static constexpr uint32_t FONT_CACHE_MIN_SIZE = 20 * 20; + struct ListHead { + ListHead* prev; + ListHead* next; + }; + struct Bitmap { + ListHead hashHead; + ListHead lruHead; + uint8_t fontId; + uint32_t unicode; + uint8_t data[]; + }; + + UIFontCache(uint8_t* ram, uint32_t size); + + ~UIFontCache(); + + uint8_t* GetSpace(uint8_t fontId, uint32_t unicode, uint32_t size); + + void PutSpace(uint8_t* addr); + + uint8_t* GetBitmap(uint8_t fontId, uint32_t unicode); + +private: + void UpdateLru(Bitmap* bitmap); + void ListInit(ListHead* head) + { + head->prev = head; + head->next = head; + } + void ListAdd(ListHead* node, ListHead* head) + { + head->next->prev = node; + node->next = head->next; + node->prev = head; + head->next = node; + } + void ListDel(ListHead* node) + { + node->next->prev = node->prev; + node->prev->next = node->next; + } + + UIFontAllocator allocator_; + ListHead* hashTable_ = nullptr; + ListHead lruList_ = {}; +}; +} // namespace OHOS +#endif /* UI_FONT_CACHE_H */ \ No newline at end of file diff --git a/frameworks/font/ui_font_vector.cpp b/frameworks/font/ui_font_vector.cpp new file mode 100755 index 0000000..2d79d65 --- /dev/null +++ b/frameworks/font/ui_font_vector.cpp @@ -0,0 +1,428 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "font/ui_font_vector.h" +#include "file.h" +#include "graphic_config.h" +#include "securec.h" +#if ENABLE_MULTI_FONT +#include "font/ui_multi_font_manager.h" +#endif + +namespace OHOS { +UIFontVector::UIFontVector() +{ +#ifdef _WIN32 + ttfDir_ = _pgmptr; + size_t len = ttfDir_.size(); + size_t pos = ttfDir_.find_last_of('\\'); + if (pos != std::string::npos) { + ttfDir_.replace((pos + 1), (len - pos), VECTOR_FONT_DIR); + } +#else + ttfDir_ = VECTOR_FONT_DIR; +#endif // _WIN32 + ftLibrary_ = nullptr; + freeTypeInited_ = ((FT_Init_FreeType(&ftLibrary_) == 0) ? true : false); + SetBaseFontId(FONT_ID_MAX); + bitmapCache_ = nullptr; +} + +UIFontVector::~UIFontVector() +{ + if (freeTypeInited_) { + FT_Done_FreeType(ftLibrary_); + freeTypeInited_ = false; + UnregisterFontInfo(DEFAULT_VECTOR_FONT_FILENAME); + } + delete bitmapCache_; +} + +uint8_t UIFontVector::RegisterFontInfo(const char* ttfName, uint8_t shaping) +{ + if ((ttfName == nullptr) || !freeTypeInited_) { + return FONT_INVALID_TTF_ID; + } + int32_t j = 0; + while (j < FONT_ID_MAX) { + if ((fontInfo_[j].ttfName != nullptr) && !strncmp(fontInfo_[j].ttfName, ttfName, TTF_NAME_LEN_MAX)) { + return j; + } else if (fontInfo_[j].ttfName == nullptr) { + std::string ttfPath = ttfDir_; + ttfPath.append(ttfName); + int32_t error = FT_New_Face(ftLibrary_, ttfPath.c_str(), 0, &ftFaces_[j]); + if (error != 0) { + return FONT_INVALID_TTF_ID; + } + fontInfo_[j].ttfName = ttfName; + fontInfo_[j].shaping = shaping; + fontInfo_[j].ttfId = j; +#if ENABLE_MULTI_FONT + UIMultiFontManager::GetInstance()->UpdateScript(fontInfo_[j]); +#endif + return j; + } + j++; + } + return FONT_INVALID_TTF_ID; +} + +uint8_t UIFontVector::RegisterFontInfo(const UITextLanguageFontParam* fontsTable, uint8_t num) +{ + if (fontsTable == nullptr) { + return FONT_INVALID_TTF_ID; + } + uint8_t count = 0; + for (int i = 0; i < num; i++) { + uint8_t result = RegisterFontInfo(fontsTable[i].ttfName, fontsTable[i].shaping); + if (result == FONT_INVALID_TTF_ID) { + continue; + } + count++; + } + return count; +} + +uint8_t UIFontVector::UnregisterFontInfo(const UITextLanguageFontParam* fontsTable, uint8_t num) +{ + if (fontsTable == nullptr) { + return 0; + } + uint8_t count = 0; + for (int i = 0; i < num; i++) { + uint8_t result = UnregisterFontInfo(fontsTable[i].ttfName); + if (result == FONT_INVALID_TTF_ID) { + return FONT_INVALID_TTF_ID; + } + count++; + } + return count; +} +uint8_t UIFontVector::UnregisterFontInfo(const char* ttfName) +{ + if (ttfName != nullptr) { + int32_t i = 0; + while (i < FONT_ID_MAX) { + if ((fontInfo_[i].ttfName != nullptr) && !strncmp(fontInfo_[i].ttfName, ttfName, TTF_NAME_LEN_MAX)) { + fontInfo_[i].ttfName = nullptr; + FT_Done_Face(ftFaces_[i]); + ftFaces_[i] = nullptr; + return static_cast(i); + } + i++; + } + } + return FONT_INVALID_TTF_ID; +} + +const UITextLanguageFontParam* UIFontVector::GetFontInfo(uint8_t fontId) const +{ + if (fontId < FONT_ID_MAX) { + return static_cast(&fontInfo_[fontId]); + } + return nullptr; +} + +int32_t UIFontVector::OpenVectorFont(uint8_t ttfId) +{ + int32_t i = 0; + int32_t fp = 0; + while (i < FONT_ID_MAX) { + if (fontInfo_[i].ttfName == nullptr) { + i++; + continue; + } + if (fontInfo_[i].ttfId == ttfId) { + std::string ttfPath = ttfDir_; + ttfPath.append(fontInfo_[i].ttfName); +#ifdef _WIN32 + fp = open(ttfPath.c_str(), O_RDONLY | O_BINARY); +#else + fp = open(ttfPath.c_str(), O_RDONLY); +#endif + return fp; + } + i++; + } + return -1; +} + +bool UIFontVector::IsVectorFont() const +{ + return true; +} + +uint8_t UIFontVector::GetFontWeight(uint8_t fontId) +{ + return FONT_BPP_8; +} + +int8_t UIFontVector::SetFontPath(const char* dpath, const char* spath) +{ + if (dpath == nullptr) { + return INVALID_RET_VALUE; + } + ttfDir_ = dpath; + return RET_VALUE_OK; +} + +int8_t UIFontVector::SetCurrentFontId(uint8_t fontId, uint8_t size) +{ + if (size == 0) { + size = key_ & 0xFF; // the last 8bit is size + } + if ((fontId >= FONT_ID_MAX) || (size == 0)) { + return INVALID_RET_VALUE; + } + const UITextLanguageFontParam* fontInfo = GetFontInfo(fontId); + if ((fontInfo == nullptr) || (fontInfo->ttfName == nullptr)) { + return INVALID_RET_VALUE; + } + uint32_t key = GetKey(fontId, size); + if (key_ == key) { + return RET_VALUE_OK; + } + + if (!freeTypeInited_) { + return INVALID_RET_VALUE; + } + + // Set the size + int error = FT_Set_Char_Size(ftFaces_[fontId], size * FONT_PIXEL_IN_POINT, 0, 0, 0); + if (error != 0) { + return INVALID_RET_VALUE; + } + key_ = key; + SetBaseFontId(fontId); + uintptr_t ramAddr_ = GetRamAddr(); + uint32_t ramLen_ = GetRamLen(); + if (bitmapCache_ == nullptr) { + bitmapCache_ = new(std::nothrow) UIFontCache(reinterpret_cast(ramAddr_), ramLen_); + if (bitmapCache_ == nullptr) { + return INVALID_RET_VALUE; + } + } + return RET_VALUE_OK; +} + +uint16_t UIFontVector::GetHeight() +{ + uint8_t fontId_ = GetBaseFontId(); + if (!freeTypeInited_ || (ftFaces_[fontId_] == nullptr) || (bitmapCache_ == nullptr)) { + return 0; + } + return static_cast(ftFaces_[fontId_]->size->metrics.height / FONT_PIXEL_IN_POINT); +} + +uint8_t UIFontVector::GetShapingFontId(char* text, uint8_t& ttfId, uint32_t& script, uint8_t fontId, uint8_t size) const +{ +#if ENABLE_MULTI_FONT + const UITextLanguageFontParam* fontParam1 = GetFontInfo(fontId); + if (fontParam1 == nullptr) { + return 0; + } + if (fontParam1->shaping == 0) { + if (!UIMultiFontManager::GetInstance()->IsNeedShaping(text, ttfId, script)) { + return 0; // 0 means no need to shape + } + uint8_t* searchLists = nullptr; + int8_t length = UIMultiFontManager::GetInstance()->GetSearchFontList(fontId, &searchLists); + const UITextLanguageFontParam* fontParam2 = nullptr; + for (uint8_t i = 0; i < length; i++) { + fontParam2 = GetFontInfo(searchLists[i]); + if (fontParam2 == nullptr) { + continue; + } + if (fontParam2->ttfId == ttfId) { + return fontParam2->shaping; + } + } + return 0; + } + ttfId = fontParam1->ttfId; + +#if ENABLE_SHAPING + script = UIMultiFontManager::GetInstance()->GetScriptByTtfId(ttfId); +#endif + return fontParam1->shaping; +#else + const UITextLanguageFontParam* fontInfo = GetFontInfo(fontId); + if (fontInfo == nullptr) { + return 0; + } + ttfId = fontInfo->ttfId; + return fontInfo->shaping; +#endif +} +uint8_t UIFontVector::GetFontId(const char* ttfName, uint8_t size) const +{ + if (ttfName != nullptr) { + int32_t i = 0; + while (i < FONT_ID_MAX) { + if ((fontInfo_[i].ttfName != nullptr) && (strstr(fontInfo_[i].ttfName, ttfName) != nullptr)) { + return static_cast(i); + } + i++; + } + } + + return FONT_ID_MAX; +} + +uint8_t UIFontVector::GetFontId(uint32_t unicode) const +{ + int32_t i = 0; + uint8_t ttfId = ((unicode >> 24) & 0x1F); // 24: Whether 25 ~29 bit storage is ttfId 0x1F:5bit + while (i < FONT_ID_MAX) { + if (fontInfo_[i].ttfName == nullptr) { + i++; + continue; + } + if (fontInfo_[i].ttfId == ttfId) { + return i; + } + i++; + } + return FONT_INVALID_TTF_ID; +} + +int16_t UIFontVector::GetWidth(uint32_t unicode, uint8_t fontId) +{ + if (!freeTypeInited_ || (ftFaces_[fontId] == nullptr) || (bitmapCache_ == nullptr)) { + return INVALID_RET_VALUE; + } + uint8_t* bitmap = bitmapCache_->GetBitmap(key_, unicode); + if (bitmap != nullptr) { + return reinterpret_cast(bitmap)->advance; + } + + int8_t error = LoadGlyphIntoFace(fontId, unicode); + if (error != RET_VALUE_OK) { + return INVALID_RET_VALUE; + } + SetFace(ftFaces_[fontId], unicode); + return static_cast(ftFaces_[fontId]->glyph->advance.x / FONT_PIXEL_IN_POINT); +} + +int8_t UIFontVector::GetCurrentFontHeader(FontHeader& fontHeader) +{ + uint8_t fontId_ = GetBaseFontId(); + if (!freeTypeInited_ || (ftFaces_[fontId_] == nullptr) || (bitmapCache_ == nullptr)) { + return INVALID_RET_VALUE; + } + + fontHeader.ascender = static_cast(ftFaces_[fontId_]->size->metrics.ascender / FONT_PIXEL_IN_POINT); + fontHeader.descender = static_cast(ftFaces_[fontId_]->size->metrics.descender / FONT_PIXEL_IN_POINT); + fontHeader.fontHeight = static_cast(ftFaces_[fontId_]->size->metrics.height / FONT_PIXEL_IN_POINT); + return RET_VALUE_OK; +} + +int8_t UIFontVector::GetGlyphNode(uint32_t unicode, GlyphNode& glyphNode) +{ + uint8_t fontId_ = GetBaseFontId(); + if (!freeTypeInited_ || (ftFaces_[fontId_] == nullptr) || (bitmapCache_ == nullptr)) { + return INVALID_RET_VALUE; + } + uint8_t* bitmap = bitmapCache_->GetBitmap(key_, unicode); + if (bitmap != nullptr) { + Metric* f = reinterpret_cast(bitmap); + glyphNode.left = f->left; + glyphNode.top = f->top; + glyphNode.cols = f->cols; + glyphNode.rows = f->rows; + glyphNode.advance = f->advance; + return RET_VALUE_OK; + } + int8_t error = LoadGlyphIntoFace(fontId_, unicode); + if (error != RET_VALUE_OK) { + return INVALID_RET_VALUE; + } + + glyphNode.left = ftFaces_[fontId_]->glyph->bitmap_left; + glyphNode.top = ftFaces_[fontId_]->glyph->bitmap_top; + glyphNode.cols = ftFaces_[fontId_]->glyph->bitmap.width; + glyphNode.rows = ftFaces_[fontId_]->glyph->bitmap.rows; + glyphNode.advance = static_cast(ftFaces_[fontId_]->glyph->advance.x / FONT_PIXEL_IN_POINT); + SetFace(ftFaces_[fontId_], unicode); + return RET_VALUE_OK; +} + +uint8_t* UIFontVector::GetBitmap(uint32_t unicode, GlyphNode& glyphNode, uint8_t fontId) +{ + if (GetGlyphNode(unicode, glyphNode) != RET_VALUE_OK) { + return nullptr; + } + uint8_t* bitmap = bitmapCache_->GetBitmap(key_, unicode); + if (bitmap != nullptr) { + return bitmap + sizeof(Metric); + } + SetFace(ftFaces_[fontId], unicode); + return static_cast(ftFaces_[fontId]->glyph->bitmap.buffer); +} + +int8_t UIFontVector::LoadGlyphIntoFace(uint8_t fontId, uint32_t unicode) +{ + int32_t error; + if (IsGlyphFont(unicode) != 0) { + if (fontId != GetFontId(unicode)) { + return INVALID_RET_VALUE; + } + unicode = unicode & (0xFFFFFF); // Whether 0 ~24 bit storage is unicode + error = FT_Load_Glyph(ftFaces_[fontId], unicode, FT_LOAD_RENDER); + } else { + error = FT_Load_Char(ftFaces_[fontId], unicode, FT_LOAD_RENDER); + } + if ((error != 0) || (ftFaces_[fontId]->glyph->glyph_index == 0)) { + return INVALID_RET_VALUE; + } + return RET_VALUE_OK; +} + +uint8_t UIFontVector::IsGlyphFont(uint32_t unicode) +{ + uint8_t unicodeFontId = GetFontId(unicode); + if (unicodeFontId == FONT_INVALID_TTF_ID) { + return 0; + } else { + return fontInfo_[unicodeFontId].shaping; + } +} + +void UIFontVector::SetFace(FT_Face ftFace, uint32_t unicode) const +{ + Metric f; + f.advance = static_cast(ftFace->glyph->advance.x / FONT_PIXEL_IN_POINT); + f.left = ftFace->glyph->bitmap_left; + f.top = ftFace->glyph->bitmap_top; + f.cols = ftFace->glyph->bitmap.width; + f.rows = ftFace->glyph->bitmap.rows; + + uint32_t bitmapSize = ftFace->glyph->bitmap.width * ftFace->glyph->bitmap.rows; + uint8_t* bitmap = bitmapCache_->GetSpace(key_, unicode, bitmapSize + sizeof(Metric)); + if (bitmap != nullptr) { + if (memcpy_s(bitmap, sizeof(Metric), &f, sizeof(Metric)) != EOK) { + return; + } + if (memcpy_s(bitmap + sizeof(Metric), bitmapSize, ftFace->glyph->bitmap.buffer, bitmapSize) != EOK) { + return; + } + } +} + +inline uint32_t UIFontVector::GetKey(uint8_t fontId, uint32_t size) +{ + return ((static_cast(fontId)) << 24) + size; // fontId store at the (24+1)th bit +} +} // namespace OHOS diff --git a/frameworks/font/ui_line_break.cpp b/frameworks/font/ui_line_break.cpp new file mode 100755 index 0000000..edda071 --- /dev/null +++ b/frameworks/font/ui_line_break.cpp @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#if ENABLE_ICU +#include "font/ui_line_break.h" +namespace OHOS { +UILineBreakEngine* UILineBreakEngine::instance_ = nullptr; +uint32_t UILineBreakEngine::GetNextLineAndWidth(const char* txt, int16_t space, bool allBreak, int16_t &maxWidth, + uint16_t len) +{ + // Need to be implemented + return 0; +} +} // namespace OHOS +#endif // ENABLE_ICU \ No newline at end of file diff --git a/frameworks/font/ui_line_break.h b/frameworks/font/ui_line_break.h new file mode 100755 index 0000000..b1c3a14 --- /dev/null +++ b/frameworks/font/ui_line_break.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GRAPHIC_LITE_LINE_BREAK_H +#define GRAPHIC_LITE_LINE_BREAK_H + +#include "graphic_config.h" +#if ENABLE_ICU +#include "font/ui_font_header.h" +#include +#include +namespace OHOS { +/** + * @brief line break engine.Use ICU as core. + * + * @since 1.0 + * @version 1.0 + */ +class UILineBreakEngine : public HeapBase { +public: + /** + * @brief Get the Instance object + * + * @return UILineBreakEngine& + * @since 1.0 + * @version 1.0 + */ + static UILineBreakEngine& GetInstance() + { + if (instance_ == nullptr) { + instance_ = new UILineBreakEngine(); + } + return *instance_; + } + + // 0xFFFF: unlimit the length until the end null. + uint32_t GetNextLineAndWidth(const char* txt, + int16_t space, + bool allBreak, + int16_t &maxWidth, + uint16_t len = 0xFFFF); +private: + static UILineBreakEngine* instance_; + UILineBreakEngine() {} + ~UILineBreakEngine() {} +}; +} // namespace OHOS +#endif // ENABLE_ICU +#endif // GRAPHIC_LITE_LINE_BREAK_H diff --git a/frameworks/font/ui_multi_font_manager.cpp b/frameworks/font/ui_multi_font_manager.cpp new file mode 100755 index 0000000..d559eb6 --- /dev/null +++ b/frameworks/font/ui_multi_font_manager.cpp @@ -0,0 +1,273 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "font/ui_multi_font_manager.h" +#include "common/typed_text.h" +#include "font/ui_font.h" + +#include "mem_api.h" +#include "securec.h" +#if ENABLE_SHAPING +#include "font/ui_text_shaping.h" +#endif +#if ENABLE_MULTI_FONT +namespace OHOS { +UIMultiFontManager::UIMultiFontManager() + : arbicTtfId_(0), thaiTtfId_(0), myanmarTtfId_(0), + devanagariTtfId_(0), hebrewTtfId_(0), + bengaliTtfId_(0), topIndex_(0) +{ + const UITextLanguageFontParam* fontParam = nullptr; + uint8_t totalFontId = GetTotalFontId(); + for (uint8_t i = 0; i < totalFontId; i++) { + fontParam = UIFont::GetInstance()->GetFontInfo(i); + if (fontParam == nullptr) { + continue; + } + if (!fontParam->shaping) { + continue; + } + if (strstr(fontParam->ttfName, ARABIC_LANG) != nullptr) { + arbicTtfId_ = fontParam->ttfId; + } else if (strstr(fontParam->ttfName, THAI_LANG) != nullptr) { + thaiTtfId_ = fontParam->ttfId; + } else if (strstr(fontParam->ttfName, MYAN_LANG) != nullptr) { + myanmarTtfId_ = fontParam->ttfId; + } else if (strstr(fontParam->ttfName, DVCARI_LANG) != nullptr) { + devanagariTtfId_ = fontParam->ttfId; + } else if (strstr(fontParam->ttfName, HBREW_LANG) != nullptr) { + hebrewTtfId_ = fontParam->ttfId; + } else if (strstr(fontParam->ttfName, BENGALI_LANG) != nullptr) { + bengaliTtfId_ = fontParam->ttfId; + } + } + fontIdIndex_ = static_cast(UIMalloc(totalFontId)); + if (fontIdIndex_ == nullptr) { + return; + } + for (uint8_t index = 0; index < totalFontId; index++) { + fontIdIndex_[index] = MAX_LIST_NUM; + } + for (uint8_t index = 0; index < MAX_LIST_NUM; index++) { + fontNodes_[index].fontIds = nullptr; + fontNodes_[index].size = 0; + } +} + +UIMultiFontManager::~UIMultiFontManager() +{ + UIFree(fontIdIndex_); + fontIdIndex_ = nullptr; +} + + +int8_t UIMultiFontManager::AddNewFont(uint8_t fontListId, uint8_t *fontIds, int8_t size, uint8_t fontId) +{ + fontNodes_[fontId].fontIds = static_cast(UIMalloc(size)); + if (fontNodes_[fontId].fontIds == nullptr) { + return fontId; + } + fontIdIndex_[fontListId] = fontId; + (void)memcpy_s(fontNodes_[fontId].fontIds, size, fontIds, size); + fontNodes_[fontId].size = size; + return fontId + 1; +} + +int8_t UIMultiFontManager::UpdateFont(uint8_t fontListId, uint8_t *fontIds, uint8_t size) +{ + uint8_t index = fontIdIndex_[fontListId]; + if (index < topIndex_) { + UIFree(fontNodes_[index].fontIds); + fontNodes_[index].fontIds = nullptr; + } else { + index = topIndex_; + } + + uint8_t nextIndex = AddNewFont(fontListId, fontIds, size, index); + if (topIndex_ < nextIndex) { + topIndex_ = nextIndex; + } + return (fontNodes_[index].fontIds == nullptr) ? INVALID_RET_VALUE : RET_VALUE_OK; +} + +void UIMultiFontManager::UpdateScript(UITextLanguageFontParam& fonts) +{ + if (strstr(fonts.ttfName, ARABIC_LANG) != nullptr) { + arbicTtfId_ = fonts.ttfId; + } else if (strstr(fonts.ttfName, THAI_LANG) != nullptr) { + thaiTtfId_ = fonts.ttfId; + } else if (strstr(fonts.ttfName, MYAN_LANG) != nullptr) { + myanmarTtfId_ = fonts.ttfId; + } else if (strstr(fonts.ttfName, DVCARI_LANG) != nullptr) { + devanagariTtfId_ = fonts.ttfId; + } else if (strstr(fonts.ttfName, HBREW_LANG) != nullptr) { + hebrewTtfId_ = fonts.ttfId; + } else if (strstr(fonts.ttfName, BENGALI_LANG) != nullptr) { + bengaliTtfId_ = fonts.ttfId; + } +} + +void UIMultiFontManager::ClearSearchFontList() +{ + uint8_t totalFontId = GetTotalFontId(); + for (uint8_t index = 0; index < totalFontId; index++) { + fontIdIndex_[index] = MAX_LIST_NUM; + } + + for (auto &node : fontNodes_) { + UIFree(node.fontIds); + node.fontIds = nullptr; + node.size = 0; + } + topIndex_ = 0; +} + +int8_t UIMultiFontManager::SetSearchFontList(uint8_t fontListId, uint8_t *fontIds, uint8_t size) +{ + if ((fontListId >= GetTotalFontId()) || (fontIds == nullptr) || (size == 0) || + (fontIdIndex_ == nullptr) || (topIndex_ >= MAX_LIST_NUM)) { + return INVALID_RET_VALUE; + } + // update + return UpdateFont(fontListId, fontIds, size); +} + +int8_t UIMultiFontManager::GetSearchFontList(uint8_t fontListId, uint8_t **fontIds) +{ + if ((fontListId >= GetTotalFontId()) || (fontIds == nullptr) || + (fontIdIndex_ == nullptr) || (fontIdIndex_[fontListId] >= MAX_LIST_NUM)) { + return INVALID_RET_VALUE; + } + *fontIds = fontNodes_[fontIdIndex_[fontListId]].fontIds; + return fontNodes_[fontIdIndex_[fontListId]].size; +} + +bool UIMultiFontManager::IsNeedShaping(const char *text, uint8_t &ttfId, uint32_t &script) +{ + if (text == nullptr) { + return false; + } + + uint32_t i = 0; + while (text[i] != '\0') { + uint32_t unicode = TypedText::GetUTF8Next(text, i, i); + if (IsShapingLetter(unicode, ttfId)) { +#if ENABLE_SHAPING + script = GetScriptByTtfId(ttfId); +#endif + return true; + } + } + return false; +} + +uint8_t UIMultiFontManager::GetShapingFontId(const char* text, uint8_t fontId, uint8_t& ttfId, uint32_t& script) +{ + // the shaping font is in search list, then shapingFontId_ store the real shaping fontid + const UITextLanguageFontParam* fontParam1 = UIFont::GetInstance()->GetFontInfo(fontId); + if (fontParam1 == nullptr) { + return DEFAULT_SHAPING_ID; + } + if (!fontParam1->shaping) { + if (!IsNeedShaping(text, ttfId, script)) { + return 0; // 0 means no need to shape + } + uint8_t* searchLists = nullptr; + int8_t length = GetSearchFontList(fontId, &searchLists); + const UITextLanguageFontParam* fontParam2 = nullptr; + for (uint8_t i = 0; i < length; i++) { + fontParam2 = UIFont::GetInstance()->GetFontInfo(searchLists[i]); + if (fontParam2 == nullptr) { + continue; + } + if (fontParam2->ttfId == ttfId) { + return searchLists[i]; + } + } + return 0; + } + ttfId = fontParam1->ttfId; + +#if ENABLE_SHAPING + script = GetScriptByTtfId(ttfId); +#endif + return DEFAULT_SHAPING_ID; +} +int8_t UIMultiFontManager::IsShapingLetter(uint32_t unicode, uint8_t &ttfId) +{ + // arbic + if ((unicode <= 0x06FF) && (unicode >= 0x0600)) { + ttfId = arbicTtfId_; + return ttfId != 0; + } + // thai + if ((unicode <= 0x0E7F) && (unicode >= 0x0E00)) { + ttfId = thaiTtfId_; + return ttfId != 0; + } + // Devanagari + if ((unicode <= 0x097F) && (unicode >= 0x0900)) { + ttfId = devanagariTtfId_; + return ttfId != 0; + } + // Hebrew + if ((unicode <= 0x05FF) && (unicode >= 0x0590)) { + ttfId = hebrewTtfId_; + return ttfId != 0; + } + // Myanmar + if ((unicode <= 0x109F) && (unicode >= 0x1000)) { + ttfId = myanmarTtfId_; + return ttfId != 0; + } + // Bengali + if ((unicode <= 0x9FF) && (unicode >= 0x980)) { + ttfId = bengaliTtfId_; + return ttfId != 0; + } + return false; +} +#if ENABLE_SHAPING +uint32_t UIMultiFontManager::GetScriptByTtfId(uint8_t ttfId) +{ + // arbic + if (ttfId == arbicTtfId_) { + return SHAPING_SCRIPT_ARABIC; + } + // thai + if (ttfId == thaiTtfId_) { + return SHAPING_SCRIPT_THAI; + } + // Devanagari + if (ttfId == devanagariTtfId_) { + return SHAPING_SCRIPT_DEVANAGARI; + } + // Hebrew + if (ttfId == hebrewTtfId_) { + return SHAPING_SCRIPT_HEBREW; + } + // Myanmar + if (ttfId == myanmarTtfId_) { + return SHAPING_SCRIPT_MYANMAR; + } + // Bengali + if (ttfId == bengaliTtfId_) { + return SHAPING_SCRIPT_BENGALI; + } + return SHAPING_SCRIPT_INVALID; +} +#endif +} +#endif diff --git a/frameworks/font/ui_multi_font_manager.h b/frameworks/font/ui_multi_font_manager.h new file mode 100755 index 0000000..05a3964 --- /dev/null +++ b/frameworks/font/ui_multi_font_manager.h @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef UI_MULTI_FONT_MANAGER_H +#define UI_MULTI_FONT_MANAGER_H + +#include "graphic_config.h" +#include "font/ui_font_header.h" +#if ENABLE_MULTI_FONT +namespace OHOS { +class UIMultiFontManager : public HeapBase { +public: + UIMultiFontManager(const UIMultiFontManager &) = delete; + UIMultiFontManager& operator=(const UIMultiFontManager &) = delete; + UIMultiFontManager(UIMultiFontManager &&) noexcept = delete; + UIMultiFontManager& operator=(UIMultiFontManager &&) noexcept = delete; + + /** + * @brief Get the Instance object + * + * @return UIMultiFontManager* + */ + static UIMultiFontManager *GetInstance() + { + static UIMultiFontManager instance; + return &instance; + } + + void ClearSearchFontList(); + int8_t SetSearchFontList(uint8_t fontListId, uint8_t *fontIds, uint8_t size); + int8_t GetSearchFontList(uint8_t fontListId, uint8_t **fontIds); + void UpdateScript(UITextLanguageFontParam& fonts); + bool IsNeedShaping(const char* text, uint8_t& ttfId, uint32_t& script); + uint8_t GetShapingFontId(const char* text, uint8_t fontId, uint8_t& ttfId, uint32_t& script); +#if ENABLE_SHAPING + uint32_t GetScriptByTtfId(uint8_t ttfId); +#endif + +private: + /** + * @brief Construct a new UIMultiFontManager object + * + */ + UIMultiFontManager(); + + /** + * @brief Destroy the UIMultiFontManager object + * + */ + ~UIMultiFontManager(); + int8_t AddNewFont(uint8_t fontListId, uint8_t *fontIds, int8_t size, uint8_t fontId); + int8_t UpdateFont(uint8_t fontListId, uint8_t *fontIds, uint8_t size); + int8_t IsShapingLetter(uint32_t unicode, uint8_t& ttfId); + struct FontIdNode { + uint8_t* fontIds = nullptr; + int8_t size = 0; + }; + static constexpr uint8_t MAX_LIST_NUM = 20; + static constexpr uint8_t DEFAULT_SHAPING_ID = 1; + static constexpr const char *ARABIC_LANG = "Arabic"; + static constexpr const char *THAI_LANG = "Thai"; + static constexpr const char *MYAN_LANG = "Myanmar"; + static constexpr const char *DVCARI_LANG = "Devanagari"; + static constexpr const char *HBREW_LANG = "Hebrew"; + static constexpr const char *BENGALI_LANG = "Bengali"; + uint8_t arbicTtfId_; + uint8_t thaiTtfId_; + uint8_t myanmarTtfId_; + uint8_t devanagariTtfId_; + uint8_t hebrewTtfId_; + uint8_t bengaliTtfId_; + uint8_t topIndex_; + uint8_t* fontIdIndex_; + FontIdNode fontNodes_[MAX_LIST_NUM]; +}; +} +#endif +#endif \ No newline at end of file diff --git a/frameworks/font/ui_text_shaping.cpp b/frameworks/font/ui_text_shaping.cpp new file mode 100755 index 0000000..8f1f701 --- /dev/null +++ b/frameworks/font/ui_text_shaping.cpp @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#if ENABLE_SHAPING +#include "font/ui_text_shaping.h" + +namespace OHOS { +bool UITextShaping::IsSameTTFId(uint8_t fontId, uint32_t unicode) +{ + // Need to be implemented + return false; +} +} // namespace OHOS +#endif /* ENABLE_SHAPING */ diff --git a/frameworks/font/ui_text_shaping.h b/frameworks/font/ui_text_shaping.h new file mode 100755 index 0000000..5eb7c6f --- /dev/null +++ b/frameworks/font/ui_text_shaping.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GRAPHIC_LITE_TEXT_SHAPING_H +#define GRAPHIC_LITE_TEXT_SHAPING_H + +#include "graphic_config.h" +#if ENABLE_SHAPING +#include "font/ui_font_header.h" +#include + +enum ShapingScript { + SHAPING_SCRIPT_ARABIC, + SHAPING_SCRIPT_THAI, + SHAPING_SCRIPT_DEVANAGARI, + SHAPING_SCRIPT_HEBREW, + SHAPING_SCRIPT_MYANMAR, + SHAPING_SCRIPT_BENGALI, + SHAPING_SCRIPT_INVALID +}; + +namespace OHOS { +class UITextShaping { +public: + /** + * @brief Construct a new UITextShaping object + */ + UITextShaping(); + + /** + * @brief Destroy the UITextShaping object + */ + ~UITextShaping(); + + /** + * @brief Get the Instance object + * @return UITextShaping* the static instance + */ + static UITextShaping* GetInstance() + { + static UITextShaping instance; + return &instance; + } + + static bool IsSameTTFId(uint8_t fontId, uint32_t unicode); +}; // class UITextShaping +} // namespace OHOS +#endif // ENABLE_SHAPING +#endif // GRAPHIC_LITE_TEXT_SHAPING_H \ No newline at end of file diff --git a/frameworks/imgdecode/cache_manager.cpp b/frameworks/imgdecode/cache_manager.cpp new file mode 100755 index 0000000..e3465c8 --- /dev/null +++ b/frameworks/imgdecode/cache_manager.cpp @@ -0,0 +1,293 @@ +/* + * Copyright (c) 2020 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "imgdecode/cache_manager.h" +#include "graphic_log.h" +#include "hal_tick.h" +#include "securec.h" + +namespace OHOS { +const uint8_t* CacheEntry::GetImgData() const +{ + return dsc_.imgInfo.data; +} + +RetCode CacheEntry::ReadLine(const Point& start, int16_t len, uint8_t* buf) +{ + RetCode ret; + if (dsc_.decoder != nullptr) { + ret = dsc_.decoder->ReadLine(dsc_, start, len, buf); + } else { + ret = RetCode::FAIL; + } + + return ret; +} + +void CacheEntry::Clear() +{ + if (dsc_.decoder != nullptr) { + dsc_.decoder->Close(dsc_); + } + + dsc_.decoder = nullptr; + ClearSrc(); + dsc_.imgInfo.data = nullptr; + dsc_.fd = -1; + dsc_.srcType = IMG_SRC_UNKNOWN; + life_ = 0; +} + +void CacheEntry::ClearSrc() +{ + if (dsc_.srcType == IMG_SRC_FILE) { + UIFree(const_cast(dsc_.path)); + } + dsc_.path = nullptr; +} + +RetCode CacheEntry::SetSrc(const char* path) +{ + ClearSrc(); + if (dsc_.srcType == IMG_SRC_FILE) { + size_t strLen = strlen(path); + if (strLen > MAX_SRC_LENGTH) { + return RetCode::FAIL; + } + char* newStr = static_cast(UIMalloc(static_cast(strLen) + 1)); + if (newStr == nullptr) { + return RetCode::FAIL; + } + if (memcpy_s(newStr, strLen + 1, path, strLen) != EOK) { + UIFree(reinterpret_cast(newStr)); + newStr = nullptr; + return RetCode::FAIL; + } + newStr[strLen] = '\0'; + dsc_.path = newStr; + } else { + dsc_.path = path; + } + return RetCode::OK; +} + +RetCode CacheManager::Init(uint16_t size) +{ + if ((size == 0) || (size > MAX_CACHE_ENTRY_NUM)) { + return RetCode::FAIL; + } + Reset(); + if (entryArr_ != nullptr) { + UIFree(reinterpret_cast(entryArr_)); + } + + uint32_t tmpCacheSize = size * sizeof(CacheEntry); + entryArr_ = static_cast(UIMalloc(tmpCacheSize)); + if (entryArr_ == nullptr) { + size_ = 0; + return RetCode::FAIL; + } + + if (memset_s(entryArr_, tmpCacheSize, 0, tmpCacheSize) != EOK) { + UIFree(reinterpret_cast(entryArr_)); + entryArr_ = nullptr; + return RetCode::FAIL; + } + + size_ = size; + return RetCode::OK; +} + +RetCode CacheManager::Open(const char* path, const Style& style, CacheEntry& entry) +{ + if ((path == nullptr) || (GetSize() <= 0)) { + return RetCode::FAIL; + } + + AgingAll(); + uint16_t indexHitted = 0; + RetCode ret = GetIndex(path, indexHitted); + if (ret == RetCode::OK) { + ReadToCache(entryArr_[indexHitted]); + entry = entryArr_[indexHitted]; + return RetCode::OK; + } + + SelectEntryToReplace(indexHitted); + if (entryArr_[indexHitted].dsc_.path != nullptr) { + entryArr_[indexHitted].dsc_.decoder->Close(entryArr_[indexHitted].dsc_); + } + + uint32_t startTime = HALTick::GetInstance().GetTime(); + entryArr_[indexHitted].life_ = 0; + + ret = TryDecode(path, style, entryArr_[indexHitted]); + if (ret != RetCode::OK) { + return ret; + } + ReadToCache(entryArr_[indexHitted]); + entryArr_[indexHitted].life_ = HALTick::GetInstance().GetElapseTime(startTime); + entry = entryArr_[indexHitted]; + return RetCode::OK; +} + +RetCode CacheManager::Close(const char* path) +{ + if (path == nullptr) { + return RetCode::FAIL; + } + + for (uint16_t index = 0; index < GetSize(); index++) { + if (entryArr_[index].dsc_.srcType == IMG_SRC_FILE) { + if (entryArr_[index].dsc_.path == nullptr) { + continue; + } + if (strcmp(entryArr_[index].dsc_.path, path) == 0) { + entryArr_[index].dsc_.decoder->Close(entryArr_[index].dsc_); + Clear(entryArr_[index]); + break; + } + } else { + if (entryArr_[index].dsc_.path == path) { + entryArr_[index].dsc_.decoder->Close(entryArr_[index].dsc_); + Clear(entryArr_[index]); + break; + } + } + } + + return RetCode::OK; +} + +bool CacheManager::GetImageHeader(const char* path, ImageHeader& header) +{ + CacheEntry entry; + Style useless; + RetCode ret = Open(path, useless, entry); + if (ret != RetCode::OK) { + GRAPHIC_LOGW("CacheManager::GetImageHeader Image get info found unknown src type\n"); + return false; + } + + header = entry.GetImgHeader(); + return true; +} + +RetCode CacheManager::Reset() +{ + if (entryArr_ == nullptr) { + return RetCode::OK; + } + + for (uint16_t index = 0; index < GetSize(); index++) { + if (entryArr_[index].dsc_.path != nullptr) { + entryArr_[index].dsc_.decoder->Close(entryArr_[index].dsc_); + Clear(entryArr_[index]); + } + } + + return RetCode::OK; +} + +RetCode CacheManager::ReadToCache(CacheEntry& entry) +{ + return entry.dsc_.decoder->ReadToCache(entry.dsc_); +} + +void CacheManager::Clear(CacheEntry& entry) +{ + entry.Clear(); +} + +void CacheManager::AgingAll(int32_t time) +{ + for (uint16_t index = 0; index < GetSize(); index++) { + if (entryArr_[index].life_ > INT32_MIN + AGING_INTERVAL) { + entryArr_[index].life_ -= time; + } + } +} + +RetCode CacheManager::GetIndex(const char* path, uint16_t& hittedIndex) +{ + for (uint16_t index = 0; index < GetSize(); index++) { + if (entryArr_[index].dsc_.srcType == IMG_SRC_FILE) { + if ((entryArr_[index].dsc_.path != nullptr) && !strcmp(path, entryArr_[index].dsc_.path)) { + entryArr_[index].life_ += entryArr_[index].dsc_.timeToOpen * LIFE_GAIN_INTERVAL; + if (entryArr_[index].life_ > LIFE_LIMIT) { + entryArr_[index].life_ = LIFE_LIMIT; + } + hittedIndex = index; + return RetCode::OK; + } + } else { + ImageInfo* imgDsc = reinterpret_cast(const_cast(path)); + if ((entryArr_[index].dsc_.path == path) && (entryArr_[index].dsc_.imgInfo.data == imgDsc->data)) { + entryArr_[index].life_ += entryArr_[index].dsc_.timeToOpen * LIFE_GAIN_INTERVAL; + if (entryArr_[index].life_ > LIFE_LIMIT) { + entryArr_[index].life_ = LIFE_LIMIT; + } + hittedIndex = index; + return RetCode::OK; + } + } + } + + return RetCode::FAIL; +} + +RetCode CacheManager::SelectEntryToReplace(uint16_t& selectedIndex) +{ + selectedIndex = 0; + for (uint16_t index = 0; index < GetSize(); index++) { + if (entryArr_[index].life_ < entryArr_[selectedIndex].life_) { + selectedIndex = index; + } + } + + return RetCode::OK; +} + +RetCode CacheManager::TryDecode(const char* path, const Style& style, CacheEntry& entry) +{ + FileImgDecoder* decoder = &(FileImgDecoder::GetInstance()); + if (decoder == nullptr) { + Clear(entry); + return RetCode::FAIL; + } + + entry.dsc_.srcType = IMG_SRC_FILE; + RetCode ret = entry.SetSrc(path); + if (ret != RetCode::OK) { + Clear(entry); + return ret; + } + entry.dsc_.decoder = decoder; + + ret = entry.dsc_.decoder->GetHeader(entry.dsc_); + if (ret != RetCode::OK) { + Clear(entry); + return ret; + } + + ret = entry.dsc_.decoder->Open(entry.dsc_); + if (ret != RetCode::OK) { + Clear(entry); + return ret; + } + + return ret; +} +} // namespace OHOS diff --git a/frameworks/imgdecode/cache_manager.h b/frameworks/imgdecode/cache_manager.h new file mode 100755 index 0000000..a7258f3 --- /dev/null +++ b/frameworks/imgdecode/cache_manager.h @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GRAPHIC_LITE_CACHE_MANAGER_H +#define GRAPHIC_LITE_CACHE_MANAGER_H + +#include "file_img_decoder.h" + +namespace OHOS { +class CacheEntry : public HeapBase { +public: + CacheEntry() : dsc_{0}, life_(0) {} + + ~CacheEntry() {} + + ImageSrcType GetImgSrcType() const + { + return dsc_.srcType; + } + + const uint8_t* GetImgData() const; + + ImageHeader GetImgHeader() const + { + return dsc_.imgInfo.header; + } + + ImageInfo GetImageInfo() const + { + return dsc_.imgInfo; + } + + RetCode ReadLine(const Point& start, int16_t bufSize, uint8_t* buf); + + bool InCache() const + { + return dsc_.inCache_; + } + +private: + static constexpr uint16_t MAX_SRC_LENGTH = 4096; + + friend class CacheManager; + + void Clear(); + void ClearSrc(); + RetCode SetSrc(const char* path); + + FileImgDecoder::ImgResDsc dsc_; + int32_t life_; +}; + +class CacheManager : public HeapBase { +public: + static CacheManager& GetInstance() + { + static CacheManager instance; + return instance; + } + + RetCode Init(uint16_t size); + + uint16_t GetSize() + { + return size_; + } + + RetCode Open(const char* path, const Style& style, CacheEntry& entry); + + RetCode Close(const char* path); + + RetCode Reset(); + + RetCode ReadToCache(CacheEntry& entry); + + bool GetImageHeader(const char* path, ImageHeader& header); + +private: + CacheManager() : size_(0), entryArr_(nullptr){} + + ~CacheManager() {} + + void Clear(CacheEntry& entry); + + void AgingAll(int32_t time = AGING_INTERVAL); + + RetCode GetIndex(const char* src, uint16_t& hittedIndex); + + RetCode SelectEntryToReplace(uint16_t& selectedIndex); + + RetCode TryDecode(const char* path, const Style& style, CacheEntry& entry); + + uint16_t size_; + CacheEntry* entryArr_; + static constexpr uint8_t AGING_INTERVAL = 1; + static constexpr uint8_t LIFE_GAIN_INTERVAL = 1; + static constexpr uint16_t LIFE_LIMIT = 1000; + static constexpr uint8_t MAX_CACHE_ENTRY_NUM = 32; +}; +} // namespace OHOS + +#endif \ No newline at end of file diff --git a/frameworks/imgdecode/file_img_decoder.cpp b/frameworks/imgdecode/file_img_decoder.cpp new file mode 100755 index 0000000..8267fc0 --- /dev/null +++ b/frameworks/imgdecode/file_img_decoder.cpp @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "file.h" +#include "imgdecode/file_img_decoder.h" +#include "imgdecode/image_load.h" +#include "mem_api.h" + +namespace OHOS { +RetCode FileImgDecoder::Open(ImgResDsc& dsc) +{ +#ifdef _WIN32 + int32_t fd = open(dsc.path, O_RDONLY | O_BINARY); +#else + int32_t fd = open(dsc.path, O_RDONLY); +#endif + if (fd == -1) { + return RetCode::FAIL; + } + dsc.fd = fd; + + dsc.imgInfo.data = nullptr; + dsc.inCache_ = false; + uint8_t colorMode = dsc.imgInfo.header.colorMode; + if (IsImgValidMode(colorMode)) { + dsc.imgInfo.data = nullptr; + return RetCode::OK; + } else { + return RetCode::FAIL; + } +} + +RetCode FileImgDecoder::Close(ImgResDsc& dsc) +{ + if (dsc.imgInfo.data != nullptr) { + ImageCacheFree(dsc.imgInfo); + dsc.imgInfo.data = nullptr; + } + if (dsc.fd && (dsc.fd != -1)) { + close(dsc.fd); + dsc.fd = -1; + } + + return RetCode::OK; +} + +RetCode FileImgDecoder::GetHeader(ImgResDsc& dsc) +{ + int32_t fd; + int32_t readCount; +#ifdef _WIN32 + fd = open(dsc.path, O_BINARY); +#else + fd = open(dsc.path, O_RDONLY); +#endif + if (fd == -1) { + return RetCode::FAIL; + } + + dsc.fd = fd; + readCount = read(fd, &dsc.imgInfo.header, sizeof(ImageHeader)); + close(fd); + dsc.fd = -1; + if (readCount != sizeof(ImageHeader)) { + dsc.imgInfo.header.width = 0; + dsc.imgInfo.header.height = 0; + dsc.imgInfo.header.colorMode = UNKNOW; + return RetCode::FAIL; + } + + return RetCode::OK; +} + +RetCode FileImgDecoder::ReadLine(ImgResDsc& dsc, const Point& start, int16_t len, uint8_t* buf) +{ + if (IsImgValidMode(dsc.imgInfo.header.colorMode)) { + return ReadLineTrueColor(dsc, start, len, buf); + } + return RetCode::FAIL; +} + +RetCode FileImgDecoder::ReadToCache(ImgResDsc& dsc) +{ + struct stat info; + if (!dsc.inCache_) { + lseek(dsc.fd, 0, SEEK_SET); + int32_t readCount = read(dsc.fd, &dsc.imgInfo.header, sizeof(ImageHeader)); + if (readCount != sizeof(ImageHeader)) { + return RetCode::FAIL; + } + + int32_t ret = fstat(dsc.fd, &info); + if (ret != 0) { + return RetCode::FAIL; + } + uint32_t pxCount = info.st_size - readCount; + if (dsc.imgInfo.data != nullptr) { + ImageCacheFree(dsc.imgInfo); + } + + bool readSuccess = false; + if (dsc.imgInfo.header.compressMode != COMPRESS_MODE_NONE) { + readSuccess = ImageLoad::GetImageInfo(dsc.fd, pxCount, dsc.imgInfo); + } else { + dsc.imgInfo.dataSize = pxCount; + dsc.imgInfo.data = reinterpret_cast(ImageCacheMalloc(dsc.imgInfo)); + if (dsc.imgInfo.data == nullptr) { + return RetCode::OK; + } + uint8_t* tmp = const_cast(dsc.imgInfo.data); + readSuccess = (static_cast(pxCount) == read(dsc.fd, reinterpret_cast(tmp), pxCount)); + } + if (!readSuccess) { + ImageCacheFree(dsc.imgInfo); + dsc.imgInfo.dataSize = 0; + close(dsc.fd); + dsc.fd = -1; + return RetCode::OK; + } + dsc.inCache_ = true; + close(dsc.fd); + dsc.fd = -1; + } + + return RetCode::OK; +} + +RetCode FileImgDecoder::ReadLineTrueColor(ImgResDsc& dsc, const Point& start, int16_t len, uint8_t* buf) +{ + uint8_t pxSizeInBit = DrawUtils::GetPxSizeByImageInfo(dsc.imgInfo); + off_t res; + + uint32_t pos = ((start.y * dsc.imgInfo.header.width + start.x) * pxSizeInBit) >> BYTE_TO_BIT_SHIFT; + pos += sizeof(ImageHeader); /* Skip the header */ + res = lseek(dsc.fd, pos, SEEK_SET); + if (res == -1) { + return RetCode::FAIL; + } + uint32_t btr = len * (pxSizeInBit >> BYTE_TO_BIT_SHIFT); + int32_t br = read(dsc.fd, buf, btr); + if ((br == -1) || (btr != static_cast(br))) { + return RetCode::FAIL; + } + + return RetCode::OK; +} +} // namespace OHOS diff --git a/frameworks/imgdecode/file_img_decoder.h b/frameworks/imgdecode/file_img_decoder.h new file mode 100755 index 0000000..0e25f93 --- /dev/null +++ b/frameworks/imgdecode/file_img_decoder.h @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GRAPHIC_LITE_FILE_IMG_DECODER_H +#define GRAPHIC_LITE_FILE_IMG_DECODER_H + +#include "draw/draw_image.h" +#include "draw/draw_utils.h" + +namespace OHOS { +constexpr uint8_t BYTE_TO_BIT_SHIFT = 3; +constexpr uint8_t ONE_BIT_LEN_IN_BIT = 1; +constexpr uint8_t TWO_BIT_LEN_IN_BIT = 2; +constexpr uint8_t FOUR_BIT_LEN_IN_BIT = 4; +constexpr uint8_t BYTE_LEN_IN_BIT = 8; + +enum class RetCode { + OK, + FAIL, +}; + +class FileImgDecoder : public HeapBase { +public: + static FileImgDecoder& GetInstance() + { + static FileImgDecoder instance; + return instance; + } + + struct ImgResDsc { + FileImgDecoder* decoder; + const char* path; + ImageInfo imgInfo; + uint32_t timeToOpen; + int32_t fd; + ImageSrcType srcType; + bool inCache_; + }; + + RetCode Open(ImgResDsc& dsc); + + RetCode Close(ImgResDsc& dsc); + + RetCode GetHeader(ImgResDsc& dsc); + + RetCode ReadLine(ImgResDsc& dsc, const Point& start, int16_t len, uint8_t* buf); + + RetCode ReadToCache(ImgResDsc& dsc); + +private: + FileImgDecoder() {}; + ~FileImgDecoder() {}; + + bool IsImgValidMode(uint8_t colorMode) + { + if ((colorMode == RGB565) || (colorMode == RGB888) || (colorMode == ARGB8888)) { + return true; + } else { + return false; + } + } + RetCode ReadLineTrueColor(ImgResDsc& dsc, const Point& start, int16_t len, uint8_t* buf); + + FileImgDecoder(const FileImgDecoder&) = delete; + FileImgDecoder& operator=(const FileImgDecoder&) = delete; + FileImgDecoder(FileImgDecoder&&) = delete; + FileImgDecoder& operator=(FileImgDecoder&&) = delete; +}; +} +#endif \ No newline at end of file diff --git a/frameworks/imgdecode/image_load.cpp b/frameworks/imgdecode/image_load.cpp new file mode 100755 index 0000000..925e301 --- /dev/null +++ b/frameworks/imgdecode/image_load.cpp @@ -0,0 +1,206 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "imgdecode/image_load.h" +#include "file.h" +#include "graphic_log.h" +#include "mem_api.h" + +namespace { +const uint8_t BITMAP_ZIP_LEN = 3; +const uint8_t BITMAP_MID_BIT = 1; +const uint8_t BITMAP_LOW_BIT = 2; +const uint32_t BITMAP_ZIP24_FLAG = 0x456789; +const uint32_t BITMAP_ZIP_FLAG = 0x23456789; +const uint32_t BITMAP_ALPHA_MASK = 0xFF000000; +const uint32_t BITMAP_MAXCON_PIXNUM = 0xCB100; +const uint32_t MOVE_HIGH = 16; +const uint32_t MOVE_LOW = 8; +} // namespace + +namespace OHOS { +bool ImageLoad::CreateImage(ImageInfo& imageInfo) +{ + uint32_t bytePerPixel = 4; + ImageHeader& imageHeader = imageInfo.header; + + switch (imageHeader.colorMode) { + case ARGB8888: + bytePerPixel = 4; // 4 bytes per pixel + break; + case RGB888: + bytePerPixel = 3; // 3 bytes per pixel + break; + case RGB565: + bytePerPixel = 2; // 2 bytes per pixel + break; + default: + GRAPHIC_LOGE("CreateImage invalid colorMode."); + return false; + } + + imageInfo.dataSize = imageHeader.width * imageHeader.height * bytePerPixel; + imageInfo.data = static_cast(ImageCacheMalloc(imageInfo)); + if (imageInfo.data == nullptr) { + GRAPHIC_LOGE("ImageCacheMalloc error."); + return false; + } + + return true; +} + +bool ImageLoad::UncompressImageInZip(ImageInfo& imageInfo, uint8_t* buffer, uint32_t size) +{ + if (!CreateImage(imageInfo)) { + GRAPHIC_LOGE("Create image error."); + return false; + } + + if (imageInfo.header.colorMode == RGB888) { + return Unzip24Image(buffer, size, imageInfo); + } else { + return UnzipImage(buffer, size, imageInfo); + } +} + +bool ImageLoad::UnzipImage(uint8_t* imageBuffer, uint32_t size, ImageInfo& imageInfo) +{ + uint32_t value = 0; + uint32_t count = 0; + + if ((imageBuffer == nullptr) || (size == 0)) { + GRAPHIC_LOGE("imageHeader is null."); + return false; + } + + uint32_t* source = reinterpret_cast(imageBuffer); + uint32_t* sourceEnd = reinterpret_cast(imageBuffer + size); + uint32_t* dest = nullptr; + uint32_t* destEnd = nullptr; + + dest = reinterpret_cast(const_cast(imageInfo.data)); + destEnd = reinterpret_cast(const_cast(imageInfo.data) + imageInfo.dataSize); + + while ((source < sourceEnd) && (dest < destEnd)) { + if (*source != BITMAP_ZIP_FLAG) { + *dest++ = *source++; + } else { + source++; + value = *source++; + count = *source++; + if (destEnd < count + dest) { + break; + } + + while (count--) { + *dest++ = value; + } + } + } + + if (dest == destEnd) { + return true; + } + ImageCacheFree(imageInfo); + return false; +} + +bool ImageLoad::Unzip24Image(uint8_t* imageBuffer, uint32_t size, ImageInfo& imageInfo) +{ + if ((imageBuffer == nullptr) || (size == 0)) { + GRAPHIC_LOGE("imageHeader is null."); + return false; + } + + uint8_t* source = reinterpret_cast(imageBuffer); + uint8_t* sourceEnd = reinterpret_cast(imageBuffer + size); + uint32_t* dest = reinterpret_cast(const_cast(imageInfo.data)); + uint32_t* destEnd = reinterpret_cast(const_cast(imageInfo.data) + imageInfo.dataSize); + while ((source < sourceEnd) && (dest < destEnd)) { + uint32_t count = 0; + uint32_t value = 0; + + // Little endian + value = ((*source)) + (*(source + BITMAP_MID_BIT) << MOVE_LOW) + (*(source + BITMAP_LOW_BIT) << MOVE_HIGH); + source = source + BITMAP_ZIP_LEN; + if (value != BITMAP_ZIP24_FLAG) { + *dest = value | BITMAP_ALPHA_MASK; + dest++; + } else { + value = 0; + value = ((*source)) + (*(source + BITMAP_MID_BIT) << MOVE_LOW) + (*(source + BITMAP_LOW_BIT) << MOVE_HIGH); + source = source + BITMAP_ZIP_LEN; + + count = 0; + count = ((*source)) + (*(source + BITMAP_MID_BIT) << MOVE_LOW) + (*(source + BITMAP_LOW_BIT) << MOVE_HIGH); + source = source + BITMAP_ZIP_LEN; + + if (count > BITMAP_MAXCON_PIXNUM) { + *dest = BITMAP_ZIP24_FLAG | BITMAP_ALPHA_MASK; + dest++; + *dest = value | BITMAP_ALPHA_MASK; + dest++; + *dest = count | BITMAP_ALPHA_MASK; + dest++; + continue; + } + if (static_cast(destEnd - dest) < static_cast(count)) { + break; + } + while (count--) { + *dest = value | BITMAP_ALPHA_MASK; + dest++; + } + } + } + + if (dest == destEnd) { + return true; + } + ImageCacheFree(imageInfo); + return false; +} + +bool ImageLoad::UnZip2ImageInfo(ImageInfo& imageInfo, uint8_t* buffer, uint32_t size) +{ + switch (imageInfo.header.compressMode) { + case COMPRESS_MODE__ZIP_ALG: + return UncompressImageInZip(imageInfo, buffer, size); + default: + return false; + } +} + +bool ImageLoad::GetImageInfo(int32_t fd, uint32_t size, ImageInfo& imageInfo) +{ + if (size == 0) { + return false; + } + + uint8_t* buffer = reinterpret_cast(UIMalloc(size)); + if (buffer == nullptr) { + return false; + } + + if (read(fd, buffer, size) != static_cast(size)) { + UIFree(buffer); + GRAPHIC_LOGE("SeekImageFile error."); + return false; + } + bool ret = UnZip2ImageInfo(imageInfo, buffer, size); + UIFree(buffer); + return ret; +} +} // namespace OHOS \ No newline at end of file diff --git a/frameworks/imgdecode/image_load.h b/frameworks/imgdecode/image_load.h new file mode 100755 index 0000000..b4ed0a3 --- /dev/null +++ b/frameworks/imgdecode/image_load.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GRAPHIC_LITE_IMAGE_LOAD_H +#define GRAPHIC_LITE_IMAGE_LOAD_H + +#include "image_info.h" + +namespace OHOS { +enum CompressMode : uint8_t { + COMPRESS_MODE_NONE = 0, + COMPRESS_MODE__ZIP_ALG, + COMPRESS_MODE_BITMAP_ALG, + COMPRESS_MODE_BLOCK_ALG, +}; + +class ImageLoad { +public: + static bool GetImageInfo(int32_t fd, uint32_t size, ImageInfo& imageInfo); + +private: + ImageLoad() = delete; + ~ImageLoad() = delete; + + static bool UnZip2ImageInfo(ImageInfo& imageInfo, uint8_t* buffer, uint32_t size); + static bool UncompressImageInZip(ImageInfo& imageInfo, uint8_t* buffer, uint32_t size); + static bool CreateImage(ImageInfo& imageInfo); + static bool Unzip24Image(uint8_t* imageBuffer, uint32_t size, ImageInfo& imageInfo); + static bool UnzipImage(uint8_t* imageBuffer, uint32_t size, ImageInfo& imageInfo); +}; +} // namespace OHOS +#endif // GRAPHIC_LITE_IMAGE_LOAD_H \ No newline at end of file diff --git a/frameworks/layout/flex_layout.cpp b/frameworks/layout/flex_layout.cpp new file mode 100755 index 0000000..98205a5 --- /dev/null +++ b/frameworks/layout/flex_layout.cpp @@ -0,0 +1,502 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "layout/flex_layout.h" + +namespace OHOS { +void FlexLayout::LayoutChildren(bool needInvalidate) +{ + if (childrenHead_ == nullptr) { + return; + } + + if ((direction_ == LAYOUT_HOR) || (direction_ == LAYOUT_HOR_R)) { + LayoutHorizontal(); + } else { + LayoutVertical(); + } + if (needInvalidate) { + Invalidate(); + } +} + +void FlexLayout::GetStartPos(const int16_t& length, + int16_t& pos, + int16_t& interval, + int16_t count, + uint16_t* validLengths, + uint16_t* childsNum) +{ + if (!validLengths || !childsNum) { + return; + } + pos = 0; + interval = 0; + + if (majorAlign_ == ALIGN_START) { + pos = 0; + } else if (majorAlign_ == ALIGN_END) { + pos = length - validLengths[count]; + /* if total length of children is too long or only one child, layout them centerly no matter what key word set. + */ + } else if ((majorAlign_ == ALIGN_CENTER) || (validLengths[count] >= length) || (childsNum[count] == 1)) { + pos = (length - validLengths[count]) / 2; // 2: half + } else if (majorAlign_ == ALIGN_AROUND) { + if (childsNum[count] == 0) { + return; + } + interval = (length - validLengths[count]) / childsNum[count]; + pos = interval / 2; // 2: half + } else if (majorAlign_ == ALIGN_EVENLY) { + interval = (length - validLengths[count]) / (childsNum[count] + 1); + pos = interval; + } else { + if (childsNum[count] == 1) { + return; + } + interval = (length - validLengths[count]) / (childsNum[count] - 1); + pos = 0; + } +} + +void FlexLayout::GetNoWrapStartPos(const int16_t& length, int16_t& majorPos, int16_t& interval) +{ + uint16_t childrenNum = 0; + uint16_t totalValidLength = 0; + + CalValidLength(totalValidLength, childrenNum); + GetStartPos(length, majorPos, interval, 0, &totalValidLength, &childrenNum); +} + +void FlexLayout::GetRowStartPos(int16_t& pos, + int16_t& interval, + int16_t count, + uint16_t* rowsWidth, + uint16_t* rowsChildNum) +{ + GetStartPos(GetWidth(), pos, interval, count, rowsWidth, rowsChildNum); +} + +void FlexLayout::GetColumnStartPos(int16_t& pos, + int16_t& interval, + int16_t count, + uint16_t* columnsHeight, + uint16_t* columnsChildNum) +{ + GetStartPos(GetHeight(), pos, interval, count, columnsHeight, columnsChildNum); +} + +void FlexLayout::CalValidLength(uint16_t& totalValidLength, uint16_t& allChildNum) +{ + UIView* child = childrenHead_; + int16_t left; + int16_t right; + int16_t top; + int16_t bottom; + + /* calculate valid length of all children views */ + while (child != nullptr) { + if (child->IsVisible()) { + if ((direction_ == LAYOUT_HOR) || (direction_ == LAYOUT_HOR_R)) { + left = child->GetStyle(STYLE_MARGIN_LEFT); + right = child->GetStyle(STYLE_MARGIN_RIGHT); + totalValidLength += (child->GetRelativeRect().GetWidth() + left + right); + } else { + top = child->GetStyle(STYLE_MARGIN_TOP); + bottom = child->GetStyle(STYLE_MARGIN_BOTTOM); + totalValidLength += (child->GetRelativeRect().GetHeight() + top + bottom); + } + allChildNum++; + } + child = child->GetNextSibling(); + } +} + +void FlexLayout::CalRowCount() +{ + UIView* child = childrenHead_; + int16_t pos = 0; + int16_t left; + int16_t right; + + rowCount_ = 1; + while (child != nullptr) { + if (child->IsVisible()) { + left = child->GetStyle(STYLE_MARGIN_LEFT); + right = child->GetStyle(STYLE_MARGIN_RIGHT); + pos += left; + if ((pos + child->GetRelativeRect().GetWidth() + right) > GetWidth()) { + pos = left; + rowCount_++; + } + pos += child->GetRelativeRect().GetWidth() + right; + } + child = child->GetNextSibling(); + } +} + +void FlexLayout::GetRowMaxHeight(uint16_t size, uint16_t* maxRosHegiht) +{ + UIView* child = childrenHead_; + int16_t pos = 0; + int16_t left; + int16_t right; + int16_t top; + int16_t bottom; + uint16_t i = 0; + uint16_t height = 0; + + if ((maxRosHegiht == nullptr) || (size > rowCount_)) { + return; + } + + while (child != nullptr) { + if (child->IsVisible()) { + left = child->GetStyle(STYLE_MARGIN_LEFT); + right = child->GetStyle(STYLE_MARGIN_RIGHT); + top = child->GetStyle(STYLE_MARGIN_TOP); + bottom = child->GetStyle(STYLE_MARGIN_BOTTOM); + pos += left; + if ((pos + child->GetRelativeRect().GetWidth() + right) > GetWidth()) { + pos = left; + maxRosHegiht[i] = height; + height = 0; + i++; + } + height = MATH_MAX(height, child->GetRelativeRect().GetHeight() + top + bottom); + maxRosHegiht[i] = height; + pos += child->GetRelativeRect().GetWidth() + right; + } + child = child->GetNextSibling(); + } +} + +void FlexLayout::GetRowsWidth(uint16_t rowNum, uint16_t* rowsWidth, uint16_t* rowsChildNum) +{ + UIView* child = childrenHead_; + int16_t pos = 0; + int16_t left; + int16_t right; + uint16_t rowChildNum = 0; + uint16_t rowCount = 0; + uint16_t width = 0; + + if ((rowsWidth == nullptr) || (rowsChildNum == nullptr) || (rowNum > rowCount_)) { + return; + } + + while (child != nullptr) { + if (child->IsVisible()) { + left = child->GetStyle(STYLE_MARGIN_LEFT); + right = child->GetStyle(STYLE_MARGIN_RIGHT); + pos += left; + if ((pos + child->GetRelativeRect().GetWidth() + right) > GetWidth()) { + pos = left; + rowsWidth[rowCount] = width; + width = 0; + rowsChildNum[rowCount] = rowChildNum; + rowChildNum = 0; + rowCount++; + } + width += child->GetRelativeRect().GetWidth() + right + left; + rowsWidth[rowCount] = width; + rowChildNum++; + rowsChildNum[rowCount] = rowChildNum; + pos += child->GetRelativeRect().GetWidth() + right; + } + child = child->GetNextSibling(); + } +} + +void FlexLayout::GetCrossAxisPosY(int16_t& posY, uint16_t& count, uint16_t* rowsMaxHeight, UIView* child) +{ + if ((rowsMaxHeight == nullptr) || (child == nullptr)) { + return; + } + + uint16_t i = 0; + uint16_t offset = 0; + int16_t top = child->GetStyle(STYLE_MARGIN_TOP); + int16_t bottom = child->GetStyle(STYLE_MARGIN_BOTTOM); + + if (secondaryAlign_ == ALIGN_START) { + for (i = 0; i < count; i++) { + offset += rowsMaxHeight[i]; + } + posY = top + offset; + } else if (secondaryAlign_ == ALIGN_END) { + for (i = rowCount_ - 1; i > count; i--) { + offset += rowsMaxHeight[i]; + } + posY = GetHeight() - child->GetRelativeRect().GetHeight() - bottom - offset; + } else { + for (i = 0; i < rowCount_; i++) { + offset += rowsMaxHeight[i]; + } + offset = (rowsMaxHeight[0] - offset) / 2; // 2: half + for (i = 1; i <= count; i++) { + offset += (rowsMaxHeight[i - 1] + rowsMaxHeight[i]) / 2; // 2: half + } + posY = (GetHeight() - child->GetRelativeRect().GetHeight() - top - bottom) / 2 + top + offset; // 2: half + } +} + +void FlexLayout::LayoutHorizontal() +{ + UIView* child = childrenHead_; + int16_t interval = 0; + int16_t posX = 0; + int16_t posY = 0; + uint16_t count = 0; + uint16_t widthsBuf[MAX_COUNT_DEFAULT] = {0}; + uint16_t maxHeightsBuf[MAX_COUNT_DEFAULT] = {0}; + uint16_t childsNumBuf[MAX_COUNT_DEFAULT] = {0}; + uint16_t* rowsWidth = widthsBuf; + uint16_t* rowsMaxHeight = maxHeightsBuf; + uint16_t* rowsChildNum = childsNumBuf; + bool allocFlag = false; + + if (wrap_ == WRAP) { + CalRowCount(); + if (rowCount_ > MAX_COUNT_DEFAULT) { + rowsWidth = new uint16_t[rowCount_](); + rowsMaxHeight = new uint16_t[rowCount_](); + rowsChildNum = new uint16_t[rowCount_](); + allocFlag = true; + } + GetRowMaxHeight(rowCount_, rowsMaxHeight); + GetRowsWidth(rowCount_, rowsWidth, rowsChildNum); + GetRowStartPos(posX, interval, count, rowsWidth, rowsChildNum); + } else { + GetNoWrapStartPos(GetWidth(), posX, interval); + } + + while (child != nullptr) { + if (child->IsVisible()) { + child->ReMeasure(); + int16_t left = child->GetStyle(STYLE_MARGIN_LEFT); + int16_t right = child->GetStyle(STYLE_MARGIN_RIGHT); + posX += left; + if (((posX + child->GetRelativeRect().GetWidth() + right) > GetWidth()) && (wrap_ == WRAP)) { + GetRowStartPos(posX, interval, ++count, rowsWidth, rowsChildNum); + posX += left; + } + + GetCrossAxisPosY(posY, count, rowsMaxHeight, child); + if (direction_ == LAYOUT_HOR_R) { + child->SetPosition(GetWidth() - (posX - left) - child->GetRelativeRect().GetWidth() - right, posY); + } else { + child->SetPosition(posX, posY); + } + posX += child->GetRelativeRect().GetWidth() + right + interval; + child->LayoutChildren(); + } + child = child->GetNextSibling(); + } + + if (allocFlag) { + delete[] rowsWidth; + delete[] rowsMaxHeight; + delete[] rowsChildNum; + } +} + +void FlexLayout::CalColumnCount() +{ + UIView* child = childrenHead_; + int16_t pos = 0; + int16_t top; + int16_t bottom; + + columnCount_ = 1; + while (child != nullptr) { + if (child->IsVisible()) { + top = child->GetStyle(STYLE_MARGIN_TOP); + bottom = child->GetStyle(STYLE_MARGIN_BOTTOM); + pos += top; + if ((pos + child->GetRelativeRect().GetHeight() + bottom) > GetHeight()) { + pos = top; + columnCount_++; + } + pos += child->GetRelativeRect().GetHeight() + bottom; + } + child = child->GetNextSibling(); + } +} + +void FlexLayout::GetColumnMaxWidth(uint16_t size, uint16_t* maxColumnsWidth) +{ + UIView* child = childrenHead_; + int16_t pos = 0; + int16_t left; + int16_t right; + int16_t bottom; + uint16_t i = 0; + uint16_t width = 0; + + if ((maxColumnsWidth == nullptr) || (size > columnCount_)) { + return; + } + + while (child != nullptr) { + if (child->IsVisible()) { + left = child->GetStyle(STYLE_MARGIN_LEFT); + right = child->GetStyle(STYLE_MARGIN_RIGHT); + bottom = child->GetStyle(STYLE_MARGIN_BOTTOM); + pos += left; + if ((pos + child->GetRelativeRect().GetHeight() + bottom) > GetHeight()) { + pos = left; + maxColumnsWidth[i] = width; + width = 0; + i++; + } + width = MATH_MAX(width, child->GetRelativeRect().GetWidth() + left + right); + maxColumnsWidth[i] = width; + pos += child->GetRelativeRect().GetHeight() + bottom; + } + child = child->GetNextSibling(); + } +} + +void FlexLayout::GetColumnsHeight(uint16_t columnNum, uint16_t* columnsHeight, uint16_t* columnsChildNum) +{ + UIView* child = childrenHead_; + int16_t pos = 0; + int16_t top; + int16_t bottom; + uint16_t columnChildNum = 0; + uint16_t columnCount = 0; + uint16_t height = 0; + + if ((columnsHeight == nullptr) || (columnsChildNum == nullptr) || (columnNum > columnCount_)) { + return; + } + + while (child != nullptr) { + if (child->IsVisible()) { + top = child->GetStyle(STYLE_MARGIN_TOP); + bottom = child->GetStyle(STYLE_MARGIN_BOTTOM); + pos += top; + if ((pos + child->GetRelativeRect().GetHeight() + bottom) > GetHeight()) { + pos = top; + columnsHeight[columnCount] = height; + height = 0; + columnsChildNum[columnCount] = columnChildNum; + columnChildNum = 0; + columnCount++; + } + height += child->GetRelativeRect().GetHeight() + top + bottom; + columnsHeight[columnCount] = height; + columnChildNum++; + columnsChildNum[columnCount] = columnChildNum; + pos += child->GetRelativeRect().GetHeight() + bottom; + } + child = child->GetNextSibling(); + } +} + +void FlexLayout::GetCrossAxisPosX(int16_t& posX, uint16_t& count, uint16_t* columnsMaxWidth, UIView* child) +{ + if ((columnsMaxWidth == nullptr) || (child == nullptr)) { + return; + } + + uint16_t i = 0; + uint16_t offset = 0; + int16_t left = child->GetStyle(STYLE_MARGIN_LEFT); + int16_t right = child->GetStyle(STYLE_MARGIN_RIGHT); + + if (secondaryAlign_ == ALIGN_START) { + for (i = 0; i < count; i++) { + offset += columnsMaxWidth[i]; + } + posX = left + offset; + } else if (secondaryAlign_ == ALIGN_END) { + for (i = columnCount_ - 1; i > count; i--) { + offset += columnsMaxWidth[i]; + } + posX = GetWidth() - child->GetRelativeRect().GetWidth() - right - offset; + } else { + for (i = 0; i < columnCount_; i++) { + offset += columnsMaxWidth[i]; + } + offset = (columnsMaxWidth[0] - offset) / 2; // 2: half + for (i = 1; i <= count; i++) { + offset += (columnsMaxWidth[i - 1] + columnsMaxWidth[i]) / 2; // 2: half + } + posX = (GetWidth() - child->GetRelativeRect().GetWidth() - left - right) / 2 + left + offset; // 2: half + } +} + +void FlexLayout::LayoutVertical() +{ + UIView* child = childrenHead_; + int16_t interval = 0; + int16_t posX = 0; + int16_t posY = 0; + uint16_t count = 0; + uint16_t heightsBuf[MAX_COUNT_DEFAULT] = {0}; + uint16_t maxWidthsBuf[MAX_COUNT_DEFAULT] = {0}; + uint16_t childsNumBuf[MAX_COUNT_DEFAULT] = {0}; + uint16_t* columnsHeight = heightsBuf; + uint16_t* columnsMaxWidth = maxWidthsBuf; + uint16_t* columnsChildNum = childsNumBuf; + bool allocFlag = false; + + if (wrap_ == WRAP) { + CalColumnCount(); + if (columnCount_ > MAX_COUNT_DEFAULT) { + columnsHeight = new uint16_t[columnCount_](); + columnsMaxWidth = new uint16_t[columnCount_](); + columnsChildNum = new uint16_t[columnCount_](); + allocFlag = true; + } + GetColumnMaxWidth(columnCount_, columnsMaxWidth); + GetColumnsHeight(columnCount_, columnsHeight, columnsChildNum); + GetColumnStartPos(posY, interval, count, columnsHeight, columnsChildNum); + } else { + GetNoWrapStartPos(GetHeight(), posY, interval); + } + + while (child != nullptr) { + if (child->IsVisible()) { + child->ReMeasure(); + int16_t top = child->GetStyle(STYLE_MARGIN_TOP); + int16_t bottom = child->GetStyle(STYLE_MARGIN_BOTTOM); + posY += top; + if (((posY + child->GetRelativeRect().GetHeight() + bottom) > GetHeight()) && (wrap_ == WRAP)) { + GetColumnStartPos(posY, interval, ++count, columnsHeight, columnsChildNum); + posY += top; + } + + GetCrossAxisPosX(posX, count, columnsMaxWidth, child); + if (direction_ == LAYOUT_VER_R) { + child->SetPosition(posX, GetHeight() - (posY - top) - child->GetRelativeRect().GetHeight() - bottom); + } else { + child->SetPosition(posX, posY); + } + posY += child->GetRelativeRect().GetHeight() + bottom + interval; + child->LayoutChildren(); + } + child = child->GetNextSibling(); + } + + if (allocFlag) { + delete[] columnsHeight; + delete[] columnsMaxWidth; + delete[] columnsChildNum; + } +} +} // namespace OHOS \ No newline at end of file diff --git a/frameworks/layout/grid_layout.cpp b/frameworks/layout/grid_layout.cpp new file mode 100755 index 0000000..415102a --- /dev/null +++ b/frameworks/layout/grid_layout.cpp @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "layout/grid_layout.h" + +namespace OHOS { +void GridLayout::LayoutChildren(bool needInvalidate) +{ + if (childrenHead_ == nullptr) { + return; + } + UIView* child = childrenHead_; + if ((rows_ == 0) || (cols_ == 0)) { + RemoveAll(); + return; + } + int16_t childrenNum = 0; + while (child != nullptr) { + childrenNum++; + child = child->GetNextSibling(); + } + if (rows_ * cols_ < childrenNum) { + RemoveAll(); + return; + } + + if (direction_ == LAYOUT_HOR) { + LayoutHorizontal(); + } else { + LayoutVertical(); + } + if (needInvalidate) { + Invalidate(); + } +} + +void GridLayout::LayoutHorizontal() +{ + UIView* child = childrenHead_; + int16_t left; + int16_t right; + int16_t top; + int16_t bottom; + int16_t layoutWidth = GetWidth() / cols_; + int16_t layoutHeight = GetHeight() / rows_; + int16_t posX; + int16_t posY; + for (int16_t i = 0; i < rows_; i++) { + posY = i * layoutHeight; + for (int16_t j = 0; j < cols_; j++) { + if (child == nullptr) { + return; + } + posX = j * layoutWidth; + child->ReMeasure(); + left = child->GetStyle(STYLE_MARGIN_LEFT); + right = child->GetStyle(STYLE_MARGIN_RIGHT); + top = child->GetStyle(STYLE_MARGIN_TOP); + bottom = child->GetStyle(STYLE_MARGIN_BOTTOM); + // 2: half + int16_t actPosX = posX + (layoutWidth - child->GetRelativeRect().GetWidth() - left - right) / 2 + left; + // 2: half + int16_t actPosY = posY + (layoutHeight - child->GetRelativeRect().GetHeight() - top - bottom) / 2 + top; + child->SetPosition(actPosX, actPosY); + child->LayoutChildren(); + child->ResizeVisibleArea(posX, posY, layoutWidth, layoutHeight); + child = child->GetNextSibling(); + } + } +} + +void GridLayout::LayoutVertical() +{ + UIView* child = childrenHead_; + int16_t left; + int16_t right; + int16_t top; + int16_t bottom; + int16_t layoutWidth = GetWidth() / cols_; + int16_t layoutHeight = GetHeight() / rows_; + int16_t posX; + int16_t posY; + for (int16_t i = 0; i < cols_; i++) { + posX = i * layoutWidth; + for (int16_t j = 0; j < rows_; j++) { + if (child == nullptr) { + return; + } + posY = j * layoutHeight; + child->ReMeasure(); + left = child->GetStyle(STYLE_MARGIN_LEFT); + right = child->GetStyle(STYLE_MARGIN_RIGHT); + top = child->GetStyle(STYLE_MARGIN_TOP); + bottom = child->GetStyle(STYLE_MARGIN_BOTTOM); + // 2: half + int16_t actPosX = posX + (layoutWidth - child->GetRelativeRect().GetWidth() - left - right) / 2 + left; + // 2: half + int16_t actPosY = posY + (layoutHeight - child->GetRelativeRect().GetHeight() - top - bottom) / 2 + top; + child->SetPosition(actPosX, actPosY); + child->LayoutChildren(); + child->ResizeVisibleArea(posX, posY, layoutWidth, layoutHeight); + child = child->GetNextSibling(); + } + } +} +} // namespace OHOS \ No newline at end of file diff --git a/frameworks/layout/list_layout.cpp b/frameworks/layout/list_layout.cpp new file mode 100755 index 0000000..5ca4c13 --- /dev/null +++ b/frameworks/layout/list_layout.cpp @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "layout/list_layout.h" + +namespace OHOS { +ListLayout::ListLayout(const uint8_t direction) : listDirection_(direction) +{ + SetLayoutDirect(direction); +} + +void ListLayout::SetDirection(uint8_t direction) +{ + listDirection_ = direction; + SetLayoutDirect(direction); + OnChildChanged(); +} + +void ListLayout::SetLayoutDirect(uint8_t direction) +{ + if (direction == VERTICAL) { + direction_ = LAYOUT_VER; + } else { + direction_ = LAYOUT_HOR; + } +} + +void ListLayout::OnChildChanged() +{ + LayoutChildren(true); + Rect rect = GetAllChildRelativeRect(); + if (listDirection_ == VERTICAL) { + SetHeight(rect.GetHeight() + rect.GetTop()); + } else { + SetWidth(rect.GetWidth() + rect.GetLeft()); + } +} +} // namespace OHOS \ No newline at end of file diff --git a/frameworks/themes/theme.cpp b/frameworks/themes/theme.cpp new file mode 100755 index 0000000..4818ece --- /dev/null +++ b/frameworks/themes/theme.cpp @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "themes/theme.h" +#include "components/root_view.h" +#include "components/ui_view.h" + +namespace OHOS { +Theme::Theme() +{ + InitBasicStyle(); + InitButtonStyle(); + InitLabelStyle(); + InitPickerStyle(); + InitProgressStyle(); + InitSliderStyle(); +}; + +void Theme::InitBasicStyle() +{ + basicStyle_ = StyleDefault::GetDefaultStyle(); +} + +void Theme::InitButtonStyle() +{ + buttonStyle_.released = StyleDefault::GetButtonReleasedStyle(); + buttonStyle_.pressed = StyleDefault::GetButtonPressedStyle(); +} + +void Theme::InitLabelStyle() +{ + labelStyle_ = StyleDefault::GetLabelStyle(); +} + +void Theme::InitPickerStyle() +{ + pickerBackgroundStyle_ = StyleDefault::GetPickerBackgroundStyle(); + pickerHighlightStyle_ = StyleDefault::GetPickerHighlightStyle(); +} + +void Theme::InitProgressStyle() +{ + progressBackgroundStyle_ = StyleDefault::GetProgressBackgroundStyle(); + progressForegroundStyle_ = StyleDefault::GetProgressForegroundStyle(); +} + +void Theme::InitSliderStyle() +{ + sliderKnobStyle_ = StyleDefault::GetPickerBackgroundStyle(); +} +} // namespace OHOS \ No newline at end of file diff --git a/frameworks/themes/theme_manager.cpp b/frameworks/themes/theme_manager.cpp new file mode 100755 index 0000000..b69879a --- /dev/null +++ b/frameworks/themes/theme_manager.cpp @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "themes/theme_manager.h" + +namespace OHOS { +void ThemeManager::SetCurrent(Theme* theme) +{ + if (theme_ != nullptr) { + delete theme_; + } + theme_ = theme; +} +} // namespace OHOS \ No newline at end of file diff --git a/frameworks/window/window.cpp b/frameworks/window/window.cpp new file mode 100755 index 0000000..ef01ea3 --- /dev/null +++ b/frameworks/window/window.cpp @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "window/window.h" + +#include "window/window_impl.h" + +namespace OHOS { +Window* Window::CreateWindow(const WindowConfig& config) +{ + WindowImpl* window = new WindowImpl; + if (window != nullptr) { + if (!window->Create(config)) { + delete window; + return nullptr; + } + window->AddToDisplay(); + } + return window; +} + +void Window::DestoryWindow(Window* window) +{ + if (window != nullptr) { + WindowImpl* windowImpl = reinterpret_cast(window); + windowImpl->RemoveFromDisplay(); + windowImpl->UnbindRootView(); + windowImpl->Destory(); + delete windowImpl; + } +} +} \ No newline at end of file diff --git a/frameworks/window/window_impl.cpp b/frameworks/window/window_impl.cpp new file mode 100755 index 0000000..82be4cc --- /dev/null +++ b/frameworks/window/window_impl.cpp @@ -0,0 +1,227 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "window/window_impl.h" +#include "core/render_manager.h" +#include "dock/screen_device_proxy.h" +#include "graphic_log.h" +#include "iwindows_manager.h" + +namespace OHOS { +WindowImpl::WindowImpl() : rootView_(nullptr), iWindow_(nullptr), isShow_(false), gfxAlloc_({}) {} + +WindowImpl::~WindowImpl() {} + +bool WindowImpl::Create(const WindowConfig& config) +{ + GRAPHIC_LOGI("Create"); + if (iWindow_ == nullptr) { + config_ = config; + LiteWinConfig liteConfig; + liteConfig.rect = config.rect; + liteConfig.pixelFormat = IMAGE_PIXEL_FORMAT_ARGB8888; + liteConfig.opacity = config.opacity; + liteConfig.isModal = config.isModal; + liteConfig.compositeMode = static_cast(config.compositeMode); + iWindow_ = IWindowsManager::GetInstance()->CreateWindow(liteConfig); + if (iWindow_ == nullptr) { + return false; + } + } + return true; +} + +void WindowImpl::Destory() +{ + Flush(); + if (iWindow_ != nullptr) { + IWindowsManager::GetInstance()->RemoveWindow(iWindow_); + iWindow_ = nullptr; + } +} + +void WindowImpl::AddToDisplay() +{ + GRAPHIC_LOGI("AddToDisplay"); + RenderManager::GetInstance().AddToDisplay(this); +} + +void WindowImpl::RemoveFromDisplay() +{ + GRAPHIC_LOGI("RemoveFromDisplay"); + RenderManager::GetInstance().RemoveFromDisplay(this); +} + +void WindowImpl::BindRootView(RootView* rootView) +{ + if (rootView == nullptr) { + return; + } + UnbindRootView(); + GRAPHIC_LOGI("BindRootView"); + rootView_ = rootView; + rootView_->boundWindow_ = this; +} + +void WindowImpl::UnbindRootView() +{ + GRAPHIC_LOGI("UnbindRootView"); + if (rootView_ != nullptr) { + rootView_->boundWindow_ = nullptr; + rootView_ = nullptr; + } +} + +RootView* WindowImpl::GetRootView() +{ + return rootView_; +} + +Rect WindowImpl::GetRect() +{ + return config_.rect; +} + +void WindowImpl::Show() +{ + if (iWindow_ == nullptr) { + return; + } + + if (!isShow_) { + isShow_ = true; + Render(); + iWindow_->Show(); + } +} + +void WindowImpl::Hide() +{ + if (iWindow_ == nullptr) { + return; + } + + if (isShow_) { + isShow_ = false; + iWindow_->Hide(); + } +} + +void WindowImpl::MoveTo(int16_t x, int16_t y) +{ + GRAPHIC_LOGI("MoveTo"); + config_.rect.SetPosition(x, y); + if (iWindow_ != nullptr) { + iWindow_->MoveTo(x, y); + } +} + +void WindowImpl::Resize(int16_t width, int16_t height) +{ + GRAPHIC_LOGI("Resize"); + if ((width == config_.rect.GetWidth()) && (height == config_.rect.GetHeight())) { + return; + } + + config_.rect.Resize(width, height); + Flush(); + if (iWindow_ != nullptr) { + iWindow_->Resize(width, height); + } + + if (rootView_ != nullptr) { + rootView_->Invalidate(); + } +} + +void WindowImpl::RaiseToTop() +{ + GRAPHIC_LOGI("RaiseToTop"); + if (iWindow_ != nullptr) { + iWindow_->RaiseToTop(); + } +} + +void WindowImpl::LowerToBottom() +{ + GRAPHIC_LOGI("LowerToBottom"); + if (iWindow_ != nullptr) { + iWindow_->LowerToBottom(); + } +} + +void WindowImpl::Render() +{ + UpdateHalDisplayBuffer(); + if (gfxAlloc_.virAddr == nullptr) { + GRAPHIC_LOGE("window buffer is null, windId=%d", GetWindowId()); + return; + } + + if (rootView_ != nullptr) { + rootView_->Measure(); + rootView_->Render(); + } +} + +void WindowImpl::Update() +{ + if (iWindow_ == nullptr) { + return; + } + iWindow_->Update(); +} + +int32_t WindowImpl::GetWindowId() +{ + if (iWindow_ != nullptr) { + return iWindow_->GetWindowId(); + } else { + GRAPHIC_LOGE("iwindow is null!"); + return INVALID_WINDOW_ID; + } +} + +void WindowImpl::Flush() +{ + GRAPHIC_LOGI("Flush"); + if (iWindow_ == nullptr) { + return; + } + ISurface* surface = iWindow_->GetSurface(); + if (surface != nullptr) { + surface->Unlock(); + gfxAlloc_ = {}; + } +} + +void WindowImpl::UpdateHalDisplayBuffer() +{ + if ((gfxAlloc_.virAddr == nullptr) && (iWindow_ != nullptr)) { + ISurface* surface = iWindow_->GetSurface(); + if (surface == nullptr) { + return; + } + surface->Lock((void**)&gfxAlloc_.virAddr, (void**)&gfxAlloc_.phyAddr, &gfxAlloc_.stride); + } + AllocationInfo& gfxAlloc = ScreenDeviceProxy::GetInstance()->GetAllocationInfo(); + gfxAlloc.phyAddr = gfxAlloc_.phyAddr; + gfxAlloc.virAddr = gfxAlloc_.virAddr; + gfxAlloc.stride = gfxAlloc_.stride; + gfxAlloc.width = config_.rect.GetWidth(); + gfxAlloc.height = config_.rect.GetHeight(); + gfxAlloc.pixelFormat = IMAGE_PIXEL_FORMAT_ARGB8888; +} +} // namespace OHOS diff --git a/frameworks/window/window_impl.h b/frameworks/window/window_impl.h new file mode 100755 index 0000000..4ab9e6a --- /dev/null +++ b/frameworks/window/window_impl.h @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GRAPHIC_LITE_WINDOW_IMPL_H +#define GRAPHIC_LITE_WINDOW_IMPL_H + +#include "iwindow.h" +#include "components/root_view.h" +#include "window/window.h" +#include "dock/screen_device.h" + +namespace OHOS { +class WindowImpl : public Window { +public: + WindowImpl(); + virtual ~WindowImpl(); + + void BindRootView(RootView* rootView) override; + void UnbindRootView() override; + RootView* GetRootView() override; + Rect GetRect() override; + + void Show() override; + void Hide() override; + void MoveTo(int16_t x, int16_t y) override; + void Resize(int16_t width, int16_t height) override; + void RaiseToTop() override; + void LowerToBottom() override; + + int32_t GetWindowId() override; + + void Render(); + void Update(); + void Flush(); + bool Create(const WindowConfig& config); + void Destory(); + void AddToDisplay(); + void RemoveFromDisplay(); + +private: + void UpdateHalDisplayBuffer(); + + RootView* rootView_; + IWindow* iWindow_; + WindowConfig config_; + bool isShow_; + AllocationInfo gfxAlloc_; +}; +} +#endif \ No newline at end of file diff --git a/interfaces/innerkits/common/graphic_startup.h b/interfaces/innerkits/common/graphic_startup.h new file mode 100755 index 0000000..c2776d7 --- /dev/null +++ b/interfaces/innerkits/common/graphic_startup.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GRAPHIC_LITE_GRAPHIC_STARTUP_H +#define GRAPHIC_LITE_GRAPHIC_STARTUP_H +#include "heap_base.h" + +namespace OHOS { +class BaseFont; + +/** @brief Entry of graphic, mainly used to start graphic. */ +class GraphicStartUp : public HeapBase { +public: + static void Init(); + + static void InitFontEngine(uintptr_t psramAddr, uint32_t psramLen, const char* dPath, const char* ttfName); +}; +} // namespace OHOS +#endif // GRAPHIC_LITE_GRAPHIC_STARTUP_H diff --git a/interfaces/innerkits/common/image_decode_ability.h b/interfaces/innerkits/common/image_decode_ability.h new file mode 100755 index 0000000..1de2263 --- /dev/null +++ b/interfaces/innerkits/common/image_decode_ability.h @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GRAPHIC_LITE_IMAGEDECODE_ABILITY_H +#define GRAPHIC_LITE_IMAGEDECODE_ABILITY_H +#include "heap_base.h" + +namespace OHOS { +enum { + IMG_SUPPORT_UNKNOW = 0x0, + IMG_SUPPORT_BITMAP = 0x01, + IMG_SUPPORT_JPEG = 0x02, + IMG_SUPPORT_PNG = 0x04, +}; +class ImageDecodeAbility : public HeapBase { +public: + static ImageDecodeAbility& GetInstance() + { + static ImageDecodeAbility instance; + return instance; + } + + void SetImageDecodeAbility(uint32_t imageType) + { + imageType_ = imageType; + } + + uint32_t GetImageDecodeAbility() + { + return imageType_; + } + +private: + ImageDecodeAbility() + { +#ifdef VERSION_STANDARD + imageType_ = IMG_SUPPORT_BITMAP | IMG_SUPPORT_JPEG | IMG_SUPPORT_PNG; +#else + imageType_ = IMG_SUPPORT_BITMAP; +#endif + } + ~ImageDecodeAbility() {} + + uint32_t imageType_; + + ImageDecodeAbility(const ImageDecodeAbility&) = delete; + ImageDecodeAbility& operator=(const ImageDecodeAbility&) = delete; + ImageDecodeAbility(ImageDecodeAbility&&) = delete; + ImageDecodeAbility& operator=(ImageDecodeAbility&&) = delete; +}; +} // namespace OHOS +#endif // GRAPHIC_LITE_IMAGEDECODE_ABILITY_H diff --git a/interfaces/innerkits/common/input_device_manager.h b/interfaces/innerkits/common/input_device_manager.h new file mode 100755 index 0000000..a732a26 --- /dev/null +++ b/interfaces/innerkits/common/input_device_manager.h @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GRAPHIC_LITE_INPUT_DEVICE_MANAGER_H +#define GRAPHIC_LITE_INPUT_DEVICE_MANAGER_H + +#include "dock/input_device.h" +#include "list.h" +#include "common/task.h" + +namespace OHOS { +/** + * @brief Manage all input devices. + */ +class InputDeviceManager : public Task { +public: + /** + * @brief Get instance of InputDeviceManager. + * @returns Instance of InputDeviceManager + */ + static InputDeviceManager* GetInstance() + { + static InputDeviceManager instance; + return &instance; + } + + void Init() override; + + /** + * @brief Add an input device. + * + * @param [in] device Specific input device + */ + void Add(InputDevice* device); + + /** + * @brief Remove an input device. + * + * @param [in] Device Specific device to remove + */ + void Remove(InputDevice* device); + + /** + * Clear all display devices. + */ + void Clear(); + + void Callback() override; + +private: + InputDeviceManager() {} + ~InputDeviceManager() {} + + InputDeviceManager(const InputDeviceManager&) = delete; + InputDeviceManager& operator=(const InputDeviceManager&) = delete; + InputDeviceManager(InputDeviceManager&&) = delete; + InputDeviceManager& operator=(InputDeviceManager&&) = delete; + + List deviceList_; +}; +} // namespace OHOS +#endif // GRAPHIC_LITE_INPUT_DEVICE_MANAGER_H diff --git a/interfaces/innerkits/common/task_manager.h b/interfaces/innerkits/common/task_manager.h new file mode 100755 index 0000000..628bb14 --- /dev/null +++ b/interfaces/innerkits/common/task_manager.h @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GRAPHIC_LITE_TASK_MANAGER_H +#define GRAPHIC_LITE_TASK_MANAGER_H + +#include + +#include "list.h" +#include "common/task.h" + +namespace OHOS { +class TaskManager : public HeapBase { +public: + /** + * @brief return TaskManager's singleton + * @return TaskManager* + */ + static TaskManager* GetInstance() + { + static TaskManager taskManager; + return &taskManager; + } + + /** + * @brief add task to task manager + * @param [in] task task pointer + */ + void Add(Task* task); + + /** + * @brief del task from task manager + * @param [in] task task pointer + */ + void Remove(Task* task); + + /** + * @brief set task run + * @param [in] enable task run + */ + void SetTaskRun(bool enable) + { + canTaskRun_ = enable; + } + + /** + * @brief get task run + * @return enable task run + */ + bool GetTaskRun() const + { + return canTaskRun_; + } + + /** + * @brief run all task + */ + void TaskHandler(); + + /** + * @brief reset the mutex of task handler, must be used carefully + */ + void ResetTaskHandlerMutex(); + +protected: + List list_; /* the task list */ + bool canTaskRun_; + bool isHandlerRunning_; + uint8_t idleLast_; + +private: + TaskManager() : canTaskRun_(false), isHandlerRunning_(false), idleLast_(0) {} + ~TaskManager() {} + + static const uint16_t IDLE_MEAS_PERIOD = 500; +}; +} // namespace OHOS +#endif // GRAPHIC_LITE_TASK_MANAGER_H diff --git a/interfaces/innerkits/dock/focus_manager.h b/interfaces/innerkits/dock/focus_manager.h new file mode 100644 index 0000000..a968d54 --- /dev/null +++ b/interfaces/innerkits/dock/focus_manager.h @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GRAPHIC_LITE_FOCUS_MANAGER_H +#define GRAPHIC_LITE_FOCUS_MANAGER_H + +#include "components/ui_view.h" + +namespace OHOS { +#if ENABLE_MOTOR +/** + * @brief 震动类型. + * + * @since 5.0 + * @version 3.0 + */ +enum class MotorType { + MOTOR_TYPE_ONE, + MOTOR_TYPE_TWO, +}; + +/** + * @brief 震动函数. + * + * @param type 震动类型. + */ +typedef void(*MotorFunc)(MotorType motorType); +#endif + +class FocusManager { +public: + static FocusManager* GetInstance() + { + static FocusManager instance; + return &instance; + } + void ClearFocus() + { + view_ = nullptr; + } + + UIView* GetFocusedView() + { + return view_; + } + + void RequestFocus(UIView* view) + { + view_ = view; + } + +#if ENABLE_MOTOR + void RegisterMotorFunc(MotorFunc motorFunc) + { + motorFunc_ = motorFunc; + } + + MotorFunc GetMotorFunc() + { + return motorFunc_; + } +#endif + +private: + FocusManager() : view_(nullptr) {} + ~FocusManager() {} + UIView* view_; +#if ENABLE_MOTOR + MotorFunc motorFunc_ = nullptr; +#endif +}; +} // namespace OHOS +#endif // GRAPHIC_LITE_FOCUS_MANAGER_H diff --git a/interfaces/innerkits/dock/rotate_input_device.h b/interfaces/innerkits/dock/rotate_input_device.h new file mode 100755 index 0000000..6cfe68a --- /dev/null +++ b/interfaces/innerkits/dock/rotate_input_device.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GRAPHIC_LITE_ROTATE_INPUT_DEVICE_H +#define GRAPHIC_LITE_ROTATE_INPUT_DEVICE_H + +#include "dock/input_device.h" +#include "components/ui_view.h" + +#if ENABLE_ROTATE_INPUT +namespace OHOS { +/** @brief A Rotate input device. */ +class RotateInputDevice : public InputDevice { +public: + /** @brief Constructor */ + RotateInputDevice(){}; + /** @brief Destructor */ + virtual ~RotateInputDevice(){}; + +protected: + void DispatchEvent(const DeviceData& data) override; +}; +} // namespace OHOS + +#endif // GRAPHIC_LITE_ROTATE_INPUT_DEVICE_H +#endif \ No newline at end of file diff --git a/interfaces/innerkits/font/ui_font_vector.h b/interfaces/innerkits/font/ui_font_vector.h new file mode 100755 index 0000000..312ef96 --- /dev/null +++ b/interfaces/innerkits/font/ui_font_vector.h @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef UI_FONT_VECTOR_H +#define UI_FONT_VECTOR_H +#include "font/base_font.h" +#include "graphic_config.h" +#include "ft2build.h" +#include "freetype/freetype.h" +#include "font/ui_font_cache.h" +#include + +namespace OHOS { +class UIFontVector : public BaseFont { +public: + UIFontVector(); + + ~UIFontVector(); + UIFontVector(const UIFontVector&) = delete; + UIFontVector& operator=(const UIFontVector&) noexcept = delete; + bool IsVectorFont() const override; + int8_t SetFontPath(const char* dpath, const char* spath) override; + int8_t SetCurrentFontId(uint8_t fontId, uint8_t size = 0) override; + uint16_t GetHeight() override; + uint8_t GetFontId(const char* ttfName, uint8_t size = 0) const override; + int16_t GetWidth(uint32_t unicode, uint8_t fontId) override; + uint8_t* GetBitmap(uint32_t unicode, GlyphNode& glyphNode, uint8_t fontId) override; + int8_t GetCurrentFontHeader(FontHeader& fontHeader) override; + int8_t GetGlyphNode(uint32_t unicode, GlyphNode& glyphNode) override; + uint8_t GetFontWeight(uint8_t fontId) override; + uint8_t GetShapingFontId(char* text, uint8_t& ttfId, uint32_t& script, + uint8_t fontId, uint8_t size) const override; + uint8_t RegisterFontInfo(const char* ttfName, uint8_t shaping = 0) override; + uint8_t RegisterFontInfo(const UITextLanguageFontParam* fontsTable, uint8_t num) override; + uint8_t UnregisterFontInfo(const char* ttfName) override; + uint8_t UnregisterFontInfo(const UITextLanguageFontParam* fontsTable, uint8_t num) override; + const UITextLanguageFontParam* GetFontInfo(uint8_t fontId) const override; + int32_t OpenVectorFont(uint8_t ttfId) override; + +private: + static constexpr uint8_t FONT_ID_MAX = 0xFF; + static constexpr uint8_t FONT_INVALID_TTF_ID = 0xFF; + static constexpr uint8_t TTF_NAME_LEN_MAX = 128; + static constexpr uint8_t FONT_BPP_8 = 8; + UITextLanguageFontParam fontInfo_[FONT_ID_MAX] = {{0}}; + std::string ttfDir_; + FT_Library ftLibrary_; + FT_Face ftFaces_[FONT_ID_MAX] = {0}; + bool freeTypeInited_; + uint32_t key_ = 0; + UIFontCache* bitmapCache_; + struct Metric { + int left; + int top; + int cols; + int rows; + int advance; + uint8_t buf[0]; + }; + void SetFace(FT_Face ftface, uint32_t unicode) const; + uint8_t GetFontId(uint32_t unicode) const; + uint32_t GetKey(uint8_t fontId, uint32_t size); + int8_t LoadGlyphIntoFace(uint8_t fontId, uint32_t unicode); + uint8_t IsGlyphFont(uint32_t unicode); +}; +} // namespace OHOS +#endif + diff --git a/interfaces/kits/animator/animator.h b/interfaces/kits/animator/animator.h new file mode 100755 index 0000000..54ff13e --- /dev/null +++ b/interfaces/kits/animator/animator.h @@ -0,0 +1,349 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @addtogroup UI_Animator + * @{ + * + * @brief Defines UI animation effects and provides matched curves. + * + * @since 1.0 + * @version 1.0 + */ + +/** + * @file animator.h + * + * @brief Defines the attributes and common functions of the animator module. + * + * @since 1.0 + * @version 1.0 + */ + +#ifndef GRAPHIC_LITE_ANIMATOR_H +#define GRAPHIC_LITE_ANIMATOR_H + +#include +#include "components/ui_view.h" +#include "list.h" +#include "common/task.h" + +namespace OHOS { +class Animator; + +/** + * @brief Represents the animator callback. + * + * You need to implement the callback function to produce specific animator effects. + * + * @since 1.0 + * @version 1.0 + */ +class AnimatorCallback : public HeapBase { +public: + /** + * @brief Called when each frame starts. This is a pure virtual function, which needs your inheritance + * and implementation. + * + * @param view Indicates the UIView instance, which is added from the constructor of + * the Animator class. + * @since 1.0 + * @version 1.0 + */ + virtual void Callback(UIView* view) = 0; + + /** + * @brief Called when an animator stops. This is a pure virtual function, which needs your inheritance and + * implementation. + * + * @param view Indicates the UIView instance, which is added from the constructor of + * the Animator class. + * @since 1.0 + * @version 1.0 + */ + virtual void OnStop(UIView& view) {}; + + /** + * @brief A default destructor used to delete an AnimatorCallback instance. + * + * @since 1.0 + * @version 1.0 + */ + virtual ~AnimatorCallback() {} +}; + +/** + * @brief Represents an animator. + * + * This class is used to set the animator attributes, such as the duration, whether an animator is repeated, + * start and stop of an animator. + * + * @see Animator + * @since 1.0 + * @version 1.0 + */ +class Animator : public HeapBase { +public: + /** + * @brief Enumerates the states of this animator. + */ + enum : uint8_t { + /** Stop */ + STOP, + /** Start */ + START, + /** Pause */ + PAUSE, + /** Running (reserved and not used currently) */ + RUNNING + }; + + /** + * @brief A default constructor used to create an Animator instance. + * + * @since 1.0 + * @version 1.0 + */ + Animator() + : callback_(nullptr), view_(nullptr), state_(STOP), time_(0), repeat_(false), runTime_(0), lastRunTime_(0) {} + + /** + * @brief A constructor used to create an Animator instance. + * + * @param callback Indicates the animator callback for producing animator effects. + * For details, see {@link AnimatorCallback}. + * @param view Indicates the UIView instance bound to an animator, which can be used when invoking + * the animator callback. + * @param time Indicates the duration of this animator, in milliseconds. + * @param repeat Specifies whether to repeat this animator. true indicates the animator is repeated, + * and false (default value) indicates the animator is played once. + * @since 1.0 + * @version 1.0 + */ + Animator(AnimatorCallback* callback, UIView* view, uint32_t time, bool repeat) + : callback_(callback), view_(view), state_(STOP), time_(time), repeat_(repeat), runTime_(0), lastRunTime_(0) {} + + /** + * @brief A destructor used to delete the Animator instance. + * + * @since 1.0 + * @version 1.0 + */ + virtual ~Animator() {} + + /** + * @brief Starts this animator. + * + * @see Stop + * @since 1.0 + * @version 1.0 + */ + void Start(); + + /** + * @brief Stops this animator. + * + * @see Start + * @since 1.0 + * @version 1.0 + */ + void Stop(); + + /** + * @brief Pauses this animator. + * + * @see Resume + * @since 1.0 + * @version 1.0 + */ + void Pause(); + + /** + * @brief Resumes this animator from where it was paused. + * + * @see Pause + * @since 1.0 + * @version 1.0 + */ + void Resume(); + + /** + * @brief Obtains the current state of this animator. + * + * @return Returns the current animator state, which can be {@link START}, {@link STOP}, or {@link PAUSE}. + * @see SetState + * @since 1.0 + * @version 1.0 + */ + uint8_t GetState() const + { + return state_; + } + + /** + * @brief Sets the current state for this animator. + * + * @param state Indicates the current animator state to set, which can be {@link STOP}, {@link START}, + * or {@link PAUSE}. + * @see GetState + * @since 1.0 + * @version 1.0 + */ + void SetState(uint8_t state) + { + state_ = state; + } + + /** + * @brief Obtains the total duration of this animator. + * + * @return Returns the total duration. + * @see SetTime + * @since 1.0 + * @version 1.0 + */ + uint32_t GetTime() const + { + return time_; + } + + /** + * @brief Sets the total duration for this animator. + * + * @param time Indicates the total duration to set, in milliseconds. + * @see GetTime + * @since 1.0 + * @version 1.0 + */ + void SetTime(uint32_t time) + { + time_ = time; + } + + /** + * @brief Obtains the running time of this animator. + * + * @return Returns the running time. + * @see SetRunTime + * @since 1.0 + * @version 1.0 + */ + uint32_t GetRunTime() const + { + return runTime_; + } + + /** + * @brief Sets the running time for this animator. + * + * @param runTime Indicates the running time to set, in milliseconds. + * @see GetRunTime + * @since 1.0 + * @version 1.0 + */ + void SetRunTime(uint32_t runTime) + { + runTime_ = runTime; + } + + /** + * @brief Checks whether this animator is repeated. + * + * @return Returns true if the animator is repeated; returns false if the animator is played once. + * @since 1.0 + * @version 1.0 + */ + bool IsRepeat() const + { + return repeat_; + } + + void Run(); + +protected: + AnimatorCallback* callback_; + UIView* view_; + uint8_t state_; + uint32_t time_; + bool repeat_; + uint32_t runTime_; + uint32_t lastRunTime_; +}; + +/** + * @brief Represents the animator manager. + * + * This is a singleton class used to manage Animator instances. + * + * @see Task + * @since 1.0 + * @version 1.0 + */ +class AnimatorManager : public Task { +public: + /** + * @brief Obtains the AnimatorManager instance. + * + * @return Returns the AnimatorManager instance. + * @since 1.0 + * @version 1.0 + */ + static AnimatorManager* GetInstance() + { + static AnimatorManager animatorManager; + return &animatorManager; + } + + void Init() override; + + /** + * @brief Adds the Animator instance to the AnimatorManager linked list for management, + * so that the {@link Run} function of the Animator class is called once for each frame. + * + * @param animator Indicates the pointer to the Animator instance to add. + * @see Remove + * @since 1.0 + * @version 1.0 + */ + void Add(Animator* animator); + + /** + * @brief Removes the Animator instance from the AnimatorManager linked list. + * + * @param animator Indicates the pointer to the Animator instance to remove. + * @see Add + * @since 1.0 + * @version 1.0 + */ + void Remove(const Animator* animator); + + void AnimatorTask(); + + void Callback() override + { + AnimatorTask(); + } + +protected: + List list_; + AnimatorManager() {} + virtual ~AnimatorManager() {} + AnimatorManager(const AnimatorManager&) = delete; + AnimatorManager& operator=(const AnimatorManager&) = delete; + AnimatorManager(AnimatorManager&&) = delete; + AnimatorManager& operator=(AnimatorManager&&) = delete; +}; +} +#endif diff --git a/interfaces/kits/animator/easing_equation.h b/interfaces/kits/animator/easing_equation.h new file mode 100755 index 0000000..e28f121 --- /dev/null +++ b/interfaces/kits/animator/easing_equation.h @@ -0,0 +1,425 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @addtogroup UI_Animator + * @{ + * + * @brief Defines UI animation effects and provides matched curves. + * + * @since 1.0 + * @version 1.0 + */ + +/** + * @file easing_equation.h + * + * @brief Defines the attributes and functions of the animation easing module. + * + * @since 1.0 + * @version 1.0 + */ + +#ifndef GRAPHIC_LITE_EASING_EQUATION_H +#define GRAPHIC_LITE_EASING_EQUATION_H + +#include "heap_base.h" +#include + +namespace OHOS { +/** + * @brief Calculates the value for the current time of an animation. + * + * @param startPos Indicates the start value of this animation. + * @param endPos Indicates the end value of this animation. + * @param curTime Indicates the current time of this animation. + * @param durationTime Indicates the total duration of this animation. + * + * @brief Returns the value for the current time. + */ +typedef int16_t (*EasingFunc)(int16_t startPos, int16_t endPos, uint16_t curTime, uint16_t durationTime); + +/** + * @brief Defines functions for specifying the velocity of an animation. + * + * @since 1.0 + * @version 1.0 + */ +class EasingEquation : public HeapBase { +public: + /** + * @brief Sets the parameter s in the equation (s+1)*t^3 - s*t^2 for a back easing. + * + * A larger s indicates a larger degree of overshoot. The default value is 1.7. You are advised to set + * this parameter to a value ranging from 1 to 4. The setting takes effect for all the back-ease animations. + * + * @param overshoot Indicates the overshoot s in the equation to set. + * @see BackEaseIn | BackEaseOut | BackEaseInOut + * @since 1.0 + * @version 1.0 + */ + static void SetBackOvershoot(double overshoot); + + /** + * @brief Eases in with an overshoot. + * + * (s+1)*t^3 - s*t^2 is the equation for a back easing. + * The animation moves back slightly at the beginning and then accelerates towards the end. + * + * @param startPos Indicates the start value of this animation. + * @param endPos Indicates the end value of this animation. + * @param curTime Indicates the current time of this animation. + * @param durationTime Indicates the total duration of this animation. + * + * @return Returns the value for the current time. + * @see SetBackOvershoot | BackEaseOut | BackEaseInOut + * @since 1.0 + * @version 1.0 + */ + static int16_t BackEaseIn(int16_t startPos, int16_t endPos, uint16_t curTime, uint16_t durationTime); + + /** + * @brief Eases out with an overshoot. + * + * (s+1)*t^3 - s*t^2 is the equation for a back easing. + * The animation moves towards the end, slightly exceeds it and finally comes back. + * + * @param startPos Indicates the start value of this animation. + * @param endPos Indicates the end value of this animation. + * @param curTime Indicates the current time of this animation. + * @param durationTime Indicates the total duration of this animation. + * + * @return Returns the value for the current time. + * @see SetBackOvershoot | BackEaseIn | BackEaseInOut + * @since 1.0 + * @version 1.0 + */ + static int16_t BackEaseOut(int16_t startPos, int16_t endPos, uint16_t curTime, uint16_t durationTime); + + /** + * @brief Eases in and then out with an overshoot. + * + * (s+1)*t^3 - s*t^2 is the equation for a back easing. The animation slightly moves back at the beginning, + * goes towards the end, slightly exceeds it and finally comes back. + * + * @param startPos Indicates the start value of this animation. + * @param endPos Indicates the end value of this animation. + * @param curTime Indicates the current time of this animation. + * @param durationTime Indicates the total duration of this animation. + * + * @return Returns the value for the current time. + * @see SetBackOvershoot | BackEaseIn | BackEaseOut + * @since 1.0 + * @version 1.0 + */ + static int16_t BackEaseInOut(int16_t startPos, int16_t endPos, uint16_t curTime, uint16_t durationTime); + + /** + * @brief Eases in shaping like a circular curve. + * + * sqrt(1-t^2) is the equation for a circular easing. The animation starts slowly from zero velocity and + * accelerates fast towards the end. The acceleration change is similar to a circular curve. + * + * @param startPos Indicates the start value of this animation. + * @param endPos Indicates the end value of this animation. + * @param curTime Indicates the current time of this animation. + * @param durationTime Indicates the total duration of this animation. + * + * @return Returns the value for the current time. + * @see CircEaseOut | CircEaseInOut + * @since 1.0 + * @version 1.0 + */ + static int16_t CircEaseIn(int16_t startPos, int16_t endPos, uint16_t curTime, uint16_t durationTime); + + /** + * @brief Eases out shaping like a circular curve. + * + * sqrt(1-t^2) is the equation for a circular easing. The animation starts fast and decelerates slowly + * towards the end. The acceleration change is similar to a circular curve. + * + * @param startPos Indicates the start value of this animation. + * @param endPos Indicates the end value of this animation. + * @param curTime Indicates the current time of this animation. + * @param durationTime Indicates the total duration of this animation. + * + * @return Returns the value for the current time. + * @see CircEaseIn | CircEaseInOut + * @since 1.0 + * @version 1.0 + */ + static int16_t CircEaseOut(int16_t startPos, int16_t endPos, uint16_t curTime, uint16_t durationTime); + + /** + * @brief Eases in and then out shaping like a circular curve. + * + * sqrt(1-t^2) is the equation for a circular easing. The animation accelerates slowly until halfway and + * decreases slowly towards the end. The acceleration change is similar to a circular curve. + * + * @param startPos Indicates the start value of this animation. + * @param endPos Indicates the end value of this animation. + * @param curTime Indicates the current time of this animation. + * @param durationTime Indicates the total duration of this animation. + * + * @return Returns the value for the current time. + * @see CircEaseIn | CircEaseOut + * @since 1.0 + * @version 1.0 + */ + static int16_t CircEaseInOut(int16_t startPos, int16_t endPos, uint16_t curTime, uint16_t durationTime); + + /** + * @brief Eases in shaping like a cubic curve. + * + * t^3 is the equation for a cubic easing. The animation starts slowly from zero velocity and accelerates + * fast towards the end. The acceleration change is similar to a cubic curve. + * + * @param startPos Indicates the start value of this animation. + * @param endPos Indicates the end value of this animation. + * @param curTime Indicates the current time of this animation. + * @param durationTime Indicates the total duration of this animation. + * + * @return Returns the value for the current time. + * @see CubicEaseOut | CubicEaseInOut + * @since 1.0 + * @version 1.0 + */ + static int16_t CubicEaseIn(int16_t startPos, int16_t endPos, uint16_t curTime, uint16_t durationTime); + + /** + * @brief Eases out shaping like a cubic curve. + * + * t^3 is the equation for a cubic easing. The animation starts fast and decelerates slowly + * towards the end. The acceleration change is similar to a cubic curve. + * + * @param startPos Indicates the start value of this animation. + * @param endPos Indicates the end value of this animation. + * @param curTime Indicates the current time of this animation. + * @param durationTime Indicates the total duration of this animation. + * + * @return Returns the value for the current time. + * @see CubicEaseIn | CubicEaseInOut + * @since 1.0 + * @version 1.0 + */ + static int16_t CubicEaseOut(int16_t startPos, int16_t endPos, uint16_t curTime, uint16_t durationTime); + + /** + * @brief Eases in and then out shaping like a cubic curve. + * + * t^3 is the equation for a cubic easing. The animation accelerates slowly until halfway + * and decelerates slowly towards the end. The acceleration change is similar to a cubic curve. + * + * @param startPos Indicates the start value of this animation. + * @param endPos Indicates the end value of this animation. + * @param curTime Indicates the current time of this animation. + * @param durationTime Indicates the total duration of this animation. + * + * @return Returns the value for the current time. + * @see CubicEaseIn | CubicEaseOut + * @since 1.0 + * @version 1.0 + */ + static int16_t CubicEaseInOut(int16_t startPos, int16_t endPos, uint16_t curTime, uint16_t durationTime); + + /** + * @brief Displays no linear easing effects. + * + * t is the equation for a linear easing. The animation progresses at a constant velocity towards the end. + * + * @param startPos Indicates the start value of this animation. + * @param endPos Indicates the end value of this animation. + * @param curTime Indicates the current time of this animation. + * @param durationTime Indicates the total duration of this animation. + * + * @return Returns the value for the current time. + * @see LinearEaseIn | LinearEaseOut | LinearEaseInOut + * @since 1.0 + * @version 1.0 + */ + static int16_t LinearEaseNone(int16_t startPos, int16_t endPos, uint16_t curTime, uint16_t durationTime); + + /** + * @brief Eases in shaping like a quadratic curve. + * + * t^2 is the equation for a quadratic easing. The animation starts slowly from zero velocity and + * accelerates fast towards the end. The acceleration change is similar to a quadratic curve. + * + * @param startPos Indicates the start value of this animation. + * @param endPos Indicates the end value of this animation. + * @param curTime Indicates the current time of this animation. + * @param durationTime Indicates the total duration of this animation. + * + * @return Returns the value for the current time. + * @see QuadEaseOut | QuadEaseInOut + * @since 1.0 + * @version 1.0 + */ + static int16_t QuadEaseIn(int16_t startPos, int16_t endPos, uint16_t curTime, uint16_t durationTime); + + /** + * @brief Eases out shaping like a quadratic curve. + * + * t^2 is the equation for a quadratic easing. The animation starts fast and decelerates slowly + * towards the end. The acceleration change is similar to a quadratic curve. + * + * @param startPos Indicates the start value of this animation. + * @param endPos Indicates the end value of this animation. + * @param curTime Indicates the current time of this animation. + * @param durationTime Indicates the total duration of this animation. + * + * @return Returns the value for the current time. + * @see QuadEaseIn | QuadEaseInOut + * @since 1.0 + * @version 1.0 + */ + static int16_t QuadEaseOut(int16_t startPos, int16_t endPos, uint16_t curTime, uint16_t durationTime); + + /** + * @brief Eases in and then out shaping like a quadratic curve. + * + * t^2 is the equation for a quadratic easing. The animation accelerates slowly until halfway + * and decelerates slowly towards the end. The acceleration change is similar to a quadratic curve. + * + * @param startPos Indicates the start value of this animation. + * @param endPos Indicates the end value of this animation. + * @param curTime Indicates the current time of this animation. + * @param durationTime Indicates the total duration of this animation. + * + * @return Returns the value for the current time. + * @see QuadEaseIn | QuadEaseOut + * @since 1.0 + * @version 1.0 + */ + static int16_t QuadEaseInOut(int16_t startPos, int16_t endPos, uint16_t curTime, uint16_t durationTime); + + /** + * @brief Eases in shaping like a quintic curve. + * + * t^5 is the equation for a quintic easing. The animation starts slowly from zero velocity and + * accelerates fast towards the end. The acceleration change is similar to a quintic curve. + * + * @param startPos Indicates the start value of this animation. + * @param endPos Indicates the end value of this animation. + * @param curTime Indicates the current time of this animation. + * @param durationTime Indicates the total duration of this animation. + * + * @return Returns the value for the current time. + * @see QuintEaseOut | QuintEaseInOut + * @since 1.0 + * @version 1.0 + */ + static int16_t QuintEaseIn(int16_t startPos, int16_t endPos, uint16_t curTime, uint16_t durationTime); + + /** + * @brief Eases out shaping like a quintic curve. + * + * t^5 is the equation for a quintic easing. The animation starts fast and decelerates slowly + * towards the end. The acceleration change is similar to a quintic curve. + * + * @param startPos Indicates the start value of this animation. + * @param endPos Indicates the end value of this animation. + * @param curTime Indicates the current time of this animation. + * @param durationTime Indicates the total duration of this animation. + * + * @return Returns the value for the current time. + * @see QuintEaseIn | QuintEaseInOut + * @since 1.0 + * @version 1.0 + */ + static int16_t QuintEaseOut(int16_t startPos, int16_t endPos, uint16_t curTime, uint16_t durationTime); + + /** + * @brief Eases in and then out shaping like a quintic curve. + * + * t^5 is the equation for a quintic easing. The animation accelerates slowly until halfway and + * decelerates slowly towards the end. The acceleration change is similar to a quintic curve. + * + * @param startPos Indicates the start value of this animation. + * @param endPos Indicates the end value of this animation. + * @param curTime Indicates the current time of this animation. + * @param durationTime Indicates the total duration of this animation. + * + * @return Returns the value for the current time. + * @see QuintEaseIn | QuintEaseOut + * @since 1.0 + * @version 1.0 + */ + static int16_t QuintEaseInOut(int16_t startPos, int16_t endPos, uint16_t curTime, uint16_t durationTime); + + /** + * @brief Eases in shaping like a sinusoidal curve. + * + * sin(t) is the equation for a sinusoidal easing. The animation starts slowly from zero velocity + * and accelerates fast towards the end. The acceleration change is similar to a sinusoidal curve. + * + * @param startPos Indicates the start value of this animation. + * @param endPos Indicates the end value of this animation. + * @param curTime Indicates the current time of this animation. + * @param durationTime Indicates the total duration of this animation. + * + * @return Returns the value for the current time. + * @see SineEaseOut | SineEaseInOut + * @since 1.0 + * @version 1.0 + */ + static int16_t SineEaseIn(int16_t startPos, int16_t endPos, uint16_t curTime, uint16_t durationTime); + + /** + * @brief Eases out shaping like a sinusoidal curve. + * + * sin(t) is the equation for a sinusoidal easing. The animation starts fast and decelerates + * slowly towards the end. The acceleration change is similar to a sinusoidal curve. + * + * @param startPos Indicates the start value of this animation. + * @param endPos Indicates the end value of this animation. + * @param curTime Indicates the current time of this animation. + * @param durationTime Indicates the total duration of this animation. + * + * @return Returns the value for the current time. + * @see SineEaseIn | SineEaseInOut + * @since 1.0 + * @version 1.0 + */ + static int16_t SineEaseOut(int16_t startPos, int16_t endPos, uint16_t curTime, uint16_t durationTime); + + /** + * @brief Eases in and then out shaping like a sinusoidal curve. + * + * sin(t) is the equation for a sinusoidal easing. The animation accelerates slowly until + * halfway and decelerates slowly towards the end. The acceleration change is similar to a sinusoidal curve. + * + * @param startPos Indicates the start value of this animation. + * @param endPos Indicates the end value of this animation. + * @param curTime Indicates the current time of this animation. + * @param durationTime Indicates the total duration of this animation. + * + * @return Returns the value for the current time. + * @see SineEaseIn | SineEaseOut + * @since 1.0 + * @version 1.0 + */ + static int16_t SineEaseInOut(int16_t startPos, int16_t endPos, uint16_t curTime, uint16_t durationTime); + +private: + static constexpr uint16_t INTERPOLATION_RANGE = 1024; + static constexpr uint16_t INTERPOLATION_RANGE_OFFSET = 10; + static constexpr uint32_t INTERPOLATION_RANGE_SQUARE = 1048576; + static constexpr double OVERSHOOT_MAX = 4; + static constexpr double OVERSHOOT_MIN = 1; + static double overshoot_; +}; +} // namespace OHOS +#endif diff --git a/interfaces/kits/animator/interpolation.h b/interfaces/kits/animator/interpolation.h new file mode 100755 index 0000000..0aa6f5f --- /dev/null +++ b/interfaces/kits/animator/interpolation.h @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @addtogroup UI_Animator + * @{ + * + * @brief Defines UI animation effects and provides matched curves. + * + * @since 1.0 + * @version 1.0 + */ + +/** + * @file interpolation.h + * + * @brief Defines the functions for calculating the interpolation in computer graphics. + * + * @since 1.0 + * @version 1.0 + */ + +#ifndef GRAPHIC_LITE_INTERPOLATION_H +#define GRAPHIC_LITE_INTERPOLATION_H + +#include "heap_base.h" + +namespace OHOS { +/** + * @brief Calculates the Bezier interpolation. + * + * @since 1.0 + * @version 1.0 + */ +class Interpolation : public HeapBase { +public: + /** + * @brief Obtains the value calculated by the cubic Bezier equation. + * + * Use [0, 1024] instead of [0, 1] in the standard Bezier equation. The cubic Bezier equation + * is B(t) = P0*(1-t)^3 + 3*P1*t*(1-t)^2 + 3*P2*t^2*(1-t) + P3*t^3. + * + * @param t Indicates the current change rate of the cubic Bezier curve, within [0, 1024]. + * @param u0 Indicates the coordinates for the start point of the cubic Bezier curve, within [0, 1024]. + * @param u1 Indicates the coordinates for the first control point of the cubic Bezier curve, within [0, 1024]. + * @param u2 Indicates the coordinates for the second control point of the cubic Bezier curve, within [0, 1024]. + * @param u3 Indicates the coordinates for the end point of the cubic Bezier curve, within [0, 1024]. + * + * @return Returns the coordinates for the current change rate. + * @since 1.0 + * @version 1.0 + */ + static int16_t GetBezierInterpolation(int16_t t, int16_t u0, int16_t u1, int16_t u2, int16_t u3); + +private: + constexpr static uint16_t BESSEL_COEFFICIENT = 3; + constexpr static uint16_t INTERPOLATION_RANGE = 1024; + constexpr static uint16_t CUBIC_BEZIER_CALCULATE_OFFSET = 30; +}; +} // namespace OHOS +#endif // GRAPHIC_LITE_INTERPOLATION_H diff --git a/interfaces/kits/common/image.h b/interfaces/kits/common/image.h new file mode 100755 index 0000000..33384f2 --- /dev/null +++ b/interfaces/kits/common/image.h @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @addtogroup UI_Common + * @{ + * + * @brief Defines common UI capabilities, such as image and text processing. + * + * @since 1.0 + * @version 1.0 + */ + +/** + * @file image.h + * + * @brief Declares basic image attributes, including the image type and path. + * + * @since 1.0 + * @version 1.0 + */ + +#ifndef GRAPHIC_LITE_IMAGE_H +#define GRAPHIC_LITE_IMAGE_H + +#include "geometry2d.h" +#include "heap_base.h" +#include "image_info.h" +#include "style.h" + +namespace OHOS { +/** + * @brief Represents basic image attributes, including the image type and path. + * + * @since 1.0 + * @version 1.0 + */ +class Image : public HeapBase { +public: + /** + * @brief A constructor used to create an Image instance. You can use this constructor when a component + * requires a map. + * + * @since 1.0 + * @version 1.0 + */ + Image(); + + /** + * @brief A destructor used to delete the Image instance. + * + * @since 1.0 + * @version 1.0 + */ + virtual ~Image(); + + /** + * @brief Obtains the image information in an array. + * + * @return Returns the pointer to the image information. + * @since 1.0 + * @version 1.0 + */ + const ImageInfo* GetImageInfo() const + { + return imageInfo_; + } + + /** + * @brief Obtains the image path in binary. + * + * @return Returns the pointer to the image path. + * @since 1.0 + * @version 1.0 + */ + const char* GetPath() const + { + return path_; + } + + /** + * @brief Obtains the basic image information, including the image format, width, and height. + * + * @param header Indicates the basic image information. + * @since 1.0 + * @version 1.0 + */ + void GetHeader(ImageHeader& header) const; + + /** + * @brief Obtains the image type. + * + * @return Returns IMG_SRC_VARIABLE for image information in an array; returns IMG_SRC_FILE for an + * image path in binary. + * @since 1.0 + * @version 1.0 + */ + uint8_t GetSrcType() const + { + return srcType_; + } + + /** + * @brief Sets the image path. + * + * @param src Indicates the pointer to image path in the format of ..\\xxx\\xxx\\xxx.bin. + * @return Returns true if the operation is successful; returns false if the operation fails. + * @since 1.0 + * @version 1.0 + */ + bool SetSrc(const char* src); + + /** + * @brief Sets the image information. + * + * @param src Indicates the pointer to the image information. + * @return Returns true if the operation is successful; returns false if the operation fails. + * @since 1.0 + * @version 1.0 + */ + bool SetSrc(const ImageInfo* src); + + void DrawImage(const Rect& coords, const Rect& mask, const Style& style, uint8_t opaScale) const; + +protected: + const ImageInfo* imageInfo_; + const char* path_; + +private: +#if ENABLE_JPEG_AND_PNG + enum ImageType { + IMG_PNG, + IMG_JPEG, + IMG_UNKNOWN, + }; + + const static uint8_t IMG_BYTES_TO_CHECK = 4; // 4: check 4 bytes of image file +#endif + + uint8_t srcType_; + bool mallocFlag_; + bool SetLiteSrc(const char* src); + bool SetStandardSrc(const char* src); +#if ENABLE_JPEG_AND_PNG + bool SetPNGSrc(const char* src); + bool SetJPEGSrc(const char* src); + ImageType CheckImgType(const char* src); +#endif + bool IsImgValid(const char* suffix) + { + return (!strcmp(suffix, ".png") || !strcmp(suffix, ".PNG") || !strcmp(suffix, ".jpg") || + !strcmp(suffix, ".JPG") || !strcmp(suffix, ".jpeg") || !strcmp(suffix, ".JPEG") || + !strcmp(suffix, ".BMP") || !strcmp(suffix, ".bmp")); + } +}; +} // namespace OHOS +#endif // GRAPHIC_LITE_IMAGE_H diff --git a/interfaces/kits/common/screen.h b/interfaces/kits/common/screen.h new file mode 100755 index 0000000..038362e --- /dev/null +++ b/interfaces/kits/common/screen.h @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @addtogroup UI_Common + * @{ + * + * @brief Defines common UI capabilities, such as image and text processing. + * + * @since 1.0 + * @version 1.0 + */ + +/** + * @file screen.h + * + * @brief Declares the screen information. + * + * @since 1.0 + * @version 1.0 + */ + +#ifndef GRAPHIC_LITE_SCREEN_H +#define GRAPHIC_LITE_SCREEN_H + +#include "heap_base.h" + +namespace OHOS { +/** + * @brief Represents the screen info of the device. + * + * @since 1.0 + * @version 1.0 + */ +class Screen : HeapBase { +public: + /** + * @brief Obtains a singleton Screen instance. + * @return Returns the Screen instance. + */ + static Screen& GetInstance() + { + static Screen instance; + return instance; + } + + /** + * @brief Obtains the width of this screen. + * + * @return Returns the screen width. + * @since 1.0 + * @version 1.0 + */ + uint16_t GetWidth(); + + /** + * @brief Obtains the height of this screen. + * + * @return Returns the screen height. + * @since 1.0 + * @version 1.0 + */ + uint16_t GetHeight(); + +private: + Screen() {} + virtual ~Screen() {} +}; +} // namespace OHOS +#endif // GRAPHIC_LITE_SCREEN_H \ No newline at end of file diff --git a/interfaces/kits/common/task.h b/interfaces/kits/common/task.h new file mode 100755 index 0000000..cd11f3f --- /dev/null +++ b/interfaces/kits/common/task.h @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @addtogroup UI_Common + * @{ + * + * @brief Defines common UI capabilities, such as image and text processing. + * + * @since 1.0 + * @version 1.0 + */ + +/** + * @file task.h + * + * @brief Declares the Task class of the graphics module, which provides functions for setting the running period + * and time of a task. + * + * @since 1.0 + * @version 1.0 + */ + +#ifndef GRAPHIC_LITE_TASK_H +#define GRAPHIC_LITE_TASK_H + +#include + +#include "heap_base.h" + +namespace OHOS { +/** + * @brief Represents the Task class of the graphics module. + * This class provides functions for setting the running period and time of a task. + * + * @since 1.0 + * @version 1.0 + */ +class Task : public HeapBase { +public: + /** + * @brief A constructor used to create a Task instance. + */ + Task() : period_(DEFAULT_TASK_PERIOD), lastRun_(0) {} + + /** + * @brief A constructor used to create a Task instance with the specified running period. + * @param period Indicates the running period of this task. + */ + Task(uint32_t period) : period_(period), lastRun_(0) {} + + /** + * @brief A destructor used to delete the Task instance. + */ + virtual ~Task() {} + + /** + * @brief Sets the running period for this task. + * @param period Indicates the running period to set. + */ + void SetPeriod(uint32_t period) + { + period_ = period; + } + + /** + * @brief Sets the end time for this task. + * @param lastRun Indicates the end time to set. + */ + void SetLastRun(uint32_t lastRun) + { + lastRun_ = lastRun; + } + + /** + * @brief Obtains the running period of this task. + * @return Returns the running period. + */ + uint32_t GetPeriod() const + { + return period_; + } + + /** + * @brief Obtains the end time of this task. + * @return Returns the end time. + */ + uint32_t GetLastRun() const + { + return lastRun_; + } + + /** + * @brief Executes this task. + */ + void TaskExecute(); + + /** + * @brief Called when this task is executed. + */ + virtual void Callback() = 0; + + /** + * @brief Initializes this task. + */ + virtual void Init(); + +protected: + uint32_t period_; /* call period in ms unit */ + uint32_t lastRun_; /* last run time */ +}; +} // namespace OHOS +#endif // GRAPHIC_LITE_TASK_H \ No newline at end of file diff --git a/interfaces/kits/common/text.h b/interfaces/kits/common/text.h new file mode 100755 index 0000000..1a5af81 --- /dev/null +++ b/interfaces/kits/common/text.h @@ -0,0 +1,367 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @addtogroup UI_Common + * @{ + * + * @brief Defines common UI capabilities, such as image and text processing. + * + * @since 1.0 + * @version 1.0 + */ + +/** + * @file text.h + * + * @brief Declares the Text class that provides functions to set basic text attributes, such as the text + * direction and alignment mode. + * + * @since 1.0 + * @version 1.0 + */ + +#ifndef GRAPHIC_LITE_TEXT_H +#define GRAPHIC_LITE_TEXT_H + +#include "geometry2d.h" +#include "graphic_types.h" +#include "style.h" + +namespace OHOS { +/** + * @brief Enumerates text alignment modes. + */ +enum UITextLanguageAlignment : uint8_t { + /** Left-aligned */ + TEXT_ALIGNMENT_LEFT = 0, + /** Right-aligned */ + TEXT_ALIGNMENT_RIGHT, + /** Centered */ + TEXT_ALIGNMENT_CENTER, + /** Top-aligned */ + TEXT_ALIGNMENT_TOP, + /** Bottom-aligned */ + TEXT_ALIGNMENT_BOTTOM, +}; + +/** + * @brief Enumerates text directions. + */ +enum UITextLanguageDirect : uint8_t { + /** Left-to-right */ + TEXT_DIRECT_LTR = 0, + /** Right-to-left */ + TEXT_DIRECT_RTL, + TEXT_DIRECT_MIXED, +}; + +/** + * @brief Represents the base class of Text, providing the text attribute setting and text drawing + * capabilities for components that require font display. + * + * @since 1.0 + * @version 1.0 + */ +class Text : public HeapBase { +public: + /** Invalid value for the ellipsis position */ + static constexpr uint16_t TEXT_ELLIPSIS_END_INV = 0xFFFF; + + /** + * @brief A constructor used to create a Text instance. + * + * @since 1.0 + * @version 1.0 + */ + Text(); + + /** + * @brief A destructor used to delete the Text instance. + * + * @since 1.0 + * @version 1.0 + */ + virtual ~Text(); + + /** + * @brief Sets the content for this text. + * + * @param text Indicates the pointer to the text content. + * @since 1.0 + * @version 1.0 + */ + virtual void SetText(const char* text); + + /** + * @brief Obtains the content of this text. + * + * @return Returns the text content. + * @since 1.0 + * @version 1.0 + */ + const char* GetText() const + { + return text_; + } + + /** + * @brief Sets the font name and size. + * + * @param name Indicates the pointer to the font name. + * @param size Indicates the font size to set. + * @since 1.0 + * @version 1.0 + */ + void SetFont(const char* name, uint8_t size); + + static void SetFont(const char* name, uint8_t size, char*& destName, uint8_t& destSize); + + /** + * @brief Sets the font ID. + * + * @param fontId Indicates the font ID to set. + * @since 1.0 + * @version 1.0 + */ + void SetFontId(uint8_t fontId); + + /** + * @brief Obtains the font ID. + * + * @return Returns the front ID. + * @since 1.0 + * @version 1.0 + */ + uint8_t GetFontId() const + { + return fontId_; + } + + /** + * @brief Obtains the font size. + * + * @return Returns the front size. + * @since 1.0 + * @version 1.0 + */ + uint8_t GetFontSize() const + { + return fontSize_; + } + + /** + * @brief Sets the direction for this text. + * + * @param direct Indicates the text direction, as defined in {@link UITextLanguageDirect}. + * @since 1.0 + * @version 1.0 + */ + void SetDirect(UITextLanguageDirect direct) + { + direct_ = direct; + } + + /** + * @brief Obtains the direction of this text. + * + * @return Returns the text direction, as defined in {@link UITextLanguageDirect}. + * @since 1.0 + * @version 1.0 + */ + UITextLanguageDirect GetDirect() const + { + return static_cast(direct_); + } + + /** + * @brief Sets the alignment mode for this text. + * + * @param horizontalAlign Indicates the horizontal alignment mode to set, + * which can be {@link TEXT_ALIGNMENT_LEFT}, + * {@link TEXT_ALIGNMENT_CENTER}, or {@link TEXT_ALIGNMENT_RIGHT}. + * @param verticalAlign Indicates the vertical alignment mode to set, which can be + * {@link TEXT_ALIGNMENT_TOP} (default mode), {@link TEXT_ALIGNMENT_CENTER}, + * or {@link TEXT_ALIGNMENT_BOTTOM}. + * @since 1.0 + * @version 1.0 + */ + void SetAlign(UITextLanguageAlignment horizontalAlign, UITextLanguageAlignment verticalAlign = TEXT_ALIGNMENT_TOP) + { + if ((horizontalAlign_ != horizontalAlign) || (verticalAlign_ != verticalAlign)) { + needRefresh_ = true; + horizontalAlign_ = horizontalAlign; + verticalAlign_ = verticalAlign; + } + } + + /** + * @brief Obtains the horizontal alignment mode. + * + * @return Returns the horizontal alignment mode. + * @since 1.0 + * @version 1.0 + */ + UITextLanguageAlignment GetHorAlign() const + { + return static_cast(horizontalAlign_); + } + + /** + * @brief Obtains the vertical alignment mode. + * + * @return Returns the vertical alignment mode. + * @since 1.0 + * @version 1.0 + */ + UITextLanguageAlignment GetVerAlign() const + { + return static_cast(verticalAlign_); + } + + /** + * @brief Obtains the size of this text. + * + * @return Returns the text size. + * @since 1.0 + * @version 1.0 + */ + Point GetTextSize() const + { + return textSize_; + } + + virtual void ReMeasureTextSize(const Rect& textRect, const Style& style); + + void OnDraw(const Rect& invalidatedArea, + const Rect& viewOrigRect, + const Rect& textRect, + int16_t offsetX, + const Style& style, + uint16_t ellipsisIndex, + OpacityType opaScale); + + /** + * @brief Sets whether to adapt the component width to this text. + * + * @param expand Specifies whether to adapt the component width to this text. The value true indicates + * that the component width will adapt to this text, and false indicates not. + * @since 1.0 + * @version 1.0 + */ + void SetExpandWidth(bool expand) + { + expandWidth_ = expand; + } + + /** + * @brief Checks whether the component width adapts to this text. + * + * @return Returns true if the component width adapts to this text; returns false otherwise. + * @since 1.0 + * @version 1.0 + */ + bool IsExpandWidth() const + { + return expandWidth_; + } + + /** + * @brief Sets whether to adapt the component height to this text. + * + * @param expand Specifies whether to adapt the component height to this text. The value true indicates + * that the component height will adapt to this text, and false indicates not. + * @since 1.0 + * @version 1.0 + */ + void SetExpandHeight(bool expand) + { + expandHeight_ = expand; + } + + /** + * @brief Checks whether the component height adapts to this text. + * + * @return Returns true if the component height adapts to this text; returns false otherwise. + * @since 1.0 + * @version 1.0 + */ + bool IsExpandHeight() const + { + return expandHeight_; + } + + bool IsNeedRefresh() const + { + return needRefresh_; + } + + /** + * @brief Obtains the index of the character from where text will be replaced by ellipses based on + * the text rectangle and style. + * + * @param textRect Indicates the text rectangle. + * @param style Indicates the text style. + * @since 1.0 + * @version 1.0 + */ + uint16_t GetEllipsisIndex(const Rect& textRect, const Style& style); + +protected: + struct TextLine { + uint16_t lineBytes; + uint16_t linePixelWidth; + }; + + /** Maximum number of lines */ + static constexpr uint16_t MAX_LINE_COUNT = 50; + static TextLine textLine_[MAX_LINE_COUNT]; + + static constexpr int16_t TEXT_ELLIPSIS_DOT_NUM = 3; + static constexpr const char* TEXT_ELLIPSIS = "..."; + + virtual uint32_t GetTextStrLen(); + + virtual uint32_t + GetTextLine(uint32_t begin, uint32_t textLen, int16_t width, uint16_t lineNum, uint8_t letterSpace); + + virtual uint16_t GetLetterIndexByPosition(const Rect& textRect, const Style& style, const Point& pos); + + virtual void Draw(const Rect& mask, + const Rect& coords, + const Style& style, + int16_t offsetX, + uint16_t ellipsisIndex, + OpacityType opaScale); + + uint16_t GetLine(int16_t width, uint8_t letterSpace, uint16_t ellipsisIndex, uint32_t& maxLineBytes); + int16_t TextPositionY(const Rect& textRect, int16_t textHeight); + int16_t LineStartPos(const Rect& textRect, uint16_t lineWidth); + + char* text_; + uint8_t fontId_; + uint8_t fontSize_; // Only the vector font library has a valid value. + Point textSize_; + bool needRefresh_ : 1; + bool expandWidth_ : 1; + bool expandHeight_ : 1; + uint8_t direct_ : 5; // UITextLanguageDirect + +private: + uint8_t horizontalAlign_ : 4; // UITextLanguageAlignment + uint8_t verticalAlign_ : 4; // UITextLanguageAlignment +}; +} // namespace OHOS +#endif // GRAPHIC_LITE_TEXT_H \ No newline at end of file diff --git a/interfaces/kits/components/abstract_adapter.h b/interfaces/kits/components/abstract_adapter.h new file mode 100755 index 0000000..ef79a0c --- /dev/null +++ b/interfaces/kits/components/abstract_adapter.h @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @addtogroup UI_Components + * @{ + * + * @brief Defines UI components such as buttons, texts, images, lists, and progress bars. + * + * @since 1.0 + * @version 1.0 + */ + +/** + * @file abstract_adapter.h + * + * @brief Defines the base class for adapters. + * + * @since 1.0 + * @version 1.0 + */ + +#ifndef GRAPHIC_LITE_ABSTRACT_ADAPTER_H +#define GRAPHIC_LITE_ABSTRACT_ADAPTER_H + +#include "components/ui_view.h" + +namespace OHOS { +/** + * @brief Defines the base class for adapters. You can derive AbstractAdapter based on actual requirements + * and use the GetView() and GetCount() functions to implement adapters of different data types. + * For details, see {@link TextAdapter}. + * @since 1.0 + * @version 1.0 + */ +class AbstractAdapter : public HeapBase { +public: + /** + * @brief A constructor used to create an AbstractAdapter instance. + * @since 1.0 + * @version 1.0 + */ + AbstractAdapter() {} + + /** + * @brief A destructor used to delete the AbstractAdapter instance. + * @since 1.0 + * @version 1.0 + */ + virtual ~AbstractAdapter() {} + + /** + * @brief Obtains the number of adapter data items. + * + * @return Returns the number of adapter data items. + * @since 1.0 + * @version 1.0 + */ + virtual uint16_t GetCount() = 0; + + /** + * @brief Obtains a UIView instance to convert adapter data into another UIView instance. + * + * @param inView Indicates the pointer to the reusable instance. If this parameter is not NULL, a reusable + * UIView instance is available. In this case, this function does not need to create a new + * UIView instance, just reusing the instance specified by inView to update the + * inView data. + * If this parameter is NULL, there is no reusable UIView instance. In this case, this + * function needs to create a new UIView instance. + * + * @param index Indicates the adapter data index. + * + * @return UIView Returns the pointer to the UIView instance constructed by the adapter. + * @since 1.0 + * @version 1.0 + */ + virtual UIView* GetView(UIView* inView, int16_t index) = 0; + + virtual void DeleteView(UIView*& view){}; + +protected: + static const uint16_t DEFAULT_WIDTH = 200; + static const uint16_t DEFAULT_HEIGHT = 50; +}; +} // namespace OHOS +#endif // GRAPHIC_LITE_ABSTRACT_ADAPTER_H diff --git a/interfaces/kits/components/root_view.h b/interfaces/kits/components/root_view.h new file mode 100755 index 0000000..0be0102 --- /dev/null +++ b/interfaces/kits/components/root_view.h @@ -0,0 +1,305 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @addtogroup UI_Components + * @{ + * + * @brief Defines UI components such as buttons, texts, images, lists, and progress bars. + * + * @since 1.0 + * @version 1.0 + */ + +/** + * @file root_view.h + * + * @brief Manages a root view. + * + * A root view is the view containing its child views. It represents the root node in a tree structure and is + * the parent to all the children. + * + * @since 1.0 + * @version 1.0 + */ + +#ifndef GRAPHIC_LITE_ROOT_VIEW_H +#define GRAPHIC_LITE_ROOT_VIEW_H + +#include +#if defined __linux__ || defined __LITEOS__ || defined __APPLE__ +#include +#endif + +#include "components/ui_view_group.h" +#include "events/key_event.h" +#include "events/virtual_device_event.h" +#include "vector.h" +#include "list.h" + +namespace OHOS { +#if ENABLE_WINDOW +class Window; +class WindowImpl; +#endif + +/** + * @brief Defines the functions related to a root view which contains its child views and represents + * the root node in a tree structure. + * + * @since 1.0 + * @version 1.0 + */ +class RootView : public UIViewGroup { +public: + /** + * @brief Obtains a singleton RootView instance. + * + * @return Returns the singleton RootView instance. + * @since 1.0 + * @version 1.0 + */ + static RootView* GetInstance() + { + static RootView instance; + return &instance; + } + +#if ENABLE_WINDOW + /** + * @brief Obtains a RootView instance bound to a window. + * + * @return Returns the RootView instance. + * @since 1.0 + * @version 1.0 + */ + static RootView* GetWindowRootView() + { + return new RootView; + } + + /** + * @brief Destroys the RootView bound to a window. + * + * @param rootView Indicates the pointer to the RootView to destroy. + * @return Returns true if the operation is successful; returns false otherwise. + * @since 1.0 + * @version 1.0 + */ + static bool DestoryWindowRootView(RootView* rootView) + { + if (rootView == RootView::GetInstance()) { + return false; + } + delete rootView; + return true; + } +#endif + + /** + * @brief Represents the listener for monitoring physical key events. + * + * @since 1.0 + * @version 1.0 + */ + class OnKeyActListener : public HeapBase { + public: + /** + * @brief Responds to a physical key event. + * + * @param view Indicates the view displayed upon a physical key event. + * @param event Indicates the physical key event to respond to. + * @return Returns true if the view is normally displayed upon a physical key + * event; returns false otherwise. + * @since 1.0 + * @version 1.0 + */ + virtual bool OnKeyAct(UIView& view, const KeyEvent& event) = 0; + }; + + /** + * @brief Obtains the view type. + * + * @return Returns UI_ROOT_VIEW, as defined in {@link UIViewType}. + * @since 1.0 + * @version 1.0 + */ + UIViewType GetViewType() const override + { + return UI_ROOT_VIEW; + } + + /** + * @brief Executes a physical key event. + * + * @param event Indicates the physical key event to execute. + * @since 1.0 + * @version 1.0 + */ + virtual void OnKeyEvent(const KeyEvent& event) + { + if (onKeyActListener_ != nullptr) { + onKeyActListener_->OnKeyAct(*this, event); + } + } + + /** + * @brief Sets the listener that contains a callback to be invoked upon a physical key event. + * + * @param onKeyActListener Indicates the pointer to the listener to set. + * @since 1.0 + * @version 1.0 + */ + void SetOnKeyActListener(OnKeyActListener* onKeyActListener) + { + onKeyActListener_ = onKeyActListener; + } + + /** + * @brief Clears the listener for monitoring physical key events. + * + * @since 1.0 + * @version 1.0 + */ + void ClearOnKeyActListener() + { + onKeyActListener_ = nullptr; + } + + /** + * @brief Listens for the input events triggered by a virtual device other than + * human touching or physical pressing. + * + * @since 1.0 + * @version 1.0 + */ + class OnVirtualDeviceEventListener : public HeapBase { + public: + /** + * @brief Responds to an input event triggered by a virtual device. + * + * @param view Indicates the view displayed upon an input event triggered by a virtual device. + * @param event Indicates the input event to respond to. + * @return Returns true if this view is normally displayed upon an input event triggered by a + * virtual device; returns false otherwise. + * @since 1.0 + * @version 1.0 + */ + virtual bool OnVirtualDeviceEvent(UIView& view, VirtualDeviceEvent event) = 0; + }; + + /** + * @brief Executes an input event triggered by a virtual device. + * + * @param event Indicates the input event to respond to. + * @since 1.0 + * @version 1.0 + */ + virtual void OnVirtualDeviceEvent(const VirtualDeviceEvent& event) + { + if (onVirtualEventListener_ != nullptr) { + onVirtualEventListener_->OnVirtualDeviceEvent(*this, event); + } + } + + /** + * @brief Sets a listener for monitoring the input events of a virtual device. + * + * @param onVirtualDeviceEventListener Indicates the pointer to the listener to set. + * @since 1.0 + * @version 1.0 + */ + void SetOnVirtualDeviceEventListener(OnVirtualDeviceEventListener* onVirtualDeviceEventListener) + { + onVirtualEventListener_ = onVirtualDeviceEventListener; + } + + /** + * @brief Clears the listener for monitoring the input events of a virtual device. + * + * @since 1.0 + * @version 1.0 + */ + void ClearOnVirtualDeviceEventListener() + { + onVirtualEventListener_ = nullptr; + } + + /** + * @brief Checks whether the target view is one of the child views of the specified parent view. + * + * @param parentView Indicates the specified parent view. + * @param subView Indicates the target child view. + * @return Returns true if the target view is available; returns false otherwise. + * @since 1.0 + * @version 1.0 + */ + static bool FindSubView(const UIView& parentView, const UIView* subView); + +#if ENABLE_WINDOW + /** + * @brief Obtains the window bound with a RootView. + * + * @return Returns the window. + * @since 1.0 + * @version 1.0 + */ + Window* GetBoundWindow() const; +#endif + +private: + friend class RenderManager; + friend class UIViewGroup; + friend class UIView; +#if ENABLE_WINDOW + friend class WindowImpl; +#endif + + RootView(); + + ~RootView() {} + + inline bool IntersectScreenRect(Rect& rect); + void AddInvalidateRectWithLock(Rect& rect, UIView *view); + void AddInvalidateRect(Rect& rect, UIView* view); + void Measure(); + void MeasureView(UIView* view); + void Render(); + void DrawTop(UIView* view, const Rect& rect); + UIView* GetTopUIView(const Rect& rect); +#if LOCAL_RENDER + void RemoveViewFromInvalidMap(UIView *view); + void DrawInvalidMap(const Rect &buffRect); + void OptimizeInvalidView(UIView* curview, UIView* backgroud, List &renderedRects); + void OptimizeInvalidMap(); + + std::map> invalidateMap_; +#else + bool renderFlag_ = false; + Rect invalidRect_; +#endif + OnKeyActListener* onKeyActListener_ {nullptr}; + OnVirtualDeviceEventListener* onVirtualEventListener_ {nullptr}; + bool enableAnimator_ {false}; +#if defined __linux__ || defined __LITEOS__ || defined __APPLE__ + pthread_mutex_t lock_; +#endif + +#if ENABLE_WINDOW + WindowImpl* boundWindow_ {nullptr}; +#endif +}; +} // namespace OHOS +#endif // GRAPHIC_LITE_ROOT_VIEW_H diff --git a/interfaces/kits/components/text_adapter.h b/interfaces/kits/components/text_adapter.h new file mode 100755 index 0000000..cf85cd2 --- /dev/null +++ b/interfaces/kits/components/text_adapter.h @@ -0,0 +1,282 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @addtogroup UI_Components + * @{ + * + * @brief Defines UI components such as buttons, texts, images, lists, and progress bars. + * + * @since 1.0 + * @version 1.0 + */ + +/** + * @file text_adapter.h + * + * @brief Defines a text adapter that is used to construct UILabel instances. + * + * @since 1.0 + * @version 1.0 + */ + +#ifndef GRAPHIC_LITE_TEXT_ADAPTER_H +#define GRAPHIC_LITE_TEXT_ADAPTER_H + +#include "components/abstract_adapter.h" +#include "components/ui_label.h" + +namespace OHOS { +/** + * @brief Defines a text formatter. You can inherit this class and implement the Format() function. + * + * @since 1.0 + * @version 1.0 + */ +class TextFormatter : public HeapBase { +public: + /** + * @brief Converts the input integer into a character string for output. + * + * @param value Indicates the input integer. + * @param outText Indicates the pointer to the converted string. + * @param textLen Indicates the length of the string. + * @since 1.0 + * @version 1.0 + */ + virtual bool Format(int16_t value, char* outText, uint16_t textLen); +}; + +/** + * @brief Defines a text adapter, which implements UILabel instance construction and supports text data and continuous + * integer data. + * + * @since 1.0 + * @version 1.0 + */ +class TextAdapter : public AbstractAdapter { +public: + /** + * @brief A constructor used to create a UILabel instance. + * @since 1.0 + * @version 1.0 + */ + TextAdapter(); + + /** + * @brief A destructor used to delete the UILabel instance. + * @since 1.0 + * @version 1.0 + */ + virtual ~TextAdapter(); + + /** + * @brief Obtains a UILabel instance to convert adapter data into another UILabel instance. + * + * @param inView Indicates the pointer to the reusable instance. If this parameter is not NULL, a reusable + * UILabel instance is available. In this case, this function does not need to create a new + * UILabel instance, just resusing the instance specified by inView to update the inView + * data. If this parameter is NULL, there is no resuable UIView instance. In this case, this + * function needs to create a new UILabel instance. + * + * @param index Indicates the adapter data index. + * + * @return Returns the address of the UILabel instance constructed by the adapter. + * @since 1.0 + * @version 1.0 + */ + UIView* GetView(UIView* inView, int16_t index) override; + + /** + * @brief Sets the UILabel adapter data, which is a string linked list. + * + * @param data Indicates the string linked list data. + * @since 1.0 + * @version 1.0 + */ + void SetData(List* data); + + /** + * @brief Sets continuously increasing data. For example, if you need to set data 0, 1, 2, + * 3, 4, use this function to set start to 0 and end to 4. + * + * @param start Indicates the start value of the integer data. + * @param end Indicates the end value of the integer data. + * @since 1.0 + * @version 1.0 + */ + void SetData(int16_t start, int16_t end); + + /** + * @brief Sets font ID. + * + * @param fontId Indicates the font ID. For details, see {@link UITextLanguageFontId}. + * @since 1.0 + * @version 1.0 + */ + void SetFontId(uint8_t fontId) + { + fontId_ = fontId; + if (fontName_ != nullptr) { + UIFree(fontName_); + fontName_ = nullptr; + } + } + + /** + * @brief Obtains font ID. + * + * @return Returns the font ID. For details, see {@link UITextLanguageFontId}. + * @since 1.0 + * @version 1.0 + */ + uint8_t GetFontId() const + { + return fontId_; + } + + /** + * @brief Sets the font. + * + * @param name Indicates the pointer to the font name. + * @param size Indicates the font size to set. + * @since 1.0 + * @version 1.0 + */ + void SetFont(const char* name, uint8_t size); + + /** + * @brief Obtains the UILabel adapter style. + * + * @return Returns the UILabel adapter style. + * @since 1.0 + * @version 1.0 + */ + Style& GetStyle() + { + return style_; + } + + /** + * @brief Obtains the data size of the UILabel adapter. + * + * @return Returns the data size of the UILabel adapter. + * @since 1.0 + * @version 1.0 + */ + virtual uint16_t GetCount() override; + + /** + * @brief Sets the width of UILabel constructed by the adapter. + * + * @return Returns the width of UILabel. + * @since 1.0 + * @version 1.0 + */ + void SetWidth(int16_t width) + { + width_ = width; + } + + /** + * @brief Sets the direction of the UILabel constructed by the adapter. + * + * @param direct Indicates the direction of the UILabel constructed by the adapter. + * @since 1.0 + * @version 1.0 + */ + void SetDirect(UITextLanguageDirect direct) + { + direct_ = direct; + } + + /** + * @brief Sets the height of the UILabel constructed by the adapter. + * + * @return Returns the height of the UILabel constructed by the adapter. + * @since 1.0 + * @version 1.0 + */ + void SetHeight(int16_t height) + { + height_ = height; + } + + /** + * @brief Sets the LineBreakMode attribute of UILabel constructed by the adapter. For details about the + * values of LineBreakMode, see {@link LINE_BREAK_ADAPT}, {@link LINE_BREAK_WRAP}, + * {@link LINE_BREAK_ELLIPSIS}, and {@link LINE_BREAK_MARQUEE}. + * + * @param lineBreakMode Indicates the LineBreakMode of UILabel. + * @since 1.0 + * @version 1.0 + */ + void SetLineBreakMode(const uint8_t lineBreakMode) + { + lineBreakMode_ = lineBreakMode; + } + + /** + * @brief Sets the callback function to be invoked upon a click event. + * + * @param clickListener Indicates the pointer to the callback function. + * @since 1.0 + * @version 1.0 + */ + void SetOnClickListener(UIView::OnClickListener* clickListener) + { + clickListener_ = clickListener; + } + + /** + * @brief Sets the text formatter. + * + * @param formatter Indicates the pointer to the text formatter. For details, see {@link TextFormatter}. + * + * @since 1.0 + * @version 1.0 + */ + void SetTextFormatter(TextFormatter* formatter) + { + formatter_ = formatter; + } + +protected: + virtual UILabel* GetTextView(UIView* inView, int16_t index); + uint8_t dataMode_; + +private: + static constexpr uint8_t DYNAMIC_TEXT_MODE = 0; + static constexpr uint8_t CONTINUOUS_INTEGER_MODE = 3; + static constexpr uint8_t BUF_LEN = 7; + UILabel* GetDynamicText(UIView* inView, int16_t index); + UILabel* GetIntegerText(UIView* inView, int16_t index); + void ClearDynamicText(); + uint8_t fontId_; + char* fontName_; + uint8_t fontSize_; + int16_t width_; + int16_t height_; + UITextLanguageDirect direct_; + uint8_t lineBreakMode_; + int16_t integerTextStart_; + int16_t integerTextEnd_; + Style style_; + List dynamicText_; + UIView::OnClickListener* clickListener_; + TextFormatter* formatter_; +}; +} // namespace OHOS +#endif // GRAPHIC_LITE_TEXT_ADAPTER_H diff --git a/interfaces/kits/components/ui_abstract_clock.h b/interfaces/kits/components/ui_abstract_clock.h new file mode 100755 index 0000000..584de05 --- /dev/null +++ b/interfaces/kits/components/ui_abstract_clock.h @@ -0,0 +1,244 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @addtogroup UI_Components + * @{ + * + * @brief Defines UI components such as buttons, texts, images, lists, and progress bars. + * + * @since 1.0 + * @version 1.0 + */ + +/** + * @file ui_abstract_clock.h + * + * @brief Declares the UIAbstractClock class that provides the functions related to clocks. + * + * @since 1.0 + * @version 1.0 + */ + +#ifndef UI_ABSTRACT_CLOCK_H +#define UI_ABSTRACT_CLOCK_H + +#include "components/ui_view_group.h" + +namespace OHOS { +/** + * @brief An abstract class that contains functions for converting units of time (hour, minute, and second), + * setting and obtaining the time. + * + * @since 1.0 + * @version 1.0 + */ +class UIAbstractClock : public UIViewGroup { +public: + /** + * @brief Represents 60 seconds per minute. + */ + static constexpr uint8_t ONE_MINUTE_IN_SECOND = 60; + + /** + * @brief Represents 60 minutes per hour. + */ + static constexpr uint8_t ONE_HOUR_IN_MINUTE = 60; + + /** + * @brief Represents 24 hours per day. + */ + static constexpr uint8_t ONE_DAY_IN_HOUR = 24; + + /** + * @brief Represents 12 hours every half day. + */ + static constexpr uint8_t HALF_DAY_IN_HOUR = 12; + + /** + * @brief A default constructor used to create a UIAbstractClock instance. + * + * @since 1.0 + * @version 1.0 + */ + UIAbstractClock() : currentHour_(0), currentMinute_(0), currentSecond_(0), mode_(WorkMode::NORMAL) {} + + /** + * @brief A constructor used to create a UIAbstractClock instance with + * time elements (hour, minute and second). + * + * @param hour Indicates the hour. + * @param minute Indicates the minute. + * @param second Indicates the second. + * @since 1.0 + * @version 1.0 + */ + UIAbstractClock(uint8_t hour, uint8_t minute, uint8_t second) + : currentHour_(hour), currentMinute_(minute), currentSecond_(second) + { + } + + /** + * @brief A destructor used to delete the UIAbstractClock instance. + * + * @since 1.0 + * @version 1.0 + */ + virtual ~UIAbstractClock() {} + + /** + * @brief Obtains the view type. + * + * @return Returns UI_ABSTRACT_CLOCK, as defined in {@link UIViewType}. + * @since 1.0 + * @version 1.0 + */ + UIViewType GetViewType() const override + { + return UI_ABSTRACT_CLOCK; + } + + /** + * @brief Sets the time in 24-hour format. + * + * @param hour Indicates the hour to set, within [0, 23] after the modulo operation. + * @param minute Indicates the minute to set, within [0, 59] after the modulo operation. + * @param second Indicates the second to set, within [0, 59] after the modulo operation. + * @since 1.0 + * @version 1.0 + */ + void SetTime24Hour(uint8_t hour, uint8_t minute, uint8_t second); + + /** + * @brief Sets the time in 12-hour format. + * + * @param hour Indicates the hour to set, within [0, 11] after the modulo operation. + * @param minute Indicates the minute to set, within [0, 59] after the modulo operation. + * @param second Indicates the second to set, within [0, 59] after the modulo operation. + * @param am Specifies whether it is in the morning. true indicates that it is in the morning, + * and false indicates that it is in the afternoon. + * @since 1.0 + * @version 1.0 + */ + void SetTime12Hour(uint8_t hour, uint8_t minute, uint8_t second, bool am); + + /** + * @brief Obtains the current number of hours. + * + * @return Returns the current number of hours. + * @since 1.0 + * @version 1.0 + */ + uint8_t GetCurrentHour() const + { + return currentHour_; + } + + /** + * @brief Obtains the current number of minutes. + * + * @return Returns the current number of minutes. + * @since 1.0 + * @version 1.0 + */ + uint8_t GetCurrentMinute() const + { + return currentMinute_; + } + + /** + * @brief Obtains the current number of seconds. + * + * @return Returns the current number of seconds. + * @since 1.0 + * @version 1.0 + */ + uint8_t GetCurrentSecond() const + { + return currentSecond_; + } + + /** + * @brief Increases the time by one second. + * + * @since 1.0 + * @version 1.0 + */ + void IncOneSecond(); + + /** + * @brief Updates this clock. + * + * @param clockInit Specifies whether it is the first initialization. true indicates it is the first + * initialization, and false indicates the opposite case. + * @since 1.0 + * @version 1.0 + */ + virtual void UpdateClock(bool clockInit); + + /** + * @brief Enumerates the working modes of this clock. + */ + enum WorkMode { + /** Always on (drawing not updated) */ + ALWAYS_ON, + /** Normal (drawing updated with the time change) */ + NORMAL, + }; + + /** + * @brief Sets the working mode for this clock. + * + * @param newMode Indicates the working mode to set. For details, see {@link WorkMode}. + * @since 1.0 + * @version 1.0 + */ + virtual void SetWorkMode(WorkMode newMode); + + /** + * @brief Obtains the working mode of this clock. + * + * @return Returns the working mode, as defined in {@link WorkMode}. + * @since 1.0 + * @version 1.0 + */ + virtual WorkMode GetWorkMode() const + { + return mode_; + } + +protected: + /** + * @brief Represents the current number of hours. + */ + uint8_t currentHour_; + + /** + * @brief Represents the current number of minutes. + */ + uint8_t currentMinute_; + + /** + * @brief Represents the current number of seconds. + */ + uint8_t currentSecond_; + + /** + * @brief Represents the current working mode of this clock. + */ + WorkMode mode_; +}; +} // namespace OHOS +#endif // UI_ABSTRACT_CLOCK_H diff --git a/interfaces/kits/components/ui_abstract_progress.h b/interfaces/kits/components/ui_abstract_progress.h new file mode 100755 index 0000000..56ad9d4 --- /dev/null +++ b/interfaces/kits/components/ui_abstract_progress.h @@ -0,0 +1,333 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @addtogroup UI_Components + * @{ + * + * @brief Defines UI components such as buttons, texts, images, lists, and progress bars. + * + * @since 1.0 + * @version 1.0 + */ + +/** + * @file ui_abstract_progress.h + * + * @brief Defines the base class attributes and common functions of a progress bar. + * + * @since 1.0 + * @version 1.0 + */ + +#ifndef GRAPHIC_LITE_UI_ABSTRACT_PROGRESS_H +#define GRAPHIC_LITE_UI_ABSTRACT_PROGRESS_H + +#include "common/image.h" +#include "components/ui_view.h" + +namespace OHOS { +/** + * @brief Represents the abstract base class which provides functions related to the progress bar. + * + * @see UIView + * @since 1.0 + * @version 1.0 + */ +class UIAbstractProgress : public UIView { +public: + /** + * @brief A constructor used to create a UIAbstractProgress instance. + * + * @since 1.0 + * @version 1.0 + */ + UIAbstractProgress(); + + /** + * @brief A destructor used to delete the UIAbstractProgress instance. + * + * @since 1.0 + * @version 1.0 + */ + virtual ~UIAbstractProgress(); + + /** + * @brief Obtains the component type. + * + * @return Returns the component type, as defined in {@link UIViewType}. + * @since 1.0 + * @version 1.0 + */ + UIViewType GetViewType() const override + { + return UI_ABSTRACT_PROGRESS; + } + + /** + * @brief Sets whether the background of the progress bar is visible. + * + * @param enable Specifies whether the background of the progress bar is visible. true (the default value) + * indicates that the background is visible, and false indicates the opposite case. + * @since 1.0 + * @version 1.0 + */ + void EnableBackground(bool enable) + { + enableBackground_ = enable; + } + + /** + * @brief Sets the current value for this progress bar. + * + * @param value Indicates the current value of this progress bar, within [rangeMin, rangeMax] specified by + * {@link SetRange}. If the value is less than rangeMin, rangeMin is used; + * if the value is greater than rangeMax, rangeMax is used. + * @see SetRange | GetValue + * @since 1.0 + * @version 1.0 + */ + void SetValue(int32_t value); + + /** + * @brief Obtains the current value of this progress bar. + * + * @return Returns the current value of this progress bar. + * @see SetValue + * @since 1.0 + * @version 1.0 + */ + int32_t GetValue() const + { + return curValue_; + } + + /** + * @brief Sets the range for this progress bar. + * + * rangeMin and rangeMax can be any value represented by int32_t. + * rangeMax must be greater than or equal to rangeMin. + * Otherwise, the setting does not take effect and the original value is used. + * + * @param rangeMax Indicates the maximum value of this progress bar. The default value is 100. + * @param rangeMin Indicates the minimum value of this progress bar. The default value is 0. + * @see GetRangeMin | GetRangeMax + * @since 1.0 + * @version 1.0 + */ + void SetRange(int32_t rangeMax, int32_t rangeMin); + + /** + * @brief Obtains the minimum value of this progress bar. + * + * @return Returns the minimum value of this progress bar. + * @see SetRange | GetRangeMax + * @since 1.0 + * @version 1.0 + */ + int32_t GetRangeMin() const + { + return rangeMin_; + } + + /** + * @brief Obtains the maximum value of this progress bar. + * + * @return Returns the maximum value of this progress bar. + * @see SetRange | GetRangeMin + * @since 1.0 + * @version 1.0 + */ + int32_t GetRangeMax() const + { + return rangeMax_; + } + + /** + * @brief Sets the image for this progress bar. + * + * The size of the image must be the same as that of the progress bar to ensure a normal display. + * If the value of any input parameter is nullptr, image filling is canceled. + * Instead, color filling will be adopted. + * + * @param foregroundImage Indicates the foreground image of the progress bar. The default value is nullptr. + * @param backgroundImage Indicates the background image of the progress bar. The default value is nullptr. + * @since 1.0 + * @version 1.0 + */ + void SetImage(const char* foregroundImage, const char* backgroundImage = nullptr); + + /** + * @brief Sets the image as a pixel map for this progress bar. + * + * The size of the image must be the same as that of the progress bar to ensure a normal display. + * If the value of any input parameter is nullptr, image filling is canceled. + * Instead, color filling will be adopted. + * + * @param foregroundImage Indicates the foreground image of the progress bar. The default value is nullptr. + * @param backgroundImage Indicates the background image of the progress bar. The default value is nullptr. + * @since 1.0 + * @version 1.0 + */ + void SetImage(const ImageInfo* foregroundImage, const ImageInfo* backgroundImage = nullptr); + + /** + * @brief Sets the step for this progress bar. + * + * The step is used to control the update frequency of the progress bar. When the value change exceeds the step, + * the progress bar is redrawn. \n + * For example, when the step is set to 10 and the current progress value is 5, the progress bar will not be + * redrawn if the progress value becomes 14, but will be redrawn if the progress value becomes 15. \n + * In addition, when its current value changes to be the maximum or minimum value, + * the progress bar is redrawn regardless of the step you set. \n + * + * @param step Indicates the step to set. The default value is 1. + * @see GetStep + * @since 1.0 + * @version 1.0 + */ + void SetStep(uint32_t step) + { + step_ = step; + } + + /** + * @brief Obtains the current step of this progress bar. + * + * @return Returns the current step. + * @see SetStep + * @since 1.0 + * @version 1.0 + */ + uint32_t GetStep() const + { + return step_; + } + + /** + * @brief Sets the background style for this progress bar. + * + * @param style Indicates the background style of the progress bar. For details, see {@link Style}. + * @see SetForegroundStyle | GetBackgroundStyle + * @since 1.0 + * @version 1.0 + */ + void SetBackgroundStyle(const Style& style); + + /** + * @brief Sets a background style for this progress bar. + * + * @param key Indicates the key of the style to set. + * @param value Indicates the value matching the key. + * @since 1.0 + * @version 1.0 + */ + void SetBackgroundStyle(uint8_t key, int64_t value); + + /** + * @brief Obtains the background style of this progress bar. + * + * @return Returns the background style. + * @See SetBackgroundStyle + * @since 1.0 + * @version 1.0 + */ + const Style& GetBackgroundStyle() const; + + /** + * @brief Obtains the value of a background style of this progress bar. + * + * @param key Indicates the key of the style. + * @return Returns the value of the style. + * @since 1.0 + * @version 1.0 + */ + int64_t GetBackgroundStyle(uint8_t key) const; + + /** + * @brief Sets the foreground style for this progress bar. + * + * @param style Indicates the foreground style of this progress bar. For details, see {@link Style}. + * @see SetBackgroundStyle | GetForegroundStyle + * @since 1.0 + * @version 1.0 + */ + void SetForegroundStyle(const Style& style); + + /** + * @brief Sets a foreground style for this progress bar. + * + * @param key Indicates the key of the style to set. + * @param value Indicates the value matching the key. + * @since 1.0 + * @version 1.0 + */ + void SetForegroundStyle(uint8_t key, int64_t value); + + /** + * @brief Obtains the foreground style of this progress bar. + * + * @return Returns the foreground style. + * @See SetForegroundStyle + * @since 1.0 + * @version 1.0 + */ + const Style& GetForegroundStyle() const; + + /** + * @brief Obtains the value of a foreground style of this progress bar. + * + * @param key Indicates the key of the style. + * @return Returns the value of the style. + * @since 1.0 + * @version 1.0 + */ + int64_t GetForegroundStyle(uint8_t key) const; + + /** + * @brief Sets the type of caps on the background and foreground of the progress bar. + * + * @param cap Indicates the cap type. For details, see {@link CapType}. + * @since 1.0 + * @version 1.0 + */ + void SetCapType(CapType cap) + { + SetBackgroundStyle(STYLE_LINE_CAP, cap); + SetForegroundStyle(STYLE_LINE_CAP, cap); + } + +protected: + static constexpr uint16_t MAX_PERCENT_VALUE = 100; + static constexpr uint16_t MIN_PERCENT_VALUE = 0; + + uint32_t GetRangeSize() const; + int16_t GetCurrentPos(int16_t distance) const; + virtual bool InitImage(); + bool enableBackground_ : 1; + bool backgroundStyleAllocFlag_ : 1; + bool foregroundStyleAllocFlag_ : 1; + Style* backgroundStyle_; + Style* foregroundStyle_; + Image* backgroundImage_; + Image* foregroundImage_; + int32_t rangeMax_; + int32_t rangeMin_; + int32_t curValue_; + uint32_t step_; + int32_t lastValue_; +}; +} // namespace OHOS +#endif // GRAPHIC_LITE_UI_ABSTRACT_PROGRESS_H diff --git a/interfaces/kits/components/ui_abstract_scroll.h b/interfaces/kits/components/ui_abstract_scroll.h new file mode 100755 index 0000000..2ceb837 --- /dev/null +++ b/interfaces/kits/components/ui_abstract_scroll.h @@ -0,0 +1,356 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @addtogroup UI_Components + * @{ + * + * @brief Defines UI components such as buttons, texts, images, lists, and progress bars. + * + * @since 1.0 + * @version 1.0 + */ + +/** + * @file ui_abstract_scroll.h + * + * @brief Declares the base class used to define the attributes of a scroll. The UIList, UIScrollView, and + * UISwipeView inherit from this class. + * + * @since 1.0 + * @version 1.0 + */ + +#ifndef GRAPHIC_LITE_UI_ABSTRACT_SCROLL_H +#define GRAPHIC_LITE_UI_ABSTRACT_SCROLL_H + +#include "animator/animator.h" +#include "animator/easing_equation.h" +#include "components/ui_view_group.h" + +namespace OHOS { +/** + * @brief Defines the attributes of a scroll, including the scroll direction, blank size of a scroll view, velocity and + * effects of a scroll animation. + * + * @since 1.0 + * @version 1.0 + */ +class UIAbstractScroll : public UIViewGroup { +public: + /** + * @brief A constructor used to create a UIAbstractScroll instance. + * + * @since 1.0 + * @version 1.0 + */ + UIAbstractScroll(); + + /** + * @brief A destructor used to delete the UIAbstractScroll instance. + * + * @since 1.0 + * @version 1.0 + */ + virtual ~UIAbstractScroll(); + + /** + * @brief Obtains the view type. + * @return Returns the view type, as defined in {@link UIViewType}. + * @since 1.0 + * @version 1.0 + */ + UIViewType GetViewType() const override + { + return UI_ABSTRACT_SCROLL; + } + + /** + * @brief Sets the blank size for this scroll view. + * + * + * @param value Indicates the blank size to set. The default value is 0. Taking a vertical scroll as an + * example, the value 0 indicates that the head node can only scroll downwards the top of the + * view and the tail node scroll upwards the bottom; the value 10 indicates that the head node + * can continue scrolling down by 10 pixels after it reaches the top of the view. + * @since 1.0 + * @version 1.0 + */ + void SetScrollBlankSize(uint16_t size) + { + scrollBlankSize_ = size; + } + + /** + * @brief Sets the maximum scroll distance after a finger lifts the screen. + * + * @param distance Indicates the maximum scroll distance to set. The default value is 0, indicating that the + * scroll distance is not limited. + * @since 1.0 + * @version 1.0 + */ + void SetMaxScrollDistance(uint16_t distance) + { + maxScrollDistance_ = distance; + } + + /** + * @brief Sets the rebound size, which is the distance a knob moves after being released when it reaches the end of + * a scrollbar. + * + * @param size Indicates the rebound size to set. + * @since 1.0 + * @version 1.0 + */ + void SetReboundSize(uint16_t size) + { + reboundSize_ = size; + } + + /** + * @brief Obtains the maximum scroll distance after a finger lifts the screen. + * + * @return Returns the maximum scroll distance. The default value is 0, indicating that the scroll distance + * is not limited. + * @since 1.0 + * @version 1.0 + */ + uint16_t GetMaxScrollDistance() const + { + return maxScrollDistance_; + } + + /** + * @brief Sets the easing function that specifies a scroll animation after a finger lifts the screen. + * + * @param func Indicates the easing function to set. The default function is {@link EasingEquation::CubicEaseOut}. + * For details, see {@link EasingEquation}. + * @since 1.0 + * @version 1.0 + */ + void SetDragFunc(EasingFunc func) + { + easingFunc_ = func; + } + + /** + * @brief Sets whether to continue scrolling after a finger lifts the screen. + * + * @param throwDrag Specifies whether to continue scrolling after a finger lifts the screen. true indicates + * the scroll continues, and false indicates the scroll stops immediately after a finger + * lifts. + * @since 1.0 + * @version 1.0 + */ + void SetThrowDrag(bool throwDrag) + { + throwDrag_ = throwDrag; + } + + /** + * @brief Moves the position of all child views. + * + * @param offsetX Indicates the offset distance by which a child view is moved on the x-axis. + * @param offsetY Indicates the offset distance by which a child view is moved on the y-axis. + * @since 1.0 + * @version 1.0 + */ + void MoveChildByOffset(int16_t offsetX, int16_t offsetY) override; + + /** + * @brief Sets the drag acceleration. + * + * @param value Indicates the drag acceleration to set. The default value is 10. A larger drag acceleration + * indicates a higher inertial scroll velocity. + * @since 1.0 + * @version 1.0 + */ + void SetDragACCLevel(uint16_t value) + { + if (value != 0) { + dragAccCoefficient_ = value; + } + } + + /** + * @brief Obtains the drag acceleration. + * + * @return Returns the drag acceleration. + * @since 1.0 + * @version 1.0 + */ + uint8_t GetDragACCLevel() const + { + return dragAccCoefficient_; + } + + /** + * @brief Sets the compensation distance after a finger lifts the screen. + * + * @param value Indicates the compensation distance to set. The default value is 0. + * @since 1.0 + * @version 1.0 + */ + void SetSwipeACCLevel(uint16_t value) + { + swipeAccCoefficient_ = value; + } + + /** + * @brief Obtains the compensation distance after a finger lifts the screen. + * + * @return Returns the compensation distance. + * @since 1.0 + * @version 1.0 + */ + uint8_t GetSwipeACCLevel() const + { + return swipeAccCoefficient_; + } + +#if ENABLE_ROTATE_INPUT + /** + * @brief תϵ + * + * @return תϵ + * @since 5.0 + * @version 3.0 + */ + int8_t GetRotateFactor() const + { + return rotateFactor_; + } + + /** + * @brief תϵ + * + * @param factor תϵת¼תתϵij˻Ϊʵʻֵ + * @since 5.0 + * @version 3.0 + */ + void SetRotateFactor(int8_t factor) + { + rotateFactor_ = factor; + } +#endif + + static constexpr uint8_t HORIZONTAL = 0; + static constexpr uint8_t VERTICAL = 1; + +protected: + static constexpr uint8_t HORIZONTAL_AND_VERTICAL = 2; + /* calculate drag throw distance, last drag distance in one tick * DRAG_DISTANCE_COEFFICIENT */ + static constexpr uint8_t DRAG_DISTANCE_COEFFICIENT = 5; + /* calculate drag throw times, drag distance / DRAG_TIMES_COEFFICIENT */ + static constexpr uint8_t DRAG_TIMES_COEFFICIENT = 18; + /* the minimum duration of the swipe animator */ + static constexpr uint8_t MIN_DRAG_TIMES = 5; + /* acceleration calculation coefficient */ + static constexpr uint8_t DRAG_ACC_FACTOR = 10; + /* the maximum number of historical drag data */ + static constexpr uint8_t MAX_DELTA_Y_SIZE = 3; + + class ListAnimatorCallback : public AnimatorCallback { + public: + ListAnimatorCallback() + : curtTime_(0), + dragTimes_(0), + startValueX_(0), + endValueX_(0), + previousValueX_(0), + startValueY_(0), + endValueY_(0), + previousValueY_(0) + { + } + + virtual ~ListAnimatorCallback() {} + + void SetDragTimes(uint16_t times) + { + dragTimes_ = times; + } + + void SetDragStartValue(int16_t startValueX, int16_t startValueY) + { + startValueX_ = startValueX; + previousValueX_ = startValueX; + startValueY_ = startValueY; + previousValueY_ = startValueY; + } + + void SetDragEndValue(int16_t endValueX, int16_t endValueY) + { + endValueX_ = endValueX; + endValueY_ = endValueY; + } + + void RsetCallback() + { + curtTime_ = 0; + dragTimes_ = 0; + startValueX_ = 0; + endValueX_ = 0; + startValueY_ = 0; + endValueY_ = 0; + } + + virtual void Callback(UIView* view) override; + + uint16_t curtTime_; + uint16_t dragTimes_; + int16_t startValueX_; + int16_t endValueX_; + int16_t previousValueX_; + int16_t startValueY_; + int16_t endValueY_; + int16_t previousValueY_; + }; + + bool DragThrowAnimator(Point currentPos, Point lastPos); + virtual void StopAnimator(); + virtual bool DragXInner(int16_t distance) = 0; + virtual bool DragYInner(int16_t distance) = 0; + void RefreshDeltaY(int16_t distance) + { + lastDeltaY_[deltaYIndex_ % MAX_DELTA_Y_SIZE] = distance; + deltaYIndex_++; + } + + void CalculateDragDistance(Point currentPos, Point lastPos, int16_t& dragDistanceX, int16_t& dragDistanceY); + void StartAnimator(int16_t dragDistanceX, int16_t dragDistanceY); + virtual void CalculateReboundDistance(int16_t& dragDistanceX, int16_t& dragDistanceY) {}; + int16_t GetMaxDeltaY() const; + + uint16_t scrollBlankSize_; + uint16_t reboundSize_; + uint16_t maxScrollDistance_; + int16_t lastDeltaY_[MAX_DELTA_Y_SIZE]; + uint8_t dragAccCoefficient_; + uint8_t swipeAccCoefficient_; + uint8_t direction_ : 2; + uint8_t deltaYIndex_ : 2; + uint8_t reserve_ : 4; + bool throwDrag_; + EasingFunc easingFunc_; + ListAnimatorCallback animatorCallback_; + Animator scrollAnimator_; +#if ENABLE_ROTATE_INPUT + int8_t rotateFactor_; + int8_t rotateThreshold_; +#endif +}; +} // namespace OHOS +#endif // GRAPHIC_LITE_UI_ABSTRACT_LIST_VIEW_H diff --git a/interfaces/kits/components/ui_analog_clock.h b/interfaces/kits/components/ui_analog_clock.h new file mode 100755 index 0000000..9d71482 --- /dev/null +++ b/interfaces/kits/components/ui_analog_clock.h @@ -0,0 +1,374 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @addtogroup UI_Components + * @{ + * + * @brief Defines UI components such as buttons, texts, images, lists, and progress bars. + * + * @since 1.0 + * @version 1.0 + */ + +/** + * @file ui_analog_clock.h + * + * @brief Declares an analog clock. + * + * @since 1.0 + * @version 1.0 + */ + +#ifndef UI_ANALOG_CLOCK_H +#define UI_ANALOG_CLOCK_H + +#include "components/ui_abstract_clock.h" + +namespace OHOS { +class UIImageView; +/** + * @brief Provides the functions related to an analog clock. + * + * @see UIAbstractClock + * @since 1.0 + * @version 1.0 + */ +class UIAnalogClock : public UIAbstractClock { +public: + /** + * @brief A default constructor used to create a UIAnalogClock instance. + * + * @since 1.0 + * @version 1.0 + */ + UIAnalogClock(); + + /** + * @brief A destructor used to delete the UIAnalogClock instance. + * + * @since 1.0 + * @version 1.0 + */ + virtual ~UIAnalogClock() {} + + /** + * @brief Enumerates the clock hand types. + */ + enum class HandType { + /** Hour hand */ + HOUR_HAND, + /** Minute hand */ + MINUTE_HAND, + /** Second hand */ + SECOND_HAND, + }; + + /** + * @brief Enumerates the drawing types of a clock hand. + */ + enum class DrawType { + /** Using a line to draw a clock hand */ + DRAW_LINE, + /** Using an image to draw a clock hand */ + DRAW_IMAGE + }; + + /** + * @brief Defines the basic attributes of the analog clock hands. This is an inner class of UIAbstractClock. + * + * @since 1.0 + * @version 1.0 + */ + class Hand : public HeapBase { + public: + /** + * @brief A default constructor used to create a Hand instance. + * + * @since 1.0 + * @version 1.0 + */ + Hand() + : center_{0, 0}, + initAngle_(0), + preAngle_(0), + nextAngle_(0), + position_{0, 0}, + imageInfo_{{0, 0, 0}}, + color_{{0, 0, 0}}, + width_(0), + height_(0), + opacity_(0), + drawtype_(DrawType::DRAW_IMAGE) + { + } + + /** + * @brief A destructor used to delete the Hand instance. + * + * @since 1.0 + * @version 1.0 + */ + virtual ~Hand() {} + + /** + * @brief Represents the rotation center of a clock hand. + */ + Point center_; + + /** + * @brief Represents the initial clockwise rotation angle of a clock hand. The default value is 0, + * indicating that the hand direction is vertically upward. + */ + uint16_t initAngle_; + + /** + * @brief Represents the latest rotation angle of this clock hand. + */ + uint16_t preAngle_; + + /** + * @brief Represents the next rotation angle of this clock hand. + */ + uint16_t nextAngle_; + + /** + * @brief Represents the position of a hand on this analog clock. + */ + Point position_; + + /** + * @brief Represents the image information of this clock hand. + */ + ImageInfo imageInfo_; + + /** + * @brief Represents the information about the rotation and translation of this clock hand. + */ + TransformMap trans_; + + /** + * @brief Represents the new rectangle area after the rotation and translation. + */ + Rect target_; + + /** + * @brief Represents the color of this clock hand. + */ + ColorType color_; + + /** + * @brief Represents the width of this clock hand. + */ + uint16_t width_; + + /** + * @brief Represents the height of this clock hand. + */ + uint16_t height_; + + /** + * @brief Represents the opacity of this clock hand. + */ + OpacityType opacity_; + + /** + * @brief Represents the drawing type of this clock hand. + */ + DrawType drawtype_; + }; + + /** + * @brief Obtains the view type. + * + * @return Returns UI_ANALOG_CLOCK, as defined in {@link UIViewType}. + * @since 1.0 + * @version 1.0 + */ + UIViewType GetViewType() const override + { + return UI_ANALOG_CLOCK; + } + + /** + * @brief Sets the image used to draw a clock hand. + * + * @param type Indicates the clock hand type, as enumerated in {@link HandType}. + * @param img Indicates the image to set. + * @param position Indicates the start position of this image. + * @param center Indicates the rotation center of this clock hand. + * @since 1.0 + * @version 1.0 + */ + void SetHandImage(HandType type, const UIImageView& img, Point position, Point center); + + /** + * @brief Sets the line used to draw a clock hand. + * + * @param type Indicates the clock hand type, as enumerated in {@link HandType}. + * @param position Indicates the position of the line endpoint close to the rotation center. + * @param center Indicates the rotation center of this clock hand. + * @param color Indicates the color of this line. + * @param width Indicates the width of this line when it is 12 o'clock. + * @param height Indicates the height of this line when it is 12 o'clock. + * @param opacity Indicates the opacity of this line. + * @since 1.0 + * @version 1.0 + */ + void SetHandLine(HandType type, + Point position, + Point center, + ColorType color, + uint16_t width, + uint16_t height, + OpacityType opacity); + + /** + * @brief Obtains the rotation center of a specified clock hand. + * + * @param type Indicates the clock hand type, as enumerated in {@link HandType}. + * @return Returns the rotation center. + * @since 1.0 + * @version 1.0 + */ + Point GetHandRotateCenter(HandType type) const; + + /** + * @brief Obtains the position of a specified clock hand. + * + * @param type Indicates the clock hand type, as enumerated in {@link HandType}. + * @return Returns the position of this specified clock hand. + * @since 1.0 + * @version 1.0 + */ + Point GetHandPosition(HandType type) const; + + /** + * @brief Obtains the initial rotation angle of the specified clock hand. + * + * @param type Indicates the clock hand type, as enumerated in {@link HandType}. + * @return Returns the initial rotation angle of this specified clock hand. + * @since 1.0 + * @version 1.0 + */ + uint16_t GetHandInitAngle(HandType type) const; + + /** + * @brief Obtains the current rotation angle of the specified clock hand. + * + * @param type Indicates the clock hand type, as enumerated in {@link HandType}. + * @return Returns the current rotation angle of this specified clock hand. + * @since 1.0 + * @version 1.0 + */ + uint16_t GetHandCurrentAngle(HandType type) const; + + /** + * @brief Sets the initial time in the 24-hour format. + * + * @param hour Indicates the hour to set. + * @param minute Indicates the minute to set. + * @param second Indicates the second to set. + * @since 1.0 + * @version 1.0 + */ + void SetInitTime24Hour(uint8_t hour, uint8_t minute, uint8_t second); + + /** + * @brief Sets the initial time in the 12-hour format. + * + * @param hour Indicates the hour to set. + * @param minute Indicates the minute to set. + * @param second Indicates the second to set. + * @param am Specifies whether it is in the morning. true indicates that it is in the morning, + * and false indicates that it is in the afternoon. + * @since 1.0 + * @version 1.0 + */ + void SetInitTime12Hour(uint8_t hour, uint8_t minute, uint8_t second, bool am); + + /** + * @brief Draws an analog clock. + * + * @param invalidatedArea Indicates the area to draw. + * @since 1.0 + * @version 1.0 + */ + void OnDraw(const Rect& invalidatedArea) override; + + /** + * @brief Performs the operations needed after the drawing. + * + * @param invalidatedArea Indicates the area to draw. + * @since 1.0 + * @version 1.0 + */ + virtual void OnPostDraw(const Rect& invalidatedArea) override; + + /** + * @brief Sets the position for this analog clock. + * + * @param x Indicates the x-coordinate to set. + * @param y Indicates the y-coordinate to set. + * @since 1.0 + * @version 1.0 + */ + void SetPosition(int16_t x, int16_t y) override; + + /** + * @brief Sets the position and size for this analog clock. + * + * @param x Indicates the x-coordinate to set. + * @param y Indicates the y-coordinate to set. + * @param width Indicates the width to set. + * @param height Indicates the height to set. + * @since 1.0 + * @version 1.0 + */ + void SetPosition(int16_t x, int16_t y, int16_t width, int16_t height) override; + + /** + * @brief Sets the working mode for this analog clock. + * + * @param newMode Indicates the working mode to set. For details, see {@link WorkMode}. + * @since 1.0 + * @version 1.0 + */ + void SetWorkMode(WorkMode newMode) override; + + /** + * @brief Updates the time of this analog clock. + * + * @param clockInit Specifies whether it is the first initialization. true indicates it is the + * first initialization, and false indicates the opposite case. + * @since 1.0 + * @version 1.0 + */ + void UpdateClock(bool clockInit) override; + +private: + Hand hourHand_; + Hand minuteHand_; + Hand secondHand_; + + void DrawHand(const Rect& current, const Rect& invalidatedArea, Hand& hand); + void DrawHandImage(const Rect& current, const Rect& invalidatedArea, Hand& hand); + void DrawHandLine(const Rect& invalidatedArea, Hand& hand); + uint16_t ConvertHandValueToAngle(uint8_t handValue, uint8_t range, uint8_t secondHandValue, uint8_t ratio) const; + uint16_t ConvertHandValueToAngle(uint8_t handValue, uint8_t range) const; + void CalculateRedrawArea(const Rect& current, Hand& hand, bool clockInit); +}; +} // namespace OHOS +#endif // UI_ANALOG_CLOCK_H diff --git a/interfaces/kits/components/ui_arc_label.h b/interfaces/kits/components/ui_arc_label.h new file mode 100755 index 0000000..fd66e0d --- /dev/null +++ b/interfaces/kits/components/ui_arc_label.h @@ -0,0 +1,381 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @addtogroup UI_Components + * @{ + * + * @brief Defines UI components such as buttons, texts, images, lists, and progress bars. + * + * @since 1.0 + * @version 1.0 + */ + +/** + * @file ui_arc_label.h + * + * @brief Defines the attributes of an arc label. + * + * The attributes include the center and radius of an arc, angle range, and text orientation. + * + * @since 1.0 + * @version 1.0 + */ + +#ifndef GRAPHIC_LITE_UI_ARC_LABEL_H +#define GRAPHIC_LITE_UI_ARC_LABEL_H + +#include "common/text.h" +#include "components/ui_view.h" + +namespace OHOS { +/** + * @brief Defines functions related to an arc label. + * + * @since 1.0 + * @version 1.0 + */ +class UIArcLabel : public UIView { +public: + /** + * @brief Enumerates text orientations. + */ + enum class TextOrientation : uint8_t { + /** Inside */ + INSIDE, + /** Outside */ + OUTSIDE, + }; + + /** + * @brief A default constructor used to create a UIArcLabel instance. + * + * @since 1.0 + * @version 1.0 + */ + UIArcLabel(); + + /** + * @brief A destructor used to delete the UIArcLabel instance. + * + * @since 1.0 + * @version 1.0 + */ + virtual ~UIArcLabel(); + + /** + * @brief Obtains the view type. + * + * @return Returns UI_ARC_LABEL, as defined in {link UIViewType}. + * @since 1.0 + * @version 1.0 + */ + UIViewType GetViewType() const override + { + return UI_ARC_LABEL; + } + + /** + * @brief Obtains the width of this arc text. + * + * @return Returns the width of this arc text. + * @since 1.0 + * @version 1.0 + */ + int16_t GetWidth() override + { + ReMeasure(); + return UIView::GetWidth(); + } + + /** + * @brief Obtains the height of this arc text. + * + * @return Returns the height of this arc text. + * @since 1.0 + * @version 1.0 + */ + int16_t GetHeight() override + { + ReMeasure(); + return UIView::GetHeight(); + } + + /** + * @brief Sets a style. + * + * @param key Indicates the key of the style to set. + * @param value Indicates the value matching the key. + * @since 1.0 + * @version 1.0 + */ + void SetStyle(uint8_t key, int64_t value) override; + + /** + * @brief Sets the text content for this arc label. + * + * @param text Indicates the pointer to the text content. + * @since 1.0 + * @version 1.0 + */ + void SetText(const char* text); + + /** + * @brief Obtains the text of this arc label. + * + * @return Returns the text. + * @since 1.0 + * @version 1.0 + */ + const char* GetText() const; + + /** + * @brief Sets the alignment mode for this text. + * + * @param horizontalAlign Indicates the horizontal alignment mode to set, + * which can be {@link TEXT_ALIGNMENT_LEFT}, + * {@link TEXT_ALIGNMENT_CENTER}, or {@link TEXT_ALIGNMENT_RIGHT}. + * @since 1.0 + * @version 1.0 + */ + void SetAlign(UITextLanguageAlignment horizontalAlign); + + /** + * @brief Obtains the horizontal alignment mode. + * + * @return Returns the horizontal alignment mode. + * @since 1.0 + * @version 1.0 + */ + UITextLanguageAlignment GetHorAlign(); + + /** + * @brief Obtains the direction of this text. + * + * @return Returns the text direction, as defined in {@link UITextLanguageDirect}. + * @since 1.0 + * @version 1.0 + */ + UITextLanguageDirect GetDirect(); + + /** + * @brief Sets the font ID for this arc label. + * + * @param fontId Indicates the font ID composed of font name and size. + * @since 1.0 + * @version 1.0 + */ + void SetFontId(uint8_t fontId); + + /** + * @brief Obtains the font ID composed of font name and size. + * + * @return Returns the front ID of this arc label. + * @since 1.0 + * @version 1.0 + */ + uint8_t GetFontId(); + + /** + * @brief Sets the font for this arc label. + * + * @param name Indicates the pointer to the font name. + * @param size Indicates the font size to set. + * @since 1.0 + * @version 1.0 + */ + void SetFont(const char* name, uint8_t size); + + /** + * @brief Sets the center position for this arc text. + * + * @param x Indicates the x-coordinate to set. + * @param y Indicates the y-coordinate to set. + * @since 1.0 + * @version 1.0 + */ + void SetArcTextCenter(int16_t x, int16_t y) + { + if ((arcCenter_.x != x) || (arcCenter_.y != y)) { + arcCenter_.x = x; + arcCenter_.y = y; + RefreshArcLabel(); + } + } + + /** + * @brief Obtains the center position of this arc text. + * + * @return Returns the center position of this arc text. + * @since 1.0 + * @version 1.0 + */ + Point GetArcTextCenter() const + { + return arcCenter_; + } + + /** + * @brief Sets the radius for this arc text. + * + * @param radius Indicates the radius to set. + * @since 1.0 + * @version 1.0 + */ + void SetArcTextRadius(uint16_t radius) + { + if (radius_ != radius) { + radius_ = radius; + RefreshArcLabel(); + } + } + + /** + * @brief Obtains the radius of this arc text. + * + * @return Returns the radius of this arc text. + * @since 1.0 + * @version 1.0 + */ + uint16_t GetArcTextRadius() const + { + return radius_; + } + + /** + * @brief Sets the start angle and end angle for this arc text. + * + * The angle in 12 o'clock direction is 0 degrees, and the value increases clockwise. + * The text direction is clockwise when the end angle is greater than the start angle, and the text direction is + * counterclockwise otherwise. + * + * @param startAngle Indicates the start angle to set. + * @param endAngle Indicates the end angle to set. + * @since 1.0 + * @version 1.0 + */ + void SetArcTextAngle(int16_t startAngle, int16_t endAngle) + { + if ((startAngle_ != startAngle) || (endAngle_ != endAngle)) { + startAngle_ = startAngle; + endAngle_ = endAngle; + RefreshArcLabel(); + } + } + + /** + * @brief Obtains the start angle of this arc text. + * + * @return Returns the start angle of this arc text. + * @since 1.0 + * @version 1.0 + */ + int16_t GetArcTextStartAngle() const + { + return startAngle_; + } + + /** + * @brief Obtains the end angle of this arc text. + * + * @return Returns the end angle of this arc text. + * @since 1.0 + * @version 1.0 + */ + int16_t GetArcTextEndAngle() const + { + return endAngle_; + } + + /** + * @brief Sets the orientation for this arc text. + * + * @param orientation Indicates the text orientation to set. + * @since 1.0 + * @version 1.0 + */ + void SetArcTextOrientation(TextOrientation orientation) + { + if (orientation_ != orientation) { + orientation_ = orientation; + RefreshArcLabel(); + } + } + + /** + * @brief Obtains the orientation of this arc text. + * + * @return Returns the orientation of this arc text. + * @since 1.0 + * @version 1.0 + */ + TextOrientation GetArcTextOrientation() const + { + return orientation_; + } + + /** + * @brief Draws an arc text. + * + * @param invalidatedArea Indicates the area to draw. + * @since 1.0 + * @version 1.0 + */ + virtual void OnDraw(const Rect& invalidatedArea) override; + + /** + * @brief Stores the attribute information about this arc text to draw. + */ + struct ArcTextInfo { + uint16_t radius; + float startAngle; + Point arcCenter; + uint32_t lineStart; + uint32_t lineEnd; + UITextLanguageDirect direct; + }; + +protected: + Text* arcLabelText_; + + virtual void InitArcLabelText() + { + if (arcLabelText_ == nullptr) { + arcLabelText_ = new Text(); + if (arcLabelText_ == nullptr) { + GRAPHIC_LOGE("new Text fail"); + return; + } + } + } + void RefreshArcLabel(); + +private: + virtual void ReMeasure() override; + void MeasureArcTextInfo(); + void DrawArcText(const Rect& mask, OpacityType opaScale); + + bool needRefresh_; + Point textSize_; + uint16_t radius_; + int16_t startAngle_; + int16_t endAngle_; + Point arcCenter_; + TextOrientation orientation_; + + ArcTextInfo arcTextInfo_; +}; +} // namespace OHOS +#endif // GRAPHIC_LITE_UI_ARC_LABEL_H diff --git a/interfaces/kits/components/ui_axis.h b/interfaces/kits/components/ui_axis.h new file mode 100755 index 0000000..ae795a0 --- /dev/null +++ b/interfaces/kits/components/ui_axis.h @@ -0,0 +1,314 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @addtogroup UI_Components + * @{ + * + * @brief Defines UI components such as buttons, texts, images, lists, and progress bars. + * + * @since 1.0 + * @version 1.0 + */ + +/** + * @file ui_axis.h + * + * @brief Defines the attributes and functions of the x- and y-axises. This class is used in {@link UIChart}. + * + * @since 1.0 + * @version 1.0 + */ + +#ifndef GRAPHIC_LITE_UI_AXIS_H +#define GRAPHIC_LITE_UI_AXIS_H + +#include "components/ui_label.h" +#include "components/ui_view_group.h" + +namespace OHOS { +/** + * @brief Represents the coordinate axis base class, which defines the basic attributes of coordinate axis, + * sets whether a coordinate axis is visible, and sets the number of scales on a coordinate axis. + * This class is used in {@link UIChart}. + * + * @since 1.0 + * @version 1.0 + */ +class UIAxis : public UIViewGroup { +public: + /** + * @brief A constructor used to create a UIAxis instance. + * + * @since 1.0 + * @version 1.0 + */ + UIAxis(); + + /** + * @brief A destructor used to delete the UIAxis instance. + * + * @since 1.0 + * @version 1.0 + */ + virtual ~UIAxis() {} + + /** + * @brief Obtains the view type. + * + * @return Returns the view type. For details, see {@link UIViewType}. + * @since 1.0 + * @version 1.0 + */ + UIViewType GetViewType() const override + { + return UI_AXIS; + } + + /** + * @brief Sets the value range of a coordinate axis. + * + * The maximum value must be greater than the minimum value. Otherwise, the setting fails. + * + * @param min Indicates the minimum value to set. + * @param max Indicates the maximum value to set. + * @return Returns true if the setting is successful; returns false otherwise. + * @since 1.0 + * @version 1.0 + */ + virtual bool SetDataRange(uint16_t min, uint16_t max) = 0; + + /** + * @brief Sets the number of scales on a coordinate axis. + * + * For a bar chart, the number of scales must be the same as that of bars so that each bar + * can be properly displayed between two scales. + * + * @param count Indicates the number of scales to set. The default value is 5. + * @since 1.0 + * @version 1.0 + */ + virtual void SetMarkNum(uint16_t count) = 0; + + void EnableReverse(bool enable) + { + enableReverse_ = enable; + } + + const Point& GetStartPoint() const + { + return start_; + } + + const Point& GetEndPoint() const + { + return end_; + } + + float GetMarkInterval() const + { + return markInterval_; + } + + /** + * @brief Sets the line color of the coordinate axis. + * + * @param color Indicates the line color to set. For details, see {@link ColorType}. + * @since 1.0 + * @version 1.0 + */ + void SetLineColor(const ColorType& color); + + void OnDraw(const Rect& invalidatedArea) override; + + bool OnPreDraw(Rect& invalidatedArea) const override + { + return false; + } + + virtual bool UpdateAxis() = 0; + + /** + * @brief Translates data into pixel coordinates. + * + * This function calculates the relative position of a pixel in the corresponding {@link UIChart} + * based on the value of value and the data range of the coordinate axis. + * + * @param value Indicates the current value. The coordinate value obtained after translation is + * also printed using this parameter. + * @since 1.0 + * @version 1.0 + */ + virtual void TranslateToPixel(int16_t& value) = 0; + + virtual void UpdateAxisPoints() = 0; + +protected: + float maxRange_; + float minRange_; + Point start_; + Point end_; + float markInterval_; + float dataPerMark_; + float dataInterval_; + uint16_t markDataCount_; + bool enableReverse_; + + static constexpr uint8_t AXIS_DEFAULT_MARK_INTERVAL = 5; + static constexpr uint8_t AXIS_DEFAULT_MARK_LENGTH = 1; + + virtual void DrawAxisMark(const Rect& invalidatedArea) = 0; +}; + +/** + * @brief Defines the unique attributes and functions for the x-axis. This class is used in {@link UIChart}. + * + * @see UIAxis + * @since 1.0 + * @version 1.0 + */ +class UIXAxis : public UIAxis { +public: + /** + * @brief A constructor used to create a UIXAxis instance. + * + * @since 1.0 + * @version 1.0 + */ + UIXAxis() {} + + /** + * @brief A destructor used to delete the UIXAxis instance. + * + * @since 1.0 + * @version 1.0 + */ + virtual ~UIXAxis() {} + + bool UpdateAxis() override; + + /** + * @brief Translates data into the x coordinate of a pixel. + * + * This function calculates the position of the corresponding pixel (relative position in the chart) + * based on the value of value and the data range of the x-axis. + * + * @param value Indicates the current value. The x coordinate obtained after translation is also + * printed using this parameter. + * @since 1.0 + * @version 1.0 + */ + void TranslateToPixel(int16_t& value) override; + + /** + * @brief Sets the value range of the X axis. + * + * The maximum value must be greater than the minimum value. Otherwise, the setting fails. + * + * @param min Indicates the minimum value to set. + * @param max Indicates the maximum value to set. + * @return Returns true if the setting is successful; returns false otherwise. + * @since 1.0 + * @version 1.0 + */ + bool SetDataRange(uint16_t min, uint16_t max) override; + + /** + * @brief Sets the number of scales on the x-axis. + * + * For a bar chart, the number of scales must be the same as that of bars so that each bar can be properly + * displayed between two scales. + * + * @param count Indicates the number of scales to set. The default value is 5. + * @since 1.0 + * @version 1.0 + */ + void SetMarkNum(uint16_t count) override; + + void UpdateAxisPoints() override; + +private: + void DrawAxisMark(const Rect& invalidatedArea) override; +}; + +/** + * @brief Defines the unique attributes and functions for the y-axis. This class is used in {@link UIChart}. + * + * @see UIAxis + * @since 1.0 + * @version 1.0 + */ +class UIYAxis : public UIAxis { +public: + /** + * @brief A constructor used to create a UIYAxis instance. + * + * @since 1.0 + * @version 1.0 + */ + UIYAxis() {} + + /** + * @brief A destructor used to delete the UIYAxis instance. + * + * @since 1.0 + * @version 1.0 + */ + virtual ~UIYAxis() {} + + bool UpdateAxis() override; + + /** + * @brief Translates data into the y coordinate of a pixel. + * + * Calculates the position of the corresponding pixel (relative position in the chart) based on the value + * of value and the data range of the Y axis. + * + * @param value Indicates the current value. The y coordinate obtained after translation is also printed + * using this parameter. + * @since 1.0 + * @version 1.0 + */ + void TranslateToPixel(int16_t& value) override; + + /** + * @brief Sets the value range of the y-axis. + * + * The maximum value must be greater than the minimum value. Otherwise, the setting fails. + * + * @param min Indicates the minimum value to set. + * @param max Indicates the maximum value to set. + * @return Returns true if the setting is successful; returns false otherwise. + * @since 1.0 + * @version 1.0 + */ + bool SetDataRange(uint16_t min, uint16_t max) override; + + /** + * @brief Sets the number of scales on the Y axis. + * + * @param count Indicates the number of scales to set. The default value is 5. + * @since 1.0 + * @version 1.0 + */ + void SetMarkNum(uint16_t count) override; + + void UpdateAxisPoints() override; + +private: + void DrawAxisMark(const Rect& invalidatedArea) override; +}; +} // namespace OHOS +#endif // GRAPHIC_LITE_UI_AXIS_H diff --git a/interfaces/kits/components/ui_box_progress.h b/interfaces/kits/components/ui_box_progress.h new file mode 100755 index 0000000..4682835 --- /dev/null +++ b/interfaces/kits/components/ui_box_progress.h @@ -0,0 +1,240 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @addtogroup UI_Components + * @{ + * + * @brief Defines UI components such as buttons, texts, images, lists, and progress bars. + * + * @since 1.0 + * @version 1.0 + */ + +/** + * @file ui_box_progress.h + * + * @brief Defines the attributes and common functions of a linear progress bar. + * + * @since 1.0 + * @version 1.0 + */ + +#ifndef GRAPHIC_LITE_UI_BOX_PROGRESS_H +#define GRAPHIC_LITE_UI_BOX_PROGRESS_H + +#include "components/ui_abstract_progress.h" + +namespace OHOS { +/** + * @brief Represents a linear progress bar. + * + * This class is used to set the range and current value to display the linear progress bar + * which can be in multiple directions. + * + * @see UIAbstractProgress + * @since 1.0 + * @version 1.0 + */ +class UIBoxProgress : public UIAbstractProgress { +public: + /** + * @brief Enumerates the directions of the progress bar. + */ + enum class Direction : uint8_t { + /** Filling from left to right */ + DIR_LEFT_TO_RIGHT, + /** Filling from right to left */ + DIR_RIGHT_TO_LEFT, + /** Filling from top to bottom */ + DIR_TOP_TO_BOTTOM, + /** Filling from bottom to top */ + DIR_BOTTOM_TO_TOP, + }; + + /** + * @brief A constructor used to create a UIBoxProgress instance. + * + * @since 1.0 + * @version 1.0 + */ + UIBoxProgress(); + + /** + * @brief A destructor used to delete the UIBoxProgress instance. + * + * @since 1.0 + * @version 1.0 + */ + virtual ~UIBoxProgress() {} + + /** + * @brief Obtains the view type. + * + * @return Returns the view type, as defined in {@link UIViewType}. + * @since 1.0 + * @version 1.0 + */ + UIViewType GetViewType() const override + { + return UI_BOX_PROGRESS; + } + + /** + * @brief Sets the width for the view holding this progress bar. + * + * The width of the view must be greater than or equal to the actual width of the progress bar + * to ensure a normal display. \n + * + * @param width Indicates the width of the view. + * @see SetHeight | SetValidWidth | SetValidHeight + * @since 1.0 + * @version 1.0 + */ + void SetWidth(int16_t width) override + { + UIView::SetWidth(width); + if (!isValidWidthSet_) { + progressWidth_ = width; + } + } + + /** + * @brief Sets the height for this view. + * + * The height of the view must be greater than or equal to the actual height of the progress bar + * to ensure a normal display. \n + * + * @param height Indicates the height to set. + * @see SetWidth | SetValidWidth | SetValidHeight + * @since 1.0 + * @version 1.0 + */ + void SetHeight(int16_t height) override + { + UIView::SetHeight(height); + if (!isValidHeightSet_) { + progressHeight_ = height; + } + } + + /** + * @brief Sets the direction for this progress bar. + * + * @param direction Indicates the direction to set. The default direction is from left to right. + * For details, see {@link Direction}. + * @see GetDirection + * @since 1.0 + * @version 1.0 + */ + void SetDirection(const Direction& direction) + { + direction_ = direction; + } + + /** + * @brief Obtains the direction of this progress bar. + * + * @return Returns the direction of this progress bar, as defined in {@link Direction}. + * @see SetDirection + * @since 1.0 + * @version 1.0 + */ + Direction GetDirection() const + { + return direction_; + } + + /** + * @brief Sets the actual width for this progress bar. + * + * The progress bar is centered in the view after the setting. By default, the width of the progress bar + * is the same as that of the view. \n + * If the width of the progress bar is greater than that of the view, the excess part cannot be displayed. \n + * + * @param width Indicates the actual width of this progress bar. + * @see GetValidWidth + * @since 1.0 + * @version 1.0 + */ + void SetValidWidth(int16_t width) + { + progressWidth_ = width; + isValidWidthSet_ = true; + } + + /** + * @brief Obtains the actual width of this progress bar. + * + * @return Returns the actual width of this progress bar. + * @see SetValidWidth + * @since 1.0 + * @version 1.0 + */ + int16_t GetValidWidth() const + { + return progressWidth_; + } + + /** + * @brief Sets the actual height for this progress bar. + * + * The progress bar is centered in the view after the setting. By default, the height of the progress bar + * is the same as that of the view. \n + * If the height of the progress bar is greater than that of the view, the excess part cannot be displayed. \n + * + * @param height Indicates the actual height to set. + * @see GetValidHeight + * @since 1.0 + * @version 1.0 + */ + void SetValidHeight(int16_t height) + { + progressHeight_ = height; + isValidHeightSet_ = true; + } + + /** + * @brief Obtains the actual height of this progress bar. + * + * @return Returns the actual height of this progress bar. + * @see SetValidHeight + * @since 1.0 + * @version 1.0 + */ + int16_t GetValidHeight() const + { + return progressHeight_; + } + + void OnDraw(const Rect& invalidatedArea) override; + +protected: + void GetBackgroundParam(Point& startPoint, int16_t& width, int16_t& height, uint16_t& radius, const Style& style); + void DrawBackground(const Rect& invalidatedArea); + void DrawForeground(const Rect& invalidatedArea, Rect& coords); + void DrawRoundCap(const Image* image, const Point& imgPos, const Rect& rect, const Rect& invalidatedArea, + uint16_t radius, const Style& style); + void DrawValidRect(const Image* image, const Rect& rect, const Rect& invalidatedArea, const Style& style, + uint16_t radius); + + uint16_t progressWidth_; + uint16_t progressHeight_; + Direction direction_; + bool isValidWidthSet_ : 1; + bool isValidHeightSet_ : 1; +}; +} // namespace OHOS +#endif // GRAPHIC_LITE_UI_BOX_PROGRESS_H diff --git a/interfaces/kits/components/ui_button.h b/interfaces/kits/components/ui_button.h new file mode 100755 index 0000000..f949ebf --- /dev/null +++ b/interfaces/kits/components/ui_button.h @@ -0,0 +1,415 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @addtogroup UI_Components + * @{ + * + * @brief Defines UI components such as buttons, texts, images, lists, and progress bars. + * + * @since 1.0 + * @version 1.0 + */ + +/** + * @file ui_button.h + * + * @brief Defines the attributes and common functions of a button. + * + * @since 1.0 + * @version 1.0 + */ + +#ifndef GRAPHIC_LITE_UI_BUTTON_H +#define GRAPHIC_LITE_UI_BUTTON_H + +#include "common/image.h" +#include "components/ui_view.h" + +namespace OHOS { +/** + * @brief Represents a button. + * + * This component responds to the press and release events. + * + * @since 1.0 + * @version 1.0 + */ +class UIButton : public UIView { +public: + /** + * @brief A constructor used to create a UIButton instance. + * + * @since 1.0 + * @version 1.0 + */ + UIButton(); + + /** + * @brief A constructor used to create a UIButton instance based on the button ID. + * + * Buttons sharing the same ID are in the same batch. + * + * @param id Indicates the button ID. + * @since 1.0 + * @version 1.0 + */ + UIButton(const char* id) : UIButton() + { + id_ = id; + } + + /** + * @brief A destructor used to delete the UIButton instance. + * + * @since 1.0 + * @version 1.0 + */ + virtual ~UIButton(); + + /** + * @brief Obtains the component type. + * + * @return Returns the component type, as defined in {@link UIViewType}. + * @since 1.0 + * @version 1.0 + */ + UIViewType GetViewType() const override + { + return UI_BUTTON; + } + + /** + * @fn virtual bool UIButton::OnPreDraw(Rect& invalidatedArea) override + * + * @brief Do something before draw, this function will be invoked mainly to check if this view need + * to cover invalidate area so render manager can decide which layer to draw firstly. + * @param [in] invalidate area. + * @returns True if need cover. + */ + bool OnPreDraw(Rect& invalidatedArea) const override; + + /** + * @fn virtual void UIButton::OnDraw(const Rect& invalidatedArea) override; + * + * @brief Executes the draw action + * + * @param [in] invalidatedArea The rectangle to draw, with coordinates relative to this drawable.. + */ + void OnDraw(const Rect& invalidatedArea) override; + + /** + * @fn virtual void UIButton::OnPressEvent(const PressEvent& event) override; + * + * @brief Executes the press event action + * + * @param [in] event The press event, contain press position. + * + * @return Returns true if the event is consumed; returns false otherwise. + */ + bool OnPressEvent(const PressEvent& event) override; + + /** + * @fn virtual void UIButton::OnReleaseEvent(const ReleaseEvent& event) override; + * + * @brief Executes the press release event action + * + * @param [in] event The press release event. + */ + bool OnReleaseEvent(const ReleaseEvent& event) override; + + /** + * @fn virtual void UIButton::OnCancelEvent(const CancelEvent& event) override; + * + * @brief Executes the cancel event action + * + * @param [in] event The cancel event. + */ + bool OnCancelEvent(const CancelEvent& event) override; + + /** + * @brief Enumerates the images for different button states. + * + * You can define a different image for each button state. + * + * @since 1.0 + * @version 1.0 + */ + enum ButtonImageSrc : uint8_t { + /* An enum constant representing the Button image default option */ + BTN_IMAGE_DEFAULT, + /* An enum constant representing the Button image triggered option */ + BTN_IMAGE_TRIGGERED, + /* An enum constant representing the Button Image Number option */ + BTN_IMG_NUM, + }; + + /** + * @brief Sets the image for this button. + * + * @param defaultImgSrc Indicates the default image. + * @param triggeredImgSrc Indicates the image for a button when it is triggered. + * @since 1.0 + * @version 1.0 + */ + void SetImageSrc(const char* defaultImgSrc, const char* triggeredImgSrc); + + /** + * @brief Sets the image for this button. + * + * @param defaultImgSrc Indicates the default image. + * @param triggeredImgSrc Indicates the image for a button when it is triggered. + * @since 1.0 + * @version 1.0 + */ + void SetImageSrc(const ImageInfo* defaultImgSrc, const ImageInfo* triggeredImgSrc); + + /** + * @brief Sets the position for this image. + * + * @param x Indicates the x-coordinate to set. + * @param y Indicates the y-coordinate to set. + * @since 1.0 + * @version 1.0 + */ + void SetImagePosition(const int16_t x, const int16_t y) + { + imgX_ = x; + imgY_ = y; + } + + /** + * @brief Obtains the x-coordinate of this image. + * + * @return Returns the x-coordinate of this image. + * @since 1.0 + * @version 1.0 + */ + int16_t GetImageX() const + { + return imgX_; + } + + /** + * @brief Obtains the y-coordinate of this image. + * + * @return Returns the y-coordinate of this image. + * @since 1.0 + * @version 1.0 + */ + int16_t GetImageY() const + { + return imgY_; + } + + /** + * @brief Obtains the image for the current button state. + * + * @return Returns the image for the current button state. + * @since 1.0 + * @version 1.0 + */ + const Image* GetCurImageSrc() const; + + /** + * @brief Enumerates the states of this button. + * + * The button has three states. + * + * @since 1.0 + * @version 1.0 + */ + enum ButtonState : uint8_t { + /* An enum constant representing the Button state released option */ + RELEASED = 0, + /* An enum constant representing the Button state pressed option */ + PRESSED, + /* An enum constant representing the Button state inactive option */ + INACTIVE, + /* An enum constant representing the Button state Number option */ + BTN_STATE_NUM, + }; + + /** + * @brief Obtains the width of this image. + * + * @return Returns the image width. + * @since 1.0 + * @version 1.0 + */ + int16_t GetWidth() override + { + Style* style = buttonStyles_[state_]; + return GetRelativeRect().GetWidth() - (style->paddingLeft_ + style->paddingRight_) - + (style->borderWidth_ * 2); /* 2: left and right border */ + } + + /** + * @brief Obtains the height of this image. + * + * @return Returns the image height. + * @since 1.0 + * @version 1.0 + */ + int16_t GetHeight() override + { + Style* style = buttonStyles_[state_]; + return GetRelativeRect().GetHeight() - (style->paddingTop_ + style->paddingBottom_) - + (style->borderWidth_ * 2); /* 2: top and bottom border */ + } + + /** + * @brief Sets the width for this image. + * + * @param width Indicates the width to set. + * @since 1.0 + * @version 1.0 + */ + void SetWidth(int16_t width) override + { + contentWidth_ = width; + Style* style = buttonStyles_[state_]; + UIView::SetWidth(width + (style->paddingLeft_ + style->paddingRight_) + + (style->borderWidth_ * 2)); /* 2: left and right border */ + } + + /** + * @brief Sets the height for this image. + * + * @param height Indicates the height to set. + * @since 1.0 + * @version 1.0 + */ + void SetHeight(int16_t height) override + { + contentHeight_ = height; + Style* style = buttonStyles_[state_]; + UIView::SetHeight(height + (style->paddingTop_ + style->paddingBottom_) + + (style->borderWidth_ * 2)); /* 2: top and bottom border */ + } + + /** + * @brief Obtains a rectangular area that contains coordinate information. + * + * @return Returns the rectangle area. + * @since 1.0 + * @version 1.0 + */ + virtual Rect GetContentRect() override + { + Rect contentRect; + Style* style = buttonStyles_[state_]; + contentRect.SetX(GetOrigRect().GetX() + style->paddingLeft_ + style->borderWidth_); + contentRect.SetY(GetOrigRect().GetY() + style->paddingTop_ + style->borderWidth_); + contentRect.SetWidth(GetWidth()); + contentRect.SetHeight(GetHeight()); + return contentRect; + } + + /** + * @brief Obtains the value of a style. + * + * @param key Indicates the key of the style. + * @return Returns the value of the style. + * @since 1.0 + * @version 1.0 + */ + int64_t GetStyle(uint8_t key) const override; + + /** + * @brief Sets a style. + * + * @param key Indicates the key of the style to set. + * @param value Indicates the value matching the key. + * @since 1.0 + * @version 1.0 + */ + void SetStyle(uint8_t key, int64_t value) override; + + /** + * @brief Obtains the style of a button in a specific state. + * + * @param key Indicates the key of the style. + * @param state Indicates the button state, as enumerated in {@link ButtonState}. + * @return Returns the style of the button in the specific state. + * @since 1.0 + * @version 1.0 + */ + int64_t GetStyleForState(uint8_t key, ButtonState state) const; + + /** + * @brief Sets the style for a button in a specific state. + * + * @param key Indicates the key of the style to set. + * @param value Indicates the value matching the key. + * @param state Indicates the button state, as enumerated in {@link ButtonState}. + * @since 1.0 + * @version 1.0 + */ + void SetStyleForState(uint8_t key, int64_t value, ButtonState state); + + /** + * @brief Disables this button. + * + * @since 1.0 + * @version 1.0 + */ + void Disable(); + + /** + * @brief Enables this button. + * + * @since 1.0 + * @version 1.0 + */ + void Enable(); + + /** + * @brief Sets the state for a button. After the setting, calling {@link SetStyle} + * will change the style of this button, but not its state. + * + * @param state Indicates the button state, as enumerated in {@link ButtonState}. + * @since 1.0 + * @version 1.0 + */ + void SetStateForStyle(ButtonState state) + { + styleState_ = state; + } + +protected: + Image* defaultImgSrc_; + Image* triggeredImgSrc_; + ButtonImageSrc currentImgSrc_; + int16_t imgX_; + int16_t imgY_; + int16_t contentWidth_; + int16_t contentHeight_; + void SetState(ButtonState state); + bool InitImage(); + + ButtonState state_; + ButtonState styleState_; + Style* buttonStyles_[BTN_STATE_NUM]; + bool buttonStyleAllocFlag_; + +private: + /** Sets up the theme styles */ + void SetupThemeStyles(); + + void DrawImg(const Rect& invalidatedArea, OpacityType opaScale); +}; +} // namespace OHOS +#endif // GRAPHIC_LITE_UI_BUTTON_H diff --git a/interfaces/kits/components/ui_canvas.h b/interfaces/kits/components/ui_canvas.h new file mode 100755 index 0000000..99a8d51 --- /dev/null +++ b/interfaces/kits/components/ui_canvas.h @@ -0,0 +1,681 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @addtogroup UI_Components + * @{ + * + * @brief Defines UI components such as buttons, texts, images, lists, and progress bars. + * + * @since 1.0 + * @version 1.0 + */ + +/** + * @file ui_canvas.h + * + * @brief Defines the attributes of the canvas component and provides functions for drawing rectangles, + * circles, and others. + * + * @since 1.0 + * @version 1.0 + */ + +#ifndef GRAPHIC_LITE_UI_CANVAS_H +#define GRAPHIC_LITE_UI_CANVAS_H + +#include "common/image.h" +#include "components/ui_label.h" +#include "list.h" + +namespace OHOS { +/** + * @brief Defines the basic styles of graphs drawn on canvases. + * + * @since 1.0 + * @version 1.0 + */ +class Paint : public HeapBase { +public: + /** + * @brief A constructor used to create a Paint instance. + * + * @since 1.0 + * @version 1.0 + */ + Paint() + : style_(PaintStyle::STROKE_FILL_STYLE), fillColor_(Color::Black()), + strokeColor_(Color::White()), opacity_(OPA_OPAQUE), strokeWidth_(2) {} + + /** + * @brief A destructor used to delete the Paint instance. + * + * @since 1.0 + * @version 1.0 + */ + virtual ~Paint() {} + + /** + * @brief Enumerates paint styles of a closed graph. The styles are invalid for non-closed graphs. + */ + enum PaintStyle { + /** Stroke only */ + STROKE_STYLE = 1, + /** Fill only */ + FILL_STYLE, + /** Stroke and fill */ + STROKE_FILL_STYLE, + }; + + /** + * @brief Sets the paint style of a closed graph. + * + * @param style Indicates the paint style. Stroke and fill are set by default. For details, see {@link PaintStyle}. + * @see GetStyle + * @since 1.0 + * @version 1.0 + */ + void SetStyle(PaintStyle style) + { + style_ = style; + } + + /** + * @brief Obtains the paint style of a closed graph. + * + * @return Returns the paint style. For details, see {@link PaintStyle}. + * @see SetStyle + * @since 1.0 + * @version 1.0 + */ + PaintStyle GetStyle() const + { + return style_; + } + + /** + * @brief Sets the width of a line or border. + * + * @param width Indicates the line width when a line is drawn or the border width when a closed graph is drawn. + * The width is extended to both sides. + * @see GetStrokeWidth + * @since 1.0 + * @version 1.0 + */ + void SetStrokeWidth(uint16_t width) + { + strokeWidth_ = width; + } + + /** + * @brief Obtains the width of a line or border. + * + * @return Returns the line width if a line is drawn or the border width if a closed graph is drawn. + * @see SetStrokeWidth + * @since 1.0 + * @version 1.0 + */ + uint16_t GetStrokeWidth() const + { + return strokeWidth_; + } + + /** + * @brief Sets the color of a line or border. + * + * @param color Indicates the line color when a line is drawn or the border color when a closed graph is drawn. + * @see GetStrokeColor + * @since 1.0 + * @version 1.0 + */ + void SetStrokeColor(ColorType color) + { + strokeColor_ = color; + } + + /** + * @brief Obtains the color of a line or border. + * + * @return Returns the line color if a line is drawn or the border color if a closed graph is drawn. + * @see SetStrokeWidth + * @since 1.0 + * @version 1.0 + */ + ColorType GetStrokeColor() const + { + return strokeColor_; + } + + /** + * @brief Sets fill color. + * + * This function is valid only for closed graphs. + * + * @param color Indicates the fill color to set. + * @see GetFillColor + * @since 1.0 + * @version 1.0 + */ + void SetFillColor(ColorType color) + { + fillColor_ = color; + } + + /** + * @brief Obtains the fill color. + * + * @return Returns the fill color. + * @see SetFillColor + * @since 1.0 + * @version 1.0 + */ + ColorType GetFillColor() const + { + return fillColor_; + } + + /** + * @brief Sets the opacity. + * + * The setting takes effect for the entire graph, including the border, line color, and fill color. + * + * @param opacity Indicates the opacity. The value range is [0, 255]. + * @see GetOpacity + * @since 1.0 + * @version 1.0 + */ + void SetOpacity(uint8_t opacity) + { + opacity_ = opacity; + } + + /** + * @brief Obtains the opacity. + * + * @return Returns the opacity. + * @see SetOpacity + * @since 1.0 + * @version 1.0 + */ + uint8_t GetOpacity() const + { + return opacity_; + } + +private: + PaintStyle style_; + ColorType fillColor_; + ColorType strokeColor_; + uint8_t opacity_; + uint16_t strokeWidth_; +}; + +/** + * @brief Defines a canvas, which is used to draw multiple types of 2D graphs. + * + * @since 1.0 + * @version 1.0 + */ +class UICanvas : public UIView { +public: + /** + * @brief A constructor used to create a UICanvas instance. + * + * @since 1.0 + * @version 1.0 + */ + UICanvas() : startPoint_({ 0, 0 }), path_(nullptr) {} + + /** + * @brief A destructor used to delete the UICanvas instance. + * + * @since 1.0 + * @version 1.0 + */ + virtual ~UICanvas(); + + /** + * @brief Obtains the view type. + * + * @return Returns the view type. For details, see {@link UIViewType}. + * @since 1.0 + * @version 1.0 + */ + UIViewType GetViewType() const override + { + return UI_CANVAS; + } + + /** + * @brief Clears the entire canvas. + * + * @since 1.0 + * @version 1.0 + */ + void Clear(); + + /** + * @brief Sets the coordinates of the start point for drawing a line. For example, if startPoint is + * set to {50, 50}, the line is drawn from this set of coordinates on the canvas. + * + * @param startPoint Indicates the coordinates of the start point. + * @see GetStartPosition + * @since 1.0 + * @version 1.0 + */ + void SetStartPosition(const Point& startPoint) + { + startPoint_ = startPoint; + } + + /** + * @brief Obtains the coordinates of the start point of a line. + * + * @return Returns the coordinates of the start point. + * @see SetStartPosition + * @since 1.0 + * @version 1.0 + */ + const Point& GetStartPosition() const + { + return startPoint_; + } + + /** + * @brief Draws a straight line. + * + * If {@link SetStartPosition} is not used to set the coordinates of the start point of the line, the drawing + * starts from the end point of the last line. + * + * @param endPoint Indicates the end point of the straight line. + * @param paint Indicates the straight line style. For details, see {@link Paint}. + * @since 1.0 + * @version 1.0 + */ + void DrawLine(const Point& endPoint, const Paint& paint); + + /** + * @brief Draws a straight line from the coordinates of the start point. + * + * @param startPoint Indicates the coordinates of the start point. + * @param endPoint Indicates the coordinates of the end point. + * @param paint Indicates the straight line style. For details, see {@link Paint}. + * @since 1.0 + * @version 1.0 + */ + void DrawLine(const Point& startPoint, const Point& endPoint, const Paint& paint); + + /** + * @brief Draws a cubic Bezier curve. + * + * If {@link SetStartPosition} is not used to set the coordinates of the start point of the curve, + * the drawing starts from the end point of the last line. + * Currently, the opacity cannot be set, and the maximum line width is 3. + * + * @param control1 Indicates the coordinates of the first control point of the cubic Bezier curve. + * @param control2 Indicates the coordinates of the second control point of the cubic Bezier curve. + * @param endPoint Indicates the coordinates of the end point of the cubic Bezier curve. + * @param paint Indicates the curve style. For details, see {@link Paint}. + * @since 1.0 + * @version 1.0 + */ + void DrawCurve(const Point& control1, const Point& control2, const Point& endPoint, const Paint& paint); + + /** + * @brief Draws a cubic Bezier curve from the start point coordinates. + * + * Currently, the opacity cannot be set, and the maximum line width is 3. + * + * @param startPoint Indicates the coordinates of the start point of the cubic Bezier curve. + * @param control1 Indicates the coordinates of the first control point of the cubic Bezier curve. + * @param control2 Indicates the coordinates of the second control point of the cubic Bezier curve. + * @param endPoint Indicates the coordinates of the end point of the cubic Bezier curve. + * @param paint Indicates the curve style. For details, see {@link Paint}. + * @since 1.0 + * @version 1.0 + */ + void DrawCurve(const Point& startPoint, const Point& control1, const Point& control2, + const Point& endPoint, const Paint& paint); + + /** + * @brief Draws a rectangle. + * + * @param startPoint Indicates the coordinates of the point at the upper left corner of the rectangle. + * @param height Indicates the height of the rectangle. + * @param width Indicates the width of the rectangle. + * @param paint Indicates the rectangle style. For details, see {@link Paint}. + * @since 1.0 + * @version 1.0 + */ + void DrawRect(const Point& startPoint, int16_t height, int16_t width, const Paint& paint); + + /** + * @brief Draws a circle. + * + * @param center Indicates the coordinates of the circle center. + * @param radius Indicates the radius of the circle. + * @param paint Indicates the circle style. For details, see {@link Paint}. + * @since 1.0 + * @version 1.0 + */ + void DrawCircle(const Point& center, uint16_t radius, const Paint& paint); + + /** + * @brief Draws a sector. + * + * When the start angle is smaller than the end angle, the sector is drawn clockwise. + * Otherwise, the sector is drawn counterclockwise. + * + * @param center Indicates the coordinates of the sector's center. + * @param radius Indicates the radius of the sector. + * @param startAngle Indicates the start angle of the sector. Value 0 indicates the 12-o'clock direction, + * and 90 indicates the 3-o'clock direction. + * @param endAngle Indicates the end angle of the sector. Value 0 indicates the 12-o'clock direction, + * and 90 indicates the 3-o'clock direction. + * @param paint Indicates the sector style. For details, see {@link Paint}. + * @since 1.0 + * @version 1.0 + */ + void DrawSector(const Point& center, uint16_t radius, int16_t startAngle, int16_t endAngle, const Paint& paint); + + /** + * @brief Draws an arc. + * + * Only stroke is supported. \n + * When the start angle is smaller than the end angle, the sector is drawn clockwise. + * Otherwise, the sector is drawn counterclockwise. \n + * + * @param center Indicates the coordinates of the arc's center. + * @param radius Indicates the radius of the arc. + * @param startAngle Indicates the start angle of the arc. Value 0 indicates the 12-o'clock direction, + * and 90 indicates the 3-o'clock direction. + * @param endAngle Indicates the end angle of the arc. Value 0 indicates the 12-o'clock direction, + * and 90 indicates the 3-o'clock direction. + * @param paint Indicates the arc style. For details, see {@link Paint}. + * @since 1.0 + * @version 1.0 + */ + void DrawArc(const Point& center, uint16_t radius, int16_t startAngle, int16_t endAngle, const Paint& paint); + + /** + * @brief Draws an image. + * + * @param startPoint Indicates the coordinates of the start point. + * @param image Indicates the pointer to the image source. + * @param paint Indicates the image style. For details, see {@link Paint}. + * @since 1.0 + * @version 1.0 + */ + void DrawImage(const Point& startPoint, const char* image, const Paint& paint); + + /** + * @brief Defines the font style. + */ + struct FontStyle { + /** Text direction. For details, see {@link UITextLanguageDirect}. */ + UITextLanguageDirect direct; + /** Text alignment mode. For details, see {@link UITextLanguageAlignment}. */ + UITextLanguageAlignment align; + /** Font size */ + uint8_t fontSize; + /** Letter-spacing */ + int16_t letterSpace; + /** Font name */ + const char* fontName; + }; + + /** + * @brief Draws text. + * + * Only fill is supported. \n + * If the text length exceeds the value of maxWidth, the text will be truncated. \n + * + * @param startPoint Indicates the coordinates of the start point. + * @param text Indicates the pointer to the text content. + * @param maxWidth Indicates the maximum width of the text that can be displayed. If the maximum width is + * exceeded, the text is truncated. + * @param fontStyle Indicates the text layout and font style. For details, see {@link FontStyle}. + * @param paint Indicates the text style. For details, see {@link Paint}. + * @since 1.0 + * @version 1.0 + */ + void DrawLabel(const Point& startPoint, const char* text, uint16_t maxWidth, const FontStyle& fontStyle, + const Paint& paint); + + /** + * @brief Creates a path. + * + * A round corner can be used to join two lines. Currently, miter and bevel joints are not supported. + * To draw this path, you need to call {@link DrawPath}. + * + * @since 3.0 + * @version 5.0 + */ + void BeginPath(); + + /** + * @brief Moves the start point of this path to a specified point. + * + * @param point Indicates the specified point to move to. + * @since 3.0 + * @version 5.0 + */ + void MoveTo(const Point& point); + + /** + * @brief Creates a straight line from the end point of this path to a specified point. + * + * @param point Indicates the coordinates of the specified point. + * @since 3.0 + * @version 5.0 + */ + void LineTo(const Point& point); + + /** + * @brief Creates an arc path. + * + * @param center Indicates the coordinates of the arc's center point. + * @param radius Indicates the radius of the arc. + * @param startAngle Indicates the start angle of the arc. The value 0 indicates the 12-o'clock direction, + * and 90 indicates the 3-o'clock direction. + * @param endAngle Indicates the end angle of the arc. The value 0 indicates the 12-o'clock direction, + * and 90 indicates the 3-o'clock direction. + * @since 3.0 + * @version 5.0 + */ + void ArcTo(const Point& center, uint16_t radius, int16_t startAngle, int16_t endAngle); + + /** + * @brief Creates a rectangular path. + * + * @param point Indicates the coordinates of the rectangle's upper left corner. + * @param height Indicates the height of the rectangle. + * @param width Indicates the width of the rectangle. + * @since 3.0 + * @version 5.0 + */ + void AddRect(const Point& point, int16_t height, int16_t width); + + /** + * @brief Closes this path. + * + * @since 3.0 + * @version 5.0 + */ + void ClosePath(); + + /** + * @brief Draws this path. + * + * @param paint Indicates the path style. For details, see {@link Paint}. + * @since 3.0 + * @version 5.0 + */ + void DrawPath(const Paint& paint); + + void OnDraw(const Rect& invalidatedArea) override; + +protected: + constexpr static uint8_t MAX_CURVE_WIDTH = 3; + + struct LineParam : public HeapBase { + Point start; + Point end; + }; + + struct CurveParam : public HeapBase { + Point start; + Point control1; + Point control2; + Point end; + }; + + struct RectParam : public HeapBase { + Point start; + int16_t height; + int16_t width; + }; + + struct CircleParam : public HeapBase { + Point center; + uint16_t radius; + }; + + struct ArcParam : public HeapBase { + Point center; + uint16_t radius; + int16_t startAngle; + int16_t endAngle; + }; + + struct ImageParam : public HeapBase { + Point start; + uint16_t height; + uint16_t width; + Image* image; + }; + + enum PathCmd { + CMD_MOVE_TO, + CMD_LINE_TO, + CMD_ARC, + CMD_CLOSE, + }; + + class UICanvasPath : public HeapBase { + public: + UICanvasPath() : startPos_({ 0, 0 }), strokeCount_(0) {}; + ~UICanvasPath(); + List points_; + List cmd_; + List arcParam_; + Point startPos_; + uint16_t strokeCount_; + }; + + struct PathParam : public HeapBase { + UICanvasPath* path; + uint16_t count; + }; + + struct DrawCmd : public HeapBase { + Paint paint; + void* param; + void(*DrawGraphics)(void*, const Paint&, const Rect&, const Rect&, const Style&); + void(*DeleteParam)(void *); + }; + + Point startPoint_; + UICanvasPath* path_; + List drawCmdList_; + + static void DeleteLineParam(void* param) + { + LineParam* lineParam = static_cast(param); + delete lineParam; + } + + static void DeleteCurveParam(void* param) + { + CurveParam* curveParam = static_cast(param); + delete curveParam; + } + + static void DeleteRectParam(void* param) + { + RectParam* rectParam = static_cast(param); + delete rectParam; + } + + static void DeleteCircleParam(void* param) + { + CircleParam* circleParam = static_cast(param); + delete circleParam; + } + + static void DeleteArcParam(void* param) + { + ArcParam* arcParam = static_cast(param); + delete arcParam; + } + + static void DeleteImageParam(void* param) + { + ImageParam* imageParam = static_cast(param); + if (imageParam->image != nullptr) { + delete imageParam->image; + } + delete imageParam; + } + + static void DeleteLabel(void* param) + { + UILabel* label = static_cast(param); + delete label; + } + + static void DeletePathParam(void* param) + { + PathParam* pathParam = static_cast(param); + pathParam->path->strokeCount_--; + if (pathParam->path->strokeCount_ == 0) { + delete pathParam->path; + } + delete pathParam; + } + + static void DoDrawLine(void* param, const Paint& paint, const Rect& rect, const Rect& invalidatedArea, + const Style& style); + static void DoDrawCurve(void* param, const Paint& paint, const Rect& rect, const Rect& invalidatedArea, + const Style& style); + static void DoDrawRect(void* param, const Paint& paint, const Rect& rect, const Rect& invalidatedArea, + const Style& style); + static void DoFillRect(void* param, const Paint& paint, const Rect& rect, const Rect& invalidatedArea, + const Style& style); + static void DoDrawCircle(void* param, const Paint& paint, const Rect& rect, const Rect& invalidatedArea, + const Style& style); + static void DoDrawArc(void* param, const Paint& paint, const Rect& rect, const Rect& invalidatedArea, + const Style& style); + static void DoDrawImage(void* param, const Paint& paint, const Rect& rect, const Rect& invalidatedArea, + const Style& style); + static void DoDrawLabel(void* param, const Paint& paint, const Rect& rect, const Rect& invalidatedArea, + const Style& style); + static void DoDrawPath(void* param, const Paint& paint, const Rect& rect, const Rect& invalidatedArea, + const Style& style); + static void GetAbsolutePosition(const Point& prePoint, const Rect& rect, const Style& style, Point& point); + static void DoDrawLineJoin(const Point& center, const Rect& invalidatedArea, const Paint& paint); +}; +} // namespace OHOS +#endif // GRAPHIC_LITE_UI_CANVAS_H diff --git a/interfaces/kits/components/ui_chart.h b/interfaces/kits/components/ui_chart.h new file mode 100755 index 0000000..1624cd0 --- /dev/null +++ b/interfaces/kits/components/ui_chart.h @@ -0,0 +1,862 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @addtogroup UI_Components + * @{ + * + * @brief Defines UI components such as buttons, texts, images, lists, and progress bars. + * + * @since 1.0 + * @version 1.0 + */ + +/** + * @file ui_chart.h + * + * @brief Defines the attributes of the chart component and provides functions for adding and deleting + * data sets to display a chart. + * + * @since 1.0 + * @version 1.0 + */ + +#ifndef GRAPHIC_LITE_UI_CHART_H +#define GRAPHIC_LITE_UI_CHART_H + +#include "components/ui_axis.h" +#include "components/ui_view_group.h" +#include "list.h" + +namespace OHOS { +class UIChart; +/** + * @brief Defines a data set and provides functions such as adding and deleting data points. + * + * @since 1.0 + * @version 1.0 + */ +class UIChartDataSerial : public HeapBase { +public: + /** + * @brief A constructor used to create a UIChartDataSerial instance. + * + * @since 1.0 + * @version 1.0 + */ + UIChartDataSerial(); + + /** + * @brief A destructor used to delete the UIChartDataSerial instance. + * + * @since 1.0 + * @version 1.0 + */ + virtual ~UIChartDataSerial() + { + if (pointArray_ != nullptr) { + UIFree(pointArray_); + pointArray_ = nullptr; + } + } + + /** + * @brief Sets the maximum number of data points that can be stored in a data set. + * + * This function must be called before data is added, deleted, or modified. Otherwise, data operations will fail. + * + * @param maxCount Indicates the number of data points. The default value is 0. + * + * @return Returns true if the operation is successful; returns false otherwise. + * @since 1.0 + * @version 1.0 + */ + bool SetMaxDataCount(uint16_t maxCount); + + /** + * @brief Modifies the value of a data point in the data set. + * + * @param index Indicates the index of the data point to modify. + * @param point Indicates the new value of the data point. + * + * @return Returns true if the operation is successful; returns false otherwise. + * @since 1.0 + * @version 1.0 + */ + bool ModifyPoint(uint16_t index, const Point& point); + + /** + * @brief Obtains the coordinates in the chart for a data point in the data set. + * + * @param index Indicates the index of the data point to obtain. + * @param point Indicates the obtained coordinates. If the data set is not added to the chart, + * the original value of the data point is printed. + * + * @return Returns true if the operation is successful; returns false otherwise. + * @since 1.0 + * @version 1.0 + */ + bool GetPoint(uint16_t index, Point& point); + + /** + * @brief Adds data points. + * + * The new data points are appended to the last added data. \n + * No more data points can be added if the maximum number is reached \n + * + * @param data Indicates the pointer to the start address of the data point. + * @param count Indicates the number of data points to add. + * @return Returns true if the data points are added successfully; returns false otherwise. + * @since 1.0 + * @version 1.0 + */ + bool AddPoints(const Point* data, uint16_t count); + + /** + * @brief Clears all data points. + * + * @since 1.0 + * @version 1.0 + */ + void ClearData(); + + /** + * @brief Obtains the number of data points available in the data set. + * + * @return Returns the number of data points. + * @since 1.0 + * @version 1.0 + */ + uint16_t GetDataCount() const + { + return dataCount_; + } + + /** + * @brief Sets whether to smooth a polyline. + * + * This function applies only to line charts. After the smoothing, some data is discarded. + * Therefore, the polyline does not pass through all data points. \n + * If smooth is set to true, the filling color, top point, and bottom point of a line chart have + * deviations. Therefore, you are advised not to use these functions at the same time. \n + * + * @param smooth Specifies whether to smooth a polyline. Value true means to smooth a polyline, and value + * false means not to smooth a polyline. The default value is false. + * @since 1.0 + * @version 1.0 + */ + void EnableSmooth(bool smooth) + { + smooth_ = smooth; + } + + /** + * @brief Checks whether smoothing is performed on a polyline. + * + * @return Returns true if smooth processing is performed on the polyline; returns false otherwise. + * @since 1.0 + * @version 1.0 + */ + bool IsSmooth() const + { + return smooth_; + } + + /** + * @brief Enables the fill color of a line chart. + * + * This function applies only to line charts. By default, the area between the polyline and the x-axis is filled. + * You can use {@link SetGradientBottom} to modify the filled region. \n + * + * @param enable Specifies whether to enable the fill color. Value true means to enable the fill color, + * and value false means to disable the fill color. The default value is false. + * @since 1.0 + * @version 1.0 + */ + void EnableGradient(bool enable) + { + enableGradient_ = enable; + } + + /** + * @brief Checks whether a polyline has a fill color. + * + * @return Returns true if there is a fill color; returns false otherwise. + * @since 1.0 + * @version 1.0 + */ + bool IsGradient() const + { + return enableGradient_; + } + + /** + * @brief Obtains the index of the top point in the data set. + * + * @return Returns the index of the top point. If there are multiple top points, the first one is returned. + * @since 1.0 + * @version 1.0 + */ + uint16_t GetPeakIndex() const + { + return peakPointIndex_; + } + + /** + * @brief Obtains the index of the frontmost point (the latest added or modified data point in a data set). + * + * @return Returns the index of the frontmost point. + * @since 1.0 + * @version 1.0 + */ + uint16_t GetLatestIndex() const + { + return latestIndex_; + } + + /** + * @brief Obtains the index of the bottom point in a data set. + * + * @return Returns the index of the bottom point. If there are multiple bottom points, the first one is returned. + * @since 1.0 + * @version 1.0 + */ + uint16_t GetValleyIndex() const + { + return valleyPointIndex_; + } + + /** + * @brief Obtains the Y value of the top point in a data set. + * + * The Y value is the data added by users, not the pixel coordinate. + * + * @return Returns the Y value. + * @since 1.0 + * @version 1.0 + */ + int16_t GetPeakData() const + { + return peakData_; + } + + /** + * @brief Obtains the Y value of the bottom point in a data set. + * + * The Y value is the data added by users, not the pixel coordinate. + * + * @return Returns the Y value. + * @since 1.0 + * @version 1.0 + */ + int16_t GetValleyData() const + { + return valleyData_; + } + + void SetLastPointIndex(uint16_t value) + { + lastPointIndex_ = value; + } + + uint16_t GetLastPointIndex() const + { + return lastPointIndex_; + } + + /** + * @brief Obtains the polyline color of the data set in a line chart. + * + * @return Returns the polyline color of the data set. + * @see SetLineColor + * @since 1.0 + * @version 1.0 + */ + ColorType GetLineColor() const + { + return serialColor_; + } + + /** + * @brief Obtains the fill color of the data set. + * + * @return Returns the fill color. + * @see SetFillColor + * @since 1.0 + * @version 1.0 + */ + ColorType GetFillColor() const + { + return fillColor_; + } + + /** + * @brief Sets the fill color of the data set. + * + * For a line chart, color refers to the fill color between the line and the x-axis. + * For a bar chart, color refers to the color of the bars. + * + * @param color Indicates the fill color to set. + * @see GetFillColor + * @since 1.0 + * @version 1.0 + */ + void SetFillColor(const ColorType& color) + { + fillColor_ = color; + } + + /** + * @brief Sets the polyline color of the data set in the line chart. + * + * This function applies only to line charts. + * + * @param color Indicates the polyline color to set. + * @see GetLineColor + * @since 1.0 + * @version 1.0 + */ + void SetLineColor(const ColorType& color) + { + serialColor_ = color; + } + + void BindToChart(UIChart* chart) + { + chart_ = chart; + } + + /** + * @brief Hides some points in the data set. + * + * This function applies only to line charts. After the points are hidden, the line connected by the points + * is not displayed. \n + * The top and bottom points may appear in the hidden region. If this method is enabled, + * you are not advised to enable the display of the top and bottom points. + * + * @param index Indicates the point from which the hide starts. + * @param count Indicates the number of points to hide. + * @since 1.0 + * @version 1.0 + */ + void HidePoint(uint16_t index, uint16_t count); + + /** + * @brief Obtains the index from which the data set starts to hide. + * + * @return Returns the index. + * @see HidePoint + * @since 1.0 + * @version 1.0 + */ + uint16_t GetHideIndex() const + { + return hideIndex_; + } + + /** + * @brief Obtains the number of hidden points in the data set. + * + * @return Returns the number of hidden points. + * @see HidePoint + * @since 1.0 + * @version 1.0 + */ + uint16_t GetHideCount() const + { + return hideCount_; + } + + /** + * @brief Defines the style for the top, bottom, and frontmost points in a line chart. + */ + struct PointStyle : public HeapBase { + /** Fill color */ + ColorType fillColor; + /** Border color */ + ColorType strokeColor; + /** Inner radius */ + uint16_t radius; + /** Border width, which extends outwards from the inner radius */ + uint16_t strokeWidth; + }; + + /** + * @brief Sets the style of the frontmost point on a polyline. + * + * @param style Indicates the style to set. For details, see {@link PointStyle}. + * @since 1.0 + * @version 1.0 + */ + void SetHeadPointStyle(const PointStyle& style) + { + headPointStyle_ = style; + } + + /** + * @brief Sets the style of the top point of a polyline. + * + * @param style Indicates the style to set. For details, see {@link PointStyle}. + * @since 1.0 + * @version 1.0 + */ + void SetTopPointStyle(const PointStyle& style) + { + topPointStyle_ = style; + } + + /** + * @brief Sets the style of the bottom point of a polyline. + * + * @param style Indicates the style to set. For details, see {@link PointStyle}. + * @since 1.0 + * @version 1.0 + */ + void SetBottomPointStyle(const PointStyle& style) + { + bottomPointStyle_ = style; + } + + /** + * @brief Obtains the style of the frontmost point on a polyline. + * + * @return Returns the style of the point. For details, see {@link PointStyle}. + * @since 1.0 + * @version 1.0 + */ + const PointStyle& GetHeadPointStyle() const + { + return headPointStyle_; + } + + /** + * @brief Obtains the style of the top point of a polyline. + * + * @return Returns the style of the point. For details, see {@link PointStyle}. + * @since 1.0 + * @version 1.0 + */ + const PointStyle& GetTopPointStyle() const + { + return topPointStyle_; + } + + /** + * @brief Obtains the style of the bottom point of a polyline. + * + * @return Returns the style of the point. For details, see {@link PointStyle}. + * @since 1.0 + * @version 1.0 + */ + const PointStyle& GetBottomPointStyle() const + { + return bottomPointStyle_; + } + + /** + * @brief Enables the feature of drawing the frontmost point on a polyline. + * + * @param enable Specifies whether to draw the frontmost point. Value true means to draw the frontmost + * point, and value false means not to draw the frontmost point. + * @since 1.0 + * @version 1.0 + */ + void EnableHeadPoint(bool enable) + { + enableHeadPoint_ = enable; + } + + /** + * @brief Enables the feature of drawing the top point of a polyline. If there are multiple top points, + * only the first one is drawn. + * + * @param enable Specifies whether to draw the top point. Value true means to draw the top point, + * and value false means not to draw the top point. + * @since 1.0 + * @version 1.0 + */ + void EnableTopPoint(bool enable) + { + enableTopPoint_ = enable; + } + + /** + * @brief Enables the feature of drawing the bottom point of a polyline. If there are multiple bottom points, + * only the first one is drawn. + * + * @param enable Specifies whether to draw the bottom point. Value true means to draw the bottom point, + * and value false means not to draw the bottom point. + * @since 1.0 + * @version 1.0 + */ + void EnableBottomPoint(bool enable) + { + enableBottomPoint_ = enable; + } + + void DrawPoint(const Rect& mask); + + void Refresh(); + +protected: + uint16_t maxCount_; + Point* pointArray_; + +private: + constexpr static uint16_t DEFAULT_POINT_RADIUS = 5; + constexpr static uint16_t MAX_POINTS_COUNT = 512; + + ColorType serialColor_; + ColorType fillColor_; + uint16_t dataCount_; + uint16_t peakPointIndex_; + int16_t peakData_; + int16_t valleyData_; + uint16_t valleyPointIndex_; + uint16_t lastPointIndex_; + uint16_t latestIndex_; + uint16_t hideIndex_; + uint16_t hideCount_; + bool smooth_ : 1; + bool enableGradient_ : 1; + bool enableHeadPoint_ : 1; + bool enableTopPoint_ : 1; + bool enableBottomPoint_ : 1; + PointStyle headPointStyle_; + PointStyle topPointStyle_; + PointStyle bottomPointStyle_; + UIChart* chart_; + Rect invalidateRect_; + void RefreshInvalidateRect(uint16_t startIndex, uint16_t endIndex); + void RefreshInvalidateRect(uint16_t pointIndex, const PointStyle& style); + bool UpdatePeakAndValley(uint16_t startPos, uint16_t endPos); + void DoDrawPoint(const Point& point, const PointStyle& style, const Rect& mask); +}; + +/** + * @brief Defines the chart class and provides functions such as adding and deleting data sets to display a chart. + * + * @since 1.0 + * @version 1.0 + */ +class UIChart : public UIViewGroup { +public: + /** + * @brief A constructor used to create a UIChart instance. + * + * @since 1.0 + * @version 1.0 + */ + UIChart() : enableReverse_(false), needRefresh_(false), mixData_(nullptr) + { + Add(&xAxis_); + Add(&yAxis_); + SetStyle(STYLE_LINE_WIDTH, 1); + SetStyle(STYLE_BACKGROUND_COLOR, Color::Black().full); + } + + /** + * @brief A destructor used to delete the UIChart instance. + * + * @since 1.0 + * @version 1.0 + */ + virtual ~UIChart(); + + /** + * @brief Obtains the view type. + * + * @return Returns the view type. For details, see {@link UIViewType}. + * @since 1.0 + * @version 1.0 + */ + UIViewType GetViewType() const override + { + return UI_CHART; + } + + /** + * @brief Sets the height for this component. + * + * @param height Indicates the height to set. + * @since 1.0 + * @version 1.0 + */ + void SetHeight(int16_t height) override; + + /** + * @brief Sets the width for this component. + * + * @param width Indicates the width to set. + * @since 1.0 + * @version 1.0 + */ + void SetWidth(int16_t width) override; + + bool OnPreDraw(Rect& invalidatedArea) const override + { + return false; + } + + void OnDraw(const Rect& invalidatedArea) override; + + /** + * @brief Adds a data set. + * + * @param dataSerial Indicates the pointer to the data set class. For details, see {@link UIChartDataSerial}. + * @return Returns true if the data set is added successfully; returns false otherwise. + * @see DeleteDataSerial + * @since 1.0 + * @version 1.0 + */ + virtual bool AddDataSerial(UIChartDataSerial* dataSerial); + + /** + * @brief Deletes a data set. + * + * @param dataSerial Indicates the pointer to the data set class. For details, see {@link UIChartDataSerial}. + * @return Returns true if the data set is deleted successfully; returns false otherwise. + * @see AddDataSerial + * @since 1.0 + * @version 1.0 + */ + virtual bool DeleteDataSerial(UIChartDataSerial* dataSerial); + + /** + * @brief Clears all data sets. + * + * @since 1.0 + * @version 1.0 + */ + virtual void ClearDataSerial(); + + /** + * @brief Refreshes a chart and redraws the dirty region. + * + * Only the parts that need to be redrawn are refreshed, for example, new data points. + * This function provides better performance than {@link Invalidate}. + * + * @since 1.0 + * @version 1.0 + */ + virtual void RefreshChart() = 0; + + /** + * @brief Obtains the x-axis instance. + * + * @return Returns the x-axis instance. + * @since 1.0 + * @version 1.0 + */ + UIXAxis& GetXAxis() + { + return xAxis_; + } + + /** + * @brief Obtains the y-axis instance. + * + * @return Returns the y-axis instance. + * @since 1.0 + * @version 1.0 + */ + UIYAxis& GetYAxis() + { + return yAxis_; + } + + /** + * @brief Enables chart reverse. + * + * After the chart is reversed, the x-axis aligns with the top of the chart. The pixel position corresponding + * to the data point remains unchanged. Complementary filling is performed on the chart + * (only the part that is not filled previously will be filled). + * + * @param enable Specifies whether to enable chart reverse. Value true means to enable chart reverse, + * and value false means not to enable chart reverse. The default value is false. + * @since 1.0 + * @version 1.0 + */ + void EnableReverse(bool enable) + { + if (enableReverse_ != enable) { + enableReverse_ = enable; + xAxis_.EnableReverse(enable); + yAxis_.EnableReverse(enable); + } + } + +protected: + List list_; + UIXAxis xAxis_; + UIYAxis yAxis_; + bool enableReverse_; + bool needRefresh_; + uint8_t* mixData_; + virtual void DrawDataSerials(const Rect& invalidatedArea) = 0; +}; + +/** + * @brief Provides special functions for implementing a bar chart. + * + * @since 1.0 + * @version 1.0 + */ +class UIChartPillar : public UIChart { +public: + /** + * @brief A constructor used to create a UIChartPillar instance. + * + * @since 1.0 + * @version 1.0 + */ + UIChartPillar() {} + + /** + * @brief A destructor used to delete the UIChartPillar instance. + * + * @since 1.0 + * @version 1.0 + */ + virtual ~UIChartPillar() {} + + /** + * @brief Refreshes a bar chart and redraws the dirty region. + * + * Only the parts that need to be redrawn are refreshed, for example, new data points. + * This function provides better performance than {@link Invalidate}. + * + * @since 1.0 + * @version 1.0 + */ + void RefreshChart() override; + +protected: + void DrawDataSerials(const Rect& invalidatedArea) override; + +private: + static constexpr float DEFAULT_MARK_PERCENTAGE = 0.1f; +}; + +/** + * @brief Provides special functions for implementing a polyline. + * + * @since 1.0 + * @version 1.0 + */ +class UIChartPolyline : public UIChart { +public: + /** + * @brief A constructor used to create a UIChartPolyline instance. + * + * @since 1.0 + * @version 1.0 + */ + UIChartPolyline() : minOpa_(OPA_TRANSPARENT), maxOpa_(OPA_OPAQUE), gradientBottom_(0) {} + + /** + * @brief A destructor used to delete the UIChartPolyline instance. + * + * @since 1.0 + * @version 1.0 + */ + virtual ~UIChartPolyline() {} + + /** + * @brief Refreshes a line chart and redraws the dirty region. + * + * Only the parts that need to be redrawn are refreshed, for example, new data points. + * This function provides better performance than {@link Invalidate}. + * + * @since 1.0 + * @version 1.0 + */ + void RefreshChart() override; + + /** + * @brief Sets the opacity range of the fill color gradient. + * + * This function sets the opacity range between the top point and bottom point of the line chart. + * The opacity of each horizontal line is calculated based on the ratio. + * + * @param minOpa Indicates the opacity closest to the x-axis. + * @param maxOpa Indicates the opacity farthest away from the x-axis. + * @since 1.0 + * @version 1.0 + */ + void SetGradientOpacity(uint8_t minOpa, uint8_t maxOpa) + { + minOpa_ = minOpa; + maxOpa_ = maxOpa; + needRefresh_ = true; + } + + /** + * @brief Sets the distance between the bottom edge of the fill color range and the x-axis. + * + * This function fills in the area between the polyline and bottom of the line chart. For a chart that is not + * reversed, if the bottom is above the polyline, there is no filling. For a reversed chart, + * if the bottom is below the polyline, there is no filling. + * + * @param bottom Indicates the bottom of the filling range. The value is the distance to the x-axis. + * @since 1.0 + * @version 1.0 + */ + void SetGradientBottom(uint16_t bottom) + { + gradientBottom_ = bottom; + } + +protected: + void DrawDataSerials(const Rect& invalidatedArea) override; + +private: + struct ChartLine { + Point start; + Point end; + }; + + struct CrossPointSet { + Point first; + Point second; + Point nextFirst; + bool firstFind; + bool secondFind; + }; + + constexpr static uint8_t SMOOTH_SLOPE_ANGLE = 3; + constexpr static uint8_t LINE_JOIN_WIDTH = 3; + uint8_t minOpa_; + uint8_t maxOpa_; + uint16_t gradientBottom_; + + void GradientColor(const Rect& invalidatedArea, UIChartDataSerial* data); + void DrawGradientColor(const Rect& invalidatedArea, + UIChartDataSerial* data, + const ChartLine& linePoints, + const ChartLine& limitPoints, + int16_t startY); + void DrawSmoothPolyLine(uint16_t startIndex, + uint16_t endIndex, + const Rect& invalidatedArea, + UIChartDataSerial* data); + void DrawPolyLine(uint16_t startIndex, uint16_t endIndex, const Rect& invalidatedArea, UIChartDataSerial* data); + bool GetLineCrossPoint(const Point& p1, const Point& p2, const Point& p3, const Point& p4, Point& cross); + void FindCrossPoints(const ChartLine& line, const ChartLine& polyLine, CrossPointSet& cross); + void ReMeasure() override; + void CalcVerticalInfo(int16_t top, int16_t bottom, int16_t start, int16_t end, int16_t& y, int16_t& yHeight); +}; +} // namespace OHOS +#endif // GRAPHIC_LITE_UI_CHART_H diff --git a/interfaces/kits/components/ui_checkbox.h b/interfaces/kits/components/ui_checkbox.h new file mode 100755 index 0000000..e98ad80 --- /dev/null +++ b/interfaces/kits/components/ui_checkbox.h @@ -0,0 +1,225 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @addtogroup UI_Components + * @{ + * + * @brief Defines UI components such as buttons, texts, images, lists, and progress bars. + * + * @since 1.0 + * @version 1.0 + */ + +/** + * @file ui_checkbox.h + * + * @brief Defines the attributes and common functions of a check box. + * + * @since 1.0 + * @version 1.0 + */ + +#ifndef GRAPHIC_LITE_UI_CHECKBOX_H +#define GRAPHIC_LITE_UI_CHECKBOX_H + +#include "common/image.h" +#include "components/ui_view.h" + +namespace OHOS { +/** + * @brief Represents a check box. + * + * A check box permits users to make a binary choice. + * + * @since 1.0 + * @version 1.0 + */ +class UICheckBox : public UIView { +public: + /** + * @brief Enumerates the states of a check box. + * + * @since 1.0 + * @version 1.0 + */ + enum UICheckBoxState : uint8_t { + /* An enum constant representing the state selected option */ + SELECTED, + /* An enum constant representing the state unselected option */ + UNSELECTED, + /* Max num of state */ + MAX_STATUS_NUM, + }; + + /** + * @brief A constructor used to create a UICheckBox instance. + * + * @since 1.0 + * @version 1.0 + */ + UICheckBox(); + + /** + * @brief A destructor used to delete the UICheckBox instance. + * + * @since 1.0 + * @version 1.0 + */ + virtual ~UICheckBox(); + + /** + * @brief Represents a listener for changes of a check box. + * + * This is an inner class of UICheckBox. It contains a callback function to be invoked when the check box + * state changes. + * + * @see UICheckBox + * @since 1.0 + * @version 1.0 + */ + class OnChangeListener : public HeapBase { + public: + /** + * @brief Called when the state of this check box is switched. This is a virtual function, which needs your + * implementation. + * + * @param state Indicates the current state of this check box. For details, see {@link UICheckBoxState}. + * @since 1.0 + * @version 1.0 + */ + virtual bool OnChange(UICheckBoxState state) = 0; + + /** + * @brief A destructor used to delete the OnChangeListener instance. + * + * @since 1.0 + * @version 1.0 + */ + virtual ~OnChangeListener() {} + }; + + /** + * @brief Obtains the component type. + * + * @return Returns the component type, as defined in {@link UIViewType}. + * @since 1.0 + * @version 1.0 + */ + UIViewType GetViewType() const override + { + return UI_CHECK_BOX; + } + + /** + * @brief Sets the listener for this check box. + * + * The listener is triggered to invoke the callback function upon click events. + * + * @param listener Indicates the listener to set. For details, see {@link OnChangeListener}. + * @since 1.0 + * @version 1.0 + */ + void SetOnChangeListener(OnChangeListener* onStateChangeListener) + { + onStateChangeListener_ = onStateChangeListener; + } + + /** + * @fn virtual bool UICheckBox::OnPreDraw(Rect& invalidatedArea) override + * + * @brief Do something before draw, this function will be invoked mainly to check if this view need + * to cover invalidate area so render manager can decide which layer to draw firstly. + * @param [in] invalidate area. + * @returns True if need cover. + */ + bool OnPreDraw(Rect& invalidatedArea) const override + { + return false; + } + + /** + * @fn virtual void UICheckBox::OnDraw(const Rect& invalidatedArea) override; + * + * @brief Executes the draw action + * Ondraw invokes the rendering function provided by the underlying layer to draw pictures + * based on the selected status of the checkbox. + * + * @param [in] invalidatedArea The invalidated area. + */ + void OnDraw(const Rect& invalidatedArea) override; + + /** + * @fn virtual void UICheckBox::OnClickEvent(const ClickEvent& event) override; + * + * @brief Executes the click event action + * OnClickEvent will reverse the selected state of checkbox. + * Example: If the check box is selected, the checkbox status is changed to + * Unselected after the click action is taken. + * + * @param [in] event The event that passed when OnClickEvent is invoked. + * @return Returns true if the event is consumed; returns false otherwise. + */ + bool OnClickEvent(const ClickEvent& event) override; + + /** + * @brief Sets the images for this check box. + * + * @param selectedImageSrc Indicates the image for this check box when selected. + * @param unselectedImageSrc Indicates the image for this check box when unselected. + * @since 1.0 + * @version 1.0 + */ + virtual void SetImages(const char* selectedImageSrc, const char* unselectedImageSrc); + + /** + * @brief Sets the images for this check box. + * + * @param selectedImageSrc Indicates the image for this check box when selected. + * @param unselectedImageSrc Indicates the image for this check box when unselected. + * @since 1.0 + * @version 1.0 + */ + virtual void SetImages(const ImageInfo* selectedImageSrc, const ImageInfo* unselectedImageSrc); + + /** + * @brief Obtains the state of this check box. + * + * @return Returns the state of this check box, as defined in {@link UICheckBoxState}. + * @since 1.0 + * @version 1.0 + */ + UICheckBoxState GetState() const + { + return state_; + }; + + /** + * @brief Sets the state for this check box. + * + * @param state Indicates the state of this check box. For details, see {@link UICheckBoxState}. + * @since 1.0 + * @version 1.0 + */ + void SetState(UICheckBoxState state); + +protected: + virtual void ReverseState(); + UICheckBoxState state_; + OnChangeListener* onStateChangeListener_; + Image image_[MAX_STATUS_NUM]; +}; +} // namespace OHOS +#endif // GRAPHIC_LITE_UI_CHECKBOX_H diff --git a/interfaces/kits/components/ui_circle_progress.h b/interfaces/kits/components/ui_circle_progress.h new file mode 100755 index 0000000..9642b1f --- /dev/null +++ b/interfaces/kits/components/ui_circle_progress.h @@ -0,0 +1,260 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @addtogroup UI_Components + * @{ + * + * @brief Defines UI components such as buttons, texts, images, lists, and progress bars. + * + * @since 1.0 + * @version 1.0 + */ + +/** + * @file ui_circle_progress.h + * + * @brief Defines the attributes and common functions of a circular progress bar. + * + * @since 1.0 + * @version 1.0 + */ + +#ifndef GRAPHIC_LITE_UI_CIRCLE_PROGRESS_H +#define GRAPHIC_LITE_UI_CIRCLE_PROGRESS_H + +#include "components/ui_abstract_progress.h" + +namespace OHOS { +/** + * @brief Represents a circular progress bar. + * + * This class is used to set the start and end angles, range, and current value to display the circular progress bar. + * + * @see UIAbstractProgress + * @since 1.0 + * @version 1.0 + */ +class UICircleProgress : public UIAbstractProgress { +public: + /** + * @brief A constructor used to create a UICircleProgress instance. + * + * @since 1.0 + * @version 1.0 + */ + UICircleProgress(); + + /** + * @brief A destructor used to delete the UICircleProgress instance. + * + * @since 1.0 + * @version 1.0 + */ + virtual ~UICircleProgress() {} + + /** + * @brief Obtains the view type. + * + * @return Returns the view type, as defined in {@link UIViewType}. + * @since 1.0 + * @version 1.0 + */ + UIViewType GetViewType() const override + { + return UI_CIRCLE_PROGRESS; + } + + bool OnPreDraw(Rect& invalidatedArea) const override + { + return false; + } + + void OnDraw(const Rect& invalidatedArea) override; + + /** + * @brief Sets the coordinates of the center point for this circular progress bar. + * + * The coordinates of the center point refer to the position relative to the view. + * + * @param x Indicates the x-coordinate to set. The default value is 0. + * @param y Indicates the y-coordinate to set. The default value is 0. + * @see GetCenterPosition + * @since 1.0 + * @version 1.0 + */ + void SetCenterPosition(int16_t x, int16_t y); + + /** + * @brief Obtains the coordinates of the center point for this circular progress bar. + * + * The coordinates of the center point refer to the position relative to the view. + * + * @return Returns the coordinates of the center point. + * @see SetCenterPosition + * @since 1.0 + * @version 1.0 + */ + Point GetCenterPosition() const + { + return center_; + } + + /** + * @brief Sets the outer radius for this circular progress bar. + * + * @param radius Indicates the outer radius to set. + * @see GetRadius + * @since 1.0 + * @version 1.0 + */ + void SetRadius(uint16_t radius) + { + radius_ = radius; + } + + /** + * @brief Obtains the outer radius of this circular progress bar. + * + * @return Returns the outer radius. + * @see SetRadius + * @since 1.0 + * @version 1.0 + */ + uint16_t GetRadius() const + { + return radius_; + } + + /** + * @brief Sets the start angle. + * + * The 12-clock direction is 0 degrees, and the 3-clock direction is 90 degrees. \n + * If the start angle is smaller than the end angle, the progress bar is clockwise. + * Otherwise, the progress bar is anticlockwise. \n + * The maximum range of a progress bar is a circle. If the difference between the start angle and end angle + * exceeds 360 degrees, a circular progress bar is created. \n + * + * @param startAngle Indicates the start angle to set, which can be any value represented by int16_t. + * The default value is 0. + * @see GetStartAngle | SetEndAngle + * @since 1.0 + * @version 1.0 + */ + void SetStartAngle(int16_t startAngle); + + /** + * @brief Obtains the start angle. + * + * @return Returns the start angle. + * @see SetStartAngle + * @since 1.0 + * @version 1.0 + */ + int16_t GetStartAngle() const + { + return startAngle_; + } + + /** + * @brief Sets the end angle. + * + * The 12-clock direction is 0 degrees, and the 3-clock direction is 90 degrees. \n + * If the start angle is smaller than the end angle, the progress bar is clockwise. + * Otherwise, the progress bar is anticlockwise. \n + * The maximum range of a progress bar is a circle. If the difference between the start angle and end angle + * exceeds 360 degrees, a circular progress bar is created. \n + * + * @param endAngle Indicates the end angle to set, which can be any value represented by int16_t. + * The default value is 360. + * @see GetEndAngle | SetStartAngle + * @since 1.0 + * @version 1.0 + */ + void SetEndAngle(int16_t endAngle); + + /** + * @brief Obtains the end angle. + * + * @return Returns the end angle. + * @see SetEndAngle + * @since 1.0 + * @version 1.0 + */ + int16_t GetEndAngle() const + { + return endAngle_; + } + + /** + * @brief Sets the coordinates of the foreground image for this progress bar relative to the view. + * + * @param x Indicates the x-coordinate to set. The default value is 0. + * @param y Indicates the y-coordinate to set. The default value is 0. + * @see SetBackgroundImagePosition + * @since 1.0 + * @version 1.0 + */ + void SetProgressImagePosition(int16_t x, int16_t y) + { + progressImagePos_.x = x + style_->paddingLeft_ + style_->borderWidth_; + progressImagePos_.y = y + style_->paddingTop_ + style_->borderWidth_; + } + + /** + * @brief Sets the coordinates of the background image for this progress bar relative to the view. + * + * @param x Indicates the x-coordinate to set. The default value is 0. + * @param y Indicates the y-coordinate to set. The default value is 0. + * @see SetProgressImagePosition + * @since 1.0 + * @version 1.0 + */ + void SetBackgroundImagePosition(int16_t x, int16_t y) + { + backgroundImagePos_.x = x + style_->paddingLeft_ + style_->borderWidth_; + backgroundImagePos_.y = y + style_->paddingTop_ + style_->borderWidth_; + } + + /** + * @brief Sets the foreground color for this progress bar. + * + * @param color Indicates the foreground color to set. For details, see {@link ColorType}. + * @since 1.0 + * @version 1.0 + */ + void SetLineColor(ColorType color) + { + SetForegroundStyle(STYLE_LINE_COLOR, color.full); + } + +private: + static constexpr uint16_t MAX_ANGLE_VALUE = CIRCLE_IN_DEGREE; + static constexpr uint16_t MIN_ANGLE_VALUE = 0; + + void GetStartEndAngle(int16_t& start, int16_t& end) const; + void GetAngleRange(int16_t& start, int16_t& end) const; + void GetRedrawAngle(int16_t& start, int16_t& end) const; + void DrawCommonCircle(const Rect& invalidatedArea); + + Point center_; + Point backgroundImagePos_; + Point progressImagePos_; + uint16_t radius_; + int16_t startAngle_; + int16_t endAngle_; +}; +} // namespace OHOS +#endif // GRAPHIC_LITE_UI_CIRCLE_PROGRESS_H diff --git a/interfaces/kits/components/ui_dialog.h b/interfaces/kits/components/ui_dialog.h new file mode 100755 index 0000000..f30c9fe --- /dev/null +++ b/interfaces/kits/components/ui_dialog.h @@ -0,0 +1,248 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @addtogroup UI_Components + * @{ + * + * @brief Defines UI components such as buttons, texts, images, lists, and progress bars. + * + * @since 1.0 + * @version 1.0 + */ + +/** + * @file ui_dialog.h + * + * @brief Declares the UIDialog class that defines the dialog box component. + * + * @since 1.0 + * @version 1.0 + */ + +#ifndef GRAPHIC_LITE_UI_DIALOG_H +#define GRAPHIC_LITE_UI_DIALOG_H + +#include "components/root_view.h" +#include "components/ui_label.h" +#include "components/ui_label_button.h" + +#if ENABLE_WINDOW +namespace OHOS { +/** + * @brief Represents a dialog box. + * + * A dialog box displays information for user input. + * After a button is clicked in the dialog box, the corresponding callback event is triggered. + * + * @since 1.0 + * @version 1.0 + */ +class UIDialogClickListener; +class UIDialog : public HeapBase { +public: + /** + * @brief A constructor used to create a UIDialog instance. + * + * @since 1.0 + * @version 1.0 + */ + UIDialog(); + + /** + * @brief A destructor used to delete the UIDialog instance. + * + * @since 1.0 + * @version 1.0 + */ + virtual ~UIDialog(); + + /** + * @brief Enumerates buttons in a dialog box. + * + * @since 1.0 + * @version 1.0 + */ + enum class DialogButtonType { + /** Left button */ + BUTTON_LEFT, + /** Middle button */ + BUTTON_MID, + /** Right button */ + BUTTON_RIGHT, + }; + + /** + * @brief Sets the title for this dialog box. + * + * @param title Indicates the pointer to the title. + * @since 1.0 + * @version 1.0 + */ + void SetTitle(const char* title); + + /** + * @brief Sets the text for this dialog box. + * + * @param text Indicates the pointer to the text. + * @since 1.0 + * @version 1.0 + */ + void SetText(const char* text); + + /** + * @brief Sets a button for this dialog box. + * + * @param buttonType Indicates the button position. + * @param text Indicates the pointer to the button text. + * @param listener Indicates the pointer to the listener registered for the button. + * @since 1.0 + * @version 1.0 + */ + void SetButton(DialogButtonType buttonType, const char* text, UIView::OnClickListener* listener); + + /** + * @brief Shows this dialog box. + * + * @since 5.0 + * @version 3.0 + */ + void Show(); + + /** + * @brief Sets the button color. + * + * @param buttonType Indicates the button type. + * @param color Indicates the button color to set. + * @since 5.0 + * @version 3.0 + */ + void SetButtonColor(DialogButtonType buttonType, ColorType color); + + /** + * @brief Sets a listener for monitoring click events occurring outside this dialog box. + * + * @param onCancelListener Indicates the pointer to the listener to set. + * @since 5.0 + * @version 3.0 + */ + void SetOnCancelListener(UIView::OnClickListener* onCancelListener); + + /** + * @brief Sets whether to close this dialog box when click events occur outside it. + * + * @param enable Specifies whether to close this dialog box when click events occur outside it. + * @since 5.0 + * @version 3.0 + */ + void EnableAutoCancel(bool enable); + +#ifdef ENABLE_DEBUG + const char* GetTitle() const + { + return titleText_; + } + + const char* GetText() const + { + return textText_; + } + + const char* GetButtonText(DialogButtonType buttonType) const; + + UIView::OnClickListener* GetButtonListener(DialogButtonType buttonType) const; + + ColorType GetButtonColor(DialogButtonType buttonType) const; + + UIView::OnClickListener* GetOnCancelListener() const + { + return onCancelListener_; + } + + bool GetEnableAutoCancel() const + { + return enableAutoCancel_; + } +#endif + +private: + const static uint8_t BUTTON_HEIGHT = 40; + const static uint8_t BUTTON_TOTAL_HEIGHT = 56; + const static uint8_t PADDING = 24; + const static uint8_t BUTTON_MID_PADDING = 10; + const static uint8_t BUTTON_PADDING = 16; + const static uint8_t BUTTON_PRESS_OPA = 26; // 10% opacity + const static uint8_t TEXT_BUTTON_PADDING = 8; + const static uint8_t TEXT_FONT_SIZE = 16; + const static uint8_t TITLE_FONT_SIZE = 20; + const static uint8_t TITLE_TOTAL_HEIGHT = 56; + const static uint8_t BUTTON_FONT_SIZE = 16; + const static uint16_t MAX_WIDTH_PERCENT = 60; + const static uint16_t MAX_HEIGHT_PERCENT = 90; + const static uint8_t TITLE_TEXT_OPA = 230; // 90% opacity + const static uint8_t LINE_BUTTON_PADDING = 4; + const static uint8_t LINE_WIDTH = 2; + const static uint8_t LINE_HEIGHT = 24; + const static uint8_t LINE_BOTTOM_PADDING = 24; + const static uint8_t LINE_OPA = 51; // 20% opacity + const char* TITLE_ID = "dialogTitle"; + const char* BUTTON1_ID = "dialogButton1"; + const char* BUTTON2_ID = "dialogButton2"; + + void AddButton(DialogButtonType buttonType, UILabelButton* button, const char* text, + UIView::OnClickListener* listener); + void InitDialog(); + void SetTitleLabel(); + void SetTextLabel(); + void AddComponents(); + void MeasureSize(); + uint16_t MeasureMaxWidth(); + uint16_t MeasureTitleWidth(); + uint16_t MeasureTextWidth(); + uint16_t MeasureButtonWidth(); + void Layout(); + void LayoutButton(); + void CreateDialogWindow(); + void DestoryWindow(); + + bool isShowing_; + bool enableAutoCancel_; + uint8_t buttonNum_; + UILabel* title_; + UILabel* text_; + UILabelButton* button1_; + UILabelButton* button2_; + UILabelButton* button3_; + UIViewGroup* dialogLayer_; + uint16_t widthMax_; + uint16_t heightMax_; + RootView* windowRootView_; + UIView::OnClickListener* onCancelListener_; + UIDialogClickListener* dialogClickListener_; + Window* window_; + UIView* line1_; + UIView* line2_; + const char* titleText_; + const char* textText_; + ColorType colorType1_; + ColorType colorType2_; + ColorType colorType3_; + + friend class UIDialogLabelButton; + friend class UIDialogClickListener; +}; +} +#endif // GRAPHIC_LITE_UI_DIALOG_H +#endif // ENABLE_WINDOW \ No newline at end of file diff --git a/interfaces/kits/components/ui_digital_clock.h b/interfaces/kits/components/ui_digital_clock.h new file mode 100755 index 0000000..2042f62 --- /dev/null +++ b/interfaces/kits/components/ui_digital_clock.h @@ -0,0 +1,222 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @addtogroup UI_Components + * @{ + * + * @brief Defines UI components such as buttons, texts, images, lists, and progress bars. + * + * @since 1.0 + * @version 1.0 + */ + +/** + * @file ui_digital_clock.h + * + * @brief Declares a digital clock. + * + * @since 1.0 + * @version 1.0 + */ + +#ifndef GRAPHIC_LITE_UI_DIGITAL_CLOCK_H +#define GRAPHIC_LITE_UI_DIGITAL_CLOCK_H + +#include "color.h" +#include "components/ui_abstract_clock.h" +#include "components/ui_label.h" + +namespace OHOS { +/** + * @brief Displays time digitally. + * + * @see UIDigitalClock + * @since 1.0 + * @version 1.0 + */ +class UIDigitalClock : public UIAbstractClock { +public: + /** + * @brief A default constructor used to create a UIDigitalClock instance. + * + * @since 1.0 + * @version 1.0 + */ + UIDigitalClock(); + + /** + * @brief A destructor used to delete the UIDigitalClock instance. + * + * @since 1.0 + * @version 1.0 + */ + virtual ~UIDigitalClock(); + + /** + * @brief Obtains the view type. + * + * @return Returns UI_DIGITAL_CLOCK, as defined in {@link UIViewType}. + * @since 1.0 + * @version 1.0 + */ + UIViewType GetViewType() const override + { + return UI_DIGITAL_CLOCK; + } + + /** + * @brief Sets the dynamic font ID for this digital clock. + * + * @param fontId Indicates the dynamic font ID to set. + * @since 1.0 + * @version 1.0 + */ + void SetFontId(uint8_t fontId); + + /** + * @brief Sets the dynamic font for this digital clock. + * + * @param name Indicates the pointer to the font name. + * @param size Indicates the font size to set. + * @since 1.0 + * @version 1.0 + */ + void SetFont(const char* name, uint8_t size); + + /** + * @brief Sets the font color for this digital clock. + * + * @param color Indicates the front color to set. + * @since 1.0 + * @version 1.0 + */ + void SetColor(ColorType color); + + /** + * @brief Enumerates the display modes of this digital clock. + */ + enum DisplayMode : uint8_t { + /** 12-hour format, without seconds */ + DISPLAY_12_HOUR_NO_SECONDS, + /** 24-hour format, without seconds */ + DISPLAY_24_HOUR_NO_SECONDS, + /** 12-hour format */ + DISPLAY_12_HOUR, + /** 24-hour format */ + DISPLAY_24_HOUR + }; + + /** + * @brief Enumerates the time elements of this digital clock. + */ + enum TimeElement : uint8_t { + /** Clock */ + HOUR_ELEMENT = 0, + /** Minute */ + MINUTE_ELEMENT, + /** Second */ + SECOND_ELEMENT + }; + + /** + * @brief Sets the display mode for this digital clock. + * + * @param dm Indicates the display mode to set. + * @since 1.0 + * @version 1.0 + */ + void SetDisplayMode(DisplayMode dm) + { + displayMode_ = dm; + UpdateClock(false); + } + + /** + * @brief Obtains the display mode of this digital clock. + * + * @return Returns the display mode of this digital clock. + * @since 1.0 + * @version 1.0 + */ + DisplayMode GetDisplayMode() const + { + return displayMode_; + } + + /** + * @brief Sets whether to add 0 before the hour of this digital clock. + * + * @param displayLeadingZero Specifies whether to add 0 before the hour of this digital clock. + * true indicates that 0 is added, and false indicates + * the opposite case. + * @since 1.0 + * @version 1.0 + */ + void DisplayLeadingZero(bool displayLeadingZero); + + /** + * @brief Sets the opacity for this digital clock. + * + * @param opacity Indicates the opacity of this digital clock. The value ranges + * from 0 (transparency) to 255 (opacity). + * @since 1.0 + * @version 1.0 + */ + void SetOpacity(uint8_t opacity); + + /** + * @brief Obtains the opacity of this digital clock. + * + * @return Returns the opacity of this digital clock, within [0, 255]. + * @since 1.0 + * @version 1.0 + */ + uint8_t GetOpacity() const; + + /** + * @brief Updates this digital clock. + * + * @param clockInit Specifies whether the first initialization. true indicates the first initialization, + * and false indicates the opposite case. + * @since 1.0 + * @version 1.0 + */ + void UpdateClock(bool clockInit) override; + +protected: + static constexpr uint8_t TIME_ELEMENT_COUNT = 3; + UILabel* timeLabels_[TIME_ELEMENT_COUNT]; + + virtual void InitTimeLabels(); + +private: + static constexpr uint8_t BUFFER_SIZE = 4; + void SetHorizontal(); + void SetVertical(); + void SetTimeLabels(const char buf[TIME_ELEMENT_COUNT][BUFFER_SIZE]); + void SetTimeLabelsPosition(); + void TimeElementRefresh(); + void RefreshTime(); + DisplayMode displayMode_; + bool leadingZero_; + ColorType color_; + uint8_t prevHour_; + uint8_t prevMinute_; + uint8_t prevSecond_; + bool verticalShow_; +}; +} // namespace OHOS +#endif // UI_DIGITAL_CLOCK_H diff --git a/interfaces/kits/components/ui_image_animator.h b/interfaces/kits/components/ui_image_animator.h new file mode 100755 index 0000000..b100a7c --- /dev/null +++ b/interfaces/kits/components/ui_image_animator.h @@ -0,0 +1,484 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @addtogroup UI_Components + * @{ + * + * @brief Defines UI components such as buttons, texts, images, lists, and progress bars. + * + * @since 1.0 + * @version 1.0 + */ + +/** + * @file ui_image_animator.h + * + * @brief Defines the attributes and functions of an image animator. + * + * @since 1.0 + * @version 1.0 + */ + +#ifndef GRAPHIC_LITE_UI_IMAGE_ANIMATOR_H +#define GRAPHIC_LITE_UI_IMAGE_ANIMATOR_H + +#include "animator/animator.h" +#include "components/ui_image_view.h" +#include "graphic_types.h" + +namespace OHOS { +/** + * @brief Provides information about the images. + */ +struct ImageAnimatorInfo { + union { + /** Image path */ + const char* imagePath; + /** Pointer to the {@link ImageInfo} structure */ + const ImageInfo* imageInfo; + }; + /** Image start position, relative to this view */ + Point pos; + /** Image width */ + int16_t width; + /** Image height */ + int16_t height; + /** Image type */ + ImageType imageType; +}; + +/** + * @brief Represents an image animator. + * + * Images are switched at a specified interval to form an animator. + * + * @see UIImageView + * @since 1.0 + * @version 1.0 + */ +class UIImageAnimatorView : public UIImageView { +public: + /** + * @brief A constructor used to create a UIImageAnimatorView instance. + * + * @since 1.0 + * @version 1.0 + */ + UIImageAnimatorView(); + + /** + * @brief A destructor used to delete the UIImageAnimatorView instance. + * + * @since 1.0 + * @version 1.0 + */ + virtual ~UIImageAnimatorView(); + + /** + * @brief Obtains the view type. + * + * @return Returns the view type, as defined in {@link UIViewType}. + * @since 1.0 + * @version 1.0 + */ + UIViewType GetViewType() const override + { + return UI_IMAGE_ANIMATOR_VIEW; + } + + /** + * @brief Sets the images and speed for this animator. + * + * @param imageSrc Indicates the pointer to the {@link ImageAnimatorInfo} array for storing the configured + * image information. The memory cannot be released before this animator stops. + * @param imageNum Indicates the number of images. + * @param timeOfUpdate Indicates the interval for updating images, in milliseconds. The default value is 1. + * @see GetTimeOfUpdate | GetImageAnimatorSrc | GetImageAnimatorImageNum + * @since 1.0 + * @version 1.0 + */ + void SetImageAnimatorSrc(const ImageAnimatorInfo imageSrc[], uint8_t imageNum, uint16_t timeOfUpdate); + + /** + * @brief Sets the images for this animator. + * + * @param imageSrc Indicates the pointer to the {@link ImageAnimatorInfo} array for storing the configured + * image information. The memory cannot be released before this animator stops. + * @param imageNum Indicates the number of images. + * @see GetImageAnimatorSrc | GetImageAnimatorImageNum + * @since 1.0 + * @version 1.0 + */ + void SetImageAnimatorSrc(const ImageAnimatorInfo imageSrc[], uint8_t imageNum); + + /** + * @brief Sets the speed for this animator. + * + * @param timeOfUpdate Indicates the interval for updating images, in milliseconds. The default value is 1. + * @see GetTimeOfUpdate + * @since 1.0 + * @version 1.0 + */ + void SetTimeOfUpdate(uint16_t timeOfUpdate); + + /** + * @brief Obtains the speed of this animator. + * + * @return Returns the interval for updating images, in milliseconds. + * @see SetTimeOfUpdate + * @since 1.0 + * @version 1.0 + */ + uint16_t GetTimeOfUpdate() const; + + /** + * @brief Sets the interval between two playbacks of an infinitely repeated animator. + * + * @param timeOfPause Indicates the interval between two playbacks of an infinitely repeated animator, in + * milliseconds. The default value is 0, indicating that there is no interval. + * @see GetTimeOfPause + * @since 1.0 + * @version 1.0 + */ + void SetTimeOfPause(uint16_t timeOfPause); + + /** + * @brief Obtains the interval between two playbacks of an infinitely repeated animator. + * + * @return Returns the interval between two playbacks, in milliseconds. + * @see SetTimeOfPause + * @since 1.0 + * @version 1.0 + */ + uint16_t GetTimeOfPause() const; + + /** + * @brief Obtains the image array of this frame animator. + * + * @return Returns a pointer to the {@link ImageAnimatorInfo} array if the operation is successful; + * returns nullptr otherwise. + * @since 1.0 + * @version 1.0 + */ + const ImageAnimatorInfo* GetImageAnimatorSrc() const; + + /** + * @brief Obtains the number of images. + * + * @return Returns the number of images. + * @since 1.0 + * @version 1.0 + */ + uint8_t GetImageAnimatorImageNum() const; + + /** + * @brief Sets whether the image size is fixed to the view size. + * + * @param fixed Specifies whether the image size is fixed to the view size. The value true indicates + * the size of the image is the same as that of the view, and false indicates the + * position and size of this image need to be set in the {@link ImageAnimatorInfo} structure. + * @see IsSizeFixed + * @since 1.0 + * @version 1.0 + */ + void SetSizeFixed(bool fixed) + { + sizeFixed_ = fixed; + } + + /** + * @brief Checks whether the image size is fixed to the view size. + * + * @return Returns true if the image size is fixed to the view size; + * returns false if the image size is set independently. + * @see SetSizeFixed + * @since 1.0 + * @version 1.0 + */ + bool IsSizeFixed() const + { + return sizeFixed_; + } + + /** + * @brief Sets whether to play this animator for infinite times. + * + * @param repeat Specifies whether the animator is played for infinite times. true (default value) + * indicates the animator is played for infinite times, and false indicates the animator + * is played a specified number of times. + * @see IsRepeat + * @since 1.0 + * @version 1.0 + */ + void SetRepeat(bool repeat) + { + repeat_ = repeat; + } + + /** + * @brief Checks whether this animator is played for infinite times. + * + * @return Returns true if this animator is played for infinite times, returns false if the + * animator is played a specified number of times. + * @see SetRepeat + * @since 1.0 + * @version 1.0 + */ + bool IsRepeat() const + { + return repeat_; + } + + /** + * @brief Sets the playback times for this animator. + * + * This function takes effect only when repeat is set to false in {@link SetRepeat}. + * + * @param times Indicates the playback times to set. The default value is 1. + * @see GetRepeatTimes + * @since 1.0 + * @version 1.0 + */ + void SetRepeatTimes(uint32_t times) + { + repeatTimes_ = times; + } + + /** + * @brief Obtains the playback times. + * + * @return Returns the playback times. + * @see SetRepeatTimes + * @since 1.0 + * @version 1.0 + */ + uint32_t GetRepeatTimes() const + { + return repeatTimes_; + } + + /** + * @brief Obtains the current state of this animator. + * + * @return Returns the current state, which can be {@link START}, {@link STOP}, or {@link PAUSE}. + * For details, see {@link Animator}. + * @since 1.0 + * @version 1.0 + */ + uint8_t GetState() const + { + return imageAnimator_.GetState(); + } + + /** + * @brief Sets the playback sequence for this animator. + * + * @param reverse Indicates the playback sequence to set. true indicates a reverse playback + * and false (default value) indicates a forward playback. + * @see IsReverse + * @since 1.0 + * @version 1.0 + */ + void SetReverse(bool reverse) + { + reverse_ = reverse; + } + + /** + * @brief Obtains the playback sequence of this animator. + * + * @return Returns true if a reverse playback is performed; + * returns false if a forward playback is performed. + * @see SetReverse + * @since 1.0 + * @version 1.0 + */ + bool IsReverse() const + { + return reverse_; + } + + /** + * @brief Sets the attribute value for the end frame of this animator. + * + * @param fillMode Indicates the attribute value for the end frame of this animator. + * When a forward playback is performed, the value true indicates setting the end frame of + * this animator to the last one of the image set, and false indicates setting the end frame + * of this animator to the first one of the image set. When a reverse playback is performed, + * the value true indicates setting the end frame of this animator to the first one of + * the image set, and false indicates setting the end frame of this animator to the last one + * of the image set. The default value is true. + * @see GetFillMode + * @since 3.0 + * @version 5.0 + */ + void SetFillMode(bool fillMode) + { + fillMode_ = fillMode; + } + + /** + * @brief Obtains the attribute value of the last frame of this animator. + * + * @return Returns true if the last frame of this animator is the last one of the image set when + * a forward playback is performed, or if the last frame of this animator is the first one of the image set + * when a reverse playback is performed; + * returns false if the last frame of this animator is the first one of the image set when + * a forward playback is performed, or if the last frame of this animator is the last one of the image set + * when a reverse playback is performed. + * + * @see SetFillMode + * @since 3.0 + * @version 5.0 + */ + bool GetFillMode() const + { + return fillMode_; + } + + /** + * @brief Starts this animator. + * + * The forward playback starts from the first image and the reverse playback starts from the last image. + * + * @see Stop + * @since 1.0 + * @version 1.0 + */ + void Start(); + + /** + * @brief Stops this animator. + * + * @see Start + * @since 1.0 + * @version 1.0 + */ + void Stop(); + + /** + * @brief Pauses this animator at the current image. + * + * @see Resume + * @since 1.0 + * @version 1.0 + */ + void Pause() + { + imageAnimator_.SetState(Animator::PAUSE); + } + + /** + * @brief Resumes this animator from the current image. + * + * @see Pause + * @since 1.0 + * @version 1.0 + */ + void Resume() + { + imageAnimator_.SetState(Animator::START); + } + + /** + * @brief Represents a listener that contains a callback to be invoked when this animator stops. + * + * @since 1.0 + * @version 1.0 + */ + class AnimatorStopListener : public HeapBase { + public: + /** + * @brief A destructor used to delete an AnimatorStopListener instance. + * + * @since 1.0 + * @version 1.0 + */ + virtual ~AnimatorStopListener() {} + + /** + * @brief Called when this animator stops. + * + * You need to inherit from the AnimatorStopListener class and implement this function. + * + * @param view Indicates the instance of this view. + * @since 1.0 + * @version 1.0 + */ + virtual void OnAnimatorStop(UIView& view) {} + }; + + /** + * @brief Sets the listener for the stop of this animator. + * + * @param listener Indicates the listener to set. For details, see {@link AnimatorStopListener}. + * @since 1.0 + * @version 1.0 + */ + void SetAnimatorStopListener(AnimatorStopListener* listener) + { + listener_ = listener; + } + +protected: + class ImageAnimatorCallback : public AnimatorCallback { + public: + ImageAnimatorCallback() + : tickNum_(0), loop_(1), drawingImage_(0), repeat_(0), + imageSrc_(nullptr), imageNum_(0) + { + }; + + virtual ~ImageAnimatorCallback() {} + + virtual void Callback(UIView* view) override; + + void Reset() + { + tickNum_ = 0; + loop_ = 1; + repeat_ = 0; + } + + protected: + uint16_t tickNum_; + uint8_t loop_; + uint8_t drawingImage_; + uint32_t repeat_; + const ImageAnimatorInfo* imageSrc_; + uint8_t imageNum_; + }; + + void Reset(bool fillMode); + void UpdateImage(uint8_t& drawingImage, uint8_t& loop); + uint8_t GetTickByTime(uint16_t time) const; + ImageAnimatorInfo* imageSrc_; + uint8_t imageNum_; + uint8_t tickOfUpdate_; + uint16_t timeOfUpdate_; + uint16_t timeOfPause_; + uint8_t tickOfPause_; + uint32_t repeatTimes_; + Animator imageAnimator_; + ImageAnimatorCallback imageAnimatorCallback_; + AnimatorStopListener* listener_; + bool reverse_; + bool repeat_; + bool sizeFixed_; + bool fillMode_; +}; +} +#endif // GRAPHIC_LITE_UI_IMAGE_ANIMATOR_H diff --git a/interfaces/kits/components/ui_image_view.h b/interfaces/kits/components/ui_image_view.h new file mode 100755 index 0000000..f3acdfe --- /dev/null +++ b/interfaces/kits/components/ui_image_view.h @@ -0,0 +1,305 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @addtogroup UI_Components + * @{ + * + * @brief Defines UI components such as buttons, texts, images, lists, and progress bars. + * + * @since 1.0 + * @version 1.0 + */ + +/** + * @file ui_image_view.h + * + * @brief Declares an image view. + * + * @since 1.0 + * @version 1.0 + */ + +#ifndef GRAPHIC_LITE_UI_IMAGE_VIEW_H +#define GRAPHIC_LITE_UI_IMAGE_VIEW_H + +#include "common/image.h" +#include "components/ui_view.h" +#include "graphic_types.h" +#ifndef VERSION_LITE +#include "animator/animator.h" +#endif + +namespace OHOS { +/** + * @brief Defines the functions related to an image view. + * + * @since 1.0 + * @version 1.0 + */ +class UIImageView : public UIView { +public: + /** + * @brief A default constructor used to create a UIImageView instance. + * + * @since 1.0 + * @version 1.0 + */ + UIImageView(); + + /** + * @brief A destructor used to delete the UIImageView instance. + * + * @since 1.0 + * @version 1.0 + */ + virtual ~UIImageView(); + + /** + * @brief Obtains the view type. + * + * @return Returns UI_IMAGE_VIEW, as defined in {@link UIViewType}. + * @since 1.0 + * @version 1.0 + */ + UIViewType GetViewType() const override + { + return UI_IMAGE_VIEW; + } + + /** + * @brief Obtains the width of this image view. + * + * @return Returns the width of this image view. + * @since 1.0 + * @version 1.0 + */ + int16_t GetWidth() override + { + if (needRefresh_ && autoEnable_) { + ReMeasure(); + } + return UIView::GetWidth(); + } + + /** + * @brief Obtains the height of this image view. + * + * @return Returns the height of this image view. + * @since 1.0 + * @version 1.0 + */ + int16_t GetHeight() override + { + if (needRefresh_ && autoEnable_) { + ReMeasure(); + } + return UIView::GetHeight(); + } + + /** + * @brief Checks whether this image view needs to be covered to optimize the drawing process. + * + * @param invalidatedArea Indicates the area to draw. + * @return Returns true if this image view needs to be covered; returns false otherwise. + * @since 1.0 + * @version 1.0 + */ + bool OnPreDraw(Rect& invalidatedArea) const override; + + /** + * @brief Draws this image view. + * + * @param invalidatedArea Indicates the area to draw. + * @since 1.0 + * @version 1.0 + */ + void OnDraw(const Rect& invalidatedArea) override; + + /** + * @brief Sets the image path. + * + * @param src Indicates the pointer to the image path represented by a string. + * @since 1.0 + * @version 1.0 + */ + void SetSrc(const char* src); + + /** + * @brief Sets the image information. + * + * @param src Indicates the pointer to the image information. For details, see {@link ImageInfo}. + * @since 1.0 + * @version 1.0 + */ + void SetSrc(const ImageInfo* src); + + /** + * @brief Sets whether the image view size needs to be adaptive to the image size. + * + * @param enable Specifies whether the image view size needs to be adaptive to the image size. + * true indicates that automatic adaption is enabled, and false indicates the opposite case. + * @since 1.0 + * @version 1.0 + */ + void SetAutoEnable(bool enable) + { + if (autoEnable_ != enable) { + needRefresh_ = autoEnable_ ? needRefresh_ : true; + autoEnable_ = enable; + } + } + + /* + * @brief Checks whether automatic adaptation is enabled. + * + * @return Returns true if automatic adaptation is enabled; returns false otherwise. + * @since 1.0 + * @version 1.0 + */ + bool GetAutoEnable() const + { + return autoEnable_; + } + + /** + * @brief Sets the blur level for this image when it is rotated or scaled. + * + * @param level Indicates the blur level to set. For details, see {@link BlurLevel}. + * @since 1.0 + * @version 1.0 + */ + void SetBlurLevel(BlurLevel level) + { + blurLevel_ = level; + } + + /** + * @brief Obtains the blur level of this image when it is rotated or scaled. + * + * @return Returns the blur level of this image, as defined in {@link BlurLevel}. + * @since 1.0 + * @version 1.0 + */ + BlurLevel GetBlurLevel() const + { + return static_cast(blurLevel_); + } + + /** + * @brief Sets the algorithm used for image rotation and scaling. + * + * @param algorithm Indicates the image transformation algorithm. For details, see {@link TransformAlgorithm}. + * @since 1.0 + * @version 1.0 + */ + void SetTransformAlgorithm(TransformAlgorithm algorithm) + { + algorithm_ = algorithm; + } + + /** + * @brief Obtains the algorithm used for image rotation and scaling. + * + * @return Returns the image transform algorithm, as defined in {@link TransformAlgorithm}. + * @since 1.0 + * @version 1.0 + */ + TransformAlgorithm GetTransformAlgorithm() const + { + return static_cast(algorithm_); + } + + /** + * @brief Obtains the image path in binary. + * + * @return Returns the pointer to the image path. + * @since 1.0 + * @version 1.0 + */ + const char* GetPath() const + { + return image_.GetPath(); + } + + /** + * @brief Obtains the image information in an array. + * + * @return Returns the pointer to the image information. + * @since 1.0 + * @version 1.0 + */ + const ImageInfo* GetImageInfo() const + { + return image_.GetImageInfo(); + } + + /** + * @brief Obtains the image type. + * + * @return Returns IMG_SRC_VARIABLE for image information in an array; returns IMG_SRC_FILE for an + * image path in binary. + * @since 1.0 + * @version 1.0 + */ + uint8_t GetSrcType() const + { + return image_.GetSrcType(); + } + +protected: + /** + * @brief Represents the width of this image. + */ + int16_t imageWidth_; + /** + * @brief Represents the height of this image. + */ + int16_t imageHeight_; + /** + * @brief Specifies whether automatic adaptation is enabled. + */ + bool autoEnable_; + /** + * @brief Specifies whether a refresh is needed. + */ + bool needRefresh_; + /** + * @brief Represents the color format of this image. + */ + uint8_t colorFormat_ : 4; + /** + * @brief Represents the blur level of this image when it is rotated or scaled. + */ + uint8_t blurLevel_ : 2; + /** + * @brief Represents the algorithm used for image rotation and scaling. + */ + uint8_t algorithm_ : 1; + uint8_t reserve_ : 1; + Image image_; + +private: + void ReMeasure() override; +#ifndef VERSION_LITE + friend class GifImageAnimator; + void AddAndStartGifAnimator(); + void RemoveAndStopGifAnimator(); + Animator* gifImageAnimator_; + bool gifFrameFlag_; +#endif +}; +} // namespace OHOS +#endif // GRAPHIC_LITE_UI_IMAGE_VIEW_H \ No newline at end of file diff --git a/interfaces/kits/components/ui_label.h b/interfaces/kits/components/ui_label.h new file mode 100755 index 0000000..66d64bb --- /dev/null +++ b/interfaces/kits/components/ui_label.h @@ -0,0 +1,443 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @addtogroup UI_Components + * @{ + * + * @brief Defines UI components such as buttons, texts, images, lists, and progress bars. + * + * @since 1.0 + * @version 1.0 + */ + +/** + * @file ui_label.h + * + * @brief Declares a UILabel class that represents a label. + * + * @since 1.0 + * @version 1.0 + */ + +#ifndef GRAPHIC_LITE_UI_LABEL_H +#define GRAPHIC_LITE_UI_LABEL_H + +#include "animator/animator.h" +#include "common/text.h" +#include "components/ui_view.h" + +namespace OHOS { +/** + * @brief Defines the functions for presenting a label in a specified area, setting the style and background color + * of a label, and setting the display mode of a long label text. + * + * @since 1.0 + * @version 1.0 + */ +class UILabel : public UIView { +public: + /** + * @brief Enumerates the display modes of a long text. + */ + enum LineBreakMode : uint8_t { + /** + * The label size is adaptive to the text size. + */ + LINE_BREAK_ADAPT = 0, + /** + * The height of this label remains unchanged, and the width is adaptive to the text size. + */ + LINE_BREAK_STRETCH, + /** + * The width of this label remains unchanged, and the height is adaptive to the text size. + * The text switches to the next line if the text exceeds the maximum label width. + */ + LINE_BREAK_WRAP, + /** + * The width and height of this label remain unchanged. + * If this text is too long, ellipsis will be used at the end. + */ + LINE_BREAK_ELLIPSIS, + /** + * The width and height of this label remain unchanged. + * If this text is too long, it will be rolled to display. + */ + LINE_BREAK_MARQUEE, + /** + * The width and height of this label remain unchanged. + * If this text is too long, it will be cropped to display. + */ + LINE_BREAK_CLIP, + /** + * Maximum value of the line break mode, which is used for validity check. + */ + LINE_BREAK_MAX, + }; + + /** + * @brief A constructor used to create a UILabel instance. + * + * @since 1.0 + * @version 1.0 + */ + UILabel(); + + /** + * @brief A destructor used to delete the UILabel instance. + * + * @since 1.0 + * @version 1.0 + */ + virtual ~UILabel(); + + /** + * @brief Obtains the view type. + * + * @return Returns UI_LABEL, as defined in {@link UIViewType}. + * @since 1.0 + * @version 1.0 + */ + UIViewType GetViewType() const override + { + return UI_LABEL; + } + + /** + * @brief Obtains the width of this label. + * + * @return Returns the label width. + * @since 1.0 + * @version 1.0 + */ + int16_t GetWidth() override; + + /** + * @brief Obtains the height of this label. + * + * @return Returns the label height. + * @since 1.0 + * @version 1.0 + */ + int16_t GetHeight() override; + + /** + * @brief Sets the view style. + * @param style Indicates the view style. + * @since 1.0 + * @version 1.0 + */ + void SetStyle(Style& style) override + { + UIView::SetStyle(style); + } + + /** + * @brief Sets a style. + * + * @param key Indicates the key of the style to set. + * @param value Indicates the value matching the key. + * @since 1.0 + * @version 1.0 + */ + void SetStyle(uint8_t key, int64_t value) override; + + /** + * @brief Checks whether this label needs to be covered before drawing it. + * + * @param invalidatedArea Indicates the area to draw. + * @return Returns true if this label needs to be covered; returns false otherwise. + * @since 1.0 + * @version 1.0 + */ + bool OnPreDraw(Rect& invalidatedArea) const override + { + return false; + } + + /** + * @brief Draws this label. + * + * @param invalidatedArea Indicates the area to draw. + * @since 1.0 + * @version 1.0 + */ + void OnDraw(const Rect& invalidatedArea) override; + + /** + * @brief Sets the text content for this label. + * + * @param text Indicates the pointer to the text content. + * @since 1.0 + * @version 1.0 + */ + void SetText(const char* text); + + /** + * @brief Obtains the text of this label. + * + * @return Returns the text. + * @since 1.0 + * @version 1.0 + */ + const char* GetText() const + { + return (labelText_ == nullptr) ? nullptr : labelText_->GetText(); + } + + /** + * @brief Sets the line break mode for this text. + * + * @param lineBreakMode Indicates the line break mode to set. + * @since 1.0 + * @version 1.0 + */ + void SetLineBreakMode(const uint8_t lineBreakMode); + + /** + * @brief Obtains the line break mode of this text. + * + * @return Returns the line break mode. + * @since 1.0 + * @version 1.0 + */ + uint8_t GetLineBreakMode() const + { + return lineBreakMode_; + } + + /** + * @brief Sets the color for this text. + * + * @param color Indicates the text color to set. + * @since 1.0 + * @version 1.0 + */ + void SetTextColor(ColorType color) + { + useTextColor_ = true; + textColor_ = color; + } + + /** + * @brief Obtains the color of this text. + * + * @return Returns the text color. + * @since 1.0 + * @version 1.0 + */ + ColorType GetTextColor() const + { + return useTextColor_ ? textColor_ : GetStyleConst().textColor_; + } + + /** + * @brief Sets the alignment mode for this text. + * + * @param horizontalAlign Indicates the horizontal alignment mode to set, + * which can be {@link TEXT_ALIGNMENT_LEFT}, + * {@link TEXT_ALIGNMENT_CENTER}, or {@link TEXT_ALIGNMENT_RIGHT}. + * @param verticalAlign Indicates the vertical alignment mode to set, which can be + * {@link TEXT_ALIGNMENT_TOP} (default mode), {@link TEXT_ALIGNMENT_CENTER}, + * or {@link TEXT_ALIGNMENT_BOTTOM}. + * @since 1.0 + * @version 1.0 + */ + void SetAlign(UITextLanguageAlignment horizontalAlign, + UITextLanguageAlignment verticalAlign = TEXT_ALIGNMENT_TOP); + + /** + * @brief Obtains the horizontal alignment mode. + * + * @return Returns the horizontal alignment mode. + * @since 1.0 + * @version 1.0 + */ + UITextLanguageAlignment GetHorAlign() + { + InitLabelText(); + return labelText_->GetHorAlign(); + } + + /** + * @brief Obtains the vertical alignment mode. + * + * @return Returns the vertical alignment mode. + * @since 1.0 + * @version 1.0 + */ + UITextLanguageAlignment GetVerAlign() + { + InitLabelText(); + return labelText_->GetVerAlign(); + } + + /** + * @brief Sets the direction for this text. + * + * @return direct Returns the text direction, as defined in {@link UITextLanguageDirect}. + * @since 1.0 + * @version 1.0 + */ + void SetDirect(UITextLanguageDirect direct) + { + InitLabelText(); + labelText_->SetDirect(direct); + } + + /** + * @brief Obtains the direction of this text. + * + * @return Returns the text direction, as defined in {@link UITextLanguageDirect}. + * @since 1.0 + * @version 1.0 + */ + UITextLanguageDirect GetDirect() + { + InitLabelText(); + return labelText_->GetDirect(); + } + + /** + * @brief Sets the font ID for this label. + * + * @param fontId Indicates the font ID composed of font name and size. + * @since 1.0 + * @version 1.0 + */ + void SetFontId(uint8_t fontId); + + /** + * @brief Obtains the font ID composed of font name and size. + * + * @return Returns the front ID of this label. + * @since 1.0 + * @version 1.0 + */ + uint8_t GetFontId() + { + InitLabelText(); + return labelText_->GetFontId(); + } + + /** + * @brief Sets the font for this label. + * + * @param name Indicates the pointer to the font name. + * @param size Indicates the font size to set. + * @since 1.0 + * @version 1.0 + */ + void SetFont(const char* name, uint8_t size); + + /** + * @brief Sets the scroll speed for this text. + * + * @param speed Indicates the scroll speed to set. + * @since 1.0 + * @version 1.0 + */ + void SetRollSpeed(uint16_t speed); + + /** + * @brief Obtains the width of this text. + * + * @return Returns the text width. + * @since 1.0 + * @version 1.0 + */ + uint16_t GetTextWidth(); + + /** + * @brief Obtains the height of this text. + * + * @return Returns the text height. + * @since 1.0 + * @version 1.0 + */ + uint16_t GetTextHeight(); + + /** + * @brief Sets the position where this text starts to roll. + * + * @param pos Indicates the position to set. + * @since 1.0 + * @version 1.0 + */ + void SetRollStartPos(int16_t pos); + + /** + * @brief Obtains the position where this text starts to roll. + * + * @return Returns the position where this text starts to roll. + * @since 1.0 + * @version 1.0 + */ + int16_t GetRollStartPos() const; + + /** + * @brief Sets the width for this label. + * + * @param width Indicates the width to set. + * @since 1.0 + * @version 1.0 + */ + void SetWidth(int16_t width) override; + + /** + * @brief Sets the height for this label. + * + * @param height Indicates the height to set. + * @since 1.0 + * @version 1.0 + */ + void SetHeight(int16_t height) override; + +protected: + Text* labelText_; + void RefreshLabel(); + + virtual void InitLabelText() + { + if (labelText_ == nullptr) { + labelText_ = new Text(); + } + } + +private: + friend class LabelAnimator; + + void ReMeasure() override; + void RemeasureForMarquee(int16_t textWidth); + + bool needRefresh_ : 1; + bool useTextColor_ : 1; + bool hasAnimator_ : 1; + uint8_t lineBreakMode_ : 4; + uint16_t ellipsisIndex_; + int16_t offsetX_; + ColorType textColor_; + + static constexpr uint16_t DEFAULT_ANIMATOR_SPEED = 35; + union { + Animator* animator; + struct { + uint16_t speed; + int16_t pos; + }; + } animator_; +}; +} // namespace OHOS +#endif // GRAPHIC_LITE_UI_LABEL_H diff --git a/interfaces/kits/components/ui_label_button.h b/interfaces/kits/components/ui_label_button.h new file mode 100755 index 0000000..efe4f81 --- /dev/null +++ b/interfaces/kits/components/ui_label_button.h @@ -0,0 +1,312 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @addtogroup UI_Components + * @{ + * + * @brief Defines UI components such as buttons, texts, images, lists, and progress bars. + * + * @since 1.0 + * @version 1.0 + */ + +/** + * @file ui_label_button.h + * + * @brief Declares a label button. + * + * @since 1.0 + * @version 1.0 + */ + +#ifndef GRAPHIC_LITE_UI_LABEL_BUTTON_H +#define GRAPHIC_LITE_UI_LABEL_BUTTON_H + +#include "common/text.h" +#include "components/ui_button.h" +#include "graphic_log.h" + +namespace OHOS { +/** + * @brief Provides the functions related to a label button. + * + * @since 1.0 + * @version 1.0 + */ +class UILabelButton : public UIButton { +public: + /** + * @brief A constructor used to create a UILabelButton instance. + * + * @since 1.0 + * @version 1.0 + */ + UILabelButton(); + + /** + * @brief A destructor used to delete the UILabelButton instance. + * + * @since 1.0 + * @version 1.0 + */ + virtual ~UILabelButton(); + + /** + * @brief Draws a label button. + * + * @since 1.0 + * @version 1.0 + */ + void OnDraw(const Rect& invalidatedArea) override; + + /** + * @brief Obtains the view type. + * + * @return Returns UI_LABEL_BUTTON, as defined in {@link UIViewType}. + * @since 1.0 + * @version 1.0 + */ + UIViewType GetViewType() const override + { + return UI_LABEL_BUTTON; + } + + /** + * @brief Sets the text for this label button. + * + * @param text Indicates the pointer to the text. + * @since 1.0 + * @version 1.0 + */ + void SetText(const char* text) + { + InitLabelButtonText(); + labelButtonText_->SetText(text); + } + + /** + * @brief Obtains the text of this label button. + * + * @return Returns the text. + * @since 1.0 + * @version 1.0 + */ + const char* GetText() const + { + return (labelButtonText_ == nullptr) ? nullptr : labelButtonText_->GetText(); + } + + /** + * @brief Sets the position for this label relative to the button holding it. + * + * @param x Indicates the offset distance by which this label is moved on the x-axis. + * @param y Indicates the offset distance by which this label is moved on the y-axis. + * @since 1.0 + * @version 1.0 + */ + void SetLablePosition(int16_t x, int16_t y) + { + offset_.x = x; + offset_.y = y; + } + + /** + * @brief Obtains the position of this label relative to the button holding it. + * + * @return Returns the position of this label. + * @since 1.0 + * @version 1.0 + */ + Point GetLabelPosition() const + { + return offset_; + } + + /** + * @brief Sets the alignment mode for this text. + * + * @param align Indicates the text alignment mode to set, as defined in {@link UITextLanguageAlignment}. + * @since 1.0 + * @version 1.0 + */ + void SetAlign(UITextLanguageAlignment align) + { + InitLabelButtonText(); + labelButtonText_->SetAlign(align, TEXT_ALIGNMENT_CENTER); + } + + /** + * @brief Obtains the alignment mode of this text. + * + * @return Returns the text alignment mode, as defined in {@link UITextLanguageAlignment}. + * @since 1.0 + * @version 1.0 + */ + UITextLanguageAlignment GetAlign() + { + InitLabelButtonText(); + return labelButtonText_->GetHorAlign(); + } + + /** + * @brief Sets the direction for this text. + * + * @param direct Indicates the text direction to set, as defined in {@link UITextLanguageDirect}. + * @since 1.0 + * @version 1.0 + */ + void SetDirect(UITextLanguageDirect direct) + { + InitLabelButtonText(); + labelButtonText_->SetDirect(direct); + } + + /** + * @brief Obtains the direction of this text. + * + * @return Returns the text direction, as defined in {@link UITextLanguageDirect}. + * @since 1.0 + * @version 1.0 + */ + UITextLanguageDirect GetDirect() + { + InitLabelButtonText(); + return labelButtonText_->GetDirect(); + } + + /** + * @brief Sets the style for this label. + * + * @param labelStyle Indicates the label style to set. + * @since 1.0 + * @version 1.0 + */ + void SetLabelStyle(Style& labelStyle) + { + labelStyle_ = labelStyle; + } + + /** + * @brief Sets a style for this label. + * + * @param key Indicates the key of the style to set. + * @param value Indicates the value matching the key. + * @since 1.0 + * @version 1.0 + */ + void SetLabelStyle(uint8_t key, int64_t value) + { + labelStyle_.SetStyle(key, value); + } + + /** + * @brief Obtains the style of this label. + * + * @return Returns the label style. + * @since 1.0 + * @version 1.0 + */ + const Style& GetLabelStyle() const + { + return labelStyle_; + } + + /** + * @brief Obtains the value of a style of this label. + * + * @param key Indicates the key of the style. + * @return Returns the value of the style. + * @since 1.0 + * @version 1.0 + */ + int64_t GetLabelStyle(uint8_t key) const + { + return labelStyle_.GetStyle(key); + } + + /** + * @brief Sets the color for this text. + * + * @param color Indicates the text color to set. + * @since 1.0 + * @version 1.0 + */ + void SetTextColor(ColorType color) + { + labelStyle_.textColor_ = color; + } + + /** + * @brief Sets the font for this text. + * + * @param name Indicates the pointer to the font name. + * @param size Indicates the font size to set. + * @since 1.0 + * @version 1.0 + */ + void SetFont(const char* name, uint8_t size) + { + InitLabelButtonText(); + labelButtonText_->SetFont(name, size); + } + + /** + * @brief Sets the font ID. + * + * @param fontId Indicates the font ID composed of the font name and size. + * @since 1.0 + * @version 1.0 + */ + void SetFontId(uint8_t fontId) + { + InitLabelButtonText(); + labelButtonText_->SetFontId(fontId); + } + + /** + * @brief Obtains the font ID. + * + * @return Returns the front ID composed of the font name and size. + * @since 1.0 + * @version 1.0 + */ + uint8_t GetFontId() + { + InitLabelButtonText(); + return labelButtonText_->GetFontId(); + } + +protected: + virtual void InitLabelButtonText() + { + if (labelButtonText_ == nullptr) { + labelButtonText_ = new Text(); + if (labelButtonText_ == nullptr) { + GRAPHIC_LOGE("new Text fail"); + return; + } + labelButtonText_->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER); + } + } + + Text* labelButtonText_; + +private: + Style labelStyle_; + Point offset_; /* Text draw position offset */ +}; +} // namespace OHOS +#endif // GRAPHIC_LITE_UI_LABEL_BUTTON_H diff --git a/interfaces/kits/components/ui_list.h b/interfaces/kits/components/ui_list.h new file mode 100755 index 0000000..8d2f406 --- /dev/null +++ b/interfaces/kits/components/ui_list.h @@ -0,0 +1,449 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @addtogroup UI_Components + * @{ + * + * @brief Defines UI components such as buttons, texts, images, lists, and progress bars. + * + * @since 1.0 + * @version 1.0 + */ + +/** + * @file ui_list.h + * + * @brief Declares a scrollable list in the vertical or horizontal direction. * This scrollable list can be used with + * the adapter {@link AbstractAdapter} to implement scrolling, inertial scrolling, automatic alignment, and + * invoking of a callback when a child view at the preset position is selected as this list scrolls. It is used + * when there is a large number of child views with a fixed format. + * + * @since 1.0 + * @version 1.0 + */ + +#ifndef GRAPHIC_LITE_UI_LIST_H +#define GRAPHIC_LITE_UI_LIST_H + +#include "components/abstract_adapter.h" +#include "components/ui_abstract_scroll.h" +#include "dock/focus_manager.h" +#include "list.h" + +namespace OHOS { +/** + * @brief Represents a listener that contains a callback to be invoked when the scroll state changes or when a new child + * view is selected at the preset position as this list scrolls. The scroll state can be {@link + * SCROLL_STATE_STOP} or {@link SCROLL_STATE_MOVE}. + * @since 1.0 + * @version 1.0 + */ +class ListScrollListener : public HeapBase { +public: + /** + * @brief A constructor used to create a ListScrollListener instance with the default state + * {@link SCROLL_STATE_STOP}. + * @since 1.0 + * @version 1.0 + */ + ListScrollListener() : state_(SCROLL_STATE_STOP) {} + + /** + * @brief A destructor used to delete the ListScrollListener instance. + * @since 1.0 + * @version 1.0 + */ + virtual ~ListScrollListener() {} + + /** + * @brief Called when a scroll starts. + * @param index Indicates the index of the child view being selected at the preset position. This parameter should + * be set to {@link NULL_SELECT_INDEX} if no child view is selected or no position is preset. + * @param view Indicates the child view being selected at the preset position. This parameter should be set to + * NULL if no child view is selected or no position is preset. + * @since 1.0 + * @version 1.0 + */ + virtual void OnScrollStart(int16_t index, UIView* view) {} + + /** + * @brief Called when a scroll ends. + * + * @param index Indicates the index of the child view being selected at the preset position. This parameter should + * be set to {@link NULL_SELECT_INDEX} if no child view is selected or no position is preset. + * @param view Indicates the child view being selected at the preset position. This parameter should be set to + * NULL if no child view is selected or no position is preset. + * @since 1.0 + * @version 1.0 + * + */ + virtual void OnScrollEnd(int16_t index, UIView* view) {} + + /** + * @brief Called when a new child view is selected at the preset position as this list scrolls. For details about + * how to set the position, see {@link SetSelectPosition}. + * + * @param index Indicates the index of the child view being selected at the preset position. This parameter should + * be set to {@link NULL_SELECT_INDEX} if no child view is selected or no position is preset. + * @param view Indicates the child view being selected at the preset position. This parameter should be set to + * NULL if no child view is selected or no position is preset. + * @since 1.0 + * @version 1.0 + * + */ + virtual void OnItemSelected(int16_t index, UIView* view) {} + + /** + * @brief Obtains the scroll state of this list. + * + * @return Returns the scroll state, either {@link SCROLL_STATE_STOP} or {@link SCROLL_STATE_MOVE}. + * @since 1.0 + * @version 1.0 + */ + uint8_t GetScrollState() const + { + return state_; + } + + void SetScrollState(uint8_t state) + { + state_ = state; + } + + static constexpr uint8_t SCROLL_STATE_STOP = 0; + static constexpr uint8_t SCROLL_STATE_MOVE = 1; + +private: + friend class UIList; + uint8_t state_; +}; + +/** + * @brief Represents a scrollable list which is used with the adapter {@link AbstractAdapter} to implement scrolling, + * inertial scrolling, automatic alignment, and invoking of a callback when a child view is selected at the + * preset position as this list scrolls. + * + * You need to override {@link AbstractAdapter} to implement functions for setting and saving data, obtaining data + * quantity, and creating child views. UIList is used when there is a large number of child views with a fixed + * format. This list automatically reclaims the child views removed out of the current view as it scrolls so that as + * many as child views can be displayed with a lower memory consumption. + * + * @since 1.0 + * @version 1.0 + */ +class UIList : public UIAbstractScroll { +public: + /** + * @brief A constructor used to create a UIList instance in the vertical direction. + * @since 1.0 + * @version 1.0 + */ + UIList(); + + /** + * @brief A constructor used to create a UIList instance in the specified direction. + * + * @param direction Indicates the UIList direction, either {@link HORIZONTAL} or {@link VERTICAL}. + * @since 1.0 + * @version 1.0 + */ + UIList(uint8_t direction); + + /** + * @brief A destructor used to delete the UIList instance. + * @since 1.0 + * @version 1.0 + */ + virtual ~UIList(); + + /** + * @brief Obtains the view type. + * @return Returns the view type, as defined in {@link UIViewType}. + * @since 1.0 + * @version 1.0 + */ + UIViewType GetViewType() const override + { + return UI_LIST; + } + + bool OnDragEvent(const DragEvent& event) override; + + bool OnDragEndEvent(const DragEvent& event) override; + + bool OnPressEvent(const PressEvent& event) override; + +#if ENABLE_ROTATE_INPUT + bool OnRotateEvent(const RotateEvent& event) override; +#endif + + /** + * @brief Sets the list direction. + * + * @param direction Indicates the list direction, either {@link HORIZONTAL} or {@link VERTICAL}. + * @since 1.0 + * @version 1.0 + */ + void SetDirection(uint8_t direction) + { + direction_ = direction; + } + + /** + * @brief Obtains the list direction. + * @return Returns the list direction, either {@link HORIZONTAL} or {@link VERTICAL}. + * @since 1.0 + * @version 1.0 + */ + uint8_t GetDirection() const + { + return direction_; + } + + /** + * @brief Sets the adapter for this list. The content of this list is initialized when the adapter is set. + * + * @param adapter Indicates the adapter to set. For details, see {@link AbstractAdapter}. + * @since 1.0 + * @version 1.0 + */ + void SetAdapter(AbstractAdapter* adapter); + + /** + * @brief Moves the position of all child views. + * + * @param offsetX Indicates the offset distance by which a child view is moved on the x-axis. + * @param offsetY Indicates the offset distance by which a child view is moved on the y-axis. + * @since 1.0 + * @version 1.0 + */ + virtual void MoveChildByOffset(int16_t x, int16_t y) override; + + /** + * @brief Scrolls to change the index of the first row or column of the current view. + * + * @param index Indicates the new index of the first row or column of the current view. + * @since 1.0 + * @version 1.0 + */ + void ScrollTo(uint16_t index); + + /** + * @brief Scrolls the content in this list. + * + * @param distance Indicates the distance by which the content is scrolled. + * @since 1.0 + * @version 1.0 + */ + void ScrollBy(int16_t distance); + + /** + * @brief Sets the start index for this list. + * + * @param index Indicates the start index to set. The default value is 0. + * @since 1.0 + * @version 1.0 + * + */ + void SetStartIndex(uint16_t index) + { + startIndex_ = index; + } + + /** + * @brief Obtains the start index of this list. The default value is 0. + * + * @return Returns the start index. + * @since 1.0 + * @version 1.0 + */ + uint16_t GetStartIndex() const + { + return startIndex_; + } + + /** + * @brief Sets the loop state for this list, in which a loop scroll is possible since the top and bottom of the list + * are connected together. + * + * @param state Indicates the loop state to set. + * @since 1.0 + * @version 1.0 + */ + void SetLoopState(bool state) + { + isLoopList_ = state; + } + + /** + * @brief Checks whether this list is in a loop state. + * + * @return Returns if the list is in the loop state; returns false if the list is in the common state. + * @since 1.0 + * @version 1.0 + */ + bool GetLoopState() const + { + return isLoopList_; + } + + /** + * @brief Sets the position where a child view is selected as this list scrolls. + * + * When a child view is selected at the specified position, the callback {@link ListScrollListener} is invoked. + * You can implement the zoom-in and color-change effects in the callback. + * + * @param position Indicates the position to set. The default value is 0, indicating that no position is + * set. + * @since 1.0 + * @version 1.0 + */ + void SetSelectPosition(uint16_t position) + { + selectPosition_ = position; + } + + /** + * @brief Obtains the child view being selected at the preset position. + * + * @return Returns the child view if available; returns NULL if no child view is selected or no position is + * set. + * @since 1.0 + * @version 1.0 + */ + UIView* GetSelectView(); + + /** + * @brief Sets the listener that contains a callback to be invoked when a child view is selected as this list + * scrolls. + * + * @param scrollListener Indicates the listener to set. + * @since 1.0 + * @version 1.0 + */ + void SetScrollStateListener(ListScrollListener* scrollListener) + { + scrollListener_ = scrollListener; + } + + /** + * @brief Refreshes this list. The number of child views in the current view is fixed and the positions of those + * reserved child views as this list scrolls remain unchanged. + * @since 1.0 + * @version 1.0 + */ + void RefreshList(); + + /** + * @brief Sets the automatic alignment state for this list. When a scroll stops, a child view is selected and its + * position is automatically aligned with the preset position. + * + * @param state Indicates the automatic alignment state. true indicates this state is enabled, and + * false indicates the opposite case. + * @since 1.0 + * @version 1.0 + */ + void EnableAutoAlign(bool state) + { + autoAlign_ = state; + } + + void RemoveAll() override; + + static constexpr int8_t NULL_SELECT_INDEX = -1; + + UIView* onSelectedView_; + +protected: + static constexpr int16_t RECALCULATE_DRAG_DISTANCE = 10; + static constexpr int16_t RECALCULATE_DRAG_TIMES = 10; + void StopAnimator() override; + bool DragXInner(int16_t distance) override; + bool DragYInner(int16_t distance) override; + +private: + friend class UIPicker; + class Recycle : public HeapBase { + public: + explicit Recycle(UIList* list) : adapter_(nullptr), listView_(list) {} + virtual ~Recycle(); + void InitRecycle(); + UIView* GetView(int16_t index); + void SetAdapter(AbstractAdapter* adapter) + { + adapter_ = adapter; + } + + void SetList(UIList* listView) + { + listView_ = listView; + } + + void AddScrapView(UIView* view) + { + scrapView_.PushBack(view); + } + + uint16_t GetAdapterItemCount() + { + return (adapter_ == nullptr) ? 0 : adapter_->GetCount(); + } + + void ClearScrapView() + { + scrapView_.Clear(); + } + + private: + void FillActiveView(); + + List scrapView_; + AbstractAdapter* adapter_; + UIList* listView_; + }; + + void PushBack(UIView* view); + void PopItem(UIView* view); + void PushFront(UIView* view); + void SetHead(UIView* view); + bool MoveChildStep(int16_t distance); + bool MoveChildStepInner(int16_t distance, + int16_t (UIView::*pfnGetXOrY)() const, + int16_t (Rect::*pfnGetWidthOrHeight)() const); + uint16_t GetIndexInc(uint16_t index); + uint16_t GetIndexDec(uint16_t index); + + bool MoveOffset(int16_t offset); + bool IsNeedReCalculateDragEnd(); + bool ReCalculateDragEnd(); +#if ENABLE_MOTOR + void SetMotorType(MotorType motorType); + MotorType motorType_; +#endif + bool isLoopList_; + bool isReCalculateDragEnd_; + bool autoAlign_; + uint16_t startIndex_; + uint16_t topIndex_; + uint16_t bottomIndex_; + uint16_t selectPosition_; + int16_t onSelectedIndex_; + Recycle recycle_; + ListScrollListener* scrollListener_; + void CalculateReboundDistance(int16_t& dragDistanceX, int16_t& dragDistanceY) override; +}; +} // namespace OHOS +#endif // GRAPHIC_LITE_UI_LIST_H diff --git a/interfaces/kits/components/ui_picker.h b/interfaces/kits/components/ui_picker.h new file mode 100755 index 0000000..6b248d9 --- /dev/null +++ b/interfaces/kits/components/ui_picker.h @@ -0,0 +1,429 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @addtogroup UI_Components + * @{ + * + * @brief Defines UI components such as buttons, texts, images, lists, and progress bars. + * + * @since 1.0 + * @version 1.0 + */ + +/** + * @file ui_picker.h + * + * @brief Defines the attributes and functions of the UIPicker class. + * + * @since 1.0 + * @version 1.0 + */ + +#ifndef UI_PICKER_H +#define UI_PICKER_H + +#include "components/text_adapter.h" +#include "components/ui_list.h" + +namespace OHOS { +/** + * @brief Defines a picker. Multiple texts or numbers can be put into a sliding list for selection. + * The selected text or numbers are highlighted. + * + * @since 1.0 + * @version 1.0 + */ +class UIPicker : public UIViewGroup { +public: + /** + * @brief A constructor used to create a UIPicker instance. + * + * @since 1.0 + * @version 1.0 + */ + UIPicker(); + + /** + * @brief A destructor used to delete the UIPicker instance. + * + * @since 1.0 + * @version 1.0 + */ + virtual ~UIPicker(); + + /** + * @brief Obtains the view type. + * + * @return Returns the view type. For details, see {@link UIViewType}. + * @since 1.0 + * @version 1.0 + */ + UIViewType GetViewType() const override + { + return UI_PICKER; + } + + bool OnPreDraw(Rect& invalidatedArea) const override + { + return false; + } + + /** + * @brief Sets dynamic text data in the picker by using a string array. + * + * @param value[] Indicates the array of text data. + * @param count Indicates the array size. + * @return Returns true if the setting is successful; returns false otherwise. + * @since 1.0 + * @version 1.0 + */ + virtual bool SetValues(const char* value[], uint16_t count); + + /** + * @brief Sets the numeric data in the picker by using a given numeric range. + * + * All integers in the range are automatically generated based on the start value and end value and placed in + * the picker in sequence. The start value must be smaller or equal to the end value. + * + * @param start Indicates the start integer. + * @param end Indicates the end integer. + * @return Returns true if the setting is successful; returns false otherwise. + * @since 1.0 + * @version 1.0 + */ + virtual bool SetValues(int16_t start, int16_t end); + + /** + * @brief Clears all values in the picker. + * + * @since 1.0 + * @version 1.0 + */ + virtual void ClearValues(); + + /** + * @brief Sets the font IDs of dynamic text, which is the string array set through {@link SetValues}. + * + * @param backgroundFontId Indicates the font ID of the background text. + * @param highlightFontId Indicates the font ID of the highlighted text. + * @since 1.0 + * @version 1.0 + */ + void SetFontId(uint8_t backgroundFontId, uint8_t highlightFontId); + + /** + * @brief Sets the font name and size for the background text. + * + * @param name Indicates the pointer to the font name to set. + * @param size Indicates the font size to set. + * @since 1.0 + * @version 1.0 + */ + void SetBackgroundFont(const char* name, uint8_t size); + + /** + * @brief Sets the font name and size for the highlighted text. + * + * @param name Indicates the pointer to the font name to set. + * @param size Indicates the font size to set. + * @since 1.0 + * @version 1.0 + */ + void SetHighlightFont(const char* name, uint8_t size); + + /** + * @brief Obtains the font ID of the background text. + * + * @return Returns the font ID. + * @since 1.0 + * @version 1.0 + */ + uint16_t GetBackgroundFontId() const + { + return backgroundFontId_; + } + + /** + * @brief Obtains the font ID of the highlighted text. + * + * @return Returns the font ID. + * @since 1.0 + * @version 1.0 + */ + uint16_t GetHighlightFontId() const + { + return highlightFontId_; + } + + /** + * @brief Sets the text color. + * + * @param backgroundColor Indicates the color of the background text. + * @param highlightColor Indicates the color of the highlighted text. + * @since 1.0 + * @version 1.0 + */ + void SetTextColor(ColorType backgroundColor, ColorType highlightColor); + + /** + * @brief Obtains the color of the background text. + * + * @return Returns the color. + * @since 1.0 + * @version 1.0 + */ + ColorType GetBackgroundTextColor() const + { + return backgroundColor_; + } + + /** + * @brief Obtains the color of the highlighted text. + * + * @return Returns the color of the highlighted text. + * @since 1.0 + * @version 1.0 + */ + ColorType GetHighlightTextColor() const + { + return highlightColor_; + } + + /** + * @brief Sets the index of the item currently selected in the picker. + * + * @param index Indicates the index to set. + * @return Returns true if the setting is successful; returns false otherwise. + * @since 1.0 + * @version 1.0 + */ + bool SetSelected(uint16_t index); + + /** + * @brief Obtains the index of the item currently selected in the picker. + * + * @return Returns the index. + * @since 1.0 + * @version 1.0 + */ + uint16_t GetSelected() const; + + /** + * @brief Sets the height of each item in the picker. + * + * @param height Indicates the height to set. + * @since 1.0 + * @version 1.0 + */ + void SetItemHeight(int16_t height); + + /** + * @brief Sets the width for this component. + * + * @param width Indicates the width to set. + * @since 1.0 + * @version 1.0 + */ + void SetWidth(int16_t width) override; + + /** + * @brief Sets the height for this component. + * + * @param height Indicates the height to set. + * @since 1.0 + * @version 1.0 + */ + void SetHeight(int16_t height) override; + + /** + * @brief Sets whether a picker can slide cyclically. + * + * @param state Specifies whether the picker can slide cyclically. Value true indicates that the picker + * can slide cyclically, and value false indicates that the picker cannot + * slide cyclically. The default value is false. + * @since 1.0 + * @version 1.0 + */ + void SetLoopState(bool state); + + /** + * @brief Defines the listener used by a picker. This listener is triggered when an item + * is selected after sliding stops. + * + * @since 1.0 + * @version 1.0 + */ + class SelectedListener : public HeapBase { + public: + /** + * @brief A constructor used to create a SelectedListener instance. + * + * @since 1.0 + * @version 1.0 + */ + SelectedListener() {} + + /** + * @brief A destructor used to delete the SelectedListener instance. + * + * @since 1.0 + * @version 1.0 + */ + virtual ~SelectedListener() {} + + /** + * @brief Called when an item is selected after sliding stops. This function is implemented by applications. + * + * @param picker Indicates the picker instance. + * @since 1.0 + * @version 1.0 + */ + virtual void OnPickerStoped(UIPicker& picker) {} + }; + + /** + * @brief Registers a listener for a selected event. + * + * @param pickerListener Indicates the listener for a selected event in the picker. For details, + * see {@link SelectedListener}. + * + * @since 1.0 + * @version 1.0 + */ + void RegisterSelectedListener(SelectedListener* pickerListener) + { + pickerListener_ = pickerListener; + } + + /** + * @brief Sets the text direction. + * + * @param direct Indicates the text direction to set. For details, see {@link UITextLanguageDirect}. + * + * @since 1.0 + * @version 1.0 + */ + void SetDirect(UITextLanguageDirect direct); + + /** + * @brief Sets the text formatter. + * + * @param formatter Indicates the pointer to the text formatter. For details, see {@link TextFormatter}. + * + * @since 1.0 + * @version 1.0 + */ + void SetTextFormatter(TextFormatter* formatter); + +#if ENABLE_ROTATE_INPUT + /** + * @brief 使组件注册获取焦点 + * + * @since 5.0 + * @version 3.0 + */ + void RequestFocus() override + { + list_.RequestFocus(); + } + + /** + * @brief 使组件失去焦点 + * + * @since 5.0 + * @version 3.0 + */ + void ClearFocus() override + { + list_.ClearFocus(); + } + + /** + * @brief 设置组件旋转系数 + * + * @param factor 旋转系数,旋转输入事件的旋转参数与旋转系数的乘积为实际滑动像素值 + * @since 5.0 + * @version 3.0 + */ + void SetRotateFactor(int8_t factor) + { + list_.SetRotateFactor(factor); + } + + /** + * @brief 获得组件旋转系数 + * @return 返回旋转系数 + * + * @since 5.0 + * @version 3.0 + */ + int8_t GetRotateFactor() + { + return list_.GetRotateFactor(); + } +#endif + +protected: + bool RefreshSelected(uint16_t index); + void RefreshList(); + virtual void ClearList(); + virtual void Refresh(); + virtual void InitTextAdapter() + { + if (textAdapter_ == nullptr) { + textAdapter_ = new TextAdapter(); + if (textAdapter_ == nullptr) { + GRAPHIC_LOGE("new TextAdapter fail"); + return; + } + } + } + + bool isWidthSet_ : 1; + bool isHeightSet_ : 1; + TextAdapter* textAdapter_; + uint16_t maxCount_; + uint16_t setSelectedIndex_; + +private: + friend class PickerListScrollListener; + bool RefreshValues(const char* value[], uint16_t count); + bool RefreshValues(int16_t start, int16_t end); + + uint16_t backgroundFontId_; + uint16_t highlightFontId_; + uint8_t backgroundFontSize_; + uint8_t highlightFontSize_; + char* backgroundFontName_; + char* highlightFontName_; + + uint16_t itemsWidth_; + uint16_t itemsHeight_; + const char** rangeValue_; + uint16_t rangeValueCount_; + int16_t startValue_; + int16_t endValue_; + ColorType backgroundColor_; + ColorType highlightColor_; + List dataList_; + bool isSetAdaptered_ : 1; + UIList list_; + void* listListener_; + SelectedListener* pickerListener_; + UITextLanguageDirect direct_; +}; +} // namespace OHOS +#endif diff --git a/interfaces/kits/components/ui_qrcode.h b/interfaces/kits/components/ui_qrcode.h new file mode 100755 index 0000000..5e23a00 --- /dev/null +++ b/interfaces/kits/components/ui_qrcode.h @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @addtogroup UI_Components + * @{ + * + * @brief Defines UI components such as buttons, texts, images, lists, and progress bars. + * + * @since 1.0 + * @version 1.0 + */ + +/** + * @file ui_qrcode.h + * + * @brief Declares the attributes and functions of the UIQrcode class. + * + * @since 3.0 + * @version 5.0 + */ + +#ifndef GRAPHIC_LITE_UI_QRCODE_H +#define GRAPHIC_LITE_UI_QRCODE_H + +#include "components/ui_image_view.h" + +namespace qrcodegen { +class QrCode; +} + +namespace OHOS { +/** + * @brief Provides functions related to quick response (QR) codes. + * + * @since 3.0 + * @version 5.0 + */ +class UIQrcode : public UIImageView { +public: + /** + * @brief A default constructor used to create a UIQrcode instance. + * + * @since 3.0 + * @version 5.0 + */ + UIQrcode(); + + /** + * @brief A destructor used to delete the UIQrcode instance. + * + * @since 3.0 + * @version 5.0 + */ + virtual ~UIQrcode(); + + /** + * @brief Sets the QR code information. + * + * @param val Indicates the pointer to the content used to generate the QR code. + * @param backgroundColor Indicates the background color of the QR code. It is white by default. + * @param qrColor Indicates the QR code color. It is black by default. + * @since 3.0 + * @version 5.0 + */ + void SetQrcodeInfo(const char* val, ColorType backgroundColor = Color::White(), ColorType qrColor = Color::Black()); + + /** + * @brief Obtains the view type. + * + * @return Returns the view type. For details, see {@link UIViewType}. + * @since 1.0 + * @version 1.0 + */ + UIViewType GetViewType() const override + { + return UI_QRCODE; + } + + /** + * @brief Sets the QR code width. + * + * @param width Indicates the width to set. + * @since 1.0 + * @version 1.0 + */ + void SetWidth(int16_t width) override; + + /** + * @brief Sets the QR code height. + * + * @param height Indicates the height to set. + * @since 1.0 + * @version 1.0 + */ + void SetHeight(int16_t height) override; + +private: + void ReMeasure() override; + void RefreshQrcode(); + void SetImageInfo(qrcodegen::QrCode& qrcode); + void GenerateQrCode(qrcodegen::QrCode& qrcode); + void FillQrCodeColor(qrcodegen::QrCode& qrcode); + void FillQrCodeBackgroundColor(); + void SetQrcodeVal(const char* val, uint32_t length); + void GetDestData(uint8_t* destData, int32_t outFilePixelPrescaler); + + static constexpr uint8_t QRCODE_FACTOR_NUM = 4; + ImageInfo imageInfo_; + int16_t width_; + bool needDraw_; + ColorType backgroundColor_; + ColorType qrColor_; + char* qrcodeVal_; +}; +} // namespace OHOS +#endif // GRAPHIC_LITE_UI_QRCODE_H diff --git a/interfaces/kits/components/ui_radio_button.h b/interfaces/kits/components/ui_radio_button.h new file mode 100755 index 0000000..0eb7934 --- /dev/null +++ b/interfaces/kits/components/ui_radio_button.h @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @addtogroup UI_Components + * @{ + * + * @brief Defines UI components such as buttons, texts, images, lists, and progress bars. + * + * @since 1.0 + * @version 1.0 + */ + +/** + * @file ui_radio_button.h + * + * @brief Defines the attributes and common functions of a radio button. + * + * @since 1.0 + * @version 1.0 + */ + +#ifndef GRAPHIC_LITE_UI_RADIO_BUTTON_H +#define GRAPHIC_LITE_UI_RADIO_BUTTON_H + +#include "components/ui_checkbox.h" + +namespace OHOS { +/** + * @brief Represents a radio button. + * + * Only one option can be selected with the radio button. + * + * @see UICheckBox + * @since 1.0 + * @version 1.0 + */ +class UIRadioButton : public UICheckBox { +public: + /** + * @brief A constructor used to create a UIRadioButton instance. + * + * @since 1.0 + * @version 1.0 + */ + UIRadioButton(); + + /** + * @fn UIRadioButton::UIRadioButton(const char* name); + * + * @brief Default constructor + */ + UIRadioButton(const char* name); + + /** + * @brief A destructor used to delete the UIRadioButton instance. + * + * @since 1.0 + * @version 1.0 + */ + virtual ~UIRadioButton() + { + if (name_ != nullptr) { + UIFree(name_); + name_ = nullptr; + } + } + + /** + * @brief Obtains the component type. + * + * @return Returns the component type, as defined in {@link UIViewType}. + * @since 1.0 + * @version 1.0 + */ + UIViewType GetViewType() const override + { + return UI_RADIO_BUTTON; + } + + bool OnPreDraw(Rect& invalidatedArea) const override + { + return false; + } + + void OnDraw(const Rect& invalidatedArea) override; + + /** + * @fn virtual void UIRadioButton::OnClickEvent(const ClickEvent& event) override; + * + * @brief Executes the click event action + * OnClickEvent will reverse the selected state of checkbox. + * Example: If the check box is selected, the checkbox status is changed to + * Unselected after the click action is taken. + * + * @param event The event that passed when OnClickEvent is invoked. + * @return Returns true if the event is consumed; returns false otherwise. + */ + bool OnClickEvent(const ClickEvent& event) override; + + /** + * @brief Sets the name for this radio button. + * + * @param name Indicates the name to set, which is a character string. + * @since 1.0 + * @version 1.0 + */ + void SetName(const char* name); + + /** + * @brief Obtains the name of this radio button. + * + * @return Returns the name of this radio button, which is a defined character string. Radio buttons sharing the + * same name are in the same batch from which only one can be selected. + * @since 1.0 + * @version 1.0 + */ + const char* GetName() const + { + return name_; + } + +private: + void FindRadioButtonAndChangeState(UIView* view); + static constexpr int16_t DEFAULT_HOT_WIDTH = 46; + static constexpr int16_t DEFAULT_HOT_HEIGHT = 46; + static constexpr int16_t DEFAULT_RADIUS_BIG = 11; + static constexpr int16_t DEFAULT_RADIUS_SMALL = 6; + static constexpr int16_t DEFAULT_LINE_WIDTH = 1; + + char* name_; + int16_t width_; + int16_t height_; + uint16_t radiusBig_; + uint16_t radiusSmall_; + int16_t lineWidth_; + + void CalculateSize(); +}; +} // namespace OHOS +#endif // GRAPHIC_LITE_UI_RADIO_BUTTON_H diff --git a/interfaces/kits/components/ui_repeat_button.h b/interfaces/kits/components/ui_repeat_button.h new file mode 100755 index 0000000..83e4e1a --- /dev/null +++ b/interfaces/kits/components/ui_repeat_button.h @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @addtogroup UI_Components + * @{ + * + * @brief Defines UI components such as buttons, texts, images, lists, and progress bars. + * + * @since 1.0 + * @version 1.0 + */ + +/** + * @file ui_repeate_button.h + * + * @brief Defines the attributes and common functions of a repeat button. + * + * If a repeat button is clicked and hold, the click event is triggered continuously. + * + * @since 1.0 + * @version 1.0 + */ + +#ifndef GRAPHIC_LITE_UI_REPEAT_BUTTON_H +#define GRAPHIC_LITE_UI_REPEAT_BUTTON_H + +#include "components/ui_button.h" + +namespace OHOS { +/** + * @brief Represents a repeat button. + * + * If a repeat button is clicked and hold, the click event is triggered continuously. + * + * @see UIButton + * @since 1.0 + * @version 1.0 */ +class UIRepeatButton : public UIButton { +public: + /** + * @brief A constructor used to create a UIRepeatButton instance. + * + * @since 1.0 + * @version 1.0 + */ + UIRepeatButton(); + + /** + * @brief A destructor used to delete the UIRepeatButton instance. + * + * @since 1.0 + * @version 1.0 + */ + virtual ~UIRepeatButton(); + + /** + * @brief Obtains the component type. + * + * @return Returns the component type, as defined in {@link UIViewType}. + * @since 1.0 + * @version 1.0 + */ + UIViewType GetViewType() const override + { + return UI_REPEAT_BUTTON; + } + + /** + * @brief Sets the interval between two consecutive click events. + * + * @param interval Indicates the interval to set. + + * @since 1.0 + * @version 1.0 + */ + virtual void SetInterval(uint16_t interval) + { + ticksInterval_ = interval; + } + + /** + * @brief Obtains the interval between two consecutive click events. + * + * @return Returns the interval between two consecutive click events. + * @since 1.0 + * @version 1.0 + */ + virtual uint16_t GetInterval() const + { + return ticksInterval_; + } + + /** + * @fn void UIRepeatButton::OnClickEvent(const ClickEvent& event) + * + * @brief The action of click event. + * + * @param [in] event click event. + * @return Returns true if the event is consumed; returns false otherwise. + */ + virtual bool OnClickEvent(const ClickEvent& event) override; + + /** + * @fn void UIRepeatButton::OnReleaseEvent(const ReleaseEvent& event) + * + * @brief The action of release event. + * + * @param [in] event release event. + */ + virtual bool OnReleaseEvent(const ReleaseEvent& event) override; + + /** + * @fn void UIRepeatButton::OnLongPressEvent(const ReleaseEvent& event) + * + * @brief The action of long press event. + * + * @param [in] event long press event. + */ + virtual bool OnLongPressEvent(const LongPressEvent& event) override; + + /** + * @fn void UIRepeatButton::HandleTickEvent() + * + * @brief handle the event of long pressing. + */ + void HandleTickEvent(); + +private: + uint16_t ticksInterval_; + ClickEvent event_; + bool longPressed_; + static UIRepeatButton* repeatButton_; + uint32_t timerRepeatID_; + + virtual void OnDraw(const Rect& invalidatedArea) override; + inline void SetEvent(const ClickEvent& event); +}; +} // namespace OHOS + +#endif // GRAPHIC_LITE_UI_REPEAT_BUTTON_H diff --git a/interfaces/kits/components/ui_scroll_view.h b/interfaces/kits/components/ui_scroll_view.h new file mode 100755 index 0000000..6785d6d --- /dev/null +++ b/interfaces/kits/components/ui_scroll_view.h @@ -0,0 +1,297 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @addtogroup UI_Components + * @{ + * + * @brief Defines UI components such as buttons, texts, images, lists, and progress bars. + * + * @since 1.0 + * @version 1.0 + */ + +/** + * @file ui_scroll_view.h + * + * @brief Declares a view group that allows its child views to be displayed as scroll events occur. + * + * @since 1.0 + * @version 1.0 + */ + +#ifndef GRAPHIC_LITE_UI_SCROLL_VIEW_H +#define GRAPHIC_LITE_UI_SCROLL_VIEW_H + +#include "animator/animator.h" +#include "components/ui_abstract_scroll.h" + +namespace OHOS { +/** + * @brief Supports horizontal or vertical scroll of child views. This class is inherited from {@link UIAbstractScroll}. + * + * Horizontal or vertical scroll occurs only when the width or height of the child view is greater than that of the + * UIScrollView. + * + * @since 1.0 + * @version 1.0 + */ +class UIScrollView : public UIAbstractScroll { +public: + /** + * @brief Represents a listener that contains a callback to be invoked upon scroll state changes. The state can + * either be {@link SCROLL_STATE_STOP} or {@link SCROLL_STATE_MOVE}. + * @since 1.0 + * @version 1.0 + */ + class OnScrollListener : public HeapBase { + public: + /** + * @brief A constructor used to create an OnScrollListener instance with the default scroll state + * {@link SCROLL_STATE_STOP}. + * @since 1.0 + * @version 1.0 + */ + OnScrollListener() : state_(SCROLL_STATE_STOP) {} + + /** + * @brief A destructor used to delete the OnScrollListener instance. + * @since 1.0 + * @version 1.0 + */ + virtual ~OnScrollListener() {} + + /** + * @brief Called when a scroll starts. + * + * @since 1.0 + * @version 1.0 + */ + virtual void OnScrollStart() {} + + /** + * @brief Called when a scroll ends. + * + * @since 1.0 + * @version 1.0 + */ + virtual void OnScrollEnd() {} + + /** + * @brief Obtains the scroll state of this view. + * + * @return Returns the scroll state, either {@link SCROLL_STATE_STOP} or {@link SCROLL_STATE_MOVE}. + * @since 1.0 + * @version 1.0 + */ + uint8_t GetScrollState() const + { + return state_; + } + + void SetScrollState(uint8_t state) + { + state_ = state; + } + + static constexpr uint8_t SCROLL_STATE_STOP = 0; + static constexpr uint8_t SCROLL_STATE_MOVE = 1; + + private: + uint8_t state_; + }; + + /** + * @brief A constructor used to create a UIScrollView instance, with both horizontal and vertical scrolls + * supported. + * + * @since 1.0 + * @version 1.0 + */ + UIScrollView(); + + /** + * @brief A destructor used to delete the UIScrollView instance. + * + * @since 1.0 + * @version 1.0 + */ + virtual ~UIScrollView() {} + + /** + * @brief Obtains the view type. + * @return Returns the view type, as defined in {@link UIViewType}. + * + * @since 1.0 + * @version 1.0 + */ + UIViewType GetViewType() const override + { + return UI_SCROLL_VIEW; + } + +#if ENABLE_ROTATE_INPUT + bool OnRotateEvent(const RotateEvent& event) override; +#endif + + void OnPostDraw(const Rect& invalidatedArea) override; + + bool OnDragEvent(const DragEvent& event) override; + + bool OnDragEndEvent(const DragEvent& event) override; + + bool OnPressEvent(const PressEvent& event) override; + + /** + * @brief Scrolls the content of this view. + * + * @param xDistance Indicates the offset distance by which the content is scrolled on the x-axis. + * @param yDistance Indicates the offset distance by which the content is scrolled on the y-axis. + * @since 1.0 + * @version 1.0 + */ + void ScrollBy(int16_t xDistance, int16_t yDistance); + + /** + * @brief Sets the width for this scroll bar. + * + * @param width Indicates the width to set. The default value is {@link DEFAULT_BAR_WIDTH}. + * @since 1.0 + * @version 1.0 + */ + void SetScrollbarWidth(uint8_t width) + { + scrollBarWidth_ = width; + minScrollBarLen_ = scrollBarWidth_ * MIN_BAR_MULTIPLE; + } + + /** + * @brief Sets whether a horizontal scroll is enabled. + * + * @param state Specifies whether a horizontal scroll is enabled. true indicates a horizontal scroll is + * enabled, and false indicates the opposite case. + * @since 1.0 + * @version 1.0 + */ + void SetHorizontalScrollState(bool state) + { + xScrollable_ = state; + } + + /** + * @brief Checks whether a horizontal scroll is enabled. + * + * @return Returns true if a horizontal scroll is enabled; returns false otherwise. + * @since 1.0 + * @version 1.0 + */ + bool GetHorizontalScrollState() const + { + return xScrollable_; + } + + /** + * @brief Sets whether a vertical scroll is enabled. + * + * @param state Specifies whether a vertical scroll is enabled. true indicates a vertical scroll is enabled, + * and false indicates the opposite case. + * @since 1.0 + * @version 1.0 + */ + void SetVerticalScrollState(bool state) + { + yScrollable_ = state; + } + + /** + * @brief Checks whether a vertical scroll is enabled. + * + * @return Returns true if a vertical scroll is enabled, returns false otherwise. + * @since 1.0 + * @version 1.0 + */ + bool GetVerticalScrollState() const + { + return yScrollable_; + } + + /** + * @brief Sets whether the horizontal scroll bar is visible. + * + * @param state Specifies whether the horizontal scroll bar is visible. true indicates the horizontal scroll + * bar is visible, and false indicates the opposite case. + * @since 1.0 + * @version 1.0 + */ + void SetXScrollBarVisible(bool state) + { + xSlider_.SetVisible(state); + } + + /** + * @brief Sets whether the vertical scroll bar is visible. + * + * @param state Specifies whether this vertical scroll bar is visible. true indicates the horizontal scroll + * bar is visible, and false indicates the opposite case. + * @since 1.0 + * @version 1.0 + */ + void SetYScrollBarVisible(bool state) + { + ySlider_.SetVisible(state); + } + + /** + * @brief Registers a listener that contains a callback to be invoked upon scroll state changes. + * + * @param scrollListener Indicates the listener to register. For details, see {@link OnScrollListener}. + * @since 1.0 + * @version 1.0 + */ + void RegisterScrollListener(OnScrollListener* scrollListener) + { + scrollListener_ = scrollListener; + } + + void RefreshScrollBar(); + +protected: + void StopAnimator() override; + bool DragXInner(int16_t distance) override; + bool DragYInner(int16_t distance) override; + +private: + void Drag(const DragEvent& event); + void RefreshScrollBarPosition(); + bool MoveOffset(int16_t offsetX, int16_t offsetY); + int16_t GetXScrollOffset(const Rect& childRect); + int16_t GetYScrollOffset(const Rect& childRect); + void OnChildChanged() override; + void CalculateReboundDistance(int16_t& dragDistanceX, int16_t& dragDistanceY) override; + static constexpr uint8_t DEFAULT_BAR_WIDTH = 5; + static constexpr uint8_t MIN_BAR_MULTIPLE = 2; + static constexpr uint8_t DEFAULT_MIN_BAR_LEN = DEFAULT_BAR_WIDTH * MIN_BAR_MULTIPLE; + UIView xSlider_; + UIView ySlider_; + Point xSliderPos_; + Point ySliderPos_; + int16_t scrollBarWidth_; + bool xScrollable_; + bool yScrollable_; + int16_t minScrollBarLen_; + OnScrollListener* scrollListener_; +}; +} // namespace OHOS +#endif // GRAPHIC_LITE_UI_SCROLL_VIEW_H diff --git a/interfaces/kits/components/ui_slider.h b/interfaces/kits/components/ui_slider.h new file mode 100755 index 0000000..0261dd1 --- /dev/null +++ b/interfaces/kits/components/ui_slider.h @@ -0,0 +1,311 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @addtogroup UI_Components + * @{ + * + * @brief Defines UI components such as buttons, texts, images, lists, and progress bars. + * + * @since 1.0 + * @version 1.0 + */ + +/** + * @file ui_slider.h + * + * @brief Defines the attributes and common functions of a slider. + * + * @since 1.0 + * @version 1.0 + */ + +#ifndef GRAPHIC_LITE_UI_SLIDER_H +#define GRAPHIC_LITE_UI_SLIDER_H + +#include "common/image.h" +#include "components/ui_box_progress.h" + +namespace OHOS { +/** + * @brief Represents a slider. + * + * Users can drag or click the knob to adjust the progress of an event. + * + * @see UIBoxProgress + * @since 1.0 + * @version 1.0 + */ +class UISlider : public UIBoxProgress { +public: + /** + * @brief A constructor used to create a UISlider instance. + * + * @since 1.0 + * @version 1.0 + */ + UISlider(); + + /** + * @brief A destructor used to delete the UISlider instance. + * + * @since 1.0 + * @version 1.0 + */ + virtual ~UISlider(); + + /** + * @brief Obtains the view type. + * + * @return Returns the view type, as defined in {@link UIViewType}. + * @since 1.0 + * @version 1.0 + */ + UIViewType GetViewType() const override + { + return UI_SLIDER; + } + + /** + * @brief Sets the width for this knob. + * + * The width of this knob is the same as its height. \n + * By default, the width of this knob is the same as the height of the horizontal slider + * or the width of the vertical slider. \n + * + * @param width Indicates the knob width to set. + * @see GetKnobWidth + * @since 1.0 + * @version 1.0 + */ + void SetKnobWidth(int16_t width) + { + knobWidth_ = width; + knobWidthSetFlag_ = true; + } + + /** + * @brief Obtains the knob width. + * + * @return Returns the knob width. + * @see SetKnobWidth + * @since 1.0 + * @version 1.0 + */ + int16_t GetKnobWidth(); + +#if ENABLE_ROTATE_INPUT + /** + * @brief תϵ + * @return תϵ + * @since 5.0 + * @version 3.0 + */ + int8_t GetRotateFactor() + { + return rotateFactor_; + } + + /** + * @brief תϵ + * @param factor תϵ + * @since 5.0 + * @version 3.0 + */ + void SetRotateFactor(int8_t factor) + { + rotateFactor_ = factor; + } + + bool OnRotateEvent(const RotateEvent& event) override; +#endif + + /** + * @brief Sets the images as pixel maps for this slider, including the background, foreground, and knob images. + * + * @param backgroundImage Indicates the background image to set. + * @param foregroundImage Indicates the foreground image to set. + * @param knobImage Indicates the knob image to set. + * @since 1.0 + * @version 1.0 + */ + void SetImage(const ImageInfo* backgroundImage, const ImageInfo* foregroundImage, const ImageInfo* knobImage); + + /** + * @brief Sets the images for this slider, including the background, foreground, and knob images. + * + * @param backgroundImage Indicates the background image to set. + * @param foregroundImage Indicates the foreground image to set. + * @param knobImage Indicates the knob image to set. + * @since 1.0 + * @version 1.0 + */ + void SetImage(const char* backgroundImage, const char* foregroundImage, const char* knobImage); + + /** + * @brief Sets the colors for this slider, including the background, foreground, and knob colors. + * + * @param backgroundColor Indicates the background color to set. + * @param foregroundColor Indicates the foreground color to set. + * @param knobColor Indicates the knob color to set. + * @since 1.0 + * @version 1.0 + */ + void SetSliderColor(const ColorType backgroundColor, const ColorType foregroundColor, const ColorType knobColor) + { + SetBackgroundStyle(STYLE_BACKGROUND_COLOR, backgroundColor.full); + SetForegroundStyle(STYLE_BACKGROUND_COLOR, foregroundColor.full); + SetKnobStyle(STYLE_BACKGROUND_COLOR, knobColor.full); + } + + /** + * @brief Sets the corner radiuses for this slider, including the background, foreground, and knob corner radiuses. + * + * @param backgroundRadius Indicates the background corner radius to set. + * @param foregroundRadius Indicates the foreground corner radius to set. + * @param knobRadius Indicates the knob corner radius to set. + * @since 1.0 + * @version 1.0 + */ + void SetSliderRadius(int16_t backgroundRadius, int16_t foregroundRadius, int16_t knobRadius) + { + SetBackgroundStyle(STYLE_BORDER_RADIUS, backgroundRadius); + SetForegroundStyle(STYLE_BORDER_RADIUS, foregroundRadius); + SetKnobStyle(STYLE_BORDER_RADIUS, knobRadius); + } + + /** + * @brief Sets the knob style. + * + * @param style Indicates the knob style to set. For details, see {@link Style}. + * @see GetKnobStyle + * @since 1.0 + * @version 1.0 + */ + void SetKnobStyle(const Style& style); + + /** + * @brief Sets a knob style. + * + * @param key Indicates the key of the style to set. + * @param value Indicates the value matching the key. + * @since 1.0 + * @version 1.0 + */ + void SetKnobStyle(uint8_t key, int64_t value); + + /** + * @brief Obtains the knob style. + * + * @return Returns the knob style. + * @since 1.0 + * @version 1.0 + */ + const Style& GetKnobStyle() const; + + /** + * @brief Obtains the value of a knob style. + * + * @param key Indicates the key of the style. + * @return Returns the value of the style. + * @since 1.0 + * @version 1.0 + */ + int64_t GetKnobStyle(uint8_t key) const; + + bool OnClickEvent(const ClickEvent& event) override; + + bool OnDragEvent(const DragEvent& event) override; + + bool OnDragEndEvent(const DragEvent& event) override; + + bool OnPreDraw(Rect& invalidatedArea) const override + { + return false; + } + + void OnDraw(const Rect& invalidatedArea) override; + + /** + * @brief Represents the listener for a slider change. + * + * This is an inner class of UISlider used to listen for slider events and invoke the callback function. + * + * @see UISlider + * @since 1.0 + * @version 1.0 + */ + class UISliderEventListener : public HeapBase { + public: + /** + * @brief A destructor used to delete the UISliderEventListener instance. + * + * @since 1.0 + * @version 1.0 + */ + virtual ~UISliderEventListener() {} + /** + * @brief Called when the slider is dragged or clicked. This is a virtual function, which needs your + * implementation. + * + * @param value Indicates the current value of the slider. + * @since 1.0 + * @version 1.0 + */ + virtual void OnChange(int32_t value) {} + /** + * @brief Called when the slider is released. This is a virtual function, which needs your implementation. + * + * @param value Indicates the current value of the slider. + * @since 1.0 + * @version 1.0 + */ + virtual void OnRelease(int32_t value) {} + }; + + /** + * @brief Sets the listener for a slider change. + * + * When a user drags or clicks the slider, listening is triggered and the OnChange callback is invoked. + * When a user releases the slider, the OnRelease callback is invoked. + * + * @param listener Indicates the listener to set. For details, see {@link UISliderEventListener}. + * @since 1.0 + * @version 1.0 + */ + void SetSliderEventListener(UISliderEventListener* listener) + { + listener_ = listener; + } + +protected: + bool InitImage() override; + +private: + void DrawKnob(const Rect& invalidatedArea, const Rect& foregroundRect); + int32_t CalculateCurrentValue(int16_t length, int16_t totalLength); + int32_t UpdateCurrentValue(const Point& knobPosition); +#if ENABLE_ROTATE_INPUT + int8_t rotateFactor_ = 1; +#endif + int16_t knobWidth_; + bool knobWidthSetFlag_; + bool knobStyleAllocFlag_; + Style* knobStyle_; + Image* knobImage_; + UISliderEventListener* listener_; +}; // class UISlider +} // namespace OHOS +#endif // GRAPHIC_LITE_UI_SLIDER_H diff --git a/interfaces/kits/components/ui_surface_view.h b/interfaces/kits/components/ui_surface_view.h new file mode 100755 index 0000000..9a49d29 --- /dev/null +++ b/interfaces/kits/components/ui_surface_view.h @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GRAPHIC_LITE_UI_SURFACE_VIEW_H +#define GRAPHIC_LITE_UI_SURFACE_VIEW_H + +/** + * @addtogroup UI_Components + * @{ + * + * @brief Defines UI components such as buttons, texts, images, lists, and progress bars. + * + * @since 1.0 + * @version 1.0 + */ + +/** + * @file ui_surface_view.h + * + * @brief Declares the surface view that interacts with the multimedia to achieve camera preview and video playback. + * + * @since 1.0 + * @version 1.0 + */ + +#include "components/ui_view.h" +#include "surface.h" + +#include + +namespace OHOS { +/** + * @brief Represents a surface view that interacts with the multimedia to achieve camera preview and video playback. + * + * @since 1.0 + * @version 1.0 + */ +class UISurfaceView : public UIView { +public: + /** + * @brief A constructor used to create a UISurfaceView instance. + * + * @since 1.0 + * @version 1.0 + */ + UISurfaceView(); + + /** + * @brief A destructor used to delete the UISurfaceView instance. + * + * @since 1.0 + * @version 1.0 + */ + ~UISurfaceView(); + + /** + * @brief Obtains the surface, which should be used together with the camera and video modules. + * + * @return Returns the surface. + * @since 1.0 + * @version 1.0 + */ + Surface* GetSurface() const; + + /** + * @brief Sets the position for this view. + * + * @param x Indicates the x-coordinate to set. + * @param y Indicates the y-coordinate to set. + * @since 1.0 + * @version 1.0 + */ + void SetPosition(int16_t x, int16_t y) override; + + /** + * @brief Sets the position and size for this view. + * + * @param x Indicates the x-coordinate to set. + * @param y Indicates the y-coordinate to set. + * @param width Indicates the width to set. + * @param height Indicates the height to set. + * @since 1.0 + * @version 1.0 + */ + void SetPosition(int16_t x, int16_t y, int16_t width, int16_t height) override; + + /** + * @brief Adjusts the size of this view. + * + * @param width Indicates the new width. + * @param height Indicates the new height. + * @since 1.0 + * @version 1.0 + */ + void Resize(int16_t width, int16_t height) override; + + /** + * @brief Sets the x-coordinate for this view. + * + * @param x Indicates the x-coordinate to set. + * @since 1.0 + * @version 1.0 + */ + void SetX(int16_t x) override; + + /** + * @brief Sets the y-coordinate for this view. + * + * @param y Indicates the y-coordinate to set. + * @since 1.0 + * @version 1.0 + */ + void SetY(int16_t y) override; + + /** + * @brief Sets the width for this view. + * + * @param width Indicates the width to set. + * @since 1.0 + * @version 1.0 + */ + void SetWidth(int16_t width) override; + + /** + * @brief Sets the height for this view. + * + * @param height Indicates the height to set. + * @since 1.0 + * @version 1.0 + */ + void SetHeight(int16_t height) override; + + /** + * @brief Called before this view is drawn. This function is used to check whether the parent view of this view + * needs to be redrawn so that the drawing process is optimized. + * + * @param invalidatedArea Indicates the area to draw. + * @return Returns true if the parent view needs to be redrawn; returns false otherwise. + * @since 1.0 + * @version 1.0 + */ + bool OnPreDraw(Rect& invalidatedArea) const override; + + /** + * @brief Called when this view is drawn. + * + * @param invalidatedArea Indicates the area to draw. + * @since 1.0 + * @version 1.0 + */ + void OnDraw(const Rect& invalidatedArea) override; + +private: + void Draw(const Rect& invalidatedArea); + + Surface* surface_; + const std::string REGION_POSITION_X = "region_position_x"; + const std::string REGION_POSITION_Y = "region_position_y"; + const std::string REGION_WIDTH = "region_width"; + const std::string REGION_HEIGHT = "region_height"; + const uint8_t DEFAULT_QUEUE_SIZE = 2; +}; +} // namespace OHOS +#endif // GRAPHIC_LITE_UI_SURFACE_VIEW_H diff --git a/interfaces/kits/components/ui_swipe_view.h b/interfaces/kits/components/ui_swipe_view.h new file mode 100755 index 0000000..990b3b6 --- /dev/null +++ b/interfaces/kits/components/ui_swipe_view.h @@ -0,0 +1,368 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @addtogroup UI_Components + * @{ + * + * @brief Defines UI components such as buttons, texts, images, lists, and progress bars. + * + * @since 1.0 + * @version 1.0 + */ + +/** + * @file ui_swipe_view.h + * + * @brief Defines the attributes and common functions of a swipe view. + * + * Each swipe view consists of multiple child views, which can be navigated through swiping. The child views can be + * either horizontal or vertical. + * + * @since 1.0 + * @version 1.0 + */ + +#ifndef GRAPHIC_LITE_UI_SWIPE_VIEW_H +#define GRAPHIC_LITE_UI_SWIPE_VIEW_H + +#include "animator/animator.h" +#include "components/ui_abstract_scroll.h" + +namespace OHOS { +/** + * @brief Represents a swipe view. + * + * Each swipe view consists of multiple child views, which can be navigated through swiping. The child views can be + * either horizontal or vertical. + * + * @see UIAbstractScroll + * @since 1.0 + * @version 1.0 + */ +class UISwipeView : public UIAbstractScroll { +public: + /** + * @brief Represents a listener for changes of the swipe view. + * + * This is an inner class of UISwipeView. It contains a callback function to be invoked when the swipe view + * state changes. + * + * @since 1.0 + * @version 1.0 + */ + class OnSwipeListener : public HeapBase { + public: + virtual void OnSwipe(UISwipeView& view) = 0; + virtual ~OnSwipeListener() {} + }; + + enum AlignMode : uint8_t { + ALIGN_LEFT, + ALIGN_CENTER, + ALIGN_RIGHT + }; + + /** + * @brief A constructor used to create a UISwipeView instance. + * + * @since 1.0 + * @version 1.0 + */ + UISwipeView(uint8_t direction = HORIZONTAL); + + /** + * @brief A destructor used to delete the UISwipeView instance. + * + * @since 1.0 + * @version 1.0 + */ + virtual ~UISwipeView(); + + /** + * @brief Obtains the component type. + * + * @return Returns the component type, as defined in {@link UIViewType}. + * @since 1.0 + * @version 1.0 + */ + UIViewType GetViewType() const override + { + return UI_SWIPE_VIEW; + } + + /** + * @brief Sets the dragging direction. + * + * @param direction Indicates the dragging direction, either {@link HORIZONTAL} or {@link VERTICAL}. + * @since 1.0 + * @version 1.0 + */ + void SetDirection(uint8_t direction) + { + direction_ = direction; + } + + /** + * @brief Obtains the dragging direction. + * + * @return Returns the dragging direction. + * @since 1.0 + * @version 1.0 + */ + uint8_t GetDirection() const + { + return direction_; + } + + /** + * @brief Adds a view. + * + * @param view Indicates the view to add. + * @since 1.0 + * @version 1.0 + */ + void Add(UIView* view) override; + + /** + * @brief Inserts a view. + * + * @param prevView Indicates the previous view. + * @param insertView Indicates the view to insert. + * @since 1.0 + * @version 1.0 + */ + void Insert(UIView* prevView, UIView* insertView) override; + + /** + * @brief Deletes a view. + * + * @param view Indicates the view to delete. + * @since 1.0 + * @version 1.0 + */ + virtual void Remove(UIView* view) override; + + /** + * @brief Sets the index for the current tab. + * + * @param index Indicates the index of a view. + * @param needAnimator Specifies whether a flip animation is needed. false (default value) indicates a flip + * animation is not needed, and true indicates the opposite case. + * @since 1.0 + * @version 1.0 + */ + void SetCurrentPage(uint16_t index, bool needAnimator = false); + + /** + * @brief Obtains the current tab index. + * + * @return Returns the current tab index. + * @since 1.0 + * @version 1.0 + */ + uint16_t GetCurrentPage() const + { + return curIndex_; + } + + /** + * @brief Obtains the current view. + * + * @return Returns the current view. + * @since 1.0 + * @version 1.0 + */ + UIView* GetCurrentView() const + { + return curView_; + } + + /** + * @brief Sets a blank size, as defined in {@link DEFAULT_BLANK_SIZE} + * + * @param size Indicates the blank size to set. + * @since 1.0 + * @version 1.0 + */ + void SetBlankSize(uint16_t size) + { + blankSize_ = size; + } + + /** + * @fn void OnDragEvent(const DragEvent& event) override + * + * @brief revice drag event, Switch to specified view when drag + * + * @param event The drag event + */ + bool OnDragEvent(const DragEvent& event) override; + + bool OnDragEndEvent(const DragEvent& event) override; + +#if ENABLE_ROTATE_INPUT + bool OnRotateEvent(const RotateEvent& event) override; +#endif + + /** + * @brief Sets the time for the page being animated. The page will go beyond the blank during this time. + * + * @param time Indicates the time of the page being animated. + * @since 1.0 + * @version 1.0 + */ + void SetAnimatorTime(uint16_t time); + + /** + * @brief Sets whether the swipe view supports a cycle swipe. + * + * @param loop Indicates the cycle swipe flag. true indicates the cycle swipe is supported, and false + * indicates the opposite case. + * @since 1.0 + * @version 1.0 + */ + void SetLoopState(bool loop) + { + loop_ = loop; + } + + /** + * @brief Obtains a view based on its index. + * + * @param Indicates the index of a view. + * @return Returns the view. + * @since 1.0 + * @version 1.0 + */ + UIView* GetViewByIndex(uint16_t index) const; + + /** + * @brief Obtains the listener set for swipe events. + * + * @return Returns the swipe event listener. + * @since 1.0 + * @version 1.0 + */ + OnSwipeListener*& GetOnSwipeListener() + { + return swipeListener_; + } + + /** + * @brief Sets the listener that contains a callback to be invoked upon a swipe event. + * + * @param onSwipeListener Indicates the listener to set. + * @since 1.0 + * @version 1.0 + */ + void SetOnSwipeListener(OnSwipeListener* onSwipeListener) + { + swipeListener_ = onSwipeListener; + } + + /** + * @brief Sets the alignment mode for child components of UISwipeView. + * + * @param alignMode Indicates the alignment mode to set, as enumerated in {@link AlignMode}. + * The default value is ALIGN_CENTER. + * @since 1.0 + * @version 1.0 + */ + void SetAlignMode(AlignMode alignMode = ALIGN_CENTER) + { + alignMode_ = alignMode; + } + + /** + * @brief Obtains the alignment mode of child components of UISwipeView. + * + * @return Returns the alignment mode. For details, see {@link AlignMode}. + * @since 1.0 + * @version 1.0 + */ + AlignMode GetAlignMode() + { + return alignMode_; + } + + /** + * @brief Indicates the horizontal direction. + * + * @since 1.0 + * @version 1.0 + */ + static constexpr uint8_t HORIZONTAL = 0; + + /** + * @brief Indicates the vertical direction. + * + * @since 1.0 + * @version 1.0 + */ + static constexpr uint8_t VERTICAL = 1; + +protected: + bool DragXInner(int16_t distance) override; + bool DragYInner(int16_t distance) override; + void SortChild(); + void StopAnimator() override; + virtual void SwitchToPage(int16_t dst, bool needAnimator = true); + void MoveChildByOffset(int16_t xOffset, int16_t yOffset) override; + + /** + * @brief Indicates that the animation duration is 12 ticks. + * + * @since 1.0 + * @version 1.0 + */ + constexpr static uint16_t ANIMATOR_TIME = 12; + + /** + * @brief Indicates the maximum distance of an invalid dragging. Dragging is not triggered if the distance is less + * than this value. + * + * @since 1.0 + * @version 1.0 + */ + constexpr static uint16_t STOP_DISTANCE = 5; + + /** + * @brief Indicates the maximum distance between the first and the last tab when the current view is not in a cycle + * swipe mode. The page can be rebound after the setting. + * + * @since 1.0 + * @version 1.0 + */ + constexpr static uint16_t DEFAULT_BLANK_SIZE = 30; + uint16_t tickTime_; + OnSwipeListener* swipeListener_; + uint16_t curIndex_; + uint16_t blankSize_; + UIView* curView_; + AlignMode alignMode_ = ALIGN_CENTER; + bool loop_; + +private: + void RefreshCurrentView(int16_t distance); + void RefreshCurrentViewInner(int16_t distance, int16_t (UIView::*pfnGetXOrY)() const, + int16_t(UIView::*pfnGetWidthOrHeight)()); + bool isNeedLoop(); + void MoveFirstChildToLast(); + void MoveLastChildToFirst(); + void CalculateInvalidate(); +}; +} // namespace OHOS +#endif // GRAPHIC_LITE_UI_SWIPE_VIEW_H diff --git a/interfaces/kits/components/ui_texture_mapper.h b/interfaces/kits/components/ui_texture_mapper.h new file mode 100755 index 0000000..460aa5f --- /dev/null +++ b/interfaces/kits/components/ui_texture_mapper.h @@ -0,0 +1,291 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @addtogroup UI_Components + * @{ + * + * @brief Defines UI components such as buttons, texts, images, lists, and progress bars. + * + * @since 1.0 + * @version 1.0 + */ + +/** + * @file ui_texture_mapper.h + * + * @brief Defines the attributes and functions of a texture mapper. + * + * @since 1.0 + * @version 1.0 + */ + +#ifndef GRAPHIC_LITE_UI_TEXTURE_MAPPER_H +#define GRAPHIC_LITE_UI_TEXTURE_MAPPER_H + +#include "components/ui_image_view.h" +#include "animator/animator.h" +#include "animator/easing_equation.h" + +namespace OHOS { +/** + * @brief Rotates and scales images. + * + * @see UIImageView + * @since 1.0 + * @version 1.0 + */ +class UITextureMapper : public UIImageView { +public: + /** + * @brief A constructor used to create a UITextureMapper instance. + * + * @since 1.0 + * @version 1.0 + */ + UITextureMapper(); + + /** + * @brief A destructor used to delete the UITextureMapper instance. + * + * @since 1.0 + * @version 1.0 + */ + virtual ~UITextureMapper(); + + /** + * @brief Obtains the view type. + * + * @return Returns the view type, as defined in {@link UIViewType}. + * @since 1.0 + * @version 1.0 + */ + UIViewType GetViewType() const override + { + return UI_TEXTURE_MAPPER; + } + + /** + * @brief Starts this animator. + * + * @since 1.0 + * @version 1.0 + */ + void Start(); + + /** + * @brief Sets the start angle of rotation for this image. + * + * By default, the image starts to rotate from the current angle of this stopped animator. + * + * @param start Indicates the start angle to set. The default value is 0. + * @since 1.0 + * @version 1.0 + */ + void SetRotateStart(int16_t start) + { + rotateCur_ = start; + } + + /** + * @brief Sets the end angle of rotation for this image. + * + * @param end Indicates the end angle of rotation to set. The default value is 0. + * @since 1.0 + * @version 1.0 + */ + void SetRotateEnd(int16_t end) + { + rotateEnd_ = end; + } + + /** + * @brief Sets the start ratio for scaling this image. + * + * By default, the image starts to scale from the current ratio of this stopped animator. \n + * The X and Y directions are scaled at the same ratio. Single-direction scaling is not supported. \n + * + * @param start Indicates the start ratio to set. The default value is 1. + * @since 1.0 + * @version 1.0 + */ + void SetScaleStart(float start) + { + scaleCur_ = static_cast(FloatToInt64(start)); + } + + /** + * @brief Sets the end ratio for scaling this image. + * + * @param end Indicates the end ratio to set. The default value is 1. + * @since 1.0 + * @version 1.0 + */ + void SetScaleEnd(float end) + { + scaleEnd_ = static_cast(FloatToInt64(end)); + } + + /** + * @brief Sets the duration for this animator, in milliseconds. + * + * The durations of image scaling and rotation are the same and cannot be set separately. + * + * @param durationTime Indicates the duration to set. + * @since 1.0 + * @version 1.0 + */ + void SetDurationTime(uint16_t durationTime) + { + animator_.SetTime(durationTime + delayTime_); + } + + /** + * @brief Sets the delay time for this animator, in milliseconds. + * + * The delays of image scaling and rotation are the same and cannot be set separately. + * + * @param delayTime Indicates the delay time to set. + * @since 1.0 + * @version 1.0 + */ + void SetDelayTime(uint16_t delayTime) + { + animator_.SetTime(animator_.GetTime() - delayTime_ + delayTime); + delayTime_ = delayTime; + } + + /** + * @brief Sets the easing function for this animator. + * + * The easing functions for image scaling and rotation are the same and cannot be set separately. + * + * @param easingFunc Indicates the easing function to set. The animation moves at a constant velocity by default. + * For details, see {@link EasingEquation}. + * @since 1.0 + * @version 1.0 + */ + void SetEasingFunc(EasingFunc easingFunc) + { + easingFunc_ = easingFunc; + } + + /** + * @brief Cancels this animator. + * + * After being cancelled, the animator will stop in the current playback state. + * + * @since 1.0 + * @version 1.0 + */ + void Cancel(); + + /** + * @brief Displays the original image. + * + * The configured parameters are reserved and will not be reset. + * + * @since 1.0 + * @version 1.0 + */ + void Reset(); + + /** + * @brief Sets the coordinates of the rotation and scaling pivots for this image. + * + * The coordinates represent the position relative to this image. For example, setting the x-coordinate and + * y-coordinate of the rotation or scaling center to the half of the image width and height respectively + * means the rotation or scaling is going to be performed around the center of this image. + * + * @param x Indicates the x-coordinate to set. + * @param y Indicates the y-coordinate to set. + * @since 1.0 + * @version 1.0 + */ + void SetPivot(float x, float y) + { + pivot_.x_ = x; + pivot_.y_ = y; + } + + /** + * @brief Represents a listener that contains a callback to be invoked when this animator stops. + * + * @since 1.0 + * @version 1.0 + */ + class AnimatorStopListener : public HeapBase { + public: + /** + * @brief A destructor used to delete an AnimatorStopListener instance. + * + * @since 1.0 + * @version 1.0 + */ + virtual ~AnimatorStopListener() {} + + /** + * @brief Called when this animator stops. This is a pure virtual function, which needs + * your inheritance and implementation. + * + * @param view Indicates the instance of this view. + * @since 1.0 + * @version 1.0 + */ + virtual void OnAnimatorStop(UIView& view) = 0; + }; + + /** + * @brief Sets the listener for the stop of this animator. + * + * @param listener Indicates the listener to set. For details, see {@link AnimatorStopListener}. + * @since 1.0 + * @version 1.0 + */ + void SetAnimatorStopListener(AnimatorStopListener* listener) + { + listener_ = listener; + } + +private: + class TextureMapperAnimatorCallback : public AnimatorCallback { + public: + virtual ~TextureMapperAnimatorCallback() {} + + void Callback(UIView* view) override; + + void OnStop(UIView& view) override; + }; + + static constexpr int16_t SCALE_CONVERTION = 256; + + void Callback(); + + TextureMapperAnimatorCallback animatorCallback_; + Animator animator_; + AnimatorStopListener* listener_; + + Vector2 pivot_; + int16_t rotateCur_; + int16_t rotateStart_; + int16_t rotateEnd_; + int16_t scaleCur_; + int16_t scaleStart_; + int16_t scaleEnd_; + uint16_t delayTime_; + EasingFunc easingFunc_; +}; +} // namespace OHOS +#endif // GRAPHIC_LITE_UI_TEXTURE_MAPPER_H diff --git a/interfaces/kits/components/ui_time_picker.h b/interfaces/kits/components/ui_time_picker.h new file mode 100755 index 0000000..91cf392 --- /dev/null +++ b/interfaces/kits/components/ui_time_picker.h @@ -0,0 +1,334 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @addtogroup UI_Components + * @{ + * + * @brief Defines UI components such as buttons, texts, images, lists, and progress bars. + * + * @since 1.0 + * @version 1.0 + */ + +/** + * @file ui_time_picker.h + * + * @brief Defines the attributes and functions of the UITimePicker class. + * + * @since 1.0 + * @version 1.0 + */ + +#ifndef GRAPHIC_LITE_UI_TIME_PICKER_H +#define GRAPHIC_LITE_UI_TIME_PICKER_H + +#include "components/ui_picker.h" + +namespace OHOS { +/** + * @brief Defines the time picker. The time is in the format of "hour:minute" or "hour:minute:second". + * The selected time is highlighted. + * + * @since 1.0 + * @version 1.0 + */ +class UITimePicker : public UIViewGroup { +public: +#if ENABLE_ROTATE_INPUT + static constexpr const char* HOUR_LIST_NAME = "hour"; + static constexpr const char* MIN_LIST_NAME = "minute"; + static constexpr const char* SEC_LIST_NAME = "second"; +#endif + /** + * @brief A constructor used to create a UITimePicker instance. + * + * @since 1.0 + * @version 1.0 + */ + UITimePicker(); + + /** + * @brief A destructor used to delete the UITimePicker instance. + * + * @since 1.0 + * @version 1.0 + */ + virtual ~UITimePicker(); + + /** + * @brief Obtains the view type. + * + * @return Returns the view type. For details, see {@link UIViewType}. + * @since 1.0 + * @version 1.0 + */ + UIViewType GetViewType() const override + { + return UI_TIME_PICKER; + } + + /** + * @brief Sets the time currently selected in the time picker. + * + * @param value Indicates the pointer to the selected time, which is in the format of "hour:minute:second", + * for example, 10:12:50. The second is optional and depends on the setting of {@link EnableSecond}. + * The time must be a valid value. For example, the hour value must range from 0 to 23. + * @return Returns true if the setting is successful; returns false otherwise. + * @since 1.0 + * @version 1.0 + */ + bool SetSelected(const char* value); + + /** + * @brief Obtains the time currently selected in the time picker. + * + * @return Returns a string representing the selected time. The time is in the format of "hour:minute:second", + * for example, 10:12:50. The second is optional and depends on the setting of {@link EnableSecond}. + * @since 1.0 + * @version 1.0 + */ + const char* GetSelectValue() + { + return selectedValue_; + } + + /** + * @brief Obtains the hour currently selected in the time picker. + * + * @return Returns a string representing the selected hour, for example, 10. + * @since 1.0 + * @version 1.0 + */ + const char* GetSelectHour() const + { + return selectedHour_; + } + + /** + * @brief Obtains the minute currently selected in the time picker. + * + * @return Returns a string representing the selected minute, for example, 12. + * @since 1.0 + * @version 1.0 + */ + const char* GetSelectMinute() const + { + return selectedMinute_; + } + + /** + * @brief Obtains the second currently selected in the time picker. + * + * @return Returns a string representing the selected second, for example, 50. + * @since 1.0 + * @version 1.0 + */ + const char* GetSelectSecond() const + { + return selectedSecond_; + } + + /** + * @brief Sets the height of each item in the time picker. + * + * @param height Indicates the height of each item. + * @since 1.0 + * @version 1.0 + */ + void SetItemHeight(int16_t height); + + /** + * @brief Sets whether to enable seconds in the time picker. + * + * @param state Specifies whether to enable seconds in the time picker. Value true means to enable seconds, + * and value false means to disable seconds. The default value is false. + * @since 1.0 + * @version 1.0 + */ + void EnableSecond(bool state); + + /** + * @brief Sets the text format in the time picker, including the font ID and color. + * + * @param backgroundFontId Indicates the font ID of the background text. + * @param highlightFontId Indicates the font ID of the highlighted text. + * @param backgroundColor Indicates the color of the background text. + * @param highlightColor Indicates the color of the highlighted text. + * @since 1.0 + * @version 1.0 + */ + void SetTextStyle(uint8_t backgroundFontId, + uint8_t highlightFontId, + ColorType backgroundColor, + ColorType highlightColor); + + /** + * @brief Sets the text color in the time picker. + * + * @param backgroundColor Indicates the color of the background text. + * @param highlightColor Indicates the color of the highlighted text. + * @since 1.0 + * @version 1.0 + */ + void SetTextColor(ColorType backgroundColor, ColorType highlightColor); + + /** + * @brief Sets the font name and size for the background text. + * + * @param name Indicates the pointer to the font name to set. + * @param size Indicates the font size to set. + * @since 1.0 + * @version 1.0 + */ + void SetBackgroundFont(const char* name, uint8_t size); + + /** + * @brief Sets the font name and size for the highlighted text. + * + * @param name Indicates the pointer to the font name to set. + * @param size Indicates the font size to set. + * @since 1.0 + * @version 1.0 + */ + void SetHighlightFont(const char* name, uint8_t size); + + /** + * @brief Sets the width for the time picker. + * + * @param width Indicates the width to set. + * @since 1.0 + * @version 1.0 + */ + void SetWidth(int16_t width) override; + + /** + * @brief Sets the height for the time picker. + * + * @param height Indicates the height to set. + * @since 1.0 + * @version 1.0 + */ + void SetHeight(int16_t height) override; + + /** + * @brief Defines the listener used by the time picker. This listener is triggered when an item is selected + * after sliding stops. + * + * @since 1.0 + * @version 1.0 + */ + class SelectedListener : public HeapBase { + public: + /** + * @brief A constructor used to create a SelectedListener instance. + * + * @since 1.0 + * @version 1.0 + */ + SelectedListener() {} + + /** + * @brief A destructor used to delete the SelectedListener instance. + * + * @since 1.0 + * @version 1.0 + */ + ~SelectedListener() {} + + /** + * @brief Called when an item is selected after sliding stops. This function is implemented by applications. + * + * @param picker Indicates the time picker instance. + * @since 1.0 + * @version 1.0 + */ + virtual void OnTimePickerStoped(UITimePicker& picker) {} + }; + + /** + * @brief Registers a listener for a selected event. + * + * @param timePickerListener Indicates the listener to register. For details, see {@link SelectedListener}. + * + * @since 1.0 + * @version 1.0 + */ + void RegisterSelectedListener(SelectedListener* timePickerListener) + { + timePickerListener_ = timePickerListener; + } + +private: + class UIPickerListener : public UIPicker::SelectedListener { + public: + UIPickerListener(UITimePicker* timePicker) + { + timePicker_ = timePicker; + } + + ~UIPickerListener() {} + + virtual void OnPickerStoped(UIPicker& picker) override + { + if (timePicker_ != nullptr) { + timePicker_->TimeSelectedCallback(); + } + } + + private: + UITimePicker* timePicker_; + }; + static constexpr uint8_t TIME_START = 0; + static constexpr uint8_t HOUR_END = 23; + static constexpr uint8_t MIN_END = 59; + static constexpr uint8_t SEC_END = 59; + static constexpr uint8_t BUF_SIZE = 3; + static constexpr uint8_t SELECTED_VALUE_SIZE = 9; + static constexpr uint8_t SEC_VISIBLE_COUNT = 3; + static constexpr uint8_t SEC_INVISIBLE_COUNT = 2; + void TimeSelectedCallback(); + void InitTimePicker(); + void DeInitTimePicker(); + void RefreshTimePicker(); + bool RefreshSelected(const char* value); + void InitPicker(UIPicker*& picker, int16_t start, int16_t end); + void DeInitPicker(UIPicker*& picker); + void GetValueByIndex(char* value, uint8_t len, uint16_t index, int16_t start, int16_t end); + UIPicker* hourPicker_; + UIPicker* minutePicker_; + UIPicker* secondPicker_; + char selectedValue_[SELECTED_VALUE_SIZE]; + char selectedHour_[BUF_SIZE]; + char selectedMinute_[BUF_SIZE]; + char selectedSecond_[BUF_SIZE]; + bool secVisible_; + const char* setSelectedTime_; + uint16_t pickerWidth_; + uint16_t itemsHeight_; + int16_t xPos_; + uint8_t highlightFontId_; + uint8_t backgroundFontId_; + uint8_t backgroundFontSize_; + uint8_t highlightFontSize_; + char* backgroundFontName_; + char* highlightFontName_; + ColorType highlightColor_; + ColorType backgroundColor_; + UIPickerListener pickerListener_; + SelectedListener* timePickerListener_; +}; +} // namespace OHOS +#endif // GRAPHIC_LITE_UI_TIME_PICKER_H diff --git a/interfaces/kits/components/ui_toggle_button.h b/interfaces/kits/components/ui_toggle_button.h new file mode 100755 index 0000000..7732da4 --- /dev/null +++ b/interfaces/kits/components/ui_toggle_button.h @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @addtogroup UI_Components + * @{ + * + * @brief Defines UI components such as buttons, texts, images, lists, and progress bars. + * + * @since 1.0 + * @version 1.0 + */ + +/** + * @file ui_toggle_button.h + * + * @brief Defines the attributes and common functions of a toggle button. + * + * Each toggle button contains two images, one for pressing and the other for releasing. + * + * @since 1.0 + * @version 1.0 + */ + +#ifndef GRAPHIC_LITE_UI_TOGGLE_BUTTON_H +#define GRAPHIC_LITE_UI_TOGGLE_BUTTON_H + +#include "components/ui_checkbox.h" +#include "components/ui_image_view.h" + +namespace OHOS { +/** + * @brief Represents a toggle button. + * + * Each toggle button contains two images, one for pressing and the other for releasing. + * + * @see UICheckBox + * @since 1.0 + * @version 1.0 + */ +class UIToggleButton : public UICheckBox { +public: + /** + * @brief A constructor used to create a UIToggleButton instance. + * + * @since 1.0 + * @version 1.0 + */ + UIToggleButton(); + + /** + * @brief A destructor used to delete the UIToggleButton instance. + * + * @since 1.0 + * @version 1.0 + */ + virtual ~UIToggleButton() {} + + /** + * @brief Obtains the component type. + * + * @return Returns the component type, as defined in {@link UIViewType}. + * @since 1.0 + * @version 1.0 + */ + UIViewType GetViewType() const override + { + return UI_TOGGLE_BUTTON; + } + + void OnDraw(const Rect& invalidatedArea) override; + + /** + * @brief Sets the state for this toggle button. + * + * @param state Indicates the state of this toggle button. The value true indicates the image displayed for + * On, and false indicates the image displayed for Off. If this function is not called, the + * image is displayed for Off. + * @since 1.0 + * @version 1.0 + */ + void SetState(bool state); + + /** + * @brief Obtains the state of this toggle button. + * + * @return Returns true if the image for On is displayed; returns false if the image is + * displayed for Off. + * @since 1.0 + * @version 1.0 */ + bool GetState() const + { + return (state_ != UNSELECTED); + } + +private: + static constexpr int16_t DEFAULT_HOT_WIDTH = 46; + static constexpr int16_t DEFAULT_HOT_HEIGHT = 46; + static constexpr int16_t DEFAULT_WIDTH = 32; + static constexpr int16_t DEFAULT_CORNER_RADIUS = 11; + static constexpr int16_t DEAFULT_RADIUS_DIFF = 2; + static constexpr uint8_t DEFAULT_UNSELECTED_OPA = 97; + static constexpr uint8_t DEFAULT_BG_RED = 31; + static constexpr uint8_t DEFAULT_BG_GREEN = 113; + static constexpr uint8_t DEFAULT_BG_BLUE = 255; + + int16_t width_; + int16_t height_; + uint16_t corner_; + uint16_t radius_; + int16_t rectWidth_; + + void CalculateSize(); +}; // class UIToggleButton +} // namespace OHOS +#endif // GRAPHIC_LITE_UI_TOGGLE_BUTTON_H diff --git a/interfaces/kits/components/ui_video.h b/interfaces/kits/components/ui_video.h new file mode 100755 index 0000000..c04ff29 --- /dev/null +++ b/interfaces/kits/components/ui_video.h @@ -0,0 +1,498 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @addtogroup UI_Components + * @{ + * + * @brief Defines UI components such as buttons, texts, images, lists, and progress bars. + * + * @since 5.0 + * @version 3.0 + */ + +/** + * @file ui_video.h + * + * @brief Declares the functions related to video playbacks. + * + * @since 5.0 + * @version 3.0 + */ + +#ifndef GRAPHIC_LITE_UI_VIDEO_H +#define GRAPHIC_LITE_UI_VIDEO_H + +#include "animator/animator.h" +#include "components/ui_label.h" +#include "components/ui_slider.h" +#include "components/ui_surface_view.h" +#include "components/ui_toggle_button.h" +#include "components/ui_view_group.h" +#include "player.h" + +#ifndef VERSION_LITE +namespace OHOS { +using namespace OHOS::Media; +/** + * @brief Provides the functions related to video playbacks. + * + * @since 5.0 + * @version 3.0 + */ +class UIVideo : public UIViewGroup, + public UIView::OnClickListener, + public UIView::OnTouchListener, + public UISlider::UISliderEventListener { +public: + /** + * @brief A constructor used to create a UIVideo instance for playback. + * + * @since 5.0 + * @version 3.0 + */ + UIVideo(); + + /** + * @brief A destructor used to delete the UIVideo instance for playback. + * + * @since 5.0 + * @version 3.0 + */ + virtual ~UIVideo(); + + /** + * @brief Sets the source file to be played. + * + * @param source Indicates the pointer to the source file path. + * @return Returns true if the setting is successful; returns false otherwise. + * @since 5.0 + * @version 3.0 + */ + bool SetSrc(const char* source); + + /** + * @brief Obtains the path of the source file to be played. + * + * @return Returns the path of the source file to be played. + * @since 5.0 + * @version 3.0 + */ + const char* GetSrc() + { + return src_; + } + + /** + * @brief Prepares for the playback. You must call this function after {@link SetSource}. + * + * @return Returns true if the preparation is successful; returns false otherwise. + * @since 5.0 + * @version 3.0 + */ + bool Prepare(); + + /** + * @brief Plays this video. + * + * @return Returns true if this video is played; returns false otherwise. + * @since 5.0 + * @version 3.0 + */ + bool Play(); + + /** + * @brief Checks whether this video is playing. + * + * @return Returns true if this video is playing; returns false otherwise. + * @since 5.0 + * @version 3.0 + */ + bool IsPlaying(); + + /** + * @brief Pauses the video being played. + * + * @return Returns true if the video is paused; returns false otherwise. + * @since 5.0 + * @version 3.0 + */ + bool Pause(); + + /** + * @brief Stops playing this video. + * + * @return Returns true if this video is stopped; returns false otherwise. + * @since 5.0 + * @version 3.0 + */ + bool Stop(); + + /** + * @brief Changes the playback position. + * + * @param mSeconds Indicates the target playback position, in milliseconds. + * @return Returns true if the playback position is changed; returns false otherwise. + * @since 5.0 + * @version 3.0 + */ + bool Rewind(int64_t mSeconds); + + /** + * @brief Sets the playback volume. + * + * @param Indicates the volume of the left audio channel to set, ranging from 0 to 100. + * @param rightVolume Indicates the volume of the right audio channel to set, ranging from 0 to 1. + * @return Returns true if the setting is successful; returns false otherwise. + * @since 5.0 + * @version 3.0 + */ + bool SetVolume(float leftVolume, float rightVolume); + + /** + * @brief Sets whether to loop playback. + * + * @param loop Specifies whether to loop playback. The value true means that the playback loops, + * and false means the opposite case. + * @return Returns true if the setting is successful; returns false otherwise. + * @since 5.0 + * @version 3.0 + */ + bool EnableSingleLooping(bool loop); + + /** + * @brief Checks whether the playback loops. + * + * @return Returns true if the playback loops; returns false otherwise. + * @since 5.0 + * @version 3.0 + */ + bool IsSingleLooping(); + + /** + * @brief Obtains the current playback time. + * + * @param time Indicates the current playback time, in milliseconds. + * @return Returns true if the operation is successful; returns false otherwise. + * @since 5.0 + * @version 3.0 + */ + bool GetCurrentTime(int64_t& time); + + /** + * @brief Obtains the total video duration. + * + * @param duration Indicates the total duration, in milliseconds. + * @return Returns true if the duration is obtained; returns false otherwise. + * @since 5.0 + * @version 3.0 + */ + bool GetDuration(int64_t& duration); + + /** + * @brief Resets the player to the initial state. + * + * @return Returns true if the player is reset; returns false otherwise. + * @since 5.0 + * @version 3.0 + */ + bool Reset(); + + /** + * @brief Sets whether to show the playback controller. + * + * @param show Specifies whether to show the playback controller. The value true means showing + * the playback controller, and false means the opposite case. + * @since 5.0 + * @version 3.0 + */ + void ShowController(bool show); + + /** + * @brief Provides callbacks for events that occur during video playback. + * + * @since 5.0 + * @version 3.0 + */ + class VideoPlayerListener { + public: + /** + * @brief A constructor used to create a VideoPlayerListener instance. + * + * @since 5.0 + * @version 3.0 + */ + VideoPlayerListener() {} + + /** + * @brief A destructor used to delete the VideoPlayerListener instance. + * + * @since 5.0 + * @version 3.0 + */ + virtual ~VideoPlayerListener() {}; + + /** + * @brief Called when the playback is complete. + * + * @since 5.0 + * @version 3.0 + */ + virtual void OnPlaybackComplete() {} + + /** + * @brief Called when a video playback error occurs. + * + * @param errorType Indicates the error type. + * @param eerrorCode Indicates the error code. + * @since 5.0 + * @version 3.0 + */ + virtual void OnError(int32_t errorType, int32_t errorCode) {} + + /** + * @brief Called when playback information is received. + * + * @param type Indicates the information type. + * @param extra Indicates the information code. + * @since 5.0 + * @version 3.0 + */ + virtual void OnInfo(int32_t type, int32_t extra) {} + + /** + * @brief Called when the video image size changes. + * + * @param width Indicates the video width. + * @param height Indicates the video height. + * @since 5.0 + * @version 3.0 + */ + virtual void OnVideoSizeChanged(int32_t width, int32_t height) {} + + /** + * @brief Called when this video is rewound. + * + * @since 5.0 + * @version 3.0 + */ + virtual void OnRewindToComplete() {} + + /** + * @brief Called when this video is paused. + * + * @since 5.0 + * @version 3.0 + */ + virtual void OnPlaybackPause() {} + + /** + * @brief Called when this video is played. + * + * @since 5.0 + * @version 3.0 + */ + virtual void OnPlaybackPlay() {} + + /** + * @brief Called when this video is stopped. + * + * @since 5.0 + * @version 3.0 + */ + virtual void OnPlaybackStop() {} + }; + + /** + * @brief Sets a listener for monitoring video playbacks. + * + * @param listener Indicates the pointer to the listener to set. + * @since 5.0 + * @version 3.0 + */ + void SetVideoPlayerListener(VideoPlayerListener* listener); + +private: + class SliderAnimatorCallback : public AnimatorCallback { + public: + SliderAnimatorCallback(UIVideo* video, UISlider* slider, UILabel* label) + { + video_ = video; + slider_ = slider; + timeLabel_ = label; + } + + virtual ~SliderAnimatorCallback() {} + void Callback(UIView* view) override; + void SetPlayButton(UIToggleButton* toggleButton) + { + playButton_ = toggleButton; + } + + void SetSliderAnimator(Animator* animator) + { + sliderAnimator_ = animator; + } + + void SetDuration(int64_t duration) + { + duration_ = duration; + } + + void ResetTickTime() + { + if (sliderAnimator_ != nullptr) { + tickCount_ = sliderAnimator_->GetRunTime(); + } + } + + friend class UIVideo; + private: + uint32_t tickCount_ = 0; + int64_t duration_ = 0; + UIVideo* video_ = nullptr; + UISlider* slider_ = nullptr; + UILabel* timeLabel_ = nullptr; + UIToggleButton* playButton_ = nullptr; + Animator* sliderAnimator_ = nullptr; + }; + + void InitVideo(); + void InitControllerLabel(); + void InitControllerButton(); + void InitControllerSlider(); + void DeleteController(); + void SetSurfaceInfo(); + void OnVideoComplete(); + bool GetTimerFromMSecond(int64_t currentTime, char* timer, int32_t len); + + bool OnClick(UIView& view, const ClickEvent& event) override; + bool OnPress(UIView& view, const PressEvent& event) override; + void OnChange(int32_t progress) override; + + class PlayerListener : public Media::PlayerCallback { + public: + PlayerListener() {} + virtual ~PlayerListener() {} + void OnPlaybackComplete() override + { + if (video_ != nullptr) { + video_->OnVideoComplete(); + } + if (videoPlayerListener_ != nullptr) { + videoPlayerListener_->OnPlaybackComplete(); + } + } + + void OnError(int32_t errorType, int32_t errorCode) override + { + if (videoPlayerListener_ != nullptr) { + videoPlayerListener_->OnError(errorType, errorCode); + } + } + + void OnInfo(int32_t type, int32_t extra) override + { + if (videoPlayerListener_ != nullptr) { + videoPlayerListener_->OnInfo(type, extra); + } + } + + void OnVideoSizeChanged(int32_t width, int32_t height) override + { + if (videoPlayerListener_ != nullptr) { + videoPlayerListener_->OnVideoSizeChanged(width, height); + } + } + + void OnRewindToComplete() override + { + if (videoPlayerListener_ != nullptr) { + videoPlayerListener_->OnRewindToComplete(); + } + } + + void OnPlaybackPause() + { + if (videoPlayerListener_ != nullptr) { + videoPlayerListener_->OnPlaybackPause(); + } + } + + void OnPlaybackPlay() + { + if (videoPlayerListener_ != nullptr) { + videoPlayerListener_->OnPlaybackPlay(); + } + } + + void OnPlaybackStop() + { + if (videoPlayerListener_ != nullptr) { + videoPlayerListener_->OnPlaybackStop(); + } + } + + void SetVideoPlayer(UIVideo* video) + { + video_ = video; + } + + void SetVideoPlayerListerner(VideoPlayerListener* listener) + { + videoPlayerListener_ = listener; + } + + private: + UIVideo* video_ = nullptr; + VideoPlayerListener* videoPlayerListener_ = nullptr; + }; + + bool completeFlag_ = false; + float leftVolumeValue_ = DEFAULT_VOLUME; + float rightVolumeValue_ = DEFAULT_VOLUME; + int64_t duration_ = 0; + const char* src_ = nullptr; + std::shared_ptr videoPlayer_ = nullptr; + UIViewGroup* controllerGroup_ = nullptr; + UILabel* totalTimeLabel_ = nullptr; + UILabel* currentTimeLabel_ = nullptr; + UILabel* titleLabel_ = nullptr; + UISlider* playSlider_ = nullptr; + Animator* sliderAnimator_ = nullptr; + SliderAnimatorCallback* sliderAnimatorCallback_ = nullptr; + UIToggleButton* volumeButton_ = nullptr; + UISurfaceView* surfaceView_ = nullptr; + UIToggleButton* playButton_ = nullptr; + UIToggleButton* pauseButton_ = nullptr; + VideoPlayerListener* videoPlayerListener_ = nullptr; + std::shared_ptr playerListener_ = nullptr; + + static constexpr uint16_t DEFAULT_VIEW_WIDTH = 960; + static constexpr uint16_t DEFAULT_VIEW_HEIGHT = 480; + static constexpr uint16_t TOGGLE_BUTTON_WIDTH = 32; + static constexpr uint16_t TOGGLE_BUTTON_HEIGHT = 32; + static constexpr uint16_t TIME_LABEL_WIDTH = 300; + static constexpr uint16_t TIME_LABEL_HEIGHT = 20; + static constexpr uint16_t KNOB_WIDTH = 18; + static constexpr uint16_t FONT_DEFAULT_SIZE = 14; + static constexpr uint16_t MAX_VOLUME = 300; + static constexpr uint16_t HIDE_MILLI_SECOND = 5000; + static constexpr float DEFAULT_VOLUME = 0.5; +}; +} // namespace OHOS + +#endif // VERSION_LITE +#endif // GRAPHIC_LITE_UI_VIDEO_H \ No newline at end of file diff --git a/interfaces/kits/components/ui_view.h b/interfaces/kits/components/ui_view.h new file mode 100755 index 0000000..c432f59 --- /dev/null +++ b/interfaces/kits/components/ui_view.h @@ -0,0 +1,1516 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @addtogroup UI_Components + * @{ + * + * @brief Defines UI components such as buttons, texts, images, lists, and progress bars. + * + * @since 1.0 + * @version 1.0 + */ + +/** + * @file ui_view.h + * + * @brief Declares the base class of a view, providing basic view attributes and operations. All views are derived + * from this class. + * + * @since 1.0 + * @version 1.0 + */ + +#ifndef GRAPHIC_LITE_UI_VIEW_H +#define GRAPHIC_LITE_UI_VIEW_H + +#include "color.h" +#include "events/cancel_event.h" +#include "events/click_event.h" +#include "events/drag_event.h" +#include "events/event.h" +#include "events/long_press_event.h" +#include "events/press_event.h" +#include "events/release_event.h" +#if ENABLE_ROTATE_INPUT +#include "events/rotate_event.h" +#endif +#include "geometry2d.h" +#include "graphic_log.h" +#include "heap_base.h" +#include "style.h" +#include "transform.h" + +namespace OHOS { +/* Enumerates view types. */ +enum UIViewType : uint8_t { + UI_ROOT_VIEW = 0, + UI_VIEW_GROUP, + UI_LABEL, + UI_ARC_LABEL, + UI_LABEL_BUTTON, + UI_CHECK_BOX, + UI_TOGGLE_BUTTON, + UI_RADIO_BUTTON, + UI_IMAGE_VIEW, + UI_BOX_PROGRESS, + UI_SLIDER, + UI_CIRCLE_PROGRESS, + UI_SCROLL_VIEW, + UI_LIST, + UI_DIGITAL_CLOCK, + UI_ANALOG_CLOCK, + UI_PICKER, + UI_SWIPE_VIEW, + UI_TIME_PICKER, + UI_ABSTRACT_CLOCK, + UI_ABSTRACT_PROGRESS, + UI_ABSTRACT_SCROLL, + UI_AXIS, + UI_BUTTON, + UI_CANVAS, + UI_CHART, + UI_IMAGE_ANIMATOR_VIEW, + UI_REPEAT_BUTTON, + UI_TEXTURE_MAPPER, + UI_DIALOG, + UI_QRCODE, + UI_NUMBER_MAX +}; + +#if ENABLE_DEBUG +const char* const VIEW_TYPE_STRING[UI_NUMBER_MAX] = { + "RootView", "UIViewGroup", "UILabel", + "UIArcLabel", "UILabelButton", "UICheckBox", + "UIToggleButton", "UIRadioButton", "UIImageView", + "UIBoxProgress", "UISlider", "UICircleProgress", + "UIScrollView", "UIList", "UIDigitalClock", + "UIAnalogClock", "UIPicker", "UISwipeView", + "UITimePicker", "UIAbstractClock", "UIAbstractProgress", + "UIAbstractScroll", "UIAxis", "UIButton", + "UICanvas", "UIChart", "UIImageAnimatorView", + "UIRepeatButton", "UITextureMapper", "UIDialog", + "UIQrcode", +}; +#endif // ENABLE_DEBUG + +/** + * @brief Defines the base class of a view, providing basic view attributes and operations. All views are derived + * from this class. + * + * @since 1.0 + * @version 1.0 + */ +class UIView : public HeapBase { +public: + /** + * @brief Defines a click event listener. You need to register this listener with the view to listen to + * click events. + * + * @since 1.0 + * @version 1.0 + */ + class OnClickListener : public HeapBase { + public: + /** + * @brief Called when a view is clicked. + * @param view Indicates the view clicked. + * @param event Indicates the click event. + * @return Returns true if the event is consumed; returns false otherwise. + * (If an event is consumed, it is not transferred to the parent view. If an event is not consumed, + * it is transferred to the parent view after local processing is complete.) + * @since 1.0 + * @version 1.0 + */ + virtual bool OnClick(UIView& view, const ClickEvent& event) + { + return false; + } + + /** + * @brief A destructor used to delete the OnClickListener instance. + * @since 1.0 + * @version 1.0 + */ + virtual ~OnClickListener() {} + }; + + /** + * @brief Defines a long-press event listener. You need to register this listener with the view to listen to + * long-press events. + * + * @since 1.0 + * @version 1.0 + */ + class OnLongPressListener : public HeapBase { + public: + /** + * @brief Called when a view is long pressed. + * @param view Indicates the view long pressed. + * @param event Indicates the long-press event. + * @return Returns true if the event is consumed; returns false otherwise. + * (If an event is consumed, it is not transferred to the parent view. If an event is not consumed, + * it is transferred to the parent view after local processing is complete.) + * @since 1.0 + * @version 1.0 + */ + virtual bool OnLongPress(UIView& view, const LongPressEvent& event) + { + return false; + } + + /** + * @brief A destructor used to delete the OnLongPressListener instance. + * @since 1.0 + * @version 1.0 + */ + virtual ~OnLongPressListener() {} + }; + + /** + * @brief Defines a drag event listener. You need to register this listener with the view to listen to drag events. + * + * @since 1.0 + * @version 1.0 + */ + class OnDragListener : public HeapBase { + public: + /** + * @brief Called when a view starts to drag. + * @param view Indicates the view dragged. + * @param event Indicates the drag event. + * @return Returns true if the event is consumed; returns false otherwise. + * (If an event is consumed, it is not transferred to the parent view. If an event is not consumed, + * it is transferred to the parent view after local processing is complete.) + * @since 1.0 + * @version 1.0 + */ + virtual bool OnDragStart(UIView& view, const DragEvent& event) + { + return false; + } + + /** + * @brief Called when a view is being dragged. + * @param view Indicates the view dragged. + * @param event Indicates the drag event. + * @return Returns true if the event is consumed; returns false otherwise. + * (If an event is consumed, it is not transferred to the parent view. If an event is not consumed, + * it is transferred to the parent view after local processing is complete.) + * @since 1.0 + * @version 1.0 + */ + virtual bool OnDrag(UIView& view, const DragEvent& event) + { + return false; + } + + /** + * @brief Called when a view stops dragging. + * @param view Indicates the view dragged. + * @param event Indicates the drag event. + * @return Returns true if the event is consumed; returns false otherwise. + * (If an event is consumed, it is not transferred to the parent view. If an event is not consumed, + * it is transferred to the parent view after local processing is complete.) + * @since 1.0 + * @version 1.0 + */ + virtual bool OnDragEnd(UIView& view, const DragEvent& event) + { + return false; + } + + /** + * @brief A destructor used to delete the OnDragListener instance. + * @since 1.0 + * @version 1.0 + */ + virtual ~OnDragListener() {} + }; + + /** + * @brief Defines a touch event listener. You need to register this listener with the view to listen to touch + * events. + * + * @since 1.0 + * @version 1.0 + */ + class OnTouchListener : public HeapBase { + public: + /** + * @brief Called when a view is pressed. + * @param view Indicates the view pressed. + * @param event Indicates the press event. + * @return Returns true if the event is consumed; returns false otherwise. + * (If an event is consumed, it is not transferred to the parent view. If an event is not consumed, + * it is transferred to the parent view after local processing is complete.) + * @since 1.0 + * @version 1.0 + */ + virtual bool OnPress(UIView& view, const PressEvent& event) + { + return false; + } + + /** + * @brief Called when a view is released. + * @param view Indicates the view released. + * @param event Indicates the release event. + * @return Returns true if the event is consumed; returns false otherwise. + * (If an event is consumed, it is not transferred to the parent view. If an event is not consumed, + * it is transferred to the parent view after local processing is complete.) + * @since 1.0 + * @version 1.0 + */ + virtual bool OnRelease(UIView& view, const ReleaseEvent& event) + { + return false; + } + + /** + * @brief Called when a click event on a view is canceled. + * @param view Indicates the view on which a click event is canceled. + * @param event Indicates the cancel event. + * @return Returns true if the event is consumed; returns false otherwise. + * (If an event is consumed, it is not transferred to the parent view. If an event is not consumed, + * it is transferred to the parent view after local processing is complete.) + * @since 1.0 + * @version 1.0 + */ + virtual bool OnCancel(UIView& view, const CancelEvent& event) + { + return false; + } + + /** + * @brief A destructor used to delete the OnTouchListener instance. + * @since 1.0 + * @version 1.0 + */ + virtual ~OnTouchListener() {} + }; + +#if ENABLE_ROTATE_INPUT + /** + * @brief 声明旋转事件监听类。你需要注册此监听类以响应旋转事件。 + * @since 5.0 + * @version 3.0 + */ + class OnRotateListener : public HeapBase { + public: + /** + * @brief 当组件收到旋转事件时调用 + * @param view 响应旋转的组件 + * @param event 旋转事件 + * @return 如果事件被消费返回true, 否则返回false. + * @since 5.0 + * @version 3.0 + */ + virtual bool OnRotate(UIView& view, const RotateEvent& event) + { + return true; + } + /** + * @brief 析构函数,删除OnRotateListener旋转事件监听类实例. + * @since 5.0 + * @version 3.0 + */ + virtual ~OnRotateListener() {} + }; + + /** + * @brief 当旋转事件传入组件时调用以产生对应响应 + * @param event 旋转事件 + * @since 5.0 + * @version 3.0 + */ + virtual bool OnRotateEvent(const RotateEvent& event); + + /** + * @brief 设置当前view组件的旋转事件监听对象 + * @param onRotateListener 输入当前view组件的旋转事件监听对象指针 + * @since 5.0 + * @version 3.0 + */ + void SetOnRotateListener(OnRotateListener* onRotateListener) + { + onRotateListener_ = onRotateListener; + } + + /** + * @brief 获得当前view组件的旋转事件监听对象指针 + * @return 返回当前view组件的旋转事件监听对象指针 + * @since 5.0 + * @version 3.0 + */ + OnRotateListener*& GetOnRotateListener() + { + return onRotateListener_; + } +#endif + + /** + * @brief Stores extra information about a UIView instance. + * @param elementPtr Indicates the void pointer to the extra information about the UIView instance. + * @since 5.0 + * @version 3.0 + */ + struct ViewExtraMsg { + void* elementPtr; + }; + + /** + * @brief A default constructor used to create an UIView instance. + * @since 1.0 + * @version 1.0 + */ + UIView(); + + /** + * @brief A constructor used to create an UIView instance. + * @param id Indicates the pointer to the view ID. + * @since 1.0 + * @version 1.0 + */ + UIView(const char* id) : UIView() + { + id_ = id; + } + + /** + * @brief A destructor used to delete the UIView instance. + * @since 1.0 + * @version 1.0 + */ + virtual ~UIView(); + + /** + * @brief Called before a view is drawn. This function is used to check whether the invalidated area + * can be fully cover by this view so as to optimize the drawing process. + * @param invalidatedArea Indicates the area to judge and returns the covered area when partly coverd. + * @return Returns true if the invalidated area is fully covered; returns false otherwise. + * @since 1.0 + * @version 1.0 + */ + virtual bool OnPreDraw(Rect& invalidatedArea) const; + + /** + * @brief Called when a view is drawn. + * @param invalidatedArea Indicates the area to draw. + * @since 1.0 + * @version 1.0 + */ + virtual void OnDraw(const Rect& invalidatedArea); + + /** + * @brief Called after a view is drawn. + * @param invalidatedArea Indicates the area in which the view is drawn. + * @since 1.0 + * @version 1.0 + */ + virtual void OnPostDraw(const Rect& invalidatedArea) {} + + /** + * @brief Remeasures the view size. + * @since 1.0 + * @version 1.0 + */ + virtual void ReMeasure() {} + + /** + * @brief Refreshes the invalidated area of the view. + * @since 1.0 + * @version 1.0 + */ + void Invalidate(); + + /** + * @brief Refreshes a view in a specified invalidated area. + * @param invalidatedArea Indicates the area to refresh. + * @since 1.0 + * @version 1.0 + */ + void InvalidateRect(const Rect& invalidatedArea); + + /** + * @brief Called when the view is long pressed. + * @param event Indicates the long-press event. + * @return Returns true if the event is consumed; returns false otherwise. + * (If an event is consumed, it is not transferred to the parent view. If an event is not consumed, + * it is transferred to the parent view after local processing is complete.) + * @since 1.0 + * @version 1.0 + */ + virtual bool OnLongPressEvent(const LongPressEvent& event); + + /** + * @brief Called when the view starts to drag. + * @param event Indicates the drag event. + * @return Returns true if the event is consumed; returns false otherwise. + * (If an event is consumed, it is not transferred to the parent view. If an event is not consumed, + * it is transferred to the parent view after local processing is complete.) + * @since 1.0 + * @version 1.0 + */ + virtual bool OnDragStartEvent(const DragEvent& event); + + /** + * @brief Called when the view is being dragged. + * @param event Indicates the drag event. + * @return Returns true if the event is consumed; returns false otherwise. + * (If an event is consumed, it is not transferred to the parent view. If an event is not consumed, + * it is transferred to the parent view after local processing is complete.) + * @since 1.0 + * @version 1.0 + */ + virtual bool OnDragEvent(const DragEvent& event); + + /** + * @brief Called when the view stops dragging. + * @param event Indicates the drag event. + * @return Returns true if the event is consumed; returns false otherwise. + * (If an event is consumed, it is not transferred to the parent view. If an event is not consumed, + * it is transferred to the parent view after local processing is complete.) + * @since 1.0 + * @version 1.0 + */ + virtual bool OnDragEndEvent(const DragEvent& event); + + /** + * @brief Called when the view is clicked. + * @param event Indicates the click event. + * @return Returns true if the event is consumed; returns false otherwise. + * (If an event is consumed, it is not transferred to the parent view. If an event is not consumed, + * it is transferred to the parent view after local processing is complete.) + * @since 1.0 + * @version 1.0 + */ + virtual bool OnClickEvent(const ClickEvent& event); + + /** + * @brief Called when the view is pressed. + * @param event Indicates the press event. + * @return Returns true if the event is consumed; returns false otherwise. + * (If an event is consumed, it is not transferred to the parent view. If an event is not consumed, + * it is transferred to the parent view after local processing is complete.) + * @since 1.0 + * @version 1.0 + */ + virtual bool OnPressEvent(const PressEvent& event); + + /** + * @brief Called when the view is released. + * @param event Indicates the release event. + * @return Returns true if the event is consumed; returns false otherwise. + * (If an event is consumed, it is not transferred to the parent view. If an event is not consumed, + * it is transferred to the parent view after local processing is complete.) + * @since 1.0 + * @version 1.0 + */ + virtual bool OnReleaseEvent(const ReleaseEvent& event); + + /** + * @brief Called when a click event on the view is canceled. + * @param event Indicates the cancel event. + * @return Returns true if the event is consumed; returns false otherwise. + * (If an event is consumed, it is not transferred to the parent view. If an event is not consumed, + * it is transferred to the parent view after local processing is complete.) + * @since 1.0 + * @version 1.0 + */ + virtual bool OnCancelEvent(const CancelEvent& event); + + /** + * @brief Sets a drag event listener for the view. + * @param onDragListener Indicates the pointer to the drag event listener to set. + * @since 1.0 + * @version 1.0 + */ + void SetOnDragListener(OnDragListener* onDragListener) + { + onDragListener_ = onDragListener; + } + + /** + * @brief Obtains the drag event listener for the view. + * @return Returns the drag event listener. + * @since 1.0 + * @version 1.0 + */ + OnDragListener*& GetOnDragListener() + { + return onDragListener_; + } + + /** + * @brief Sets a click event listener for the view. + * @param onClickListener Indicates the pointer to the click event listener to set. + * @since 1.0 + * @version 1.0 + */ + void SetOnClickListener(OnClickListener* onClickListener) + { + onClickListener_ = onClickListener; + } + + /** + * @brief Obtains the click event listener for the view. + * @return Returns the click event listener. + * @since 1.0 + * @version 1.0 + */ + OnClickListener*& GetOnClickListener() + { + return onClickListener_; + } + + /** + * @brief Sets a long-press event listener for the view. + * @param onLongPressListener Indicates the pointer to the long-press event listener to set. + * @since 1.0 + * @version 1.0 + */ + void SetOnLongPressListener(OnLongPressListener* onLongPressListener) + { + onLongPressListener_ = onLongPressListener; + } + + /** + * @brief Obtains the long-press event listener for the view. + * @return Returns the long-press event listener. + * @since 1.0 + * @version 1.0 + */ + OnLongPressListener*& GetOnLongPressListener() + { + return onLongPressListener_; + } + + /** + * @brief Sets a touch event listener for the view. + * @param onTouchListener Indicates the pointer to the touch event listener to set. + * @since 1.0 + * @version 1.0 + */ + void SetOnTouchListener(OnTouchListener* onTouchListener) + { + onTouchListener_ = onTouchListener; + } + + /** + * @brief Obtains the touch event listener for the view. + * @return Returns the touch event listener. + * @since 1.0 + * @version 1.0 + */ + OnTouchListener*& GetTouchListener() + { + return onTouchListener_; + } + + /** + * @brief Obtains the top-level view based on specified coordinates. + * @param point Indicates the coordinates to specify. + * @param last Indicates the double pointer to the view that contains the specified coordinates. + * @since 1.0 + * @version 1.0 + */ + virtual void GetTargetView(const Point& point, UIView** last); + + /** + * @brief Obtains the current view and target view based on specified coordinates. The obtained current view must + * include the specified coordinates and is a visible top view that can respond to touch events, and the + * obtained target view must be the top view that includes the specified coordinates. + * @param point Indicates the specified coordinates. + * @param current Indicates the double pointer to the current view to obtain. + * nullptr indicates that the target view fails to be obtained. + * @param target Indicates the double pointer to the target view to obtain. + * nullptr indicates that the target view fails to be obtained. + * @since 5.0 + * @version 3.0 + */ + virtual void GetTargetView(const Point& point, UIView** current, UIView** target); + + /** + * @brief Sets the parent view for the view. + * @param parent Indicates the pointer to the parent view to set. + * @since 1.0 + * @version 1.0 + */ + void SetParent(UIView* parent) + { + parent_ = parent; + } + + /** + * @brief Obtains the parent view of the view. + * @return Returns the pointer to the parent view. + * @since 1.0 + * @version 1.0 + */ + UIView* GetParent() const + { + return parent_; + } + + /** + * @brief Sets the next sibling view for the view. + * @param sibling Indicates the pointer to the next sibling view to set. + * @since 1.0 + * @version 1.0 + */ + void SetNextSibling(UIView* sibling) + { + nextSibling_ = sibling; + } + + /** + * @brief Obtains the next sibling view of the view. + * @return Returns the pointer to the next sibling view. + * @since 1.0 + * @version 1.0 + */ + UIView* GetNextSibling() const + { + return nextSibling_; + } + + /** + * @brief Sets whether the view is visible. + * @param visible Specifies whether to set the view visible. Value true means to set the view visible, + * and false means the opposite. + * @since 1.0 + * @version 1.0 + */ + virtual void SetVisible(bool visible) + { + if (visible_ != visible) { + visible_ = visible; + needRedraw_ = true; + Invalidate(); + } + } + + /** + * @brief Checks whether the view is visible. + * @return Returns true if the view is visible; returns false otherwise. + * @since 1.0 + * @version 1.0 + */ + bool IsVisible() const + { + return visible_; + } + + /** + * @brief Sets whether the view is touchable. + * @param touchable Specifies whether to set the view touchable. Value true means to set the view touchable, + * and false means the opposite. + * @since 1.0 + * @version 1.0 + */ + void SetTouchable(bool touch) + { + touchable_ = touch; + } + + /** + * @brief Checks whether the view is touchable. + * @return Returns true if the view is touchable; returns false otherwise. + * @since 1.0 + * @version 1.0 + */ + bool IsTouchable() const + { + return touchable_; + } + + /** + * @brief Sets whether the view is draggable. + * @param draggable Specifies whether to set the view draggable. Value true means to set the view draggable, + * and false means the opposite. + * @since 1.0 + * @version 1.0 + */ + void SetDraggable(bool draggable) + { + draggable_ = draggable; + dragParentInstead_ = !draggable; + } + + /** + * @brief Checks whether the view is draggable. + * @return Returns true if the view is draggable; returns false otherwise. + * @since 1.0 + * @version 1.0 + */ + bool IsDraggable() const + { + return draggable_; + } + + /** + * @brief Sets whether to transfer the drag event to the parent view for processing when the view is being dragged. + * @param dragParentInstead Specifies whether to transfer the event to the parent view for processing. + * Value true means to transfer the event to the parent view for processing, + * and false means the opposite. + * @since 1.0 + * @version 1.0 + */ + void SetDragParentInstead(bool dragParentInstead) + { + dragParentInstead_ = dragParentInstead; + } + + /** + * @brief Obtains whether the view transfers a drag event to the parent view for processing. + * @return Returns true if the view transfers the event to the parent view for processing; + * returns false otherwise. + * @since 1.0 + * @version 1.0 + */ + bool IsDragParentInstead() const + { + return dragParentInstead_; + } + + /** + * @brief Obtains the absolute rectangle area of the view. When the view has deformation such as rotation, + * the rectangle area is the intersection set of the absolute rectangle area and deformation matrix. + * @return Returns the absolute rectangle area. + * @since 1.0 + * @version 1.0 + */ + Rect GetRect() const; + + /** + * @brief Obtains the visible absolute rectangle area of the view. + * @return Returns the visible absolute rectangle area. + * @since 1.0 + * @version 1.0 + */ + Rect GetVisibleRect() const; + + /** + * @brief Obtains the valid absolute rectangle area of the view. The valid area refers to the area where the view + * can be displayed. Generally, the valid area is the same as the visible view area, but they may be + * different in the grid layout. + * @return Returns the valid absolute rectangle area. + * @since 1.0 + * @version 1.0 + */ + Rect GetMaskedRect() const; + + /** + * @brief Obtains the absolute rectangle area of the view. + * @return Returns the absolute rectangle area. + * @since 1.0 + * @version 1.0 + */ + Rect GetOrigRect() const; + + /** + * @brief Obtains the content of the absolute rectangle area of the view. This area excludes padding. + * @return Returns the content of the absolute rectangle area. + * @since 1.0 + * @version 1.0 + */ + virtual Rect GetContentRect(); + + /** + * @brief Obtains the rectangular area of the view relative to the parent view, that is, the rectangular area + * relative to the coordinates of the parent view. + * @return Returns the rectangle area relative to the parent view. + * @since 1.0 + * @version 1.0 + */ + Rect GetRelativeRect() const + { + return rect_; + } + + /** + * @brief Adjusts the size of the visible area. This operation may affect the final display size. + * @param x Indicates the new x-coordinate. + * @param y Indicates the new y-coordinate. + * @param width Indicates the new width. + * @param height Indicates the new height. + * @since 1.0 + * @version 1.0 + */ + void ResizeVisibleArea(int16_t x, int16_t y, int16_t width, int16_t height) + { + if (visibleRect_ == nullptr) { + visibleRect_ = new Rect(); + if (visibleRect_ == nullptr) { + GRAPHIC_LOGE("new Rect fail"); + return; + } + } + visibleRect_->SetWidth(width); + visibleRect_->SetHeight(height); + visibleRect_->SetPosition(x, y); + } + + /** + * @brief Sets the width for the view. + * @param width Indicates the width to set. + * @since 1.0 + * @version 1.0 + */ + virtual void SetWidth(int16_t width) + { + if (GetWidth() != width) { + int16_t newWidth = width + style_->paddingLeft_ + style_->paddingRight_ + + (style_->borderWidth_ * 2); /* 2: left and right border */ + rect_.SetWidth(newWidth); + } + } + + /** + * @brief Sets a percentage that represents the proportion of the view's width to the parent view's width. + * @param widthPercent Indicates the percentage to set, the decimal form of which ranges from 0 to 1. + * @since 5.0 + * @version 3.0 + */ + virtual void SetWidthPercent(float widthPercent); + + /** + * @brief Obtains the width for the view. + * @return Returns the view width. + * @since 1.0 + * @version 1.0 + */ + virtual int16_t GetWidth() + { + return rect_.GetWidth() - (style_->paddingLeft_ + style_->paddingRight_) - + (style_->borderWidth_ * 2); /* 2: left and right border */ + } + + /** + * @brief Sets the height for the view. + * @param height Indicates the height to set. + * @since 1.0 + * @version 1.0 + */ + virtual void SetHeight(int16_t height) + { + if (GetHeight() != height) { + int16_t newHeight = height + style_->paddingTop_ + style_->paddingBottom_ + + (style_->borderWidth_ * 2); /* 2: top and bottom border */ + rect_.SetHeight(newHeight); + } + } + + /** + * @brief Sets a percentage that represents the proportion of the view's hieght to the parent view's hieght. + * @param widthPercent Indicates the percentage to set, the decimal form of which ranges from 0 to 1. + * @since 5.0 + * @version 3.0 + */ + virtual void SetHeightPercent(float heightPercent); + + /** + * @brief Obtains the height for the view. + * @return Returns the view height. + * @since 1.0 + * @version 1.0 + */ + virtual int16_t GetHeight() + { + return rect_.GetHeight() - (style_->paddingTop_ + style_->paddingBottom_) - + (style_->borderWidth_ * 2); /* 2: top and bottom border */ + } + + /** + * @brief Adjusts the size of the view. + * @param width Indicates the new width. + * @param height Indicates the new height. + * @since 1.0 + * @version 1.0 + */ + virtual void Resize(int16_t width, int16_t height) + { + SetWidth(width); + SetHeight(height); + } + + /** + * @brief Adjusts the size of the view based on specified percentages. + * @param widthPercent Indicates the percentage that represents the proportion of the view's width + * to the parent view's width to set, the decimal form of which ranges from 0 to 1. + * @param heightPercent Indicates the percentage that represents the proportion of the view's height + * to the parent view's height to set, the decimal form of which ranges from 0 to 1. + * @since 5.0 + * @version 3.0 + */ + virtual void ResizePercent(float widthPercent, float heightPercent); + + /** + * @brief Sets the x-coordinate for the view. + * @param x Indicates the x-coordinate to set. + * @since 1.0 + * @version 1.0 + */ + virtual void SetX(int16_t x) + { + rect_.SetX(x); + } + + /** + * @brief Sets a percentage that represents the proportion of the view's x-coordinate + * to the parent view's x-coordinate. + * @param xPercent Indicates the percentage to set, the decimal form of which ranges from 0 to 1. + * @since 5.0 + * @version 3.0 + */ + virtual void SetXPercent(float xPercent); + + /** + * @brief Obtains the x-coordinate for the view. + * @return Returns the x-coordinate. + * @since 1.0 + * @version 1.0 + */ + int16_t GetX() const + { + return rect_.GetX(); + } + + /** + * @brief Sets the y-coordinate for the view. + * @param y Indicates the y-coordinate to set. + * @since 1.0 + * @version 1.0 + */ + virtual void SetY(int16_t y) + { + rect_.SetY(y); + } + + /** + * @brief Sets a percentage that represents the proportion of the view's y-coordinate + * to the parent view's y-coordinate. + * @param yPercent Indicates the percentage to set, the decimal form of which ranges from 0 to 1. + * @since 5.0 + * @version 3.0 + */ + virtual void SetYPercent(float yPercent); + + /** + * @brief Obtains the y-coordinate for the view. + * @return Returns the y-coordinate. + * @since 1.0 + * @version 1.0 + */ + int16_t GetY() const + { + return rect_.GetY(); + } + + /** + * @brief Sets the position for the view. + * @param x Indicates the x-coordinate to set. + * @param y Indicates the y-coordinate to set. + * @since 1.0 + * @version 1.0 + */ + virtual void SetPosition(int16_t x, int16_t y) + { + rect_.SetPosition(x, y); + } + + /** + * @brief Sets the position percentages for the view. + * @param xPercent Indicates the percentage that represents the proportion of the view's x-coordinate + * to the parent view's x-coordinate to set, the decimal form of which ranges from 0 to 1. + * @param yPercent Indicates the percentage that represents the proportion of the view's y-coordinate + * to the parent view's y-coordinate to set, the decimal form of which ranges from 0 to 1. + * @since 5.0 + * @version 3.0 + */ + virtual void SetPositionPercent(float xPercent, float yPercent); + + /** + * @brief Adjusts the position and size of the view. + * @param x Indicates the new x-coordinate. + * @param y Indicates the new y-coordinate. + * @param width Indicates the new width. + * @param height Indicates the new height. + * @since 1.0 + * @version 1.0 + */ + virtual void SetPosition(int16_t x, int16_t y, int16_t width, int16_t height) + { + SetPosition(x, y); + SetWidth(width); + SetHeight(height); + } + + /** + * @brief Sets the position and size percentages for the view. + * @param xPercent Indicates the percentage that represents the proportion of the view's x-coordinate + * to the parent view's x-coordinate to set, the decimal form of which ranges from 0 to 1. + * @param yPercent Indicates the percentage that represents the proportion of the view's y-coordinate + * to the parent view's y-coordinate, the decimal form of which ranges from 0 to 1. + * @param widthPercent Indicates the percentage that represents the proportion of the view's width + * to the parent view's width, the decimal form of which ranges from 0 to 1. + * @param heightPercent Indicates the percentage that represents the proportion of the view's height + * to the parent view's height, the decimal form of which ranges from 0 to 1. + * @since 5.0 + * @version 3.0 + */ + virtual void SetPositionPercent(float xPercent, float yPercent, float widthPercent, float heightPercent); + + /** + * @brief Checks whether the view is a container view. + * @return Returns true if the view is a container view; returns false otherwise. + * @since 1.0 + * @version 1.0 + */ + bool IsViewGroup() const + { + return isViewGroup_; + } + + /** + * @brief Sets whether to intercept the drag event. If intercepted, the view does not transfer the drag event to + * the parent view after local processing. + * @param isIntercept Specifies whether to intercept the drag event. Value true means to intercept the drag + * event, and false means the opposite. + * @since 1.0 + * @version 1.0 + */ + void SetIntercept(bool isIntercept) + { + isIntercept_ = isIntercept; + } + + /** + * @brief Sets the affine transformation matrix. + * @param transMap Indicates the transformation matrix. + * @since 1.0 + * @version 1.0 + */ + void SetTransformMap(const TransformMap& transMap); + + /** + * @brief Obtains an affine transformation matrix. + * @return Returns the transform matrix. + * @since 1.0 + * @version 1.0 + */ + TransformMap& GetTransformMap() + { + if (transMap_ == nullptr) { + transMap_ = new TransformMap(); + } + return *transMap_; + } + + /** + * @brief Obtains the child view of a specified ID. + * @return Returns the pointer to the child view. + * @since 1.0 + * @version 1.0 + */ + virtual UIView* GetChildById(const char* id) const + { + return nullptr; + } + + /** + * @brief Sets the view ID. + * @param id Indicates the pointer to the view ID. + * @since 1.0 + * @version 1.0 + */ + void SetViewId(const char* id) + { + id_ = id; + } + + /** + * @brief Obtains the view ID. + * @return Returns the pointer to the view ID. + * @since 1.0 + * @version 1.0 + */ + const char* GetViewId() const + { + return id_; + } + + /** + * @brief Sets the view index. + * @param index Indicates the view index to set. + * @since 1.0 + * @version 1.0 + */ + void SetViewIndex(int16_t index) + { + index_ = index; + } + + /** + * @brief Obtains the view index. + * @return Returns the view index. + * @since 1.0 + * @version 1.0 + */ + int16_t GetViewIndex() const + { + return index_; + } + + /** + * @brief Obtains the view type. + * @return Returns the view type. + * @since 1.0 + * @version 1.0 + */ + virtual UIViewType GetViewType() const + { + return UI_NUMBER_MAX; + } + + /** + * @brief Lays out all child views according to the preset arrangement mode. + * @param needInvalidate Specifies whether to refresh the invalidated area after the layout is complete. + * Value true means to refresh the invalidated area after the layout is complete, + * and false means the opposite. + * @since 1.0 + * @version 1.0 + */ + virtual void LayoutChildren(bool neeInvalidate = false) {} + + /** + * @brief Lays out the view in the center of the parent view. + * @param xOffset Indicates the offset added to the x-axis after the view is placed. A positive number indicates + * the offset to the right, and a negative number indicates the offset to the left. + * @param yOffset Indicates the offset added to the y-axis after the view is placed. A positive number indicates + * the offset to the bottom, and a negative number indicates the offset to the top. + * @since 1.0 + * @version 1.0 + */ + void LayoutCenterOfParent(int16_t xOffSet = 0, int16_t yOffset = 0); + + /** + * @brief Lays out the view on the left of the parent view. + * @param offset Indicates the offset added to the x-axis after the view is placed. A positive number indicates + * the offset to the right, and a negative number indicates the offset to the left. + * @since 1.0 + * @version 1.0 + */ + void LayoutLeftOfParent(int16_t offset = 0); + + /** + * @brief Lays out the view on the right of the parent view. + * @param offset Indicates the offset added to the x-axis after the view is placed. A positive number indicates + * the offset to the left, and a negative number indicates the offset to the right. + * @since 1.0 + * @version 1.0 + */ + void LayoutRightOfParent(int16_t offset = 0); + + /** + * @brief Lays out the view on the top of the parent view. + * @param offset Indicates the offset added to the y-axis after the view is placed. A positive number indicates + * the offset to the bottom, and a negative number indicates the offset to the top. + * @since 1.0 + * @version 1.0 + */ + void LayoutTopOfParent(int16_t offset = 0); + + /** + * @brief Lays out the view on the bottom of the parent view. + * @param offset Indicates the offset added to the y-axis after the view is placed. A positive number indicates + * the offset to the top, and a negative number indicates the offset to the bottom. + * @since 1.0 + * @version 1.0 + */ + void LayoutBottomOfParent(int16_t offset = 0); + + /** + * @brief Aligns the view with the left of a sibling view. + * @param id Indicates the pointer to the ID of the sibling view. + * @param offset Indicates the offset added to the x-axis after the view is placed. A positive number indicates + * the offset to the right, and a negative number indicates the offset to the left. + * @since 1.0 + * @version 1.0 + */ + void AlignLeftToSibling(const char* id, int16_t offset = 0); + + /** + * @brief Aligns the view with the right of a sibling view. + * @param id Indicates the pointer to the ID of the sibling view. + * @param offset Indicates the offset added to the x-axis after the view is placed. A positive number indicates + * the offset to the left, and a negative number indicates the offset to the right. + * @since 1.0 + * @version 1.0 + */ + void AlignRightToSibling(const char* id, int16_t offset = 0); + + /** + * @brief Aligns the view with the top of a sibling view. + * @param id Indicates the pointer to the ID of the sibling view. + * @param offset Indicates the offset added to the y-axis after the view is placed. A positive number indicates + * the offset to the bottom, and a negative number indicates the offset to the top. + * @since 1.0 + * @version 1.0 + */ + void AlignTopToSibling(const char* id, int16_t offset = 0); + + /** + * @brief Aligns the view with the bottom of a sibling view. + * @param id Indicates the pointer to the ID of the sibling view. + * @param offset Indicates the offset added to the y-axis after the view is placed. A positive number indicates + * the offset to the top, and a negative number indicates the offset to the bottom. + * @since 1.0 + * @version 1.0 + */ + void AlignBottomToSibling(const char* id, int16_t offset = 0); + + /** + * @brief Aligns the view with the center of a sibling view in the x-axis. + * @param id Indicates the pointer to the ID of the sibling view. + * @param offset Indicates the offset added to the x-axis after the view is placed. A positive number indicates + * the offset to the right, and a negative number indicates the offset to the left. + * @since 1.0 + * @version 1.0 + */ + void AlignHorCenterToSibling(const char* id, int16_t offset = 0); + + /** + * @brief Aligns the view with the center of a sibling view in the y-axis. + * @param id Indicates the pointer to the ID of the sibling view. + * @param offset Indicates the offset added to the y-axis after the view is placed. A positive number indicates + * the offset to the bottom, and a negative number indicates the offset to the top. + * @since 1.0 + * @version 1.0 + */ + void AlignVerCenterToSibling(const char* id, int16_t offset = 0); + + /** + * @brief Lays out the view on the left of a sibling view. + * @param id Indicates the pointer to the ID of the sibling view. + * @param offset Indicates the offset added to the x-axis after the view is placed. A positive number indicates + * the offset to the left, and a negative number indicates the offset to the right. + * @since 1.0 + * @version 1.0 + */ + void LayoutLeftToSibling(const char* id, int16_t offset = 0); + + /** + * @brief Lays out the view on the right of a sibling view. + * @param id Indicates the pointer to the ID of the sibling view. + * @param offset Indicates the offset added to the x-axis after the view is placed. A positive number indicates + * the offset to the right, and a negative number indicates the offset to the left. + * @since 1.0 + * @version 1.0 + */ + void LayoutRightToSibling(const char* id, int16_t offset = 0); + + /** + * @brief Lays out the view on the above of a sibling view. + * @param id Indicates the pointer to the ID of the sibling view. + * @param offset Indicates the offset added to the y-axis after the view is placed. A positive number indicates + * the offset to the top, and a negative number indicates the offset to the bottom. + * @since 1.0 + * @version 1.0 + */ + void LayoutTopToSibling(const char* id, int16_t offset = 0); + + /** + * @brief Lays out the view on the below of a sibling view. + * @param id Indicates the pointer to the ID of the sibling view. + * @param offset Indicates the offset added to the y-axis after the view is placed. A positive number indicates + * the offset to the bottom, and a negative number indicates the offset to the top. + * @since 1.0 + * @version 1.0 + */ + void LayoutBottomToSibling(const char* id, int16_t offset = 0); + + /** + * @brief Sets the view style. + * @param style Indicates the view style. + * @since 1.0 + * @version 1.0 + */ + virtual void SetStyle(Style& style); + + /** + * @brief Sets a style. + * + * @param key Indicates the key of the style to set. + * @param value Indicates the value matching the key. + * @since 1.0 + * @version 1.0 + */ + virtual void SetStyle(uint8_t key, int64_t value); + + /** + * @brief Obtains the value of a style. + * + * @param key Indicates the key of the style. + * @return Returns the value of the style. + * @since 1.0 + * @version 1.0 + */ + virtual int64_t GetStyle(uint8_t key) const + { + return style_->GetStyle(key); + } + + /** + * @brief Obtains the view style. This function applies to scenarios where the style does not need to be modified, + * which saves memory. + * @return Returns the view style. + * @since 1.0 + * @version 1.0 + */ + const Style& GetStyleConst() const + { + return *style_; + } + + /** + * @brief Sets the opacity for the view. + * + * @param opaScale Indicates the opacity to set. + * @since 1.0 + * @version 1.0 + */ + void SetOpaScale(uint8_t opaScale) + { + opaScale_ = opaScale; + } + + /** + * @brief Obtains the view opacity. + * + * @return Returns the view opacity. + * @since 1.0 + * @version 1.0 + */ + uint8_t GetOpaScale() const + { + return opaScale_; + } + + /** + * @brief Obtains the extra message about a UIView instance. This field is ignored by the graphics + * framework and can be anything you set. + * + * @return Returns the pointer to the extra message about the UIView instance. + * @since 5.0 + * @version 3.0 + */ + ViewExtraMsg* GetExtraMsg() + { + return viewExtraMsg_; + } + + /** + * @brief Sets the extra message about a UIView instance. This field is ignored by the graphics + * framework and can be anything you set. + * + * @param extraMsg Indicates the extra message to set. + * @since 5.0 + * @version 3.0 + */ + void SetExtraMsg(ViewExtraMsg* extraMsg) + { + viewExtraMsg_ = extraMsg; + } + + /** + * @brief Rotates the view. + * @param angle Indicates the rotation angle. + * @param pivot Indicates the coordinates of the rotation pivot. + * @since 5.0 + * @version 3.0 + */ + void Rotate(int16_t angle, const Vector2& pivot); + + /** + * @brief Scales the view. + * + * @param scale Indicates the scale factor on x- and y- axes. + * @param pivot Indicates the scaling pivot. + * @since 5.0 + * @version 3.0 + */ + void Scale(const Vector2& scale, const Vector2& pivot); + + void Translate(const Vector2& trans); + + bool IsTransInvalid(); + + void ResetTransParameter(); + +#if ENABLE_ROTATE_INPUT + /** + * @brief 使组件注册获取焦点 + * + * @since 5.0 + * @version 3.0 + */ + virtual void RequestFocus(); + + /** + * @brief 使组件失去焦点 + * + * @since 5.0 + * @version 3.0 + */ + virtual void ClearFocus(); +#endif + +protected: + bool touchable_ : 1; + bool visible_ : 1; + bool draggable_ : 1; + bool dragParentInstead_ : 1; + bool isViewGroup_ : 1; + bool needRedraw_ : 1; + bool styleAllocFlag_ : 1; + bool isIntercept_ : 1; + uint8_t opaScale_; + int16_t index_; + const char* id_; + UIView* parent_; + UIView* nextSibling_; + Style* style_; + TransformMap* transMap_; + OnClickListener* onClickListener_; + OnLongPressListener* onLongPressListener_; + OnDragListener* onDragListener_; + OnTouchListener* onTouchListener_; +#if ENABLE_ROTATE_INPUT + OnRotateListener* onRotateListener_; +#endif + ViewExtraMsg* viewExtraMsg_; + + uint8_t GetMixOpaScale(); + bool IsInvalid(float percent); + +private: + Rect rect_; + Rect* visibleRect_; + void SetupThemeStyles(); +}; +} // namespace OHOS +#endif // GRAPHIC_LITE_UI_VIEW_H diff --git a/interfaces/kits/components/ui_view_group.h b/interfaces/kits/components/ui_view_group.h new file mode 100755 index 0000000..88c4542 --- /dev/null +++ b/interfaces/kits/components/ui_view_group.h @@ -0,0 +1,265 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @addtogroup UI_Components + * @{ + * + * @brief Defines UI components such as buttons, texts, images, lists, and progress bars. + * + * @since 1.0 + * @version 1.0 + */ + +/** + * @file ui_view_group.h + * + * @brief Declares a view group. + * + * @since 1.0 + * @version 1.0 + */ + +#ifndef GRAPHIC_LITE_UI_VIEW_GROUP_H +#define GRAPHIC_LITE_UI_VIEW_GROUP_H + +#include "components/ui_view.h" + +namespace OHOS { +/** + * @brief Represents a view group that consists of its child views. + * + * The child views can be added to, inserted in, and removed from the view group. + * Child views that are added later are displayed at the upper layer of this view group. + * All child views are stored in a linked list. + * + * @since 1.0 + * @version 1.0 + */ +class UIViewGroup : public UIView { +public: + /** + * @brief A default constructor used to create a UIViewGroup instance. + * + * @since 1.0 + * @version 1.0 + */ + UIViewGroup(); + + /** + * @brief A destructor used to delete the UIViewGroup instance. + * + * @since 1.0 + * @version 1.0 + */ + virtual ~UIViewGroup(); + + /** + * @brief Obtains the view type. + * + * @return Returns UI_VIEW_GROUP, as defined in {@link UIViewType}. + * @since 1.0 + * @version 1.0 + */ + UIViewType GetViewType() const override + { + return UI_VIEW_GROUP; + } + + /** + * @brief Adds a child view. + * + * @param view Indicates the pointer to the child view to add. + * @since 1.0 + * @version 1.0 + */ + virtual void Add(UIView* view); + + /** + * @brief Inserts a new child view behind the current one. + * + * @param prevView Indicates the pointer to the current child view, previous to the new child view to insert. + * @param view Indicates the pointer to the new child view to insert. + * @since 1.0 + * @version 1.0 + */ + virtual void Insert(UIView* prevView, UIView* insertView); + + /** + * @brief Removes a child view. + * + * @param view Indicates the pointer to the child view to remove. + * @since 1.0 + * @version 1.0 + */ + virtual void Remove(UIView* view); + + /** + * @brief Removes all child views. + * + * @since 1.0 + * @version 1.0 + */ + virtual void RemoveAll(); + + /** + * @brief Obtains the target child view that is visible and can respond to touch events based on given coordinates. + * + * @param point Indicates the given coordinates. + * @param last Indicates the double pointer to the target view. nullptr indicates that the target + * view is not available. + * @since 1.0 + * @version 1.0 + */ + virtual void GetTargetView(const Point& point, UIView** last) override; + + /** + * @brief Obtains the current view and target view based on specified coordinates. The obtained current view + * must include the specified coordinates and is a visible top view that can respond to touch events, + * and the obtained target view must be the top view that includes the specified coordinates. + * + * @param point Indicates the specified coordinates. + * @param current Indicates the double pointer to the current view to obtain. + * nullptr indicates that the target view fails to be obtained. + * @param target Indicates the double pointer to the target view to obtain. + * nullptr indicates that the target view fails to be obtained. + * @since 5.0 + * @version 3.0 + */ + virtual void GetTargetView(const Point& point, UIView** current, UIView** target) override; + + /** + * @brief Moves all child views. + * + * @param x Indicates the offset distance by which this view group is moved on the x-axis. + * @param y Indicates the offset distance by which this view group is moved on the y-axis. + * @since 1.0 + * @version 1.0 + */ + virtual void MoveChildByOffset(int16_t x, int16_t y); + + /** + * @brief Obtains the first child view in this view group. + * + * @return Returns the first child view. + * @since 1.0 + * @version 1.0 + */ + UIView* GetChildrenHead() const + { + return childrenHead_; + } + + /** + * @brief Obtains the last child view in this view group. + * + * @return Returns the last child view. + * @since 1.0 + * @version 1.0 + */ + UIView* GetChildrenTail() const + { + return childrenTail_; + } + + /** + * @brief Sets whether this view group is intercepted upon touch events. + * + * @param flag Specifies whether this view group is intercepted upon touch events. true indicates that + * this view group is intercepted upon touch events, and false indicates the opposite case. + * @since 1.0 + * @version 1.0 + */ + void SetDisallowIntercept(bool flag) + { + disallowIntercept_ = flag; + } + + /** + * @brief Obtains the target child view with a specified ID. + * + * @param id Indicates the pointer to the ID of the target child view. + * @return Returns the target child view if available; returns nullptr otherwise. + * @since 1.0 + * @version 1.0 + */ + UIView* GetChildById(const char* id) const override; + + /** + * @brief Sets whether the size of this view group is adaptive to that of all child views. + * + * @param state Specifies whether the size of this view group is adaptive to that of all child views. + * true indicates automatic adaption is enabled, and false indicates the opposite case. + * @since 1.0 + * @version 1.0 + */ + void SetAutoSize(bool state) + { + isAutoSize_ = state; + } + +protected: + /** + * @brief Obtains the rectangle area of a new view group after being adaptive to the size of all child views. + * + * @return Returns the rectangle area of the new view group. + * @since 1.0 + * @version 1.0 + */ + Rect GetAllChildRelativeRect() const; + + /** + * @brief Performs operations needed after a child view is added or removed. + * + * @since 1.0 + * @version 1.0 + */ + virtual void OnChildChanged() {}; + + /** + * @brief Indicates the pointer to the first child view of this view group. + */ + UIView* childrenHead_; + + /** + * @brief Indicates the pointer to the last child view of this view group. + */ + UIView* childrenTail_; + + /** + * @brief Represents the number of child views. + */ + uint16_t childrenNum_; + + /** + * @brief Specifies the sliding state of this view group. + */ + bool isDragging_; + + /** + * @brief Specifies whether this view group is intercepted upon touch events. + */ + bool disallowIntercept_; + + /** + * @brief Specifies whether the size of this view group is adaptive to that of all child views. + */ + bool isAutoSize_; + +private: + void AutoResize(); +}; +} // namespace OHOS +#endif // GRAPHIC_LITE_UI_VIEW_GROUP_H diff --git a/interfaces/kits/dfx/event_injector.h b/interfaces/kits/dfx/event_injector.h new file mode 100755 index 0000000..2d20123 --- /dev/null +++ b/interfaces/kits/dfx/event_injector.h @@ -0,0 +1,191 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @addtogroup UI_DFX + * @{ + * + * @brief Provides test and analysis capabilities, such as stimulating input events and viewing information about a + * Document Object Model (DOM) tree. + * + * @since 1.0 + * @version 1.0 + */ + +/** + * @file event_injector.h + * + * @brief Declares functions for simulating input events. + * + * @since 1.0 + * @version 1.0 + */ +#ifndef GRAPHIC_LITE_EVENT_INJECTOR_H +#define GRAPHIC_LITE_EVENT_INJECTOR_H + +#include "graphic_config.h" +#if ENABLE_DEBUG +#include "heap_base.h" +#include "input_event_info.h" +namespace OHOS { +/** + * @ Enumerates the event types. + */ +enum class EventDataType { + /** Point event */ + POINT_TYPE, + /** Key event */ + KEY_TYPE, + /** Other events */ + OTHERS +}; +class PointEventInjector; +class KeyEventInjector; + +/** + * @brief Manages all types of simulated input events, registers and unregisters event injectors, and simulates + * input events. + * + * @since 1.0 + * @version 1.0 + */ +class EventInjector : public HeapBase { +public: + /** + * @brief Obtains a singleton EventInjector instance. + * + * @return Returns the EventInjector instance. + * @since 1.0 + * @version 1.0 + */ + static EventInjector* GetInstance() + { + static EventInjector instance; + return &instance; + } + + /** + * @brief Registers an event injector of a specified type. + * + * @param type Indicates the event type. For details, see {@link EventDataType}. + * @return Returns true if the operation is successful; returns false otherwise. + * @since 1.0 + * @version 1.0 + */ + bool RegisterEventInjector(EventDataType type); + + /** + * @brief Unregisters an event injector of a specified type. + * + * @param type Indicates the event type. For details, see {@link EventDataType}. + * @since 1.0 + * @version 1.0 + */ + void UnregisterEventInjector(EventDataType type); + + /** + * @brief Checks whether the event injector of a specified type is registered. + * + * @param type Indicates the event type. For details, see {@link EventDataType}. + * @return Returns true if the device is registered; returns false otherwise. + * @since 1.0 + * @version 1.0 + */ + bool IsEventInjectorRegistered(EventDataType type) const; + + /** + * @brief Uses a data array of a specified length to simulate an input event of a specified type. + * + * @param dataArray Indicates the pointer to the data array used for simulating the event. + * @param arrayLength Indicates the length of the data array. + * @param type Indicates the event type. For details, see {@link EventDataType}. + * @return Returns true if the operation is successful; returns false otherwise. + * @since 1.0 + * @version 1.0 + */ + bool SetInjectEvent(const DeviceData* dataArray, uint16_t arrayLength, EventDataType type); + + /** + * @brief Stimulates a click event. + * + * @param clickPoint Indicates the coordinates of the click point. + * @return Returns true if the operation is successful; returns false otherwise. + * @since 3.0 + * @version 5.0 + */ + bool SetClickEvent(const Point& clickPoint); + + /** + * @brief Stimulates a long press event. + * + * @param longPressPoint Indicates the coordinates of the long press point. + * @return Returns true if the operation is successful; returns false otherwise. + * @since 3.0 + * @version 5.0 + */ + bool SetLongPressEvent(const Point& longPressPoint); + + /** + * @brief Stimulates a drag event that occurs between two points. + * + * @param startPoint Indicates the coordinates of the start point. + * @param endPoint Indicates the coordinates of the end point. + * @param dragTime Indicates the duration of dragging from the start point to the end point, in milliseconds. + * The value range is [2, 499] x {@link INDEV_READ_PERIOD}. + * The shorter the duration is, the faster the sliding is. + * + * @return Returns true if the operation is successful; returns false otherwise. + * @since 3.0 + * @version 5.0 + */ + bool SetDragEvent(const Point& startPoint, const Point& endPoint, uint32_t dragTime); + + /** + * @brief Simulates a key event. + * + * @param keyId Indicates the key ID. + * @param state Indicates the key state. For details, see {@link InputDevice}. + * @return Returns true if the operation is successful; returns false otherwise. + * @since 3.0 + * @version 5.0 + */ + bool SetKeyEvent(uint16_t keyId, uint16_t state); + +#if ENABLE_WINDOW + /** + * @brief Set id of mock window. + * + * @param windowId id of mock window + * + * @since 3.0 + * @version 5.0 + */ + void SetWindowId(uint8_t windowId); +#endif +private: + EventInjector() : pointEventInjector_(nullptr), keyEventInjector_(nullptr) {} + virtual ~EventInjector(); + + EventInjector(const EventInjector&) = delete; + EventInjector& operator=(const EventInjector&) = delete; + EventInjector(EventInjector&&) = delete; + EventInjector& operator=(EventInjector&&) = delete; + + PointEventInjector* pointEventInjector_; + KeyEventInjector* keyEventInjector_; +}; +} // namespace OHOS +#endif // ENABLE_DEBUG +#endif // GRAPHIC_LITE_EVENT_INJECTOR_H diff --git a/interfaces/kits/dfx/ui_dump_dom_tree.h b/interfaces/kits/dfx/ui_dump_dom_tree.h new file mode 100755 index 0000000..3b94471 --- /dev/null +++ b/interfaces/kits/dfx/ui_dump_dom_tree.h @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @addtogroup UI_DFX + * @{ + * + * @brief Provides test and analysis capabilities, such as stimulating input events and viewing information about a + * Document Object Model (DOM) tree. + * + * @since 1.0 + * @version 1.0 + */ + +/** + * @file ui_dump_dom_tree.h + * + * @brief Declares a function for exporting information about a Document Object Model (DOM) tree or DOM node. + * + * @since 1.0 + * @version 1.0 + */ +#ifndef GRAPHIC_LITE_UI_DUMP_DOM_TREE_H +#define GRAPHIC_LITE_UI_DUMP_DOM_TREE_H + +#include "graphic_config.h" +#include "heap_base.h" +#if ENABLE_DEBUG +#include "cJSON.h" +#include "components/ui_view.h" +#endif // ENABLE_DEBUG +namespace OHOS { +#if ENABLE_DEBUG +/** + * @brief Enumerates export modes. + */ +enum DumpMode { + /** DOM tree */ + DUMP_TREE, + /** DOM node */ + DUMP_NODE +}; +#endif // ENABLE_DEBUG + +/** + * @brief Provides functions for exporting information about a specified DOM node or information about the DOM tree + * starting from a specified DOM node. + * + * @since 1.0 + * @version 1.0 + */ +class UIDumpDomTree : public HeapBase { +public: + /** + * @brief Obtains an instance in singleton pattern. + * + * @return Returns the function instance for exporting DOM information. + * @since 1.0 + * @version 1.0 + */ + static UIDumpDomTree* GetInstance() + { + static UIDumpDomTree instance; + return &instance; + } + + /** + * @brief Exports information about a DOM tree starting from a specified DOM node and saves the information to + * a specified path. + * + * @param id Indicates the pointer to the DOM node ID. + * @param path Indicates the pointer to the specified path. + * @return Returns true if the operation is successful; returns false otherwise. + * @since 1.0 + * @version 1.0 + */ + bool DumpDomTree(const char* id, const char* path); + + /** + * @brief Exports information about a DOM tree starting from a specified DOM node and saves the information to + * the default path. + * The default path is {@link DEFAULT_DUMP_DOM_TREE_PATH}. + * + * @param id Indicates the pointer to the DOM node ID. + * @return Returns true if the operation is successful; returns false otherwise. + * @since 1.0 + * @version 1.0 + */ + bool DumpDomTree(const char* id) + { + return DumpDomTree(id, nullptr); + } + + /** + * @brief Exports information about a specified DOM node. + * + * @param id Indicates the pointer to the DOM node ID. + * @return Returns a character string about the DOM node information. The character string memory is released by + * invoking cJSON_free(). + * @since 1.0 + * @version 1.0 + */ + char* DumpDomNode(const char* id); + +private: +#if ENABLE_DEBUG + bool searchFlag_; // Search flag, set when find the view with right id. + bool allocErrorFlag_; // Alloc error flag, set when cJSON fails to allocate memory. + char* pJson_; // Pointer for returning cJSON string. + cJSON* rootJson_; // Root pointer of cJSON structure. + const char* path_; + + void DumpJsonById(UIView* view, const char* id, DumpMode mode); + + void AddNameField(UIViewType type, cJSON* usr) const; + void AddCommonField(UIView* view, cJSON* usr) const; + void AddImageViewSpecialField(const UIView* view, cJSON* usr) const; + void AddSpecialField(const UIView* view, cJSON* usr) const; + void OutputDomNode(UIView* view); + void OutputDomTree(UIView* view, cJSON* usr); + bool WriteDumpFile() const; + + UIDumpDomTree() + : searchFlag_(false), + allocErrorFlag_(false), + pJson_(nullptr), + rootJson_(nullptr), + path_(DEFAULT_DUMP_DOM_TREE_PATH) + { + } +#else + UIDumpDomTree() {} +#endif // ENABLE_DEBUG + virtual ~UIDumpDomTree() {} + + UIDumpDomTree(const UIDumpDomTree&) = delete; + UIDumpDomTree& operator=(const UIDumpDomTree&) = delete; + UIDumpDomTree(UIDumpDomTree&&) = delete; + UIDumpDomTree& operator=(UIDumpDomTree&&) = delete; +}; +} // namespace OHOS +#endif // GRAPHIC_LITE_UI_DUMP_DOM_TREE_H diff --git a/interfaces/kits/dfx/ui_screenshot.h b/interfaces/kits/dfx/ui_screenshot.h new file mode 100755 index 0000000..27e1225 --- /dev/null +++ b/interfaces/kits/dfx/ui_screenshot.h @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @addtogroup UI_DFX + * @{ + * + * @brief Provides test and analysis capabilities, such as stimulating input events and viewing information about a + * Document Object Model (DOM) tree. + * + * @since 1.0 + * @version 1.0 + */ + +/** + * @file ui_screenshot.h + * + * @brief Declares the screenshot function. + * + * @since 1.0 + * @version 1.0 + */ +#ifndef GRAPHIC_LITE_UI_SCREENSHOT_H +#define GRAPHIC_LITE_UI_SCREENSHOT_H + +#include "graphic_config.h" + +#if ENABLE_DEBUG +#include "heap_base.h" + +namespace OHOS { +/** + * @brief Provides callback functions for screenshot events. + * + * @since 1.0 + * @version 1.0 + */ +class UIScreenshotListener; + +/** + * @brief Provides external screenshot functions. + * + * @since 1.0 + * @version 1.0 + */ +class UIScreenshot : public HeapBase { +public: + /** + * @brief Obtains an instance in singleton pattern. + * + * @return Returns the screenshot function instance. + * @since 1.0 + * @version 1.0 + */ + static UIScreenshot* GetInstance() + { + static UIScreenshot instance; + return &instance; + } + + /** + * @brief Takes a screenshot and saves it to a file. + * + * @param path Indicates the pointer to the path for storing the screenshot file. + * @return Returns true if the operation is successful; returns false otherwise. + * @since 1.0 + * @version 1.0 + */ + bool ScreenshotToFile(const char* path); + +private: + UIScreenshotListener* screenshotListener_; + + UIScreenshot() : screenshotListener_(nullptr) {} + virtual ~UIScreenshot(); + + UIScreenshot(const UIScreenshot&) = delete; + UIScreenshot& operator=(const UIScreenshot&) = delete; + UIScreenshot(UIScreenshot&&) = delete; + UIScreenshot& operator=(UIScreenshot&&) = delete; +}; +} // namespace OHOS +#endif // ENABLE_DEBUG +#endif // GRAPHIC_LITE_UI_SCREENSHOT_H diff --git a/interfaces/kits/events/aod_callback.h b/interfaces/kits/events/aod_callback.h new file mode 100755 index 0000000..7f0698d --- /dev/null +++ b/interfaces/kits/events/aod_callback.h @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @addtogroup UI_Events + * @{ + * + * @brief Defines UI events, such as press, click and drag events. + * + * @since 1.0 + * @version 1.0 + */ + +/** + * @file aod_callback.h + * + * @brief Defines the callback for the touch event notification in screen-off mode. + * + * @since 1.0 + * @version 1.0 + */ + +#include "components/ui_view.h" +#include "graphic_config.h" + +#ifndef AOD_CALLBACK_H +#define AOD_CALLBACK_H + +#if ENABLE_AOD +namespace OHOS { +/** + * @brief Called when a tap event occurs on the state machine. The screen-off display module will be notified of this + * event. + * + * @param view Indicates the view instance where the tap event occurs. + */ +void OnClickEventHappen(UIView& view); + +/** + * @brief Called when a sliding end event occurs on the state machine. The screen-off display module will be notified of + * this event. + * + * @param view Indicates the view instance where the sliding end event occurs. + */ +void OnDragEndEventHappen(UIView& view); + +/** + * @brief Called when a press event occurs on the state machine. The screen-off display module will be notified of this + * event. + * + * @param view the attached view + */ +void OnLongPressEventHappen(UIView& view); +} // namespace OHOS +#endif // ENABLE_AOD +#endif // AOD_CALLBACK_H diff --git a/interfaces/kits/events/cancel_event.h b/interfaces/kits/events/cancel_event.h new file mode 100755 index 0000000..d05c43a --- /dev/null +++ b/interfaces/kits/events/cancel_event.h @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @addtogroup UI_Events + * @{ + * + * @brief Defines UI events, such as press, click and drag events. + * + * @since 1.0 + * @version 1.0 + */ + +/** + * @file cancel_event.h + * + * @brief Declares a cancel event, which usually occurs when the target view changes due to user sliding. For example, + this event is triggered if you, after pressing a button, does not release the button but slides. + * + * @since 1.0 + * @version 1.0 + */ + +#ifndef GRAPHIC_LITE_CANCEL_EVENT_H +#define GRAPHIC_LITE_CANCEL_EVENT_H + +#include "event.h" + +namespace OHOS { +/** + * @brief Defines a cancel event, which usually occurs when the target view changes due to user sliding. For example, + this event is triggered if you, after pressing a button, does not release the button but slides. + * @since 1.0 + * @version 1.0 + */ +class CancelEvent : public Event { +public: + /** + * @brief A constructor used to create a CancelEvent instance. + * @param pos Indicates the position where the cancel event occurs. + * @since 1.0 + * @version 1.0 + */ + CancelEvent(const Point& pos) : Event(pos) {} + + /** + * @brief A destructor used to delete the CancelEvent instance. + * @since 1.0 + * @version 1.0 + */ + virtual ~CancelEvent() {} +}; +} // namespace OHOS +#endif // GRAPHIC_LITE_CANCEL_EVENT_H diff --git a/interfaces/kits/events/click_event.h b/interfaces/kits/events/click_event.h new file mode 100755 index 0000000..f29d8e8 --- /dev/null +++ b/interfaces/kits/events/click_event.h @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @addtogroup UI_Events + * @{ + * + * @brief Defines UI events, such as press, click and drag events. + * + * @since 1.0 + * @version 1.0 + */ + +/** + * @file click_event.h + * + * @brief Declares a click event, which indicates that a finger is pressed on the screen and then + * lifted within 1 second. + * + * @since 1.0 + * @version 1.0 + */ + +#ifndef GRAPHIC_LITE_CLICK_EVENT_H +#define GRAPHIC_LITE_CLICK_EVENT_H + +#include "event.h" + +namespace OHOS { +/** + * @brief Defines a click event, which indicates that a finger is pressed on the screen and then lifted within 1 second. + * + * @since 1.0 + * @version 1.0 + */ +class ClickEvent : public Event { +public: + /** + * @brief A constructor used to create a ClickEvent instance. + * @param pos Indicates the position where the click event occurs. + * @since 1.0 + * @version 1.0 + */ + ClickEvent(const Point& pos) : Event(pos) {} + + /** + * @brief A destructor used to delete the ClickEvent instance. + * @since 1.0 + * @version 1.0 + */ + virtual ~ClickEvent() {} +}; +} // namespace OHOS +#endif // GRAPHIC_LITE_CLICK_EVENT_H diff --git a/interfaces/kits/events/drag_event.h b/interfaces/kits/events/drag_event.h new file mode 100755 index 0000000..2bd11ef --- /dev/null +++ b/interfaces/kits/events/drag_event.h @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @addtogroup UI_Events + * @{ + * + * @brief Defines UI events, such as press, click and drag events. + * + * @since 1.0 + * @version 1.0 + */ + +/** + * @file drag_event.h + * + * @brief Declares a drag event, which indicates a certain movement (more than 10 pixels) after a view is pressed. + * + * @since 1.0 + * @version 1.0 + */ + +#ifndef GRAPHIC_LITE_DRAG_EVENT_H +#define GRAPHIC_LITE_DRAG_EVENT_H + +#include "event.h" + +namespace OHOS { +/** + * @brief Defines a drag event, which indicates a certain movement (more than 10 pixels) after a view is pressed. + * + * @since 1.0 + * @version 1.0 + */ +class DragEvent : public Event { +public: + /** + * @brief A constructor used to create a DragEvent instance. + * @param newPos Indicates the new position in the drag event. + * @param lastPos Indicates the last position in the drag event. + * @param totalLen Indicates the distance that the view has been dragged, including the movement on the x-axis + * and y-axis. + * @since 1.0 + * @version 1.0 + */ + DragEvent(const Point& newPos, const Point& lastPos, const Point& totalLen) : Event(newPos) + { + lastPos_ = lastPos; + startPos_.x = newPos.x - totalLen.x; + startPos_.y = newPos.y - totalLen.y; + preLastPos_ = lastPos; + deltaX_ = newPos.x - lastPos.x; + deltaY_ = newPos.y - lastPos.y; + } + + /** + * @brief A destructor used to delete the DragEvent instance. + * @since 1.0 + * @version 1.0 + */ + virtual ~DragEvent() {} + + /** + * @brief Obtains the coordinates of the last position in the drag event. + * @return Returns the coordinates of the last position. + * @since 1.0 + * @version 1.0 + */ + const Point& GetLastPoint() const + { + return lastPos_; + } + + /** + * @brief Obtains the start coordinates in the drag event. + * @return Returns the start coordinates. + * @since 1.0 + * @version 1.0 + */ + const Point& GetStartPoint() const + { + return startPos_; + } + + /** + * @brief Sets the coordinates of the stay position before the last position in the drag event. + * @param preLastPos Indicates the coordinates of the stay position to set. + * @since 1.0 + * @version 1.0 + */ + void SetPreLastPoint(const Point& preLastPos) + { + preLastPos_ = preLastPos; + } + + /** + * @brief Obtains the coordinates of the stay position before the last position in the drag event. + * @return Returns the coordinates of the stay position. + * @since 1.0 + * @version 1.0 + */ + const Point& GetPreLastPoint() const + { + return preLastPos_; + } + + /** + * @brief Obtains the direction in the drag event. + * @return Returns the direction. Available values are as follows: + * 0 indicates dragging from left to right. + * 1 indicates dragging from right to left. + * 2 indicates dragging from top to bottom. + * 3 indicates dragging from bottom to top. + * @since 1.0 + * @version 1.0 + */ + uint8_t GetDragDirection() const + { + if (MATH_ABS(curPos_.x - startPos_.x) >= MATH_ABS(curPos_.y - startPos_.y)) { + return (curPos_.x > startPos_.x) ? DIRECTION_LEFT_TO_RIGHT : DIRECTION_RIGHT_TO_LEFT; + } else { + return (curPos_.y > startPos_.y) ? DIRECTION_TOP_TO_BOTTOM : DIRECTION_BOTTOM_TO_TOP; + } + } + + /** + * @brief Obtains the difference between the current position and the last position of the view in the x-axis. + * @return Returns the different in the x-aix. + * @since 1.0 + * @version 1.0 + */ + int16_t GetDeltaX() const + { + return deltaX_; + } + + /** + * @brief Obtains the difference between the current position and the last position of the view in the y-axis. + * @return Returns the different in the y-aix. + * @since 1.0 + * @version 1.0 + */ + int16_t GetDeltaY() const + { + return deltaY_; + } + + static constexpr uint8_t DIRECTION_LEFT_TO_RIGHT = 0; + static constexpr uint8_t DIRECTION_RIGHT_TO_LEFT = 1; + static constexpr uint8_t DIRECTION_TOP_TO_BOTTOM = 2; + static constexpr uint8_t DIRECTION_BOTTOM_TO_TOP = 3; + +private: + Point lastPos_; + Point startPos_; + Point preLastPos_; + int16_t deltaX_; + int16_t deltaY_; +}; +} // namespace OHOS +#endif // GRAPHIC_LITE_DRAG_EVENT_H diff --git a/interfaces/kits/events/event.h b/interfaces/kits/events/event.h new file mode 100755 index 0000000..b92f57c --- /dev/null +++ b/interfaces/kits/events/event.h @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @addtogroup UI_Events + * @{ + * + * @brief Defines UI events, such as press, click and drag events. + * + * @since 1.0 + * @version 1.0 + */ + +/** + * @file event.h + * + * @brief Declares the base class of a user input event and provides functions to record information such as + * the event position and timestamp. + * + * @since 1.0 + * @version 1.0 + */ + +#ifndef GRAPHIC_LITE_EVENT_H +#define GRAPHIC_LITE_EVENT_H + +#include "geometry2d.h" +#include "graphic_types.h" + +namespace OHOS { +class UIView; +/** + * @brief Defines the base class of a user input event and provides functions to record information such as + * the event position and timestamp. + * + * @since 1.0 + * @version 1.0 + */ +class Event : public HeapBase { +public: + /** + * @brief A default constructor used to create an Event instance. + * @since 1.0 + * @version 1.0 + */ + Event(); + + /** + * @brief A constructor used to create an Event instance. + * @param pos Indicates the position where the event occurs. + * @since 1.0 + * @version 1.0 + */ + Event(const Point& curPos); + + /** + * @brief A destructor used to delete the Event instance. + * @since 1.0 + * @version 1.0 + */ + virtual ~Event() {} + + /** + * @brief Obtains the position where an event occurs. + * @return Returns the position. + * @since 1.0 + * @version 1.0 + */ + const Point& GetCurrentPos() const + { + return curPos_; + } + + /** + * @brief Obtains the timestamp when an event occurs. + * @return Returns the timestamp. + * @since 1.0 + * @version 1.0 + */ + const TimeType& GetTimeStamp() const + { + return timeStamp_; + } + + /** + * @brief Sets the timestamp when an event occurs. + * @param timeStamp Indicates the timestamp of the event to set. + * @since 1.0 + * @version 1.0 + */ + void SetTimeStamp(const TimeType& timeStamp) + { + timeStamp_ = timeStamp; + } +protected: + TimeType timeStamp_; + Point curPos_; + UIView* targetView_ = nullptr; + UIView* currentView_ = nullptr; +}; +} // namespace OHOS +#endif // GRAPHIC_LITE_EVENT_H diff --git a/interfaces/kits/events/key_event.h b/interfaces/kits/events/key_event.h new file mode 100755 index 0000000..555edbc --- /dev/null +++ b/interfaces/kits/events/key_event.h @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @addtogroup UI_Events + * @{ + * + * @brief Defines UI events, such as press, click and drag events. + * + * @since 1.0 + * @version 1.0 + */ + +/** + * @file key_event.h + * + * @brief Declares a key event, which indicates that a physical button is pressed or released. + * + * @since 1.0 + * @version 1.0 + */ + +#ifndef GRAPHIC_LITE_KEY_EVENT_H +#define GRAPHIC_LITE_KEY_EVENT_H + +#include "event.h" + +namespace OHOS { +constexpr uint16_t INVALID_KEY_STATE = UINT16_MAX; + +/** + * @brief Defines a key event, which indicates that a physical button is pressed or released. + * + * @since 1.0 + * @version 1.0 + */ +class KeyEvent : public Event { +public: + KeyEvent() = delete; + + /** + * @brief A constructor used to create a KeyEvent instance. + * @param keyId Indicates the key ID. + * @param state Indicates the key state. + * @since 1.0 + * @version 1.0 + */ + KeyEvent(uint16_t keyId, uint16_t state) : keyId_(keyId), state_(state) {} + + /** + * @brief A destructor used to delete the KeyEvent instance. + * @since 1.0 + * @version 1.0 + */ + ~KeyEvent() {} + + /** + * @brief Obtains the key ID. + * @since 1.0 + * @version 1.0 + */ + uint16_t GetKeyId() const + { + return keyId_; + } + + /** + * @brief Obtains the key state. + * @since 1.0 + * @version 1.0 + */ + uint16_t GetState() const + { + return state_; + } + +private: + uint16_t keyId_; + uint16_t state_; +}; +} // namespace OHOS +#endif // GRAPHIC_LITE_KEY_EVENT_H diff --git a/interfaces/kits/events/long_press_event.h b/interfaces/kits/events/long_press_event.h new file mode 100755 index 0000000..a0c53f5 --- /dev/null +++ b/interfaces/kits/events/long_press_event.h @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @addtogroup UI_Events + * @{ + * + * @brief Defines UI events, such as press, click and drag events. + * + * @since 1.0 + * @version 1.0 + */ + +/** + * @file long_press_event.h + * + * @brief Declares a long-press event, which indicates that the finger is not raised or moved within 1 second + * after a view is pressed. + * + * @since 1.0 + * @version 1.0 + */ + +#ifndef GRAPHIC_LITE_LONG_PRESS_EVENT_H +#define GRAPHIC_LITE_LONG_PRESS_EVENT_H + +#include "event.h" + +namespace OHOS { +/** + * @brief Defines a long-press event, which indicates that the finger is not raised or moved within 1 second + * after a view is pressed. + * + * @since 1.0 + * @version 1.0 + */ +class LongPressEvent : public Event { +public: + /** + * @brief A constructor used to create a LongPressEvent instance. + * @param pos Indicates the position where the long-press event occurs. + * @param timeStamp Indicates the time stamp when the long-press event occurs. + * @since 1.0 + * @version 1.0 + */ + LongPressEvent(const Point& pos, const TimeType& timeStamp) : Event(pos) + { + timeStamp_ = timeStamp; + } + + /** + * @brief A destructor used to delete the LongPressEvent instance. + * @since 1.0 + * @version 1.0 + */ + virtual ~LongPressEvent() {} +}; +} // namespace OHOS +#endif // GRAPHIC_LITE_LONG_PRESS_EVENT_H diff --git a/interfaces/kits/events/press_event.h b/interfaces/kits/events/press_event.h new file mode 100755 index 0000000..f598658 --- /dev/null +++ b/interfaces/kits/events/press_event.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GRAPHIC_LITE_PRESS_EVENT_H +#define GRAPHIC_LITE_PRESS_EVENT_H + +/** + * @addtogroup UI_Events + * @{ + * + * @brief Defines UI events, such as press, click and drag events. + * + * @since 1.0 + * @version 1.0 + */ + +/** + * @file press_event.h + * + * @brief Declares a touch event, which indicates that the touchscreen or mouse is pressed. + * + * @since 1.0 + * @version 1.0 + */ + +#include "event.h" + +namespace OHOS { +/** + * @brief Defines a touch event, which indicates that the touchscreen or mouse is pressed. + * + * @since 1.0 + * @version 1.0 + */ +class PressEvent : public Event { +public: + /** + * @brief A constructor used to create a PressEvent instance. + * @param pos Indicates the position where the press event occurs. + * @since 1.0 + * @version 1.0 + */ + PressEvent(const Point& pos) : Event(pos) {} + + /** + * @brief A destructor used to delete the PressEvent instance. + * @since 1.0 + * @version 1.0 + */ + virtual ~PressEvent() {} +}; +} // namespace OHOS +#endif // GRAPHIC_LITE_PRESS_EVENT_H diff --git a/interfaces/kits/events/release_event.h b/interfaces/kits/events/release_event.h new file mode 100755 index 0000000..931cc8d --- /dev/null +++ b/interfaces/kits/events/release_event.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @addtogroup UI_Events + * @{ + * + * @brief Defines UI events, such as press, click and drag events. + * + * @since 1.0 + * @version 1.0 + */ + +/** + * @file release_event.h + * + * @brief Declares a release event, which indicates that the touchscreen or mouse is released. + * + * @since 1.0 + * @version 1.0 + */ + +#ifndef GRAPHIC_LITE_RELEASE_EVENT_H +#define GRAPHIC_LITE_RELEASE_EVENT_H + +#include "event.h" + +namespace OHOS { +/** + * @brief Defines a release event, which indicates that the touchscreen or mouse is released. + * + * @since 1.0 + * @version 1.0 + */ +class ReleaseEvent : public Event { +public: + /** + * @brief A constructor used to create a ReleaseEvent instance. + * @param pos Indicates the position where the release event occurs. + * @since 1.0 + * @version 1.0 + */ + ReleaseEvent(const Point& pos) : Event(pos) {} + + /** + * @brief A destructor used to delete the ReleaseEvent instance. + * @since 1.0 + * @version 1.0 + */ + virtual ~ReleaseEvent() {} +}; +} // namespace OHOS +#endif // GRAPHIC_LITE_RELEASE_EVENT_H diff --git a/interfaces/kits/events/rotate_event.h b/interfaces/kits/events/rotate_event.h new file mode 100755 index 0000000..14da4ce --- /dev/null +++ b/interfaces/kits/events/rotate_event.h @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GRAPHIC_LITE_ROTATE_EVENT_H +#define GRAPHIC_LITE_ROTATE_EVENT_H +/** + * @addtogroup Graphic + * @{ + * + * @brief Defines a lightweight graphics system that provides basic UI and container views, + * including buttons, images, labels, lists, animators, scroll views, swipe views, and layouts. + * This system also provides the Design for X (DFX) capability to implement features such as + * view rendering, animation, and input event distribution. + * + * @since 1.0 + * @version 1.0 + */ + +/** + * @file rotate_event.h + * + * @brief ת¼RotateEventťӲ + * + * @since 5.0 + * @version 3.0 + */ + +#include "event.h" + +namespace OHOS { +/** + * @brief ת¼RotateEventťӲ + * + * @since 5.0 + * @version 3.0 + */ +class RotateEvent : public Event { +public: + RotateEvent() = delete; + + /** + * @brief ת¼RotateEventʵĹ캯. + * @param rotate shortתֵ + * + * @since 5.0 + * @version 3.0 + */ + RotateEvent(int16_t rotate) : rotate_(-rotate * ROTATE_SENSITIVITY) {} // תת + + ~RotateEvent() {}; + + /** + * @brief ȡתֵ + * @return תֵ + * + * @since 5.0 + * @version 3.0 + */ + int16_t GetRotate() const + { + return rotate_; + } + +private: + int16_t rotate_; +}; +} // namespace OHOS +#endif // GRAPHIC_LITE_ROTATE_EVENT_H \ No newline at end of file diff --git a/interfaces/kits/events/virtual_device_event.h b/interfaces/kits/events/virtual_device_event.h new file mode 100755 index 0000000..80333d4 --- /dev/null +++ b/interfaces/kits/events/virtual_device_event.h @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @addtogroup UI_Events + * @{ + * + * @brief Defines UI events, such as press, click and drag events. + * + * @since 1.0 + * @version 1.0 + */ + +/** + * @file virtual_device_event.h + * + * @brief Declares a virtual device event, which is used to receive a customized input event and call back + * the listening function registered. + * + * @since 1.0 + * @version 1.0 + */ + +#ifndef GRAPHIC_LITE_VIRTUAL_DEVICE_EVENT_H +#define GRAPHIC_LITE_VIRTUAL_DEVICE_EVENT_H + +#include "event.h" + +namespace OHOS { +/** + * @brief Defines a virtual device event, which is used to receive a customized input event and call back + * the listening function registered. + * + * @since 1.0 + * @version 1.0 + */ +class VirtualDeviceEvent : public Event { +public: + VirtualDeviceEvent() = delete; + + /** + * @brief A constructor used to create a VirtualDeviceEvent instance. + * + * @param type Indicates the virtual device type. The options are AOD and PRIVATE. + * @param value Indicates the virtual event value. + * @since 1.0 + * @version 1.0 + */ + VirtualDeviceEvent(uint16_t type, uint16_t value) : type_(type), state_(value) {} + + /** + * @brief A destructor used to delete the VirtualDeviceEvent instance. + * + * @since 1.0 + * @version 1.0 + */ + ~VirtualDeviceEvent() {} + + /** + * @brief Enumerates virtual device types. + * + */ + enum DeviceType { + AOD, // Standby event + PRIVATE, // Other customized events + }; + + /** + * @brief Obtains the type of the virtual device. + * + * @return Returns the type. + * @since 1.0 + * @version 1.0 + */ + uint16_t GetType() const + { + return type_; + } + + /** + * @brief Obtains the state of the virtual event. + * + * @return Returns the state. + * @since 1.0 + * @version 1.0 + */ + uint16_t GetState() const + { + return state_; + } + +private: + uint16_t type_; + uint16_t state_; + static constexpr uint16_t INVALID_VIRTUAL_INPUT_STATE = UINT16_MAX; +}; +} // namespace OHOS +#endif // GRAPHIC_LITE_VIRTUAL_DEVICE_EVENT_H diff --git a/interfaces/kits/font/base_font.h b/interfaces/kits/font/base_font.h new file mode 100755 index 0000000..fe055f6 --- /dev/null +++ b/interfaces/kits/font/base_font.h @@ -0,0 +1,222 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef BASE_FONT +#define BASE_FONT +#include "font/ui_font_header.h" +#include "graphic_config.h" + +namespace OHOS { +class BaseFont : public HeapBase { +public: + BaseFont() : fontId_(0), ramAddr_(0), ramLen_(0) {} + virtual ~BaseFont() {} + + /** + * @brief Indicates whether the current font library is a vector font library. + * @return uint8_t: 0 BitmapFont 1 VectorFont + */ + virtual bool IsVectorFont() const = 0; + + /** + * @brief Set font by id + * + * @param fontid [in] the font id + * @param size [in] the font size + * @return int8_t: -1 failed, 0 success + */ + virtual int8_t SetCurrentFontId(uint8_t fontId, uint8_t size) = 0; + + /** + * @brief Get height for specific font + * + * @return uint16_t + */ + virtual uint16_t GetHeight() = 0; + + /** + * @brief Get font id + * + * @param ttfName + * @param size 0: invaild size + * @return uint8_t + */ + virtual uint8_t GetFontId(const char* ttfName, uint8_t size) const = 0; + + /** + * @brief Get width + * + * @param unicode + * @return int16_t + */ + virtual int16_t GetWidth(uint32_t unicode, uint8_t fontId) = 0; + + virtual int32_t OpenVectorFont(uint8_t ttfId) + { + return -1; + } + /** + * @brief Get bitmap for specific unicode + * + * @param unicode + * @return uint8_t* + */ + virtual uint8_t* GetBitmap(uint32_t unicode, GlyphNode& glyphNode, uint8_t fontId) = 0; + + /** + * @brief Get font header + * + * @param fontHeader + * @return int8_t + */ + virtual int8_t GetCurrentFontHeader(FontHeader& fontHeader) = 0; + + /** + * @brief Get the glyph node + * + * @param unicode + * @param glyphNode + * @param isGlyph + * @return int8_t + */ + virtual int8_t GetGlyphNode(uint32_t unicode, GlyphNode& glyphNode) = 0; + virtual uint8_t GetFontWeight(uint8_t fontId) = 0; + + virtual int8_t SetCurrentLangId(uint8_t langId) + { + return 0; + } + + virtual uint8_t GetCurrentLangId() const + { + return GetTotalLangId(); + } + + int8_t GetDefaultParamByLangId(uint8_t langId, LangTextParam** pParam) const; + + /** + * @brief Get the Font Shaping Property + * @param text [in] the content + * @param ttfId [out] the ttf id + * @param fontId [in] the font id + * @param size [in] the font size + * @return uint8_t: needShaping property + */ + virtual uint8_t GetShapingFontId(char* text, uint8_t& ttfId, uint32_t& script, uint8_t fontId, uint8_t size) const + { + return 0; + } + + /** + * @brief Set the Font Path + * + * @param dpath + * @param spath + * @return int8_t + */ + virtual int8_t SetFontPath(const char* dpath, const char* spath) = 0; + + virtual int8_t GetFontVersion(char* dVersion, uint8_t dLen, char* sVersion, uint8_t sLen) const + { + return INVALID_RET_VALUE; + } + + /** + * @brief Get the text in utf-8 format + * + * @param textId + * @param utf8Addr + * @param utf8Len + * @return int8_t + */ + virtual int8_t GetTextUtf8(uint16_t textId, uint8_t** utf8Addr, uint16_t& utf8Len) const + { + return 0; + } + + /** + * @brief Get the ttfId + * @param fontId [in] the font id + * @param size [in] the font size + * @return uint8_t: ttfId property + */ + virtual uint8_t GetFontTtfId(uint8_t fontId, uint8_t size) const + { + return 0; + } + + virtual const UITextLanguageFontParam* GetFontInfo(uint8_t fontId) const + { + return nullptr; + } + + virtual uint8_t RegisterFontInfo(const char* ttfName, uint8_t shaping) + { + return 0; + } + + virtual uint8_t RegisterFontInfo(const UITextLanguageFontParam* fontsTable, uint8_t num) + { + return 0; + } + + virtual uint8_t UnregisterFontInfo(const char* ttfName) + { + return 0; + } + + virtual uint8_t UnregisterFontInfo(const UITextLanguageFontParam* fontsTable, uint8_t num) + { + return 0; + } + + virtual int8_t GetWildCardStaticStr(uint16_t textId, + UITextWildcardStaticType type, + uint8_t** strAddr, + uint16_t& strLen) const + { + return 0; + } + + virtual int8_t GetCodePoints(uint16_t textId, uint32_t** codePoints, uint16_t& codePointsNum) const + { + return 0; + } + + virtual int8_t GetTextParam(uint16_t textId, UITextLanguageTextParam& param) const + { + return 0; + } + + /** + * @brief Get current font id + * + * @return uint8_t + */ + void SetBaseFontId(uint8_t fontId); + uint8_t GetBaseFontId(); + void SetRamAddr(uintptr_t ramAddr); + uintptr_t GetRamAddr(); + uint32_t GetRamLen(); + void SetRamLen(uint32_t ramLen); + void SetPsramMemory(uintptr_t psramAddr, uint32_t psramLen); + +private: + uint8_t fontId_; + uintptr_t ramAddr_; + uint32_t ramLen_; +}; +} // namespace OHOS +#endif /* UI_BASE_FONT_H */ diff --git a/interfaces/kits/font/ui_font.h b/interfaces/kits/font/ui_font.h new file mode 100755 index 0000000..e1ef36c --- /dev/null +++ b/interfaces/kits/font/ui_font.h @@ -0,0 +1,238 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef UI_FONT_H +#define UI_FONT_H +#include "font/base_font.h" + +namespace OHOS { +class UIFont : public HeapBase { +public: + UIFont(const UIFont &) = delete; + UIFont& operator=(const UIFont &) = delete; + + /** + * @brief judge whether you need shaping for text + * + * @param text means input text + * @param ttfId font index + * @param script shaping script + * @param fontId font index + * @param size font size + * @return int8_t Shaping mode + */ + uint8_t GetShapingFontId(char* text, uint8_t& ttfId, uint32_t& script, uint8_t fontId, uint8_t size) const + { + return instance_->GetShapingFontId(text, ttfId, script, fontId, size); + } + + /** + * @brief Set font id + * + * @param fontId + * @param size + * @return int8_t + */ + int8_t SetCurrentFontId(uint8_t fontId, uint8_t size = 0) + { + return instance_->SetCurrentFontId(fontId, size); + } + + /** + * @brief Get width of the letter + * + * @param unicode: [in] unicode or glyph index according to isGlyph param + * @param shapingId: [in] + * @return uint16_t: the letter width + */ + uint16_t GetWidth(uint32_t unicode, uint8_t shapingId); + + /** + * @brief Get height for specific font + * + * @return uint16_t + */ + uint16_t GetHeight() + { + return instance_->GetHeight(); + } + + /** + * @brief Get the font weight + * @param fontId + * @return uint8_t: fontWeight + */ + uint8_t GetFontWeight(uint8_t fontId) + { + return instance_->GetFontWeight(fontId); + } + + /** + * @brief Get font header + * + * @param fontHeader + * @return int8_t + */ + int8_t GetCurrentFontHeader(FontHeader& fontHeader) + { + return instance_->GetCurrentFontHeader(fontHeader); + } + + /** + * @brief Get font id + * + * @param name + * @param size + * @return uint8_t + */ + uint8_t GetFontId(const char* name, uint8_t size = 0) + { + return instance_->GetFontId(name, size); + } + + /** + * @brief Set the cache start address + * + * @param psramAddr + * @param psramLen + */ + void SetPsramMemory(uintptr_t psramAddr, uint32_t psramLen) + { + instance_->SetPsramMemory(psramAddr, psramLen); + } + + /** + * @brief Get bitmap for specific unicode + * + * @param unicode + * @return uint8_t* + */ + uint8_t* GetBitmap(uint32_t unicode, GlyphNode& glyphNode, uint8_t shapingFont); + + /** + * @brief Indicates whether the current font library is a vector font library. + * @return uint8_t: 0 BitmapFont 1 VectorFont + */ + bool IsVectorFont() + { + return instance_->IsVectorFont(); + } + + /** + * @brief Get current font id + * + * @return uint8_t + */ + uint8_t GetCurrentFontId() + { + return instance_->GetBaseFontId(); + } + + int8_t SetCurrentLangId(uint8_t langId) + { + return instance_->SetCurrentLangId(langId); + } + + uint8_t GetCurrentLangId() const + { + return instance_->GetCurrentLangId(); + } + + int8_t GetDefaultParamByLangId(uint8_t langId, LangTextParam** pParam) const + { + return instance_->GetDefaultParamByLangId(langId, pParam); + } + + int8_t GetTextUtf8(uint16_t textId, uint8_t** utf8Addr, uint16_t& utf8Len) const + { + return instance_->GetTextUtf8(textId, utf8Addr, utf8Len); + } + + uint8_t GetFontTtfId(uint8_t fontId, uint8_t size) const + { + return instance_->GetFontTtfId(fontId, size); + } + + int32_t OpenVectorFont(uint8_t ttfId) + { + return instance_->OpenVectorFont(ttfId); + } + + const UITextLanguageFontParam* GetFontInfo(uint8_t fontId) const + { + return instance_->GetFontInfo(fontId); + } + + int8_t SetFontPath(const char* dpath, const char* spath) + { + return instance_->SetFontPath(dpath, spath); + } + + int8_t GetFontVersion(char* dVersion, uint8_t dLen, char* sVersion, uint8_t sLen) const + { + return instance_->GetFontVersion(dVersion, dLen, sVersion, sLen); + } + + uint8_t RegisterFontInfo(const char* ttfName, uint8_t shaping = 0) + { + return instance_->RegisterFontInfo(ttfName, shaping); + } + + uint8_t RegisterFontInfo(const UITextLanguageFontParam* fontsTable, uint8_t num) + { + return instance_->RegisterFontInfo(fontsTable, num); + } + + uint8_t UnregisterFontInfo(const char* ttfName) + { + return instance_->UnregisterFontInfo(ttfName); + } + + uint8_t UnregisterFontInfo(const UITextLanguageFontParam* fontsTable, uint8_t num) + { + return instance_->UnregisterFontInfo(fontsTable, num); + } + + int8_t GetTextParam(uint16_t textId, UITextLanguageTextParam& param) const + { + return instance_->GetTextParam(textId, param); + } + + int8_t GetWildCardStaticStr(uint16_t textId, UITextWildcardStaticType type, + uint8_t** strAddr, uint16_t& strLen) const + { + return instance_->GetWildCardStaticStr(textId, type, strAddr, strLen); + } + + int8_t GetCodePoints(uint16_t textId, uint32_t** codePoints, uint16_t& codePointsNum) const + { + return instance_->GetCodePoints(textId, codePoints, codePointsNum); + } + + static UIFont* GetInstance(); + void SetFont(BaseFont* font); + +private: + UIFont(); + /** + * @brief Destroy the UIFontFactor object + * + */ + ~UIFont(); + + BaseFont* instance_; + BaseFont* defaultInstance_; +}; +} // namespace OHOS +#endif diff --git a/interfaces/kits/font/ui_font_header.h b/interfaces/kits/font/ui_font_header.h new file mode 100755 index 0000000..38370eb --- /dev/null +++ b/interfaces/kits/font/ui_font_header.h @@ -0,0 +1,412 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GRAPHIC_LITE_UI_FONT_HEADER_H +#define GRAPHIC_LITE_UI_FONT_HEADER_H + +#include +#include "common/text.h" +namespace OHOS { +#pragma pack(1) + +enum UITextWildcardStaticType : uint8_t { TEXT_STATIC_STR1 = 0, TEXT_STATIC_STR2, TEXT_STATIC_STR3 }; + +struct UITextLanguageTextParam { + uint16_t textId; + uint8_t fontId; + uint8_t align : 2; // UITextLanguageAlignment + uint8_t direct : 1; // UITextLanguageDirect + uint8_t shaping : 1; + uint8_t wildCard1 : 1; + uint8_t wildCard2 : 1; + uint8_t reserve : 2; + uint32_t strOff1; + uint16_t strLen1; + uint16_t strLen2; + uint16_t strLen3; + uint32_t codePointsOff; + uint16_t codePointsLen; +}; // UITextLanguageTextParam + +// bin header +constexpr uint8_t FONT_VERSION_OFFSET = 0; +constexpr uint8_t FONT_VERSION_LEN = 10; +constexpr uint8_t FONT_MAGIC_NUM_OFFSET = FONT_VERSION_OFFSET + FONT_VERSION_LEN; +constexpr uint8_t FONT_MAGIC_NUM_LEN = 6; +constexpr uint8_t FONT_NUMBER_OFFSET = FONT_MAGIC_NUM_OFFSET + FONT_MAGIC_NUM_LEN; +constexpr uint8_t FONT_NUMBER_LEN = 2; +constexpr uint8_t BIN_HEAD_RES_OFFSET = FONT_NUMBER_OFFSET + FONT_NUMBER_LEN; +constexpr uint8_t BIN_HEAD_RES_LEN = 2; +constexpr uint8_t FONT_DATA_OFFSET = BIN_HEAD_RES_OFFSET + BIN_HEAD_RES_LEN; +constexpr uint8_t BIN_HEAD_TOTAL_LEN = FONT_VERSION_LEN + FONT_MAGIC_NUM_LEN + FONT_NUMBER_LEN + BIN_HEAD_RES_LEN; + +// font header +constexpr uint8_t FONT_ID_OFFSET = 0; +constexpr uint8_t FONT_ID_LEN = 1; +constexpr uint8_t GLYPH_NUM_OFFSET = FONT_ID_OFFSET + FONT_ID_LEN; +constexpr uint8_t GLYPH_NUM_LEN = 2; +constexpr uint8_t FONT_HEIGHT_OFFSET = GLYPH_NUM_OFFSET + GLYPH_NUM_LEN; +constexpr uint8_t FONT_HEIGHT_LEN = 2; +constexpr uint8_t FONT_ASCEND_OFFSET = FONT_HEIGHT_OFFSET + FONT_HEIGHT_LEN; +constexpr uint8_t FONT_ASCEND_LEN = 2; +constexpr uint8_t FONT_DESCEND_OFFSET = FONT_ASCEND_OFFSET + FONT_ASCEND_LEN; +constexpr uint8_t FONT_DESCEND_LEN = 2; +constexpr uint8_t FONT_INDEX_OFF_OFFSET = FONT_DESCEND_OFFSET + FONT_DESCEND_LEN; +constexpr uint8_t FONT_INDEX_OFF_LEN = 4; +constexpr uint8_t FONT_INDEX_LEN_OFFSET = FONT_INDEX_OFF_OFFSET + FONT_INDEX_OFF_LEN; +constexpr uint8_t FONT_INDEX_LEN_LEN = 4; +constexpr uint8_t GLYPH_OFF_OFFSET = FONT_INDEX_LEN_OFFSET + FONT_INDEX_LEN_LEN; +constexpr uint8_t GLYPH_OFF_LEN = 4; +constexpr uint8_t GLYPH_LEN_OFFSET = GLYPH_OFF_OFFSET + GLYPH_OFF_LEN; +constexpr uint8_t GLYPH_LEN_LEN = 4; +constexpr uint8_t FONT_HEAD_RES_OFFSET = GLYPH_LEN_OFFSET + GLYPH_LEN_LEN; +constexpr uint8_t FONT_HEAD_RES_LEN = 2; +constexpr uint8_t FONT_HEAD_TOTAL_LEN = FONT_ID_LEN + GLYPH_NUM_LEN + FONT_HEIGHT_LEN + FONT_ASCEND_LEN + + FONT_DESCEND_LEN + GLYPH_OFF_LEN + GLYPH_LEN_LEN + FONT_HEAD_RES_LEN; + +// glyph header, internal using, not write to bin file +constexpr uint8_t FONT_G_HEIGHT_OFFSET = 0; +constexpr uint8_t FONT_G_HEIGHT_LEN = 2; +constexpr uint8_t GLYPH_G_NUM_OFFSET = FONT_G_HEIGHT_OFFSET + FONT_G_HEIGHT_LEN; +constexpr uint8_t GLYPH_G_NUM_LEN = 2; +constexpr uint8_t GLYPH_G_ASCEND_OFFSET = GLYPH_G_NUM_OFFSET + GLYPH_G_NUM_LEN; +constexpr uint8_t GLYPH_G_ASCEND_LEN = 2; +constexpr uint8_t GLYPH_G_DESCEND_OFFSET = GLYPH_G_ASCEND_OFFSET + GLYPH_G_ASCEND_LEN; +constexpr uint8_t GLYPH_G_DESCEND_LEN = 2; +constexpr uint8_t GLYPH_G_INDEX_OFF_OFFSET = GLYPH_G_DESCEND_OFFSET + GLYPH_G_DESCEND_LEN; +constexpr uint8_t GLYPH_G_INDEX_OFF_LEN = 4; +constexpr uint8_t GLYPH_G_INDEX_LEN_OFFSET = GLYPH_G_INDEX_OFF_OFFSET + GLYPH_G_INDEX_OFF_LEN; +constexpr uint8_t GLYPH_G_INDEX_LEN_LEN = 4; +constexpr uint8_t GLYPH_G_OFF_OFFSET = GLYPH_G_INDEX_LEN_OFFSET + GLYPH_G_INDEX_LEN_LEN; +constexpr uint8_t GLYPH_G_OFF_LEN = 4; +constexpr uint8_t GLYPH_G_LEN_OFFSET = GLYPH_G_OFF_OFFSET + GLYPH_G_OFF_LEN; +constexpr uint8_t GLYPH_G_LEN_LEN = 4; +constexpr uint8_t FONT_G_HEAD_TOTAL_LEN = + FONT_G_HEIGHT_LEN + GLYPH_G_NUM_LEN + GLYPH_G_ASCEND_LEN + GLYPH_G_DESCEND_LEN + GLYPH_G_OFF_LEN + GLYPH_G_LEN_LEN; + +// glyph node +constexpr uint8_t GLYPH_UNICODE_OFFSET = GLYPH_G_NUM_OFFSET + GLYPH_G_NUM_LEN; +constexpr uint8_t GLYPH_UNICODE_LEN = 4; +constexpr uint8_t GLYPH_LEFT_OFFSET = GLYPH_UNICODE_OFFSET + GLYPH_UNICODE_LEN; +constexpr uint8_t GLYPH_LEFT_LEN = 2; +constexpr uint8_t GLYPH_TOP_OFFSET = GLYPH_LEFT_OFFSET + GLYPH_LEFT_LEN; +constexpr uint8_t GLYPH_TOP_LEN = 2; +constexpr uint8_t GLYPH_ADVANCE_OFFSET = GLYPH_TOP_OFFSET + GLYPH_TOP_LEN; +constexpr uint8_t GLYPH_ADVANCE_LEN = 2; +constexpr uint8_t GLYPH_COLS_OFFSET = GLYPH_ADVANCE_OFFSET + GLYPH_ADVANCE_LEN; +constexpr uint8_t GLYPH_COLS_LEN = 2; +constexpr uint8_t GLYPH_ROWS_OFFSET = GLYPH_COLS_OFFSET + GLYPH_COLS_LEN; +constexpr uint8_t GLYPH_ROWS_LEN = 2; +constexpr uint8_t GLYPH_DATA_OFF_OFFSET = GLYPH_ROWS_OFFSET + GLYPH_ROWS_LEN; +constexpr uint8_t GLYPH_DATA_OFF_LEN = 4; +constexpr uint8_t GLYPH_KERN_OFF_OFFSET = GLYPH_DATA_OFF_OFFSET + GLYPH_DATA_OFF_LEN; +constexpr uint8_t GLYPH_KERN_OFF_LEN = 4; +constexpr uint8_t GLYPH_KERN_SIZE_OFFSET = GLYPH_KERN_OFF_OFFSET + GLYPH_KERN_OFF_LEN; +constexpr uint8_t GLYPH_KERN_SIZE_LEN = 2; +constexpr uint8_t GLYPH_HEAD_RES_OFFSET = GLYPH_KERN_SIZE_OFFSET + GLYPH_KERN_SIZE_LEN; +constexpr uint8_t GLYPH_HEAD_RES_LEN = 2; +constexpr uint8_t GLYPH_HEAD_TOTAL_LEN = GLYPH_UNICODE_LEN + GLYPH_LEFT_LEN + GLYPH_TOP_LEN + GLYPH_ADVANCE_LEN + + GLYPH_COLS_LEN + GLYPH_ROWS_LEN + GLYPH_DATA_OFF_LEN + GLYPH_KERN_OFF_LEN + + GLYPH_KERN_SIZE_LEN + GLYPH_HEAD_RES_LEN; + +// language texts table header +constexpr uint8_t LANG_TEXTS_PARAM_OFF = 0; +constexpr uint8_t LANG_TEXTS_PARAM_LEN = 4; +constexpr uint8_t LANG_TEXTS_UTF8P_OFF = LANG_TEXTS_PARAM_OFF + LANG_TEXTS_PARAM_LEN; +constexpr uint8_t LANG_TEXTS_UTF8P_LEN = 4; +constexpr uint8_t LANG_TEXTS_UTF8_OFF = LANG_TEXTS_UTF8P_OFF + LANG_TEXTS_UTF8P_LEN; +constexpr uint8_t LANG_TEXTS_UTF8_LEN = 4; +constexpr uint8_t LANG_TEXTS_TOTAL_LEN = LANG_TEXTS_PARAM_LEN + LANG_TEXTS_UTF8P_LEN + LANG_TEXTS_UTF8_LEN; + +// language texts utf-8 param header +constexpr uint8_t LANG_UTF8_PARAM_TOTAL_LEN = 8; // 4B offset + 4B len + +// language bin header +constexpr uint8_t LANG_OFFSET_OFF = 0; +constexpr uint8_t LANG_OFFSET_LEN = 4; +constexpr uint8_t LANG_SIZE_OFF = LANG_OFFSET_OFF + LANG_OFFSET_LEN; +constexpr uint8_t LANG_SIZE_LEN = 4; +constexpr uint8_t LANG_HEAD_TOTAL_LEN = LANG_OFFSET_LEN + LANG_SIZE_LEN; + +// dynamic font +constexpr uint32_t GLYPH_HEADER_LENGTH = 0xD7C00; // 860 K, almost 33870 glyph header +constexpr uint32_t FONT_HEADER_LENGTH = FONT_HEAD_TOTAL_LEN * 150; // 150 font, almost 3K +constexpr uint32_t DYNAMIC_FONT_HEADER_TOTAL_LENGTH = GLYPH_HEADER_LENGTH + FONT_HEADER_LENGTH; + +// static font psram +constexpr uint32_t UTF8_PARAM_LENGTH = LANG_UTF8_PARAM_TOTAL_LEN * 100; // 100 lang, almost 1K +constexpr uint32_t STATIC_PARAM_LENGTH = sizeof(UITextLanguageTextParam) * 3218; // 3218 texts, 44K +constexpr uint32_t STATIC_UTF8_LENGTH = 0xC800; // 50 * 1024 = 50K +constexpr uint32_t STATIC_FONT_HEADER_LENGTH = FONT_HEAD_TOTAL_LEN * 150; // 150 font, almost 3K +constexpr uint32_t STATIC_GLYPH_HEADER_LENGTH = 0x15C00; // 87K, almost 26 * 3426 glyph header +constexpr uint32_t STATIC_FONT_HEADER_TOTAL_LENGTH = UTF8_PARAM_LENGTH + STATIC_PARAM_LENGTH + STATIC_UTF8_LENGTH + + STATIC_FONT_HEADER_LENGTH + STATIC_GLYPH_HEADER_LENGTH; + +constexpr uint8_t MAX_CACHE_CHAR_NUMBER = 50; // cache 50 letters +constexpr uint32_t DYNAMIC_ONE_CHAR_LENGTH = 0x5000; // 20 K means w200:h200:bpp4, 200 x 200 / 2 + +// 50 * 20K = 1000K +constexpr uint32_t DYNAMIC_TEXT_CACHE_LENGTH = MAX_CACHE_CHAR_NUMBER * DYNAMIC_ONE_CHAR_LENGTH; +constexpr uint32_t MIN_FONT_PSRAM_LENGTH = 0x380000; + +// text shaping psram +constexpr uint32_t SHAPING_TTF_TABLE_LENGTH = 0x4B000; // 307K +constexpr uint32_t SHAPING_WORD_DICT_LENGTH = 0xD000; // 53K +constexpr uint32_t SHAPING_CACHE_LENGTH = 0x50000; // 328K +constexpr uint32_t MIN_SHAPING_PSRAM_LENGTH = + SHAPING_TTF_TABLE_LENGTH + SHAPING_WORD_DICT_LENGTH + SHAPING_CACHE_LENGTH + HARFBUZ_CACHE_LENGTH; + +// other definition +constexpr uint8_t FONT_DPI = 100; +constexpr uint8_t FONT_PIXEL_IN_POINT = 64; +constexpr uint8_t BPP_BIT_1 = 1; +constexpr uint8_t BPP_MASK_1 = 7; +constexpr uint8_t BPP_SHIFT_1 = 3; +constexpr uint8_t BPP_MASK_NUM_1 = 0x80; +constexpr uint8_t BPP_BIT_2 = 2; +constexpr uint8_t BPP_MASK_2 = 3; +constexpr uint8_t BPP_SHIFT_2 = 2; +constexpr uint8_t BPP_MASK_NUM_2 = 0xC0; +constexpr uint8_t BPP_BIT_4 = 4; +constexpr uint8_t BPP_MASK_4 = 1; +constexpr uint8_t BPP_SHIFT_4 = 1; +constexpr uint8_t BPP_MASK_NUM_4 = 0xF0; +constexpr uint8_t BPP_BIT_8 = 8; +constexpr uint32_t DEFAULT_FONT_SIZE = 10; +constexpr uint32_t MAX_CHAR_MB_LEN = 16; +constexpr uint32_t MAX_CHAR_NUM = 512; +constexpr uint8_t BITS_PER_BYTE = 8; +constexpr uint8_t BITS_MASK_IN_BYTE = 7; +constexpr uint32_t MAX_MONO_ADVANCE = 128; +constexpr uint8_t BIT0 = 0; +constexpr uint8_t BIT1 = 1; +constexpr uint8_t BIT2 = 2; +constexpr uint8_t BIT3 = 3; +constexpr uint8_t BIT4 = 4; +constexpr uint8_t BIT5 = 5; +constexpr uint8_t BIT6 = 6; +constexpr uint8_t BIT7 = 7; +constexpr uint8_t BIT8 = 8; +constexpr uint8_t BIT12 = 12; +constexpr uint8_t BIT18 = 18; +constexpr uint8_t GLYPH_KERN_ITEM_LEN = 2; +constexpr uint8_t UTF_TO_UNICODE_MASK1 = 0x80; +constexpr uint8_t UTF_TO_UNICODE_MASK2 = 0xC0; +constexpr uint8_t UTF_TO_UNICODE_MASK3 = 0xE0; +constexpr uint8_t UTF_TO_UNICODE_MASK4 = 0xF0; +constexpr uint8_t UTF_TO_UNICODE_MASK5 = 0xF8; +constexpr uint8_t UTF_TO_UNICODE_MASK6 = 0x3F; +constexpr uint8_t UTF_TO_UNICODE_MASK7 = 0x1F; +constexpr uint8_t UTF_TO_UNICODE_MASK8 = 0x07; +constexpr uint8_t UTF_TO_UNICODE_MASK9 = 0x0F; +constexpr uint8_t MAX_FILE_NAME_LEN = 255; +constexpr uint8_t GPU_FONT_ALIGN_BIT = 16; +constexpr uint8_t INVALID_UCHAR_ID = 0xFF; +constexpr uint16_t INVALID_USHORT_ID = 0xFFFF; +constexpr uint32_t INVALID_UINT_ID = 0xFFFFFFFF; +constexpr int8_t INVALID_RET_VALUE = -1; +constexpr int8_t RET_VALUE_OK = 0; +constexpr const char* FONT_MAGIC_NUMBER = const_cast("OHOS."); + +/** + * @brief struct BinHeader for font + * refer to ui_font.h + */ +struct BinHeader { + char fontVersion[FONT_VERSION_LEN]; // FONT_VERSION_OFFSET + char fontMagic[FONT_MAGIC_NUM_LEN]; // FONT_MAGIC_NUM_OFFSET + uint16_t fontNum; // FONT_NUMBER_OFFSET + uint16_t reserve; // BIN_HEAD_RES_OFFSET +}; + +/** + * @brief struct FontHeader + * refer to ui_font.h + */ +struct FontHeader { + uint8_t fontId; // FONT_ID_OFFSET + uint16_t glyphNum; // GLYPH_NUM_OFFSET + uint16_t fontHeight; // FONT_HEIGHT_OFFSET + int16_t ascender; // FONT_ASCEND_LEN + int16_t descender; // FONT_DESCEND_LEN + uint32_t indexOffset; + uint32_t indexLen; + uint32_t glyphOffset; // GLYPH_G_OFF_OFFSET + uint32_t glyphLen; // GLYPH_OFF_OFFSET + uint16_t reserve; // GLYPH_LEN_OFFSET +}; + +/** + * @brief struct GlyphHeader for font, font tool using, not for bin file + * refer to ui_font.h + */ +struct GlyphHeader { + uint16_t fontHeight; // FONT_G_HEIGHT_OFFSET + uint16_t glyphNum; // GLYPH_G_NUM_OFFSET + int16_t ascender; // GLYPH_G_ASCEND_LEN + int16_t descender; // GLYPH_G_DESCEND_LEN + uint32_t indexOffset; + uint32_t indexLen; + uint32_t glyphOffset; // GLYPH_G_OFF_OFFSET + uint32_t glyphLen; // GLYPH_G_LEN_OFFSET +}; + +/** + * @brief struct GlyphNode for font + * refer to ui_font.h + */ +struct GlyphNode { + uint32_t unicode; // GLYPH_UNICODE_OFFSET + int16_t left; // GLYPH_LEFT_OFFSET + int16_t top; // GLYPH_TOP_OFFSET + uint16_t advance; // GLYPH_ADVANCE_OFFSET + uint16_t cols; // GLYPH_COLS_OFFSET + uint16_t rows; // GLYPH_ROWS_OFFSET + uint32_t dataOff; // GLYPH_DATA_OFF_OFFSET + uint32_t kernOff; // GLYPH_KERN_OFF_OFFSET + uint16_t kernSize; // GLYPH_KERN_SIZE_OFFSET + uint16_t reserve; // GLYPH_HEAD_RES_OFFSET +}; + +/** + * @brief struct LangBinHeader for font + * refer to ui_font.h + */ +struct LangBinHeader { + char fontVersion[FONT_VERSION_LEN]; // FONT_VERSION_OFFSET + char fontMagic[FONT_MAGIC_NUM_LEN]; // FONT_MAGIC_NUM_OFFSET +}; + +/** + * @brief struct LangFontBinHeader for font + * refer to ui_font.h + */ +struct LangFontBinHeader { + uint32_t offset; // LANG_OFFSET_LEN + uint32_t size; // LANG_SIZE_LEN +}; + +/** + * @brief struct LangTexsTableHeader for font + * refer to ui_font.h + */ +struct LangTexsTableHeader { + uint32_t textParamStart; + uint32_t textParamLen; // LANG_TEXTS_PARAM_LEN + uint32_t textUtf8ParamStart; + uint32_t textUtf8ParamLen; // LANG_TEXTS_UTF8P_LEN + uint32_t textUtf8Start; + uint32_t textUtf8Len; // LANG_TEXTS_UTF8_LEN + uint32_t textCodepointsStart; + uint32_t textCodepointsMaxItemLen; + uint32_t textCodepointsTotalLen; +}; + +/** + * @brief struct LangTexsUtf8Param for font + * refer to ui_font.h + */ +struct LangTexsUtf8Param { + uint32_t offset; + uint32_t len; + uint32_t codePointOffset; + uint32_t codePointLen; +}; + +/** + * @brief struct KernNode for kerning of font + * refer to ui_font.h + */ +struct KernNode { + uint32_t unicode; // GLYPH_UNICODE_LEN + int16_t kernValue; // GLYPH_KERN_SIZE_LEN + uint8_t found; // found the kerning or not +}; + +/** + * @brief struct TtfHeader for header of ttftab.bin + * refer to ui_font.h + */ +struct TtfHeader { + uint8_t ttfId; + uint32_t offset; + uint32_t len; +}; + +struct UITextLanguageFontParam { + uint8_t size; // Invalid value for vector font + uint8_t fontWeight; + uint8_t shaping; + uint8_t ttfId; // auto create during registration for vector font + const char* ttfName; +}; // UITextLanguageFontParam + +struct TextIsoHeader { + uint16_t paramLen; + uint16_t strLen; +}; + +struct TextIsoParam { + uint16_t offset; + uint16_t len; +}; + +struct LangTextParam { + uint8_t fontDirect; // refer to UITextLanguageDirect + uint8_t fontSize; + const char* familyName; // such as "Roboto-Medium" + const char* langName; // such as "ar", "zh" +}; + +struct FileCommonHeader { + const char magicWord[5]; // 5:file identifier's length is 5 + uint32_t fileLength; +}; +#pragma pack() + +/** + * @brief Get the Text Lang Fonts Table + * @param uint8_t langFontId [in] the font id + * @return UITextLanguageFontParam* the font table, definition is in font tool + */ +UITextLanguageFontParam* GetTextLangFontsTable(uint8_t langFontId); + +/** + * @brief Get the Total Lang Id + * @return uint8_t the total Lang id, definition is in font tool + */ +uint8_t GetTotalLangId(); + +/** + * @brief Get the Total Font Id + * @return uint8_t the total font id, definition is in font tool + */ +uint8_t GetTotalFontId(); + +uint8_t GetBitmapFontIdMax(); + +/** + * @brief Get the Total Text Id + * @return uint8_t the total text id, definition is in font tool + */ +uint16_t GetTotalTextId(); + +/** + * @brief Get the Lang Text default param table + * @return LangTextParam* the param table, definition is in font tool + */ +LangTextParam* GetLangTextDefaultParamTable(); +} // namespace OHOS +#endif /* UI_FONT_HEADER_H */ \ No newline at end of file diff --git a/interfaces/kits/layout/flex_layout.h b/interfaces/kits/layout/flex_layout.h new file mode 100755 index 0000000..940c3a4 --- /dev/null +++ b/interfaces/kits/layout/flex_layout.h @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @addtogroup UI_Layout + * @{ + * + * @brief Defines UI layouts such as FlexLayout and GridLayout. + * + * @since 1.0 + * @version 1.0 + */ + +/** + * @file flex_layout.h + * + * @brief Declares a flexible layout container. You can perform simple adaptive layout on child views that the + * container holds, for example, to evenly arrange all child views in the same row or column. + * + * @since 1.0 + * @version 1.0 + */ + +#ifndef GRAPHIC_LITE_FLEX_LAYOUT_H +#define GRAPHIC_LITE_FLEX_LAYOUT_H + +#include "layout.h" + +namespace OHOS { +/** + * @brief Defines a flexible layout container. You can perform simple adaptive layout on child views that the + * container holds, for example, to evenly arrange all child views in the same row or column. + * + * @since 1.0 + * @version 1.0 + */ +class FlexLayout : public Layout { +public: + static constexpr uint8_t NOWRAP = 0; + static constexpr uint8_t WRAP = 1; + + /** + * @brief A default constructor used to create a FlexLayout instance. + * @since 1.0 + * @version 1.0 + */ + FlexLayout() + : majorAlign_(ALIGN_START), secondaryAlign_(ALIGN_CENTER), wrap_(NOWRAP), rowCount_(1), columnCount_(1) {} + + /** + * @brief A destructor used to delete the FlexLayout instance. + * @since 1.0 + * @version 1.0 + */ + virtual ~FlexLayout() {} + + /** + * @brief Sets the alignment mode of the primary axis (the axis where the layout direction is located). + * The child views in the layout are placed in this mode in the direction of the primary axis. + * @param align Indicates the alignment mode to set. The value can be ALIGN_START, ALIGN_END, + * ALIGN_CENTER, ALIGN_EVENLY, ALIGN_AROUND, or ALIGN_BETWEEN. + * @since 1.0 + * @version 1.0 + */ + void SetMajorAxisAlign(const AlignType& align) + { + majorAlign_ = align; + } + + /** + * @brief Sets the alignment mode of the secondary axis (the axis perpendicular to the set layout direction). + * @param align Indicates the alignment mode to set. The value can be ALIGN_START, ALIGN_CENTER, + * or ALIGN_END. + * @since 1.0 + * @version 1.0 + */ + void SetSecondaryAxisAlign(const AlignType& align) + { + secondaryAlign_ = align; + } + + /** + * @brief Sets whether to support word wrap. + * @param wrap Indicates the word wrap attribute. + * @since 1.0 + * @version 1.0 + */ + void SetFlexWrap(uint8_t wrap) + { + wrap_ = wrap; + } + + /** + * @brief Lays out all child views according to the preset arrangement mode. + * @param needInvalidate Specifies whether to refresh the invalidated area after the layout is complete. + * Value true means to refresh the invalidated area after the layout is complete, + * and false means the opposite. + * @since 1.0 + * @version 1.0 + */ + virtual void LayoutChildren(bool needInvalidate = false) override; +private: + void LayoutHorizontal(); + void LayoutVertical(); + void CalValidLength(uint16_t& totalValidLength, uint16_t& allChildNum); + void GetStartPos(const int16_t& length, int16_t& pos, int16_t& interval, int16_t count, + uint16_t* validLengths, uint16_t* childsNum); + void GetNoWrapStartPos(const int16_t& length, int16_t& majorPos, int16_t& interval); + void CalRowCount(); + void GetRowMaxHeight(uint16_t size, uint16_t* maxRosHegiht); + void GetRowsWidth(uint16_t rowNum, uint16_t* rowsWidth, uint16_t* rowsChildNum); + void GetRowStartPos(int16_t& pos, int16_t& interval, int16_t count, + uint16_t* rowsWidth, uint16_t* rowsChildNum); + void CalColumnCount(); + void GetColumnMaxWidth(uint16_t size, uint16_t* maxColumnsWidth); + void GetColumnsHeight(uint16_t columnNum, uint16_t* columnsHeight, uint16_t* columnsChildNum); + void GetColumnStartPos(int16_t& pos, int16_t& interval, int16_t count, + uint16_t* columnsHeight, uint16_t* columnsChildNum); + void GetCrossAxisPosY(int16_t& posY, uint16_t& count, uint16_t* rowsMaxHeight, UIView* child); + void GetCrossAxisPosX(int16_t& posX, uint16_t& count, uint16_t* columnsMaxWidth, UIView* child); + static constexpr uint16_t MAX_COUNT_DEFAULT = 100; + AlignType majorAlign_; + AlignType secondaryAlign_; + uint8_t wrap_; + uint16_t rowCount_; + uint16_t columnCount_; +}; +} // namespace OHOS +#endif // GRAPHIC_LITE_FLEX_LAYOUT_H diff --git a/interfaces/kits/layout/grid_layout.h b/interfaces/kits/layout/grid_layout.h new file mode 100755 index 0000000..6dbbb2a --- /dev/null +++ b/interfaces/kits/layout/grid_layout.h @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GRAPHIC_LITE_GRID_LAYOUT_H +#define GRAPHIC_LITE_GRID_LAYOUT_H + +/** + * @addtogroup UI_Layout + * @{ + * + * @brief Defines UI layouts such as FlexLayout and GridLayout. + * + * @since 1.0 + * @version 1.0 + */ + +/** + * @file grid_layout.h + * + * @brief Declares a grid layout container. You can perform simple grid layout on child views that the container holds. + * + * @since 1.0 + * @version 1.0 + */ + +#include "layout.h" + +namespace OHOS { +/** + * @brief Defines a grid layout container. You can perform simple grid layout on child views that the container holds. + * + * @since 1.0 + * @version 1.0 + */ +class GridLayout : public Layout { +public: + /** + * @brief A default constructor used to create a GridLayout instance. + * @since 1.0 + * @version 1.0 + */ + GridLayout() : rows_(0), cols_(0) {} + + /** + * @brief A destructor used to delete the GridLayout instance. + * @since 1.0 + * @version 1.0 + */ + virtual ~GridLayout() {} + + /** + * @brief Sets the number of rows in a grid. + * @param rows Indicates the number of rows to set. + * @since 1.0 + * @version 1.0 + */ + void SetRows(const uint16_t& rows) + { + rows_ = rows; + } + + /** + * @brief Sets the number of columns in a grid. + * @param cols Indicates the number of columns to set. + * @since 1.0 + * @version 1.0 + */ + void SetCols(const uint16_t& cols) + { + cols_ = cols; + } + + /** + * @brief Lays out all child views according to the preset arrangement mode. + * @param needInvalidate Specifies whether to refresh the invalidated area after the layout is complete. + * Value true means to refresh the invalidated area after the layout is complete, + * and false means the opposite. + * @since 1.0 + * @version 1.0 + */ + virtual void LayoutChildren(bool needInvalidate = false) override; + +private: + void LayoutHorizontal(); + void LayoutVertical(); + + uint16_t rows_; + uint16_t cols_; +}; +} // namespace OHOS +#endif // GRAPHIC_LITE_GRID_LAYOUT_H diff --git a/interfaces/kits/layout/layout.h b/interfaces/kits/layout/layout.h new file mode 100755 index 0000000..f2c0d96 --- /dev/null +++ b/interfaces/kits/layout/layout.h @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @addtogroup UI_Layout + * @{ + * + * @brief Defines UI layouts such as FlexLayout and GridLayout. + * + * @since 1.0 + * @version 1.0 + */ + +/** + * @file layout.h + * + * @brief Declares the base class of the layout, which indicates the basic data types and operations that may be + * used in the layout. + * + * @since 1.0 + * @version 1.0 + */ + +#ifndef GRAPHIC_LITE_LAYOUT_H +#define GRAPHIC_LITE_LAYOUT_H + +#include "components/ui_view_group.h" + +namespace OHOS { +using DirectionType = uint8_t; +using AlignType = uint8_t; +/* Arranges child views by row from left to right. */ +const DirectionType LAYOUT_HOR = 0; +/* Arranges child views by row from right to left. */ +const DirectionType LAYOUT_HOR_R = 1; +/* Arranges child views by column from top to bottom. */ +const DirectionType LAYOUT_VER = 2; +/* Arranges child views by column from bottom to top. */ +const DirectionType LAYOUT_VER_R = 3; + +/* Places all child views from the start point to the end point. */ +const AlignType ALIGN_START = 0; +/* Places all child views from the end point to the start point. */ +const AlignType ALIGN_END = 1; +/* Places all child views in the center. */ +const AlignType ALIGN_CENTER = 2; +/* Evenly places all child views between the start point and end point. The distance between the start point and + the end as well as the distance between the end point and the end is the same as that between child views. */ +const AlignType ALIGN_EVENLY = 3; +/* Evenly places all child views between the start point and end point. The distance between the start point and + the end as well as the distance between the end point and the end is half of the distance between child views. */ +const AlignType ALIGN_AROUND = 4; +/* Evenly places all child views between the start point and end point. No left or right margin is reserved. */ +const AlignType ALIGN_BETWEEN = 5; + +/** + * @brief Defines the base class of the layout, which indicates the basic data types and operations that may be used in + * the layout. + * + * @since 1.0 + * @version 1.0 + */ +class Layout : public UIViewGroup { +public: + /** + * @brief A default constructor used to create a Layout instance. + * @since 1.0 + * @version 1.0 + */ + Layout() : direction_(LAYOUT_HOR) {} + + /** + * @brief A destructor used to delete the Layout instance. + * @since 1.0 + * @version 1.0 + */ + virtual ~Layout() {} + + /** + * @brief Sets the layout direction. + * @param direction Indicates the direction of the layout. Available values are as follows: + * LAYOUT_HOR: from left to right + * LAYOUT_HOR_R: from right to left + * LAYOUT_VER: from top to bottom + * LAYOUT_VER_R: from bottom to top + * @since 1.0 + * @version 1.0 + */ + void SetLayoutDirection(const DirectionType& direction) + { + direction_ = direction; + } + +protected: + DirectionType direction_; +}; +} // namespace OHOS +#endif // GRAPHIC_LITE_LAYOUT_H diff --git a/interfaces/kits/layout/list_layout.h b/interfaces/kits/layout/list_layout.h new file mode 100755 index 0000000..ec1e801 --- /dev/null +++ b/interfaces/kits/layout/list_layout.h @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @addtogroup UI_Layout + * @{ + * + * @brief Defines UI layouts such as FlexLayout and GridLayout. + * + * @since 1.0 + * @version 1.0 + */ + +#ifndef GRAPHIC_LITE_LIST_LAYOUT_H +#define GRAPHIC_LITE_LIST_LAYOUT_H + +#include "flex_layout.h" + +namespace OHOS { +class ListLayout : public FlexLayout { +public: + /** @brief Default constructor */ + ListLayout(const uint8_t direction = VERTICAL); + + /** + * @brief Destructor + */ + virtual ~ListLayout() {} + + /** + * @brief set list direction, HORIZONTAL list view will recount X position, VERTICAL recount Y. + * + * @param [in] direction the direction, @see HORIZONTAL, VERTICAL. + */ + void SetDirection(uint8_t direction); + + /** + * @brief get list direction. + * + * @param [in] null. + */ + uint8_t GetDirection() + { + return listDirection_; + } + + static constexpr uint8_t HORIZONTAL = 0; + static constexpr uint8_t VERTICAL = 1; + +protected: + void SetLayoutDirect(uint8_t direction); + void OnChildChanged() override; + uint8_t listDirection_; +}; +} // namespace OHOS +#endif // GRAPHIC_LITE_LIST_LAYOUT_H diff --git a/interfaces/kits/themes/theme.h b/interfaces/kits/themes/theme.h new file mode 100755 index 0000000..5c577d4 --- /dev/null +++ b/interfaces/kits/themes/theme.h @@ -0,0 +1,192 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @addtogroup UI_Theme + * @{ + * + * @brief Defines UI themes. + * + * @since 1.0 + * @version 1.0 + */ + +/** + * @file theme.h + * + * @brief Declares the base class used to define the functions related to the styles of different components. + * + * @since 1.0 + * @version 1.0 + */ + +#ifndef GRAPHIC_LITE_THEME_H +#define GRAPHIC_LITE_THEME_H + +#include "components/ui_view.h" +#include "style.h" + +namespace OHOS { +/** + * @brief Stores styles of a button in its different states. + */ +struct ButtonStyle { + /** Style when released */ + Style released; + /** Style when pressed */ + Style pressed; + /** Style when inactive */ + Style inactive; +}; + +/** + * @brief Defines the theme class used to define the functions related to the styles of different components. + * + * @since 1.0 + * @version 1.0 + */ +class Theme : public HeapBase { +public: + /** + * @brief A constructor used to create a Theme instance. + * + * @since 1.0 + * @version 1.0 + */ + Theme(); + + /** + * @brief A destructor used to delete the Theme instance. + * + * @since 1.0 + * @version 1.0 + */ + virtual ~Theme(){}; + + /** + * @brief Obtains the basic style. + * + * @return Returns the basic style. + * @since 1.0 + * @version 1.0 + */ + Style& GetMainStyle() + { + return basicStyle_; + } + + /** + * @brief Obtains the style of this button. + * + * @return Returns the button style. + * @since 1.0 + * @version 1.0 + */ + ButtonStyle& GetButtonStyle() + { + return buttonStyle_; + } + + /** + * @brief Obtains the style of this label. + * + * @return Returns the label style. + * @since 1.0 + * @version 1.0 + */ + Style& GetLabelStyle() + { + return labelStyle_; + } + + /** + * @brief Obtains the background style of this picker. + * + * @return Returns the background style of this picker. + * @since 1.0 + * @version 1.0 + */ + Style& GetPickerBackgroundStyle() + { + return pickerBackgroundStyle_; + } + + /** + * @brief Obtains the highlight style of this picker. + * + * @return Returns the highlight style of this picker. + * @since 1.0 + * @version 1.0 + */ + Style& GetPickerHighlightStyle() + { + return pickerHighlightStyle_; + } + + /** + * @brief Obtains the background style of this progress bar. + * + * @return Returns the background style of this progress bar. + * @since 1.0 + * @version 1.0 + */ + Style& GetProgressBackgroundStyle() + { + return progressBackgroundStyle_; + } + + /** + * @brief Obtains the foreground style of this progress bar. + * + * @return Returns the foreground style of this progress bar. + * @since 1.0 + * @version 1.0 + */ + Style& GetProgressForegroundStyle() + { + return progressForegroundStyle_; + } + + /** + * @brief Obtains the style of this slider knob. + * + * @return Returns the style of this slider knob. + * @since 1.0 + * @version 1.0 + */ + Style& GetSliderKnobStyle() + { + return sliderKnobStyle_; + } + +protected: + Style basicStyle_; + ButtonStyle buttonStyle_; + Style labelStyle_; + Style pickerBackgroundStyle_; + Style pickerHighlightStyle_; + Style progressBackgroundStyle_; + Style progressForegroundStyle_; + Style sliderKnobStyle_; + + virtual void InitBasicStyle(); + virtual void InitButtonStyle(); + virtual void InitLabelStyle(); + virtual void InitPickerStyle(); + virtual void InitProgressStyle(); + virtual void InitSliderStyle(); +}; +} // namespace OHOS +#endif // GRAPHIC_LITE_THEME_H diff --git a/interfaces/kits/themes/theme_manager.h b/interfaces/kits/themes/theme_manager.h new file mode 100755 index 0000000..e3c29d4 --- /dev/null +++ b/interfaces/kits/themes/theme_manager.h @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @addtogroup UI_Theme + * @{ + * + * @brief Defines UI themes. + * + * @since 1.0 + * @version 1.0 + */ + +/** + * @file theme_manager.h + * + * @brief Declares the singleton class used to manage the current screen theme of an application. + * + * @since 1.0 + * @version 1.0 + */ + +#ifndef GRAPHIC_LITE_THEME_MANAGER_H +#define GRAPHIC_LITE_THEME_MANAGER_H + +#include "themes/theme.h" + +namespace OHOS { +/** + * @brief Declares the singleton class used to manage the current screen theme of an application. + * + * @since 1.0 + * @version 1.0 + */ +class ThemeManager : public HeapBase { +public: + /** + * @brief Obtains the singleton instance of the ThemeManager class. + * + * @return Returns the singleton instance of the ThemeManager class. + */ + static ThemeManager& GetInstance() + { + static ThemeManager instance; + return instance; + } + + /** + * @brief Sets the current screen theme for this application. + * + * @param theme Indicates the theme to set. + * + * @since 1.0 + * @version 1.0 + */ + void SetCurrent(Theme* theme); + + /** + * @brief Obtains the current screen theme of this application. + * + * @return Returns the current theme if available; returns nullptr otherwise. + * + * @since 1.0 + * @version 1.0 + */ + Theme* GetCurrent() + { + return theme_; + }; + +private: + ThemeManager() : theme_(nullptr) {} + ~ThemeManager() {} + + Theme* theme_; +}; +} // namespace OHOS +#endif // GRAPHIC_LITE_THEME_MANAGER_H diff --git a/interfaces/kits/window/window.h b/interfaces/kits/window/window.h new file mode 100755 index 0000000..f67e92b --- /dev/null +++ b/interfaces/kits/window/window.h @@ -0,0 +1,226 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @addtogroup Window + * @{ + * + * @brief Provides window management capabilities, including creating, destroying, showing, hiding, moving, + * resizing a window, raising a window to the top, and lowering a window to the bottom. + * + * @since 1.0 + * @version 1.0 + */ + +/** + * @file window.h + * + * @brief Declares the Window class that provides a drawing canvas for the RootView, + * which represents the root node of a view tree. + * + * Each window is bound to a RootView. For details, see {@link RootView}. + * The Window class also provides window management capabilities, including creating, destroying, showing, + * hiding, moving, resizing a window, raising a window to the top, and lowering a window to the bottom. + * + * @since 1.0 + * @version 1.0 + */ + +#ifndef GRAPHIC_LITE_WINDOW_H +#define GRAPHIC_LITE_WINDOW_H + +#include "color.h" +#include "components/root_view.h" + +namespace OHOS { +/** + * @brief Enumerates the pixel formats of this window. + * + * @since 1.0 + * @version 1.0 + */ +enum WindowPixelFormat { + /** RGB565 format */ + WINDOW_PIXEL_FORMAT_RGB565 = 101, + /** ARGB1555 format */ + WINDOW_PIXEL_FORMAT_ARGB1555, + /** RGB888 format */ + WINDOW_PIXEL_FORMAT_RGB888, + /** ARGB8888 format */ + WINDOW_PIXEL_FORMAT_ARGB8888, +}; + +/** + * @brief Sets the attributes for this window. + * + * This structure stores the attributes such as the rectangle, opacity, and pixel format of this window. + * + * @since 1.0 + * @version 1.0 + */ +struct WindowConfig { + WindowConfig() : rect(), + opacity(OPA_OPAQUE), + pixelFormat(WINDOW_PIXEL_FORMAT_ARGB8888), + compositeMode(COPY), + isModal(false) + { + } + ~WindowConfig() {} + enum CompositeMode { + COPY, + BLEND + }; + /** Rectangle */ + Rect rect; + /** Opacity, within [0, 255] */ + uint8_t opacity; + /** Pixel format */ + WindowPixelFormat pixelFormat; + CompositeMode compositeMode; + bool isModal; +}; + +/** + * @brief Provides a drawing canvas for the RootView, which represents the root node of a view tree. + * + * Each window is bound to a RootView. For details, see {@link RootView}. + * This class also provides window management capabilities, including creating, destroying, showing, hiding, + * moving, resizing a window, raising a window to the top, and lowering a window to the bottom. + * + * @since 1.0 + * @version 1.0 + */ +class Window { +public: + /** + * @brief Creates a Window instance. + * + * @param config Indicates the window configuration. For details, see {@link WindowConfig}. + * @return Returns the Window instance if the operation is successful; returns nullptr otherwise. + * @since 1.0 + * @version 1.0 + */ + static Window* CreateWindow(const WindowConfig& config); + + /** + * @brief Destroys a specified window. + * + * @param window Indicates the Window instance to destroy. + * @since 1.0 + * @version 1.0 + */ + static void DestoryWindow(Window* window); + + /** + * @brief Binds the RootView to this window. + * + * @param rootView Indicates the RootView to bind. + * @since 1.0 + * @version 1.0 + */ + virtual void BindRootView(RootView* rootView) = 0; + + /** + * @brief Unbinds the RootView from this window. + * + * @since 1.0 + * @version 1.0 + */ + virtual void UnbindRootView() = 0; + + /** + * @brief Obtains the RootView bound to this window. + * + * @return Returns the RootView if available; returns nullptr otherwise. + * @since 1.0 + * @version 1.0 + */ + virtual RootView* GetRootView() = 0; + + /** + * @brief Obtains the rectangle information (position, width, and height) of this window. + * + * @return Returns the rectangle information of this window. + * @since 1.0 + * @version 1.0 + */ + virtual Rect GetRect() = 0; + + /** + * @brief Shows this window. + * + * @since 1.0 + * @version 1.0 */ + virtual void Show() = 0; + + /** + * @brief Hides this window. + * + * @since 1.0 + * @version 1.0 + */ + virtual void Hide() = 0; + + /** + * @brief Moves this window to a specified position. + * + * @param x Indicates the x-coordinate of the target position. + * @param y Indicates the y-coordinate of the target position. + * @since 1.0 + * @version 1.0 + */ + virtual void MoveTo(int16_t x, int16_t y) = 0; + + /** + * @brief Resizes this window. + * + * @param width Indicates the new window width. + * @param height Indicates the new window height. + * @since 1.0 + * @version 1.0 + */ + virtual void Resize(int16_t width, int16_t height) = 0; + + /** + * @brief Raises this window to the top. + * + * @since 1.0 + * @version 1.0 + */ + virtual void RaiseToTop() = 0; + + /** + * @brief Lowers this window to the bottom. + * + * @since 1.0 + * @version 1.0 + */ + virtual void LowerToBottom() = 0; + + /** + * @brief Obtains the unique ID of this window. + * + * The window ID is within [0, 31]. An ID will be reused after the current window is destroyed. + * A maximum of 32 windows can be displayed at the same time. + * + * @return Returns the unique ID of this window if the operation is successful; returns -1 otherwise. + * @since 1.0 + * @version 1.0 + */ + virtual int32_t GetWindowId() = 0; +}; +} +#endif \ No newline at end of file diff --git a/test/framework/BUILD.gn b/test/framework/BUILD.gn new file mode 100755 index 0000000..9dd08db --- /dev/null +++ b/test/framework/BUILD.gn @@ -0,0 +1,84 @@ +# Copyright (c) 2020-2021 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("//build/lite/config/component/lite_component.gni") +import("//build/lite/config/subsystem/graphic/config.gni") + +static_library("framework") { + sources = [ + "../uitest/test_animator/ui_test_animator.cpp", + "../uitest/test_anti_aliasing/ui_test_anti_aliasing.cpp", + "../uitest/test_arc_label/ui_test_arc_label.cpp", + "../uitest/test_button/ui_test_button.cpp", + "../uitest/test_canvas/ui_test_canvas.cpp", + "../uitest/test_chart/ui_test_chart_pillar.cpp", + "../uitest/test_chart/ui_test_chart_polyline.cpp", + "../uitest/test_dialog/ui_test_dialog.cpp", + "../uitest/test_digital_clock/ui_test_digital_clock.cpp", + "../uitest/test_draw_line/ui_test_draw_line.cpp", + "../uitest/test_draw_rect/ui_test_draw_rect.cpp", + "../uitest/test_event_injector/ui_test_event_injector.cpp", + "../uitest/test_font/ui_test_font.cpp", + "../uitest/test_image/ui_test_image.cpp", + "../uitest/test_image_animator/ui_test_image_animator.cpp", + "../uitest/test_input_event/ui_test_input_event.cpp", + "../uitest/test_label/ui_test_label.cpp", + "../uitest/test_layout/ui_test_advanced_layout.cpp", + "../uitest/test_layout/ui_test_basic_layout.cpp", + "../uitest/test_opacity/ui_test_opacity.cpp", + "../uitest/test_picker/ui_test_ui_picker.cpp", + "../uitest/test_progress_bar/ui_test_box_progress.cpp", + "../uitest/test_progress_bar/ui_test_circle_progress.cpp", + "../uitest/test_qrcode/ui_test_qrcode.cpp", + "../uitest/test_render/ui_test_render.cpp", + "../uitest/test_rotate_input/ui_test_rotate_input.cpp", + "../uitest/test_screenshot/ui_test_screenshot.cpp", + "../uitest/test_slider/ui_test_slider.cpp", + "../uitest/test_texture_mapper/ui_test_texture_mapper.cpp", + "../uitest/test_transform/ui_test_transform.cpp", + "../uitest/test_ui_analog_clock/ui_test_analog_clock.cpp", + "../uitest/test_ui_dump_dom_tree/ui_test_dump_dom.cpp", + "../uitest/test_ui_list/ui_test_ui_list.cpp", + "../uitest/test_ui_list_view/ui_test_list_layout.cpp", + "../uitest/test_ui_scroll_view/ui_test_ui_scroll_view.cpp", + "../uitest/test_ui_swipe_view/ui_test_ui_swipe_view.cpp", + "../uitest/test_vector_font/ui_test_vector_font.cpp", + "../uitest/test_video/ui_test_video.cpp", + "../uitest/test_view_percent/ui_test_view_percent.cpp", + "../uitest/test_view_scale_rotate/ui_test_view_scale_rotate.cpp", + "src/test_ability.cpp", + "src/test_case_list_adapter.cpp", + "src/ui_test_app.cpp", + "src/ui_test_group.cpp", + ] + include_dirs = [ + "include", + "../uitest", + "../../tools", + "../../interfaces", + "../../frameworks", + "//foundation/multimedia/utils/lite/include", + "//foundation/multimedia/media_lite/interfaces/kits/player_lite", + ] + public_deps = [ + "//build/lite/config/component/cJSON:cjson_shared", + "//foundation/graphic/ui:ui", + "//foundation/graphic/wms:wms_client", + ] + configs += [ "//foundation/graphic/ui:graphic_define_config" ] +} + +group("lite_graphic_test_framework") { + public_deps = [] + if (ohos_build_type == "debug") { + public_deps += [ ":framework" ] + } +} diff --git a/test/framework/include/test_case_list_adapter.h b/test/framework/include/test_case_list_adapter.h new file mode 100755 index 0000000..bf65b35 --- /dev/null +++ b/test/framework/include/test_case_list_adapter.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TEST_CASE_LIST_ADAPTER_H +#define TEST_CASE_LIST_ADAPTER_H + +#include "components/abstract_adapter.h" +#include "components/root_view.h" +#include "components/ui_label.h" +#include "components/ui_label_button.h" + +namespace OHOS { +class TestCaseListAdapter : public AbstractAdapter { +public: + TestCaseListAdapter(RootView* rootView, UIView* mainList, UILabelButton* backBtn, + UILabel* label, UILabel* testTitleLabel) + : rootView_(rootView), mainList_(mainList), backBtn_(backBtn), testCaseLabel_(label), + testLabel_(testTitleLabel) {} + ~TestCaseListAdapter() {} + UIView* GetView(UIView* inView, int16_t index) override; + uint16_t GetCount() override; + +private: + RootView* rootView_; + UIView* mainList_; + UILabelButton* backBtn_; + UILabel* testCaseLabel_; + UILabel* testLabel_; +}; +} // namespace OHOS +#endif // TEST_CASE_LIST_ADAPTER_H \ No newline at end of file diff --git a/test/framework/include/test_resource_config.h b/test/framework/include/test_resource_config.h new file mode 100755 index 0000000..aa61caa --- /dev/null +++ b/test/framework/include/test_resource_config.h @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TEST_RESOURCE_CONFIG_H +#define TEST_RESOURCE_CONFIG_H + +#include "graphic_config.h" + +namespace OHOS { +#if defined QT_COMPILER +#define IMAGE_DIR "..\\simulator\\config\\images\\" +#define FACE_DIR "..\\simulator\\config\\faces\\default\\" +#elif defined _WIN32 +#define IMAGE_DIR "..\\config\\images\\" +#define FACE_DIR "..\\config\\faces\\default\\" +#else +#define IMAGE_DIR RESOURCE_DIR +#define FACE_DIR RESOURCE_DIR +#endif + +#define BLUE_RGB888_IMAGE_PATH (IMAGE_DIR "blue_rgb888.bin") +#define BLUE_ARGB8888_IMAGE_PATH (IMAGE_DIR "blue_argb8888.bin") +#define BLUE_RGB565_IMAGE_PATH (IMAGE_DIR "blue_rgb565.bin") +#define BLUE_IMAGE_PATH (IMAGE_DIR "blue.png") +#define RED_IMAGE_PATH (IMAGE_DIR "red.png") +#define GREEN_IMAGE_PATH (IMAGE_DIR "green.png") +#define YELLOW_IMAGE_PATH (IMAGE_DIR "yellow.png") +#define IMAGE_ANIMATOR_0_PATH (IMAGE_DIR "image_animator_0.bin") +#define IMAGE_ANIMATOR_1_PATH (IMAGE_DIR "image_animator_1.bin") +#define IMAGE_ANIMATOR_2_PATH (IMAGE_DIR "image_animator_2.bin") +#define IMAGE_ANIMATOR_3_PATH (IMAGE_DIR "image_animator_3.bin") +#define B024_002_IMAGE_PATH (FACE_DIR "B024_002.bin") +#define B024_009_IMAGE_PATH (FACE_DIR "B024_009.bin") +#define B024_010_IMAGE_PATH (FACE_DIR "B024_010.bin") +#define B024_011_IMAGE_PATH (FACE_DIR "B024_011.bin") +#define SLIDER_BACKGROUND_IMAGE_PATH (IMAGE_DIR "sliderBackground.bin") +#define SLIDER_INDICATOR_IMAGE_PATH (IMAGE_DIR "sliderIndicator.bin") +#define SLIDER_KNOB_IMAGE_PATH (IMAGE_DIR "sliderKnob.bin") +#define JPEG_IMAGE_PATH (IMAGE_DIR "jpeg.jpg") +#define PNG_1_PALETTE_IMAGE_PATH (IMAGE_DIR "png_1_palette.png") +#define PNG_2_PALETTE_IMAGE_PATH (IMAGE_DIR "png_2_palette.png") +#define PNG_4_PALETTE_IMAGE_PATH (IMAGE_DIR "png_4_palette.png") +#define PNG_8_PALETTE_IMAGE_PATH (IMAGE_DIR "png_8_palette.png") +#define PNG_8_GRAY_IMAGE_PATH (IMAGE_DIR "png_8_gray.png") +#define PNG_16_GRAY_IMAGE_PATH (IMAGE_DIR "png_16_gray.png") +#define PNG_24_RGB_IMAGE_PATH (IMAGE_DIR "png_24_rgb.png") +#define PNG_32_RGB_ALPHA_IMAGE_PATH (IMAGE_DIR "png_32_rgba.png") +#ifndef VERSION_LITE +#define GIF_IMAGE_PATH1 (IMAGE_DIR "gif_image1.gif") +#define GIF_IMAGE_PATH2 (IMAGE_DIR "gif_image2.gif") +#define GIF_IMAGE_PATH_ERROR (IMAGE_DIR "gif_error.gif") +#define TIF_IMAGE_PATH (IMAGE_DIR "tif_image.tif") +#endif +#define VIDEO_SOURCE_DIRECTORY (IMAGE_DIR "video.mp4") +#define TEST_RIGHT_ARROW (IMAGE_DIR "ic_arrow_right.png") +#define TEST_BACK_LEFT_ARROW (IMAGE_DIR "ic_back.png") +} // namespace OHOS +#endif // TEST_RESOURCE_CONFIG_H \ No newline at end of file diff --git a/test/framework/include/ui_test.h b/test/framework/include/ui_test.h new file mode 100755 index 0000000..03993e3 --- /dev/null +++ b/test/framework/include/ui_test.h @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef UI_TEST_H +#define UI_TEST_H + +#include "components/ui_label.h" +#include "components/ui_view_group.h" +#include "common/screen.h" + +namespace OHOS { +static constexpr uint16_t TITLE_LABEL_DEFAULT_HEIGHT = 29; +static constexpr uint16_t FONT_DEFAULT_SIZE = 20; +static constexpr uint16_t VIEW_DISTANCE_TO_LEFT_SIDE = 48; +static constexpr uint16_t VIEW_DISTANCE_TO_TOP_SIDE = 48; +static constexpr uint16_t VIEW_DISTANCE_TO_LEFT_SIDE2 = 24; +static constexpr uint16_t TEXT_DISTANCE_TO_LEFT_SIDE = 48; +static constexpr uint16_t TEXT_DISTANCE_TO_TOP_SIDE = 11; +static constexpr uint16_t HALF_OPA_OPAQUE = OPA_OPAQUE / 2; +static constexpr uint16_t VIEW_STYLE_BORDER_WIDTH = 2; +static constexpr uint16_t VIEW_STYLE_BORDER_RADIUS = 8; +static constexpr uint16_t BUTTON_LABEL_SIZE = 16; +static constexpr uint8_t BUTTON_STYLE_BORDER_RADIUS_VALUE = 20; +static constexpr uint32_t BUTTON_STYLE_BACKGROUND_COLOR_VALUE = 0xFF333333; +static constexpr uint32_t BUTTON_STYLE_BACKGROUND_COLOR_PRESS = 0xFF2D2D2D; +static constexpr int16_t BACK_BUTTON_HEIGHT = 64; +static constexpr uint16_t BUTTON_WIDHT1 = 80; +static constexpr uint16_t BUTTON_HEIGHT1 = 40; +static constexpr uint16_t BUTTON_WIDHT2 = 120; +static constexpr uint16_t BUTTON_HEIGHT2 = 40; +static constexpr uint16_t BUTTON_WIDHT3 = 150; +static constexpr uint16_t BUTTON_HEIGHT3 = 40; + +class UITest : public HeapBase { +public: + UITest() {} + + virtual ~UITest() {} + + /** + * @brief Set up display enviroment. + * + */ + virtual void SetUp() = 0; + + /** + * @brief Tear down display enviroment. + * + */ + virtual void TearDown() = 0; + + /** + * @brief Get test view to add to root view. + * + * @returns test container view. + * + */ + virtual const UIView* GetTestView() = 0; + + void DeleteChildren(UIView* view) + { + if (view == nullptr) { + return; + } + while (view != nullptr) { + UIView* tempView = view; + view = view->GetNextSibling(); + if (tempView->IsViewGroup() && (tempView->GetViewType() != UI_DIGITAL_CLOCK)) { + DeleteChildren(static_cast(tempView)->GetChildrenHead()); + } + if (tempView->GetParent()) { + static_cast(tempView->GetParent())->Remove(tempView); + } + delete tempView; + } + } + + UILabel* GetTitleLabel(const char* titleName) + { + if (titleName == nullptr) { + return nullptr; + } + UILabel* label = new UILabel(); + if (label == nullptr) { + return nullptr; + } + // 2: half of screen width + label->SetPosition(0, 0, Screen::GetInstance().GetWidth() / 2, TITLE_LABEL_DEFAULT_HEIGHT); + label->SetText(titleName); + label->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + return label; + } + +protected: + int16_t positionX_ = 0; + int16_t positionY_ = 0; +}; +} // namespace OHOS +#endif \ No newline at end of file diff --git a/test/framework/include/ui_test_app.h b/test/framework/include/ui_test_app.h new file mode 100755 index 0000000..164b452 --- /dev/null +++ b/test/framework/include/ui_test_app.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef UI_TEST_APP_LIST_H +#define UI_TEST_APP_LIST_H + +#include "components/root_view.h" +#include "components/ui_label.h" +#include "components/ui_label_button.h" +#include "components/ui_list.h" +#include "test_case_list_adapter.h" + +namespace OHOS { +class UITestApp { +public: + static UITestApp* GetInstance() + { + static UITestApp instance; + return &instance; + } + void Start(); + void Init(); + +private: + UITestApp() {} + ~UITestApp(); + + UITestApp(const UITestApp&) = delete; + UITestApp& operator=(const UITestApp&) = delete; + UITestApp(UITestApp&&) = delete; + UITestApp& operator=(UITestApp&&) = delete; + + RootView* rootView_ = nullptr; + UIList* mainList_ = nullptr; + TestCaseListAdapter* adapter_ = nullptr; + UILabelButton* backBtn_ = nullptr; + UILabel* testCaseLabel_ = nullptr; + UILabel* testLabel_ = nullptr; +}; +} // namespace OHOS +#endif \ No newline at end of file diff --git a/test/framework/include/ui_test_group.h b/test/framework/include/ui_test_group.h new file mode 100755 index 0000000..e5f7fa7 --- /dev/null +++ b/test/framework/include/ui_test_group.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef UI_TEST_GROUP_H +#define UI_TEST_GROUP_H + +#include "components/ui_view.h" +#include "list.h" + +namespace OHOS { +class UITest; +struct TestCaseInfo { + const char* sliceId; + UITest* testObj; +}; + +class UITestGroup { +public: + static void SetUpTestCase(); + static List& GetTestCase(); + UITest* GetTestCase(const char* id); + static void TearDownTestCase(); + static void AddTestCase(TestCaseInfo testCaseInfo); + +private: + static List testCaseList_; +}; +} // namespace OHOS +#endif \ No newline at end of file diff --git a/test/framework/src/test_ability.cpp b/test/framework/src/test_ability.cpp new file mode 100755 index 0000000..066e56b --- /dev/null +++ b/test/framework/src/test_ability.cpp @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ui_test_app.h" + +void RunApp() +{ + OHOS::UITestApp::GetInstance()->Start(); +} \ No newline at end of file diff --git a/test/framework/src/test_case_list_adapter.cpp b/test/framework/src/test_case_list_adapter.cpp new file mode 100755 index 0000000..2747d85 --- /dev/null +++ b/test/framework/src/test_case_list_adapter.cpp @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "test_case_list_adapter.h" +#include "common/screen.h" +#include "components/ui_button.h" +#include "components/ui_label_button.h" +#include "list.h" +#include "test_resource_config.h" +#include "ui_test.h" +#include "ui_test_group.h" + +namespace OHOS { +namespace { +const uint16_t TESTCASE_BUTTON_HEIGHT = 64; +const uint16_t STYLE_BORDER_WIDTH_VALUE = 4; +const uint16_t STYLE_BORDER_RADIUS_VALUE = 12; +const char* g_uiTestId = "graphic_ui_test_case_id"; +} // namespace + +uint16_t TestCaseListAdapter::GetCount() +{ + return UITestGroup::GetTestCase().Size(); +} + +class BtnOnClickBackListener : public UIView::OnClickListener { +public: + BtnOnClickBackListener(UIViewGroup* uiView, + UIView* mainList, + UITest* uiTest, + UILabel* testCaseLabel, + UILabel* testLabel) + : rootView_(uiView), mainList_(mainList), uiTest_(uiTest), testCaseLabel_(testCaseLabel), testLabel_(testLabel) + { + } + + ~BtnOnClickBackListener() {} + + bool OnClick(UIView& view, const ClickEvent& event) override + { + if ((rootView_ == nullptr) || (mainList_ == nullptr) || (uiTest_ == nullptr) || (testCaseLabel_ == nullptr) || + (testLabel_ == nullptr)) { + return false; + } + + rootView_->Remove(testCaseLabel_); + rootView_->Remove(&view); + + UIView* tempView = rootView_->GetChildById(g_uiTestId); + if (tempView != nullptr) { + rootView_->Remove(tempView); + uiTest_->TearDown(); + } + + rootView_->Add(testLabel_); + rootView_->Add(mainList_); + rootView_->Invalidate(); + return true; + } + +private: + UIViewGroup* rootView_; + UIView* mainList_; + UILabel* testCaseLabel_; + UILabel* testLabel_; + UITest* uiTest_; +}; + +class BtnOnClickUiTestListener : public UIView::OnClickListener { +public: + BtnOnClickUiTestListener(UIViewGroup* uiView, + UIView* mainList, + UILabelButton* backBtn, + TestCaseInfo* uiTestInfo, + UILabel* testCaseLabel, + UILabel* testLabel) + : rootView_(uiView), + mainList_(mainList), + backBtn_(backBtn), + testCaseLabel_(testCaseLabel), + testLabel_(testLabel), + uiTest_(nullptr), + sliceId_(nullptr) + { + if (uiTestInfo != nullptr) { + uiTest_ = uiTestInfo->testObj; + sliceId_ = uiTestInfo->sliceId; + } + } + ~BtnOnClickUiTestListener() {} + bool OnClick(UIView& view, const ClickEvent& event) override + { + if ((rootView_ == nullptr) || (mainList_ == nullptr) || (backBtn_ == nullptr) || (testCaseLabel_ == nullptr) || + (testLabel_ == nullptr) || (uiTest_ == nullptr) || (sliceId_ == nullptr)) { + return false; + } + rootView_->Remove(testLabel_); + rootView_->Remove(mainList_); + + UIView::OnClickListener* click = backBtn_->GetOnClickListener(); + if (click != nullptr) { + delete click; + click = nullptr; + } + click = new BtnOnClickBackListener(rootView_, mainList_, uiTest_, testCaseLabel_, testLabel_); + backBtn_->SetOnClickListener(click); + rootView_->Add(backBtn_); + if (testCaseLabel_ != nullptr) { + testCaseLabel_->SetText(sliceId_); + } + rootView_->Add(testCaseLabel_); + + uiTest_->SetUp(); + UIView* tempView = const_cast(uiTest_->GetTestView()); + if (tempView != nullptr) { + tempView->SetViewId(g_uiTestId); + tempView->SetPosition(tempView->GetX(), tempView->GetY() + backBtn_->GetHeight()); + rootView_->Add(tempView); + } + rootView_->Invalidate(); + return true; + } + +private: + UIViewGroup* rootView_; + UIView* mainList_; + UILabel* testLabel_; + UILabelButton* backBtn_; + UITest* uiTest_; + UILabel* testCaseLabel_; + const char* sliceId_; +}; + +UIView* TestCaseListAdapter::GetView(UIView* inView, int16_t index) +{ + List testCaseList = UITestGroup::GetTestCase(); + if (testCaseList.IsEmpty()) { + return nullptr; + } + if ((index > testCaseList.Size() - 1) || (index < 0)) { + return nullptr; + } + UILabelButton* item = nullptr; + if (inView == nullptr) { + item = new UILabelButton(); + item->SetPosition(0, 0); + item->SetStyleForState(STYLE_BORDER_WIDTH, STYLE_BORDER_WIDTH_VALUE, UIButton::RELEASED); + item->SetStyleForState(STYLE_BORDER_WIDTH, STYLE_BORDER_WIDTH_VALUE, UIButton::PRESSED); + item->SetStyleForState(STYLE_BORDER_WIDTH, STYLE_BORDER_WIDTH_VALUE, UIButton::INACTIVE); + item->SetStyleForState(STYLE_BORDER_OPA, 0, UIButton::RELEASED); + item->SetStyleForState(STYLE_BORDER_OPA, 0, UIButton::PRESSED); + item->SetStyleForState(STYLE_BORDER_OPA, 0, UIButton::INACTIVE); + item->Resize(Screen::GetInstance().GetWidth() - TEXT_DISTANCE_TO_LEFT_SIDE, TESTCASE_BUTTON_HEIGHT); + } else { + item = static_cast(inView); + } + + UIView::OnClickListener* listener = item->GetOnClickListener(); + if (listener != nullptr) { + delete listener; + listener = nullptr; + } + ListNode* node = testCaseList.Begin(); + for (uint16_t i = 0; i < index; i++) { + node = node->next_; + } + listener = new BtnOnClickUiTestListener(rootView_, mainList_, backBtn_, &node->data_, testCaseLabel_, testLabel_); + item->SetOnClickListener(listener); + item->SetText(node->data_.sliceId); + item->SetFont(DEFAULT_VECTOR_FONT_FILENAME, 24); // 24: means font size + item->SetViewIndex(index); + item->SetAlign(TEXT_ALIGNMENT_LEFT); + item->SetLablePosition(24, 0); // 24: lable x-coordinate + item->SetImageSrc(TEST_RIGHT_ARROW, TEST_RIGHT_ARROW); + // 2: half of button height; 18: half px of image height + item->SetImagePosition(item->GetWidth() - TEXT_DISTANCE_TO_LEFT_SIDE, TESTCASE_BUTTON_HEIGHT / 2 - 18); + item->SetStyleForState(STYLE_BORDER_RADIUS, STYLE_BORDER_RADIUS_VALUE, UIButton::RELEASED); + item->SetStyleForState(STYLE_BORDER_RADIUS, STYLE_BORDER_RADIUS_VALUE, UIButton::PRESSED); + item->SetStyleForState(STYLE_BORDER_RADIUS, STYLE_BORDER_RADIUS_VALUE, UIButton::INACTIVE); + item->SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::RELEASED); + item->SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::PRESSED); + item->SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::INACTIVE); + return item; +} +} // namespace OHOS \ No newline at end of file diff --git a/test/framework/src/ui_test_app.cpp b/test/framework/src/ui_test_app.cpp new file mode 100755 index 0000000..5444a84 --- /dev/null +++ b/test/framework/src/ui_test_app.cpp @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ui_test_app.h" +#include "common/screen.h" +#include "test_resource_config.h" +#include "ui_test.h" +#include "ui_test_group.h" + +namespace OHOS { +void UITestApp::Start() +{ + if (rootView_ == nullptr) { + rootView_ = RootView::GetInstance(); + rootView_->SetPosition(0, 0); + rootView_->Resize(Screen::GetInstance().GetWidth(), Screen::GetInstance().GetHeight()); + } + Init(); +} + +void UITestApp::Init() +{ + if (backBtn_ == nullptr) { + backBtn_ = new UILabelButton(); + backBtn_->SetPosition(0, 0); + backBtn_->Resize(163, 64); // 163: button width; 64: button height + backBtn_->SetText("Back"); + backBtn_->SetLablePosition(72, 0); // 72: button label x-coordinate + backBtn_->SetFont(DEFAULT_VECTOR_FONT_FILENAME, 24); // 24: means font size + backBtn_->SetImageSrc(TEST_BACK_LEFT_ARROW, TEST_BACK_LEFT_ARROW); + // 27: button Image x-coordinate; 18: half px of image height + backBtn_->SetImagePosition(27, BACK_BUTTON_HEIGHT / 2 - 18); + backBtn_->SetStyleForState(STYLE_BORDER_RADIUS, 0, UIButton::RELEASED); + backBtn_->SetStyleForState(STYLE_BORDER_RADIUS, 0, UIButton::PRESSED); + backBtn_->SetStyleForState(STYLE_BORDER_RADIUS, 0, UIButton::INACTIVE); + backBtn_->SetStyleForState(STYLE_BACKGROUND_OPA, 0, UIButton::RELEASED); + backBtn_->SetStyleForState(STYLE_BACKGROUND_OPA, 0, UIButton::PRESSED); + backBtn_->SetStyleForState(STYLE_BACKGROUND_OPA, 0, UIButton::INACTIVE); + } + if (testCaseLabel_ == nullptr) { + testCaseLabel_ = new UILabel(); + testCaseLabel_->Resize(Screen::GetInstance().GetWidth(), BACK_BUTTON_HEIGHT); + testCaseLabel_->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER); + testCaseLabel_->SetText("Test Case Name"); + testCaseLabel_->SetFont(DEFAULT_VECTOR_FONT_FILENAME, 32); // 32: means font size + } + if (testLabel_ == nullptr) { + testLabel_ = new UILabel(); + testLabel_->Resize(Screen::GetInstance().GetWidth(), BACK_BUTTON_HEIGHT); + testLabel_->SetAlign(TEXT_ALIGNMENT_LEFT, TEXT_ALIGNMENT_CENTER); + testLabel_->SetPosition(TEXT_DISTANCE_TO_LEFT_SIDE, 0); + testLabel_->SetText("Test Demo"); + testLabel_->SetFont(DEFAULT_VECTOR_FONT_FILENAME, 32); // 32: means font size + rootView_->Add(testLabel_); + } + if ((mainList_ == nullptr) && (adapter_ == nullptr)) { + uint8_t deltaHeight = 60; // 60: UIList height(64) - first button border width(4) + mainList_ = new UIList(UIList::VERTICAL); + mainList_->SetPosition(24, deltaHeight); // 24: x-coordinate + mainList_->Resize(Screen::GetInstance().GetWidth(), Screen::GetInstance().GetHeight() - deltaHeight); + mainList_->SetThrowDrag(true); + adapter_ = new TestCaseListAdapter(rootView_, mainList_, backBtn_, testCaseLabel_, testLabel_); + UITestGroup::SetUpTestCase(); + mainList_->SetAdapter(adapter_); + rootView_->Add(mainList_); + rootView_->Invalidate(); + } +} + +UITestApp::~UITestApp() +{ + if (mainList_ != nullptr) { + delete adapter_; + adapter_ = nullptr; + } + if (adapter_ != nullptr) { + delete mainList_; + mainList_ = nullptr; + } + if (backBtn_ != nullptr) { + delete backBtn_; + backBtn_ = nullptr; + } + if (rootView_ != nullptr) { + rootView_ = nullptr; + } +} +} // namespace OHOS \ No newline at end of file diff --git a/test/framework/src/ui_test_group.cpp b/test/framework/src/ui_test_group.cpp new file mode 100755 index 0000000..1f7bb78 --- /dev/null +++ b/test/framework/src/ui_test_group.cpp @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ui_test_group.h" +#include "graphic_config.h" +#include "test_animator/ui_test_animator.h" +#include "test_anti_aliasing/ui_test_anti_aliasing.h" +#include "test_arc_label/ui_test_arc_label.h" +#include "test_button/ui_test_button.h" +#include "test_canvas/ui_test_canvas.h" +#include "test_chart/ui_test_chart_pillar.h" +#include "test_chart/ui_test_chart_polyline.h" +#ifdef ENABLE_WINDOW +#include "test_dialog/ui_test_dialog.h" +#endif +#include "test_draw_line/ui_test_draw_line.h" +#include "test_draw_rect/ui_test_draw_rect.h" +#include "test_event_injector/ui_test_event_injector.h" +#include "test_font/ui_test_font.h" +#include "test_image/ui_test_image.h" +#include "test_image_animator/ui_test_image_animator.h" +#include "test_input_event/ui_test_input_event.h" +#include "test_label/ui_test_label.h" +#include "test_layout/ui_test_advanced_layout.h" +#include "test_layout/ui_test_basic_layout.h" +#include "test_opacity/ui_test_opacity.h" +#include "test_picker/ui_test_ui_picker.h" +#include "test_progress_bar/ui_test_box_progress.h" +#include "test_progress_bar/ui_test_circle_progress.h" +#include "test_qrcode/ui_test_qrcode.h" +#include "test_render/ui_test_render.h" +#if ENABLE_ROTATE_INPUT +#include "test_rotate_input/ui_test_rotate_input.h" +#endif +#ifdef VERSION_STANDARD +#include "test_screenshot/ui_test_screenshot.h" +#endif +#include "test_digital_clock/ui_test_digital_clock.h" +#include "test_slider/ui_test_slider.h" +#include "test_texture_mapper/ui_test_texture_mapper.h" +#include "test_transform/ui_test_transform.h" +#include "test_ui_analog_clock/ui_test_analog_clock.h" +#include "test_ui_dump_dom_tree/ui_test_dump_dom.h" +#include "test_ui_list/ui_test_ui_list.h" +#include "test_ui_list_view/ui_test_list_layout.h" +#include "test_ui_scroll_view/ui_test_ui_scroll_view.h" +#include "test_ui_swipe_view/ui_test_ui_swipe_view.h" +#include "test_view_percent/ui_test_view_percent.h" +#include "test_view_scale_rotate/ui_test_view_scale_rotate.h" +#if ENABLE_VECTOR_FONT +#include "test_vector_font/ui_test_vector_font.h" +#endif +#ifndef VERSION_LITE +#include "test_video/ui_test_video.h" +#endif + +namespace OHOS { +List UITestGroup::testCaseList_; + +void UITestGroup::AddTestCase(TestCaseInfo testCaseInfo) +{ + testCaseList_.PushBack(testCaseInfo); +} + +void UITestGroup::SetUpTestCase() +{ +#if ENABLE_ROTATE_INPUT + testCaseList_.PushBack(TestCaseInfo{"Rotate_Input", new UITestRotateInput()}); +#endif + testCaseList_.PushBack(TestCaseInfo{"View_Scale_Rotate", new UITestViewScaleRotate()}); +#if ENABLE_VECTOR_FONT + if (UIFont::GetInstance()->IsVectorFont()) { + testCaseList_.PushBack(TestCaseInfo{"Vector_Font", new UITestVectorFont()}); + } +#endif + testCaseList_.PushBack(TestCaseInfo{"Input_Event", new UITestInputEvent()}); + testCaseList_.PushBack(TestCaseInfo{"Button", new UITestBUTTON()}); + testCaseList_.PushBack(TestCaseInfo{"UILabel", new UITestLabel()}); + testCaseList_.PushBack(TestCaseInfo{"Image", new UITestImage()}); + testCaseList_.PushBack(TestCaseInfo{"Basic_Layout", new UITestBasicLayout()}); + testCaseList_.PushBack(TestCaseInfo{"Advanced_Layout", new UITestAdvancedLayout()}); + testCaseList_.PushBack(TestCaseInfo{"ListLayout", new UITestListLayout()}); + testCaseList_.PushBack(TestCaseInfo{"UIScrollView", new UITestUIScrollView()}); + testCaseList_.PushBack(TestCaseInfo{"Box_Progress", new UITestBoxProgress()}); + testCaseList_.PushBack(TestCaseInfo{"Circle_Progress", new UITestCircleProgress()}); + testCaseList_.PushBack(TestCaseInfo{"Slider", new UITestSlider()}); + testCaseList_.PushBack(TestCaseInfo{"Animator", new UITestAnimator()}); + testCaseList_.PushBack(TestCaseInfo{"Canvas", new UITestCanvas()}); + testCaseList_.PushBack(TestCaseInfo{"Draw_Rect", new UITestDrawRect()}); + testCaseList_.PushBack(TestCaseInfo{"Draw_Line", new UITestDrawLine()}); + testCaseList_.PushBack(TestCaseInfo{"Render", new UITestRender()}); + testCaseList_.PushBack(TestCaseInfo{"Anti_Aliasing", new UITestAntiAliasing()}); + testCaseList_.PushBack(TestCaseInfo{"UIList", new UITestUIList()}); + testCaseList_.PushBack(TestCaseInfo{"UISwipeView", new UITestUISwipeView()}); + testCaseList_.PushBack(TestCaseInfo{"Image_Animator", new UITestImageAnimator()}); + testCaseList_.PushBack(TestCaseInfo{"UIPicker", new UITestUIPicker()}); + testCaseList_.PushBack(TestCaseInfo{"Font", new UITestFont()}); + testCaseList_.PushBack(TestCaseInfo{"Arc_Label", new UITestArcLabel()}); +#if ENABLE_DEBUG + testCaseList_.PushBack(TestCaseInfo{"Event_Injector", new UITestEventInjector()}); + testCaseList_.PushBack(TestCaseInfo{"Dump_Dom", new UITestDumpDomTree()}); +#ifdef VERSION_STANDARD + testCaseList_.PushBack(TestCaseInfo{"Screenshot", new UITestScreenshot()}); +#endif +#endif // ENABLE_DEBUG + testCaseList_.PushBack(TestCaseInfo{"View_Percent", new UITestViewPercent()}); + testCaseList_.PushBack(TestCaseInfo{"Texture_Mapper", new UITestTextureMapper()}); + testCaseList_.PushBack(TestCaseInfo{"Chart_Polyline", new UITestChartPolyline()}); + testCaseList_.PushBack(TestCaseInfo{"Chart_Pillar", new UITestChartPillar()}); +#if ENABLE_WINDOW + testCaseList_.PushBack(TestCaseInfo{"Dialog", new UITestDialog()}); +#endif + testCaseList_.PushBack(TestCaseInfo{"Analog_Clock", new UITestAnalogClock()}); + testCaseList_.PushBack(TestCaseInfo{"Digital_Clock", new UITestDigitalClock()}); + testCaseList_.PushBack(TestCaseInfo{"Transform", new UITestTransform()}); + testCaseList_.PushBack(TestCaseInfo{"Opacity", new UITestOpacity()}); + testCaseList_.PushBack(TestCaseInfo{"UIQrcode", new UITestQrcode()}); +#ifndef VERSION_LITE + testCaseList_.PushBack(TestCaseInfo{"Video", new UITestVideo()}); +#endif +} + +List& UITestGroup::GetTestCase() +{ + return testCaseList_; +} + +UITest* UITestGroup::GetTestCase(const char* id) +{ + if (id == nullptr) { + return nullptr; + } + ListNode* node = testCaseList_.Begin(); + while (node != testCaseList_.End()) { + if (!strcmp(id, node->data_.sliceId)) { + return node->data_.testObj; + } + node = node->next_; + } + return nullptr; +} +void UITestGroup::TearDownTestCase() +{ + ListNode* node = testCaseList_.Begin(); + while (node != testCaseList_.End()) { + delete node->data_.testObj; + node->data_.testObj = nullptr; + node = node->next_; + } + testCaseList_.Clear(); +} +} // namespace OHOS diff --git a/test/uitest/test_animator/ui_test_animator.cpp b/test/uitest/test_animator/ui_test_animator.cpp new file mode 100755 index 0000000..73c8f54 --- /dev/null +++ b/test/uitest/test_animator/ui_test_animator.cpp @@ -0,0 +1,310 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ui_test_animator.h" +#include "common/screen.h" +#include "components/ui_label.h" +#include "test_resource_config.h" + +namespace OHOS { +namespace { +const int16_t LABEL_HEIGHT = 29; +const int16_t BUTTON_WIDTH = BUTTON_WIDHT3; +const int16_t BUTTON_HEIGHT = BUTTON_HEIGHT3; +const int16_t DELTA_X_COORDINATE = 5; +const int16_t DELTA_Y_COORDINATE = 12; +} // namespace + +void UITestAnimator::SetUp() +{ + if (container_ == nullptr) { + container_ = new UIScrollView(); + container_->Resize(Screen::GetInstance().GetWidth(), Screen::GetInstance().GetHeight() - BACK_BUTTON_HEIGHT); + uiViewGroupFrame_ = new UIViewGroup(); + uiViewGroupFrame_->SetStyle(STYLE_BORDER_COLOR, Color::White().full); + uiViewGroupFrame_->SetStyle(STYLE_BORDER_OPA, HALF_OPA_OPAQUE); + uiViewGroupFrame_->SetStyle(STYLE_BORDER_WIDTH, VIEW_STYLE_BORDER_WIDTH); + uiViewGroupFrame_->SetStyle(STYLE_BORDER_RADIUS, VIEW_STYLE_BORDER_RADIUS); + uiViewGroupFrame_->SetStyle(STYLE_BACKGROUND_OPA, 0); + uiViewGroupFrame_->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE2, VIEW_DISTANCE_TO_TOP_SIDE); + container_->Add(uiViewGroupFrame_); + UILabel* label = new UILabel(); + container_->Add(label); + // 264: label width; 48: label height + label->SetPosition(TEXT_DISTANCE_TO_LEFT_SIDE, TEXT_DISTANCE_TO_TOP_SIDE, 264, 48); + label->SetText("动画效果"); + label->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + image_ = new UIImageView(); + image_->SetSrc(RED_IMAGE_PATH); + image_->SetPosition(0, 5); // 5:y-coordinate + callback_ = new ImageEaseAnimatorCallback(image_, 0, 338); // 338:endPos + animator_ = callback_->GetAnimator(); + uiViewGroupFrame_->Add(image_); + uiViewGroupFrame_->Resize(Screen::GetInstance().GetWidth() - VIEW_DISTANCE_TO_LEFT_SIDE, + image_->GetHeight() + 10); // 10: increase y-coordinate + } + if (scroll_ == nullptr) { + scroll_ = new UIScrollView(); + // 20: increase y-coordinate + uint16_t y = uiViewGroupFrame_->GetY() + uiViewGroupFrame_->GetHeight() + 20; + scroll_->SetPosition(0, y, Screen::GetInstance().GetWidth(), + Screen::GetInstance().GetHeight() - y - 64); // 64: decrease y-coordinate + container_->Add(scroll_); + positionX_ = 0; + } +} + +void UITestAnimator::TearDown() +{ + DeleteChildren(container_); + container_ = nullptr; + image_ = nullptr; + scroll_ = nullptr; + delete callback_; + callback_ = nullptr; + uiViewGroupFrame_ = nullptr; +} + +UIView* UITestAnimator::GetTestView() +{ + UIKit_Animator_Test_BackEasing_001(); + UIKit_Animator_Test_CircEasing_002(); + UIKit_Animator_Test_CubicEasing_003(); + UIKit_Animator_Test_SineEasing_004(); + UIKit_Animator_Test_QuadEasing_005(); + UIKit_Animator_Test_QuintEasing_006(); + UIKit_Animator_Test_LinearEasing_007(); + UIKit_Animator_Test_FPS_008(); + return container_; +} + +void UITestAnimator::SetUpLabel(const char* title, int16_t x, int16_t y) +{ + UILabel* label = GetTitleLabel(title); + scroll_->Add(label); + label->SetPosition(x, y, 288, LABEL_HEIGHT); // 288: label width +} + +void UITestAnimator::SetUpButton(UILabelButton* btn, const char* title, int16_t x, int16_t y) +{ + if (btn == nullptr) { + return; + } + scroll_->Add(btn); + btn->SetPosition(x, y, BUTTON_WIDHT3, BUTTON_HEIGHT3); + btn->SetText(title); + btn->SetFont(DEFAULT_VECTOR_FONT_FILENAME, BUTTON_LABEL_SIZE); + btn->SetOnClickListener(this); + btn->SetStyleForState(STYLE_BORDER_RADIUS, BUTTON_STYLE_BORDER_RADIUS_VALUE, UIButton::RELEASED); + btn->SetStyleForState(STYLE_BORDER_RADIUS, BUTTON_STYLE_BORDER_RADIUS_VALUE, UIButton::PRESSED); + btn->SetStyleForState(STYLE_BORDER_RADIUS, BUTTON_STYLE_BORDER_RADIUS_VALUE, UIButton::INACTIVE); + btn->SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::RELEASED); + btn->SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::PRESSED); + btn->SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::INACTIVE); + scroll_->Invalidate(); +} + +void UITestAnimator::UIKit_Animator_Test_BackEasing_001() +{ + backOvershootBtn_ = new UILabelButton(); + backEaseInBtn_ = new UILabelButton(); + backEaseOutBtn_ = new UILabelButton(); + backEaseInOutBtn_ = new UILabelButton(); + positionX_ = TEXT_DISTANCE_TO_LEFT_SIDE; + positionY_ = 0; + SetUpLabel("back动画效果: ", positionX_, positionY_); + positionY_ += LABEL_HEIGHT + DELTA_X_COORDINATE; + SetUpButton(backOvershootBtn_, "overshoot+", positionX_, positionY_); + positionX_ += BUTTON_WIDTH + DELTA_Y_COORDINATE; + SetUpButton(backEaseInBtn_, "BackEaseIn", positionX_, positionY_); + positionX_ = TEXT_DISTANCE_TO_LEFT_SIDE; + positionY_ += BUTTON_HEIGHT + DELTA_Y_COORDINATE; + SetUpButton(backEaseOutBtn_, "BackEaseOut", positionX_, positionY_); + positionX_ += BUTTON_WIDTH + DELTA_Y_COORDINATE; + SetUpButton(backEaseInOutBtn_, "BackEaseInOut", positionX_, positionY_); +} + +void UITestAnimator::UIKit_Animator_Test_CircEasing_002() +{ + circEaseInBtn_ = new UILabelButton(); + circEaseOutBtn_ = new UILabelButton(); + circEaseInOutBtn_ = new UILabelButton(); + // 2: half of screen width + positionX_ = Screen::GetInstance().GetWidth() / 2 + TEXT_DISTANCE_TO_LEFT_SIDE; + positionY_ = 0; + SetUpLabel("circ动画效果: ", positionX_, positionY_); + positionY_ += LABEL_HEIGHT + DELTA_X_COORDINATE; + SetUpButton(circEaseInBtn_, "CircEaseIn", positionX_, positionY_); + positionX_ += BUTTON_WIDTH + DELTA_Y_COORDINATE; + SetUpButton(circEaseOutBtn_, "CircEaseOut", positionX_, positionY_); + // 2: half of screen width + positionX_ = Screen::GetInstance().GetWidth() / 2 + TEXT_DISTANCE_TO_LEFT_SIDE; + positionY_ += BUTTON_HEIGHT + DELTA_Y_COORDINATE; + SetUpButton(circEaseInOutBtn_, "CircEaseInOut", positionX_, positionY_); +} + +void UITestAnimator::UIKit_Animator_Test_CubicEasing_003() +{ + cubicEaseInBtn_ = new UILabelButton(); + cubicEaseOutBtn_ = new UILabelButton(); + cubicEaseInOutBtn_ = new UILabelButton(); + positionX_ = TEXT_DISTANCE_TO_LEFT_SIDE; + positionY_ = 148; // 148: y-coordinate + SetUpLabel("cubic动画效果: ", positionX_, positionY_); + positionY_ += LABEL_HEIGHT + DELTA_X_COORDINATE; + SetUpButton(cubicEaseInBtn_, "CubicEaseIn", positionX_, positionY_); + positionX_ += BUTTON_WIDTH + DELTA_Y_COORDINATE; + SetUpButton(cubicEaseOutBtn_, "CubicEaseOut", positionX_, positionY_); + positionX_ = TEXT_DISTANCE_TO_LEFT_SIDE; + positionY_ += BUTTON_HEIGHT + DELTA_Y_COORDINATE; + SetUpButton(cubicEaseInOutBtn_, "CubicEaseInOut", positionX_, positionY_); +} + +void UITestAnimator::UIKit_Animator_Test_SineEasing_004() +{ + sineEaseInBtn_ = new UILabelButton(); + sineEaseOutBtn_ = new UILabelButton(); + sineEaseInOutBtn_ = new UILabelButton(); + // 2: half of screen width + positionX_ = Screen::GetInstance().GetWidth() / 2 + TEXT_DISTANCE_TO_LEFT_SIDE; + positionY_ = 148; // 148: y-coordinate + SetUpLabel("sine动画效果: ", positionX_, positionY_); + positionY_ += LABEL_HEIGHT + DELTA_X_COORDINATE; + SetUpButton(sineEaseInBtn_, "SineEaseIn", positionX_, positionY_); + positionX_ += BUTTON_WIDTH + DELTA_Y_COORDINATE; + SetUpButton(sineEaseOutBtn_, "SineEaseOut", positionX_, positionY_); + // 2: half of screen width + positionX_ = Screen::GetInstance().GetWidth() / 2 + TEXT_DISTANCE_TO_LEFT_SIDE; + positionY_ += BUTTON_HEIGHT + DELTA_Y_COORDINATE; + SetUpButton(sineEaseInOutBtn_, "SineEaseInOut", positionX_, positionY_); +} + +void UITestAnimator::UIKit_Animator_Test_QuadEasing_005() +{ + quadEaseInBtn_ = new UILabelButton(); + quadEaseOutBtn_ = new UILabelButton(); + quadEaseInOutBtn_ = new UILabelButton(); + positionX_ = TEXT_DISTANCE_TO_LEFT_SIDE; + positionY_ = 296; // 296: y-coordinate + SetUpLabel("quad动画效果: ", positionX_, positionY_); + positionY_ += LABEL_HEIGHT + DELTA_X_COORDINATE; + SetUpButton(quadEaseInBtn_, "QuadEaseIn", positionX_, positionY_); + positionX_ += BUTTON_WIDTH + DELTA_Y_COORDINATE; + SetUpButton(quadEaseOutBtn_, "QuadEaseOut", positionX_, positionY_); + positionX_ = TEXT_DISTANCE_TO_LEFT_SIDE; + positionY_ += BUTTON_HEIGHT + DELTA_Y_COORDINATE; + SetUpButton(quadEaseInOutBtn_, "QuadEaseInOut", positionX_, positionY_); +} + +void UITestAnimator::UIKit_Animator_Test_QuintEasing_006() +{ + quintEaseInBtn_ = new UILabelButton(); + quintEaseOutBtn_ = new UILabelButton(); + quintEaseInOutBtn_ = new UILabelButton(); + // 2: half of screen width + positionX_ = Screen::GetInstance().GetWidth() / 2 + TEXT_DISTANCE_TO_LEFT_SIDE; + positionY_ = 296; // 296: y-coordinate + SetUpLabel("quint动画效果: ", positionX_, positionY_); + positionY_ += LABEL_HEIGHT + DELTA_X_COORDINATE; + SetUpButton(quintEaseInBtn_, "QuintEaseIn", positionX_, positionY_); + positionX_ += BUTTON_WIDTH + DELTA_Y_COORDINATE; + SetUpButton(quintEaseOutBtn_, "QuintEaseOut", positionX_, positionY_); + // 2: half of screen width + positionX_ = Screen::GetInstance().GetWidth() / 2 + TEXT_DISTANCE_TO_LEFT_SIDE; + positionY_ += BUTTON_HEIGHT + DELTA_Y_COORDINATE; + SetUpButton(quintEaseInOutBtn_, "QuintEaseInOut", positionX_, positionY_); +} + +void UITestAnimator::UIKit_Animator_Test_LinearEasing_007() +{ + linearEaseNoneBtn_ = new UILabelButton(); + positionX_ = TEXT_DISTANCE_TO_LEFT_SIDE; + positionY_ = 444; // 444: y-coordinate + SetUpLabel("linear动画效果: ", positionX_, positionY_); + positionY_ += LABEL_HEIGHT + DELTA_X_COORDINATE; + SetUpButton(linearEaseNoneBtn_, "LinearEaseNone", positionX_, positionY_); +} + +void UITestAnimator::UIKit_Animator_Test_FPS_008() +{ + fpsLabel_ = new UILabel(); + // 90:x-coordinate, 0:y-coordinate, 90:new width, 20:new height + fpsLabel_->SetPosition(Screen::GetInstance().GetWidth() - 90, TEXT_DISTANCE_TO_TOP_SIDE, 90, + TITLE_LABEL_DEFAULT_HEIGHT); + fpsLabel_->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + char buf[10] = "FPS"; + fpsLabel_->SetText(reinterpret_cast(buf)); + OnFPSChangedListener::SetFPSCalculateType(SysInfo::FPS_CT_PRECISE_SAMPLING); + container_->Add(fpsLabel_); + fpsLabel_->Invalidate(); +} + +bool UITestAnimator::OnClick(UIView& view, const ClickEvent& event) +{ + static double overshoot = 1.7; + if (&view == backOvershootBtn_) { + overshoot += 1; + EasingEquation::SetBackOvershoot(overshoot); + } else if (&view == backEaseInBtn_) { + callback_->SetEasingFunc(EasingEquation::BackEaseIn); + } else if (&view == backEaseOutBtn_) { + callback_->SetEasingFunc(EasingEquation::BackEaseOut); + } else if (&view == backEaseInOutBtn_) { + callback_->SetEasingFunc(EasingEquation::BackEaseInOut); + } else if (&view == circEaseInBtn_) { + callback_->SetEasingFunc(EasingEquation::CircEaseIn); + } else if (&view == circEaseOutBtn_) { + callback_->SetEasingFunc(EasingEquation::CircEaseOut); + } else if (&view == circEaseInOutBtn_) { + callback_->SetEasingFunc(EasingEquation::CircEaseInOut); + } else if (&view == cubicEaseInBtn_) { + callback_->SetEasingFunc(EasingEquation::CubicEaseIn); + } else if (&view == cubicEaseOutBtn_) { + callback_->SetEasingFunc(EasingEquation::CubicEaseOut); + } else if (&view == cubicEaseInOutBtn_) { + callback_->SetEasingFunc(EasingEquation::CubicEaseInOut); + } else if (&view == linearEaseNoneBtn_) { + callback_->SetEasingFunc(EasingEquation::LinearEaseNone); + } else if (&view == quadEaseInBtn_) { + callback_->SetEasingFunc(EasingEquation::QuadEaseIn); + } else if (&view == quadEaseOutBtn_) { + callback_->SetEasingFunc(EasingEquation::QuadEaseOut); + } else if (&view == quadEaseInOutBtn_) { + callback_->SetEasingFunc(EasingEquation::QuadEaseInOut); + } else if (&view == quintEaseInBtn_) { + callback_->SetEasingFunc(EasingEquation::QuintEaseIn); + } else if (&view == quintEaseOutBtn_) { + callback_->SetEasingFunc(EasingEquation::QuintEaseOut); + } else if (&view == quintEaseInOutBtn_) { + callback_->SetEasingFunc(EasingEquation::QuintEaseInOut); + } else if (&view == sineEaseInBtn_) { + callback_->SetEasingFunc(EasingEquation::SineEaseIn); + } else if (&view == sineEaseOutBtn_) { + callback_->SetEasingFunc(EasingEquation::SineEaseOut); + } else if (&view == sineEaseInOutBtn_) { + callback_->SetEasingFunc(EasingEquation::SineEaseInOut); + } + animator_->Start(); + container_->Invalidate(); + return true; +} + +void UITestAnimator::OnFPSChanged(float newFPS) +{ + char buf[10] = "FPS"; + fpsLabel_->SetText(reinterpret_cast(buf)); + fpsLabel_->Invalidate(); +} +} // namespace OHOS \ No newline at end of file diff --git a/test/uitest/test_animator/ui_test_animator.h b/test/uitest/test_animator/ui_test_animator.h new file mode 100755 index 0000000..6efd1a4 --- /dev/null +++ b/test/uitest/test_animator/ui_test_animator.h @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef UI_TEST_ANIMATOR_H +#define UI_TEST_ANIMATOR_H + +#include "animator/easing_equation.h" +#include "components/ui_image_view.h" +#include "components/ui_label.h" +#include "components/ui_label_button.h" +#include "components/ui_scroll_view.h" +#include "sys_info.h" +#include "ui_test.h" + +namespace OHOS { +class ImageEaseAnimatorCallback : public AnimatorCallback { +public: + ImageEaseAnimatorCallback(UIView* uiView, int16_t startPos, int16_t endPos) + : startPos_(startPos), + endPos_(endPos), + easingFunc_(EasingEquation::LinearEaseNone), + animator_(new Animator(this, uiView, 1000, true)) // 1000:duration of animator_, in milliseconds. + { + AnimatorManager::GetInstance()->Add(animator_); + } + + virtual ~ImageEaseAnimatorCallback() + { + AnimatorManager::GetInstance()->Remove(animator_); + delete animator_; + } + + Animator* GetAnimator() const + { + return animator_; + } + + void SetEasingFunc(EasingFunc easingFunc) + { + easingFunc_ = easingFunc; + } + + virtual void Callback(UIView* view) + { + if (view == nullptr) { + return; + } + int16_t pos = easingFunc_(startPos_, endPos_, animator_->GetRunTime(), animator_->GetTime()); + Rect invalidatedArea = view->GetRect(); + view->SetPosition(pos, view->GetY()); + invalidatedArea.Join(invalidatedArea, view->GetRect()); + view->InvalidateRect(invalidatedArea); + } + +protected: + int16_t startPos_; + int16_t endPos_; + EasingFunc easingFunc_; + Animator* animator_; +}; + +class UITestAnimator : public UITest, public UIView::OnClickListener, public SysInfo::OnFPSChangedListener { +public: + UITestAnimator() {} + ~UITestAnimator(){}; + void SetUp() override; + void TearDown() override; + UIView* GetTestView() override; + + void SetUpButton(UILabelButton* btn, const char* title, int16_t x, int16_t y); + void SetUpLabel(const char* title, int16_t x, int16_t y); + + bool OnClick(UIView& view, const ClickEvent& event) override; + void OnFPSChanged(float newFPS) override; + + void UIKit_Animator_Test_BackEasing_001(); + void UIKit_Animator_Test_CircEasing_002(); + void UIKit_Animator_Test_CubicEasing_003(); + void UIKit_Animator_Test_SineEasing_004(); + void UIKit_Animator_Test_QuadEasing_005(); + void UIKit_Animator_Test_QuintEasing_006(); + void UIKit_Animator_Test_LinearEasing_007(); + void UIKit_Animator_Test_FPS_008(); + +private: + UIScrollView* container_ = nullptr; + UIScrollView* scroll_ = nullptr; + UIImageView* image_ = nullptr; + ImageEaseAnimatorCallback* callback_ = nullptr; + Animator* animator_ = nullptr; + UIViewGroup* uiViewGroupFrame_ = nullptr; + + UILabelButton* backOvershootBtn_ = nullptr; + UILabelButton* backEaseInBtn_ = nullptr; + UILabelButton* backEaseOutBtn_ = nullptr; + UILabelButton* backEaseInOutBtn_ = nullptr; + UILabelButton* circEaseInBtn_ = nullptr; + UILabelButton* circEaseOutBtn_ = nullptr; + UILabelButton* circEaseInOutBtn_ = nullptr; + UILabelButton* cubicEaseInBtn_ = nullptr; + UILabelButton* cubicEaseOutBtn_ = nullptr; + UILabelButton* cubicEaseInOutBtn_ = nullptr; + UILabelButton* linearEaseNoneBtn_ = nullptr; + UILabelButton* quadEaseInBtn_ = nullptr; + UILabelButton* quadEaseOutBtn_ = nullptr; + UILabelButton* quadEaseInOutBtn_ = nullptr; + UILabelButton* quintEaseInBtn_ = nullptr; + UILabelButton* quintEaseOutBtn_ = nullptr; + UILabelButton* quintEaseInOutBtn_ = nullptr; + UILabelButton* sineEaseInBtn_ = nullptr; + UILabelButton* sineEaseOutBtn_ = nullptr; + UILabelButton* sineEaseInOutBtn_ = nullptr; + UILabel* fpsLabel_ = nullptr; +}; +} // namespace OHOS +#endif // UI_TEST_ANIMATOR_H diff --git a/test/uitest/test_anti_aliasing/ui_test_anti_aliasing.cpp b/test/uitest/test_anti_aliasing/ui_test_anti_aliasing.cpp new file mode 100755 index 0000000..406edd8 --- /dev/null +++ b/test/uitest/test_anti_aliasing/ui_test_anti_aliasing.cpp @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ui_test_anti_aliasing.h" +#include "common/screen.h" +#include "components/ui_canvas.h" +#include "components/ui_circle_progress.h" +#include "components/ui_label.h" +#include "components/ui_view_group.h" + +namespace OHOS { +void UITestAntiAliasing::SetUp() +{ + if (container_ == nullptr) { + container_ = new UIScrollView(); + container_->Resize(Screen::GetInstance().GetWidth(), Screen::GetInstance().GetHeight() - BACK_BUTTON_HEIGHT); + container_->SetHorizontalScrollState(false); + } +} + +void UITestAntiAliasing::TearDown() +{ + DeleteChildren(container_); + container_ = nullptr; +} + +UIView* UITestAntiAliasing::GetTestView() +{ + UIKit_Anti_Aliasing_Test_Rect_001(); + UIKit_Anti_Aliasing_Test_Circle_001(); + UIKit_Anti_Aliasing_Test_Line_001(); + return container_; +} + +UIViewGroup* UITestAntiAliasing::CreateTestCaseGroup() const +{ + UIViewGroup* group = new UIViewGroup(); + group->Resize(Screen::GetInstance().GetWidth(), 200); // 200: group height + return group; +} + +UILabel* UITestAntiAliasing::CreateTitleLabel() const +{ + UILabel* label = new UILabel(); + // 2: half of screen width + label->SetPosition(TEXT_DISTANCE_TO_LEFT_SIDE, 0, Screen::GetInstance().GetWidth() / 2, TITLE_LABEL_DEFAULT_HEIGHT); + label->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + return label; +} + +void UITestAntiAliasing::UIKit_Anti_Aliasing_Test_Rect_001() +{ + if (container_ == nullptr) { + return; + } + + UIViewGroup* group = CreateTestCaseGroup(); + // 2: half of screen width, 110: group height + group->Resize(Screen::GetInstance().GetWidth() / 2, 110); + group->SetViewId("UIKit_Draw_Rect_Test_Rect_001"); + group->SetPosition(0, 0); + + UILabel* label = CreateTitleLabel(); + label->SetText("矩形抗锯齿效果:"); + label->SetPosition(TEXT_DISTANCE_TO_LEFT_SIDE, TEXT_DISTANCE_TO_TOP_SIDE); + group->Add(label); + + UIView* view = new UIView(); + Style style = view->GetStyleConst(); + style.bgColor_ = Color::Green(); + style.bgOpa_ = OPA_OPAQUE; + style.borderRadius_ = 5; // 5:border radius + style.borderColor_ = Color::Gray(); + style.borderOpa_ = OPA_OPAQUE; + style.borderWidth_ = 3; // 3:border width + view->SetStyle(style); + view->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, VIEW_DISTANCE_TO_TOP_SIDE); + view->Resize(120, 50); // 120:new width, 50:new width + group->Add(view); + + container_->Add(group); +} + +void UITestAntiAliasing::UIKit_Anti_Aliasing_Test_Circle_001() +{ + if (container_ == nullptr) { + return; + } + + UIViewGroup* group = CreateTestCaseGroup(); + // 336: x-coordinate + group->SetPosition(336, 0); + // 2: half of screen width; 180: group height + group->Resize(Screen::GetInstance().GetWidth() / 2, 180); + group->SetViewId("UIKit_Anti_Aliasing_Test_Circle_001"); + + UILabel* label = CreateTitleLabel(); + label->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE2, TEXT_DISTANCE_TO_TOP_SIDE); + label->SetText("弧形/圆形抗锯齿效果:"); + group->Add(label); + + UICircleProgress* circleProgress = new UICircleProgress(); + circleProgress->SetPosition(0, 23, 150, 150); // 23:y-coordinate, 150:new width, 150:new height + circleProgress->SetCenterPosition(75, 75); // 75:x-coordinate, 75:y-coordinate + circleProgress->SetRadius(50); // 50:outer radius + circleProgress->SetValue(20); // 20:current value of progress bar + circleProgress->SetLineColor(Color::White()); + group->Add(circleProgress); + + UICircleProgress* circleProgress1 = new UICircleProgress(); + circleProgress1->SetPosition(130, 23, 150, 150); // 130:x-coordinate, 23:y-coordinate, 150:new width, 150:new height + circleProgress1->SetCenterPosition(75, 75); // 75:x-coordinate, 75:y-coordinate + circleProgress1->SetRadius(50); // 50:outer radius + circleProgress1->SetValue(100); // 100:current value of progress bar + circleProgress1->SetLineColor(Color::White()); + group->Add(circleProgress1); + + container_->Add(group); +} + +void UITestAntiAliasing::UIKit_Anti_Aliasing_Test_Line_001() +{ + if (container_ == nullptr) { + return; + } + + UIViewGroup* group = CreateTestCaseGroup(); + // 648: x-coordinate + group->SetPosition(648, 0); + group->Resize(Screen::GetInstance().GetWidth() / 2, 250); // 2: half of screen width; // 250: y-coordinate + group->SetViewId("UIKit_Anti_Aliasing_Test_Line_001"); + + UILabel* label = CreateTitleLabel(); + label->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE2, TEXT_DISTANCE_TO_TOP_SIDE); + label->SetText("直线抗锯齿效果:"); + group->Add(label); + + UICanvas* canvas = new UICanvas(); + canvas->SetHeight(200); // 200:height + canvas->SetWidth(200); // 200:width + canvas->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE2, VIEW_DISTANCE_TO_TOP_SIDE); + canvas->SetStyle(STYLE_BACKGROUND_COLOR, Color::Gray().full); + Paint paint; + // 0:start point x-coordinate, 50:start point y-coordinate, 150:end point x-coordinate, 150:end point y-coordinate + canvas->DrawLine({0, 50}, {150, 50}, paint); + // 0:start point x-coordinate, 50:start point y-coordinate, 150:end point x-coordinate, 150:end point y-coordinate + canvas->DrawLine({0, 50}, {150, 100}, paint); + // 0:start point x-coordinate, 50:start point y-coordinate, 150:end point x-coordinate, 150:end point y-coordinate + canvas->DrawLine({0, 50}, {150, 200}, paint); + // 0:start point x-coordinate, 50:start point y-coordinate, 150:end point x-coordinate, 150:end point y-coordinate + canvas->DrawLine({0, 50}, {100, 200}, paint); + group->Add(canvas); + container_->Add(group); +} +} // namespace OHOS \ No newline at end of file diff --git a/test/uitest/test_anti_aliasing/ui_test_anti_aliasing.h b/test/uitest/test_anti_aliasing/ui_test_anti_aliasing.h new file mode 100755 index 0000000..94d3cb3 --- /dev/null +++ b/test/uitest/test_anti_aliasing/ui_test_anti_aliasing.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef UI_TEST_ANTI_ALIASING_H +#define UI_TEST_ANTI_ALIASING_H + +#include "components/ui_label.h" +#include "components/ui_scroll_view.h" +#include "ui_test.h" + +namespace OHOS { +class UITestAntiAliasing : public UITest { +public: + UITestAntiAliasing() {} + ~UITestAntiAliasing() {} + void SetUp() override; + void TearDown() override; + UIView* GetTestView() override; + + /** + * @brief Test if rect anti-aliasing + */ + void UIKit_Anti_Aliasing_Test_Rect_001(); + + /** + * @brief Test if circle anti-aliasing + */ + void UIKit_Anti_Aliasing_Test_Circle_001(); + + /** + * @brief Test if line anti-aliasing + */ + void UIKit_Anti_Aliasing_Test_Line_001(); + +private: + UIViewGroup* CreateTestCaseGroup() const; + UILabel* CreateTitleLabel() const; + UIScrollView* container_ = nullptr; +}; +} // namespace OHOS +#endif // UI_TEST_ANTI_ALIASING_H diff --git a/test/uitest/test_arc_label/ui_test_arc_label.cpp b/test/uitest/test_arc_label/ui_test_arc_label.cpp new file mode 100755 index 0000000..ca03ca8 --- /dev/null +++ b/test/uitest/test_arc_label/ui_test_arc_label.cpp @@ -0,0 +1,200 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ui_test_arc_label.h" +#include "common/screen.h" +#include "font/ui_font.h" + +namespace OHOS { +namespace { +const int16_t GAP = 5; +const int16_t TITLE_HEIGHT = 29; +const uint16_t CENTER_X = 185; +const uint16_t CENTER_Y = 155; +const uint16_t RADIUS = 150; +} // namespace + +void UITestArcLabel::SetUp() +{ + if (container_ == nullptr) { + container_ = new UIScrollView(); + container_->SetThrowDrag(true); + container_->SetHorizontalScrollState(false); + container_->Resize(Screen::GetInstance().GetWidth(), Screen::GetInstance().GetHeight() - BACK_BUTTON_HEIGHT); + positionX_ = 0; + positionY_ = 0; + } +} + +void UITestArcLabel::TearDown() +{ + DeleteChildren(container_); + container_ = nullptr; +} + +void UITestArcLabel::InnerTestTitle(const char* title, int16_t x, int16_t y) const +{ + UILabel* titleLabel = new UILabel(); + titleLabel->SetPosition(x, y, Screen::GetInstance().GetWidth(), TITLE_HEIGHT); + titleLabel->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + titleLabel->SetText(title); + container_->Add(titleLabel); +} + +UIView* UITestArcLabel::GetTestView() +{ + UIKit_UIArcLabel_Test_Display_001(); + UIKit_UIArcLabel_Test_Display_002(); + UIKit_UIArcLabel_Test_Display_003(); + UIKit_UIArcLabel_Test_Display_004(); + + UIKit_UIArcLabel_Test_Align_001(); + UIKit_UIArcLabel_Test_Align_002(); + UIKit_UIArcLabel_Test_Align_003(); + return container_; +} + +void UITestArcLabel::UIKit_UIArcLabel_Test_Display_001() +{ + const char* title = "显示内部角度从0到270的弧形文本 "; + // 0: start angle 270: end angle + TestArcLabelDisplay(title, 0, 270, UIArcLabel::TextOrientation::INSIDE, VIEW_DISTANCE_TO_LEFT_SIDE, + VIEW_DISTANCE_TO_TOP_SIDE); +} + +void UITestArcLabel::UIKit_UIArcLabel_Test_Display_002() +{ + const char* title = "显示内部角度从0到-270的弧形文本 "; + // 0: start angle -270: end angle 100: y-coordinate + TestArcLabelDisplay(title, 0, -270, UIArcLabel::TextOrientation::INSIDE, VIEW_DISTANCE_TO_LEFT_SIDE, 100); +} + +void UITestArcLabel::UIKit_UIArcLabel_Test_Display_003() +{ + const char* title = "显示外部角度从0到270的弧形文本 "; + // 0: start angle 270: end angle 200: y-coordinate + TestArcLabelDisplay(title, 0, 270, UIArcLabel::TextOrientation::OUTSIDE, VIEW_DISTANCE_TO_LEFT_SIDE, 200); +} + +void UITestArcLabel::UIKit_UIArcLabel_Test_Display_004() +{ + const char* title = "显示外部角度从0到-270的弧形文本 "; + // 0: start angle -270: end angle 300: y-coordinate + TestArcLabelDisplay(title, 0, -270, UIArcLabel::TextOrientation::OUTSIDE, VIEW_DISTANCE_TO_LEFT_SIDE, 300); +} + +void UITestArcLabel::TestArcLabelDisplay(const char* title, + const int16_t startAngle, + const int16_t endAngle, + const UIArcLabel::TextOrientation orientation, + int16_t x, + int16_t y) +{ + if (container_ != nullptr) { + InnerTestTitle(title, x, positionY_); + UIArcLabel* label = new UIArcLabel(); + label->SetArcTextCenter(CENTER_X, CENTER_Y + positionY_ + GAP * 4); // 4: 4 times GAP + label->SetArcTextRadius(RADIUS); + label->SetArcTextAngle(startAngle, endAngle); + label->SetArcTextOrientation(orientation); + label->SetFont(DEFAULT_VECTOR_FONT_FILENAME, 30); // 30: font size + label->SetText("012345678ABCDEF0123456789ABCDE"); + label->SetStyle(STYLE_LETTER_SPACE, 10); // 10: space + container_->Add(label); + positionY_ += (RADIUS * 2) + GAP + TITLE_HEIGHT; // 2: diameter + } +} + +void UITestArcLabel::UIKit_UIArcLabel_Test_Align_001() +{ + if (container_ != nullptr) { + // 2: half of screen width + positionX_ = Screen::GetInstance().GetWidth() / 2 + VIEW_DISTANCE_TO_LEFT_SIDE; + positionY_ = 0; + InnerTestTitle("弧形文本在0到90度之间左对齐 ", positionX_, positionY_); + positionY_ += TITLE_HEIGHT + GAP; + UIView* back = new UIView(); + back->SetPosition(positionX_, positionY_ + GAP); + back->Resize(RADIUS, RADIUS); + back->SetStyle(STYLE_BACKGROUND_COLOR, Color::Gray().full); + back->SetStyle(STYLE_BACKGROUND_OPA, OPA_OPAQUE); + + UIArcLabel* label = new UIArcLabel(); + label->SetArcTextCenter(positionX_, CENTER_Y + positionY_); + label->SetArcTextRadius(RADIUS); + label->SetArcTextAngle(0, 90); // 0: start angle, 90: end angle + label->SetFont(DEFAULT_VECTOR_FONT_FILENAME, 30); // 30: font size + label->SetText("LEFT"); + label->SetAlign(UITextLanguageAlignment::TEXT_ALIGNMENT_LEFT); + label->SetStyle(STYLE_LETTER_SPACE, 6); // 6: space + + container_->Add(back); + container_->Add(label); + positionY_ += RADIUS + GAP + TITLE_HEIGHT; + } +} + +void UITestArcLabel::UIKit_UIArcLabel_Test_Align_002() +{ + if (container_ != nullptr) { + InnerTestTitle("弧形文本在0到90度之间居中对齐 ", positionX_, positionY_); + positionY_ += TITLE_HEIGHT + GAP; + UIView* back = new UIView(); + back->SetPosition(positionX_, positionY_ + GAP); + back->Resize(RADIUS, RADIUS); + back->SetStyle(STYLE_BACKGROUND_COLOR, Color::Gray().full); + back->SetStyle(STYLE_BACKGROUND_OPA, OPA_OPAQUE); + + UIArcLabel* label = new UIArcLabel(); + label->SetArcTextCenter(positionX_, CENTER_Y + positionY_); + label->SetArcTextRadius(RADIUS); + label->SetArcTextAngle(0, 90); // 0: start angle, 90: end angle + label->SetFont(DEFAULT_VECTOR_FONT_FILENAME, 30); // 30: font size + label->SetText("CENTER"); + label->SetAlign(UITextLanguageAlignment::TEXT_ALIGNMENT_CENTER); + label->SetStyle(STYLE_LETTER_SPACE, 6); // 6: space + + container_->Add(back); + container_->Add(label); + positionY_ += RADIUS + GAP + TITLE_HEIGHT; + } +} + +void UITestArcLabel::UIKit_UIArcLabel_Test_Align_003() +{ + if (container_ != nullptr) { + InnerTestTitle("弧形文本在0到90度之间右对齐 ", positionX_, positionY_); + positionY_ += TITLE_HEIGHT + GAP; + UIView* back = new UIView(); + back->SetPosition(positionX_, positionY_ + GAP); + back->Resize(RADIUS, RADIUS); + back->SetStyle(STYLE_BACKGROUND_COLOR, Color::Gray().full); + back->SetStyle(STYLE_BACKGROUND_OPA, OPA_OPAQUE); + + UIArcLabel* label = new UIArcLabel(); + label->SetArcTextCenter(positionX_, CENTER_Y + positionY_); + label->SetArcTextRadius(RADIUS); + label->SetArcTextAngle(0, 90); // 0: start angle, 90: end angle + label->SetFont(DEFAULT_VECTOR_FONT_FILENAME, 30); // 30: font size + label->SetText("RIGHT"); + label->SetAlign(UITextLanguageAlignment::TEXT_ALIGNMENT_RIGHT); + label->SetStyle(STYLE_LETTER_SPACE, 6); // 6: space + + container_->Add(back); + container_->Add(label); + positionY_ += RADIUS + GAP + TITLE_HEIGHT; + } +} +} // namespace OHOS \ No newline at end of file diff --git a/test/uitest/test_arc_label/ui_test_arc_label.h b/test/uitest/test_arc_label/ui_test_arc_label.h new file mode 100755 index 0000000..a03b441 --- /dev/null +++ b/test/uitest/test_arc_label/ui_test_arc_label.h @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef UI_TEST_ARC_LABEL_H +#define UI_TEST_ARC_LABEL_H + +#include "components/ui_arc_label.h" +#include "components/ui_label.h" +#include "components/ui_scroll_view.h" +#include "ui_test.h" + +namespace OHOS { +class UITestArcLabel : public UITest { +public: + UITestArcLabel() {} + ~UITestArcLabel() {} + void SetUp() override; + void TearDown() override; + UIView* GetTestView() override; + + /** + * @brief Test display arc text from 0 to 270 INSIDE + */ + void UIKit_UIArcLabel_Test_Display_001(); + + /** + * @brief Test display arc text from 270 to 0 INSIDE + */ + void UIKit_UIArcLabel_Test_Display_002(); + + /** + * @brief Test display arc text from 0 to 270 OUTSIDE + */ + void UIKit_UIArcLabel_Test_Display_003(); + + /** + * @brief Test display arc text from 270 to 0 OUTSIDE + */ + void UIKit_UIArcLabel_Test_Display_004(); + + /** + * @brief Test arc text align LEFT + */ + void UIKit_UIArcLabel_Test_Align_001(); + + /** + * @brief Test arc text align CENTER + */ + void UIKit_UIArcLabel_Test_Align_002(); + + /** + * @brief Test arc text align RIGHT + */ + void UIKit_UIArcLabel_Test_Align_003(); + +private: + UIScrollView* container_ = nullptr; + + void InnerTestTitle(const char* title, int16_t x, int16_t y) const; + + // for direction and orientation + void TestArcLabelDisplay(const char* title, const int16_t startAngle, const int16_t endAngle, + const UIArcLabel::TextOrientation orientation, int16_t x, int16_t y); +}; +} // namespace OHOS +#endif // UI_TEST_ARC_LABEL_H diff --git a/test/uitest/test_button/ui_test_button.cpp b/test/uitest/test_button/ui_test_button.cpp new file mode 100755 index 0000000..d7c31f5 --- /dev/null +++ b/test/uitest/test_button/ui_test_button.cpp @@ -0,0 +1,671 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ui_test_button.h" +#include "common/screen.h" +#include "components/root_view.h" +#include "components/ui_checkbox.h" +#include "components/ui_digital_clock.h" +#include "components/ui_label.h" +#include "components/ui_label_button.h" +#include "components/ui_radio_button.h" +#include "components/ui_toggle_button.h" +#include "font/ui_font.h" +#include "test_resource_config.h" + +namespace OHOS { +void UITestBUTTON::SetUp() +{ + if (container_ == nullptr) { + container_ = new UIScrollView(); + container_->Resize(Screen::GetInstance().GetWidth(), Screen::GetInstance().GetHeight() - BACK_BUTTON_HEIGHT); + container_->SetHorizontalScrollState(false); + } +} + +void UITestBUTTON::TearDown() +{ + DeleteChildren(container_); + container_ = nullptr; +} + +UIView* UITestBUTTON::GetTestView() +{ + UIKit_Check_Box_Test_001(); + UIKit_Radio_Button_Test_001(); + UIKit_Toggle_Button_Test_001(); + UIKit_Check_Box_Test_002(); + UIKit_Radio_Button_Test_002(); + UIKit_Toggle_Button_Test_002(); + UIKit_Button_Test_001(); + return container_; +} + +void UITestBUTTON::UIKit_Check_Box_Test_001() const +{ + if (container_ != nullptr) { + UILabel* label = new UILabel(); + container_->Add(label); + label->SetPosition(TEXT_DISTANCE_TO_LEFT_SIDE, 5, // 5: y-coordinate + Screen::GetInstance().GetWidth(), TITLE_LABEL_DEFAULT_HEIGHT); + label->SetText("checkbox功能"); + label->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + + UICheckBox* checkbox = new UICheckBox(); + checkbox->SetPosition(30, 30); // 30: x-coordinate, 30: y-coordinate + + UICheckBox* checkbox2 = new UICheckBox(); + checkbox2->SetPosition(100, 30); // 100: x-coordinate, 30: y-coordinate + + UICheckBox* checkbox3 = new UICheckBox(); + checkbox3->SetPosition(170, 30); // 170: x-coordinate, 30: y-coordinate + + container_->Add(checkbox); + container_->Add(checkbox2); + container_->Add(checkbox3); + } +} + +void UITestBUTTON::UIKit_Radio_Button_Test_001() const +{ + if (container_ != nullptr) { + UILabel* label = new UILabel(); + container_->Add(label); + // 0:x-coordinate, 130:y-coordinate, 20:new height + label->SetPosition(TEXT_DISTANCE_TO_LEFT_SIDE, 130, Screen::GetInstance().GetWidth(), + TITLE_LABEL_DEFAULT_HEIGHT); + label->SetText("radiobutton功能"); + label->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + + UIRadioButton* radioButton = new UIRadioButton("aaa"); + radioButton->SetPosition(0, 10); // 0: x-coordinate, 10: y-coordinate + + UIRadioButton* radioButton2 = new UIRadioButton("aaa"); + radioButton2->SetPosition(50, 10); // 50: x-coordinate, 10: y-coordinate + radioButton2->SetWidth(40); // 40: width + radioButton2->SetHeight(30); // 30: height + + UIRadioButton* radioButton3 = new UIRadioButton("aaa"); + radioButton3->SetPosition(110, 10); // 110: x-coordinate, 10: y-coordinate + radioButton3->SetWidth(80); // 80: width + radioButton3->SetHeight(60); // 60: height + + UIRadioButton* radioButton4 = new UIRadioButton("aaa"); + radioButton4->SetPosition(10, 10); // 10: x-coordinate, 10: y-coordinate + radioButton4->SetWidth(70); // 70: width + radioButton4->SetHeight(90); // 90: width + + UIRadioButton* radioButton5 = new UIRadioButton("aaa"); + radioButton5->SetPosition(110, 10); // 110: x-coordinate, 10: y-coordinate + radioButton5->SetWidth(-1); // -1: width + radioButton5->SetHeight(90); // 90: width + + UIRadioButton* radioButton6 = new UIRadioButton("aaa"); + radioButton6->SetPosition(130, 10); // 130: x-coordinate, 10: y-coordinate + radioButton6->SetWidth(0); // 0: width + radioButton6->SetHeight(0); // 0: width + + UIRadioButton* radioButton7 = new UIRadioButton("aaa"); + radioButton7->SetPosition(140, 10); // 140: x-coordinate, 10: y-coordinate + radioButton7->SetWidth(1000); // 1000: width + radioButton7->SetHeight(50); // 50: width + + OHOS::UIViewGroup* viewGroup = new UIViewGroup(); + viewGroup->SetPosition(30, 150); // 30: x-coordinate, 150: y-coordinate + viewGroup->SetWidth(Screen::GetInstance().GetWidth() / 2); // 2: half width + viewGroup->SetHeight(Screen::GetInstance().GetHeight() / 2); // 2: half height + + OHOS::UIViewGroup* viewGroup2 = new UIViewGroup(); + // 40, increase width, 2: half width, 150: y-coordinate + viewGroup2->SetPosition(40 + Screen::GetInstance().GetWidth() / 2, 150); + viewGroup2->SetWidth(Screen::GetInstance().GetWidth() / 2); // 2: half width + viewGroup2->SetHeight(Screen::GetInstance().GetHeight() / 2); // 2: half height + + viewGroup2->Add(radioButton4); + viewGroup2->Add(radioButton5); + viewGroup2->Add(radioButton6); + viewGroup2->Add(radioButton7); + + viewGroup->Add(radioButton); + viewGroup->Add(radioButton2); + viewGroup->Add(radioButton3); + viewGroup->SetStyle(STYLE_BACKGROUND_OPA, 0); + + container_->Add(viewGroup); + container_->Add(viewGroup2); + } +} + +class TestBtnOnStateChangeListener : public OHOS::UICheckBox::OnChangeListener { +public: + explicit TestBtnOnStateChangeListener(UIView* uiView) + { + uiView_ = uiView; + } + + ~TestBtnOnStateChangeListener() {} + + bool OnChange(UICheckBox::UICheckBoxState state) override + { + int16_t xPos = uiView_->GetX(); + int16_t yPos = uiView_->GetY(); + uiView_->Invalidate(); + xPos += 10; // 10: increase xPos + yPos += 10; // 10: increase yPos + + uiView_->SetX(xPos); + uiView_->SetY(yPos); + uiView_->Invalidate(); + return true; + } + +private: + UIView* uiView_; +}; + +void UITestBUTTON::UIKit_Toggle_Button_Test_001() const +{ + if (container_ != nullptr) { + UILabel* label = new UILabel(); + container_->Add(label); + // 0:x-coordinate, 270:y-coordinate, 20:new height + label->SetPosition(TEXT_DISTANCE_TO_LEFT_SIDE, 270, Screen::GetInstance().GetWidth(), + TITLE_LABEL_DEFAULT_HEIGHT); + label->SetText("togglebutton功能"); + label->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + + UIToggleButton* togglebutton = new UIToggleButton(); + togglebutton->SetPosition(20, 300); // 20: x-coordinate, 300: y-coordinate + togglebutton->SetStyle(STYLE_BACKGROUND_OPA, 0); // 0: opacity + togglebutton->SetState(false); + + UIToggleButton* togglebutton2 = new UIToggleButton(); + togglebutton2->SetPosition(100, 300); // 100: x-coordinate, 300: y-coordinate + togglebutton2->SetStyle(STYLE_BACKGROUND_OPA, 0); // 0: opacity + togglebutton2->SetState(false); + togglebutton2->SetWidth(60); // 60: width + togglebutton2->SetHeight(40); // 40: height + + UIToggleButton* togglebutton3 = new UIToggleButton(); + togglebutton3->SetPosition(190, 300); // 190: x-coordinate, 300: y-coordinate + togglebutton3->SetStyle(STYLE_BACKGROUND_OPA, 0); // 0: opacity + togglebutton3->SetState(false); + togglebutton3->SetWidth(50); // 50: width + togglebutton3->SetHeight(70); // 70: height + + UIToggleButton* togglebutton4 = new UIToggleButton(); + + UICheckBox::OnChangeListener* ChangeListener = + new TestBtnOnStateChangeListener(reinterpret_cast(togglebutton4)); + togglebutton4->SetOnChangeListener(ChangeListener); + + togglebutton4->SetPosition(250, 300); // 250: x-coordinate, 300: y-coordinate + togglebutton4->SetStyle(STYLE_BACKGROUND_OPA, 0); // 0: opacity + togglebutton4->SetState(false); + togglebutton4->SetWidth(80); // 80: width + togglebutton4->SetHeight(80); // 80: height + togglebutton4->SetState(true); + + UIToggleButton* togglebutton5 = new UIToggleButton(); + togglebutton5->SetPosition(340, 300); // 340: x-coordinate, 300: y-coordinate + togglebutton5->SetWidth(-1); // -1: width + togglebutton5->SetHeight(90); // 90: width + + UIToggleButton* togglebutton6 = new UIToggleButton(); + togglebutton6->SetPosition(350, 300); // 350: x-coordinate, 300: y-coordinate + togglebutton6->SetWidth(0); // 0: width + togglebutton6->SetHeight(0); // 0: width + + UIToggleButton* togglebutton7 = new UIToggleButton(); + togglebutton7->SetPosition(360, 300); // 360: x-coordinate, 300: y-coordinate + togglebutton7->SetWidth(1000); // 1000: width + togglebutton7->SetHeight(50); // 50: width + + container_->Add(togglebutton); + container_->Add(togglebutton2); + container_->Add(togglebutton3); + container_->Add(togglebutton4); + container_->Add(togglebutton5); + container_->Add(togglebutton6); + container_->Add(togglebutton7); + } +} +void UITestBUTTON::UIKit_Check_Box_Test_002() const +{ + if (container_ != nullptr) { + UILabel* label = new UILabel(); + container_->Add(label); + // 0:x-coordinate, 400:y-coordinate, 20:new height + label->SetPosition(TEXT_DISTANCE_TO_LEFT_SIDE, 400, Screen::GetInstance().GetWidth(), + TITLE_LABEL_DEFAULT_HEIGHT); + label->SetText("checkbox SetImage功能"); + label->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + + UICheckBox* checkbox = new UICheckBox(); + checkbox->SetPosition(30, 430); // 30: x-coordinate, 430: y-coordinate + checkbox->SetWidth(130); // 130: width + checkbox->SetHeight(130); // 130: height + checkbox->SetImages(BLUE_IMAGE_PATH, YELLOW_IMAGE_PATH); + + UICheckBox* checkbox2 = new UICheckBox(); + checkbox2->SetPosition(200, 430); // 200: x-coordinate, 430: y-coordinate + checkbox2->SetWidth(130); // 130: width + checkbox2->SetHeight(130); // 130: height + checkbox2->SetImages(GREEN_IMAGE_PATH, RED_IMAGE_PATH); + + container_->Add(checkbox); + container_->Add(checkbox2); + } +} + +void UITestBUTTON::UIKit_Radio_Button_Test_002() const +{ + if (container_ != nullptr) { + UILabel* label = new UILabel(); + container_->Add(label); + // 0:x-coordinate, 570:y-coordinate, 20:new height + label->SetPosition(TEXT_DISTANCE_TO_LEFT_SIDE, 570, Screen::GetInstance().GetWidth(), + TITLE_LABEL_DEFAULT_HEIGHT); + label->SetText("radiobutton SetImage功能"); + label->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + + UIRadioButton* radioButton = new UIRadioButton("aaa"); + radioButton->SetPosition(30, 10); // 30: x-coordinate, 10: y-coordinate + radioButton->SetWidth(130); // 130: width + radioButton->SetHeight(130); // 130: height + radioButton->SetImages(BLUE_IMAGE_PATH, YELLOW_IMAGE_PATH); + + UIRadioButton* radioButton2 = new UIRadioButton("aaa"); + radioButton2->SetPosition(200, 10); // 200: x-coordinate, 10: y-coordinate + radioButton2->SetWidth(130); // 130: width + radioButton2->SetHeight(130); // 130: height + radioButton2->SetImages(GREEN_IMAGE_PATH, RED_IMAGE_PATH); + + OHOS::UIViewGroup* viewGroup = new UIViewGroup(); + viewGroup->SetPosition(0, 600); // 0: x-coordinate, 600: y-coordinate + viewGroup->SetWidth(Screen::GetInstance().GetWidth()); // 2: half width + viewGroup->SetHeight(Screen::GetInstance().GetHeight() / 2); // 2: half height + + viewGroup->Add(radioButton); + viewGroup->Add(radioButton2); + viewGroup->SetStyle(STYLE_BACKGROUND_OPA, 0); + + container_->Add(viewGroup); + } +} + +void UITestBUTTON::UIKit_Toggle_Button_Test_002() const +{ + if (container_ != nullptr) { + UILabel* label = new UILabel(); + container_->Add(label); + // 0:x-coordinate, 740:y-coordinate, 20:new height + label->SetPosition(TEXT_DISTANCE_TO_LEFT_SIDE, 740, Screen::GetInstance().GetWidth(), + TITLE_LABEL_DEFAULT_HEIGHT); + label->SetText("togglebutton SetImage功能"); + label->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + + UIToggleButton* togglebutton = new UIToggleButton(); + togglebutton->SetPosition(30, 770); // 30: x-coordinate, 770: y-coordinate + togglebutton->SetStyle(STYLE_BACKGROUND_OPA, 0); // 0: opacity + togglebutton->SetState(true); + togglebutton->SetWidth(130); // 130: width + togglebutton->SetHeight(130); // 130: height + togglebutton->SetImages(BLUE_IMAGE_PATH, YELLOW_IMAGE_PATH); + + UIToggleButton* togglebutton2 = new UIToggleButton(); + UICheckBox::OnChangeListener* ChangeListener = + new TestBtnOnStateChangeListener(reinterpret_cast(togglebutton2)); + togglebutton2->SetOnChangeListener(ChangeListener); + togglebutton2->SetPosition(200, 770); // 200: x-coordinate, 770: y-coordinate + togglebutton2->SetStyle(STYLE_BACKGROUND_OPA, 0); // 0: opacity + togglebutton2->SetState(false); + togglebutton2->SetWidth(130); // 130: width + togglebutton2->SetHeight(130); // 130: height + togglebutton2->SetImages(GREEN_IMAGE_PATH, RED_IMAGE_PATH); + + container_->Add(togglebutton); + container_->Add(togglebutton2); + } +} + +class TestBtnOnClickShapeChangeListener : public UIView::OnClickListener { +public: + TestBtnOnClickShapeChangeListener(UIView* uiView, int16_t grid) : uiView_(uiView), changeGrid_(grid) {} + + ~TestBtnOnClickShapeChangeListener() {} + + bool OnClick(UIView& view, const ClickEvent& event) override + { + int16_t width = uiView_->GetWidth() + changeGrid_; + int16_t height = uiView_->GetHeight() + changeGrid_; + if (changeGrid_ < 0) { + uiView_->Invalidate(); + } + uiView_->Resize(width, height); + uiView_->Invalidate(); + return true; + } + +private: + UIView* uiView_; + int16_t changeGrid_; +}; + +class TestBtnOnClickPositionChangeListener : public OHOS::UIView::OnClickListener { +public: + enum class MoveType { + MOVE_LEFT, + MOVE_RIGHT, + MOVE_TOP, + MOVE_BOTTOM, + }; + TestBtnOnClickPositionChangeListener(UIView* uiView, MoveType moveType, uint16_t grid) + : uiView_(uiView), moveType_(moveType), grid_(grid) + { + } + + ~TestBtnOnClickPositionChangeListener() {} + + bool OnClick(UIView& view, const ClickEvent& event) override + { + int16_t xPos = uiView_->GetX(); + int16_t yPos = uiView_->GetY(); + uiView_->Invalidate(); + if (moveType_ == MoveType::MOVE_LEFT) { + xPos = xPos - grid_; + } else if (moveType_ == MoveType::MOVE_RIGHT) { + xPos = xPos + grid_; + } else if (moveType_ == MoveType::MOVE_TOP) { + yPos = yPos - grid_; + } else if (moveType_ == MoveType::MOVE_BOTTOM) { + yPos = yPos + grid_; + } + uiView_->SetX(xPos); + uiView_->SetY(yPos); + uiView_->Invalidate(); + return true; + } + +private: + UIView* uiView_; + MoveType moveType_; + uint16_t grid_; +}; + +class TestBtnOnClickVisableListener : public OHOS::UIView::OnClickListener { +public: + TestBtnOnClickVisableListener(UIView* uiView, bool visible) : uiView_(uiView), visible_(visible) {} + + ~TestBtnOnClickVisableListener() {} + + bool OnClick(UIView& view, const ClickEvent& event) override + { + uiView_->SetVisible(visible_); + uiView_->Invalidate(); + return true; + } + +private: + UIView* uiView_; + bool visible_; +}; + +class TestBtnOnClickTouchableListener : public UIView::OnClickListener { +public: + TestBtnOnClickTouchableListener(UIView* uiView, bool touchable) : uiView_(uiView), touchable_(touchable) {} + + ~TestBtnOnClickTouchableListener() {} + + bool OnClick(UIView& view, const ClickEvent& event) override + { + uiView_->SetTouchable(touchable_); + uiView_->Invalidate(); + return true; + } + +private: + UIView* uiView_; + bool touchable_; +}; + +class TestBtnOnClickChangeColorListener : public OHOS::UIView::OnClickListener { +public: + TestBtnOnClickChangeColorListener(UIView* uiView, uint16_t red, uint16_t green, uint16_t blue) + : uiView_(uiView), red_(red), green_(green), blue_(blue) + { + } + + ~TestBtnOnClickChangeColorListener() {} + + bool OnClick(UIView& view, const ClickEvent& event) override + { + ColorType color; + color.full = uiView_->GetStyle(STYLE_BACKGROUND_COLOR); + color.red = red_; + color.green = green_; + color.blue = blue_; + uiView_->SetStyle(STYLE_BACKGROUND_COLOR, color.full); + uiView_->Invalidate(); + return true; + } + +private: + UIView* uiView_; + uint16_t red_; + uint16_t green_; + uint16_t blue_; +}; + +class TestBtnOnClickRevertColorListener : public UIView::OnClickListener { +public: + explicit TestBtnOnClickRevertColorListener(UIView* uiView) : uiView_(uiView), originColor_(0) + { + if (uiView != nullptr) { + originColor_ = uiView->GetStyle(STYLE_BACKGROUND_COLOR); + } + } + + ~TestBtnOnClickRevertColorListener() {} + + bool OnClick(UIView& view, const ClickEvent& event) override + { + uiView_->SetStyle(STYLE_BACKGROUND_COLOR, originColor_); + uiView_->Invalidate(); + return true; + } + +private: + UIView* uiView_; + uint32_t originColor_; +}; + +class TestBtnOnClickRevertToOriginStateListener : public UIView::OnClickListener { +public: + explicit TestBtnOnClickRevertToOriginStateListener(UIView* uiView) : uiView_(uiView), originColor_(0), + width_(0), height_(0), visible_(false), + touchable_(false) + { + if (uiView != nullptr) { + originColor_ = uiView->GetStyle(STYLE_BACKGROUND_COLOR); + width_ = uiView->GetWidth(); + height_ = uiView->GetHeight(); + visible_ = uiView->IsVisible(); + touchable_ = uiView->IsTouchable(); + } + } + + ~TestBtnOnClickRevertToOriginStateListener() {} + + bool OnClick(UIView& view, const ClickEvent& event) override + { + uiView_->Invalidate(); + uiView_->SetStyle(STYLE_BACKGROUND_COLOR, originColor_); + uiView_->SetWidth(width_); + uiView_->SetHeight(height_); + uiView_->SetVisible(visible_); + uiView_->SetTouchable(touchable_); + ((UIButton*)uiView_)->Invalidate(); + return true; + } + +private: + UIView* uiView_; + uint32_t originColor_; + uint32_t width_; + uint32_t height_; + bool visible_; + bool touchable_; +}; + +UILabel* GetTestUILabel(const char* titlename) +{ + if (titlename == nullptr) { + return nullptr; + } + + UILabel* label = new UILabel(); + // 900:y-coordinate + label->SetPosition(TEXT_DISTANCE_TO_LEFT_SIDE, 900, Screen::GetInstance().GetWidth(), TITLE_LABEL_DEFAULT_HEIGHT); + label->SetText(titlename); + label->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + return label; +} + +UILabelButton* GetTestUIButton(const char* buttonText, int16_t x, int16_t y, UIButton* button) +{ + if (buttonText == nullptr) { + return nullptr; + } + + UILabelButton* labelButton = new UILabelButton(); + // 150: x-coordinate, 440: y-coordinate + labelButton->SetPosition(x, y); + labelButton->Resize(BUTTON_WIDHT1, BUTTON_HEIGHT1); + labelButton->SetText(buttonText); + labelButton->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + labelButton->SetStyleForState(STYLE_BORDER_RADIUS, BUTTON_STYLE_BORDER_RADIUS_VALUE, UIButton::PRESSED); + labelButton->SetStyleForState(STYLE_BORDER_RADIUS, BUTTON_STYLE_BORDER_RADIUS_VALUE, UIButton::INACTIVE); + labelButton->SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::RELEASED); + labelButton->SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::PRESSED); + labelButton->SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::INACTIVE); + return labelButton; +} + +void UIKit_BUTTON_TEST_002(UIScrollView* container, UIButton* button) +{ + if ((container == nullptr) || (button == nullptr)) { + return; + } + UILabelButton* button8 = GetTestUIButton("隐藏", 430, 940, button); // 430: x-coordinate, 940: y-coordinate + UIView::OnClickListener* clickEnableVisiableListener = new TestBtnOnClickVisableListener((UIView*)button, false); + button8->SetOnClickListener(clickEnableVisiableListener); + + UILabelButton* button9 = GetTestUIButton("显示", 430, 990, button); // 430: x-coordinate, 990: y-coordinate + UIView::OnClickListener* clickDisableVisiableListener = new TestBtnOnClickVisableListener((UIView*)button, true); + button9->SetOnClickListener(clickDisableVisiableListener); + + UILabelButton* button10 = GetTestUIButton("可触摸 ", 520, 940, button); // 520: x-coordinate, 940: y-coordinate + UIView::OnClickListener* clickEnableTouchableListener = new TestBtnOnClickTouchableListener((UIView*)button, true); + button10->SetOnClickListener(clickEnableTouchableListener); + + UILabelButton* button11 = GetTestUIButton("不可触摸", 520, 990, button); // 520: x-coordinate, 990: y-coordinate + UIView::OnClickListener* clickDisableTouchableListener = + new TestBtnOnClickTouchableListener((UIView*)button, false); + button11->SetOnClickListener(clickDisableTouchableListener); + + UILabelButton* button12 = GetTestUIButton("变白", 160, 1040, button); // 160: x-coordinate, 1040: y-coordinate + UIView::OnClickListener* clickColorToWhiteListener = + new TestBtnOnClickChangeColorListener((UIView*)button, 0xFF, 0xFF, 0xFF); + button12->SetOnClickListener(clickColorToWhiteListener); + + UILabelButton* button13 = GetTestUIButton("变红", 160, 1090, button); // 160: x-coordinate, 1090: y-coordinate + UIView::OnClickListener* clickColorToRedListener = + new TestBtnOnClickChangeColorListener((UIView*)button, 0xFF, 0, 0); + button13->SetOnClickListener(clickColorToRedListener); + + UILabelButton* button14 = GetTestUIButton("色彩还原", 250, 1040, button); // 250: x-coordinate, 1040: y-coordinate + UIView::OnClickListener* clickRevetColorListener = new TestBtnOnClickRevertColorListener((UIView*)button); + button14->SetOnClickListener(clickRevetColorListener); + + UILabelButton* button15 = GetTestUIButton("全部还原", 250, 1090, button); // 250: x-coordinate, 1090: y-coordinate + UIView::OnClickListener* clickRevetToOriginListener = + new TestBtnOnClickRevertToOriginStateListener((UIView*)button); + button15->SetOnClickListener(clickRevetToOriginListener); + + container->Add(button8); + container->Add(button9); + container->Add(button10); + container->Add(button11); + container->Add(button12); + container->Add(button13); + container->Add(button14); + container->Add(button15); +} + +void UITestBUTTON::UIKit_Button_Test_001() const +{ + if (container_ != nullptr) { + UILabel* label = GetTestUILabel("普通button功能效果"); + container_->Add(label); + + UIButton* button = new UIButton(); + button->SetPosition(10, 935); // 10: x-coordinate, 935: y-coordinate + button->SetWidth(80); // 80: width + button->SetHeight(60); // 60: height + button->SetImageSrc(BLUE_IMAGE_PATH, YELLOW_IMAGE_PATH); + + UILabelButton* button2 = GetTestUIButton("放大", 160, 940, button); // 160: x-coordinate, 940: y-coordinate + UIView::OnClickListener* clickBigListener = new TestBtnOnClickShapeChangeListener(button, CHANGE_SIZE); + button2->SetOnClickListener(clickBigListener); + + UILabelButton* button3 = GetTestUIButton("缩小", 160, 990, button); // 160: x-coordinate, 990: y-coordinate + UIView::OnClickListener* clickSmallListener = new TestBtnOnClickShapeChangeListener(button, -CHANGE_SIZE); + button3->SetOnClickListener(clickSmallListener); + + UILabelButton* button4 = GetTestUIButton("左移", 250, 940, button); // 250: x-coordinate, 940: y-coordinate + UIView::OnClickListener* clickLeftListener = new TestBtnOnClickPositionChangeListener( + (UIView*)button, TestBtnOnClickPositionChangeListener::MoveType::MOVE_LEFT, CHANGE_SIZE); + button4->SetOnClickListener(clickLeftListener); + + UILabelButton* button5 = GetTestUIButton("右移", 250, 990, button); // 250: x-coordinate, 990: y-coordinate + button5->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + UIView::OnClickListener* clickRightListener = new TestBtnOnClickPositionChangeListener( + (UIView*)button, TestBtnOnClickPositionChangeListener::MoveType::MOVE_RIGHT, CHANGE_SIZE); + button5->SetOnClickListener(clickRightListener); + + UILabelButton* button6 = GetTestUIButton("上移", 340, 940, button); // 340: x-coordinate, 940: y-coordinate + button6->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + UIView::OnClickListener* clickUpListener = new TestBtnOnClickPositionChangeListener( + (UIView*)button, TestBtnOnClickPositionChangeListener::MoveType::MOVE_TOP, CHANGE_SIZE); + button6->SetOnClickListener(clickUpListener); + + UILabelButton* button7 = GetTestUIButton("下移", 340, 990, button); // 340: x-coordinate, 990: y-coordinate + UIView::OnClickListener* clickDownListener = new TestBtnOnClickPositionChangeListener( + (UIView*)button, TestBtnOnClickPositionChangeListener::MoveType::MOVE_BOTTOM, CHANGE_SIZE); + button7->SetOnClickListener(clickDownListener); + + container_->Add(button); + container_->Add(button2); + container_->Add(button3); + container_->Add(button4); + container_->Add(button5); + container_->Add(button6); + container_->Add(button7); + + UIKit_BUTTON_TEST_002(container_, button); + } +} +} // namespace OHOS \ No newline at end of file diff --git a/test/uitest/test_button/ui_test_button.h b/test/uitest/test_button/ui_test_button.h new file mode 100755 index 0000000..b6d1000 --- /dev/null +++ b/test/uitest/test_button/ui_test_button.h @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef UI_TEST_BUTTON_H +#define UI_TEST_BUTTON_H + +#include "components/ui_scroll_view.h" +#include "ui_test.h" + +namespace OHOS { +class UITestBUTTON : public UITest { +public: + UITestBUTTON() {} + ~UITestBUTTON() {} + void SetUp() override; + void TearDown() override; + UIView* GetTestView() override; + + /** + * @brief Test Checkbox Function + */ + void UIKit_Check_Box_Test_001() const; + + /** + * @brief Test Checkbox's SetImage Function + */ + void UIKit_Check_Box_Test_002() const; + + /** + * @brief Test Radiobutton Function + */ + void UIKit_Radio_Button_Test_001() const; + + /** + * @brief Test Radiobutton's SetImage Function + */ + void UIKit_Radio_Button_Test_002() const; + + /** + * @brief Test Togglebutton Function + */ + void UIKit_Toggle_Button_Test_001() const; + + /** + * @brief Test Togglebutton's SetImage Function + */ + void UIKit_Toggle_Button_Test_002() const; + + /** + * @brief Test button Function + */ + void UIKit_Button_Test_001() const; + +private: + static constexpr int16_t CHANGE_SIZE = 10; + UIScrollView* container_ = nullptr; +}; +} // namespace OHOS +#endif // UI_TEST_BUTTON_H diff --git a/test/uitest/test_canvas/ui_test_canvas.cpp b/test/uitest/test_canvas/ui_test_canvas.cpp new file mode 100755 index 0000000..5b13ad7 --- /dev/null +++ b/test/uitest/test_canvas/ui_test_canvas.cpp @@ -0,0 +1,927 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ui_test_canvas.h" +#include "common/screen.h" +#include "components/ui_label.h" +#include "test_resource_config.h" + +namespace OHOS { +void UITestCanvas::SetUp() +{ + if (container_ == nullptr) { + container_ = new UIScrollView(); + container_->Resize(Screen::GetInstance().GetWidth(), Screen::GetInstance().GetHeight() - BACK_BUTTON_HEIGHT); + container_->SetHorizontalScrollState(false); + container_->SetThrowDrag(true); + } + positionY_ = 0; +} + +void UITestCanvas::TearDown() +{ + DeleteChildren(container_); + container_ = nullptr; +} + +UIView* UITestCanvas::GetTestView() +{ + UIKitCanvasTestDrawLine001(); + UIKitCanvasTestDrawLine002(); + UIKitCanvasTestDrawCurve001(); + UIKitCanvasTestDrawCurve002(); + UIKitCanvasTestDrawRect001(); + UIKitCanvasTestDrawRect002(); + UIKitCanvasTestDrawRect003(); + UIKitCanvasTestDrawCircle001(); + UIKitCanvasTestDrawCircle002(); + UIKitCanvasTestDrawCircle003(); + UIKitCanvasTestDrawArc001(); + UIKitCanvasTestDrawImage001(); + UIKitCanvasTestDrawLabel001(); + UIKitCanvasTestDrawSector001(); + UIKitCanvasTestClear001(); + UIKitCanvasTestDrawPath001(); + UIKitCanvasTestDrawPath002(); + UIKitCanvasTestDrawPath003(); + UIKitCanvasTestDrawPath004(); + UIKitCanvasTestDrawPath005(); + UIKitCanvasTestDrawPath006(); + UIKitCanvasTestDrawPath007(); + UIKitCanvasTestDrawPath008(); + UIKitCanvasTestDrawPath009(); + UIKitCanvasTestDrawPath010(); + UIKitCanvasTestDrawPath011(); + UIKitCanvasTestDrawPath012(); + UIKitCanvasTestDrawPath013(); + UIKitCanvasTestDrawPath014(); + UIKitCanvasTestDrawPath015(); + UIKitCanvasTestDrawPath016(); + UIKitCanvasTestDrawPath017(); + UIKitCanvasTestDrawPath018(); + UIKitCanvasTestDrawPath019(); + UIKitCanvasTestDrawPath020(); + UIKitCanvasTestDrawPath021(); + UIKitCanvasTestDrawPath022(); + UIKitCanvasTestDrawPath023(); + UIKitCanvasTestDrawPath024(); + UIKitCanvasTestDrawPath025(); + UIKitCanvasTestDrawPath026(); + UIKitCanvasTestDrawPath027(); + UIKitCanvasTestDrawPath028(); + UIKitCanvasTestDrawPath029(); + UIKitCanvasTestDrawPath030(); + UIKitCanvasTestDrawPath031(); + UIKitCanvasTestDrawPath032(); + UIKitCanvasTestDrawPath033(); + UIKitCanvasTestDrawPath034(); + + return container_; +} + +void UITestCanvas::CreateTitleLabel(const char* title) +{ + UILabel* titleLabel = new UILabel(); + titleLabel->SetPosition(TEXT_DISTANCE_TO_LEFT_SIDE, positionY_, Screen::GetInstance().GetWidth(), TITLE_HEIGHT); + titleLabel->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + titleLabel->SetText(title); + container_->Add(titleLabel); + positionY_ += TITLE_HEIGHT + 8; // 8: gap +} + +UICanvas* UITestCanvas::CreateCanvas() +{ + UICanvas* canvas = new UICanvas(); + canvas->SetHeight(CANVAS_HEIGHT); + canvas->SetWidth(CANVAS_WIDTH); + canvas->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, positionY_); + canvas->SetStyle(STYLE_BACKGROUND_COLOR, Color::Gray().full); + container_->Add(canvas); + positionY_ += CANVAS_HEIGHT + GAP; + return canvas; +} + +void UITestCanvas::UIKitCanvasTestDrawLine001() +{ + if (container_ == nullptr) { + return; + } + CreateTitleLabel("绘制直线"); + UICanvas* canvas = CreateCanvas(); + Paint paint; + // {0, 10}: Start point coordinates x, y; {50, 10}: end point coordinates x, y + canvas->DrawLine({0, 10}, {50, 10}, paint); +} + +void UITestCanvas::UIKitCanvasTestDrawLine002() +{ + if (container_ == nullptr) { + return; + } + CreateTitleLabel("绘制直线"); + UICanvas* canvas = CreateCanvas(); + + Paint paint; + paint.SetStrokeWidth(5); // 5: line width + canvas->SetStartPosition({ 50, 10 }); // {50, 10}: Start point coordinates x, y; + canvas->DrawLine({ 100, 50 }, paint); // {100, 50}: end point coordinates x, y +} + +void UITestCanvas::UIKitCanvasTestDrawCurve001() +{ + if (container_ == nullptr) { + return; + } + CreateTitleLabel("绘制曲线"); + UICanvas* canvas = CreateCanvas(); + + Paint paint; + paint.SetStrokeColor(Color::Red()); + canvas->DrawCurve({ 100, 50 }, { 150, 50 }, { 150, 50 }, { 150, 100 }, paint); +} + +void UITestCanvas::UIKitCanvasTestDrawCurve002() +{ + if (container_ == nullptr) { + return; + } + CreateTitleLabel("绘制曲线"); + UICanvas* canvas = CreateCanvas(); + + Paint paint; + paint.SetStrokeColor(Color::Red()); + canvas->DrawCurve({ 100, 50 }, { 150, 50 }, { 150, 100 }, paint); +} + +void UITestCanvas::UIKitCanvasTestDrawRect001() +{ + if (container_ == nullptr) { + return; + } + CreateTitleLabel("矩形填充"); + UICanvas* canvas = CreateCanvas(); + + Paint paint; + paint.SetStyle(Paint::PaintStyle::FILL_STYLE); + paint.SetFillColor(Color::Yellow()); + paint.SetStrokeWidth(30); // 30: line width + // {100, 10}: left corner coordinates point, 50: width, 50: rectangle style + canvas->DrawRect({ 100, 10 }, 50, 50, paint); +} + +void UITestCanvas::UIKitCanvasTestDrawRect002() +{ + if (container_ == nullptr) { + return; + } + CreateTitleLabel("矩形描边"); + UICanvas* canvas = CreateCanvas(); + + Paint paint; + paint.SetStyle(Paint::PaintStyle::STROKE_STYLE); + paint.SetStrokeColor(Color::Blue()); + // {200, 10}: left corner coordinates point, 50: width, 50: rectangle style + canvas->DrawRect({ 200, 10 }, 50, 50, paint); +} + +void UITestCanvas::UIKitCanvasTestDrawRect003() +{ + if (container_ == nullptr) { + return; + } + CreateTitleLabel("矩形填充 + 描边"); + UICanvas* canvas = CreateCanvas(); + + Paint paint; + paint.SetStyle(Paint::PaintStyle::STROKE_FILL_STYLE); + paint.SetFillColor(Color::Yellow()); + paint.SetStrokeColor(Color::Blue()); + // {300, 10}: left corner coordinates point, 50: width, 50: rectangle style + canvas->DrawRect({ 300, 10 }, 50, 50, paint); +} + +void UITestCanvas::UIKitCanvasTestDrawCircle001() +{ + if (container_ == nullptr) { + return; + } + CreateTitleLabel("圆形填充"); + UICanvas* canvas = CreateCanvas(); + + Paint paint; + paint.SetStyle(Paint::PaintStyle::FILL_STYLE); + paint.SetStrokeColor(Color::Yellow()); + paint.SetFillColor(Color::Yellow()); + paint.SetStrokeWidth(10); // 10: line width + paint.SetOpacity(127); // 127: opacity + // {100, 100}: circle center coordinates, 30: circle radius + canvas->DrawCircle({ 100, 100 }, 30, paint); +} + +void UITestCanvas::UIKitCanvasTestDrawCircle002() +{ + if (container_ == nullptr) { + return; + } + CreateTitleLabel("圆形描边"); + UICanvas* canvas = CreateCanvas(); + + Paint paint; + paint.SetStyle(Paint::PaintStyle::STROKE_STYLE); + paint.SetStrokeColor(Color::Blue()); + paint.SetStrokeWidth(10); // 10: line width + paint.SetOpacity(127); // 127: opacity + // {200, 100}: circle center coordinates, 30: circle radius + canvas->DrawCircle({ 200, 100 }, 30, paint); +} + +void UITestCanvas::UIKitCanvasTestDrawCircle003() +{ + if (container_ == nullptr) { + return; + } + CreateTitleLabel("圆形填充 + 描边"); + UICanvas* canvas = CreateCanvas(); + + Paint paint; + paint.SetStyle(Paint::PaintStyle::STROKE_FILL_STYLE); + paint.SetFillColor(Color::Yellow()); + paint.SetStrokeColor(Color::Blue()); + paint.SetStrokeWidth(10); // 10: line width + paint.SetOpacity(127); // 127: opacity + // {300, 100}: circle center coordinates, 30: circle radius + canvas->DrawCircle({ 300, 100 }, 30, paint); +} + +void UITestCanvas::UIKitCanvasTestDrawArc001() +{ + if (container_ == nullptr) { + return; + } + CreateTitleLabel("绘制弧线"); + UICanvas* canvas = CreateCanvas(); + + Paint paint; + paint.SetStyle(Paint::PaintStyle::STROKE_STYLE); + paint.SetStrokeColor(Color::Red()); + paint.SetStrokeWidth(10); // 10: line width + // {100, 150}: arc's center coordinates, 50: arc radius, 135: start angle, 270: end angle + canvas->DrawArc({ 100, 150 }, 50, 135, 270, paint); +} + +void UITestCanvas::UIKitCanvasTestDrawImage001() +{ + if (container_ == nullptr) { + return; + } + CreateTitleLabel("绘制图片"); + UICanvas* canvas = CreateCanvas(); + + Paint paint; + paint.SetOpacity(127); // 127: opacity + // {200, 50}: start point coordinates + canvas->DrawImage({ 200, 50 }, GREEN_IMAGE_PATH, paint); +} + +void UITestCanvas::UIKitCanvasTestDrawLabel001() +{ + if (container_ == nullptr) { + return; + } + CreateTitleLabel("绘制文字"); + UICanvas* canvas = CreateCanvas(); + + Paint paint; + paint.SetFillColor(Color::Blue()); + UICanvas::FontStyle fontStyle; + fontStyle.align = TEXT_ALIGNMENT_RIGHT; + fontStyle.direct = TEXT_DIRECT_RTL; + fontStyle.fontName = DEFAULT_VECTOR_FONT_FILENAME; + fontStyle.fontSize = 30; // 30: font size + fontStyle.letterSpace = 10; // 10 letter space + // {50, 50}: start point coordinates, 100: max width + canvas->DrawLabel({ 50, 50 }, "canvas绘制字体", 100, fontStyle, paint); +} + +void UITestCanvas::UIKitCanvasTestDrawSector001() +{ + if (container_ == nullptr) { + return; + } + CreateTitleLabel("扇形填充"); + UICanvas* canvas = CreateCanvas(); + + Paint paint; + paint.SetStyle(Paint::PaintStyle::FILL_STYLE); + paint.SetFillColor(Color::Yellow()); + // {350, 150}: sector's center coordinates, 100: sector radius, 0: start angle, 30: end angle + canvas->DrawSector({ 350, 150 }, 100, 0, 30, paint); +} + +void UITestCanvas::UIKitCanvasTestClear001() +{ + if (container_ == nullptr) { + return; + } + CreateTitleLabel("清空画布,无显示"); + UICanvas* canvas = CreateCanvas(); + + Paint paint; + // {0, 10}: Start point coordinates x, y; {50, 10}: end point coordinates x, y + canvas->DrawLine({ 0, 10 }, { 50, 10 }, paint); + canvas->Clear(); +} + +void UITestCanvas::UIKitCanvasTestDrawPath001() +{ + if (container_ == nullptr) { + return; + } + CreateTitleLabel("moveTo,无显示"); + UICanvas* canvas = CreateCanvas(); + + Paint paint; + canvas->BeginPath(); + canvas->MoveTo({ START1_X, START1_Y }); + canvas->ClosePath(); + canvas->DrawPath(paint); +} + +void UITestCanvas::UIKitCanvasTestDrawPath002() +{ + if (container_ == nullptr) { + return; + } + CreateTitleLabel("lineTo,无显示"); + UICanvas* canvas = CreateCanvas(); + + Paint paint; + canvas->BeginPath(); + canvas->LineTo({ LINE1_X, LINE1_Y }); + canvas->DrawPath(paint); +} + +void UITestCanvas::UIKitCanvasTestDrawPath003() +{ + if (container_ == nullptr) { + return; + } + CreateTitleLabel("arc"); + UICanvas* canvas = CreateCanvas(); + + Paint paint; + canvas->BeginPath(); + canvas->ArcTo({ CENTER_X, CENTER_Y }, RADIUS, START_ANGLE, END_ANGLE); + canvas->DrawPath(paint); +} + +void UITestCanvas::UIKitCanvasTestDrawPath004() +{ + if (container_ == nullptr) { + return; + } + CreateTitleLabel("rect"); + UICanvas* canvas = CreateCanvas(); + + Paint paint; + canvas->BeginPath(); + canvas->AddRect({ RECT_X, RECT_Y }, RECT_HEIGHT, RECT_WIDTH); + canvas->DrawPath(paint); +} + +void UITestCanvas::UIKitCanvasTestDrawPath005() +{ + if (container_ == nullptr) { + return; + } + CreateTitleLabel("closePath,无显示"); + UICanvas* canvas = CreateCanvas(); + + Paint paint; + canvas->BeginPath(); + canvas->ClosePath(); + canvas->DrawPath(paint); +} + +void UITestCanvas::UIKitCanvasTestDrawPath006() +{ + if (container_ == nullptr) { + return; + } + CreateTitleLabel("moveTo + lineTo"); + UICanvas* canvas = CreateCanvas(); + + Paint paint; + canvas->BeginPath(); + canvas->MoveTo({ START1_X, START1_Y }); + canvas->LineTo({ LINE1_X, LINE1_Y }); + canvas->DrawPath(paint); +} + +void UITestCanvas::UIKitCanvasTestDrawPath007() +{ + if (container_ == nullptr) { + return; + } + CreateTitleLabel("moveTo + arc"); + UICanvas* canvas = CreateCanvas(); + + Paint paint; + canvas->BeginPath(); + canvas->MoveTo({ START1_X, START1_Y }); + canvas->ArcTo({ CENTER_X, CENTER_Y }, RADIUS, START_ANGLE, END_ANGLE); + canvas->DrawPath(paint); +} + +void UITestCanvas::UIKitCanvasTestDrawPath008() +{ + if (container_ == nullptr) { + return; + } + CreateTitleLabel("moveTo + rect"); + UICanvas* canvas = CreateCanvas(); + + Paint paint; + canvas->BeginPath(); + canvas->MoveTo({ START1_X, START1_Y }); + canvas->AddRect({ RECT_X, RECT_Y }, RECT_HEIGHT, RECT_WIDTH); + canvas->DrawPath(paint); +} + +void UITestCanvas::UIKitCanvasTestDrawPath009() +{ + if (container_ == nullptr) { + return; + } + CreateTitleLabel("moveTo + closePath"); + UICanvas* canvas = CreateCanvas(); + + Paint paint; + canvas->BeginPath(); + canvas->MoveTo({ START1_X, START1_Y }); + canvas->LineTo({ LINE1_X, LINE1_Y }); + canvas->LineTo({ LINE2_X, LINE2_Y }); + canvas->ClosePath(); + canvas->DrawPath(paint); +} + +void UITestCanvas::UIKitCanvasTestDrawPath010() +{ + if (container_ == nullptr) { + return; + } + CreateTitleLabel("闭合路径调用closePath"); + UICanvas* canvas = CreateCanvas(); + + Paint paint; + canvas->BeginPath(); + canvas->MoveTo({ START1_X, START1_Y }); + canvas->LineTo({ LINE1_X, LINE1_Y }); + canvas->LineTo({ LINE2_X, LINE2_Y }); + canvas->LineTo({ START1_X, START1_Y }); + canvas->ClosePath(); + canvas->DrawPath(paint); +} + +void UITestCanvas::UIKitCanvasTestDrawPath011() +{ + if (container_ == nullptr) { + return; + } + CreateTitleLabel("moveTo + lineTo + moveTo + lineTo"); + UICanvas* canvas = CreateCanvas(); + + Paint paint; + canvas->BeginPath(); + canvas->MoveTo({ START1_X, START1_Y }); + canvas->LineTo({ LINE1_X, LINE1_Y }); + canvas->MoveTo({ START2_X, START2_Y }); + canvas->LineTo({ LINE2_X, LINE2_Y }); + canvas->DrawPath(paint); +} + +void UITestCanvas::UIKitCanvasTestDrawPath012() +{ + if (container_ == nullptr) { + return; + } + CreateTitleLabel("moveTo + lineTo + arc"); + UICanvas* canvas = CreateCanvas(); + + Paint paint; + canvas->BeginPath(); + canvas->MoveTo({ START1_X, START1_Y }); + canvas->LineTo({ LINE1_X, LINE1_Y }); + canvas->ArcTo({ CENTER_X, CENTER_Y }, RADIUS, START_ANGLE, END_ANGLE); + canvas->DrawPath(paint); +} + +void UITestCanvas::UIKitCanvasTestDrawPath013() +{ + if (container_ == nullptr) { + return; + } + CreateTitleLabel("moveTo + lineTo + arc + closePath"); + UICanvas* canvas = CreateCanvas(); + + Paint paint; + canvas->BeginPath(); + canvas->MoveTo({ START1_X, START1_Y }); + canvas->LineTo({ LINE1_X, LINE1_Y }); + canvas->ArcTo({ CENTER_X, CENTER_Y }, RADIUS, START_ANGLE, END_ANGLE); + canvas->ClosePath(); + canvas->DrawPath(paint); +} + +void UITestCanvas::UIKitCanvasTestDrawPath014() +{ + if (container_ == nullptr) { + return; + } + CreateTitleLabel("moveTo + lineTo + rect"); + UICanvas* canvas = CreateCanvas(); + + Paint paint; + canvas->BeginPath(); + canvas->MoveTo({ START1_X, START1_Y }); + canvas->LineTo({ LINE1_X, LINE1_Y }); + canvas->AddRect({ RECT_X, RECT_Y }, RECT_HEIGHT, RECT_WIDTH); + canvas->DrawPath(paint); +} + +void UITestCanvas::UIKitCanvasTestDrawPath015() +{ + if (container_ == nullptr) { + return; + } + CreateTitleLabel("moveTo + lineTo + rect + closePath"); + UICanvas* canvas = CreateCanvas(); + + Paint paint; + canvas->BeginPath(); + canvas->MoveTo({ START1_X, START1_Y }); + canvas->LineTo({ LINE1_X, LINE1_Y }); + canvas->AddRect({ RECT_X, RECT_Y }, RECT_HEIGHT, RECT_WIDTH); + canvas->ClosePath(); + canvas->DrawPath(paint); +} + +void UITestCanvas::UIKitCanvasTestDrawPath016() +{ + if (container_ == nullptr) { + return; + } + CreateTitleLabel("rect + lineTo"); + UICanvas* canvas = CreateCanvas(); + + Paint paint; + canvas->BeginPath(); + canvas->AddRect({ RECT_X, RECT_Y }, RECT_HEIGHT, RECT_WIDTH); + canvas->LineTo({ LINE1_X, LINE1_Y }); + canvas->DrawPath(paint); +} + +void UITestCanvas::UIKitCanvasTestDrawPath017() +{ + if (container_ == nullptr) { + return; + } + CreateTitleLabel("rect + moveTo + lineTo"); + UICanvas* canvas = CreateCanvas(); + + Paint paint; + canvas->BeginPath(); + canvas->AddRect({ RECT_X, RECT_Y }, RECT_HEIGHT, RECT_WIDTH); + canvas->MoveTo({ START1_X, START1_Y }); + canvas->LineTo({ LINE1_X, LINE1_Y }); + canvas->DrawPath(paint); +} + +void UITestCanvas::UIKitCanvasTestDrawPath018() +{ + if (container_ == nullptr) { + return; + } + CreateTitleLabel("rect + arc"); + UICanvas* canvas = CreateCanvas(); + + Paint paint; + canvas->BeginPath(); + canvas->AddRect({ RECT_X, RECT_Y }, RECT_HEIGHT, RECT_WIDTH); + canvas->ArcTo({ CENTER_X, CENTER_Y }, RADIUS, START_ANGLE, END_ANGLE); + canvas->DrawPath(paint); +} + +void UITestCanvas::UIKitCanvasTestDrawPath019() +{ + if (container_ == nullptr) { + return; + } + CreateTitleLabel("arc + rect"); + UICanvas* canvas = CreateCanvas(); + + Paint paint; + canvas->BeginPath(); + canvas->ArcTo({ CENTER_X, CENTER_Y }, RADIUS, START_ANGLE, END_ANGLE); + canvas->AddRect({ RECT_X, RECT_Y }, RECT_HEIGHT, RECT_WIDTH); + canvas->DrawPath(paint); +} + +void UITestCanvas::UIKitCanvasTestDrawPath020() +{ + if (container_ == nullptr) { + return; + } + CreateTitleLabel("moveTo + arc + closePath + lineTo"); + UICanvas* canvas = CreateCanvas(); + + Paint paint; + canvas->BeginPath(); + canvas->MoveTo({ START1_X, START1_Y }); + canvas->ArcTo({ CENTER_X, CENTER_Y }, RADIUS, START_ANGLE, END_ANGLE); + canvas->ClosePath(); + canvas->LineTo({ LINE1_X, LINE1_Y }); + canvas->DrawPath(paint); +} + +void UITestCanvas::UIKitCanvasTestDrawPath021() +{ + if (container_ == nullptr) { + return; + } + CreateTitleLabel("不调用beginPath,无显示"); + UICanvas* canvas = CreateCanvas(); + + Paint paint; + canvas->MoveTo({ START1_X, START1_Y }); + canvas->ArcTo({ CENTER_X, CENTER_Y }, RADIUS, START_ANGLE, END_ANGLE); + canvas->ClosePath(); + canvas->LineTo({ LINE1_X, LINE1_Y }); + canvas->DrawPath(paint); +} + +void UITestCanvas::UIKitCanvasTestDrawPath022() +{ + if (container_ == nullptr) { + return; + } + CreateTitleLabel("不调用drawPath,无显示"); + UICanvas* canvas = CreateCanvas(); + + Paint paint; + canvas->BeginPath(); + canvas->MoveTo({ START1_X, START1_Y }); + canvas->ArcTo({ CENTER_X, CENTER_Y }, RADIUS, START_ANGLE, END_ANGLE); + canvas->ClosePath(); + canvas->LineTo({ LINE1_X, LINE1_Y }); +} + +void UITestCanvas::UIKitCanvasTestDrawPath023() +{ + if (container_ == nullptr) { + return; + } + CreateTitleLabel("moveTo + lineTo + closePath"); + UICanvas* canvas = CreateCanvas(); + + Paint paint; + canvas->BeginPath(); + canvas->MoveTo({ START1_X, START1_Y }); + canvas->LineTo({ LINE1_X, LINE1_Y }); + canvas->ClosePath(); + canvas->DrawPath(paint); +} + +void UITestCanvas::UIKitCanvasTestDrawPath024() +{ + if (container_ == nullptr) { + return; + } + CreateTitleLabel("moveTo + closePath,无显示"); + UICanvas* canvas = CreateCanvas(); + + Paint paint; + canvas->BeginPath(); + canvas->MoveTo({ START1_X, START1_Y }); + canvas->ClosePath(); + canvas->DrawPath(paint); +} + +void UITestCanvas::UIKitCanvasTestDrawPath025() +{ + if (container_ == nullptr) { + return; + } + CreateTitleLabel("多次drawPath"); + UICanvas* canvas = CreateCanvas(); + + Paint paint; + canvas->BeginPath(); + canvas->MoveTo({ START1_X, START1_Y }); + canvas->LineTo({ LINE1_X, LINE1_Y }); + canvas->DrawPath(paint); + paint.SetStrokeColor(Color::Blue()); + paint.SetStrokeWidth(1); + canvas->LineTo({ LINE2_X, LINE2_Y }); + canvas->DrawPath(paint); +} + +void UITestCanvas::UIKitCanvasTestDrawPath026() +{ + if (container_ == nullptr) { + return; + } + CreateTitleLabel("arc起止角度互换"); + UICanvas* canvas = CreateCanvas(); + + Paint paint; + canvas->BeginPath(); + canvas->MoveTo({ START1_X, START1_Y }); + canvas->ArcTo({ CENTER_X, CENTER_Y }, RADIUS, START_ANGLE, END_ANGLE); + + canvas->MoveTo({ START2_X, START2_Y }); + canvas->ArcTo({ CENTER_X + CENTER_X, CENTER_Y }, RADIUS, END_ANGLE, START_ANGLE); + + canvas->DrawPath(paint); +} + +void UITestCanvas::UIKitCanvasTestDrawPath027() +{ + if (container_ == nullptr) { + return; + } + CreateTitleLabel("arc扫描范围超过360度 "); + UICanvas* canvas = CreateCanvas(); + + Paint paint; + canvas->BeginPath(); + canvas->MoveTo({ START1_X, START1_Y }); + canvas->ArcTo({ CENTER_X, CENTER_Y }, RADIUS, START_ANGLE, START_ANGLE + CIRCLE_IN_DEGREE + QUARTER_IN_DEGREE); + canvas->ArcTo({ CENTER_X + CENTER_X, CENTER_Y }, RADIUS, END_ANGLE, START_ANGLE + CIRCLE_IN_DEGREE); + + canvas->DrawPath(paint); +} + +void UITestCanvas::UIKitCanvasTestDrawPath028() +{ + if (container_ == nullptr) { + return; + } + CreateTitleLabel("创建两条路径,只绘制后一条 "); + UICanvas* canvas = CreateCanvas(); + + Paint paint; + canvas->BeginPath(); + canvas->MoveTo({ START1_X, START1_Y }); + canvas->ArcTo({ CENTER_X, CENTER_Y }, RADIUS, START_ANGLE, START_ANGLE + CIRCLE_IN_DEGREE); + + canvas->BeginPath(); + canvas->MoveTo({ START2_X, START2_Y }); + canvas->ArcTo({ CENTER_X + CENTER_X, CENTER_Y }, RADIUS, END_ANGLE, START_ANGLE + CIRCLE_IN_DEGREE); + + canvas->DrawPath(paint); +} + +void UITestCanvas::UIKitCanvasTestDrawPath029() +{ + if (container_ == nullptr) { + return; + } + CreateTitleLabel("绘制两条不同样式的路径 "); + UICanvas* canvas = CreateCanvas(); + + Paint paint; + canvas->BeginPath(); + canvas->MoveTo({ START1_X, START1_Y }); + canvas->ArcTo({ CENTER_X, CENTER_Y }, RADIUS, START_ANGLE, START_ANGLE + CIRCLE_IN_DEGREE); + canvas->DrawPath(paint); + + canvas->BeginPath(); + canvas->MoveTo({ START2_X, START2_Y }); + canvas->ArcTo({ CENTER_X + CENTER_X, CENTER_Y }, RADIUS, END_ANGLE, START_ANGLE + CIRCLE_IN_DEGREE); + paint.SetStrokeColor(Color::Blue()); + canvas->DrawPath(paint); +} + +void UITestCanvas::UIKitCanvasTestDrawPath030() +{ + if (container_ == nullptr) { + return; + } + CreateTitleLabel("同一条路径绘制100遍 "); + UICanvas* canvas = CreateCanvas(); + + Paint paint; + canvas->BeginPath(); + canvas->MoveTo({ START1_X, START1_Y }); + canvas->LineTo({ LINE1_X, LINE1_Y }); + canvas->ArcTo({ CENTER_X, CENTER_Y }, RADIUS, START_ANGLE, START_ANGLE + CIRCLE_IN_DEGREE); + canvas->AddRect({ RECT_X, RECT_Y }, RECT_HEIGHT, RECT_WIDTH); + for (uint8_t i = 0; i < 100; i++) { // 100: number of times for drawing loops + canvas->DrawPath(paint); + } +} + +void UITestCanvas::UIKitCanvasTestDrawPath031() +{ + if (container_ == nullptr) { + return; + } + CreateTitleLabel("绘制直线超出canvas范围"); + UICanvas* canvas = CreateCanvas(); + + Paint paint; + canvas->BeginPath(); + canvas->MoveTo({ START1_X, START1_Y }); + canvas->LineTo({ LINE1_X + HORIZONTAL_RESOLUTION, LINE1_Y }); + canvas->LineTo({ LINE2_X, LINE2_Y }); + canvas->DrawPath(paint); +} + +void UITestCanvas::UIKitCanvasTestDrawPath032() +{ + if (container_ == nullptr) { + return; + } + CreateTitleLabel("绘制直线传入临界值 "); + UICanvas* canvas = CreateCanvas(); + + Paint paint; + canvas->BeginPath(); + canvas->MoveTo({ INT16_MAX, INT16_MAX }); + canvas->LineTo({ 0, 0 }); + + canvas->MoveTo({ 0, 0 }); + canvas->LineTo({ INT16_MAX, INT16_MAX }); + + canvas->MoveTo({ INT16_MIN, INT16_MIN }); + canvas->LineTo({ 0, 0 }); + + canvas->MoveTo({ 0, 0 }); + canvas->LineTo({ INT16_MIN, INT16_MIN }); + canvas->DrawPath(paint); +} + + +void UITestCanvas::UIKitCanvasTestDrawPath033() +{ + if (container_ == nullptr) { + return; + } + + CreateTitleLabel("绘制arc传入临界值 "); + UICanvas* canvas = CreateCanvas(); + + Paint paint; + canvas->BeginPath(); + canvas->ArcTo({ INT16_MAX, INT16_MAX }, RADIUS, START_ANGLE, END_ANGLE); + canvas->DrawPath(paint); + + canvas->BeginPath(); + canvas->ArcTo({ INT16_MIN, INT16_MIN }, RADIUS, START_ANGLE, END_ANGLE); + paint.SetStrokeColor(Color::Red()); + canvas->DrawPath(paint); + + canvas->BeginPath(); + canvas->ArcTo({ CENTER_X, CENTER_Y }, UINT16_MAX, START_ANGLE, END_ANGLE); + paint.SetStrokeColor(Color::Yellow()); + canvas->DrawPath(paint); + + canvas->BeginPath(); + canvas->ArcTo({ CENTER_X + CENTER_X, CENTER_Y }, RADIUS, INT16_MIN, INT16_MAX); + paint.SetStrokeColor(Color::Blue()); + canvas->DrawPath(paint); +} + +void UITestCanvas::UIKitCanvasTestDrawPath034() +{ + if (container_ == nullptr) { + return; + } + + CreateTitleLabel("绘制rect传入临界值 "); + UICanvas* canvas = CreateCanvas(); + + Paint paint; + canvas->BeginPath(); + canvas->AddRect({ INT16_MAX, INT16_MAX }, RECT_HEIGHT, RECT_WIDTH); + canvas->DrawPath(paint); + + canvas->BeginPath(); + canvas->AddRect({ RECT_X, RECT_Y }, INT16_MAX, INT16_MAX); + paint.SetStrokeColor(Color::Red()); + canvas->DrawPath(paint); +} +} // namespace OHOS \ No newline at end of file diff --git a/test/uitest/test_canvas/ui_test_canvas.h b/test/uitest/test_canvas/ui_test_canvas.h new file mode 100755 index 0000000..61e91a6 --- /dev/null +++ b/test/uitest/test_canvas/ui_test_canvas.h @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef UI_TEST_CANVAS_H +#define UI_TEST_CANVAS_H + +#include "components/ui_canvas.h" +#include "components/ui_label_button.h" +#include "components/ui_scroll_view.h" +#include "layout/grid_layout.h" +#include "ui_test.h" + +namespace OHOS { +class UITestCanvas : public UITest { +public: + UITestCanvas() {} + ~UITestCanvas() {} + void SetUp() override; + void TearDown() override; + UIView* GetTestView() override; + + void UIKitCanvasTestDrawLine001(); + void UIKitCanvasTestDrawLine002(); + void UIKitCanvasTestDrawCurve001(); + void UIKitCanvasTestDrawCurve002(); + void UIKitCanvasTestDrawRect001(); + void UIKitCanvasTestDrawRect002(); + void UIKitCanvasTestDrawRect003(); + void UIKitCanvasTestDrawCircle001(); + void UIKitCanvasTestDrawCircle002(); + void UIKitCanvasTestDrawCircle003(); + void UIKitCanvasTestDrawArc001(); + void UIKitCanvasTestDrawImage001(); + void UIKitCanvasTestDrawLabel001(); + void UIKitCanvasTestDrawSector001(); + void UIKitCanvasTestClear001(); + void UIKitCanvasTestDrawPath001(); + void UIKitCanvasTestDrawPath002(); + void UIKitCanvasTestDrawPath003(); + void UIKitCanvasTestDrawPath004(); + void UIKitCanvasTestDrawPath005(); + void UIKitCanvasTestDrawPath006(); + void UIKitCanvasTestDrawPath007(); + void UIKitCanvasTestDrawPath008(); + void UIKitCanvasTestDrawPath009(); + void UIKitCanvasTestDrawPath010(); + void UIKitCanvasTestDrawPath011(); + void UIKitCanvasTestDrawPath012(); + void UIKitCanvasTestDrawPath013(); + void UIKitCanvasTestDrawPath014(); + void UIKitCanvasTestDrawPath015(); + void UIKitCanvasTestDrawPath016(); + void UIKitCanvasTestDrawPath017(); + void UIKitCanvasTestDrawPath018(); + void UIKitCanvasTestDrawPath019(); + void UIKitCanvasTestDrawPath020(); + void UIKitCanvasTestDrawPath021(); + void UIKitCanvasTestDrawPath022(); + void UIKitCanvasTestDrawPath023(); + void UIKitCanvasTestDrawPath024(); + void UIKitCanvasTestDrawPath025(); + void UIKitCanvasTestDrawPath026(); + void UIKitCanvasTestDrawPath027(); + void UIKitCanvasTestDrawPath028(); + void UIKitCanvasTestDrawPath029(); + void UIKitCanvasTestDrawPath030(); + void UIKitCanvasTestDrawPath031(); + void UIKitCanvasTestDrawPath032(); + void UIKitCanvasTestDrawPath033(); + void UIKitCanvasTestDrawPath034(); + +private: + const static int16_t GAP = 10; + const static int16_t TITLE_HEIGHT = 29; + const static uint16_t CANVAS_WIDTH = 454; + const static uint16_t CANVAS_HEIGHT = 200; + + static constexpr int16_t START1_X = 10; + static constexpr int16_t START1_Y = 10; + static constexpr int16_t START2_X = 80; + static constexpr int16_t START2_Y = 10; + static constexpr int16_t LINE1_X = 40; + static constexpr int16_t LINE1_Y = 100; + static constexpr int16_t LINE2_X = 100; + static constexpr int16_t LINE2_Y = 120; + static constexpr int16_t CENTER_X = 150; + static constexpr int16_t CENTER_Y = 150; + static constexpr int16_t RADIUS = 50; + static constexpr int16_t START_ANGLE = 30; + static constexpr int16_t END_ANGLE = 250; + static constexpr int16_t RECT_X = 250; + static constexpr int16_t RECT_Y = 50; + static constexpr int16_t RECT_WIDTH = 100; + static constexpr int16_t RECT_HEIGHT = 50; + void CreateTitleLabel(const char* title); + UICanvas* CreateCanvas(); + + UIScrollView* container_ = nullptr; +}; +} // namespace OHOS +#endif // UI_TEST_CANVAS_H diff --git a/test/uitest/test_chart/ui_test_chart_pillar.cpp b/test/uitest/test_chart/ui_test_chart_pillar.cpp new file mode 100755 index 0000000..6551dcd --- /dev/null +++ b/test/uitest/test_chart/ui_test_chart_pillar.cpp @@ -0,0 +1,225 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ui_test_chart_pillar.h" +#include "common/screen.h" + +namespace OHOS { +namespace { + static int16_t g_blank = 20; +} + +void UITestChartPillar::SetUp() +{ + if (container_ == nullptr) { + container_ = new UIScrollView(); + container_->Resize(Screen::GetInstance().GetWidth(), Screen::GetInstance().GetHeight() - BACK_BUTTON_HEIGHT); + container_->SetThrowDrag(true); + } + + dataSerial_[0] = new UIChartDataSerial(); + dataSerial_[0]->SetMaxDataCount(5); // 5: number of data points + Point pointArray[5] = {{0, 2478}, {1, 2600}, {2, 3000}, {3, 3200}, {4, 3500}}; + dataSerial_[0]->AddPoints(pointArray, 5); // 5: number of data points + dataSerial_[0]->SetFillColor(Color::Red()); + + dataSerial_[1] = new UIChartDataSerial(); + dataSerial_[1]->SetMaxDataCount(5); // 5: number of data points + Point pointArray1[5] = {{0, 2000}, {1, 0}, {2, 800}, {3, 700}, {4, 433}}; + dataSerial_[1]->AddPoints(pointArray1, 5); // 5: number of data points + dataSerial_[1]->SetFillColor(Color::Green()); + + dataSerial_[2] = new UIChartDataSerial(); // 2 array index + dataSerial_[2]->SetMaxDataCount(5); // 2 array index, 5: number of data points + Point pointArray2[5] = {{0, 100}, {1, 200}, {2, 300}, {3, 400}, {4, 500}}; + dataSerial_[2]->AddPoints(pointArray2, 5); // 2 array index, 5: number of data points + dataSerial_[2]->SetFillColor(Color::Blue()); // 2 array index + curDataIndex_ = 0; +} + +void UITestChartPillar::InnerDeleteChildren(UIView* view) const +{ + if (view == nullptr) { + return; + } + while (view != nullptr) { + UIView* tempView = view; + view = view->GetNextSibling(); + if (tempView->IsViewGroup()) { + InnerDeleteChildren(static_cast(tempView)->GetChildrenHead()); + } + if (tempView->GetViewType() == UI_AXIS) { + return; + } + if (tempView->GetParent()) { + static_cast(tempView->GetParent())->Remove(tempView); + } + delete tempView; + } +} + +void UITestChartPillar::TearDown() +{ + chart_->ClearDataSerial(); + for (uint8_t i = 0; i < DATA_NUM; i++) { + delete dataSerial_[i]; + dataSerial_[i] = nullptr; + } + InnerDeleteChildren(container_); + container_ = nullptr; + lastX_ = 0; + lastY_ = 0; + positionX_ = 0; + positionY_ = 0; +} + +UIView* UITestChartPillar::GetTestView() +{ + UIKit_ChartPillar_Test_AddDataSerial_001(); + UIKit_ChartPillar_Test_EnableReverse_002(); + UIKit_ChartPillar_Test_SetAxisLineColor_003(); + UIKit_ChartPillar_Test_SetAxisLineVisible_004(); + return container_; +} + +void UITestChartPillar::UIKit_ChartPillar_Test_AddDataSerial_001() +{ + UILabel* label = GetTitleLabel("UIChartPillar效果 "); + label->SetPosition(TEXT_DISTANCE_TO_LEFT_SIDE, TEXT_DISTANCE_TO_TOP_SIDE); + container_->Add(label); + + chart_ = new UIChartPillar(); + chart_->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, VIEW_DISTANCE_TO_TOP_SIDE); + chart_->SetWidth(454); // 454: width + chart_->SetHeight(250); // 250: height + + UIXAxis& xAxis = chart_->GetXAxis(); + UIYAxis& yAxis = chart_->GetYAxis(); + xAxis.SetMarkNum(5); // 5: number of scales + yAxis.SetDataRange(0, 5000); // 0: minimum value, 5000: maximum value + + chart_->AddDataSerial(dataSerial_[0]); + curDataIndex_++; + container_->Add(chart_); + SetLastPos(chart_); + + addDataSerialBtn_ = new UILabelButton(); + deleteDataSerialBtn_ = new UILabelButton(); + clearDataSerialBtn_ = new UILabelButton(); + + positionY_ = lastY_ + 10; // 10: increase y-coordinate + positionX_ = 48; // 48: x-coordinate + SetUpButton(addDataSerialBtn_, "添加数据 "); + + positionX_ = addDataSerialBtn_->GetX() + addDataSerialBtn_->GetWidth() + g_blank; + positionY_ = addDataSerialBtn_->GetY(); + SetUpButton(deleteDataSerialBtn_, "删除数据 "); + + positionX_ = deleteDataSerialBtn_->GetX() + deleteDataSerialBtn_->GetWidth() + g_blank; + positionY_ = deleteDataSerialBtn_->GetY(); + SetUpButton(clearDataSerialBtn_, "清空数据 "); +} + +void UITestChartPillar::UIKit_ChartPillar_Test_EnableReverse_002() +{ + reverseBtn_ = new UILabelButton(); + positionX_ = 48; // 48: x-coordinate + SetUpButton(reverseBtn_, "翻转 "); +} + +void UITestChartPillar::UIKit_ChartPillar_Test_SetAxisLineColor_003() +{ + setAxisColorBtn_ = new UILabelButton(); + positionX_ = reverseBtn_->GetX() + reverseBtn_->GetWidth() + g_blank; + positionY_ = reverseBtn_->GetY(); + SetUpButton(setAxisColorBtn_, "坐标轴颜色 "); +} + +void UITestChartPillar::UIKit_ChartPillar_Test_SetAxisLineVisible_004() +{ + setAxisVisibleBtn_ = new UILabelButton(); + positionX_ = setAxisColorBtn_->GetX() + setAxisColorBtn_->GetWidth() + g_blank; + positionY_ = setAxisColorBtn_->GetY(); + SetUpButton(setAxisVisibleBtn_, "坐标轴不可见"); +} + +bool UITestChartPillar::OnClick(UIView& view, const ClickEvent& event) +{ + if (&view == addDataSerialBtn_) { + if (curDataIndex_ >= DATA_NUM) { + return true; + } + chart_->AddDataSerial(dataSerial_[curDataIndex_]); + curDataIndex_++; + chart_->Invalidate(); + } else if (&view == deleteDataSerialBtn_) { + if (curDataIndex_ <= 0) { + return true; + } + chart_->DeleteDataSerial(dataSerial_[curDataIndex_ - 1]); + curDataIndex_--; + chart_->Invalidate(); + } else if (&view == clearDataSerialBtn_) { + chart_->ClearDataSerial(); + curDataIndex_ = 0; + chart_->Invalidate(); + } else if (&view == reverseBtn_) { + chart_->EnableReverse(true); + chart_->Invalidate(); + } else if (&view == setAxisColorBtn_) { + UIXAxis& xAxis = chart_->GetXAxis(); + UIYAxis& yAxis = chart_->GetYAxis(); + xAxis.SetLineColor(Color::Red()); + yAxis.SetLineColor(Color::Red()); + chart_->Invalidate(); + } else if (&view == setAxisVisibleBtn_) { + UIXAxis& xAxis = chart_->GetXAxis(); + UIYAxis& yAxis = chart_->GetYAxis(); + xAxis.SetVisible(false); + yAxis.SetVisible(false); + chart_->Invalidate(); + } + return true; +} + +void UITestChartPillar::SetUpButton(UILabelButton* btn, const char* title) +{ + if (btn == nullptr) { + return; + } + container_->Add(btn); + btn->SetPosition(positionX_, positionY_, BUTTON_WIDHT2, BUTTON_HEIGHT2); + positionY_ += btn->GetHeight() + 10; // 10: increase height + btn->SetText(title); + btn->SetFont(DEFAULT_VECTOR_FONT_FILENAME, BUTTON_LABEL_SIZE); + btn->SetOnClickListener(this); + btn->SetStyleForState(STYLE_BORDER_RADIUS, BUTTON_STYLE_BORDER_RADIUS_VALUE, UIButton::RELEASED); + btn->SetStyleForState(STYLE_BORDER_RADIUS, BUTTON_STYLE_BORDER_RADIUS_VALUE, UIButton::PRESSED); + btn->SetStyleForState(STYLE_BORDER_RADIUS, BUTTON_STYLE_BORDER_RADIUS_VALUE, UIButton::INACTIVE); + btn->SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::RELEASED); + btn->SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::PRESSED); + btn->SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::INACTIVE); + container_->Invalidate(); +} + +void UITestChartPillar::SetLastPos(UIView* view) +{ + if (view == nullptr) { + return; + } + lastX_ = view->GetX(); + lastY_ = view->GetY() + view->GetHeight(); +} +} // namespace OHOS \ No newline at end of file diff --git a/test/uitest/test_chart/ui_test_chart_pillar.h b/test/uitest/test_chart/ui_test_chart_pillar.h new file mode 100755 index 0000000..a1f993f --- /dev/null +++ b/test/uitest/test_chart/ui_test_chart_pillar.h @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef UI_TEST_CHART_PILLAR_H +#define UI_TEST_CHART_PILLAR_H + +#include "components/ui_chart.h" +#include "components/ui_label.h" +#include "components/ui_label_button.h" +#include "components/ui_scroll_view.h" +#include "ui_test.h" + +namespace OHOS { +class UITestChartPillar : public UITest, public UIView::OnClickListener { +public: + UITestChartPillar() : dataSerial_() {} + ~UITestChartPillar() {} + void SetUp() override; + void TearDown() override; + UIView* GetTestView() override; + + void SetUpButton(UILabelButton* btn, const char* title); + + bool OnClick(UIView& view, const ClickEvent& event) override; + + void UIKit_ChartPillar_Test_AddDataSerial_001(); + void UIKit_ChartPillar_Test_EnableReverse_002(); + void UIKit_ChartPillar_Test_SetAxisLineColor_003(); + void UIKit_ChartPillar_Test_SetAxisLineVisible_004(); + +private: + void SetLastPos(UIView* view); + void InnerDeleteChildren(UIView* view) const; + static constexpr uint8_t DATA_NUM = 3; + UIChartDataSerial *dataSerial_[DATA_NUM]; + uint8_t curDataIndex_ = 0; + + UIScrollView* container_ = nullptr; + UIChartPillar* chart_ = nullptr; + UILabelButton* addDataSerialBtn_ = nullptr; + UILabelButton* deleteDataSerialBtn_ = nullptr; + UILabelButton* clearDataSerialBtn_ = nullptr; + UILabelButton* reverseBtn_ = nullptr; + UILabelButton* setAxisColorBtn_ = nullptr; + UILabelButton* setAxisVisibleBtn_ = nullptr; + + int16_t lastX_ = 0; + int16_t lastY_ = 0; +}; +} // namespace OHOS +#endif // UI_TEST_CHART_PILLAR_H diff --git a/test/uitest/test_chart/ui_test_chart_polyline.cpp b/test/uitest/test_chart/ui_test_chart_polyline.cpp new file mode 100755 index 0000000..59ad7ad --- /dev/null +++ b/test/uitest/test_chart/ui_test_chart_polyline.cpp @@ -0,0 +1,416 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ui_test_chart_polyline.h" +#include "common/screen.h" + +namespace OHOS { +namespace { +static int16_t g_blank = 20; +static int g_curSerialCount = 0; +static int g_curArrayCount = 0; +static bool g_secondScreenFlag = false; +static bool g_addPointsFlag = false; +static bool g_hidePointsFlag = false; +} // namespace + +void UITestChartPolyline::SetUp() +{ + if (container_ == nullptr) { + container_ = new UIScrollView(); + container_->Resize(Screen::GetInstance().GetWidth(), Screen::GetInstance().GetHeight() - BACK_BUTTON_HEIGHT); + container_->SetHorizontalScrollState(false); + container_->SetThrowDrag(true); + } + + dataSerial_[0] = new UIChartDataSerial(); + dataSerial_[0]->SetMaxDataCount(5); // 5: number of data points + Point pointArray[5] = {{0, 2478}, {1, 2600}, {2, 3000}, {3, 3200}, {4, 3500}}; + dataSerial_[0]->AddPoints(pointArray, 5); // 5: number of data points + dataSerial_[0]->SetLineColor(Color::Red()); + dataSerial_[0]->SetFillColor(Color::Red()); + dataSerial_[0]->EnableGradient(true); + + dataSerial_[1] = new UIChartDataSerial(); + dataSerial_[1]->SetMaxDataCount(5); // 5: number of data points + Point pointArray1[5] = {{0, 2000}, {1, 0}, {2, 800}, {3, 700}, {4, 433}}; + dataSerial_[1]->AddPoints(pointArray1, 5); // 5: number of data points + dataSerial_[1]->SetLineColor(Color::Green()); + dataSerial_[1]->SetFillColor(Color::Green()); + dataSerial_[1]->EnableGradient(true); + + dataSerial_[2] = new UIChartDataSerial(); // 2: array index + dataSerial_[2]->SetMaxDataCount(5); // 2: array index, 5: number of data points + Point pointArray2[5] = {{0, 100}, {1, 200}, {2, 300}, {3, 400}, {4, 500}}; + dataSerial_[2]->AddPoints(pointArray2, 5); // 2: array index, 5: number of data points + dataSerial_[2]->SetLineColor(Color::Blue()); // 2: array index + curDataIndex_ = 0; +} + +void UITestChartPolyline::InnerDeleteChildren(UIView* view) const +{ + if (view == nullptr) { + return; + } + while (view != nullptr) { + UIView* tempView = view; + view = view->GetNextSibling(); + if (tempView->IsViewGroup()) { + InnerDeleteChildren(static_cast(tempView)->GetChildrenHead()); + } + if (tempView->GetViewType() == UI_AXIS) { + return; + } + if (tempView->GetParent()) { + static_cast(tempView->GetParent())->Remove(tempView); + } + delete tempView; + } +} + +void UITestChartPolyline::TearDown() +{ + ECGAnimator_->Stop(); + AnimatorManager::GetInstance()->Remove(ECGAnimator_); + delete ECGAnimator_; + ECGAnimator_ = nullptr; + chart_->ClearDataSerial(); + for (uint8_t i = 0; i < DATA_NUM; i++) { + delete dataSerial_[i]; + dataSerial_[i] = nullptr; + } + InnerDeleteChildren(container_); + container_ = nullptr; + lastX_ = 0; + lastY_ = 0; + positionX_ = 0; + positionY_ = 0; + g_curSerialCount = 0; + g_curArrayCount = 0; + g_secondScreenFlag = false; + g_addPointsFlag = false; + g_hidePointsFlag = false; +} + +UIView* UITestChartPolyline::GetTestView() +{ + UIKit_ChartPolyline_Test_AddDataSerial_001(); + UIKit_ChartPolyline_Test_EnableReverse_002(); + UIKit_ChartPolyline_Test_SetGradientBottom_003(); + UIKit_ChartPolyline_Test_AddPoints_004(); + + return container_; +} + +void UITestChartPolyline::UIKit_ChartPolyline_Test_AddDataSerial_001() +{ + UILabel* label = new UILabel(); + container_->Add(label); + lastY_ = TEXT_DISTANCE_TO_TOP_SIDE; + // 29: label height + label->SetPosition(TEXT_DISTANCE_TO_LEFT_SIDE, lastY_, Screen::GetInstance().GetWidth(), 29); + label->SetText("chart添加、删除数据串 "); + label->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + + chart_ = new UIChartPolyline(); + chart_->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, VIEW_DISTANCE_TO_TOP_SIDE); + chart_->SetWidth(454); // 454: width + chart_->SetHeight(250); // 250: height + + UIXAxis& xAxis = chart_->GetXAxis(); + UIYAxis& yAxis = chart_->GetYAxis(); + xAxis.SetMarkNum(5); // 5: number of scales + xAxis.SetDataRange(0, 5); // 0: minimum value, 5: maximum value + yAxis.SetDataRange(0, 5000); // 0: minimum value, 5000: maximum value + + chart_->SetGradientOpacity(25, 127); // 25: min opacity, 127: max opacity + chart_->AddDataSerial(dataSerial_[0]); + curDataIndex_++; + container_->Add(chart_); + SetLastPos(chart_); + + addDataSerialBtn_ = new UILabelButton(); + deleteDataSerialBtn_ = new UILabelButton(); + clearDataSerialBtn_ = new UILabelButton(); + topPointBtn_ = new UILabelButton(); + bottomPointBtn_ = new UILabelButton(); + headPointBtn_ = new UILabelButton(); + + positionX_ = VIEW_DISTANCE_TO_LEFT_SIDE; + positionY_ = lastY_ + 10; // 10: increase y-coordinate + SetUpButton(addDataSerialBtn_, "添加数据 "); + positionX_ = addDataSerialBtn_->GetX() + addDataSerialBtn_->GetWidth() + g_blank; + positionY_ = addDataSerialBtn_->GetY(); + SetUpButton(deleteDataSerialBtn_, "删除数据 "); + positionX_ = deleteDataSerialBtn_->GetX() + deleteDataSerialBtn_->GetWidth() + g_blank; + positionY_ = deleteDataSerialBtn_->GetY(); + SetUpButton(clearDataSerialBtn_, "清空数据 "); + + positionX_ = VIEW_DISTANCE_TO_LEFT_SIDE; + SetUpButton(topPointBtn_, "最高点 "); + positionX_ = topPointBtn_->GetX() + topPointBtn_->GetWidth() + g_blank; + positionY_ = topPointBtn_->GetY(); + SetUpButton(bottomPointBtn_, "最低点 "); + positionX_ = bottomPointBtn_->GetX() + bottomPointBtn_->GetWidth() + g_blank; + positionY_ = bottomPointBtn_->GetY(); + SetUpButton(headPointBtn_, "最新点 "); +} + +void UITestChartPolyline::UIKit_ChartPolyline_Test_EnableReverse_002() +{ + reverseBtn_ = new UILabelButton(); + positionX_ = VIEW_DISTANCE_TO_LEFT_SIDE; + SetUpButton(reverseBtn_, "翻转 "); + SetLastPos(reverseBtn_); +} + +void UITestChartPolyline::UIKit_ChartPolyline_Test_SetGradientBottom_003() +{ + gradientBottomBtn_ = new UILabelButton(); + positionX_ = reverseBtn_->GetX() + reverseBtn_->GetWidth() + g_blank; + positionY_ = reverseBtn_->GetY(); + SetUpButton(gradientBottomBtn_, "填充底部位置 "); + SetLastPos(gradientBottomBtn_); +} +namespace { +const int16_t DATA_COUNT = 480; +} +/* ECG test data */ +static int16_t g_ECGData[DATA_COUNT] = { + 68, 70, 73, 83, 95, 107, 118, 127, 118, 103, 90, 77, 66, 61, 57, 58, 60, 61, 62, 62, 63, 64, 64, 65, 67, 69, + 70, 71, 73, 75, 76, 78, 78, 79, 80, 80, 80, 80, 80, 80, 79, 78, 77, 76, 75, 73, 72, 71, 70, 70, 70, 70, + 70, 70, 70, 70, 71, 71, 71, 71, 71, 71, 72, 72, 72, 72, 73, 73, 73, 73, 74, 74, 74, 74, 75, 75, 75, 75, + 75, 75, 75, 75, 74, 74, 74, 74, 74, 74, 74, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 72, 71, + 71, 71, 70, 70, 70, 70, 71, 73, 75, 78, 80, 81, 82, 82, 82, 80, 78, 76, 73, 71, 69, 69, 68, 68, 68, 68, + 68, 70, 76, 88, 100, 111, 122, 126, 112, 98, 85, 73, 61, 58, 57, 59, 60, 61, 62, 62, 63, 64, 65, 66, 68, 69, + 70, 72, 74, 76, 77, 78, 79, 79, 80, 80, 80, 80, 79, 79, 79, 78, 77, 76, 74, 73, 72, 71, 70, 70, 70, 70, + 70, 70, 70, 71, 71, 71, 71, 71, 71, 72, 72, 72, 72, 73, 73, 73, 73, 74, 74, 74, 74, 74, 75, 75, 75, 75, + 75, 75, 75, 75, 74, 74, 74, 74, 74, 74, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 72, 72, 71, + 71, 70, 70, 69, 70, 70, 72, 74, 76, 78, 80, 82, 82, 82, 81, 79, 77, 75, 72, 70, 69, 68, 68, 68, 68, 68, + 69, 72, 80, 93, 104, 115, 126, 121, 106, 93, 80, 68, 59, 57, 58, 60, 61, 62, 62, 63, 63, 64, 65, 67, 68, 70, + 71, 73, 75, 76, 77, 78, 79, 80, 80, 80, 80, 80, 79, 79, 78, 78, 76, 75, 74, 72, 71, 70, 70, 70, 70, 70, + 70, 70, 70, 71, 71, 71, 71, 71, 71, 72, 72, 72, 73, 73, 73, 73, 74, 74, 74, 74, 74, 75, 75, 75, 75, 75, + 75, 75, 75, 75, 74, 74, 74, 74, 74, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 72, 72, 71, + 71, 70, 70, 69, 70, 71, 72, 75, 77, 79, 81, 82, 82, 82, 80, 79, 76, 74, 71, 70, 69, 68, 68, 68, 68, 69, + 70, 75, 85, 97, 109, 120, 127, 115, 101, 88, 75, 64, 57, 56, 58, 60, 61, 62, 62, 63, 64, 65, 66, 67, 69, 71, + 72, 74, 75, 77, 78, 79, 79, 80, 80, 80, 80, 80, 79, 79, 78, 77, 76, 74, 73, 72, 71, 70, 70, 70, 70, 70, + 70, 70, 71, 71, 71, 71, 71, 71, 72, 72, 72, 72, 80, 93, 104, 115, 126, 121, 106, 93, 80, 68, 59, 57, 58, 60, + 61, 62, 62, 63, 63, 64, 65, 67, 68, 70, 71, 73}; + +class ImageAnimatorCallbackDemo : public OHOS::AnimatorCallback { +public: + explicit ImageAnimatorCallbackDemo(UIChartDataSerial* serial) : times_(0), serial_(serial) {} + + ~ImageAnimatorCallbackDemo() {} + + virtual void Callback(UIView* view) + { + if (view == nullptr) { + return; + } + if (!g_addPointsFlag) { + return; /* Control the addition of points by button, and automate if commented out */ + } + if (times_++ != 6) { /* Add 10 points for every 6 ticks */ + return; + } + times_ = 0; + if (g_curSerialCount == 0) { + view->Invalidate(); + } + Point pointArray1[10]; + for (uint16_t i = 0; i < 10; i++) { // 10: array max index + pointArray1[i].x = g_curSerialCount; + pointArray1[i].y = g_ECGData[g_curArrayCount]; + g_curSerialCount++; + g_curArrayCount++; + if (g_curArrayCount == DATA_COUNT) { + g_curArrayCount = 0; + } + if (!g_secondScreenFlag) { + serial_->AddPoints(&pointArray1[i], 1); + } else { + serial_->ModifyPoint(g_curSerialCount, pointArray1[i]); + if (g_hidePointsFlag) { + serial_->HidePoint(g_curSerialCount, 30); // 30: the number of points + } + } + } + if (g_curSerialCount > 454) { // 454: max serial count + g_curSerialCount = 0; + g_secondScreenFlag = true; + } + + UIChart* chart = static_cast(view); + chart->RefreshChart(); + g_addPointsFlag = false; + } + +protected: + int16_t times_; + UIChartDataSerial* serial_; +}; + +void UITestChartPolyline::UIKit_ChartPolyline_Test_AddPoints_004() +{ + UIViewGroup* uiViewGroup = new UIViewGroup(); + // 2: x-coordinate, half of screen width; 2: half of screen width; 470: screen height + uiViewGroup->SetPosition(Screen::GetInstance().GetWidth() / 2, 0, Screen::GetInstance().GetWidth() / 2, 470); + container_->Add(uiViewGroup); + + UILabel* label = new UILabel(); + uiViewGroup->Add(label); + label->SetPosition(TEXT_DISTANCE_TO_LEFT_SIDE, TEXT_DISTANCE_TO_TOP_SIDE, + Screen::GetInstance().GetWidth() / 2 - TEXT_DISTANCE_TO_LEFT_SIDE, // 2: half of screen width; + TITLE_LABEL_DEFAULT_HEIGHT); + label->SetText("chart追加点、修改点、平滑化"); + label->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + + ECGChart_ = new UIChartPolyline(); + // 454: new width, 250: new height + ECGChart_->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, VIEW_DISTANCE_TO_TOP_SIDE, 454, 250); + uiViewGroup->Add(ECGChart_); + SetLastPos(ECGChart_); + ECGChart_->SetStyle(STYLE_LINE_WIDTH, 5); // 5: line width + + UIXAxis& xAxis = ECGChart_->GetXAxis(); + UIYAxis& yAxis = ECGChart_->GetYAxis(); + xAxis.SetDataRange(0, 454); // 454: maximum value + xAxis.SetMarkNum(10); // 10: number of scales + yAxis.SetDataRange(0, 200); // 200: maximum value + + ECGDataSerial_ = new UIChartDataSerial(); + ECGDataSerial_->SetMaxDataCount(454); // 454: number of data points + ECGDataSerial_->SetLineColor(Color::Red()); + ECGDataSerial_->EnableHeadPoint(true); + ECGChart_->AddDataSerial(ECGDataSerial_); + ImageAnimatorCallbackDemo* imageAnimCallback = new ImageAnimatorCallbackDemo(ECGDataSerial_); + ECGAnimator_ = new OHOS::Animator(imageAnimCallback, ECGChart_, 0, true); + AnimatorManager::GetInstance()->Add(ECGAnimator_); + ECGAnimator_->Start(); + + addPointsBtn_ = new UILabelButton(); + smoothBtn_ = new UILabelButton(); + hidePointsBtn_ = new UILabelButton(); + + // 2: half of screen width + positionX_ = Screen::GetInstance().GetWidth() / 2 + VIEW_DISTANCE_TO_LEFT_SIDE; + positionY_ = lastY_ + 10; // 10: increase y-coordinate + SetUpButton(addPointsBtn_, "添加点 "); + + positionX_ = addPointsBtn_->GetX() + addPointsBtn_->GetWidth() + g_blank; + positionY_ = addPointsBtn_->GetY(); + SetUpButton(smoothBtn_, "平滑化 "); + + positionX_ = smoothBtn_->GetX() + smoothBtn_->GetWidth() + g_blank; + positionY_ = smoothBtn_->GetY(); + SetUpButton(hidePointsBtn_, "隐藏点 "); +} + +bool UITestChartPolyline::OnClick(UIView& view, const ClickEvent& event) +{ + UIChartDataSerial::PointStyle pointStyle; + pointStyle.fillColor = Color::White(); + pointStyle.radius = 5; // 5: Inner radius + pointStyle.strokeColor = Color::Red(); + pointStyle.strokeWidth = 2; // 2: border width + + if (&view == addDataSerialBtn_) { + if (curDataIndex_ >= DATA_NUM) { + return true; + } + chart_->AddDataSerial(dataSerial_[curDataIndex_]); + curDataIndex_++; + chart_->Invalidate(); + } else if (&view == deleteDataSerialBtn_) { + if (curDataIndex_ <= 0) { + return true; + } + chart_->DeleteDataSerial(dataSerial_[curDataIndex_ - 1]); + curDataIndex_--; + chart_->Invalidate(); + } else if (&view == clearDataSerialBtn_) { + chart_->ClearDataSerial(); + curDataIndex_ = 0; + chart_->Invalidate(); + } else if (&view == topPointBtn_) { + dataSerial_[0]->EnableTopPoint(true); + pointStyle.strokeColor = Color::Red(); + dataSerial_[0]->SetTopPointStyle(pointStyle); + chart_->Invalidate(); + } else if (&view == bottomPointBtn_) { + dataSerial_[0]->EnableBottomPoint(true); + pointStyle.strokeColor = Color::Blue(); + dataSerial_[0]->SetBottomPointStyle(pointStyle); + chart_->Invalidate(); + } else if (&view == headPointBtn_) { + dataSerial_[0]->EnableHeadPoint(true); + pointStyle.strokeColor = Color::Yellow(); + dataSerial_[0]->SetHeadPointStyle(pointStyle); + chart_->Invalidate(); + } else if (&view == reverseBtn_) { + chart_->EnableReverse(true); + chart_->Invalidate(); + } else { + ClickExpand(view, pointStyle); + } + return true; +} + +bool UITestChartPolyline::ClickExpand(UIView& view, UIChartDataSerial::PointStyle pointStyle) +{ + if (&view == gradientBottomBtn_) { + chart_->SetGradientBottom(50); // 50: bottom of the filling range + chart_->Invalidate(); + } else if (&view == addPointsBtn_) { + g_addPointsFlag = true; + } else if (&view == smoothBtn_) { + ECGDataSerial_->EnableSmooth(true); + ECGChart_->Invalidate(); + } else if (&view == hidePointsBtn_) { + g_hidePointsFlag = true; + } + return true; +} + +void UITestChartPolyline::SetUpButton(UILabelButton* btn, const char* title) +{ + if (btn == nullptr) { + return; + } + container_->Add(btn); + btn->SetPosition(positionX_, positionY_, BUTTON_WIDHT2, BUTTON_HEIGHT2); + positionY_ += btn->GetHeight() + 10; // 10: increase height + btn->SetText(title); + btn->SetFont(DEFAULT_VECTOR_FONT_FILENAME, BUTTON_LABEL_SIZE); + btn->SetOnClickListener(this); + btn->SetStyleForState(STYLE_BORDER_RADIUS, BUTTON_STYLE_BORDER_RADIUS_VALUE, UIButton::RELEASED); + btn->SetStyleForState(STYLE_BORDER_RADIUS, BUTTON_STYLE_BORDER_RADIUS_VALUE, UIButton::PRESSED); + btn->SetStyleForState(STYLE_BORDER_RADIUS, BUTTON_STYLE_BORDER_RADIUS_VALUE, UIButton::INACTIVE); + btn->SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::RELEASED); + btn->SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::PRESSED); + btn->SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::INACTIVE); + container_->Invalidate(); +} + +void UITestChartPolyline::SetLastPos(UIView* view) +{ + if (view == nullptr) { + return; + } + lastX_ = view->GetX(); + lastY_ = view->GetY() + view->GetHeight(); +} +} // namespace OHOS \ No newline at end of file diff --git a/test/uitest/test_chart/ui_test_chart_polyline.h b/test/uitest/test_chart/ui_test_chart_polyline.h new file mode 100755 index 0000000..640499f --- /dev/null +++ b/test/uitest/test_chart/ui_test_chart_polyline.h @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef UI_TEST_CHART_POLYLINE_H +#define UI_TEST_CHART_POLYLINE_H + +#include "components/ui_chart.h" +#include "components/ui_label.h" +#include "components/ui_label_button.h" +#include "components/ui_scroll_view.h" +#include "ui_test.h" + +namespace OHOS { +class UITestChartPolyline : public UITest, public UIView::OnClickListener { +public: + UITestChartPolyline() {} + ~UITestChartPolyline() {} + void SetUp() override; + void TearDown() override; + UIView* GetTestView() override; + + void SetUpButton(UILabelButton* btn, const char* title); + + bool OnClick(UIView& view, const ClickEvent& event) override; + bool ClickExpand(UIView& view, UIChartDataSerial::PointStyle pointStyle); + + void UIKit_ChartPolyline_Test_AddDataSerial_001(); + void UIKit_ChartPolyline_Test_EnableReverse_002(); + void UIKit_ChartPolyline_Test_SetGradientBottom_003(); + void UIKit_ChartPolyline_Test_AddPoints_004(); + +private: + void SetLastPos(UIView* view); + void InnerDeleteChildren(UIView* view) const; + static constexpr uint8_t DATA_NUM = 3; + UIScrollView* container_ = nullptr; + UIChartPolyline* chart_ = nullptr; + UIChartDataSerial *dataSerial_[DATA_NUM] = {0}; + uint8_t curDataIndex_ = 0; + + UIChartPolyline* ECGChart_ = nullptr; + UIChartDataSerial* ECGDataSerial_ = nullptr; + Animator* ECGAnimator_ = nullptr; + + UILabelButton* addDataSerialBtn_ = nullptr; + UILabelButton* deleteDataSerialBtn_ = nullptr; + UILabelButton* clearDataSerialBtn_ = nullptr; + UILabelButton* reverseBtn_ = nullptr; + UILabelButton* topPointBtn_ = nullptr; + UILabelButton* bottomPointBtn_ = nullptr; + UILabelButton* headPointBtn_ = nullptr; + UILabelButton* gradientBottomBtn_ = nullptr; + + UILabelButton* addPointsBtn_ = nullptr; + UILabelButton* smoothBtn_ = nullptr; + UILabelButton* hidePointsBtn_ = nullptr; + + int16_t lastX_ = 0; + int16_t lastY_ = 0; +}; +} // namespace OHOS +#endif // UI_TEST_CHART_POLYLINE_H diff --git a/test/uitest/test_dialog/ui_test_dialog.cpp b/test/uitest/test_dialog/ui_test_dialog.cpp new file mode 100755 index 0000000..c6f222b --- /dev/null +++ b/test/uitest/test_dialog/ui_test_dialog.cpp @@ -0,0 +1,493 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ui_test_dialog.h" +#include "common/screen.h" +#include "components/ui_dialog.h" +#include "components/ui_label.h" +#include "graphic_config.h" +#include "test_resource_config.h" + +#if ENABLE_WINDOW +namespace OHOS { +namespace { +const int16_t GAP = 5; +const int16_t TITLE_HEIGHT = 20; +const uint16_t LABEL_WIDTH = 350; +const uint16_t LABEL_HEIGHT = 50; +const uint16_t BUTTON_WIDTH = 100; +const uint16_t BUTTON_HEIGHT = 50; +} // namespace + +class TestUIDialogButtonListener : public UIView::OnClickListener { +public: + TestUIDialogButtonListener(UIDialog::DialogButtonType buttonType, UILabel* label) + { + dialog_ = nullptr; + buttonType_ = buttonType; + label_ = label; + } + virtual ~TestUIDialogButtonListener() {} + + void SetDialog(UIDialog** dialog) + { + dialog_ = dialog; + } + + bool OnClick(UIView &view, const ClickEvent& event) override + { + switch (buttonType_) { + case UIDialog::DialogButtonType::BUTTON_LEFT: + label_->SetText("button left click!"); + break; + case UIDialog::DialogButtonType::BUTTON_MID: + label_->SetText("button mid click!"); + break; + case UIDialog::DialogButtonType::BUTTON_RIGHT: + label_->SetText("button right click!"); + break; + default: + break; + } + if (*dialog_ != nullptr) { + delete *dialog_; + *dialog_ = nullptr; + } + return true; + } + +private: + UIDialog** dialog_; + UIDialog::DialogButtonType buttonType_; + UILabel* label_; +}; + +class TestUIDialogOnCancelListener : public UIView::OnClickListener { +public: + TestUIDialogOnCancelListener(UILabel* label) + { + dialog_ = nullptr; + label_ = label; + } + virtual ~TestUIDialogOnCancelListener() {} + + void SetDialog(UIDialog** dialog) + { + dialog_ = dialog; + } + + bool OnClick(UIView &view, const ClickEvent& event) override + { + label_->SetText("Click outside the dialog."); + if (*dialog_ != nullptr) { + delete *dialog_; + *dialog_ = nullptr; + } + return true; + } + +private: + UIDialog** dialog_; + UILabel* label_; +}; + +void UITestDialog::SetUp() +{ + if (container_ == nullptr) { + container_ = new UIScrollView(); + container_->SetThrowDrag(true); + container_->SetHorizontalScrollState(false); + container_->Resize(Screen::GetInstance().GetWidth(), Screen::GetInstance().GetHeight() - LABEL_HEIGHT); + positionX_ = 50; // 50: init position x0 + positionY_ = 5; // 5: init position y + } + + if (label_ == nullptr) { + label_ = new UILabel(); + label_->SetPosition(0, positionY_); + label_->Resize(LABEL_WIDTH, LABEL_HEIGHT); + label_->SetFont(DEFAULT_VECTOR_FONT_FILENAME, 18); // 18: font size + label_->SetText("label"); + container_->Add(label_); + positionY_ += LABEL_HEIGHT + GAP; + } + if (listener1_ == nullptr) { + listener1_ = new TestUIDialogButtonListener(UIDialog::DialogButtonType::BUTTON_LEFT, label_); + } + if (listener2_ == nullptr) { + listener2_ = new TestUIDialogButtonListener(UIDialog::DialogButtonType::BUTTON_MID, label_); + } + if (listener3_ == nullptr) { + listener3_ = new TestUIDialogButtonListener(UIDialog::DialogButtonType::BUTTON_RIGHT, label_); + } + if (listener_ == nullptr) { + listener_ = new TestUIDialogOnCancelListener(label_); + } +} + +void UITestDialog::TearDown() +{ + DeleteChildren(container_); + if (listener1_ != nullptr) { + delete listener1_; + listener1_ = nullptr; + } + if (listener2_ != nullptr) { + delete listener2_; + listener2_ = nullptr; + } + if (listener3_ != nullptr) { + delete listener3_; + listener3_ = nullptr; + } + if (listener_ != nullptr) { + delete listener_; + listener_ = nullptr; + } + if (dialog_ != nullptr) { + delete dialog_; + dialog_ = nullptr; + } + label_ = nullptr; + container_ = nullptr; +} + +void UITestDialog::InnerTestTitle(const char* title) +{ + UILabel* titleLabel = new UILabel(); + titleLabel->SetPosition(0, positionY_, Screen::GetInstance().GetWidth(), TITLE_HEIGHT); + titleLabel->SetStyle(STYLE_TEXT_COLOR, Color::Black().full); + titleLabel->SetStyle(STYLE_BACKGROUND_COLOR, Color::White().full); + titleLabel->SetStyle(STYLE_BACKGROUND_OPA, OPA_OPAQUE); + titleLabel->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + titleLabel->SetText(title); + + container_->Add(titleLabel); + positionY_ += TITLE_HEIGHT + GAP; +} + +UIView* UITestDialog::GetTestView() +{ + UIKitDialogTest001(); + UIKitDialogTest002(); + UIKitDialogTest003(); + UIKitDialogTest004(); + UIKitDialogTest005(); + UIKitDialogTest006(); + UIKitDialogTest007(); + UIKitDialogTest008(); + UIKitDialogTest009(); + UIKitDialogTest010(); + return container_; +} + +void UITestDialog::UIKitDialogTest001() +{ + if (container_ == nullptr) { + return; + } + InnerTestTitle("测试设置较长标题、较短正文、单个较短按钮"); + button1_ = new UILabelButton(); + button1_->SetPosition(0, positionY_, BUTTON_WIDTH, BUTTON_HEIGHT); + button1_->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + button1_->SetText("Dialog1"); + button1_->SetOnClickListener(this); + container_->Add(button1_); + positionY_ += BUTTON_HEIGHT + GAP; +} + +void UITestDialog::UIKitDialogTest002() +{ + if (container_ == nullptr) { + return; + } + InnerTestTitle("测试设置较短标题、较长正文、两个较短按钮"); + button2_ = new UILabelButton(); + button2_->SetPosition(0, positionY_, BUTTON_WIDTH, BUTTON_HEIGHT); + button2_->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + button2_->SetText("Dialog2"); + button2_->SetOnClickListener(this); + container_->Add(button2_); + positionY_ += BUTTON_HEIGHT + GAP; +} + +void UITestDialog::UIKitDialogTest003() +{ + if (container_ == nullptr) { + return; + } + InnerTestTitle("测试设置较短标题、较短正文、单个较长按钮"); + button3_ = new UILabelButton(); + button3_->SetPosition(0, positionY_, BUTTON_WIDTH, BUTTON_HEIGHT); + button3_->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + button3_->SetText("Dialog3"); + button3_->SetOnClickListener(this); + container_->Add(button3_); + positionY_ += BUTTON_HEIGHT + GAP; +} + +void UITestDialog::UIKitDialogTest004() +{ + if (container_ == nullptr) { + return; + } + InnerTestTitle("测试设置较短标题、较短正文、三个长按钮"); + button4_ = new UILabelButton(); + button4_->SetPosition(0, positionY_, BUTTON_WIDTH, BUTTON_HEIGHT); + button4_->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + button4_->SetText("Dialog4"); + button4_->SetOnClickListener(this); + container_->Add(button4_); + positionY_ += BUTTON_HEIGHT + GAP; +} + +void UITestDialog::UIKitDialogTest005() +{ + if (container_ == nullptr) { + return; + } + InnerTestTitle("测试设置较短正文、自动关闭、弹框外点击事件监听"); + button5_ = new UILabelButton(); + button5_->SetPosition(0, positionY_, BUTTON_WIDTH, BUTTON_HEIGHT); + button5_->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + button5_->SetText("Dialog5"); + button5_->SetOnClickListener(this); + container_->Add(button5_); + positionY_ += BUTTON_HEIGHT + GAP; +} + +void UITestDialog::UIKitDialogTest006() +{ + if (container_ == nullptr) { + return; + } + InnerTestTitle("测试只设置较长正文,点击弹框外自动关闭"); + button6_ = new UILabelButton(); + button6_->SetPosition(0, positionY_, BUTTON_WIDTH, BUTTON_HEIGHT); + button6_->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + button6_->SetText("Dialog6"); + button6_->SetOnClickListener(this); + container_->Add(button6_); + positionY_ += BUTTON_HEIGHT + GAP; +} + +void UITestDialog::UIKitDialogTest007() +{ + if (container_ == nullptr) { + return; + } + InnerTestTitle("测试设置长标题,长正文,三个长按钮"); + button7_ = new UILabelButton(); + button7_->SetPosition(0, positionY_, BUTTON_WIDTH, BUTTON_HEIGHT); + button7_->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + button7_->SetText("Dialog7"); + button7_->SetOnClickListener(this); + container_->Add(button7_); + positionY_ += BUTTON_HEIGHT + GAP; +} + +void UITestDialog::UIKitDialogTest008() +{ + if (container_ == nullptr) { + return; + } + InnerTestTitle("测试设置按钮颜色不带透明度"); + button8_ = new UILabelButton(); + button8_->SetPosition(0, positionY_, BUTTON_WIDTH, BUTTON_HEIGHT); + button8_->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + button8_->SetText("Dialog8"); + button8_->SetOnClickListener(this); + container_->Add(button8_); + positionY_ += BUTTON_HEIGHT + GAP; +} + +void UITestDialog::UIKitDialogTest009() +{ + if (container_ == nullptr) { + return; + } + InnerTestTitle("测试设置按钮颜色带透明度"); + button9_ = new UILabelButton(); + button9_->SetPosition(0, positionY_, BUTTON_WIDTH, BUTTON_HEIGHT); + button9_->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + button9_->SetText("Dialog9"); + button9_->SetOnClickListener(this); + container_->Add(button9_); + positionY_ += BUTTON_HEIGHT + GAP; +} + +void UITestDialog::UIKitDialogTest010() +{ + if (container_ == nullptr) { + return; + } + InnerTestTitle("测试连续多次设置标题、正文、按钮"); + button10_ = new UILabelButton(); + button10_->SetPosition(0, positionY_, BUTTON_WIDTH, BUTTON_HEIGHT); + button10_->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + button10_->SetText("Dialog10"); + button10_->SetOnClickListener(this); + container_->Add(button10_); + positionY_ += BUTTON_HEIGHT + GAP; +} + +bool UITestDialog::OnClick(UIView &view, const ClickEvent& event) +{ + if (&view == button1_) { + if (dialog_ != nullptr) { + delete dialog_; + dialog_ = nullptr; + } + dialog_ = new UIDialog(); + dialog_->SetTitle("标题标题标题标题标题"); + dialog_->SetText("段落文本"); + listener1_->SetDialog(&dialog_); + dialog_->SetButton(UIDialog::DialogButtonType::BUTTON_LEFT, "Button1", listener1_); + dialog_->Show(); + } else if (&view == button2_) { + if (dialog_ != nullptr) { + delete dialog_; + dialog_ = nullptr; + } + dialog_ = new UIDialog(); + dialog_->SetTitle("标题"); + dialog_->SetText("段落文本段落文本段落文本段落文本段落文本段落文本段落文本"); + listener1_->SetDialog(&dialog_); + listener3_->SetDialog(&dialog_); + dialog_->SetButton(UIDialog::DialogButtonType::BUTTON_LEFT, "Button1", listener1_); + dialog_->SetButton(UIDialog::DialogButtonType::BUTTON_RIGHT, "Button2", listener3_); + dialog_->Show(); + } else if (&view == button3_) { + if (dialog_ != nullptr) { + delete dialog_; + dialog_ = nullptr; + } + dialog_ = new UIDialog(); + dialog_->SetTitle("标题"); + dialog_->SetText("段落文本"); + listener2_->SetDialog(&dialog_); + dialog_->SetButton(UIDialog::DialogButtonType::BUTTON_MID, "Button1Button1Button1Button1", listener2_); + dialog_->Show(); + } else if (&view == button4_) { + if (dialog_ != nullptr) { + delete dialog_; + dialog_ = nullptr; + } + dialog_ = new UIDialog(); + dialog_->SetTitle("标题"); + dialog_->SetText("段落文本"); + listener1_->SetDialog(&dialog_); + listener2_->SetDialog(&dialog_); + listener3_->SetDialog(&dialog_); + dialog_->SetButton(UIDialog::DialogButtonType::BUTTON_LEFT, "Button111111111111111111", listener1_); + dialog_->SetButton(UIDialog::DialogButtonType::BUTTON_MID, "Button22222222222222222", listener2_); + dialog_->SetButton(UIDialog::DialogButtonType::BUTTON_RIGHT, "Button33333333333333333", listener3_); + dialog_->Show(); + } else if (&view == button5_) { + if (dialog_ != nullptr) { + delete dialog_; + dialog_ = nullptr; + } + dialog_ = new UIDialog(); + dialog_->SetText("段落文本"); + dialog_->EnableAutoCancel(true); + listener_->SetDialog(&dialog_); + dialog_->SetOnCancelListener(listener_); + dialog_->Show(); + } else { + return ClickExpand(view, event); + } + return true; +} + +bool UITestDialog::ClickExpand(UIView &view, const ClickEvent& event) +{ + if (&view == button6_) { + if (dialog_ != nullptr) { + delete dialog_; + dialog_ = nullptr; + } + dialog_ = new UIDialog(); + dialog_->SetText("段落文本段落文本段落文本段落文本段落文本段落文本段落文本段落文本段落文本段落文本"); + dialog_->EnableAutoCancel(true); + listener_->SetDialog(&dialog_); + dialog_->SetOnCancelListener(listener_); + dialog_->Show(); + } else if (&view == button7_) { + if (dialog_ != nullptr) { + delete dialog_; + dialog_ = nullptr; + } + dialog_ = new UIDialog(); + dialog_->SetTitle("段落文本段落文本段落文本段落文本段落文本段落文本段落文本段落文本段落文本段落文本段落文本"); + dialog_->SetText( + "段落文本段落文本段落文本段落文本段落文本段落文本段落文本段落文本段落文本段落文本 \ + 段落文本段落文本段落文本段落文本段落文本段落文本段落文本段落文本段落文本段落文本段落文本段落文本段落文本 \ + 段落文本段落文本段落文本段落文本段落文本段落文本段落文本段落文本段落文本段落文本段落文本 \ + 段落文本段落文本段落文本段落文本段落文本段落文本段落文本段落文本段落文本段落文本段落文本 \ + 段落文本段落文本段落文本段落文本段落文本段落文本段落文本段落文本段落文本段落文本段落文本 \ + 段落文本段落文本段落文本段落文本段落文本段落文本段落文本段落文本段落文本段落文本段落文本段落文本段落文本段落文本段落文本 \ + 段落文本段落文本段落文本段落文本段落文本段落文本段落文本段落文本段落文本段落文本段落文本段落文本段落文本段落文本段落文 \ + 段落文本段落文本段落文本段落文本段落文本段落文本段落文本段落文本段落文本段落文本段落文本段落文本段落文本段落文本段落文"); + listener1_->SetDialog(&dialog_); + listener2_->SetDialog(&dialog_); + listener3_->SetDialog(&dialog_); + dialog_->SetButton(UIDialog::DialogButtonType::BUTTON_LEFT, "Button111111111111111111111", listener1_); + dialog_->SetButton(UIDialog::DialogButtonType::BUTTON_MID, "Button22222222222222222222", listener2_); + dialog_->SetButton(UIDialog::DialogButtonType::BUTTON_RIGHT, "Button33333333333333333333", listener3_); + dialog_->Show(); + } else if (&view == button8_) { + if (dialog_ != nullptr) { + delete dialog_; + dialog_ = nullptr; + } + dialog_ = new UIDialog(); + dialog_->SetText("段落正文"); + listener2_->SetDialog(&dialog_); + dialog_->SetButton(UIDialog::DialogButtonType::BUTTON_MID, "button", listener2_); + dialog_->SetButtonColor(UIDialog::DialogButtonType::BUTTON_MID, Color::Red()); + dialog_->Show(); + } else if (&view == button9_) { + if (dialog_ != nullptr) { + delete dialog_; + dialog_ = nullptr; + } + dialog_ = new UIDialog(); + dialog_->SetText("段落正文"); + listener2_->SetDialog(&dialog_); + dialog_->SetButton(UIDialog::DialogButtonType::BUTTON_MID, "button", listener2_); + // 0xFF, 0x00, 0x00, 0x7F: color red with 50% opacity + ColorType color = Color::GetColorFromRGBA(0xFF, 0x00, 0x00, 0x7F); + dialog_->SetButtonColor(UIDialog::DialogButtonType::BUTTON_MID, color); + dialog_->Show(); + } else if (&view == button10_) { + if (dialog_ != nullptr) { + delete dialog_; + dialog_ = nullptr; + } + dialog_ = new UIDialog(); + dialog_->SetTitle("标题1"); + dialog_->SetText("段落正文1"); + listener2_->SetDialog(&dialog_); + dialog_->SetButton(UIDialog::DialogButtonType::BUTTON_MID, "按钮1", listener2_); + dialog_->SetTitle("标题2"); + dialog_->SetText("段落正文2"); + dialog_->SetButton(UIDialog::DialogButtonType::BUTTON_MID, "按钮2", listener2_); + dialog_->Show(); + } + return true; +} +} // namespace OHOS +#endif // ENABLE_WINDOW \ No newline at end of file diff --git a/test/uitest/test_dialog/ui_test_dialog.h b/test/uitest/test_dialog/ui_test_dialog.h new file mode 100755 index 0000000..19f6c83 --- /dev/null +++ b/test/uitest/test_dialog/ui_test_dialog.h @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef UI_TEST_DIALOG_H +#define UI_TEST_DIALOG_H + +#include "components/ui_dialog.h" +#include "components/ui_scroll_view.h" +#include "graphic_config.h" +#include "ui_test.h" + +#if ENABLE_WINDOW +namespace OHOS { +class TestUIDialogButtonListener; +class TestUIDialogOnCancelListener; + +class UITestDialog : public UITest, public UIView::OnClickListener { +public: + UITestDialog() {} + ~UITestDialog() {} + void SetUp() override; + void TearDown() override; + void InnerTestTitle(const char* title); + UIView* GetTestView() override; + + bool OnClick(UIView &view, const ClickEvent& event) override; + bool ClickExpand(UIView &view, const ClickEvent& event); + + void UIKitDialogTest001(); + void UIKitDialogTest002(); + void UIKitDialogTest003(); + void UIKitDialogTest004(); + void UIKitDialogTest005(); + void UIKitDialogTest006(); + void UIKitDialogTest007(); + void UIKitDialogTest008(); + void UIKitDialogTest009(); + void UIKitDialogTest010(); + +private: + UIScrollView* container_ = nullptr; + UILabel* label_ = nullptr; + UILabelButton* button1_ = nullptr; + UILabelButton* button2_ = nullptr; + UILabelButton* button3_ = nullptr; + UILabelButton* button4_ = nullptr; + UILabelButton* button5_ = nullptr; + UILabelButton* button6_ = nullptr; + UILabelButton* button7_ = nullptr; + UILabelButton* button8_ = nullptr; + UILabelButton* button9_ = nullptr; + UILabelButton* button10_ = nullptr; + UIDialog* dialog_ = nullptr; + TestUIDialogButtonListener* listener1_ = nullptr; + TestUIDialogButtonListener* listener2_ = nullptr; + TestUIDialogButtonListener* listener3_ = nullptr; + TestUIDialogOnCancelListener* listener_ = nullptr; +}; +} // namespace OHOS +#endif // UI_TEST_DIALOG_H +#endif // ENABLE_WINDOW \ No newline at end of file diff --git a/test/uitest/test_digital_clock/ui_test_digital_clock.cpp b/test/uitest/test_digital_clock/ui_test_digital_clock.cpp new file mode 100755 index 0000000..16ac898 --- /dev/null +++ b/test/uitest/test_digital_clock/ui_test_digital_clock.cpp @@ -0,0 +1,214 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ui_test_digital_clock.h" +#include "common/screen.h" +#include "components/ui_digital_clock.h" +#include "components/ui_label.h" +#include "font/ui_font.h" + +namespace OHOS { +namespace { +const int16_t GAP = 10; +const int16_t TITLE_HEIGHT = 30; +const uint16_t DIGITAL_CLOCK_WIDTH = 350; +const uint16_t DIGITAL_CLOCK_HEIGHT = 50; +} // namespace + +void UITestDigitalClock::SetUp() +{ + if (container_ == nullptr) { + container_ = new UIScrollView(); + container_->SetThrowDrag(true); + container_->SetHorizontalScrollState(false); + container_->Resize(Screen::GetInstance().GetWidth(), Screen::GetInstance().GetHeight() - BACK_BUTTON_HEIGHT); + positionX_ = 50; // 50: init position x + positionY_ = 5; // 5: init position y + } +} + +void UITestDigitalClock::TearDown() +{ + UIView* view = static_cast(container_)->GetChildrenHead(); + while (view != nullptr) { + UIView* tempView = view; + view = view->GetNextSibling(); + if (tempView->GetParent()) { + static_cast(tempView->GetParent())->Remove(tempView); + } + delete tempView; + } + delete container_; + container_ = nullptr; +} + +void UITestDigitalClock::InnerTestTitle(const char* title) +{ + UILabel* titleLabel = new UILabel(); + titleLabel->SetPosition(TEXT_DISTANCE_TO_LEFT_SIDE, positionY_, Screen::GetInstance().GetWidth(), + TITLE_LABEL_DEFAULT_HEIGHT); + titleLabel->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + titleLabel->SetText(title); + container_->Add(titleLabel); + positionY_ += TITLE_HEIGHT + GAP; +} + +UIView* UITestDigitalClock::GetTestView() +{ + UIKit_UIDigitalClock_Test_Display_001(); + UIKit_UIDigitalClock_Test_Display_002(); + UIKit_UIDigitalClock_Test_SetColor(); + UIKit_UIDigitalClock_Test_DisplayMode_001(); + UIKit_UIDigitalClock_Test_DisplayMode_002(); + UIKit_UIDigitalClock_Test_DisplayMode_003(); + UIKit_UIDigitalClock_Test_DisplayMode_004(); + UIKit_UIDigitalClock_Test_DisplayLeadingZero(); + UIKit_UIDigitalClock_Test_SetOpacity(); + return container_; +} + +void UITestDigitalClock::UIKit_UIDigitalClock_Test_Display_001() +{ + if (container_ != nullptr) { + InnerTestTitle("UIDigitalClock效果"); + UIDigitalClock* dClock = new UIDigitalClock(); + dClock->SetPosition(positionX_, positionY_); + dClock->Resize(DIGITAL_CLOCK_WIDTH, DIGITAL_CLOCK_HEIGHT); + dClock->SetFont(DEFAULT_VECTOR_FONT_FILENAME, 30); // 30: font size + dClock->SetTime24Hour(7, 25, 50); // 7: hour, 25: minute, 50: second + container_->Add(dClock); + positionY_ += DIGITAL_CLOCK_HEIGHT + GAP; + } +} + +void UITestDigitalClock::UIKit_UIDigitalClock_Test_Display_002() +{ + if (container_ != nullptr) { + InnerTestTitle("UIDigitalClock效果"); + UIDigitalClock* dClock = new UIDigitalClock(); + dClock->SetPosition(positionX_, positionY_); + dClock->Resize(DIGITAL_CLOCK_WIDTH, DIGITAL_CLOCK_HEIGHT); + dClock->SetFont(DEFAULT_VECTOR_FONT_FILENAME, 30); // 30: font size + dClock->SetTime24Hour(7, 25, 50); // 7:hour, 25:minute, 50:second + container_->Add(dClock); + positionY_ += DIGITAL_CLOCK_HEIGHT + GAP; + } +} + +void UITestDigitalClock::UIKit_UIDigitalClock_Test_SetColor() +{ + if (container_ != nullptr) { + InnerTestTitle("设置Digital Clock颜色"); + UIDigitalClock* dClock = new UIDigitalClock(); + dClock->SetPosition(positionX_, positionY_); + dClock->Resize(DIGITAL_CLOCK_WIDTH, DIGITAL_CLOCK_HEIGHT); + dClock->SetFont(DEFAULT_VECTOR_FONT_FILENAME, 30); // 30: font size + dClock->SetTime24Hour(7, 25, 50); // 7:hour, 25:minute, 50:second + dClock->SetColor(Color::Red()); + container_->Add(dClock); + positionY_ += DIGITAL_CLOCK_HEIGHT + GAP; + } +} + +void UITestDigitalClock::UIKit_UIDigitalClock_Test_DisplayMode_001() +{ + if (container_ != nullptr) { + InnerTestTitle("设置Digital Clock仅显示小时、分钟 "); + UIDigitalClock* dClock = new UIDigitalClock(); + dClock->SetPosition(positionX_, positionY_); + dClock->Resize(DIGITAL_CLOCK_WIDTH, DIGITAL_CLOCK_HEIGHT); + dClock->SetFont(DEFAULT_VECTOR_FONT_FILENAME, 30); // 30: font size + dClock->SetTime24Hour(17, 25, 50); // 17:hour, 25:minute, 50:second + dClock->SetDisplayMode(UIDigitalClock::DISPLAY_12_HOUR_NO_SECONDS); + container_->Add(dClock); + positionY_ += DIGITAL_CLOCK_HEIGHT + GAP; + } +} + +void UITestDigitalClock::UIKit_UIDigitalClock_Test_DisplayMode_002() +{ + if (container_ != nullptr) { + InnerTestTitle("设置Digital Clock 24小时制仅显示小时、分钟 "); + UIDigitalClock* dClock = new UIDigitalClock(); + dClock->SetPosition(positionX_, positionY_); + dClock->Resize(DIGITAL_CLOCK_WIDTH, DIGITAL_CLOCK_HEIGHT); + dClock->SetFont(DEFAULT_VECTOR_FONT_FILENAME, 30); // 30: font size + dClock->SetTime24Hour(17, 25, 50); // 17:hour, 25:minute, 50:second + dClock->SetDisplayMode(UIDigitalClock::DISPLAY_24_HOUR_NO_SECONDS); + container_->Add(dClock); + positionY_ += DIGITAL_CLOCK_HEIGHT + GAP; + } +} + +void UITestDigitalClock::UIKit_UIDigitalClock_Test_DisplayMode_003() +{ + if (container_ != nullptr) { + InnerTestTitle("设置Digital Clock 12小时制 "); + UIDigitalClock* dClock = new UIDigitalClock(); + dClock->SetPosition(positionX_, positionY_); + dClock->Resize(DIGITAL_CLOCK_WIDTH, DIGITAL_CLOCK_HEIGHT); + dClock->SetFont(DEFAULT_VECTOR_FONT_FILENAME, 30); // 30: font size + dClock->SetTime24Hour(17, 25, 50); // 17:hour, 25:minute, 50:second + dClock->SetDisplayMode(UIDigitalClock::DISPLAY_12_HOUR); + container_->Add(dClock); + positionY_ += DIGITAL_CLOCK_HEIGHT + GAP; + } +} + +void UITestDigitalClock::UIKit_UIDigitalClock_Test_DisplayMode_004() +{ + if (container_ != nullptr) { + InnerTestTitle("设置Digital Clock 24小时制 "); + UIDigitalClock* dClock = new UIDigitalClock(); + dClock->SetPosition(positionX_, positionY_); + dClock->Resize(DIGITAL_CLOCK_WIDTH, DIGITAL_CLOCK_HEIGHT); + dClock->SetFont(DEFAULT_VECTOR_FONT_FILENAME, 30); // 30: font size + dClock->SetTime24Hour(17, 25, 50); // 17:hour, 25:minute, 50:second + dClock->SetDisplayMode(UIDigitalClock::DISPLAY_24_HOUR); + container_->Add(dClock); + positionY_ += DIGITAL_CLOCK_HEIGHT + GAP; + } +} + +void UITestDigitalClock::UIKit_UIDigitalClock_Test_DisplayLeadingZero() +{ + if (container_ != nullptr) { + InnerTestTitle("设置Digital Clock不显示首位的0 "); + UIDigitalClock* dClock = new UIDigitalClock(); + dClock->SetPosition(positionX_, positionY_); + dClock->Resize(DIGITAL_CLOCK_WIDTH, DIGITAL_CLOCK_HEIGHT); + dClock->SetFont(DEFAULT_VECTOR_FONT_FILENAME, 30); // 30: font size + dClock->SetTime24Hour(7, 5, 9); // 7:hour, 5:minute, 9:second + dClock->DisplayLeadingZero(false); + container_->Add(dClock); + positionY_ += DIGITAL_CLOCK_HEIGHT + GAP; + } +} + +void UITestDigitalClock::UIKit_UIDigitalClock_Test_SetOpacity() +{ + if (container_ != nullptr) { + InnerTestTitle("设置Digital Clock透明度 "); + UIDigitalClock* dClock = new UIDigitalClock(); + dClock->SetPosition(positionX_, positionY_); + dClock->Resize(DIGITAL_CLOCK_WIDTH, DIGITAL_CLOCK_HEIGHT); + dClock->SetFont(DEFAULT_VECTOR_FONT_FILENAME, 30); // 30: font size + dClock->SetTime24Hour(7, 5, 9); // 7:hour, 5:minute, 9:second + dClock->SetOpacity(178); // 178: num of opacity + container_->Add(dClock); + positionY_ += DIGITAL_CLOCK_HEIGHT + GAP; + } +} +} // namespace OHOS \ No newline at end of file diff --git a/test/uitest/test_digital_clock/ui_test_digital_clock.h b/test/uitest/test_digital_clock/ui_test_digital_clock.h new file mode 100755 index 0000000..72a1813 --- /dev/null +++ b/test/uitest/test_digital_clock/ui_test_digital_clock.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef UI_TEST_DIGITAL_CLOCK_H +#define UI_TEST_DIGITAL_CLOCK_H + +#include "components/ui_scroll_view.h" +#include "font/ui_font.h" +#include "ui_test.h" + +namespace OHOS { +class UITestDigitalClock : public UITest { +public: + UITestDigitalClock() {} + ~UITestDigitalClock() {} + void SetUp() override; + void TearDown() override; + UIView* GetTestView() override; + + void UIKit_UIDigitalClock_Test_Display_001(); + void UIKit_UIDigitalClock_Test_Display_002(); + void UIKit_UIDigitalClock_Test_SetColor(); + void UIKit_UIDigitalClock_Test_DisplayMode_001(); + void UIKit_UIDigitalClock_Test_DisplayMode_002(); + void UIKit_UIDigitalClock_Test_DisplayMode_003(); + void UIKit_UIDigitalClock_Test_DisplayMode_004(); + void UIKit_UIDigitalClock_Test_DisplayLeadingZero(); + void UIKit_UIDigitalClock_Test_SetOpacity(); + +private: + UIScrollView* container_ = nullptr; + void InnerTestTitle(const char* title); +}; +} // namespace OHOS +#endif // UI_TEST_DIGITAL_CLOCK_H diff --git a/test/uitest/test_draw_line/ui_test_draw_line.cpp b/test/uitest/test_draw_line/ui_test_draw_line.cpp new file mode 100755 index 0000000..aff37b8 --- /dev/null +++ b/test/uitest/test_draw_line/ui_test_draw_line.cpp @@ -0,0 +1,398 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ui_test_draw_line.h" +#include "common/screen.h" +#include "components/ui_canvas.h" +#include "components/ui_label.h" +#include "components/ui_label_button.h" + +namespace OHOS { +namespace { +const uint16_t DELTA_X = 24; +} + +void UITestDrawLine::SetUp() +{ + if (container_ == nullptr) { + container_ = new UIScrollView(); + container_->Resize(Screen::GetInstance().GetWidth(), Screen::GetInstance().GetHeight() - BACK_BUTTON_HEIGHT); + container_->SetHorizontalScrollState(false); + } +} + +void UITestDrawLine::TearDown() +{ + DeleteChildren(container_); + container_ = nullptr; +} + +UIView* UITestDrawLine::GetTestView() +{ + UIKit_Draw_Line_Test_Line_001(); + UIKit_Draw_Line_Test_Line_002(); + UIKit_Draw_Line_Test_Line_003(); + UIKit_Draw_Line_Test_Line_004(); + UIKit_Draw_Line_Test_Line_005(); + UIKit_Draw_Line_Test_Line_006(); + UIKit_Draw_Line_Test_Line_007(); + UIKit_Draw_Line_Test_Line_008(); + UIKit_Draw_Line_Test_Line_009(); + UIKit_Draw_Line_Test_Line_010(); + UIKit_Draw_Line_Test_Line_011(); + UIKit_Draw_Line_Test_Line_012(); + return container_; +} + +UIViewGroup* UITestDrawLine::CreateTestCaseGroup() const +{ + UIViewGroup* group = new UIViewGroup(); + // 288: group width; 250: group height + group->Resize(288, 250); + return group; +} + +UILabel* UITestDrawLine::CreateTitleLabel() const +{ + UILabel* label = new UILabel(); + // 2: half of screen width; + label->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, TEXT_DISTANCE_TO_TOP_SIDE, Screen::GetInstance().GetWidth() / 2, + TITLE_LABEL_DEFAULT_HEIGHT); + label->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + return label; +} + +UICanvas* UITestDrawLine::CreateCanvas() const +{ + UICanvas* canvas = new UICanvas(); + canvas->SetHeight(200); // 200 : height size + canvas->SetWidth(200); // 200 : width size + canvas->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, VIEW_DISTANCE_TO_TOP_SIDE); + canvas->SetStyle(STYLE_BACKGROUND_COLOR, Color::Gray().full); + return canvas; +} + +void UITestDrawLine::UIKit_Draw_Line_Test_Line_001() +{ + if (container_ == nullptr) { + return; + } + + UIViewGroup* group = CreateTestCaseGroup(); + group->SetViewId("UIKit_Draw_Line_Test_Line_001"); + group->SetPosition(0, 0); + + UILabel* label = CreateTitleLabel(); + group->Add(label); + label->SetText("水平直线绘制 "); + + UICanvas* canvas = CreateCanvas(); + Paint paint; + canvas->DrawLine({50, 50}, {150, 50}, paint); // 50 : start x 50 : start y 150 : end x 50 : end y + group->Add(canvas); + + container_->Add(group); +} + +void UITestDrawLine::UIKit_Draw_Line_Test_Line_002() +{ + if (container_ == nullptr) { + return; + } + + UIViewGroup* group = CreateTestCaseGroup(); + group->SetViewId("UIKit_Draw_Line_Test_Line_002"); + group->SetPosition(0, 0); + + UILabel* label = CreateTitleLabel(); + group->Add(label); + label->SetText("垂直直线绘制 "); + + UICanvas* canvas = CreateCanvas(); + Paint paint; + canvas->DrawLine({100, 50}, {100, 150}, paint); // 100 : start x 50 : start y 100 : end x 150 : end y + group->Add(canvas); + + container_->Add(group); + group->LayoutRightToSibling("UIKit_Draw_Line_Test_Line_001", DELTA_X); +} + +void UITestDrawLine::UIKit_Draw_Line_Test_Line_003() +{ + if (container_ == nullptr) { + return; + } + + UIViewGroup* group = CreateTestCaseGroup(); + group->SetViewId("UIKit_Draw_Line_Test_Line_003"); + group->SetPosition(0, 0); + + UILabel* label = CreateTitleLabel(); + group->Add(label); + label->SetText("左下-右上方向直线绘制 "); + + UICanvas* canvas = CreateCanvas(); + Paint paint; + canvas->DrawLine({20, 180}, {160, 20}, paint); // 20 : start x 180 : start y 160 : end x 20 : end y + canvas->DrawLine({20, 180}, {180, 20}, paint); // 20 : start x 180 : start y 180 : end x 20 : end y + canvas->DrawLine({20, 180}, {180, 40}, paint); // 20 : start x 180 : start y 180 : end x 40 : end y + group->Add(canvas); + + container_->Add(group); + group->LayoutRightToSibling("UIKit_Draw_Line_Test_Line_002", DELTA_X); +} + +void UITestDrawLine::UIKit_Draw_Line_Test_Line_004() +{ + if (container_ == nullptr) { + return; + } + + UIViewGroup* group = CreateTestCaseGroup(); + group->SetViewId("UIKit_Draw_Line_Test_Line_004"); + group->SetPosition(0, 0); + + UILabel* label = CreateTitleLabel(); + group->Add(label); + label->SetText("左上-右下方向直线绘制 "); + + UICanvas* canvas = CreateCanvas(); + Paint paint; + canvas->DrawLine({20, 20}, {160, 180}, paint); // 20 : start x 20 : start y 160 : end x 180 : end y + canvas->DrawLine({20, 20}, {180, 180}, paint); // 20 : start x 20 : start y 180 : end x 180 : end y + canvas->DrawLine({20, 20}, {180, 160}, paint); // 20 : start x 20 : start y 180 : end x 160 : end y + group->Add(canvas); + + container_->Add(group); + group->LayoutBottomToSibling("UIKit_Draw_Line_Test_Line_001", 10); // 10 : offset size +} + +void UITestDrawLine::UIKit_Draw_Line_Test_Line_005() +{ + if (container_ == nullptr) { + return; + } + + UIViewGroup* group = CreateTestCaseGroup(); + group->SetViewId("UIKit_Draw_Line_Test_Line_005"); + group->SetPosition(0, 0); + + UILabel* label = CreateTitleLabel(); + group->Add(label); + label->SetText("右上-左下方向直线绘制 "); + + UICanvas* canvas = CreateCanvas(); + Paint paint; + canvas->DrawLine({180, 20}, {20, 160}, paint); // 180 : start x 20 : start y 20 : end x 160 : end y + canvas->DrawLine({180, 20}, {20, 180}, paint); // 180 : start x 20 : start y 20 : end x 180 : end y + canvas->DrawLine({180, 20}, {40, 180}, paint); // 180 : start x 20 : start y 40 : end x 180 : end y + group->Add(canvas); + + container_->Add(group); + group->LayoutBottomToSibling("UIKit_Draw_Line_Test_Line_002", 10); // 10: offset + group->LayoutRightToSibling("UIKit_Draw_Line_Test_Line_004", DELTA_X); +} + +void UITestDrawLine::UIKit_Draw_Line_Test_Line_006() +{ + if (container_ == nullptr) { + return; + } + + UIViewGroup* group = CreateTestCaseGroup(); + group->SetViewId("UIKit_Draw_Line_Test_Line_006"); + group->SetPosition(0, 0); + + UILabel* label = CreateTitleLabel(); + group->Add(label); + label->SetText("左上-右下方向直线绘制 "); + + UICanvas* canvas = CreateCanvas(); + Paint paint; + canvas->DrawLine({180, 180}, {20, 40}, paint); // 180 : start x 180 : start y 20 : end x 40 : end y + canvas->DrawLine({180, 180}, {20, 20}, paint); // 180 : start x 180 : start y 20 : end x 20 : end y + canvas->DrawLine({180, 180}, {40, 20}, paint); // 180 : start x 180 : start y 40 : end x 20 : end y + group->Add(canvas); + + container_->Add(group); + group->LayoutBottomToSibling("UIKit_Draw_Line_Test_Line_003", 10); // 10: offset + group->LayoutRightToSibling("UIKit_Draw_Line_Test_Line_005", DELTA_X); +} + +void UITestDrawLine::UIKit_Draw_Line_Test_Line_007() +{ + if (container_ == nullptr) { + return; + } + + UIViewGroup* group = CreateTestCaseGroup(); + group->SetViewId("UIKit_Draw_Line_Test_Line_007"); + group->SetPosition(0, 0); + + UILabel* label = CreateTitleLabel(); + group->Add(label); + label->SetText("宽度为零直线绘制 "); + + UICanvas* canvas = CreateCanvas(); + Paint paint; + paint.SetStrokeWidth(0); // 0 : stroke width + canvas->DrawLine({10, 190}, {190, 10}, paint); // 10 : start x 190 : start y 190 : end x 10 : end y + group->Add(canvas); + + container_->Add(group); + group->LayoutBottomToSibling("UIKit_Draw_Line_Test_Line_004", 10); // 10 : offset size +} + +void UITestDrawLine::UIKit_Draw_Line_Test_Line_008() +{ + if (container_ == nullptr) { + return; + } + + UIViewGroup* group = CreateTestCaseGroup(); + group->SetViewId("UIKit_Draw_Line_Test_Line_008"); + group->SetPosition(0, 0); + + UILabel* label = CreateTitleLabel(); + group->Add(label); + label->SetText("不同宽度的直线绘制 "); + + UICanvas* canvas = CreateCanvas(); + Paint paint; + paint.SetStrokeWidth(2); // 2 : stroke width + canvas->DrawLine({10, 190}, {190, 10}, paint); // 10 : start x 190 : start y 190 : end x 10 : end y + Paint paint2; + paint2.SetStrokeWidth(4); // 4 : stroke width + canvas->DrawLine({30, 190}, {190, 30}, paint2); // 30 : start x 190 : start y 190 : end x 30 : end y + Paint paint3; + paint3.SetStrokeWidth(5); // 5 : stroke width + canvas->DrawLine({50, 190}, {190, 50}, paint3); // 50 : start x 190 : start y 190 : end x 50 : end y + Paint paint4; + paint4.SetStrokeWidth(90); // 90 : stroke width + canvas->DrawLine({40, 90}, {90, 40}, paint4); // 40 : start x 90 : start y 90 : end x 40 : end y + group->Add(canvas); + container_->Add(group); + group->LayoutBottomToSibling("UIKit_Draw_Line_Test_Line_005", 10); // 10: offset + group->LayoutRightToSibling("UIKit_Draw_Line_Test_Line_007", DELTA_X); +} + +void UITestDrawLine::UIKit_Draw_Line_Test_Line_009() +{ + if (container_ == nullptr) { + return; + } + + UIViewGroup* group = CreateTestCaseGroup(); + group->SetViewId("UIKit_Draw_Line_Test_Line_009"); + group->SetPosition(0, 0); + + UILabel* label = CreateTitleLabel(); + group->Add(label); + label->SetText("全透明的直线绘制 "); + + UICanvas* canvas = CreateCanvas(); + Paint paint; + paint.SetOpacity(OPA_TRANSPARENT); + canvas->DrawLine({10, 190}, {190, 10}, paint); // 10 : start x 190 : start y 190 : end x 10 : end y + group->Add(canvas); + container_->Add(group); + group->LayoutBottomToSibling("UIKit_Draw_Line_Test_Line_006", 10); // 10: offset + group->LayoutRightToSibling("UIKit_Draw_Line_Test_Line_008", DELTA_X); +} + +void UITestDrawLine::UIKit_Draw_Line_Test_Line_010() +{ + if (container_ == nullptr) { + return; + } + + UIViewGroup* group = CreateTestCaseGroup(); + group->SetViewId("UIKit_Draw_Line_Test_Line_010"); + group->SetPosition(0, 0); + + UILabel* label = CreateTitleLabel(); + group->Add(label); + label->SetText("不透明的直线绘制 "); + + UICanvas* canvas = CreateCanvas(); + Paint paint; + paint.SetOpacity(OPA_OPAQUE); + canvas->DrawLine({10, 190}, {190, 10}, paint); // 10 : start x 190 : start y 190 : end x 10 : end y + group->Add(canvas); + + container_->Add(group); + group->LayoutBottomToSibling("UIKit_Draw_Line_Test_Line_007", 10); // 10 : offset size +} + +void UITestDrawLine::UIKit_Draw_Line_Test_Line_011() +{ + if (container_ == nullptr) { + return; + } + + UIViewGroup* group = CreateTestCaseGroup(); + group->SetViewId("UIKit_Draw_Line_Test_Line_011"); + group->SetPosition(0, 0); + + UILabel* label = CreateTitleLabel(); + group->Add(label); + label->SetText("不同透明度的直线绘制 "); + + UICanvas* canvas = CreateCanvas(); + Paint paint; + paint.SetOpacity(51); // 51 : opacity size + canvas->DrawLine({10, 190}, {190, 10}, paint); // 10 : start x 190 : start y 190 : end x 10 : end y + Paint paint2; + paint2.SetOpacity(127); // 127 : opacity size + canvas->DrawLine({30, 190}, {190, 30}, paint2); // 30 : start x 190 : start y 190 : end x 30 : end y + Paint paint3; + paint3.SetOpacity(178); // 178 : opacity size + canvas->DrawLine({50, 190}, {190, 50}, paint3); // 50 : start x 190 : start y 190 : end x 50 : end y + group->Add(canvas); + container_->Add(group); + group->LayoutBottomToSibling("UIKit_Draw_Line_Test_Line_008", 10); // 10 : offset size + group->LayoutRightToSibling("UIKit_Draw_Line_Test_Line_010", DELTA_X); +} + +void UITestDrawLine::UIKit_Draw_Line_Test_Line_012() +{ + if (container_ == nullptr) { + return; + } + + UIViewGroup* group = CreateTestCaseGroup(); + group->SetViewId("UIKit_Draw_Line_Test_Line_012"); + group->SetPosition(0, 0); + + UILabel* label = CreateTitleLabel(); + group->Add(label); + label->SetText("不同颜色的直线绘制 "); + + UICanvas* canvas = CreateCanvas(); + Paint paint; + paint.SetStrokeColor(Color::Red()); + canvas->DrawLine({10, 190}, {190, 10}, paint); // 10 : start x 190 : start y 190 : end x 10 : end y + Paint paint2; + paint2.SetStrokeColor(Color::Yellow()); + canvas->DrawLine({30, 190}, {190, 30}, paint2); // 30 : start x 190 : start y 190 : end x 30 : end y + Paint paint3; + paint3.SetStrokeColor(Color::Green()); + canvas->DrawLine({50, 190}, {190, 50}, paint3); // 50 : start x 190 : start y 190 : end x 50 : end y + group->Add(canvas); + container_->Add(group); + group->LayoutBottomToSibling("UIKit_Draw_Line_Test_Line_009", 10); // 10 : offset size + group->LayoutRightToSibling("UIKit_Draw_Line_Test_Line_011", DELTA_X); +} +} // namespace OHOS \ No newline at end of file diff --git a/test/uitest/test_draw_line/ui_test_draw_line.h b/test/uitest/test_draw_line/ui_test_draw_line.h new file mode 100755 index 0000000..10560ca --- /dev/null +++ b/test/uitest/test_draw_line/ui_test_draw_line.h @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef UI_TEST_DRAW_LINE_H +#define UI_TEST_DRAW_LINE_H + +#include "components/ui_canvas.h" +#include "components/ui_label.h" +#include "components/ui_scroll_view.h" +#include "components/ui_view_group.h" +#include "ui_test.h" + +namespace OHOS { +class UITestDrawLine : public UITest { +public: + UITestDrawLine() {} + ~UITestDrawLine() {} + void SetUp() override; + void TearDown() override; + UIView* GetTestView() override; + + /** + * @brief Test draw line Function + */ + void UIKit_Draw_Line_Test_Line_001(); + void UIKit_Draw_Line_Test_Line_002(); + void UIKit_Draw_Line_Test_Line_003(); + void UIKit_Draw_Line_Test_Line_004(); + void UIKit_Draw_Line_Test_Line_005(); + void UIKit_Draw_Line_Test_Line_006(); + void UIKit_Draw_Line_Test_Line_007(); + void UIKit_Draw_Line_Test_Line_008(); + void UIKit_Draw_Line_Test_Line_009(); + void UIKit_Draw_Line_Test_Line_010(); + void UIKit_Draw_Line_Test_Line_011(); + void UIKit_Draw_Line_Test_Line_012(); + +private: + UIViewGroup* CreateTestCaseGroup() const; + UILabel* CreateTitleLabel() const; + UICanvas* CreateCanvas() const; + UIScrollView* container_ = nullptr; +}; +} // namespace OHOS +#endif // UI_TEST_DRAW_LINE_H diff --git a/test/uitest/test_draw_rect/ui_test_draw_rect.cpp b/test/uitest/test_draw_rect/ui_test_draw_rect.cpp new file mode 100755 index 0000000..7731717 --- /dev/null +++ b/test/uitest/test_draw_rect/ui_test_draw_rect.cpp @@ -0,0 +1,1427 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ui_test_draw_rect.h" +#include "common/screen.h" +#include "components/ui_button.h" +#include "components/ui_label.h" +#include "components/ui_view_group.h" + +namespace OHOS { +void UITestDrawRect::SetUp() +{ + if (container_ == nullptr) { + container_ = new UIScrollView(); + container_->Resize(Screen::GetInstance().GetWidth(), Screen::GetInstance().GetHeight() - BACK_BUTTON_HEIGHT); + container_->SetHorizontalScrollState(false); + } +} + +void UITestDrawRect::TearDown() +{ + DeleteChildren(container_); + container_ = nullptr; +} + +UIView* UITestDrawRect::GetTestView() +{ + UIKit_DrawRect_Test_RectNoBorder_001(); + UIKit_DrawRect_Test_RectNoBorder_002(); + UIKit_DrawRect_Test_RectNoBorder_003(); + UIKit_DrawRect_Test_RectNoBorder_004(); + UIKit_DrawRect_Test_RectNoBorder_005(); + UIKit_DrawRect_Test_RectNoBorder_006(); + UIKit_DrawRect_Test_RectNoBorder_007(); + UIKit_DrawRect_Test_RectNoBorder_008(); + UIKit_DrawRect_Test_RectNoBorder_009(); + UIKit_DrawRect_Test_RectNoBorder_0010(); + UIKit_DrawRect_Test_RectBorderNoRadius_001(); + UIKit_DrawRect_Test_RectBorderNoRadius_002(); + UIKit_DrawRect_Test_RectBorderRadius_001(); + UIKit_DrawRect_Test_RectBorderRadius_002(); + UIKit_DrawRect_Test_RectBorderRadius_003(); + UIKit_DrawRect_Test_RectBorderRadius_004(); + UIKit_DrawRect_Test_RectBorderRadius_005(); + UIKit_DrawRect_Test_RectBorderRadius_006(); + UIKit_DrawRect_Test_RectBorderRadius_007(); + UIKit_DrawRect_Test_RectBorderRadius_008(); + UIKit_DrawRect_Test_RectBorderRadius_009(); + UIKit_DrawRect_Test_RectBorderRadius_010(); + UIKit_DrawRect_Test_RectBorderRadius_011(); + + UIKit_DrawRect_Test_Rect_001(); + UIKit_DrawRect_Test_RectColor_001(); + UIKit_DrawRect_Test_RectRadius_001(); + UIKit_DrawRect_Test_RectRadius_002(); + UIKit_DrawRect_Test_RectRadius_003(); + UIKit_DrawRect_Test_RectRadius_004(); + UIKit_DrawRect_Test_RectOpacity_001(); + UIKit_DrawRect_Test_RectOpacity_002(); + UIKit_DrawRect_Test_RectOpacity_003(); + UIKit_DrawRect_Test_RectBorderColor_001(); + UIKit_DrawRect_Test_RectBorderWidth_001(); + UIKit_DrawRect_Test_RectBorderWidth_002(); + UIKit_DrawRect_Test_RectBorderOpacity_001(); + UIKit_DrawRect_Test_RectBorderOpacity_002(); + UIKit_DrawRect_Test_RectBorderOpacity_003(); + UIKit_DrawRect_Test_RectPadding_001(); + UIKit_DrawRect_Test_RectPadding_002(); + UIKit_DrawRect_Test_RectMargin_001(); + UIKit_DrawRect_Test_RectMargin_002(); + UIKit_DrawRect_Test_RectPaddingMargin_001(); + UIKit_DrawRect_Test_RectPaddingMargin_002(); + UIKit_DrawRect_Test_RectWidthAndHeight_001(); + UIKit_DrawRect_Test_RectWidthAndHeight_002(); + UIKit_DrawRect_Test_RectWidthAndHeight_003(); + return container_; +} + +UIViewGroup* UITestDrawRect::CreateTestCaseGroup() const +{ + UIViewGroup* group = new UIViewGroup(); + group->Resize(Screen::GetInstance().GetWidth(), 180); // 180 : value + return group; +} + +UILabel* UITestDrawRect::CreateTitleLabel() const +{ + UILabel* label = new UILabel(); + label->SetPosition(TEXT_DISTANCE_TO_LEFT_SIDE, TEXT_DISTANCE_TO_TOP_SIDE, + Screen::GetInstance().GetWidth() - VIEW_DISTANCE_TO_LEFT_SIDE, TITLE_LABEL_DEFAULT_HEIGHT); + label->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + return label; +} + +UIView* UITestDrawRect::CreateUIView() const +{ + UIView* view = new UIView(); + view->SetStyle(STYLE_BACKGROUND_COLOR, Color::Green().full); + view->SetStyle(STYLE_BACKGROUND_OPA, 127); // 127 : background opacity + view->SetStyle(STYLE_BORDER_RADIUS, 5); // 5 : radius value + view->SetStyle(STYLE_BORDER_COLOR, Color::Gray().full); + view->SetStyle(STYLE_BORDER_OPA, 127); // 127 : border opacity + view->SetStyle(STYLE_BORDER_WIDTH, 20); // 20 : border width + view->SetStyle(STYLE_PADDING_LEFT, 0); + view->SetStyle(STYLE_PADDING_RIGHT, 0); + view->SetStyle(STYLE_PADDING_TOP, 0); + view->SetStyle(STYLE_PADDING_BOTTOM, 0); + view->SetStyle(STYLE_MARGIN_LEFT, 0); + view->SetStyle(STYLE_MARGIN_RIGHT, 0); + view->SetStyle(STYLE_MARGIN_TOP, 0); + view->SetStyle(STYLE_MARGIN_BOTTOM, 0); + return view; +} + +void UITestDrawRect::UIKit_DrawRect_Test_RectNoBorder_001() +{ + if (container_ == nullptr) { + return; + } + + UIViewGroup* group = CreateTestCaseGroup(); + group->SetViewId("UIKit_DrawRect_Test_RectNoBorder_001"); + group->SetPosition(0, 0); + + UILabel* label = CreateTitleLabel(); + label->SetText("矩形无border, radius=0"); + group->Add(label); + + UIView* view = CreateUIView(); + int16_t width = 120; // 120 : width + int16_t height = 50; // 50 : height + view->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, VIEW_DISTANCE_TO_TOP_SIDE); + view->SetStyle(STYLE_BORDER_WIDTH, 0); + view->SetStyle(STYLE_BORDER_RADIUS, 0); + view->Resize(width, height); + group->Add(view); + + container_->Add(group); +} + +void UITestDrawRect::UIKit_DrawRect_Test_RectNoBorder_002() +{ + if (container_ == nullptr) { + return; + } + + UIViewGroup* group = CreateTestCaseGroup(); + group->SetViewId("UIKit_DrawRect_Test_RectNoBorder_002"); + group->SetPosition(0, 0); + + UILabel* label = CreateTitleLabel(); + label->SetText("矩形无border, radius>0, width > height, radius > height/2"); + group->Add(label); + + UIView* view = CreateUIView(); + int16_t width = 120; // 120 : width + int16_t height = 50; // 50 : height + view->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, VIEW_DISTANCE_TO_TOP_SIDE); + view->SetStyle(STYLE_BORDER_WIDTH, 0); + view->SetStyle(STYLE_BORDER_RADIUS, height / 2 + 10); // 2 : half height 10 : offset + view->Resize(width, height); + group->Add(view); + + container_->Add(group); + group->LayoutBottomToSibling("UIKit_DrawRect_Test_RectNoBorder_001", 10); // 10 : offset size +} + +void UITestDrawRect::UIKit_DrawRect_Test_RectNoBorder_003() +{ + if (container_ == nullptr) { + return; + } + + UIViewGroup* group = CreateTestCaseGroup(); + group->SetViewId("UIKit_DrawRect_Test_RectNoBorder_003"); + group->SetPosition(0, 0); + + UILabel* label = CreateTitleLabel(); + label->SetText("矩形无border, radius>0, width > height, radius = height/2 "); + group->Add(label); + + UIView* view = CreateUIView(); + int16_t width = 120; // 120 : width size + int16_t height = 50; // 50 : height size + view->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, VIEW_DISTANCE_TO_TOP_SIDE); + view->SetStyle(STYLE_BORDER_WIDTH, 0); + view->SetStyle(STYLE_BORDER_RADIUS, height / 2); // 2 : half height + view->Resize(width, height); + group->Add(view); + + container_->Add(group); + group->LayoutBottomToSibling("UIKit_DrawRect_Test_RectNoBorder_002", 10); // 10 : offset size +} + +void UITestDrawRect::UIKit_DrawRect_Test_RectNoBorder_004() +{ + if (container_ == nullptr) { + return; + } + + UIViewGroup* group = CreateTestCaseGroup(); + group->SetViewId("UIKit_DrawRect_Test_RectNoBorder_004"); + group->SetPosition(0, 0); + + UILabel* label = CreateTitleLabel(); + label->SetText("矩形无border, radius>0, width > height, radius < height/2 "); + group->Add(label); + + UIView* view = CreateUIView(); + int16_t width = 120; // 120 : width size + int16_t height = 50; // 50 : height size + view->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, VIEW_DISTANCE_TO_TOP_SIDE); + view->SetStyle(STYLE_BORDER_WIDTH, 0); + view->SetStyle(STYLE_BORDER_RADIUS, height / 2 - 10); // 2 : half height 10 : offset + view->Resize(width, height); + group->Add(view); + + container_->Add(group); + group->LayoutBottomToSibling("UIKit_DrawRect_Test_RectNoBorder_003", 10); // 10 : offset size +} + +void UITestDrawRect::UIKit_DrawRect_Test_RectNoBorder_005() +{ + if (container_ == nullptr) { + return; + } + + UIViewGroup* group = CreateTestCaseGroup(); + group->SetViewId("UIKit_DrawRect_Test_RectNoBorder_005"); + group->SetPosition(0, 0); + + UILabel* label = CreateTitleLabel(); + label->SetText("矩形无border, radius>0, width = height, radius > height/2 "); + group->Add(label); + + UIView* view = CreateUIView(); + int16_t width = 50; // 50 : width + int16_t height = width; + view->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, VIEW_DISTANCE_TO_TOP_SIDE); + view->SetStyle(STYLE_BORDER_WIDTH, 0); + view->SetStyle(STYLE_BORDER_RADIUS, height / 2 + 10); // 2 : half height 10 : offset + view->Resize(width, height); + group->Add(view); + + container_->Add(group); + group->LayoutBottomToSibling("UIKit_DrawRect_Test_RectNoBorder_004", 10); // 10 : offset size +} + +void UITestDrawRect::UIKit_DrawRect_Test_RectNoBorder_006() +{ + if (container_ == nullptr) { + return; + } + + UIViewGroup* group = CreateTestCaseGroup(); + group->SetViewId("UIKit_DrawRect_Test_RectNoBorder_006"); + group->SetPosition(0, 0); + + UILabel* label = CreateTitleLabel(); + label->SetText("矩形无border, radius>0, width = height, radius = height/2 "); + group->Add(label); + + UIView* view = CreateUIView(); + int16_t width = 50; // 50 : width + int16_t height = width; + view->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, VIEW_DISTANCE_TO_TOP_SIDE); + view->SetStyle(STYLE_BORDER_WIDTH, 0); + view->SetStyle(STYLE_BORDER_RADIUS, height / 2); // 2 : half + view->Resize(width, height); + group->Add(view); + + container_->Add(group); + group->LayoutBottomToSibling("UIKit_DrawRect_Test_RectNoBorder_005", 10); // 10 : offset size +} + +void UITestDrawRect::UIKit_DrawRect_Test_RectNoBorder_007() +{ + if (container_ == nullptr) { + return; + } + + UIViewGroup* group = CreateTestCaseGroup(); + group->SetViewId("UIKit_DrawRect_Test_RectNoBorder_007"); + group->SetPosition(0, 0); + + UILabel* label = CreateTitleLabel(); + label->SetText("矩形无border, radius>0, width = height, radius < height/2 "); + group->Add(label); + + UIView* view = CreateUIView(); + int16_t width = 50; // 50 : width + int16_t height = width; + view->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, VIEW_DISTANCE_TO_TOP_SIDE); + view->SetStyle(STYLE_BORDER_WIDTH, 0); + view->SetStyle(STYLE_BORDER_RADIUS, height / 2 - 10); // 2 : half height 10 : offset + view->Resize(width, height); + group->Add(view); + + container_->Add(group); + group->LayoutBottomToSibling("UIKit_DrawRect_Test_RectNoBorder_006", 10); // 10 : offset size +} + +void UITestDrawRect::UIKit_DrawRect_Test_RectNoBorder_008() +{ + if (container_ == nullptr) { + return; + } + + UIViewGroup* group = CreateTestCaseGroup(); + group->SetViewId("UIKit_DrawRect_Test_RectNoBorder_008"); + group->SetPosition(0, 0); + + UILabel* label = CreateTitleLabel(); + label->SetText("矩形无border, radius>0, width < height, radius > width/2 "); + group->Add(label); + + UIView* view = CreateUIView(); + int16_t width = 50; // 50 : width + int16_t height = 120; // 120 : height + view->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, VIEW_DISTANCE_TO_TOP_SIDE); + view->SetStyle(STYLE_BORDER_WIDTH, 0); + view->SetStyle(STYLE_BORDER_RADIUS, width / 2 + 10); // 2 : half width 10 : offset + view->Resize(width, height); + group->Add(view); + + container_->Add(group); + group->LayoutBottomToSibling("UIKit_DrawRect_Test_RectNoBorder_007", 10); // 10 : offset size +} + +void UITestDrawRect::UIKit_DrawRect_Test_RectNoBorder_009() +{ + if (container_ == nullptr) { + return; + } + + UIViewGroup* group = CreateTestCaseGroup(); + group->SetViewId("UIKit_DrawRect_Test_RectNoBorder_009"); + group->SetPosition(0, 0); + + UILabel* label = CreateTitleLabel(); + label->SetText("矩形无border, radius>0, width < height, radius = width/2 "); + group->Add(label); + + UIView* view = CreateUIView(); + int16_t width = 51; // 51 : width + int16_t height = 120; // 120 : height + view->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, VIEW_DISTANCE_TO_TOP_SIDE); + view->SetStyle(STYLE_BORDER_WIDTH, 0); + view->SetStyle(STYLE_BORDER_RADIUS, width / 2); // 2 : half width + view->Resize(width, height); + group->Add(view); + + container_->Add(group); + group->LayoutBottomToSibling("UIKit_DrawRect_Test_RectNoBorder_008", 10); // 10 : offset size +} + +void UITestDrawRect::UIKit_DrawRect_Test_RectNoBorder_0010() +{ + if (container_ == nullptr) { + return; + } + + UIViewGroup* group = CreateTestCaseGroup(); + group->SetViewId("UIKit_DrawRect_Test_RectNoBorder_0010"); + group->SetPosition(0, 0); + + UILabel* label = CreateTitleLabel(); + label->SetText("矩形无border, radius>0, width < height, radius < width/2 "); + group->Add(label); + + UIView* view = CreateUIView(); + int16_t width = 50; // 50 : width + int16_t height = 120; // 120 : height + view->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, VIEW_DISTANCE_TO_TOP_SIDE); + view->SetStyle(STYLE_BORDER_WIDTH, 0); + view->SetStyle(STYLE_BORDER_RADIUS, width / 2 - 10); // 2 : half width 10 : offset + view->Resize(width, height); + group->Add(view); + + container_->Add(group); + group->LayoutBottomToSibling("UIKit_DrawRect_Test_RectNoBorder_009", 10); // 10 : offset size +} + +void UITestDrawRect::UIKit_DrawRect_Test_RectBorderNoRadius_001() +{ + if (container_ == nullptr) { + return; + } + + UIViewGroup* group = CreateTestCaseGroup(); + group->SetViewId("UIKit_DrawRect_Test_RectBorderNoRadius_001"); + group->SetPosition(0, 0); + + UILabel* label = CreateTitleLabel(); + label->SetText("矩形有border, radius=0"); + group->Add(label); + + UIView* view = CreateUIView(); + int16_t width = 120; // 120 : width + int16_t height = 50; // 50 : height + view->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, VIEW_DISTANCE_TO_TOP_SIDE); + view->SetStyle(STYLE_BORDER_WIDTH, 10); // 10 : value + view->SetStyle(STYLE_BORDER_RADIUS, 0); + view->Resize(width, height); + group->Add(view); + + container_->Add(group); + group->LayoutBottomToSibling("UIKit_DrawRect_Test_RectNoBorder_0010", 10); // 10 : offset size +} + +void UITestDrawRect::UIKit_DrawRect_Test_RectBorderNoRadius_002() +{ + if (container_ == nullptr) { + return; + } + + UIViewGroup* group = CreateTestCaseGroup(); + group->SetViewId("UIKit_DrawRect_Test_RectBorderNoRadius_002"); + group->SetPosition(0, 0); + + UILabel* label = CreateTitleLabel(); + label->SetText("矩形有border = 1, radius=0"); + group->Add(label); + + UIView* view = CreateUIView(); + int16_t width = 120; // 120 : width + int16_t height = 50; // 50 : height + view->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, VIEW_DISTANCE_TO_TOP_SIDE); + view->SetStyle(STYLE_BORDER_WIDTH, 1); // 1 : value + view->SetStyle(STYLE_BORDER_RADIUS, 0); + view->Resize(width, height); + group->Add(view); + + container_->Add(group); + group->LayoutBottomToSibling("UIKit_DrawRect_Test_RectBorderNoRadius_001", 10); // 10 : offset size +} + +void UITestDrawRect::UIKit_DrawRect_Test_RectBorderRadius_001() +{ + if (container_ == nullptr) { + return; + } + + UIViewGroup* group = CreateTestCaseGroup(); + group->SetViewId("UIKit_DrawRect_Test_RectBorderRadius_001"); + group->SetPosition(0, 0); + + UILabel* label = CreateTitleLabel(); + label->SetText("矩形有border, radiusAdd(label); + + UIView* view = CreateUIView(); + int16_t width = 120; // 120 : width + int16_t height = 50; // 50 : height + int16_t borderWidth = 10; // 10 : borderWidth + view->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, VIEW_DISTANCE_TO_TOP_SIDE); + view->SetStyle(STYLE_BORDER_WIDTH, borderWidth); + view->SetStyle(STYLE_BORDER_RADIUS, borderWidth - 5); // 5 : math_num + view->Resize(width, height); + group->Add(view); + + container_->Add(group); + group->LayoutBottomToSibling("UIKit_DrawRect_Test_RectBorderNoRadius_001", 10); // 10 : offset size +} + +void UITestDrawRect::UIKit_DrawRect_Test_RectBorderRadius_002() +{ + if (container_ == nullptr) { + return; + } + + UIViewGroup* group = CreateTestCaseGroup(); + group->SetViewId("UIKit_DrawRect_Test_RectBorderRadius_002"); + group->SetPosition(0, 0); + + UILabel* label = CreateTitleLabel(); + label->SetText("border场景,radius>border.width,宽度 >高度,radius > 高度/2 + border.width"); + group->Add(label); + + UIView* view = CreateUIView(); + int16_t width = 120; // 120 : width + int16_t height = 50; // 50 : height + int16_t borderWidth = 10; // 10 : borderWidth + view->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, VIEW_DISTANCE_TO_TOP_SIDE); + view->SetStyle(STYLE_BORDER_WIDTH, borderWidth); + view->SetStyle(STYLE_BORDER_RADIUS, height / 2 + borderWidth + 5); // 2 : half height 5 : offset + view->Resize(width, height); + group->Add(view); + + container_->Add(group); + group->LayoutBottomToSibling("UIKit_DrawRect_Test_RectBorderRadius_001", 10); // 10 : offset size +} + +void UITestDrawRect::UIKit_DrawRect_Test_RectBorderRadius_003() +{ + if (container_ == nullptr) { + return; + } + + UIViewGroup* group = CreateTestCaseGroup(); + group->SetViewId("UIKit_DrawRect_Test_RectBorderRadius_003"); + group->SetPosition(0, 0); + + UILabel* label = CreateTitleLabel(); + label->SetText("border场景,radius>border.width,宽度>高度,radius = 高度/2 + border.width"); + group->Add(label); + + UIView* view = CreateUIView(); + int16_t width = 120; // 120 : width + int16_t height = 50; // 50 : height + int16_t borderWidth = 10; // 10 : borderWidth + view->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, VIEW_DISTANCE_TO_TOP_SIDE); + view->SetStyle(STYLE_BORDER_WIDTH, borderWidth); + view->SetStyle(STYLE_BORDER_RADIUS, height / 2 + borderWidth); // 2 : half height + view->Resize(width, height); + group->Add(view); + + container_->Add(group); + group->LayoutBottomToSibling("UIKit_DrawRect_Test_RectBorderRadius_002", 10); // 10 : offset size +} + +void UITestDrawRect::UIKit_DrawRect_Test_RectBorderRadius_004() +{ + if (container_ == nullptr) { + return; + } + + UIViewGroup* group = CreateTestCaseGroup(); + group->SetViewId("UIKit_DrawRect_Test_RectBorderRadius_004"); + group->SetPosition(0, 0); + + UILabel* label = CreateTitleLabel(); + label->SetText("border场景,radius>border.width,宽度>高度,radius < 高度/2 + border.width"); + group->Add(label); + + UIView* view = CreateUIView(); + int16_t width = 120; // 120 : width + int16_t height = 50; // 50 : height + int16_t borderWidth = 10; // 10 : borderWidth + view->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, VIEW_DISTANCE_TO_TOP_SIDE); + view->SetStyle(STYLE_BORDER_WIDTH, borderWidth); + view->SetStyle(STYLE_BORDER_RADIUS, height / 2 + borderWidth - 5); // 2 : half height 5 : offset + view->Resize(width, height); + group->Add(view); + + container_->Add(group); + group->LayoutBottomToSibling("UIKit_DrawRect_Test_RectBorderRadius_003", 10); // 10 : offset size +} + +void UITestDrawRect::UIKit_DrawRect_Test_RectBorderRadius_005() +{ + if (container_ == nullptr) { + return; + } + + UIViewGroup* group = CreateTestCaseGroup(); + group->SetViewId("UIKit_DrawRect_Test_RectBorderRadius_005"); + group->SetPosition(0, 0); + + UILabel* label = CreateTitleLabel(); + label->SetText("border场景,radius>border.width,宽度=高度,radius > 宽度/2 + border.width"); + group->Add(label); + + UIView* view = CreateUIView(); + int16_t width = 50; // 50 : width + int16_t height = 50; // 50 : height + int16_t borderWidth = 10; // 10 : borderWidth + view->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, VIEW_DISTANCE_TO_TOP_SIDE); + view->SetStyle(STYLE_BORDER_WIDTH, borderWidth); + view->SetStyle(STYLE_BORDER_RADIUS, width / 2 + borderWidth + 5); // 2 : half width 5 : offset + view->Resize(width, height); + group->Add(view); + + container_->Add(group); + group->LayoutBottomToSibling("UIKit_DrawRect_Test_RectBorderRadius_004", 10); // 10 : offset size +} + +void UITestDrawRect::UIKit_DrawRect_Test_RectBorderRadius_006() +{ + if (container_ == nullptr) { + return; + } + + UIViewGroup* group = CreateTestCaseGroup(); + group->SetViewId("UIKit_DrawRect_Test_RectBorderRadius_006"); + group->SetPosition(0, 0); + + UILabel* label = CreateTitleLabel(); + label->SetText("border场景,radius>border.width,宽度=高度,radius = 宽度/2 + border.width"); + group->Add(label); + + UIView* view = CreateUIView(); + int16_t width = 50; // 50 : width + int16_t height = 50; // 50 : height + int16_t borderWidth = 10; // 10 : borderWidth + view->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, VIEW_DISTANCE_TO_TOP_SIDE); + view->SetStyle(STYLE_BORDER_WIDTH, borderWidth); + view->SetStyle(STYLE_BORDER_RADIUS, width / 2 + borderWidth); // 2 : half width + view->Resize(width, height); + group->Add(view); + + container_->Add(group); + group->LayoutBottomToSibling("UIKit_DrawRect_Test_RectBorderRadius_005", 10); // 10 : offset size +} + +void UITestDrawRect::UIKit_DrawRect_Test_RectBorderRadius_007() +{ + if (container_ == nullptr) { + return; + } + + UIViewGroup* group = CreateTestCaseGroup(); + group->SetViewId("UIKit_DrawRect_Test_RectBorderRadius_007"); + group->SetPosition(0, 0); + + UILabel* label = CreateTitleLabel(); + label->SetText("border场景,radius>border.width,宽度=高度,radius < 宽度/2 + border.width"); + group->Add(label); + + UIView* view = CreateUIView(); + int16_t width = 50; // 50 : width + int16_t height = 50; // 50 : height + int16_t borderWidth = 10; // 10 : borderWidth + view->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, VIEW_DISTANCE_TO_TOP_SIDE); + view->SetStyle(STYLE_BORDER_WIDTH, borderWidth); + view->SetStyle(STYLE_BORDER_RADIUS, width / 2 + borderWidth - 5); // 2 : half width 5 : offset + view->Resize(width, height); + group->Add(view); + + container_->Add(group); + group->LayoutBottomToSibling("UIKit_DrawRect_Test_RectBorderRadius_006", 10); // 10 : offset size +} + +void UITestDrawRect::UIKit_DrawRect_Test_RectBorderRadius_008() +{ + if (container_ == nullptr) { + return; + } + + UIViewGroup* group = CreateTestCaseGroup(); + group->SetViewId("UIKit_DrawRect_Test_RectBorderRadius_008"); + group->SetPosition(0, 0); + + UILabel* label = CreateTitleLabel(); + label->SetText("border场景,radius>border.width,宽度<高度,radius > 宽度/2 + border.width"); + group->Add(label); + + UIView* view = CreateUIView(); + int16_t width = 50; // 50 : width + int16_t height = 100; // 100 : height + int16_t borderWidth = 10; // 10 : borderWidth + view->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, VIEW_DISTANCE_TO_TOP_SIDE); + view->SetStyle(STYLE_BORDER_WIDTH, borderWidth); + view->SetStyle(STYLE_BORDER_RADIUS, width / 2 + borderWidth + 5); // 2 : half width 5 : math_num + view->Resize(width, height); + group->Add(view); + + container_->Add(group); + group->LayoutBottomToSibling("UIKit_DrawRect_Test_RectBorderRadius_007", 10); // 10 : value +} + +void UITestDrawRect::UIKit_DrawRect_Test_RectBorderRadius_009() +{ + if (container_ == nullptr) { + return; + } + + UIViewGroup* group = CreateTestCaseGroup(); + group->SetViewId("UIKit_DrawRect_Test_RectBorderRadius_009"); + group->SetPosition(0, 0); + + UILabel* label = CreateTitleLabel(); + label->SetText("border场景,radius>border.width,宽度<高度,radius = 宽度/2 + border.width"); + group->Add(label); + + UIView* view = CreateUIView(); + int16_t width = 50; // 50 : width + int16_t height = 100; // 100 : height + int16_t borderWidth = 10; // 10 : value + view->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, VIEW_DISTANCE_TO_TOP_SIDE); + view->SetStyle(STYLE_BORDER_WIDTH, borderWidth); + view->SetStyle(STYLE_BORDER_RADIUS, width / 2 + borderWidth); // 2 : math_num + view->Resize(width, height); + group->Add(view); + + container_->Add(group); + group->LayoutBottomToSibling("UIKit_DrawRect_Test_RectBorderRadius_008", 10); // 10 : value +} + +void UITestDrawRect::UIKit_DrawRect_Test_RectBorderRadius_010() +{ + if (container_ == nullptr) { + return; + } + + UIViewGroup* group = CreateTestCaseGroup(); + group->SetViewId("UIKit_DrawRect_Test_RectBorderRadius_010"); + group->SetPosition(0, 0); + + UILabel* label = CreateTitleLabel(); + label->SetText("border场景,radius>border.width,宽度<高度,radius < 宽度/2 + border.width"); + group->Add(label); + + UIView* view = CreateUIView(); + int16_t width = 50; // 50 : width + int16_t height = 100; // 100 : height + int16_t borderWidth = 10; // 10 : borderWidth + view->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, VIEW_DISTANCE_TO_TOP_SIDE); + view->SetStyle(STYLE_BORDER_WIDTH, borderWidth); + view->SetStyle(STYLE_BORDER_RADIUS, width / 2 + borderWidth - 5); // 2 : math_num 5 : math_num + view->Resize(width, height); + group->Add(view); + + container_->Add(group); + group->LayoutBottomToSibling("UIKit_DrawRect_Test_RectBorderRadius_009", 10); // 10 : value +} + +void UITestDrawRect::UIKit_DrawRect_Test_RectBorderRadius_011() +{ + if (container_ == nullptr) { + return; + } + + UIViewGroup* group = CreateTestCaseGroup(); + group->SetViewId("UIKit_DrawRect_Test_RectBorderRadius_011"); + group->SetPosition(0, 0); + + UILabel* label = CreateTitleLabel(); + label->SetText("矩形有border = 2, radius = 1"); + group->Add(label); + + UIView* view = CreateUIView(); + int16_t width = 120; // 120 : width + int16_t height = 50; // 50 : height + int16_t borderWidth = 10; // 10 : borderWidth + view->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, VIEW_DISTANCE_TO_TOP_SIDE); + view->SetStyle(STYLE_BORDER_WIDTH, 2); // 2: borderWidth + view->SetStyle(STYLE_BORDER_RADIUS, 1); // 1 : math_num + view->Resize(width, height); + group->Add(view); + + container_->Add(group); + group->LayoutBottomToSibling("UIKit_DrawRect_Test_RectBorderRadius_010", 10); // 10 : offset size +} + +void UITestDrawRect::UIKit_DrawRect_Test_Rect_001() +{ + if (container_ == nullptr) { + return; + } + + UIViewGroup* group = CreateTestCaseGroup(); + group->SetViewId("UIKit_DrawRect_Test_Rect_001"); + group->SetPosition(0, 0); + + UILabel* label = CreateTitleLabel(); + label->SetText("矩形绘制: "); + group->Add(label); + + UIView* view = CreateUIView(); + view->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, VIEW_DISTANCE_TO_TOP_SIDE); + view->SetStyle(STYLE_BORDER_WIDTH, 15); // 15 : value + view->SetStyle(STYLE_BORDER_OPA, OPA_OPAQUE); + view->SetStyle(STYLE_BORDER_RADIUS, 10); // 10 : offset size + view->Resize(120, 50); // 120 : width 50 : weight + group->Add(view); + + container_->Add(group); + group->LayoutBottomToSibling("UIKit_DrawRect_Test_RectBorderRadius_009", 10); // 10 : offset size +} + +void UITestDrawRect::UIKit_DrawRect_Test_RectColor_001() +{ + if (container_ == nullptr) { + return; + } + + UIViewGroup* group = CreateTestCaseGroup(); + group->SetViewId("UIKit_DrawRect_Test_RectColor_001"); + + UILabel* label = CreateTitleLabel(); + label->SetText("设置不同颜色的矩形绘制 "); + group->Add(label); + + UIView* view = CreateUIView(); + view->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, VIEW_DISTANCE_TO_TOP_SIDE); + view->SetStyle(STYLE_BORDER_WIDTH, 20); // 20 : value + view->SetStyle(STYLE_BORDER_RADIUS, 30); // 30 : value + view->Resize(120, 50); // 120 : width 50 : height + group->Add(view); + + UIView* view1 = CreateUIView(); + view1->SetPosition(250, 50); // 250 : start x 50 : start y + view1->SetStyle(STYLE_BORDER_WIDTH, 3); // 3 : value + view1->SetStyle(STYLE_BORDER_RADIUS, 3); // 3 : value + view1->Resize(120, 50); // 120 : width 50 : height + group->Add(view1); + + container_->Add(group); + group->LayoutBottomToSibling("UIKit_DrawRect_Test_Rect_001", 10); // 10 : offset size +} + +void UITestDrawRect::UIKit_DrawRect_Test_RectRadius_001() +{ + if (container_ == nullptr) { + return; + } + + UIViewGroup* group = CreateTestCaseGroup(); + group->SetViewId("UIKit_DrawRect_Test_RectRadius_001"); + + UILabel* label = CreateTitleLabel(); + label->SetText("设置圆角为零的矩形绘制 "); + group->Add(label); + + UIView* view = CreateUIView(); + view->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, VIEW_DISTANCE_TO_TOP_SIDE); + view->SetStyle(STYLE_BORDER_WIDTH, 20); // 20 : value + view->SetStyle(STYLE_BORDER_RADIUS, 10); // 10 : value + view->Resize(120, 50); // 120 : width 50 : height + group->Add(view); + + container_->Add(group); + group->LayoutBottomToSibling("UIKit_DrawRect_Test_RectColor_001", 10); // 10 : offset size +} + +void UITestDrawRect::UIKit_DrawRect_Test_RectRadius_002() +{ + if (container_ == nullptr) { + return; + } + + UIViewGroup* group = CreateTestCaseGroup(); + group->SetViewId("UIKit_DrawRect_Test_RectRadius_002"); + + UILabel* label = CreateTitleLabel(); + label->SetText("设置圆角等于宽度的矩形绘制 "); + group->Add(label); + + UIView* view = CreateUIView(); + view->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, VIEW_DISTANCE_TO_TOP_SIDE); + view->SetStyle(STYLE_BORDER_WIDTH, 20); // 20 : value + view->SetStyle(STYLE_BORDER_RADIUS, 50); // 50 : value + view->Resize(120, 50); // 120 : width 50 : height + group->Add(view); + + container_->Add(group); + group->LayoutBottomToSibling("UIKit_DrawRect_Test_RectRadius_001", 10); // 10 : offset size +} + +void UITestDrawRect::UIKit_DrawRect_Test_RectRadius_003() +{ + if (container_ == nullptr) { + return; + } + + UIViewGroup* group = CreateTestCaseGroup(); + group->SetViewId("UIKit_DrawRect_Test_RectRadius_003"); + + UILabel* label = CreateTitleLabel(); + label->SetText("设置圆角等于宽度的正方形绘制:"); + group->Add(label); + + UIView* view = CreateUIView(); + view->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, VIEW_DISTANCE_TO_TOP_SIDE); + view->SetStyle(STYLE_BORDER_WIDTH, 20); // 20 : value + view->SetStyle(STYLE_BORDER_RADIUS, 50); // 50 : value + view->Resize(50, 50); // 50 : value 50 : value + group->Add(view); + + container_->Add(group); + group->LayoutBottomToSibling("UIKit_DrawRect_Test_RectRadius_002", 10); // 10 : offset size +} + +void UITestDrawRect::UIKit_DrawRect_Test_RectRadius_004() +{ + if (container_ == nullptr) { + return; + } + + UIViewGroup* group = CreateTestCaseGroup(); + group->SetViewId("UIKit_DrawRect_Test_RectRadius_004"); + + UILabel* label = CreateTitleLabel(); + label->SetText("设置不同圆角大小的矩形绘制 "); + group->Add(label); + + UIView* view = CreateUIView(); + view->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, VIEW_DISTANCE_TO_TOP_SIDE); + view->SetStyle(STYLE_BORDER_WIDTH, 20); // 20 : value + view->SetStyle(STYLE_BORDER_RADIUS, 10); // 10 : value + view->Resize(120, 50); // 120 : width 50 : height + group->Add(view); + + UIView* view1 = CreateUIView(); + view1->SetPosition(250, 50); // 250 : offset 50 : offset + view1->SetStyle(STYLE_BORDER_WIDTH, 3); // 3 : value + view1->SetStyle(STYLE_BORDER_RADIUS, 5); // 5 : value + view1->Resize(120, 50); // 120 : width 50 : height + group->Add(view1); + + container_->Add(group); + group->LayoutBottomToSibling("UIKit_DrawRect_Test_RectRadius_003", 10); // 10 : offset size +} + +void UITestDrawRect::UIKit_DrawRect_Test_RectOpacity_001() +{ + if (container_ == nullptr) { + return; + } + + UIViewGroup* group = CreateTestCaseGroup(); + group->SetViewId("UIKit_DrawRect_Test_RectOpacity_001"); + + UILabel* label = CreateTitleLabel(); + label->SetText("设置不透明背景矩形绘制: "); + group->Add(label); + + UIView* view = CreateUIView(); + view->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, VIEW_DISTANCE_TO_TOP_SIDE); + view->SetStyle(STYLE_BACKGROUND_OPA, OPA_OPAQUE); + view->Resize(120, 50); // 120 : width 50 : height + group->Add(view); + + container_->Add(group); + group->LayoutBottomToSibling("UIKit_DrawRect_Test_RectRadius_004", 10); // 10 : offset size +} + +void UITestDrawRect::UIKit_DrawRect_Test_RectOpacity_002() +{ + if (container_ == nullptr) { + return; + } + + UIViewGroup* group = CreateTestCaseGroup(); + group->SetViewId("UIKit_DrawRect_Test_RectOpacity_002"); + + UILabel* label = CreateTitleLabel(); + label->SetText("设置全透明背景矩形绘制:"); + group->Add(label); + + UIView* view = CreateUIView(); + view->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, VIEW_DISTANCE_TO_TOP_SIDE); + view->SetStyle(STYLE_BACKGROUND_OPA, OPA_TRANSPARENT); + view->Resize(120, 50); // 120 : width 50 : height + group->Add(view); + + container_->Add(group); + group->LayoutBottomToSibling("UIKit_DrawRect_Test_RectOpacity_001", 10); // 10 : offset size +} + +void UITestDrawRect::UIKit_DrawRect_Test_RectOpacity_003() +{ + if (container_ == nullptr) { + return; + } + + UIViewGroup* group = CreateTestCaseGroup(); + group->SetViewId("UIKit_DrawRect_Test_RectOpacity_003"); + + UILabel* label = CreateTitleLabel(); + label->SetText("设置不同透明度矩形绘制 "); + group->Add(label); + + UIView* view = CreateUIView(); + view->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, VIEW_DISTANCE_TO_TOP_SIDE); + view->SetStyle(STYLE_BACKGROUND_OPA, 102); // 102 : value + view->Resize(120, 50); // 120 : width 50 : height + group->Add(view); + + UIView* view1 = CreateUIView(); + view1->SetPosition(250, 50); // 250 : start x 50 : start y + view1->SetStyle(STYLE_BACKGROUND_OPA, 204); // 204 : value + view1->Resize(120, 50); // 120 : width 50 : height + group->Add(view1); + + container_->Add(group); + group->LayoutBottomToSibling("UIKit_DrawRect_Test_RectOpacity_002", 10); // 10 : offset size +} + +void UITestDrawRect::UIKit_DrawRect_Test_RectBorderColor_001() +{ + if (container_ == nullptr) { + return; + } + + UIViewGroup* group = CreateTestCaseGroup(); + group->SetViewId("UIKit_DrawRect_Test_RectBorderColor_001"); + + UILabel* label = CreateTitleLabel(); + label->SetText("设置不同边框颜色的矩形绘制 "); + group->Add(label); + + UIView* view = CreateUIView(); + view->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, VIEW_DISTANCE_TO_TOP_SIDE); + view->SetStyle(STYLE_BORDER_COLOR, Color::Yellow().full); + view->SetStyle(STYLE_BORDER_WIDTH, 5); // 5 : value + + view->Resize(120, 50); // 120 : width 50 : height + group->Add(view); + + UIView* view1 = CreateUIView(); + view1->SetPosition(250, 50); // 250 : start x 50 : start y + view1->SetStyle(STYLE_BORDER_COLOR, Color::Red().full); + view1->SetStyle(STYLE_BORDER_WIDTH, 5); // 5 : value + view1->Resize(120, 50); // 120 : width 50 : height + group->Add(view1); + + container_->Add(group); + group->LayoutBottomToSibling("UIKit_DrawRect_Test_RectOpacity_003", 10); // 10 : offset size +} + +void UITestDrawRect::UIKit_DrawRect_Test_RectBorderWidth_001() +{ + if (container_ == nullptr) { + return; + } + + UIViewGroup* group = CreateTestCaseGroup(); + group->SetViewId("UIKit_DrawRect_Test_RectBorderWidth_001"); + + UILabel* label = CreateTitleLabel(); + label->SetText("设置边框宽度为零的矩形绘制 "); + group->Add(label); + + UIView* view = CreateUIView(); + view->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, VIEW_DISTANCE_TO_TOP_SIDE); + view->SetStyle(STYLE_BORDER_WIDTH, 0); + view->Resize(120, 50); // 120 : width 50 : height + group->Add(view); + + container_->Add(group); + group->LayoutBottomToSibling("UIKit_DrawRect_Test_RectBorderColor_001", 10); // 10 : offset size +} + +void UITestDrawRect::UIKit_DrawRect_Test_RectBorderWidth_002() +{ + if (container_ == nullptr) { + return; + } + + UIViewGroup* group = CreateTestCaseGroup(); + group->SetViewId("UIKit_DrawRect_Test_RectBorderWidth_002"); + + UILabel* label = CreateTitleLabel(); + label->SetText("设置不同宽度边框大小的矩形绘制 "); + group->Add(label); + + UIView* view = CreateUIView(); + view->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, VIEW_DISTANCE_TO_TOP_SIDE); + view->SetStyle(STYLE_BORDER_WIDTH, 5); // 5 : value + view->Resize(120, 50); // 120 : width 50 : height + group->Add(view); + + UIView* view1 = CreateUIView(); + view1->SetPosition(250, 50); // 250 : offset 50 : offset + view1->SetStyle(STYLE_BORDER_WIDTH, 10); // 10 : value + view1->Resize(120, 50); // 120 : width 50 : height + group->Add(view1); + + container_->Add(group); + group->LayoutBottomToSibling("UIKit_DrawRect_Test_RectBorderWidth_001", 10); // 10 : offset size +} + +void UITestDrawRect::UIKit_DrawRect_Test_RectBorderOpacity_001() +{ + if (container_ == nullptr) { + return; + } + + UIViewGroup* group = CreateTestCaseGroup(); + group->SetViewId("UIKit_DrawRect_Test_RectBorderOpacity_001"); + + UILabel* label = CreateTitleLabel(); + label->SetText("设置不透明边框矩形绘制:"); + group->Add(label); + + UIView* view = CreateUIView(); + view->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, VIEW_DISTANCE_TO_TOP_SIDE); + view->SetStyle(STYLE_BORDER_WIDTH, 5); // 5 : value + view->SetStyle(STYLE_BORDER_OPA, OPA_OPAQUE); + view->Resize(120, 50); // 120 : width 50 : height + group->Add(view); + + container_->Add(group); + group->LayoutBottomToSibling("UIKit_DrawRect_Test_RectBorderWidth_002", 10); // 10 : offset size +} + +void UITestDrawRect::UIKit_DrawRect_Test_RectBorderOpacity_002() +{ + if (container_ == nullptr) { + return; + } + + UIViewGroup* group = CreateTestCaseGroup(); + group->SetViewId("UIKit_DrawRect_Test_RectBorderOpacity_002"); + + UILabel* label = CreateTitleLabel(); + label->SetText("设置全透明边框矩形绘制:"); + group->Add(label); + + UIView* view = CreateUIView(); + view->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, VIEW_DISTANCE_TO_TOP_SIDE); + view->SetStyle(STYLE_BORDER_WIDTH, 5); // 5 : value + view->SetStyle(STYLE_BORDER_OPA, OPA_TRANSPARENT); + view->Resize(120, 50); // 120 : width 50 : height + group->Add(view); + + container_->Add(group); + group->LayoutBottomToSibling("UIKit_DrawRect_Test_RectBorderOpacity_001", 10); // 10 : offset size +} + +void UITestDrawRect::UIKit_DrawRect_Test_RectBorderOpacity_003() +{ + if (container_ == nullptr) { + return; + } + + UIViewGroup* group = CreateTestCaseGroup(); + group->SetViewId("UIKit_DrawRect_Test_RectBorderOpacity_003"); + + UILabel* label = CreateTitleLabel(); + label->SetText("设置不同透明度边框矩形绘制 "); + group->Add(label); + + UIView* view = CreateUIView(); + view->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, VIEW_DISTANCE_TO_TOP_SIDE); + view->SetStyle(STYLE_BORDER_WIDTH, 5); // 5 : value + view->SetStyle(STYLE_BORDER_OPA, 153); // 153 : value + view->Resize(120, 50); // 120 : width 50 : height + group->Add(view); + + UIView* view1 = CreateUIView(); + view1->SetPosition(250, 50); // 250 : start x 50 : start y + view1->SetStyle(STYLE_BORDER_WIDTH, 5); // 5 : value + view1->SetStyle(STYLE_BORDER_OPA, 204); // 204 : value + view1->Resize(120, 50); // 120 : width 50 : height + group->Add(view1); + + container_->Add(group); + group->LayoutBottomToSibling("UIKit_DrawRect_Test_RectBorderOpacity_002", 10); // 10 : offset size +} + +void UITestDrawRect::UIKit_DrawRect_Test_RectPadding_001() +{ + if (container_ == nullptr) { + return; + } + + UIViewGroup* group = CreateTestCaseGroup(); + group->SetViewId("UIKit_DrawRect_Test_RectPadding_001"); + + UILabel* label = CreateTitleLabel(); + label->SetText("设置padding为零的矩形绘制 "); + group->Add(label); + + UIView* view = CreateUIView(); + view->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, VIEW_DISTANCE_TO_TOP_SIDE); + view->SetStyle(STYLE_PADDING_LEFT, 0); + view->SetStyle(STYLE_PADDING_RIGHT, 0); + view->SetStyle(STYLE_PADDING_TOP, 0); + view->SetStyle(STYLE_PADDING_BOTTOM, 0); + view->Resize(120, 50); // 120 : width 50 : height + group->Add(view); + + container_->Add(group); + group->LayoutBottomToSibling("UIKit_DrawRect_Test_RectBorderOpacity_003", 10); // 10 : offset size +} + +void UITestDrawRect::UIKit_DrawRect_Test_RectPadding_002() +{ + if (container_ == nullptr) { + return; + } + + UIViewGroup* group = CreateTestCaseGroup(); + group->SetViewId("UIKit_DrawRect_Test_RectPadding_002"); + + UILabel* label = CreateTitleLabel(); + label->SetText("设置padding不为零的矩形绘制:"); + group->Add(label); + + UIView* view = CreateUIView(); + view->SetPosition(0, 50); // 0 : start x 50 : start y + view->Resize(100, 50); // 100 : width 50 : height + group->Add(view); + + UIView* view1 = CreateUIView(); + view1->SetPosition(120, 50); // 120 : start x 50 : start y + view1->SetStyle(STYLE_PADDING_LEFT, 10); // 10 : value + view1->SetStyle(STYLE_PADDING_RIGHT, 10); // 10 : value + view1->SetStyle(STYLE_PADDING_TOP, 10); // 10 : value + view1->SetStyle(STYLE_PADDING_BOTTOM, 10); // 10 : value + view1->Resize(120, 50); // 120 : width 50 : height + group->Add(view1); + + UIView* view2 = CreateUIView(); + view2->SetPosition(280, 50); // 280 : start x 50 : start y + view2->SetStyle(STYLE_PADDING_LEFT, 20); // 20 : value + view2->SetStyle(STYLE_PADDING_RIGHT, 10); // 10 : value + view2->SetStyle(STYLE_PADDING_TOP, 20); // 20 : value + view2->SetStyle(STYLE_PADDING_BOTTOM, 10); // 10 : value + view2->Resize(120, 50); // 120 : width 50 : height + group->Add(view2); + + container_->Add(group); + group->LayoutBottomToSibling("UIKit_DrawRect_Test_RectPadding_001", 10); // 10 : offset size +} + +void UITestDrawRect::UIKit_DrawRect_Test_RectMargin_001() +{ + if (container_ == nullptr) { + return; + } + + UIViewGroup* group = CreateTestCaseGroup(); + group->SetViewId("UIKit_DrawRect_Test_RectMargin_001"); + + UILabel* label = CreateTitleLabel(); + label->SetText("设置margin为零的矩形绘制 "); + group->Add(label); + + UIView* view = CreateUIView(); + view->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, VIEW_DISTANCE_TO_TOP_SIDE); + view->SetStyle(STYLE_MARGIN_LEFT, 0); + view->SetStyle(STYLE_MARGIN_RIGHT, 0); + view->SetStyle(STYLE_MARGIN_TOP, 0); + view->SetStyle(STYLE_MARGIN_BOTTOM, 0); + view->Resize(120, 50); // 120 : width 50 : height + group->Add(view); + + container_->Add(group); + group->LayoutBottomToSibling("UIKit_DrawRect_Test_RectPadding_002", 10); // 10 : offset size +} + +void UITestDrawRect::UIKit_DrawRect_Test_RectMargin_002() +{ + if (container_ == nullptr) { + return; + } + + UIViewGroup* group = CreateTestCaseGroup(); + group->SetViewId("UIKit_DrawRect_Test_RectMargin_002"); + + UILabel* label = CreateTitleLabel(); + label->SetText("设置margin不为零的矩形绘制:"); + group->Add(label); + + UIView* view = CreateUIView(); + view->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, VIEW_DISTANCE_TO_TOP_SIDE); + view->SetStyle(STYLE_MARGIN_LEFT, 10); // 10 : margin left + view->SetStyle(STYLE_MARGIN_RIGHT, 10); // 10 : margin right + view->SetStyle(STYLE_MARGIN_TOP, 10); // 10 : margin top + view->SetStyle(STYLE_MARGIN_BOTTOM, 10); // 10 : margin bottom + view->Resize(120, 50); // 120 : width 50 : height + group->Add(view); + + UIView* view1 = CreateUIView(); + view1->SetPosition(250, 50); // 250 : start x 50 : start y + view1->SetStyle(STYLE_MARGIN_LEFT, 20); // 20 : margin left + view1->SetStyle(STYLE_MARGIN_RIGHT, 10); // 10 : margin right + view1->SetStyle(STYLE_MARGIN_TOP, 20); // 20 : margin top + view1->SetStyle(STYLE_MARGIN_BOTTOM, 10); // 10 : margin bottom + view1->Resize(120, 50); // 120 : width 50 : height + group->Add(view1); + + container_->Add(group); + group->LayoutBottomToSibling("UIKit_DrawRect_Test_RectMargin_001", 10); // 10 : offset size +} + +void UITestDrawRect::UIKit_DrawRect_Test_RectPaddingMargin_001() +{ + if (container_ == nullptr) { + return; + } + + UIViewGroup* group = CreateTestCaseGroup(); + group->SetViewId("UIKit_DrawRect_Test_RectPaddingMargin_001"); + + UILabel* label = CreateTitleLabel(); + label->SetText("设置 padding/margin 均为零的矩形绘制:"); + group->Add(label); + + UIView* view = CreateUIView(); + view->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, VIEW_DISTANCE_TO_TOP_SIDE); + view->SetStyle(STYLE_MARGIN_LEFT, 0); + view->SetStyle(STYLE_MARGIN_RIGHT, 0); + view->SetStyle(STYLE_MARGIN_TOP, 0); + view->SetStyle(STYLE_MARGIN_BOTTOM, 0); + view->SetStyle(STYLE_PADDING_LEFT, 0); + view->SetStyle(STYLE_PADDING_RIGHT, 0); + view->SetStyle(STYLE_PADDING_TOP, 0); + view->SetStyle(STYLE_PADDING_BOTTOM, 0); + view->Resize(120, 50); // 120 : width 50 : height + group->Add(view); + + container_->Add(group); + group->LayoutBottomToSibling("UIKit_DrawRect_Test_RectMargin_002", 10); // 10 : offset size +} + +void UITestDrawRect::UIKit_DrawRect_Test_RectPaddingMargin_002() +{ + if (container_ == nullptr) { + return; + } + + UIViewGroup* group = CreateTestCaseGroup(); + group->SetViewId("UIKit_DrawRect_Test_RectPaddingMargin_002"); + + UILabel* label = CreateTitleLabel(); + label->SetText("设置padding/margin均不为零的矩形绘制 "); + group->Add(label); + + UIView* view = CreateUIView(); + view->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, VIEW_DISTANCE_TO_TOP_SIDE); + view->SetStyle(STYLE_MARGIN_LEFT, 0); + view->SetStyle(STYLE_MARGIN_RIGHT, 0); + view->SetStyle(STYLE_MARGIN_TOP, 0); + view->SetStyle(STYLE_MARGIN_BOTTOM, 0); + view->SetStyle(STYLE_PADDING_LEFT, 0); + view->SetStyle(STYLE_PADDING_RIGHT, 0); + view->SetStyle(STYLE_PADDING_TOP, 0); + view->SetStyle(STYLE_PADDING_BOTTOM, 0); + view->Resize(120, 50); // 120 : width 50 : height + group->Add(view); + + UIView* view1 = CreateUIView(); + view1->SetPosition(250, 50); // 250 : start 50 : start + view1->SetStyle(STYLE_MARGIN_LEFT, 10); // 10 : margin left + view1->SetStyle(STYLE_MARGIN_RIGHT, 10); // 10 : margin right + view1->SetStyle(STYLE_MARGIN_TOP, 10); // 10 : margin top + view1->SetStyle(STYLE_MARGIN_BOTTOM, 10); // 10 : margin bottom + view1->SetStyle(STYLE_PADDING_LEFT, 10); // 10 : padding left + view1->SetStyle(STYLE_PADDING_RIGHT, 10); // 10 : padding right + view1->SetStyle(STYLE_PADDING_TOP, 10); // 10 : padding top + view1->SetStyle(STYLE_PADDING_BOTTOM, 10); // 10 : padding bottom + view1->Resize(120, 50); // 120 : width 50 : height + group->Add(view1); + + container_->Add(group); + group->LayoutBottomToSibling("UIKit_DrawRect_Test_RectPaddingMargin_001", 10); // 10 : offset size +} + +void UITestDrawRect::UIKit_DrawRect_Test_RectWidthAndHeight_001() +{ + if (container_ == nullptr) { + return; + } + + UIViewGroup* group = CreateTestCaseGroup(); + group->SetViewId("UIKit_DrawRect_Test_RectWidthAndHeight_001"); + + UILabel* label = CreateTitleLabel(); + label->SetText("设置宽度为零的矩形绘制 "); + group->Add(label); + + UIView* view = CreateUIView(); + view->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, VIEW_DISTANCE_TO_TOP_SIDE); + view->SetStyle(STYLE_BORDER_WIDTH, 0); + view->SetStyle(STYLE_BORDER_RADIUS, 10); // 10 : border radius + view->Resize(0, 50); // 0 : width 50 : height + container_->Add(view); + + container_->Add(group); + group->LayoutBottomToSibling("UIKit_DrawRect_Test_RectPaddingMargin_002", 20); // 20 : offset size +} + +void UITestDrawRect::UIKit_DrawRect_Test_RectWidthAndHeight_002() +{ + if (container_ == nullptr) { + return; + } + + UIViewGroup* group = CreateTestCaseGroup(); + group->SetViewId("UIKit_DrawRect_Test_RectWidthAndHeight_002"); + + UILabel* label = CreateTitleLabel(); + label->SetText("设置高度为零的矩形绘制 "); + group->Add(label); + + UIView* view = CreateUIView(); + view->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, VIEW_DISTANCE_TO_TOP_SIDE); + view->SetStyle(STYLE_BORDER_WIDTH, 0); // 10 : value + view->SetStyle(STYLE_BORDER_RADIUS, 0); // 10 : value + view->Resize(120, 0); // 120 : width 0 : height + group->Add(view); + + container_->Add(group); + group->LayoutBottomToSibling("UIKit_DrawRect_Test_RectWidthAndHeight_001", 20); // 20 : offset size +} + +void UITestDrawRect::UIKit_DrawRect_Test_RectWidthAndHeight_003() +{ + if (container_ == nullptr) { + return; + } + + UIViewGroup* group = CreateTestCaseGroup(); + group->SetViewId("UIKit_DrawRect_Test_RectWidthAndHeight_003"); + + UILabel* label = CreateTitleLabel(); + label->SetText("设置宽度和高度都为零的矩形绘制 "); + group->Add(label); + + UIView* view = CreateUIView(); + view->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, VIEW_DISTANCE_TO_TOP_SIDE); + view->SetStyle(STYLE_BORDER_WIDTH, 0); + view->SetStyle(STYLE_BORDER_RADIUS, 10); // 10 : border radius + view->Resize(0, 0); // 0 : width 0 : height + group->Add(view); + + container_->Add(group); + group->LayoutBottomToSibling("UIKit_DrawRect_Test_RectWidthAndHeight_002", 20); // 20 : offset size +} +} // namespace OHOS diff --git a/test/uitest/test_draw_rect/ui_test_draw_rect.h b/test/uitest/test_draw_rect/ui_test_draw_rect.h new file mode 100755 index 0000000..8ab7732 --- /dev/null +++ b/test/uitest/test_draw_rect/ui_test_draw_rect.h @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef UI_TEST_DRAW_RECT_H +#define UI_TEST_DRAW_RECT_H + +#include "components/ui_button.h" +#include "components/ui_label.h" +#include "components/ui_scroll_view.h" +#include "ui_test.h" + +namespace OHOS { +class UITestDrawRect : public UITest { +public: + UITestDrawRect() {} + ~UITestDrawRect() {} + void SetUp() override; + void TearDown() override; + UIView* GetTestView() override; + + void UIKit_DrawRect_Test_RectNoBorder_001(); + void UIKit_DrawRect_Test_RectNoBorder_002(); + void UIKit_DrawRect_Test_RectNoBorder_003(); + void UIKit_DrawRect_Test_RectNoBorder_004(); + void UIKit_DrawRect_Test_RectNoBorder_005(); + void UIKit_DrawRect_Test_RectNoBorder_006(); + void UIKit_DrawRect_Test_RectNoBorder_007(); + void UIKit_DrawRect_Test_RectNoBorder_008(); + void UIKit_DrawRect_Test_RectNoBorder_009(); + void UIKit_DrawRect_Test_RectNoBorder_0010(); + void UIKit_DrawRect_Test_RectBorderNoRadius_001(); + void UIKit_DrawRect_Test_RectBorderNoRadius_002(); + void UIKit_DrawRect_Test_RectBorderRadius_001(); + void UIKit_DrawRect_Test_RectBorderRadius_002(); + void UIKit_DrawRect_Test_RectBorderRadius_003(); + void UIKit_DrawRect_Test_RectBorderRadius_004(); + void UIKit_DrawRect_Test_RectBorderRadius_005(); + void UIKit_DrawRect_Test_RectBorderRadius_006(); + void UIKit_DrawRect_Test_RectBorderRadius_007(); + void UIKit_DrawRect_Test_RectBorderRadius_008(); + void UIKit_DrawRect_Test_RectBorderRadius_009(); + void UIKit_DrawRect_Test_RectBorderRadius_010(); + void UIKit_DrawRect_Test_RectBorderRadius_011(); + + /** + * @brief Test draw rect function + */ + void UIKit_DrawRect_Test_Rect_001(); + + /** + * @brief Test draw rect background color + */ + void UIKit_DrawRect_Test_RectColor_001(); + + /** + * @brief Test draw rect radius is 0 + */ + void UIKit_DrawRect_Test_RectRadius_001(); + + /** + * @brief Test draw rect radius + */ + void UIKit_DrawRect_Test_RectRadius_002(); + + /** + * @brief Test draw rect radius equal width + */ + void UIKit_DrawRect_Test_RectRadius_003(); + + /** + * @brief Test draw rect radius equal width and height + */ + void UIKit_DrawRect_Test_RectRadius_004(); + + /** + * @brief Test draw rect opacity is not transparent + */ + void UIKit_DrawRect_Test_RectOpacity_001(); + + /** + * @brief Test draw rect opacity is transparent + */ + void UIKit_DrawRect_Test_RectOpacity_002(); + + /** + * @brief Test draw rect opacity with different value + */ + void UIKit_DrawRect_Test_RectOpacity_003(); + + /** + * @brief Test draw rect border color with different value + */ + void UIKit_DrawRect_Test_RectBorderColor_001(); + + /** + * @brief Test draw rect border width is 0 + */ + void UIKit_DrawRect_Test_RectBorderWidth_001(); + + /** + * @brief Test draw rect border with different value + */ + void UIKit_DrawRect_Test_RectBorderWidth_002(); + + /** + * @brief Test draw rect border opacity is not transparent + */ + void UIKit_DrawRect_Test_RectBorderOpacity_001(); + + /** + * @brief Test draw rect border opacity is transparent + */ + void UIKit_DrawRect_Test_RectBorderOpacity_002(); + + /** + * @brief Test draw rect border opacity with different value + */ + void UIKit_DrawRect_Test_RectBorderOpacity_003(); + + /** + * @brief Test draw rect padding is 0 + */ + void UIKit_DrawRect_Test_RectPadding_001(); + + /** + * @brief Test draw rect padding with different value + */ + void UIKit_DrawRect_Test_RectPadding_002(); + + /** + * @brief Test draw rect margin is 0 + */ + void UIKit_DrawRect_Test_RectMargin_001(); + + /** + * @brief Test draw rect margin is not 0 + */ + void UIKit_DrawRect_Test_RectMargin_002(); + + /** + * @brief Test draw rect padding and margin is 0 + */ + void UIKit_DrawRect_Test_RectPaddingMargin_001(); + + /** + * @brief Test draw rect padding and margin with different value + */ + void UIKit_DrawRect_Test_RectPaddingMargin_002(); + + /** + * @brief Test rect width is 0 + */ + void UIKit_DrawRect_Test_RectWidthAndHeight_001(); + + /** + * @brief Test rect height is 0 + */ + void UIKit_DrawRect_Test_RectWidthAndHeight_002(); + + /** + * @brief Test rect width and height is 0 + */ + void UIKit_DrawRect_Test_RectWidthAndHeight_003(); + +private: + UIViewGroup* CreateTestCaseGroup() const; + UILabel* CreateTitleLabel() const; + UIView* CreateUIView() const; + UIScrollView* container_ = nullptr; +}; +} // namespace OHOS +#endif // UI_TEST_DRAW_RECT_H diff --git a/test/uitest/test_event_injector/ui_test_event_injector.cpp b/test/uitest/test_event_injector/ui_test_event_injector.cpp new file mode 100755 index 0000000..efda267 --- /dev/null +++ b/test/uitest/test_event_injector/ui_test_event_injector.cpp @@ -0,0 +1,472 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "graphic_config.h" + +#if ENABLE_DEBUG +#include "common/screen.h" +#include "components/root_view.h" +#include "dfx/event_injector.h" +#include "ui_test_event_injector.h" +#include +#if ENABLE_WINDOW +#include "window/window.h" +#endif + +namespace OHOS { +namespace { + const int16_t ITEM_H = 50; + const int16_t TEXT_H = 29; + const int16_t TEXT_W = 250; + const int16_t TEST_VIEW_H = 50; + const int16_t TEST_VIEW_W = 50; + const int16_t GAP = 5; + const int16_t TEST_VIEW_GAP = 80; + const int16_t TEST_BUTTON_W = 100; + const int16_t TEST_BUTTON_H = 30; + const int16_t LAYOUT_HEIGHT = 150; + const int16_t LAYOUT_WIDTH = 480; + const int16_t BLANK = 20; + const int16_t SCROLL_WIDTH = 300; + const int16_t SCROLL_HEIGHT = 300; + const int16_t SCROLL_BUTTON_W = 800; + const int16_t SCROLL_BUTTON_H = 400; + const int16_t DRAG_TIME_OFFSET = 20; + const int16_t RATIO_X = 3; + const int16_t RATIO_Y = 10; + const int16_t POINT_OFFSET = 4; +} + +class TestEventInjectorView : public UIView, public RootView::OnKeyActListener { +public: + TestEventInjectorView() : label_(nullptr) {} + virtual ~TestEventInjectorView() {} + + void InitListener() + { + RootView::GetInstance()->SetOnKeyActListener(this); + } + + bool OnLongPressEvent(const LongPressEvent& event) override + { + if (label_ != nullptr) { + label_->SetText("long press!"); + label_->Invalidate(); + } + return UIView::OnLongPressEvent(event); + } + + bool OnDragEvent(const DragEvent& event) override + { + if (label_ != nullptr) { + label_->SetText("drag!"); + label_->Invalidate(); + } + return UIView::OnDragEvent(event); + } + + void SetSentence(const char* sentence) + { + sentence_ = sentence; + } + + bool OnClickEvent(const ClickEvent& event) override + { + if (label_ != nullptr) { + label_->SetText(sentence_); + label_->Invalidate(); + } + return UIView::OnClickEvent(event); + } + + bool OnPressEvent(const PressEvent& event) override + { + if (label_ != nullptr) { + label_->SetText("press!"); + label_->Invalidate(); + } + return UIView::OnPressEvent(event); + } + + bool OnReleaseEvent(const ReleaseEvent& event) override + { + if (label_ != nullptr) { + label_->SetText("release!"); + label_->Invalidate(); + } + return UIView::OnReleaseEvent(event); + } + + bool OnCancelEvent(const CancelEvent& event) override + { + if (label_ != nullptr) { + label_->SetText("cancel!"); + label_->Invalidate(); + } + return UIView::OnCancelEvent(event); + } + + void SetLabel(UILabel* label) + { + label_ = label; + } + + bool OnKeyAct(UIView& view, const KeyEvent& event) override + { + if (label_ == nullptr) { + return false; + } + label_->SetText("key!"); + label_->Invalidate(); + return true; + } + +private: + UILabel* label_; + const char* sentence_ = "click"; +}; + + +void UITestEventInjector::SetUp() +{ + if (container_ == nullptr) { + container_ = new UIScrollView(); + container_->Resize(Screen::GetInstance().GetWidth(), Screen::GetInstance().GetHeight() - BACK_BUTTON_HEIGHT); + container_->SetHorizontalScrollState(false); + container_->SetThrowDrag(true); + } + EventInjector::GetInstance()->RegisterEventInjector(EventDataType::POINT_TYPE); + EventInjector::GetInstance()->RegisterEventInjector(EventDataType::KEY_TYPE); + +#if ENABLE_WINDOW + Window* window = RootView::GetInstance()->GetBoundWindow(); + if (window != nullptr) { + EventInjector::GetInstance()->SetWindowId(window->GetWindowId()); + } +#endif +} + +void UITestEventInjector::TearDown() +{ + DeleteChildren(container_); + container_ = nullptr; + layout_ = nullptr; + positionX_ = 20; // 20 : start x + positionY_ = 20; // 20 : start y + if (EventInjector::GetInstance()->IsEventInjectorRegistered(EventDataType::POINT_TYPE)) { + EventInjector::GetInstance()->UnregisterEventInjector(EventDataType::POINT_TYPE); + } + if (EventInjector::GetInstance()->IsEventInjectorRegistered(EventDataType::KEY_TYPE)) { + EventInjector::GetInstance()->UnregisterEventInjector(EventDataType::KEY_TYPE); + } +} + +UIView* UITestEventInjector::GetTestView() +{ + UIKit_Event_Injector_Click_Event_001(); + UIKit_Event_Injector_Drag_Event_002(); + UIKit_Event_Injector_Long_Press_Event_003(); + UIKit_Event_Injector_Key_Event_004(); + + SetUpScrollView(); + UIKit_Event_Injector_Up_to_Down_005(); + UIKit_Event_Injector_Down_to_Up_006(); + UIKit_Event_Injector_Left_to_Right_007(); + UIKit_Event_Injector_Right_to_Left_008(); + UIKit_Event_Injector_ULeft_to_LRight_009(); + UIKit_Event_Injector_LRight_to_ULeft_010(); + IncreaseDragTime(); + DecreaseDragTime(); + DragTimeDisplay(); + + layout_->LayoutChildren(); + return container_; +} + +void UITestEventInjector::UIKit_Event_Injector_Click_Event_001() +{ + clickBtn_ = new UILabelButton(); + InnerTest("模拟点击事件 ", true, false, false, "click", clickBtn_, clickTestView_); +} + +void UITestEventInjector::UIKit_Event_Injector_Drag_Event_002() +{ + dragBtn_ = new UILabelButton(); + InnerTest("模拟滑动事件 ", true, true, false, "drag", dragBtn_, dragTestView_); +} + +void UITestEventInjector::UIKit_Event_Injector_Long_Press_Event_003() +{ + longPressBtn_ = new UILabelButton(); + InnerTest("模拟长按事件 ", true, true, true, "long press", longPressBtn_, longPressTestView_); +} + +void UITestEventInjector::UIKit_Event_Injector_Key_Event_004() +{ + keyBtn_ = new UILabelButton(); + InnerTest("模拟按键输入 ", true, false, false, "key event", keyBtn_, KeyEventTestView_); +} + +void UITestEventInjector::UIKit_Event_Injector_Up_to_Down_005() +{ + upToDownBtn_ = new UILabelButton(); + SetUpButton(upToDownBtn_, "up to down"); +} + +void UITestEventInjector::UIKit_Event_Injector_Down_to_Up_006() +{ + downToUpBtn_ = new UILabelButton(); + SetUpButton(downToUpBtn_, "down to up"); +} + +void UITestEventInjector::UIKit_Event_Injector_Left_to_Right_007() +{ + leftToRightBtn_ = new UILabelButton(); + SetUpButton(leftToRightBtn_, "left to right"); +} + +void UITestEventInjector::UIKit_Event_Injector_Right_to_Left_008() +{ + rightToLeftBtn_ = new UILabelButton(); + SetUpButton(rightToLeftBtn_, "right to left"); +} + +void UITestEventInjector::UIKit_Event_Injector_ULeft_to_LRight_009() +{ + uLeftTolRightBtn_ = new UILabelButton(); + SetUpButton(uLeftTolRightBtn_, "uLeft to lRight"); +} + +void UITestEventInjector::UIKit_Event_Injector_LRight_to_ULeft_010() +{ + lRightTouLeftBtn_ = new UILabelButton(); + SetUpButton(lRightTouLeftBtn_, "lRight to uleft"); +} + +void UITestEventInjector::IncreaseDragTime() +{ + increaseDragTimeBtn_ = new UILabelButton(); + SetUpButton(increaseDragTimeBtn_, "increase drag time"); +} + +void UITestEventInjector::DecreaseDragTime() +{ + decreaseDragTimeBtn_ = new UILabelButton(); + SetUpButton(decreaseDragTimeBtn_, "decrease drag time"); +} + +void UITestEventInjector::DragTimeDisplay() +{ + DragTimeDisplayBtn_ = new UILabelButton(); + std::string dragTimestr = "dragTime:" + std::to_string(dragTime_); + SetUpButton(DragTimeDisplayBtn_, dragTimestr.c_str()); +} + +void UITestEventInjector::InnerTest(const char* title, + bool touchable, + bool draggable, + bool dragParent, + const char* btnTitle, + UILabelButton* btn, + TestEventInjectorView*& testView) +{ + if ((container_ != nullptr) && (btn != nullptr)) { + UILabel* label = new UILabel(); + container_->Add(label); + label->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, positionY_, Screen::GetInstance().GetWidth(), + TITLE_LABEL_DEFAULT_HEIGHT); + label->SetText(title); + label->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + positionY_ += (TEXT_H + GAP); + testView = new TestEventInjectorView(); + testView->InitListener(); + container_->Add(testView); + testView->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, positionY_, TEST_VIEW_W, TEST_VIEW_H); + testView->SetStyle(STYLE_BACKGROUND_COLOR, Color::Blue().full); + testView->SetTouchable(touchable); + testView->SetDraggable(draggable); + testView->SetDragParentInstead(dragParent); + UILabel* label1 = new UILabel(); + container_->Add(label1); + label1->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE + TEST_VIEW_GAP, + positionY_ + 2 * GAP, TEXT_W, TEXT_H); // 2 : ratio + label1->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + testView->SetLabel(label1); + + container_->Add(btn); + btn->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE + 3 * TEST_VIEW_GAP, positionY_ + GAP, // 3 : ratio + BUTTON_WIDHT2, BUTTON_HEIGHT2); + btn->SetText(btnTitle); + btn->SetFont(DEFAULT_VECTOR_FONT_FILENAME, BUTTON_LABEL_SIZE); + btn->SetOnClickListener(this); + btn->SetStyleForState(STYLE_BORDER_RADIUS, BUTTON_STYLE_BORDER_RADIUS_VALUE, UIButton::RELEASED); + btn->SetStyleForState(STYLE_BORDER_RADIUS, BUTTON_STYLE_BORDER_RADIUS_VALUE, UIButton::PRESSED); + btn->SetStyleForState(STYLE_BORDER_RADIUS, BUTTON_STYLE_BORDER_RADIUS_VALUE, UIButton::INACTIVE); + btn->SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::RELEASED); + btn->SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::PRESSED); + btn->SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::INACTIVE); + + positionY_ += ITEM_H; + } +} + +void UITestEventInjector::SetUpScrollView() +{ + if (container_ == nullptr) { + return; + } + UILabel* label = GetTitleLabel("模拟Drag事件测试"); + container_->Add(label); + label->SetPosition(TEXT_DISTANCE_TO_LEFT_SIDE, positionY_); + SetLastPos(label); + + scrollTestView_ = new UIScrollView(); + scrollTestView_->SetStyle(STYLE_BACKGROUND_COLOR, Color::Red().full); + scrollTestView_->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, positionY_, SCROLL_WIDTH, SCROLL_HEIGHT); + scrollTestView_->SetXScrollBarVisible(true); + scrollTestView_->SetYScrollBarVisible(true); + scrollTestView_->SetThrowDrag(true); + container_->Add(scrollTestView_); + UILabelButton* button1 = new UILabelButton(); + button1->SetText("button1"); + button1->SetPosition(0, 0, SCROLL_BUTTON_W, SCROLL_BUTTON_H); + UILabelButton* button2 = new UILabelButton(); + button2->SetText("button2"); + button2->SetPosition(0, SCROLL_BUTTON_H, SCROLL_BUTTON_W, SCROLL_BUTTON_H); + scrollTestView_->Add(button1); + scrollTestView_->Add(button2); + + if (layout_ == nullptr) { + layout_ = new GridLayout(); + layout_->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE + SCROLL_WIDTH + GAP, positionY_, + LAYOUT_WIDTH, LAYOUT_HEIGHT); + container_->Add(layout_); + layout_->SetLayoutDirection(LAYOUT_VER); + layout_->SetRows(3); // 3 : rows + layout_->SetCols(3); // 3 : columns + } +} + +void UITestEventInjector::SetLastPos(UIView* view) +{ + if (view == nullptr) { + return; + } + lastX_ = view->GetX(); + lastY_ = view->GetY() + view->GetHeight(); + positionY_ = lastY_ + 8 * GAP; /* 8:ratio */ +} + +void UITestEventInjector::SetUpButton(UILabelButton* btn, const char* title) +{ + if (btn == nullptr) { + return; + } + layout_->Add(btn); + btn->Resize(BUTTON_WIDHT3, BUTTON_HEIGHT3); + btn->SetText(title); + btn->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + btn->SetOnClickListener(this); + btn->SetStyleForState(STYLE_BORDER_RADIUS, BUTTON_STYLE_BORDER_RADIUS_VALUE, UIButton::RELEASED); + btn->SetStyleForState(STYLE_BORDER_RADIUS, BUTTON_STYLE_BORDER_RADIUS_VALUE, UIButton::PRESSED); + btn->SetStyleForState(STYLE_BORDER_RADIUS, BUTTON_STYLE_BORDER_RADIUS_VALUE, UIButton::INACTIVE); + btn->SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::RELEASED); + btn->SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::PRESSED); + btn->SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::INACTIVE); +} + +void UITestEventInjector::SetDragTimeDisplay(uint16_t dragTime) +{ + std::string dragTimestr = "dragTime:" + std::to_string(dragTime); + DragTimeDisplayBtn_->SetText(dragTimestr.c_str()); + DragTimeDisplayBtn_->Invalidate(); +} + +/* drag range:0 < x < 300, 62 < y < 362, click range:20 < x < 60, 392 < y < 432 */ +bool UITestEventInjector::OnClick(UIView& view, const ClickEvent& event) +{ + Point point; + Point scrollPoint; + if (&view == clickBtn_) { + point = {static_cast(clickTestView_->GetRect().GetX() + TEST_VIEW_W / POINT_OFFSET), + clickBtn_->GetRect().GetBottom()}; + EventInjector::GetInstance()->SetClickEvent(point); + } else if (&view == dragBtn_) { + point = {static_cast(dragTestView_->GetRect().GetX() + TEST_VIEW_W / POINT_OFFSET), + dragBtn_->GetRect().GetBottom()}; + Point endPoint = {static_cast(point.x + (RATIO_X * TEST_VIEW_W) / POINT_OFFSET), point.y}; + // 80:ms + EventInjector::GetInstance()->SetDragEvent(point, endPoint, 80); + } else if (&view == longPressBtn_) { + point = {static_cast(longPressTestView_->GetRect().GetX() + TEST_VIEW_W / POINT_OFFSET), + longPressBtn_->GetRect().GetBottom()}; + EventInjector::GetInstance()->SetLongPressEvent(point); + } else if (&view == keyBtn_) { + // 26:key id + uint16_t keyId = 26; + EventInjector::GetInstance()->SetKeyEvent(keyId, InputDevice::STATE_PRESS); + } else if (&view == upToDownBtn_) { + scrollPoint = {scrollTestView_->GetRect().GetX(), scrollTestView_->GetRect().GetY()}; + Point startPoint = {static_cast(scrollPoint.x + SCROLL_WIDTH / RATIO_X), + static_cast(scrollPoint.y + SCROLL_HEIGHT / RATIO_Y)}; + Point endPoint = {static_cast(scrollPoint.x + SCROLL_WIDTH / RATIO_X), + static_cast(scrollPoint.y + SCROLL_HEIGHT / RATIO_X)}; + EventInjector::GetInstance()->SetDragEvent(startPoint, endPoint, dragTime_); + } else if (&view == downToUpBtn_) { + scrollPoint = {scrollTestView_->GetRect().GetX(), scrollTestView_->GetRect().GetY()}; + Point startPoint = {static_cast(scrollPoint.x + SCROLL_WIDTH / RATIO_X), + static_cast(scrollPoint.y + SCROLL_HEIGHT / RATIO_X)}; + Point endPoint = {static_cast(scrollPoint.x + SCROLL_WIDTH / RATIO_X), + static_cast(scrollPoint.y + SCROLL_HEIGHT / RATIO_Y)}; + EventInjector::GetInstance()->SetDragEvent(startPoint, endPoint, dragTime_); + } else if (&view == leftToRightBtn_) { + scrollPoint = {scrollTestView_->GetRect().GetX(), scrollTestView_->GetRect().GetY()}; + Point startPoint = {static_cast(scrollPoint.x + SCROLL_WIDTH / RATIO_Y), + static_cast(scrollPoint.y + SCROLL_HEIGHT / RATIO_X)}; + Point endPoint = {static_cast(scrollPoint.x + SCROLL_WIDTH / RATIO_X), + static_cast(scrollPoint.y + SCROLL_HEIGHT / RATIO_X)}; + EventInjector::GetInstance()->SetDragEvent(startPoint, endPoint, dragTime_); + } else if (&view == rightToLeftBtn_) { + scrollPoint = {scrollTestView_->GetRect().GetX(), scrollTestView_->GetRect().GetY()}; + Point startPoint = {static_cast(scrollPoint.x + SCROLL_WIDTH / RATIO_X), + static_cast(scrollPoint.y + SCROLL_HEIGHT / RATIO_X)}; + Point endPoint = {static_cast(scrollPoint.x + SCROLL_WIDTH / RATIO_Y), + static_cast(scrollPoint.y + SCROLL_HEIGHT / RATIO_X)}; + EventInjector::GetInstance()->SetDragEvent(startPoint, endPoint, dragTime_); + } else if (&view == uLeftTolRightBtn_) { + scrollPoint = {scrollTestView_->GetRect().GetX(), scrollTestView_->GetRect().GetY()}; + Point startPoint = {static_cast(scrollPoint.x + SCROLL_WIDTH / RATIO_Y), + static_cast(scrollPoint.y + SCROLL_HEIGHT / RATIO_Y)}; + Point endPoint = {static_cast(scrollPoint.x + SCROLL_WIDTH / RATIO_X), + static_cast(scrollPoint.y + SCROLL_HEIGHT / RATIO_X)}; + EventInjector::GetInstance()->SetDragEvent(startPoint, endPoint, dragTime_); + } else if (&view == lRightTouLeftBtn_) { + scrollPoint = {scrollTestView_->GetRect().GetX(), scrollTestView_->GetRect().GetY()}; + Point startPoint = {static_cast(scrollPoint.x + SCROLL_WIDTH / RATIO_X), + static_cast(scrollPoint.y + SCROLL_HEIGHT / RATIO_X)}; + Point endPoint = {static_cast(scrollPoint.x + SCROLL_WIDTH / RATIO_Y), + static_cast(scrollPoint.y + SCROLL_HEIGHT / RATIO_Y)}; + EventInjector::GetInstance()->SetDragEvent(startPoint, endPoint, dragTime_); + } else if (&view == increaseDragTimeBtn_) { + dragTime_ += DRAG_TIME_OFFSET; + SetDragTimeDisplay(dragTime_); + } else if (&view == decreaseDragTimeBtn_) { + dragTime_ -= DRAG_TIME_OFFSET; + SetDragTimeDisplay(dragTime_); + } + return true; +} +} // namespace OHOS +#endif // ENABLE_DEBUG \ No newline at end of file diff --git a/test/uitest/test_event_injector/ui_test_event_injector.h b/test/uitest/test_event_injector/ui_test_event_injector.h new file mode 100755 index 0000000..ba9507c --- /dev/null +++ b/test/uitest/test_event_injector/ui_test_event_injector.h @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef UI_TEST_EVENT_INJECTOR_H +#define UI_TEST_EVENT_INJECTOR_H + +#include "graphic_config.h" + +#if ENABLE_DEBUG +#include "components/root_view.h" +#include "components/ui_label.h" +#include "components/ui_label_button.h" +#include "components/ui_scroll_view.h" +#include "dock/input_device.h" +#include "ui_test.h" +#include "layout/grid_layout.h" + +namespace OHOS { +class TestEventInjectorView; +class UITestEventInjector : public UITest, public UIView::OnClickListener { +public: + UITestEventInjector() {} + ~UITestEventInjector() {} + void SetUp() override; + void TearDown() override; + UIView* GetTestView() override; + bool OnClick(UIView& view, const ClickEvent& event) override; + + void UIKit_Event_Injector_Click_Event_001(); + void UIKit_Event_Injector_Drag_Event_002(); + void UIKit_Event_Injector_Long_Press_Event_003(); + void UIKit_Event_Injector_Key_Event_004(); + void UIKit_Event_Injector_Up_to_Down_005(); + void UIKit_Event_Injector_Down_to_Up_006(); + void UIKit_Event_Injector_Left_to_Right_007(); + void UIKit_Event_Injector_Right_to_Left_008(); + void UIKit_Event_Injector_ULeft_to_LRight_009(); + void UIKit_Event_Injector_LRight_to_ULeft_010(); + +private: + UIScrollView* container_ = nullptr; + GridLayout* layout_ = nullptr; + UILabelButton* clickBtn_ = nullptr; + UILabelButton* dragBtn_ = nullptr; + UILabelButton* longPressBtn_ = nullptr; + UILabelButton* keyBtn_ = nullptr; + UILabelButton* upToDownBtn_ = nullptr; + UILabelButton* downToUpBtn_ = nullptr; + UILabelButton* leftToRightBtn_ = nullptr; + UILabelButton* rightToLeftBtn_ = nullptr; + UILabelButton* uLeftTolRightBtn_ = nullptr; + UILabelButton* lRightTouLeftBtn_ = nullptr; + UILabelButton* increaseDragTimeBtn_ = nullptr; + UILabelButton* decreaseDragTimeBtn_ = nullptr; + UILabelButton* DragTimeDisplayBtn_ = nullptr; + TestEventInjectorView* clickTestView_ = nullptr; + TestEventInjectorView* dragTestView_ = nullptr; + TestEventInjectorView* longPressTestView_ = nullptr; + TestEventInjectorView* KeyEventTestView_ = nullptr; + UIScrollView* scrollTestView_ = nullptr; + + int16_t lastX_ = 0; + int16_t lastY_ = 0; + uint16_t dragTime_ = 100; // 100: 100ms + + void InnerTest(const char* title, bool touchable, bool draggable, bool dragParent, + const char* btnTitle, UILabelButton* btn, TestEventInjectorView*& testView); + void SetLastPos(UIView* view); + void SetUpButton(UILabelButton* btn, const char* title); + void SetUpScrollView(); + void IncreaseDragTime(); + void DecreaseDragTime(); + void DragTimeDisplay(); + void SetDragTimeDisplay(uint16_t dragTime); +}; +} // namespace OHOS +#endif // UI_TEST_EVENT_INJECTOR_H +#endif // ENABLE_DEBUG diff --git a/test/uitest/test_font/ui_test_font.cpp b/test/uitest/test_font/ui_test_font.cpp new file mode 100755 index 0000000..a40f53b --- /dev/null +++ b/test/uitest/test_font/ui_test_font.cpp @@ -0,0 +1,208 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ui_test_font.h" +#include "common/screen.h" +#include "components/ui_checkbox.h" +#include "components/ui_label.h" +#include "font/ui_font.h" + +namespace OHOS { +namespace { +const int16_t GAP = 15; +const int16_t TITLE_HEIGHT = 20; +const uint16_t LABEL_WIDTH = 400; +const uint16_t LABEL_HEIGHT = 50; +const uint16_t BUF_SIZE = 200; +static uint8_t g_newFontPsramBaseAddr[OHOS::MIN_FONT_PSRAM_LENGTH]; +} // namespace + +void UITestFont::SetUp() +{ + if (container_ == nullptr) { + container_ = new UIScrollView(); + container_->SetThrowDrag(true); + container_->SetHorizontalScrollState(false); + container_->Resize(Screen::GetInstance().GetWidth(), Screen::GetInstance().GetHeight() - LABEL_HEIGHT); + positionX_ = 50; // 50: init position x + positionY_ = 5; // 5: init position y + } +} + +void UITestFont::TearDown() +{ + DeleteChildren(container_); + container_ = nullptr; +} + +void UITestFont::InnerTestTitle(const char* title) +{ + UILabel* titleLabel = new UILabel(); + titleLabel->SetPosition(TEXT_DISTANCE_TO_LEFT_SIDE, positionY_, Screen::GetInstance().GetWidth(), + TITLE_LABEL_DEFAULT_HEIGHT); + titleLabel->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + titleLabel->SetText(title); + container_->Add(titleLabel); + positionY_ += TITLE_HEIGHT + GAP; +} + +UIView* UITestFont::GetTestView() +{ + Font_FontEngine_FontConvert_Test_FontTestSetFontId_001(); + Font_FontEngine_FontConvert_Test_FontTestSetFont_001(); + Font_FontEngine_FontConvert_Test_FontTestGetFontHeight_001(); + Font_FontEngine_FontConvert_Test_FontTestGetFontVersion_001(); + Font_FontEngine_FontConvert_Test_FontTestGetFontId_001(); + Font_FontEngine_FontConvert_Test_FontTestGetFontHeight_001(); + Font_FontEngine_FontConvert_Test_FontTestPsram_001(); + Font_FontEngine_FontConvert_Test_FontTestFontPath_001(); + return container_; +} + +void UITestFont::Font_FontEngine_FontConvert_Test_FontTestPsram_001() +{ + if (container_ != nullptr) { + InnerTestTitle("Test Psram"); + UIFont::GetInstance()->SetPsramMemory(reinterpret_cast(g_newFontPsramBaseAddr), + OHOS::MIN_FONT_PSRAM_LENGTH); + UILabel* label = new UILabel(); + label->SetPosition(positionX_, positionY_); + label->Resize(LABEL_WIDTH, LABEL_HEIGHT); + label->SetLineBreakMode(UILabel::LINE_BREAK_WRAP); + label->SetFont(DEFAULT_VECTOR_FONT_FILENAME, 18); // 18 : size + char buf[BUF_SIZE] = {0}; + label->SetText(reinterpret_cast(buf)); + container_->Add(label); + positionY_ += LABEL_HEIGHT + GAP; + } +} + +void UITestFont::Font_FontEngine_FontConvert_Test_FontTestFontPath_001() +{ + if (container_ != nullptr) { + InnerTestTitle("Test Font Path"); + std::string dpath; + std::string spath; + size_t len = dpath.size(); + size_t pos = dpath.find_last_of('\\'); + dpath.replace((pos + 1), (len - pos), "..\\..\\config\\font.bin"); + spath.replace((pos + 1), (len - pos), "..\\..\\config\\glyphs.bin"); + UILabel* label = new UILabel(); + label->SetPosition(positionX_, positionY_); + label->Resize(LABEL_WIDTH, LABEL_HEIGHT); + char buf[BUF_SIZE] = {0}; + label->SetFont(DEFAULT_VECTOR_FONT_FILENAME, 18); // 18 : size + label->SetText(reinterpret_cast(buf)); + container_->Add(label); + positionY_ += LABEL_HEIGHT + GAP; + } +} + +void UITestFont::Font_FontEngine_FontConvert_Test_FontTestSetFontId_001() +{ + if (container_ != nullptr) { + InnerTestTitle("Test Set Font Id"); + UILabel* label = new UILabel(); + label->SetPosition(positionX_, positionY_); + label->Resize(LABEL_WIDTH, LABEL_HEIGHT); + UIFont::GetInstance()->SetCurrentFontId(0); + label->SetFontId(0); + label->SetText("Hello!\nOHOS UIKIT!"); + label->SetLineBreakMode(UILabel::LINE_BREAK_WRAP); + container_->Add(label); + positionY_ += LABEL_HEIGHT + GAP; + } +} + +void UITestFont::Font_FontEngine_FontConvert_Test_FontTestSetFont_001() +{ + if (container_ != nullptr) { + InnerTestTitle("Test Set Font"); + UILabel* label = new UILabel(); + label->SetPosition(positionX_, positionY_); + label->Resize(LABEL_WIDTH, LABEL_HEIGHT); + uint8_t fontId = UIFont::GetInstance()->GetFontId("RobotoCondensed-Regular"); + UIFont::GetInstance()->SetCurrentFontId(fontId, 18); // 18: means font size + label->SetFont("RobotoCondensed-Regular", 18); // 18: means font size + label->SetText("Hello!\nOHOS UIKIT!"); + label->SetLineBreakMode(UILabel::LINE_BREAK_WRAP); + container_->Add(label); + positionY_ += LABEL_HEIGHT + GAP; + } +} + +void UITestFont::Font_FontEngine_FontConvert_Test_FontTestGetFontHeight_001() +{ + if (container_ != nullptr) { + InnerTestTitle("Test Get Font Height"); + UILabel* label = new UILabel(); + label->SetFont(DEFAULT_VECTOR_FONT_FILENAME, 18); // 18 : size + char buf[BUF_SIZE] = {0}; + label->SetText(reinterpret_cast(buf)); + label->SetPosition(positionX_, positionY_); + label->Resize(LABEL_WIDTH, LABEL_HEIGHT); + positionY_ += LABEL_HEIGHT + GAP; + container_->Add(label); + } +} + +void UITestFont::Font_FontEngine_FontConvert_Test_FontTestGetFontVersion_001() +{ + if (container_ != nullptr) { + InnerTestTitle("Test Get Font Version"); + UILabel* label = new UILabel(); + label->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + char buf[BUF_SIZE] = {0}; + label->SetText(reinterpret_cast(buf)); + label->SetPosition(positionX_, positionY_); + label->Resize(LABEL_WIDTH, LABEL_HEIGHT); + positionY_ += LABEL_HEIGHT + GAP; + container_->Add(label); + } +} + +void UITestFont::Font_FontEngine_FontConvert_Test_FontTestGetFontId_001() +{ + if (container_ != nullptr) { + InnerTestTitle("Test Get Font Id"); + UILabel* label = new UILabel(); + label->SetFont("HYQiHei-65S", 18); // 18: means font size + UIFont::GetInstance()->GetFontId("HYQiHei-65S", 18); // 18: means font size + char buf[BUF_SIZE] = {0}; + label->SetText(reinterpret_cast(buf)); + label->SetPosition(positionX_, positionY_); + label->Resize(LABEL_WIDTH, LABEL_HEIGHT); + positionY_ += LABEL_HEIGHT + GAP; + container_->Add(label); + } +} + +void UITestFont::Font_FontEngine_FontConvert_Test_FontTestGetFontHeader_001() +{ + if (container_ != nullptr) { + InnerTestTitle("Test Get Font Header"); + UILabel* label = new UILabel(); + label->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + FontHeader fontHeader; + UIFont::GetInstance()->GetCurrentFontHeader(fontHeader); + char buf[BUF_SIZE] = {0}; + label->SetText(reinterpret_cast(buf)); + label->SetPosition(positionX_, positionY_); + label->Resize(LABEL_WIDTH, LABEL_HEIGHT); + positionY_ += LABEL_HEIGHT + GAP; + container_->Add(label); + } +} +} // namespace OHOS \ No newline at end of file diff --git a/test/uitest/test_font/ui_test_font.h b/test/uitest/test_font/ui_test_font.h new file mode 100755 index 0000000..c4471da --- /dev/null +++ b/test/uitest/test_font/ui_test_font.h @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef UI_TEST_FONT_H +#define UI_TEST_FONT_H + +#include "components/ui_scroll_view.h" +#include "font/ui_font.h" +#include "ui_test.h" + +namespace OHOS { +class UITestFont : public UITest { +public: + UITestFont() {} + ~UITestFont() {} + void SetUp() override; + void TearDown() override; + UIView* GetTestView() override; + + /** + * @brief Test psram + */ + void Font_FontEngine_FontConvert_Test_FontTestPsram_001(); + + /** + * @brief Test Font path + */ + void Font_FontEngine_FontConvert_Test_FontTestFontPath_001(); + + /** + * @brief Test Set Font Id + */ + void Font_FontEngine_FontConvert_Test_FontTestSetFontId_001(); + + /** + * @brief Test Set Font + */ + void Font_FontEngine_FontConvert_Test_FontTestSetFont_001(); + + /** + * @brief Test Get Font Height + */ + void Font_FontEngine_FontConvert_Test_FontTestGetFontHeight_001(); + + /** + * @brief Test get font version + */ + void Font_FontEngine_FontConvert_Test_FontTestGetFontVersion_001(); + + /** + * @brief Test get font Id + */ + void Font_FontEngine_FontConvert_Test_FontTestGetFontId_001(); + + /** + * @brief Test get font header + */ + void Font_FontEngine_FontConvert_Test_FontTestGetFontHeader_001(); + +private: + UIScrollView* container_ = nullptr; + void InnerTestTitle(const char* title); +}; +} // namespace OHOS +#endif // UI_TEST_FONT_H diff --git a/test/uitest/test_image/ui_test_image.cpp b/test/uitest/test_image/ui_test_image.cpp new file mode 100755 index 0000000..fe21ed1 --- /dev/null +++ b/test/uitest/test_image/ui_test_image.cpp @@ -0,0 +1,518 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ui_test_image.h" +#include "common/screen.h" +#include "components/ui_label.h" +#include "default_resource/check_box_res.h" +#ifdef VERSION_LITE +#include "components/ui_image_view.h" +#include "test_resource_config.h" +#endif +#include "imgdecode/image_load.h" + +namespace OHOS { +namespace { +int16_t g_height = 0; +} + +void UITestImage::SetUp() +{ + g_height = 0; + if (container_ == nullptr) { + container_ = new UIScrollView(); + container_->Resize(Screen::GetInstance().GetWidth(), Screen::GetInstance().GetHeight() - BACK_BUTTON_HEIGHT); + container_->SetHorizontalScrollState(false); + } +} + +void UITestImage::TearDown() +{ + DeleteChildren(container_); + container_ = nullptr; +#ifndef VERSION_LITE + gifImageView_ = nullptr; + gifToGif_ = nullptr; + gifToJpeg_ = nullptr; + gifToPng_ = nullptr; + gifToBin01_ = nullptr; + gifToBin02_ = nullptr; + gifToBin03_ = nullptr; + gifToBin04_ = nullptr; +#endif +} + +UIView* UITestImage::GetTestView() +{ + UIKit_UIImage_Test_SetImage_001(); + UIKit_UIImage_Test_SetImage_002(); + UIKit_UIImage_Test_SetImage_004(); + UIKit_UIImage_Test_SetImage_005(); + UIKit_UIImage_Test_SetImage_006(); + UIKit_UIImage_Test_SetImage_007(); + UIKit_UIImage_Test_SetImage_008(); + UIKit_UIImage_Test_SetImage_009(); + UIKit_UIImage_Test_SetImage_010(); + UIKit_UIImage_Test_SetImage_011(); + UIKit_UIImage_Test_SetImage_012(); + UIKit_UIImage_Test_SetImage_013(); + UIKit_UIImage_Test_SetImage_014(); +#ifndef VERSION_LITE + UIKit_UIImage_Test_SetImage_015(); + UIKit_UIImage_Test_SetImage_016(); +#endif + UIKit_UIImage_Test_Uncompress_001(); + return container_; +} + +const char* UITestImage::GetCharByImageSrcType(ImageSrcType srcType) const +{ + if (srcType == IMG_SRC_VARIABLE) { + return "the src of image is variable type"; + } else if (srcType == IMG_SRC_FILE) { + return "the src of image is file type"; + } else { + return "Null"; + } +} + +void UITestImage::UIKit_UIImage_Test_SetImage_001() +{ + if (container_ != nullptr) { + UILabel* label = new UILabel(); + container_->Add(label); + label->SetPosition(TEXT_DISTANCE_TO_LEFT_SIDE, g_height + 5, // 5: increase y-coordinate + Screen::GetInstance().GetWidth(), TITLE_LABEL_DEFAULT_HEIGHT); + label->SetText("rgb888 格式图片测试 "); + label->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + + UIImageView* imageView = new UIImageView(); + imageView->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, g_height + 40); // 40 : offset + imageView->SetWidth(50); // 50 : size + imageView->SetHeight(50); // 50 : size + imageView->SetSrc(BLUE_RGB888_IMAGE_PATH); + + UILabel* label2 = new UILabel(); + // 160 : offset 65 : offset 300 : offset + label2->SetPosition(160, g_height + 65, 300, TITLE_LABEL_DEFAULT_HEIGHT); + label2->SetText(GetCharByImageSrcType(imageView->GetSrcType())); + label2->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + label2->SetStyle(STYLE_TEXT_COLOR, Color::Black().full); + label2->SetStyle(STYLE_BACKGROUND_COLOR, Color::Yellow().full); + label2->SetStyle(STYLE_BACKGROUND_OPA, OPA_OPAQUE); + + container_->Add(label2); + container_->Add(imageView); + g_height += 130; // offset=130 + } +} + +void UITestImage::UIKit_UIImage_Test_SetImage_002() +{ + if (container_ != nullptr) { + UILabel* label = new UILabel(); + container_->Add(label); + label->SetPosition(TEXT_DISTANCE_TO_LEFT_SIDE, g_height, Screen::GetInstance().GetWidth(), + TITLE_LABEL_DEFAULT_HEIGHT); + label->SetText("显示ARGB8888bin文件图片 "); + label->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + + UIImageView* imageView = new UIImageView(); + imageView->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, g_height + 30); // 30: increase offset + imageView->SetWidth(50); // 50 : size + imageView->SetHeight(50); // 50 : size + imageView->SetSrc(BLUE_ARGB8888_IMAGE_PATH); + + container_->Add(imageView); + g_height += 120; // offset=120 + } +} + +void UITestImage::UIKit_UIImage_Test_SetImage_004() +{ + if (container_ != nullptr) { + UILabel* label = new UILabel(); + container_->Add(label); + label->SetPosition(TEXT_DISTANCE_TO_LEFT_SIDE, g_height + 5, // 5: increase offset + Screen::GetInstance().GetWidth(), TITLE_LABEL_DEFAULT_HEIGHT); // 5: increase offset + label->SetText("显示RGB565bin文件图片 "); + label->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + + UIImageView* imageView = new UIImageView(); + imageView->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, g_height + 35); // 35 : offset + imageView->SetWidth(50); // 50 : size + imageView->SetHeight(50); // 50 : size + imageView->SetSrc(BLUE_RGB565_IMAGE_PATH); + imageView->GetHeight(); + + container_->Add(imageView); + g_height += 140; // offset=140 + } +} + +void UITestImage::UIKit_UIImage_Test_SetImage_005() +{ + if (container_ != nullptr) { + UILabel* label = new UILabel(); + container_->Add(label); + label->SetPosition(TEXT_DISTANCE_TO_LEFT_SIDE, g_height, Screen::GetInstance().GetWidth(), + TITLE_LABEL_DEFAULT_HEIGHT); + label->SetText("显示ARGB8888 数组型图片 "); + label->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + + UIImageView* imageView = new UIImageView(); + imageView->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, g_height + 40); // 40 : increase offset + imageView->SetWidth(50); // 50 : size + imageView->SetHeight(50); // 50 : size + imageView->SetSrc(GetCheckBoxOnInfo()); + imageView->GetHeight(); + + UIImageView* imageView2 = new UIImageView(); + imageView2->SetPosition(110, g_height + 40); // 110 : offset 40 : increase offset + imageView2->SetWidth(50); // 50 : size + imageView2->SetHeight(50); // 50 : size + imageView2->SetSrc(GetCheckBoxOffInfo()); + imageView2->GetHeight(); + + container_->Add(imageView); + container_->Add(imageView2); + g_height += 100; // offset=100 + } +} + +void UITestImage::UIKit_UIImage_Test_SetImage_006() +{ + if (container_ != nullptr) { + UILabel* label = new UILabel(); + container_->Add(label); + label->SetPosition(TEXT_DISTANCE_TO_LEFT_SIDE, g_height, Screen::GetInstance().GetWidth(), + TITLE_LABEL_DEFAULT_HEIGHT); + label->SetText("显示24位真彩色jpg图片 "); + label->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + + UIImageView* imageView = new UIImageView(); + imageView->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, g_height + 30); // 30 : offset + imageView->SetWidth(50); // 50 : size + imageView->SetHeight(50); // 50 : size + imageView->SetSrc(JPEG_IMAGE_PATH); + imageView->GetHeight(); + container_->Add(imageView); + g_height += 190; // offset=190 + } +} + +void UITestImage::UIKit_UIImage_Test_SetImage_007() +{ + if (container_ != nullptr) { + UILabel* label = new UILabel(); + container_->Add(label); + label->SetPosition(TEXT_DISTANCE_TO_LEFT_SIDE, g_height, Screen::GetInstance().GetWidth(), + TITLE_LABEL_DEFAULT_HEIGHT); + label->SetText("显示位深1索引png图片 "); + label->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + + UIImageView* imageView = new UIImageView(); + imageView->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, g_height + 30); // 30 : offset + imageView->SetWidth(50); // 50 : size + imageView->SetHeight(50); // 50 : size + imageView->SetSrc(PNG_1_PALETTE_IMAGE_PATH); + imageView->GetHeight(); + container_->Add(imageView); + g_height += 90; // offset=90 + } +} + +void UITestImage::UIKit_UIImage_Test_SetImage_008() +{ + if (container_ != nullptr) { + UILabel* label = new UILabel(); + container_->Add(label); + label->SetPosition(TEXT_DISTANCE_TO_LEFT_SIDE, g_height, Screen::GetInstance().GetWidth(), + TITLE_LABEL_DEFAULT_HEIGHT); + label->SetText("显示位深2索引png图片 "); + label->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + + UIImageView* imageView = new UIImageView(); + imageView->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, g_height + 30); // 30 : offset + imageView->SetWidth(50); // 50 : size + imageView->SetHeight(50); // 50 : size + imageView->SetSrc(PNG_2_PALETTE_IMAGE_PATH); + imageView->GetHeight(); + container_->Add(imageView); + g_height += 90; // offset=90 + } +} + +void UITestImage::UIKit_UIImage_Test_SetImage_009() +{ + if (container_ != nullptr) { + UILabel* label = new UILabel(); + container_->Add(label); + label->SetPosition(TEXT_DISTANCE_TO_LEFT_SIDE, g_height, Screen::GetInstance().GetWidth(), + TITLE_LABEL_DEFAULT_HEIGHT); + label->SetText("显示位深4索引png图片 "); + label->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + + UIImageView* imageView = new UIImageView(); + imageView->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, g_height + 30); // 30 : offset + imageView->SetWidth(50); // 50 : size + imageView->SetHeight(50); // 50 : size + imageView->SetSrc(PNG_4_PALETTE_IMAGE_PATH); + imageView->GetHeight(); + container_->Add(imageView); + g_height += 100; // offset=100 + } +} + +void UITestImage::UIKit_UIImage_Test_SetImage_010() +{ + if (container_ != nullptr) { + UILabel* label = new UILabel(); + container_->Add(label); + label->SetPosition(TEXT_DISTANCE_TO_LEFT_SIDE, g_height, Screen::GetInstance().GetWidth(), + TITLE_LABEL_DEFAULT_HEIGHT); + label->SetText("显示位深8索引png图片 "); + label->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + + UIImageView* imageView = new UIImageView(); + imageView->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, g_height + 30); // 30 : offset + imageView->SetWidth(50); // 50 : size + imageView->SetHeight(50); // 50 : size + imageView->SetSrc(PNG_8_PALETTE_IMAGE_PATH); + imageView->GetHeight(); + container_->Add(imageView); + g_height += 100; // offset=100 + } +} + +void UITestImage::UIKit_UIImage_Test_SetImage_011() +{ + if (container_ != nullptr) { + UILabel* label = new UILabel(); + container_->Add(label); + label->SetPosition(TEXT_DISTANCE_TO_LEFT_SIDE, g_height, Screen::GetInstance().GetWidth(), + TITLE_LABEL_DEFAULT_HEIGHT); + label->SetText("显示位深24真彩色png图片 "); + label->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + + UIImageView* imageView = new UIImageView(); + imageView->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, g_height + 30); // 30 : offset + imageView->SetWidth(50); // 50 : size + imageView->SetHeight(50); // 50 : size + imageView->SetSrc(PNG_24_RGB_IMAGE_PATH); + imageView->GetHeight(); + container_->Add(imageView); + g_height += 100; // offset=100 + } +} + +void UITestImage::UIKit_UIImage_Test_SetImage_012() +{ + if (container_ != nullptr) { + UILabel* label = new UILabel(); + container_->Add(label); + label->SetPosition(TEXT_DISTANCE_TO_LEFT_SIDE, g_height, Screen::GetInstance().GetWidth(), + TITLE_LABEL_DEFAULT_HEIGHT); + label->SetText("显示位深32真彩色带Alpha通道png图片 "); + label->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + + UIImageView* imageView = new UIImageView(); + imageView->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, g_height + 30); // 30 : offset + imageView->SetWidth(50); // 50 : size + imageView->SetHeight(50); // 50 : size + imageView->SetSrc(PNG_32_RGB_ALPHA_IMAGE_PATH); + imageView->GetHeight(); + container_->Add(imageView); + g_height += 100; // offset=100 + } +} + +void UITestImage::UIKit_UIImage_Test_SetImage_013() +{ + if (container_ != nullptr) { + UILabel* label = new UILabel(); + container_->Add(label); + label->SetPosition(TEXT_DISTANCE_TO_LEFT_SIDE, g_height, Screen::GetInstance().GetWidth(), + TITLE_LABEL_DEFAULT_HEIGHT); + label->SetText("显示位深8灰度png图片 "); + label->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + + UIImageView* imageView = new UIImageView(); + imageView->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, g_height + 30); // 30 : offset + imageView->SetWidth(50); // 50 : size + imageView->SetHeight(50); // 50 : size + imageView->SetSrc(PNG_8_GRAY_IMAGE_PATH); + imageView->GetHeight(); + container_->Add(imageView); + g_height += 100; // offset=100 + } +} + +void UITestImage::UIKit_UIImage_Test_SetImage_014() +{ + if (container_ != nullptr) { + UILabel* label = new UILabel(); + container_->Add(label); + label->SetPosition(TEXT_DISTANCE_TO_LEFT_SIDE, g_height, Screen::GetInstance().GetWidth(), + TITLE_LABEL_DEFAULT_HEIGHT); + label->SetText("显示位深16灰度png图片 "); + label->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + + UIImageView* imageView = new UIImageView(); + imageView->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, g_height + 30); // 30 : offset + imageView->SetWidth(50); // 50 : size + imageView->SetHeight(50); // 50 : size + imageView->SetSrc(PNG_16_GRAY_IMAGE_PATH); + imageView->GetHeight(); + container_->Add(imageView); + g_height += 100; // offset=100 + } +} + +#ifndef VERSION_LITE +void UITestImage::UIKit_UIImage_Test_SetImage_015() +{ + if (container_ != nullptr) { + UILabel* label = new UILabel(); + container_->Add(label); + label->SetPosition(TEXT_DISTANCE_TO_LEFT_SIDE, g_height, Screen::GetInstance().GetWidth(), + TITLE_LABEL_DEFAULT_HEIGHT); + label->SetText("显示gif文件图片 "); + label->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + + UIImageView* imageView = new UIImageView(); + imageView->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, g_height + 30); // 30: Y-coordinate + imageView->SetSrc(GIF_IMAGE_PATH1); + container_->Add(imageView); + + UIImageView* imageView2 = new UIImageView(); + imageView2->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, g_height + 120); // 120: Y-coordinate + imageView2->SetSrc(GIF_IMAGE_PATH2); + container_->Add(imageView2); + g_height += 180; // offset=180 + } +} + +void UITestImage::UIKit_UIImage_Test_SetImage_016() +{ + if (container_ != nullptr) { + UILabel* label = new UILabel(); + container_->Add(label); + label->SetPosition(TEXT_DISTANCE_TO_LEFT_SIDE, g_height, Screen::GetInstance().GetWidth(), + TITLE_LABEL_DEFAULT_HEIGHT); + label->SetText("不同类型图片切换 "); + label->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + gifImageView_ = new UIImageView(); + gifImageView_->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, 1790); // 1790: Y-coordinate + gifImageView_->SetSrc(GIF_IMAGE_PATH1); + container_->Add(gifImageView_); + gifToGif_ = new UILabelButton(); + SetUpButton(gifToGif_, "切换GIF"); + uint16_t gifHeight = g_height + 30; // 30: offset + gifToGif_->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, gifHeight); + gifToJpeg_ = new UILabelButton(); + SetUpButton(gifToJpeg_, "切换JPEG"); + gifToJpeg_->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE + 160, gifHeight); // 160: offset + gifToPng_ = new UILabelButton(); + SetUpButton(gifToPng_, "切换PNG"); + gifToPng_->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE + 320, gifHeight); // 320: offset + gifToBin01_ = new UILabelButton(); + SetUpButton(gifToBin01_, "切换rgb888"); + gifToBin01_->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE + 480, gifHeight); // 480: offset + gifToBin02_ = new UILabelButton(); + SetUpButton(gifToBin02_, "ARGB8888bin"); + gifHeight += 50; // 50: offset + gifToBin02_->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, gifHeight); + gifToBin03_ = new UILabelButton(); + SetUpButton(gifToBin03_, "RGB565bin"); + gifToBin03_->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE + 160, gifHeight); // 160: offset + gifToBin04_ = new UILabelButton(); + SetUpButton(gifToBin04_, "ARGB8888数组"); + gifToBin04_->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE + 320, gifHeight); // 320: offset + g_height += 300; // offset=300 + } +} + +void UITestImage::SetUpButton(UILabelButton* btn, const char* title) +{ + if (btn == nullptr) { + return; + } + container_->Add(btn); + btn->Resize(BUTTON_WIDHT3, BUTTON_HEIGHT3); + btn->SetText(title); + btn->SetFont(DEFAULT_VECTOR_FONT_FILENAME, BUTTON_LABEL_SIZE); + btn->SetOnClickListener(this); + btn->SetStyleForState(STYLE_BORDER_RADIUS, BUTTON_STYLE_BORDER_RADIUS_VALUE, UIButton::RELEASED); + btn->SetStyleForState(STYLE_BORDER_RADIUS, BUTTON_STYLE_BORDER_RADIUS_VALUE, UIButton::PRESSED); + btn->SetStyleForState(STYLE_BORDER_RADIUS, BUTTON_STYLE_BORDER_RADIUS_VALUE, UIButton::INACTIVE); + btn->SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::RELEASED); + btn->SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::PRESSED); + btn->SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::INACTIVE); +} + +bool UITestImage::OnClick(UIView &view, const ClickEvent& event) +{ + if (gifImageView_ == nullptr) { + return false; + } + if (&view == gifToGif_) { + gifImageView_->SetSrc(GIF_IMAGE_PATH2); + } else if (&view == gifToJpeg_) { + gifImageView_->SetSrc(JPEG_IMAGE_PATH); + } else if (&view == gifToPng_) { + gifImageView_->SetSrc(BLUE_IMAGE_PATH); + } else if (&view == gifToBin01_) { + gifImageView_->SetSrc(BLUE_RGB888_IMAGE_PATH); + } else if (&view == gifToBin02_) { + gifImageView_->SetSrc(BLUE_ARGB8888_IMAGE_PATH); + } else if (&view == gifToBin03_) { + gifImageView_->SetSrc(BLUE_RGB565_IMAGE_PATH); + } else if (&view == gifToBin04_) { + gifImageView_->SetSrc(GetCheckBoxOnInfo()); + } + return true; +} +#endif + +void UITestImage::UIKit_UIImage_Test_Uncompress_001() +{ + if (container_ != nullptr) { + UILabel* label = new UILabel(); + container_->Add(label); + label->SetPosition(TEXT_DISTANCE_TO_LEFT_SIDE, g_height, Screen::GetInstance().GetWidth(), + TITLE_LABEL_DEFAULT_HEIGHT); + label->SetText("图片解压测试"); + label->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + + UIImageView* imageView1 = new UIImageView(); + imageView1->SetPosition(48, g_height + 30); // 48 : offset; 30 : offset + imageView1->SetSrc(IMAGE_DIR "blue_compress.bin"); + + UIImageView* imageView2 = new UIImageView(); + imageView2->SetPosition(158, g_height + 30); // 158 : offset 30 : offset + imageView2->SetSrc(IMAGE_DIR "red_compress.bin"); + + UIImageView* imageView3 = new UIImageView(); + imageView3->SetPosition(268, g_height + 30); // 268 : offset 30 : offset + imageView3->SetSrc(IMAGE_DIR "FaultInjection.bin"); + + container_->Add(imageView1); + container_->Add(imageView2); + container_->Add(imageView3); + g_height += 100; // offset=100 + } +} +} // namespace OHOS diff --git a/test/uitest/test_image/ui_test_image.h b/test/uitest/test_image/ui_test_image.h new file mode 100755 index 0000000..e99f106 --- /dev/null +++ b/test/uitest/test_image/ui_test_image.h @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef UI_TEST_IMAGE_H +#define UI_TEST_IMAGE_H + +#include "components/ui_scroll_view.h" +#include "imgdecode/cache_manager.h" +#include "ui_test.h" + +#ifndef VERSION_LITE +#include "components/ui_image_view.h" +#include "components/ui_label_button.h" +#include "test_resource_config.h" +#endif + +namespace OHOS { +#ifndef VERSION_LITE +class UITestImage : public UITest, public OHOS::UIView::OnClickListener { +#else +class UITestImage : public UITest { +#endif +public: + UITestImage() {} + ~UITestImage() {} + void SetUp() override; + void TearDown() override; + UIView* GetTestView() override; + void UIKit_UIImage_Test_SetImage_001(); + void UIKit_UIImage_Test_SetImage_002(); + void UIKit_UIImage_Test_SetImage_004(); + void UIKit_UIImage_Test_SetImage_005(); + void UIKit_UIImage_Test_SetImage_006(); + void UIKit_UIImage_Test_SetImage_007(); + void UIKit_UIImage_Test_SetImage_008(); + void UIKit_UIImage_Test_SetImage_009(); + void UIKit_UIImage_Test_SetImage_010(); + void UIKit_UIImage_Test_SetImage_011(); + void UIKit_UIImage_Test_SetImage_012(); + void UIKit_UIImage_Test_SetImage_013(); + void UIKit_UIImage_Test_SetImage_014(); +#ifndef VERSION_LITE + void UIKit_UIImage_Test_SetImage_015(); + void UIKit_UIImage_Test_SetImage_016(); +#endif + void UIKit_UIImage_Test_Uncompress_001(); + +private: + const char* GetCharByImageSrcType(ImageSrcType srcType) const; +#ifndef VERSION_LITE + bool OnClick(UIView &view, const ClickEvent& event) override; + void SetUpButton(UILabelButton* btn, const char* title); +#endif + UIScrollView* container_ = nullptr; +#ifndef VERSION_LITE + UIImageView* gifImageView_ = nullptr; + UILabelButton* gifToGif_ = nullptr; + UILabelButton* gifToJpeg_ = nullptr; + UILabelButton* gifToPng_ = nullptr; + UILabelButton* gifToBin01_ = nullptr; + UILabelButton* gifToBin02_ = nullptr; + UILabelButton* gifToBin03_ = nullptr; + UILabelButton* gifToBin04_ = nullptr; +#endif +}; +} // namespace OHOS +#endif // UI_TEST_IMAGE_H diff --git a/test/uitest/test_image_animator/ui_test_image_animator.cpp b/test/uitest/test_image_animator/ui_test_image_animator.cpp new file mode 100755 index 0000000..8ae85b3 --- /dev/null +++ b/test/uitest/test_image_animator/ui_test_image_animator.cpp @@ -0,0 +1,428 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ui_test_image_animator.h" +#include "common/screen.h" +#include "test_resource_config.h" + +namespace OHOS { +namespace { +static ImageAnimatorInfo g_imageAnimatorInfo[4] = { + {IMAGE_ANIMATOR_0_PATH, {84, 108}, 116, 116, IMG_SRC_FILE_PATH}, + {IMAGE_ANIMATOR_1_PATH, {84, 108}, 116, 116, IMG_SRC_FILE_PATH}, + {IMAGE_ANIMATOR_2_PATH, {84, 108}, 116, 116, IMG_SRC_FILE_PATH}, + {IMAGE_ANIMATOR_3_PATH, {84, 108}, 116, 116, IMG_SRC_FILE_PATH}, +}; + +static ImageAnimatorInfo g_imageAnimatorInfo2[4] = { + {BLUE_IMAGE_PATH, {84, 108}, 94, 94, IMG_SRC_FILE_PATH}, + {RED_IMAGE_PATH, {84, 108}, 94, 94, IMG_SRC_FILE_PATH}, + {GREEN_IMAGE_PATH, {84, 108}, 94, 94, IMG_SRC_FILE_PATH}, + {YELLOW_IMAGE_PATH, {84, 108}, 94, 94, IMG_SRC_FILE_PATH}, +}; +} // namespace + +void UITestImageAnimator::SetUp() +{ + if (container_ == nullptr) { + container_ = new UIScrollView(); + container_->Resize(Screen::GetInstance().GetWidth(), Screen::GetInstance().GetHeight() - BACK_BUTTON_HEIGHT); + + UIViewGroup* uiViewGroup = new UIViewGroup(); + // 320: width; 390: height + uiViewGroup->SetPosition(0, 0, 320, 390); + container_->Add(uiViewGroup); + UILabel* label = new UILabel(); + uiViewGroup->Add(label); + // 288: width; 48: height + label->SetPosition(TEXT_DISTANCE_TO_LEFT_SIDE, TEXT_DISTANCE_TO_TOP_SIDE, 288, 48); + label->SetText("UIImageAnimator效果"); + label->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + UIViewGroup* uiViewGroupFrame_ = new UIViewGroup(); + uiViewGroup->Add(uiViewGroupFrame_); + // 288: width; 336: height + uiViewGroupFrame_->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE2, VIEW_DISTANCE_TO_TOP_SIDE, 288, 336); + uiViewGroupFrame_->SetStyle(STYLE_BORDER_COLOR, Color::White().full); + uiViewGroupFrame_->SetStyle(STYLE_BORDER_OPA, HALF_OPA_OPAQUE); + uiViewGroupFrame_->SetStyle(STYLE_BORDER_WIDTH, VIEW_STYLE_BORDER_WIDTH); + uiViewGroupFrame_->SetStyle(STYLE_BORDER_RADIUS, VIEW_STYLE_BORDER_RADIUS); + uiViewGroupFrame_->SetStyle(STYLE_BACKGROUND_OPA, 0); + + imageAnimator_ = new UIImageAnimatorView(); + imageAnimator_->SetPosition(50, 50, 200, 200); // 50 : offset 50 : offset 200 : offset 200: offset + imageAnimator_->SetImageAnimatorSrc(g_imageAnimatorInfo, 4, 100); // 4: the number of images, 100: updating time + imageAnimator_->Start(); + listener_ = new TestAnimatorStopListener(container_); + listener_->Init(); + uiViewGroupFrame_->Add(imageAnimator_); + imageAnimator_->LayoutCenterOfParent(); + } + + if (layout_ == nullptr) { + layout_ = new GridLayout(); + uint16_t height = Screen::GetInstance().GetHeight(); + layout_->SetPosition(370, 0, 320, height - 50); // 370: x , 320: width, 50: offset + container_->Add(layout_); + layout_->SetLayoutDirection(LAYOUT_VER); + layout_->SetRows(9); // 9 : rows + layout_->SetCols(2); // 2 : columns + } + + InitImageInfo(); +} + +void UITestImageAnimator::InitImageInfo() +{ + const uint16_t pixelByteSize = 4; // 4: bytes per pixel + const uint16_t width = 100; // 100: image's width + const uint16_t height = 100; // 100: image's height + uint32_t dataSize = width * height * pixelByteSize; + uint8_t* srcData1 = static_cast(UIMalloc(dataSize)); + if (srcData1 == nullptr) { + return; + } + + for (uint32_t i = 0; i < dataSize; i += pixelByteSize) { + srcData1[i] = 255; // 255: pixel value + srcData1[i + 1] = 0; // 1: set green channel + srcData1[i + 2] = 0; // 2: set red channel + srcData1[i + 3] = OPA_OPAQUE; // 3: set alpha channel + } + imageInfo1_ = static_cast(UIMalloc(sizeof(ImageInfo))); + if (imageInfo1_ == nullptr) { + UIFree(srcData1); + return; + } + + imageInfo1_->header.width = width; + imageInfo1_->header.height = height; + imageInfo1_->header.colorMode = ColorMode::ARGB8888; + imageInfo1_->dataSize = dataSize; + imageInfo1_->data = srcData1; + + uint8_t* srcData2 = static_cast(UIMalloc(dataSize)); + if (srcData2 == nullptr) { + UIFree(imageInfo1_); + UIFree(srcData1); + return; + } + for (uint32_t i = 0; i < dataSize; i += pixelByteSize) { + srcData2[i] = 0; // set blue channel + srcData2[i + 1] = 255; // 1: set green channel 255: pixel value + srcData2[i + 2] = 0; // 2: set red channel + srcData2[i + 3] = OPA_OPAQUE; // 3: set alpha channel + } + + imageInfo2_ = static_cast(UIMalloc(sizeof(ImageInfo))); + if (imageInfo2_ == nullptr) { + UIFree(srcData2); + UIFree(imageInfo1_); + UIFree(srcData1); + return; + } + imageInfo2_->header.width = width; + imageInfo2_->header.height = height; + imageInfo2_->header.colorMode = ARGB8888; + imageInfo2_->dataSize = dataSize; + imageInfo2_->data = srcData2; + + uint8_t* srcData3 = static_cast(UIMalloc(dataSize)); + if (srcData3 == nullptr) { + UIFree(imageInfo2_); + UIFree(srcData2); + UIFree(imageInfo1_); + UIFree(srcData1); + return; + } + for (uint32_t i = 0; i < dataSize; i += pixelByteSize) { + srcData3[i] = 0; // set blue channel + srcData3[i + 1] = 0; // 1: set green channel + srcData3[i + 2] = 255; // 2: set red channel 255: pixel value + srcData3[i + 3] = OPA_OPAQUE; // 3: set alpha channel + } + + imageInfo3_ = static_cast(UIMalloc(sizeof(ImageInfo))); + if (imageInfo3_ == nullptr) { + UIFree(srcData3); + UIFree(imageInfo2_); + UIFree(srcData2); + UIFree(imageInfo1_); + UIFree(srcData1); + return; + } + imageInfo3_->header.width = width; + imageInfo3_->header.height = height; + imageInfo3_->header.colorMode = ARGB8888; + imageInfo3_->dataSize = dataSize; + imageInfo3_->data = srcData3; + + imageAnimatorImageInfo_[0].imageInfo = imageInfo1_; + imageAnimatorImageInfo_[0].pos = {84, 108}; + imageAnimatorImageInfo_[0].width = 100; // 100: width value + imageAnimatorImageInfo_[0].height = 100; // 100: height value + imageAnimatorImageInfo_[0].imageType = IMG_SRC_IMAGE_INFO; + + imageAnimatorImageInfo_[1].imageInfo = imageInfo2_; + imageAnimatorImageInfo_[1].pos = {84, 108}; + imageAnimatorImageInfo_[1].width = 100; // 100: width value + imageAnimatorImageInfo_[1].height = 100; // 100: height value + imageAnimatorImageInfo_[1].imageType = IMG_SRC_IMAGE_INFO; + + imageAnimatorImageInfo_[2].imageInfo = imageInfo3_; // 2: image index + imageAnimatorImageInfo_[2].pos = {84, 108}; // 2: image index + imageAnimatorImageInfo_[2].width = 100; // 2: image index, 100: width value + imageAnimatorImageInfo_[2].height = 100; // 2: image index, 100: height value + imageAnimatorImageInfo_[2].imageType = IMG_SRC_IMAGE_INFO; // 2: image index +} + +void UITestImageAnimator::TearDown() +{ + DeleteChildren(container_); + container_ = nullptr; + imageAnimator_ = nullptr; + layout_ = nullptr; + delete listener_; + listener_ = nullptr; + + UIFree(reinterpret_cast(const_cast(imageInfo1_->data))); + imageInfo1_->data = nullptr; + UIFree(reinterpret_cast(const_cast(imageInfo2_->data))); + imageInfo2_->data = nullptr; + UIFree(reinterpret_cast(const_cast(imageInfo3_->data))); + imageInfo3_->data = nullptr; + UIFree(reinterpret_cast(const_cast(imageInfo1_))); + imageInfo1_ = nullptr; + UIFree(reinterpret_cast(const_cast(imageInfo2_))); + imageInfo2_ = nullptr; + UIFree(reinterpret_cast(const_cast(imageInfo3_))); + imageInfo3_ = nullptr; +} + +UIView* UITestImageAnimator::GetTestView() +{ + UIKit_ImageAnimator_Test_Start_001(); + UIKit_ImageAnimator_Test_Stop_002(); + UIKit_ImageAnimator_Test_Pause_003(); + UIKit_ImageAnimator_Test_Resume_004(); + UIKit_ImageAnimator_Test_SetImageAnimatorSrc_005(); + UIKit_ImageAnimator_Test_SetTickOfUpdate_006(); + UIKit_ImageAnimator_Test_SetSizeFixed_007(); + UIKit_ImageAnimator_Test_SetRepeat_008(); + UIKit_ImageAnimator_Test_SetReverse_009(); + UIKit_ImageAnimator_Test_SetAnimatorStopListener_010(); + UIKit_ImageAnimator_Test_SetRepeatTimes_011(); + UIKit_ImageAnimator_Test_SetFillMode_True_Forward_012(); + UIKit_ImageAnimator_Test_SetFillMode_True_Backward_013(); + UIKit_ImageAnimator_Test_SetFillMode_False_Forward_014(); + UIKit_ImageAnimator_Test_SetFillMode_False_Backward_015(); + UIKit_ImageAnimator_Test_SetImageInfo_016(); + + layout_->LayoutChildren(); + return container_; +} + +void UITestImageAnimator::SetUpButton(UILabelButton* btn, const char* title) +{ + if (btn == nullptr) { + return; + } + layout_->Add(btn); + btn->Resize(BUTTON_WIDHT2, BUTTON_HEIGHT2); + btn->SetText(title); + btn->SetFont(DEFAULT_VECTOR_FONT_FILENAME, BUTTON_LABEL_SIZE); + btn->SetOnClickListener(this); + btn->SetStyleForState(STYLE_BORDER_RADIUS, BUTTON_STYLE_BORDER_RADIUS_VALUE, UIButton::RELEASED); + btn->SetStyleForState(STYLE_BORDER_RADIUS, BUTTON_STYLE_BORDER_RADIUS_VALUE, UIButton::PRESSED); + btn->SetStyleForState(STYLE_BORDER_RADIUS, BUTTON_STYLE_BORDER_RADIUS_VALUE, UIButton::INACTIVE); + btn->SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::RELEASED); + btn->SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::PRESSED); + btn->SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::INACTIVE); +} + +void UITestImageAnimator::UIKit_ImageAnimator_Test_Start_001() +{ + startBtn_ = new UILabelButton(); + SetUpButton(startBtn_, "Start"); +} + +void UITestImageAnimator::UIKit_ImageAnimator_Test_Stop_002() +{ + stopBtn_ = new UILabelButton(); + SetUpButton(stopBtn_, "Stop"); +} + +void UITestImageAnimator::UIKit_ImageAnimator_Test_Pause_003() +{ + pauseBtn_ = new UILabelButton(); + SetUpButton(pauseBtn_, "Pause"); +} + +void UITestImageAnimator::UIKit_ImageAnimator_Test_Resume_004() +{ + resumeBtn_ = new UILabelButton(); + SetUpButton(resumeBtn_, "Resume"); +} + +void UITestImageAnimator::UIKit_ImageAnimator_Test_SetImageAnimatorSrc_005() +{ + setImageBtn_ = new UILabelButton(); + SetUpButton(setImageBtn_, "设置图片"); +} + +void UITestImageAnimator::UIKit_ImageAnimator_Test_SetTickOfUpdate_006() +{ + setSpeedBtn_ = new UILabelButton(); + SetUpButton(setSpeedBtn_, "设置速度"); +} + +void UITestImageAnimator::UIKit_ImageAnimator_Test_SetSizeFixed_007() +{ + fixedBtn_ = new UILabelButton(); + SetUpButton(fixedBtn_, "图片大小固定"); +} + +void UITestImageAnimator::UIKit_ImageAnimator_Test_SetRepeat_008() +{ + repeatBtn_ = new UILabelButton(); + noRepeatBtn_ = new UILabelButton(); + SetUpButton(repeatBtn_, "循环播放"); + SetUpButton(noRepeatBtn_, "播放一次 "); +} + +void UITestImageAnimator::UIKit_ImageAnimator_Test_SetReverse_009() +{ + reverseOrderBtn_ = new UILabelButton(); + positiveOrderBtn_ = new UILabelButton(); + SetUpButton(reverseOrderBtn_, "逆序播放"); + SetUpButton(positiveOrderBtn_, "正序播放"); +} + +void UITestImageAnimator::UIKit_ImageAnimator_Test_SetAnimatorStopListener_010() +{ + listenerBtn_ = new UILabelButton(); + SetUpButton(listenerBtn_, "播放结束监听"); +} + +void UITestImageAnimator::UIKit_ImageAnimator_Test_SetRepeatTimes_011() +{ + repeatTimesBtn_ = new UILabelButton(); + SetUpButton(repeatTimesBtn_, "播放次数+"); +} + +void UITestImageAnimator::UIKit_ImageAnimator_Test_SetFillMode_True_Forward_012() +{ + fillModeTrueForwardBtn_ = new UILabelButton(); + SetUpButton(fillModeTrueForwardBtn_, "fillMode true 正播"); +} + +void UITestImageAnimator::UIKit_ImageAnimator_Test_SetFillMode_True_Backward_013() +{ + fillModeTrueBackwardBtn_ = new UILabelButton(); + SetUpButton(fillModeTrueBackwardBtn_, "fillMode true 逆播"); +} + +void UITestImageAnimator::UIKit_ImageAnimator_Test_SetFillMode_False_Forward_014() +{ + fillModeFalseForwardBtn_ = new UILabelButton(); + SetUpButton(fillModeFalseForwardBtn_, "fillMode false 正播"); +} + +void UITestImageAnimator::UIKit_ImageAnimator_Test_SetFillMode_False_Backward_015() +{ + fillModeFalseBackwardBtn_ = new UILabelButton(); + SetUpButton(fillModeFalseBackwardBtn_, "fillMode false 逆播"); +} + +void UITestImageAnimator::UIKit_ImageAnimator_Test_SetImageInfo_016() +{ + setImageInfoBtn_ = new UILabelButton(); + SetUpButton(setImageInfoBtn_, "设置ImageInfo"); +} + +bool UITestImageAnimator::OnClick(UIView& view, const ClickEvent& event) +{ + if (&view == startBtn_) { + imageAnimator_->Start(); + } else if (&view == stopBtn_) { + imageAnimator_->Stop(); + } else if (&view == pauseBtn_) { + imageAnimator_->Pause(); + } else if (&view == resumeBtn_) { + imageAnimator_->Resume(); + } else if (&view == setImageBtn_) { + imageAnimator_->Stop(); + imageAnimator_->SetImageAnimatorSrc(g_imageAnimatorInfo2, 4); // 4: the number of images + imageAnimator_->Start(); + } else if (&view == setSpeedBtn_) { + imageAnimator_->Stop(); + imageAnimator_->SetTimeOfUpdate(10); // 10: set time of update + imageAnimator_->Start(); + } else if (&view == fixedBtn_) { + imageAnimator_->SetSizeFixed(true); + imageAnimator_->SetPosition(50, 50, 200, 200); // 50 : offset 50 : offset 200 : offset 200 :offset + } else if (&view == repeatBtn_) { + imageAnimator_->Stop(); + imageAnimator_->SetRepeat(true); + imageAnimator_->Start(); + } else if (&view == noRepeatBtn_) { + imageAnimator_->Stop(); + imageAnimator_->SetRepeat(false); + imageAnimator_->Start(); + } else if (&view == reverseOrderBtn_) { + imageAnimator_->Stop(); + imageAnimator_->SetReverse(true); + imageAnimator_->Start(); + } else if (&view == positiveOrderBtn_) { + imageAnimator_->Stop(); + imageAnimator_->SetReverse(false); + imageAnimator_->Start(); + } else if (&view == listenerBtn_) { + imageAnimator_->SetAnimatorStopListener(listener_); + } else if (&view == repeatTimesBtn_) { + imageAnimator_->Stop(); + imageAnimator_->SetRepeatTimes(imageAnimator_->GetRepeatTimes() + 1); + imageAnimator_->Start(); + } else if (&view == fillModeTrueForwardBtn_) { + imageAnimator_->Stop(); + imageAnimator_->SetFillMode(true); + imageAnimator_->SetRepeat(false); + imageAnimator_->SetReverse(false); + imageAnimator_->Start(); + } else if (&view == fillModeTrueBackwardBtn_) { + imageAnimator_->Stop(); + imageAnimator_->SetFillMode(true); + imageAnimator_->SetRepeat(false); + imageAnimator_->SetReverse(true); + imageAnimator_->Start(); + } else if (&view == fillModeFalseForwardBtn_) { + imageAnimator_->Stop(); + imageAnimator_->SetFillMode(false); + imageAnimator_->SetRepeat(false); + imageAnimator_->SetReverse(false); + imageAnimator_->Start(); + } else if (&view == fillModeFalseBackwardBtn_) { + imageAnimator_->Stop(); + imageAnimator_->SetFillMode(false); + imageAnimator_->SetRepeat(false); + imageAnimator_->SetReverse(true); + imageAnimator_->Start(); + } else if (&view == setImageInfoBtn_) { + imageAnimator_->Stop(); + imageAnimator_->SetImageAnimatorSrc(imageAnimatorImageInfo_, 3); // 3: the number of images + imageAnimator_->Start(); + } + return true; +} +} // namespace OHOS diff --git a/test/uitest/test_image_animator/ui_test_image_animator.h b/test/uitest/test_image_animator/ui_test_image_animator.h new file mode 100755 index 0000000..75854d1 --- /dev/null +++ b/test/uitest/test_image_animator/ui_test_image_animator.h @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef UI_TEST_IMAGE_ANIMATOR_H +#define UI_TEST_IMAGE_ANIMATOR_H + +#include "components/ui_image_animator.h" +#include "components/ui_label.h" +#include "components/ui_label_button.h" +#include "components/ui_scroll_view.h" +#include "layout/grid_layout.h" +#include "ui_test.h" + +namespace OHOS { +class TestAnimatorStopListener : public UIImageAnimatorView::AnimatorStopListener { +public: + explicit TestAnimatorStopListener(UIViewGroup* viewGroup) : viewGroup_(viewGroup) + { + viewGroup_ = viewGroup; + } + ~TestAnimatorStopListener() {} + + void Init() + { + label_ = new UILabel(); + viewGroup_->Add(label_); + label_->SetPosition(24, 50, 200, 29); // 24: position x 50: position y 200: width 29: height + label_->SetText("OnAnimatorStop"); + label_->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + label_->SetStyle(STYLE_TEXT_COLOR, Color::Black().full); + label_->SetStyle(STYLE_BACKGROUND_COLOR, Color::White().full); + label_->SetStyle(STYLE_BACKGROUND_OPA, 255); // 255: value + label_->SetVisible(false); + } + + void OnAnimatorStop(UIView& view) override + { + label_->SetVisible(true); + label_->Invalidate(); + } +private: + UILabel* label_ = nullptr; + UIViewGroup* viewGroup_ = nullptr; +}; + +class UITestImageAnimator : public UITest, public UIView::OnClickListener { +public: + UITestImageAnimator() {} + ~UITestImageAnimator() {} + void SetUp() override; + void TearDown() override; + UIView* GetTestView() override; + + void SetUpButton(UILabelButton* btn, const char* title); + + bool OnClick(UIView& view, const ClickEvent& event) override; + + void UIKit_ImageAnimator_Test_Start_001(); + void UIKit_ImageAnimator_Test_Stop_002(); + void UIKit_ImageAnimator_Test_Pause_003(); + void UIKit_ImageAnimator_Test_Resume_004(); + void UIKit_ImageAnimator_Test_SetImageAnimatorSrc_005(); + void UIKit_ImageAnimator_Test_SetTickOfUpdate_006(); + void UIKit_ImageAnimator_Test_SetSizeFixed_007(); + void UIKit_ImageAnimator_Test_SetRepeat_008(); + void UIKit_ImageAnimator_Test_SetReverse_009(); + void UIKit_ImageAnimator_Test_SetAnimatorStopListener_010(); + void UIKit_ImageAnimator_Test_SetRepeatTimes_011(); + void UIKit_ImageAnimator_Test_SetFillMode_True_Forward_012(); + void UIKit_ImageAnimator_Test_SetFillMode_True_Backward_013(); + void UIKit_ImageAnimator_Test_SetFillMode_False_Forward_014(); + void UIKit_ImageAnimator_Test_SetFillMode_False_Backward_015(); + void UIKit_ImageAnimator_Test_SetImageInfo_016(); + +private: + void InitImageInfo(); + UIScrollView* container_ = nullptr; + GridLayout* layout_ = nullptr; + UIImageAnimatorView* imageAnimator_ = nullptr; + TestAnimatorStopListener* listener_ = nullptr; + + UILabelButton* startBtn_ = nullptr; + UILabelButton* stopBtn_ = nullptr; + UILabelButton* pauseBtn_ = nullptr; + UILabelButton* resumeBtn_ = nullptr; + UILabelButton* setImageBtn_ = nullptr; + UILabelButton* setSpeedBtn_ = nullptr; + UILabelButton* fixedBtn_ = nullptr; + UILabelButton* repeatBtn_ = nullptr; + UILabelButton* noRepeatBtn_ = nullptr; + UILabelButton* reverseOrderBtn_ = nullptr; + UILabelButton* positiveOrderBtn_ = nullptr; + UILabelButton* listenerBtn_ = nullptr; + UILabelButton* repeatTimesBtn_ = nullptr; + UILabelButton* fillModeTrueForwardBtn_ = nullptr; + UILabelButton* fillModeTrueBackwardBtn_ = nullptr; + UILabelButton* fillModeFalseForwardBtn_ = nullptr; + UILabelButton* fillModeFalseBackwardBtn_ = nullptr; + UILabelButton* setImageInfoBtn_ = nullptr; + + ImageInfo* imageInfo1_ = nullptr; + ImageInfo* imageInfo2_ = nullptr; + ImageInfo* imageInfo3_ = nullptr; + ImageAnimatorInfo imageAnimatorImageInfo_[3] = { 0 }; +}; +} // namespace OHOS +#endif // UI_TEST_IMAGE_ANIMATOR_H diff --git a/test/uitest/test_input_event/ui_test_input_event.cpp b/test/uitest/test_input_event/ui_test_input_event.cpp new file mode 100755 index 0000000..a4c453f --- /dev/null +++ b/test/uitest/test_input_event/ui_test_input_event.cpp @@ -0,0 +1,617 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ui_test_input_event.h" +#include "common/screen.h" +#include "components/root_view.h" + +namespace OHOS { +namespace { +const int16_t ITEM_H = 50; +const int16_t TEXT_H = 29; +const int16_t TEXT_W = 250; +const int16_t TEST_VIEW_H = 40; +const int16_t TEST_VIEW_W = 40; +const int16_t GAP = 5; +const int16_t TEST_VIEW_GAP = 80; +} // namespace + +class TestView : public UIView { +public: + TestView() {} + virtual ~TestView() {} + bool OnLongPressEvent(const LongPressEvent& event) override + { + if (label_ != nullptr) { + label_->SetText("long press!"); + label_->Invalidate(); + } + return UIView::OnLongPressEvent(event); + } + + bool OnDragEvent(const DragEvent& event) override + { + if (label_ != nullptr) { + label_->SetText("drag!"); + label_->Invalidate(); + } + return UIView::OnDragEvent(event); + } + + void SetSentence(const char* sentence) + { + sentence_ = sentence; + } + + bool OnClickEvent(const ClickEvent& event) override + { + if (label_ != nullptr) { + label_->SetText(sentence_); + label_->Invalidate(); + } + return UIView::OnClickEvent(event); + } + + bool OnPressEvent(const PressEvent& event) override + { + if (label_ != nullptr) { + label_->SetText("press!"); + label_->Invalidate(); + } + return UIView::OnPressEvent(event); + } + + bool OnReleaseEvent(const ReleaseEvent& event) override + { + if (label_ != nullptr) { + label_->SetText("release!"); + label_->Invalidate(); + } + return UIView::OnReleaseEvent(event); + } + + bool OnCancelEvent(const CancelEvent& event) override + { + if (label_ != nullptr) { + label_->SetText("cancel!"); + label_->Invalidate(); + } + return UIView::OnCancelEvent(event); + } + + void SetLabel(UILabel* label) + { + label_ = label; + } + + void SetLabel2(UILabel* label) + { + label2_ = label; + } + +private: + UILabel* label_ = nullptr; + UILabel* label2_ = nullptr; + const char* sentence_ = "click"; +}; + +class TestUIScrollView : public UIScrollView { +public: + TestUIScrollView() {} + virtual ~TestUIScrollView() {} + bool OnLongPressEvent(const LongPressEvent& event) override + { + if (label_ != nullptr) { + label_->SetText("long press!"); + label_->Invalidate(); + } + return UIView::OnLongPressEvent(event); + } + + bool OnDragEvent(const DragEvent& event) override + { + if (label_ != nullptr) { + label_->SetText("drag!"); + label_->Invalidate(); + } + return UIScrollView::OnDragEvent(event); + } + + bool OnDragStartEvent(const DragEvent& event) override + { + if (label_ != nullptr) { + label_->SetText("drag start!"); + label_->Invalidate(); + } + return UIScrollView::OnDragStartEvent(event); + } + + bool OnDragEndEvent(const DragEvent& event) override + { + if (label_ != nullptr) { + label_->SetText("drag end!"); + label_->Invalidate(); + } + return UIScrollView::OnDragEndEvent(event); + } + + void SetSentence(const char* sentence) + { + sentence_ = sentence; + } + + bool OnClickEvent(const ClickEvent& event) override + { + if (label_ != nullptr) { + label_->SetText(sentence_); + label_->Invalidate(); + } + return UIView::OnClickEvent(event); + } + + bool OnPressEvent(const PressEvent& event) override + { + if (label_ != nullptr) { + label_->SetText("press!"); + label_->Invalidate(); + } + return UIView::OnPressEvent(event); + } + + bool OnReleaseEvent(const ReleaseEvent& event) override + { + if (label_ != nullptr) { + label_->SetText("release!"); + label_->Invalidate(); + } + return UIView::OnReleaseEvent(event); + } + + bool OnCancelEvent(const CancelEvent& event) override + { + if (label_ != nullptr) { + label_->SetText("cancel!"); + label_->Invalidate(); + } + return UIView::OnCancelEvent(event); + } + + void SetLabel(UILabel* label) + { + label_ = label; + } + +private: + UILabel* label_ = nullptr; + const char* sentence_ = "click"; +}; + +void UITestInputEvent::SetUp() +{ + if (container_ == nullptr) { + container_ = new UIScrollView(); + container_->Resize(Screen::GetInstance().GetWidth(), Screen::GetInstance().GetHeight() - BACK_BUTTON_HEIGHT); + container_->SetHorizontalScrollState(false); + } + positionX_ = 48; // 48: position x + positionY_ = 0; +} + +void DeleteChildrenAndListener(UIView* view) +{ + if (view == nullptr) { + return; + } + while (view != nullptr) { + UIView* tempView = view; + view = view->GetNextSibling(); + if (tempView->IsViewGroup()) { + DeleteChildrenAndListener(static_cast(tempView)->GetChildrenHead()); + } + if (tempView->GetParent()) { + static_cast(tempView->GetParent())->Remove(tempView); + } + + if (tempView->GetOnClickListener()) { + delete tempView->GetOnClickListener(); + } + if (tempView->GetOnDragListener()) { + delete tempView->GetOnDragListener(); + } + if (tempView->GetOnLongPressListener()) { + delete tempView->GetOnLongPressListener(); + } + if (tempView->GetTouchListener()) { + delete tempView->GetTouchListener(); + } + + delete tempView; + } +} + +void UITestInputEvent::TearDown() +{ + DeleteChildrenAndListener(container_); + container_ = nullptr; + if (keyListener_ != nullptr) { + delete keyListener_; + keyListener_ = nullptr; + } +} + +UIView* UITestInputEvent::GetTestView() +{ + UIKit_Pointer_Input_Test_Dispatch_Simple_Event_001(); + UIKit_Pointer_Input_Test_Dispatch_Simple_Event_002(); + UIKit_Pointer_Input_Test_Dispatch_Drag_Event_001(); + UIKit_Pointer_Input_Test_Dispatch_Drag_Event_002(); + UIKit_Pointer_Input_Test_Dispatch_Drag_Event_003(); + UIKit_Pointer_Input_Test_Dispatch_Key_Event_001(); + UIKit_Pointer_Input_Test_Dispatch_InVisible_Event_001(); + UIKit_Pointer_Input_Test_Dispatch_Bubble_001(); + UIKit_Pointer_Input_Test_Dispatch_Bubble_002(); + UIKit_Pointer_Input_Test_Dispatch_Bubble_003(); + UIKit_Pointer_Input_Test_Dispatch_Bubble_004(); + UIKit_Pointer_Input_Test_Dispatch_Bubble_005(); + UIKit_Pointer_Input_Test_Dispatch_Bubble_006(); + UIKit_Pointer_Input_Test_Dispatch_Bubble_007(); + UIKit_Pointer_Input_Test_Dispatch_Bubble_008(); + UIKit_Pointer_Input_Test_Dispatch_Bubble_009(); + UIKit_Pointer_Input_Test_Dispatch_Bubble_010(); + return container_; +} + +void UITestInputEvent::UIKit_Pointer_Input_Test_Dispatch_Simple_Event_001() +{ + InnerTest("可点击对象事件测试 ", true, false, false); +} + +void UITestInputEvent::UIKit_Pointer_Input_Test_Dispatch_Simple_Event_002() +{ + InnerTest("不可点击对象事件测试 ", false, false, false); +} + +void UITestInputEvent::UIKit_Pointer_Input_Test_Dispatch_Drag_Event_001() +{ + InnerTest("可点击可拖拽dragparent测试 ", true, true, true); +} + +void UITestInputEvent::UIKit_Pointer_Input_Test_Dispatch_Drag_Event_002() +{ + InnerTest("可点击可拖拽非dragparent测试 ", true, true, false); +} + +void UITestInputEvent::UIKit_Pointer_Input_Test_Dispatch_Drag_Event_003() +{ + InnerTest("不可点击可拖拽测试 ", false, true, false); +} + +void UITestInputEvent::UIKit_Pointer_Input_Test_Dispatch_Bubble_001() +{ + positionY_ = 0; + InnerBubbleTest("可点击有监听事件不消费冒泡测试 ", true, true, true, false); +} + +void UITestInputEvent::UIKit_Pointer_Input_Test_Dispatch_Bubble_002() +{ + InnerBubbleTest("可点击有监听事件消费冒泡测试 ", true, true, true, true); +} + +void UITestInputEvent::UIKit_Pointer_Input_Test_Dispatch_Bubble_003() +{ + InnerBubbleTest("可点击无监听事件不消费冒泡测试 ", true, true, false, false); +} + +void UITestInputEvent::UIKit_Pointer_Input_Test_Dispatch_Bubble_004() +{ + InnerBubbleTest("不可点击有监听事件消费冒泡测试 ", false, false, true, true); +} + +void UITestInputEvent::UIKit_Pointer_Input_Test_Dispatch_Bubble_005() +{ + InnerBubbleDragTest("子父可拖拽有监听事件不消费冒泡测试 ", true, true, true, false); +} + +void UITestInputEvent::UIKit_Pointer_Input_Test_Dispatch_Bubble_006() +{ + InnerBubbleDragTest("子父可拖拽有监听事件消费冒泡测试 ", true, true, true, true); +} + +void UITestInputEvent::UIKit_Pointer_Input_Test_Dispatch_Bubble_007() +{ + InnerBubbleDragTest("子父可拖拽无监听事件消费冒泡测试 ", true, true, false, true); +} + +void UITestInputEvent::UIKit_Pointer_Input_Test_Dispatch_Bubble_008() +{ + InnerBubbleDragTest("子父不可拖拽有监听事件消费冒泡测试 ", false, false, true, true); +} + +void UITestInputEvent::UIKit_Pointer_Input_Test_Dispatch_Bubble_009() +{ + InnerBubbleDragTest("子不可拖拽父可拖拽有监听事件消费冒泡测试 ", false, true, true, true); +} + +void UITestInputEvent::UIKit_Pointer_Input_Test_Dispatch_Bubble_010() +{ + InnerBubbleDragTest("子不可拖拽父可拖拽有监听事件不消费冒泡测试 ", false, true, true, false); +} + +void UITestInputEvent::UIKit_Pointer_Input_Test_Dispatch_Key_Event_001() +{ + if (container_ != nullptr) { + UIViewGroup* uiViewGroup = new UIViewGroup(); + // 2: half of screen width; + uiViewGroup->SetPosition( + TEXT_DISTANCE_TO_LEFT_SIDE, positionY_, + (Screen::GetInstance().GetWidth() / 2 - TEXT_DISTANCE_TO_LEFT_SIDE), // 2: half of screen width; + 128); // 128: height + container_->Add(uiViewGroup); + + UILabel* label = new UILabel(); + uiViewGroup->Add(label); + // 2: half of screen width; + label->SetPosition(0, 0, Screen::GetInstance().GetWidth() / 2, TEXT_H); + label->SetText("物理按键事件测试 "); + label->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + positionY_ += (TEXT_H + GAP); + UILabel* label1 = new UILabel(); + uiViewGroup->Add(label1); + label1->SetPosition(0, TEXT_H + GAP, TEXT_W, TEXT_H); + label1->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + if (keyListener_ == nullptr) { + keyListener_ = new TestKeyInputListener(label1); + } + RootView::GetInstance()->SetOnKeyActListener(keyListener_); + positionY_ += ITEM_H; + } +} + +void UITestInputEvent::UIKit_Pointer_Input_Test_Dispatch_InVisible_Event_001() +{ + if (container_ != nullptr) { + UIViewGroup* uiViewGroup = new UIViewGroup(); + // 2: half of screen width; 36: decrease x-coordinate; 90: y-coordinate + uiViewGroup->SetPosition(TEXT_DISTANCE_TO_LEFT_SIDE, positionY_, 480, 128); // 480: width; 128: height + container_->Add(uiViewGroup); + UILabel* label = new UILabel(); + uiViewGroup->Add(label); + // 2: half of screen width; + label->SetPosition(0, 0, Screen::GetInstance().GetWidth() / 2, TEXT_H); + label->SetText("不可见对象事件传递测试 "); + label->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + positionY_ = 0; + positionY_ += (TEXT_H + GAP * 2); // 2: double GAP + UIViewGroup* group1 = new UIViewGroup(); + uiViewGroup->Add(group1); + // 20: increase width 20: increase height + group1->SetPosition(positionX_, positionY_, TEST_VIEW_W + 20, TEST_VIEW_H + 20); + group1->SetStyle(STYLE_BACKGROUND_COLOR, Color::White().full); + group1->SetVisible(false); + TestView* testView = new TestView(); + group1->Add(testView); + testView->SetPosition(5, 5, TEST_VIEW_W, TEST_VIEW_H); // 5: position x 5:position y + testView->SetStyle(STYLE_BACKGROUND_COLOR, Color::Blue().full); + testView->SetTouchable(true); + testView->SetSentence("Click From test 0!"); + UIViewGroup* group2 = new UIViewGroup(); + uiViewGroup->Add(group2); + // 20: increase width 20: increase height + group2->SetPosition(0, positionY_, TEST_VIEW_W + 20, TEST_VIEW_H + 20); + group2->SetStyle(STYLE_BACKGROUND_COLOR, Color::White().full); + TestView* testView1 = new TestView(); + group2->Add(testView1); + testView1->SetPosition(5, 5, TEST_VIEW_W, TEST_VIEW_H); // 5: position x 5:position y + testView1->SetStyle(STYLE_BACKGROUND_COLOR, Color::Red().full); + testView1->SetTouchable(true); + testView1->SetSentence("Click From test 1!"); + UILabel* label1 = new UILabel(); + uiViewGroup->Add(label1); + // 50: increase width; 2:double GAP + label1->SetPosition(positionX_ + 50, positionY_ + 2 * GAP, TEXT_W, TEXT_H); + label1->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + testView->SetLabel(label1); + testView1->SetLabel(label1); + positionY_ += ITEM_H; + } +} + +void UITestInputEvent::InnerTest(const char* title, bool touchable, bool draggable, bool dragParent) +{ + if (container_ != nullptr) { + UILabel* label = new UILabel(); + container_->Add(label); + // 2: half Screen width + label->SetPosition(positionX_, positionY_, Screen::GetInstance().GetWidth() / 2, TEXT_H); + label->SetText(title); + label->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + positionY_ += (TEXT_H + GAP); + auto testView = new TestView(); + container_->Add(testView); + testView->SetPosition(positionX_, positionY_, TEST_VIEW_W, TEST_VIEW_H); + testView->SetStyle(STYLE_BACKGROUND_COLOR, Color::Blue().full); + testView->SetTouchable(touchable); + testView->SetDraggable(draggable); + testView->SetDragParentInstead(dragParent); + UILabel* label1 = new UILabel(); + container_->Add(label1); + label1->SetPosition(positionX_ + TEST_VIEW_GAP, positionY_ + 2 * GAP, TEXT_W, TEXT_H); // 2: double GAP + label1->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + testView->SetLabel(label1); + positionY_ += ITEM_H; + } +} + +void UITestInputEvent::InnerBubbleTest(const char* title, + bool touchable, + bool draggable, + bool hasListener, + bool isBubble) +{ + if (container_ != nullptr) { + UILabel* label = new UILabel(); + container_->Add(label); + label->SetPosition((Screen::GetInstance().GetWidth() / 2 + TEXT_DISTANCE_TO_LEFT_SIDE), // 2: half screen width + positionY_, + (Screen::GetInstance().GetWidth() / 2 - TEXT_DISTANCE_TO_LEFT_SIDE), // 2: half screen width + TEXT_H); + label->SetText(title); + label->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + positionY_ += (TEXT_H + GAP); + + OHOS::UIScrollView* parentContainer = new UIScrollView(); + // 2: half screen width + parentContainer->SetPosition(Screen::GetInstance().GetWidth() / 2 + TEXT_DISTANCE_TO_LEFT_SIDE, positionY_, + Screen::GetInstance().GetWidth() - TEXT_DISTANCE_TO_LEFT_SIDE, ITEM_H); + container_->Add(parentContainer); + TestView* testView = new TestView(); + parentContainer->Add(testView); + testView->SetPosition(0, 0, TEST_VIEW_W, TEST_VIEW_H); + testView->SetStyle(STYLE_BACKGROUND_COLOR, Color::Red().full); + testView->SetTouchable(touchable); + testView->SetDraggable(draggable); + testView->SetDragParentInstead(true); + UILabel* label1 = new UILabel(); + parentContainer->Add(label1); + label1->SetPosition(positionX_ + TEST_VIEW_GAP, 2 * GAP, TEXT_W, TEXT_H); // 2: tow gap + label1->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + testView->SetLabel(label1); + + UILabel* label2 = new UILabel(); + parentContainer->Add(label2); + label2->SetPosition(positionX_ + 2 * TEST_VIEW_GAP, 2 * GAP, TEXT_W, TEXT_H); // 2: tow gap + label2->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + testView->SetLabel2(label2); + + if (hasListener) { + UIView::OnClickListener* clickListenerParent = + new TestOnClickListener(label2, const_cast("l-parent click"), isBubble); + UIView::OnClickListener* clickListenerChild = + new TestOnClickListener(label1, const_cast("l-click"), isBubble); + testView->SetOnClickListener(clickListenerChild); + parentContainer->SetOnClickListener(clickListenerParent); + + UIView::OnLongPressListener* longTouchListenerParent = + new TestOnLongPressListener(label2, const_cast("l-parent long press"), isBubble); + UIView::OnLongPressListener* longTouchListenerChild = + new TestOnLongPressListener(label1, const_cast("l-long press"), isBubble); + testView->SetOnLongPressListener(longTouchListenerChild); + parentContainer->SetOnLongPressListener(longTouchListenerParent); + + UIView::OnTouchListener* touchListenerParent = new TestOnTouchListener( + label2, const_cast("l-parent press"), const_cast("l-parent release"), + const_cast("l-parent cancel"), isBubble); + UIView::OnTouchListener* touchListenerChild = + new TestOnTouchListener(label1, const_cast("l-press"), const_cast("l-release"), + const_cast("l-cancel"), isBubble); + testView->SetOnTouchListener(touchListenerChild); + parentContainer->SetOnTouchListener(touchListenerParent); + } + + positionY_ += ITEM_H; + } +} + +void UITestInputEvent::InnerBubbleDragTest(const char* title, + bool childDraggable, + bool parentDraggable, + bool hasListener, + bool isBubble) +{ + int itemH1 = ITEM_H * 2; // 2 times of ITEM_H + int itemH2 = itemH1 + ITEM_H; + int itemH3 = itemH2 + ITEM_H; + int halfScreenWith = Screen::GetInstance().GetWidth() / 2; // 2: half screen width + int offset = 30; // 40 pixel offset + + if (container_ != nullptr) { + UILabel* label = new UILabel(); + container_->Add(label); + // 2: half screen width + label->SetPosition(halfScreenWith + TEXT_DISTANCE_TO_LEFT_SIDE, positionY_, + halfScreenWith - TEXT_DISTANCE_TO_LEFT_SIDE, TEXT_H); + label->SetText(title); + label->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + positionY_ += (TEXT_H + GAP); + + OHOS::TestUIScrollView* parentScroll = new TestUIScrollView(); + parentScroll->SetStyle(STYLE_BACKGROUND_COLOR, Color::Blue().full); + parentScroll->SetPosition(halfScreenWith + TEXT_DISTANCE_TO_LEFT_SIDE, positionY_, itemH1, itemH1); + parentScroll->SetThrowDrag(parentDraggable); + parentScroll->SetDraggable(parentDraggable); + container_->Add(parentScroll); + + OHOS::TestUIScrollView* childScroll = new TestUIScrollView(); + childScroll->SetStyle(STYLE_BACKGROUND_COLOR, Color::Red().full); + childScroll->SetPosition(offset, offset, itemH2, itemH2); + childScroll->SetThrowDrag(childDraggable); + childScroll->SetDraggable(childDraggable); + parentScroll->Add(childScroll); + + UILabelButton* button1 = new UILabelButton(); + button1->SetStyle(STYLE_BACKGROUND_COLOR, Color::Yellow().full); + button1->SetText("button1"); + button1->SetPosition(offset, offset, itemH3 * 2, itemH3); // 2: tow width + childScroll->Add(button1); + + UILabel* label1 = new UILabel(); + container_->Add(label1); + label1->SetPosition(itemH3 + offset + halfScreenWith, positionY_ + 2 * GAP, TEXT_W, TEXT_H); // 2: tow gap + label1->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + childScroll->SetLabel(label1); + + UILabel* label2 = new UILabel(); + container_->Add(label2); + label2->SetPosition(itemH3 + offset + halfScreenWith, positionY_ + 6 * GAP, TEXT_W, TEXT_H); // 6: tow gap + label2->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + parentScroll->SetLabel(label2); + + if (hasListener) { + UIView::OnClickListener* clickListenerParent = + new TestOnClickListener(label2, const_cast("l-parent click"), isBubble); + UIView::OnClickListener* clickListenerChild = + new TestOnClickListener(label1, const_cast("l-click"), isBubble); + childScroll->SetOnClickListener(clickListenerChild); + parentScroll->SetOnClickListener(clickListenerParent); + + UIView::OnLongPressListener* longTouchListenerParent = + new TestOnLongPressListener(label2, const_cast("l-parent long press"), isBubble); + UIView::OnLongPressListener* longTouchListenerChild = + new TestOnLongPressListener(label1, const_cast("l-long press"), isBubble); + childScroll->SetOnLongPressListener(longTouchListenerChild); + parentScroll->SetOnLongPressListener(longTouchListenerParent); + + UIView::OnTouchListener* touchListenerParent = new TestOnTouchListener( + label2, const_cast("l-parent press"), const_cast("l-parent release"), + const_cast("l-parent cancel"), isBubble); + UIView::OnTouchListener* touchListenerChild = + new TestOnTouchListener(label1, const_cast("l-press"), const_cast("l-release"), + const_cast("l-cancel"), isBubble); + childScroll->SetOnTouchListener(touchListenerChild); + parentScroll->SetOnTouchListener(touchListenerParent); + + UIView::OnDragListener* dragListenerParent = new TestOnDragListener( + label2, const_cast("l-dragStart parent"), const_cast("l-drag parent"), + const_cast("l-dragEnd parent"), isBubble); + UIView::OnDragListener* dragListenerChild = + new TestOnDragListener(label1, const_cast("l-dragStart"), const_cast("l-drag"), + const_cast("l-dragEnd"), isBubble); + childScroll->SetOnDragListener(dragListenerChild); + parentScroll->SetOnDragListener(dragListenerParent); + } + positionY_ += itemH1; + } +} +} // namespace OHOS diff --git a/test/uitest/test_input_event/ui_test_input_event.h b/test/uitest/test_input_event/ui_test_input_event.h new file mode 100755 index 0000000..c2acebd --- /dev/null +++ b/test/uitest/test_input_event/ui_test_input_event.h @@ -0,0 +1,252 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef UI_TEST_INPUT_EVENT_H +#define UI_TEST_INPUT_EVENT_H + +#include "components/root_view.h" +#include "components/ui_label.h" +#include "components/ui_label_button.h" +#include "components/ui_scroll_view.h" +#include "ui_test.h" + +namespace OHOS { +class TestKeyInputListener : public RootView::OnKeyActListener { +public: + explicit TestKeyInputListener(UILabel* label) : label_(label) {} + virtual ~TestKeyInputListener() {} + bool OnKeyAct(UIView& view, const KeyEvent& event) override + { + if (label_ != nullptr) { + label_->SetText("key pressed!"); + label_->Invalidate(); + } + return true; + } + +private: + UILabel* label_; +}; + +class TestOnClickListener : public UIView::OnClickListener { +public: + explicit TestOnClickListener(UILabel* label, char* sentence, bool isConsume) + : label_(label), sentence_(sentence), isConsume_(isConsume) {} + virtual ~TestOnClickListener() {} + virtual bool OnClick(UIView& view, const ClickEvent& event) + { + if (label_ != nullptr) { + label_->SetText(sentence_); + label_->Invalidate(); + } + return isConsume_; + } + +private: + UILabel* label_; + char* sentence_; + bool isConsume_; +}; + +class TestOnLongPressListener : public UIView::OnLongPressListener { +public: + explicit TestOnLongPressListener(UILabel* label, char* sentence, bool isConsume) + : label_(label), sentence_(sentence), isConsume_(isConsume) {} + virtual ~TestOnLongPressListener() {} + virtual bool OnLongPress(UIView& view, const LongPressEvent& event) + { + if (label_ != nullptr) { + label_->SetText(sentence_); + label_->Invalidate(); + } + return isConsume_; + } + +private: + UILabel* label_; + char* sentence_; + bool isConsume_; +}; + +class TestOnTouchListener : public UIView::OnTouchListener { +public: + explicit TestOnTouchListener(UILabel* label, char* strPress, char* strRelease, char* strCancel, bool isConsume) + : label_(label), strPress_(strPress), strRelease_(strRelease), strCancel_(strCancel), isConsume_(isConsume) {} + virtual ~TestOnTouchListener() {} + virtual bool OnPress(UIView& view, const PressEvent& event) + { + if (label_ != nullptr) { + label_->SetText(strPress_); + label_->Invalidate(); + } + return isConsume_; + } + + virtual bool OnRelease(UIView& view, const ReleaseEvent& event) + { + if (label_ != nullptr) { + label_->SetText(strRelease_); + label_->Invalidate(); + } + return isConsume_; + } + + virtual bool OnCancel(UIView& view, const CancelEvent& event) + { + if (label_ != nullptr) { + label_->SetText(strCancel_); + label_->Invalidate(); + } + return isConsume_; + } + +private: + UILabel* label_; + char* strPress_; + char* strRelease_; + char* strCancel_; + bool isConsume_; +}; + +class TestOnDragListener : public UIView::OnDragListener { +public: + explicit TestOnDragListener(UILabel* label, char* strDragStart, char* strDrag, char* strDragEnd, bool isConsume) + : label_(label), strDragStart_(strDragStart), strDrag_(strDrag), strDragEnd_(strDragEnd), isConsume_(isConsume) + { + } + virtual ~TestOnDragListener() {} + virtual bool OnDragStart(UIView& view, const DragEvent& event) + { + if (label_ != nullptr) { + label_->SetText(strDragStart_); + label_->Invalidate(); + } + return isConsume_; + } + + virtual bool OnDrag(UIView& view, const DragEvent& event) + { + if (label_ != nullptr) { + label_->SetText(strDrag_); + label_->Invalidate(); + } + return isConsume_; + } + + virtual bool OnDragEnd(UIView& view, const DragEvent& event) + { + if (label_ != nullptr) { + label_->SetText(strDragEnd_); + label_->Invalidate(); + } + return isConsume_; + } + +private: + UILabel* label_; + char* strDragStart_; + char* strDrag_; + char* strDragEnd_; + bool isConsume_; +}; + + +class UITestInputEvent : public UITest { +public: + UITestInputEvent() {} + ~UITestInputEvent() {} + void SetUp() override; + void TearDown() override; + UIView* GetTestView() override; + + /** + * @brief Test if dispatch press\release\longpress\cancel event act normal when target is touchable + */ + void UIKit_Pointer_Input_Test_Dispatch_Simple_Event_001(); + /** + * @brief Test if dispatch press\release\longpress\cancel\drag event act normal when target is untouchable + */ + void UIKit_Pointer_Input_Test_Dispatch_Simple_Event_002(); + /** + * @brief Test if dispatch drag event act normal when target is touchable and draggable and drag parent instead. + */ + void UIKit_Pointer_Input_Test_Dispatch_Drag_Event_001(); + /** + * @brief Test if dispatch drag event act normal when target is touchable and draggable and not drag parent instead. + */ + void UIKit_Pointer_Input_Test_Dispatch_Drag_Event_002(); + /** + * @brief Test if dispatch drag event act normal when target is untouchable but draggable. + */ + void UIKit_Pointer_Input_Test_Dispatch_Drag_Event_003(); + /** + * @brief Test if dispatch drag event act normal when target is untouchable but draggable. + */ + void UIKit_Pointer_Input_Test_Dispatch_Key_Event_001(); + /** + * @brief Test if dispatch drag event act normal when target is untouchable but draggable. + */ + void UIKit_Pointer_Input_Test_Dispatch_InVisible_Event_001(); + /** + * @brief Test click, release or longClick event bubble act normal when both of parent and child is triggered. + */ + void UIKit_Pointer_Input_Test_Dispatch_Bubble_001(); + /** + * @brief Test click, release or longClick event bubble act normal when child is triggered but and parent not. + */ + void UIKit_Pointer_Input_Test_Dispatch_Bubble_002(); + /** + * @brief Test click, release or longClick event bubble act normal when child is triggered but and parent not. + */ + void UIKit_Pointer_Input_Test_Dispatch_Bubble_003(); + /** + * @brief Test click, release or longClick event bubble act normal when parent is triggered but and child not. + */ + void UIKit_Pointer_Input_Test_Dispatch_Bubble_004(); + /** + * @brief Test drag event bubble act normal when both of parent and child is triggered. + */ + void UIKit_Pointer_Input_Test_Dispatch_Bubble_005(); + /** + * @brief Test drag event bubble act normal when child is triggered but and parent not. + */ + void UIKit_Pointer_Input_Test_Dispatch_Bubble_006(); + /** + * @brief Test drag event bubble act normal when child is triggered but and parent not. + */ + void UIKit_Pointer_Input_Test_Dispatch_Bubble_007(); + /** + * @brief Test drag event bubble act normal when both of parent and child is not triggered. + */ + void UIKit_Pointer_Input_Test_Dispatch_Bubble_008(); + /** + * @brief Test drag event bubble act normal when parent is triggered but and child not. + */ + void UIKit_Pointer_Input_Test_Dispatch_Bubble_009(); + /** + * @brief Test drag event bubble act normal when parent is triggered but and child not. + */ + void UIKit_Pointer_Input_Test_Dispatch_Bubble_010(); + +private: + UIScrollView* container_ = nullptr; + TestKeyInputListener* keyListener_ = nullptr; + void InnerTest(const char* title, bool touchable, bool draggable, bool dragParent); + void InnerBubbleTest(const char* title, bool touchable, bool draggable, bool hasListener, bool isBubble); + void InnerBubbleDragTest(const char* title, bool childDraggable, bool parentDraggable, bool hasListener, + bool isBubble); +}; +} // namespace OHOS +#endif // UI_TEST_INPUT_EVENT_H diff --git a/test/uitest/test_label/ui_test_label.cpp b/test/uitest/test_label/ui_test_label.cpp new file mode 100755 index 0000000..ee1d617 --- /dev/null +++ b/test/uitest/test_label/ui_test_label.cpp @@ -0,0 +1,280 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ui_test_label.h" +#include "common/screen.h" +#include "components/ui_checkbox.h" +#include "components/ui_label.h" +#include "font/ui_font.h" + +namespace OHOS { +namespace { +const int16_t GAP = 5; +const int16_t TITLE_HEIGHT = 20; +const uint16_t LABEL_WIDTH = 350; +const uint16_t LABEL_HEIGHT = 50; +} // namespace + +void UITestLabel::SetUp() +{ + if (container_ == nullptr) { + container_ = new UIScrollView(); + container_->SetThrowDrag(true); + container_->SetHorizontalScrollState(false); + container_->Resize(Screen::GetInstance().GetWidth(), Screen::GetInstance().GetHeight() - BACK_BUTTON_HEIGHT); + positionX_ = 0; + positionY_ = 0; + } +} + +void UITestLabel::TearDown() +{ + DeleteChildren(container_); + container_ = nullptr; + uiLabel = nullptr; + labelFontSizeBtn1_ = nullptr; + labelFontSizeBtn2_ = nullptr; + labelFontSizeBtn3_ = nullptr; + labelHorAlignBtn1_ = nullptr; + labelHorAlignBtn2_ = nullptr; + labelHorAlignBtn3_ = nullptr; + labelColorBtn1_ = nullptr; + labelColorBtn2_ = nullptr; + labelColorBtn3_ = nullptr; + labelBeyondBtn1_ = nullptr; + labelBeyondBtn2_ = nullptr; + labelBeyondBtn3_ = nullptr; + labelLineBtn1_ = nullptr; + labelLineBtn2_ = nullptr; + labelVerAlignlBtn1_ = nullptr; + labelVerAlignlBtn2_ = nullptr; + labelVerAlignlBtn3_ = nullptr; + labelDirectionBtn1_ = nullptr; + labelDirectionBtn2_ = nullptr; + labelSizeBtn1_ = nullptr; + labelSizeBtn2_ = nullptr; +} + +UIView* UITestLabel::GetTestView() +{ + UIKit_UILabel_Test_Display_001(); + UIKit_UILabel_Test_Display_002(); + UIKit_UILabel_Test_Display_003(); + return container_; +} + +void UITestLabel::UIKit_UILabel_Test_Display_001() +{ + if (container_ != nullptr) { + UIViewGroup* uiViewGroup = new UIViewGroup(); + // 320: width; 390: height + uiViewGroup->SetPosition(positionX_, positionY_, 320, 390); + container_->Add(uiViewGroup); + UILabel* label = new UILabel(); + uiViewGroup->Add(label); + // 288: width; 48: height + label->SetPosition(TEXT_DISTANCE_TO_LEFT_SIDE, TEXT_DISTANCE_TO_TOP_SIDE, 288, 48); + label->SetText("UILabel效果"); + label->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + UIViewGroup* uiViewGroupFrame = new UIViewGroup(); + uiViewGroup->Add(uiViewGroupFrame); + // 288: width; 336: height + uiViewGroupFrame->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE2, VIEW_DISTANCE_TO_TOP_SIDE, 288, 336); + uiViewGroupFrame->SetStyle(STYLE_BORDER_COLOR, Color::White().full); + uiViewGroupFrame->SetStyle(STYLE_BORDER_OPA, HALF_OPA_OPAQUE); + uiViewGroupFrame->SetStyle(STYLE_BORDER_WIDTH, VIEW_STYLE_BORDER_WIDTH); + uiViewGroupFrame->SetStyle(STYLE_BORDER_RADIUS, VIEW_STYLE_BORDER_RADIUS); + uiViewGroupFrame->SetStyle(STYLE_BACKGROUND_OPA, 0); + uiLabel = new UILabel(); + uiLabel->SetText("鸿蒙轻量级图形子系统"); + uiLabel->SetFont(DEFAULT_VECTOR_FONT_FILENAME, 24); // 24: font size + uiLabel->SetPosition(0, 141, 280, 336); // 141: y-coordinate; 280: width; 336: height + uiViewGroupFrame->Add(uiLabel); + positionX_ += 336; // 336: x-coordinate + + UIViewGroup* uiViewGroup2 = new UIViewGroup(); + uiViewGroup2->SetPosition(positionX_, 0, 300, 384); // 300: width; 384: height + container_->Add(uiViewGroup2); + CreateLabels(uiViewGroup2); + } +} + +void UITestLabel::CreateLabels(UIViewGroup* uiViewGroup) +{ + if (uiViewGroup == nullptr) { + return; + } + UILabel* label1 = new UILabel(); + uiViewGroup->Add(label1); + // 288: width; 48: height + label1->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE2, TEXT_DISTANCE_TO_TOP_SIDE, 288, 48); + label1->SetText("字号"); + label1->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + labelFontSizeBtn1_ = SetUpButton("18 ", 24, 48, uiViewGroup); // 24: x-coordinate; 48: y-coordinate + labelFontSizeBtn2_ = SetUpButton("24", 116, 48, uiViewGroup); // 116: x-coordinate; 48: y-coordinate + labelFontSizeBtn3_ = SetUpButton("30", 208, 48, uiViewGroup); // 208: x-coordinate; 48: y-coordinate + + UILabel* label2 = new UILabel(); + uiViewGroup->Add(label2); + label2->SetPosition(24, 107, 288, 48); // 24: x-coordinate, 107: y-coordinate, 288: width; 48: height + label2->SetText("水平对齐方式"); + label2->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + labelHorAlignBtn1_ = SetUpButton("左对齐 ", 24, 144, uiViewGroup); // 24: x-coordinate; 144: y-coordinate + labelHorAlignBtn2_ = SetUpButton("中对齐 ", 116, 144, uiViewGroup); // 116: x-coordinate; 144: y-coordinate + labelHorAlignBtn3_ = SetUpButton("右对齐 ", 208, 144, uiViewGroup); // 208: x-coordinate; 144: y-coordinate + + UILabel* label3 = new UILabel(); + uiViewGroup->Add(label3); + label3->SetPosition(24, 203, 288, 48); // 24: x-coordinate, 203: y-coordinate, 288: width; 48: height + label3->SetText("颜色"); + label3->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + labelColorBtn1_ = SetUpButton("白色 ", 24, 240, uiViewGroup); // 24: x-coordinate; 240: y-coordinate + labelColorBtn2_ = SetUpButton("变色 ", 116, 240, uiViewGroup); // 116: x-coordinate; 240: y-coordinate + labelColorBtn3_ = SetUpButton("半透明 ", 208, 240, uiViewGroup); // 208: x-coordinate; 240: y-coordinate + + UILabel* label4 = new UILabel(); + uiViewGroup->Add(label4); + label4->SetPosition(24, 299, 288, 48); // 24: x-coordinate, 299: y-coordinate, 288: width; 48: height + label4->SetText("超出处理"); + label4->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + labelBeyondBtn1_ = SetUpButton("截断 ", 24, 336, uiViewGroup); // 24: x-coordinate; 336: y-coordinate + labelBeyondBtn2_ = SetUpButton("省略 ", 116, 336, uiViewGroup); // 116: x-coordinate; 336: y-coordinate + labelBeyondBtn3_ = SetUpButton("滚动 ", 208, 336, uiViewGroup); // 208: x-coordinate; 336: y-coordinate + positionX_ += 312; // 312: increase y-coordinate +} + +void UITestLabel::UIKit_UILabel_Test_Display_002() +{ + if (container_ != nullptr) { + UIViewGroup* uiViewGroup = new UIViewGroup(); + uiViewGroup->SetPosition(positionX_, positionY_, 320, 390); // 320: width; 390: height + container_->Add(uiViewGroup); + UILabel* label = new UILabel(); + uiViewGroup->Add(label); + // 288: width; 48: height + label->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE2, TEXT_DISTANCE_TO_TOP_SIDE, 288, 48); + label->SetText("行数"); + label->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + labelLineBtn1_ = SetUpButton("单行 ", 24, 48, uiViewGroup); // 24: x-coordinate; 48: y-coordinate + labelLineBtn2_ = SetUpButton("多行 ", 116, 48, uiViewGroup); // 116: x-coordinate; 48: y-coordinate + UILabel* label2 = new UILabel(); + uiViewGroup->Add(label2); + label2->SetPosition(24, 107, 288, 48); // 24: x-coordinate, 107: y-coordinate, 288: width; 48: height + label2->SetText("竖直对齐方式"); + label2->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + labelVerAlignlBtn1_ = SetUpButton("上对齐 ", 24, 144, uiViewGroup); // 24: x-coordinate; 144: y-coordinate + labelVerAlignlBtn2_ = SetUpButton("中对齐 ", 116, 144, uiViewGroup); // 116: x-coordinate; 144: y-coordinate + labelVerAlignlBtn3_ = SetUpButton("下对齐 ", 208, 144, uiViewGroup); // 208: x-coordinate; 144: y-coordinate + UILabel* label3 = new UILabel(); + uiViewGroup->Add(label3); + label3->SetPosition(24, 203, 288, 48); // 24: x-coordinate, 203: y-coordinate, 288: width; 48: height + label3->SetText("方向"); + label3->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + labelDirectionBtn1_ = SetUpButton("左往右 ", 24, 240, uiViewGroup); // 24: x-coordinate; 240: y-coordinate + labelDirectionBtn2_ = SetUpButton("右往左 ", 116, 240, uiViewGroup); // 116: x-coordinate; 240: y-coordinate + positionY_ += 384; // 384: increase x-coordinate + positionX_ -= 312; // 312: increase y-coordinate + } +} + +void UITestLabel::UIKit_UILabel_Test_Display_003() +{ + if (container_ != nullptr) { + UIViewGroup* uiViewGroup = new UIViewGroup(); + uiViewGroup->SetPosition(positionX_, positionY_, 450, 110); // 450: width; 110: height + container_->Add(uiViewGroup); + UILabel* label = new UILabel(); + uiViewGroup->Add(label); + label->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE2, TEXT_DISTANCE_TO_TOP_SIDE, 420, 48); // 420: width; 48: height + label->SetText("Label大小"); + label->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + // 24: x-coordinate; 48: y-coordinate; 120: width + labelSizeBtn1_ = SetUpButton("动态宽度 ", 24, 48, uiViewGroup, 120); + // 156: x-coordinate; 48: y-coordinate; 120: width + labelSizeBtn2_ = SetUpButton("动态高度 ", 156, 48, uiViewGroup, 120); + } +} + +bool UITestLabel::OnClick(UIView& view, const ClickEvent& event) +{ + uiLabel->Resize(288, 100); // 288: width; 100: height + uiLabel->SetFont(DEFAULT_VECTOR_FONT_FILENAME, 24); // 24: font size + if (&view == labelFontSizeBtn1_) { + uiLabel->SetFont(DEFAULT_VECTOR_FONT_FILENAME, 18); // 18: font size + } else if (&view == labelFontSizeBtn2_) { + uiLabel->SetFont(DEFAULT_VECTOR_FONT_FILENAME, 24); // 24: font size + } else if (&view == labelFontSizeBtn3_) { + uiLabel->SetFont(DEFAULT_VECTOR_FONT_FILENAME, 30); // 30: font size + } else if (&view == labelHorAlignBtn1_) { + uiLabel->SetAlign(UITextLanguageAlignment::TEXT_ALIGNMENT_LEFT); + } else if (&view == labelHorAlignBtn2_) { + uiLabel->SetAlign(UITextLanguageAlignment::TEXT_ALIGNMENT_CENTER); + } else if (&view == labelHorAlignBtn3_) { + uiLabel->SetAlign(UITextLanguageAlignment::TEXT_ALIGNMENT_RIGHT); + } else if (&view == labelColorBtn1_) { + uiLabel->SetStyle(STYLE_TEXT_COLOR, Color::White().full); + uiLabel->SetStyle(STYLE_TEXT_OPA, OPA_OPAQUE); + } else if (&view == labelColorBtn2_) { + uiLabel->SetStyle(STYLE_TEXT_COLOR, Color::Red().full); + uiLabel->SetStyle(STYLE_TEXT_OPA, OPA_OPAQUE); + } else if (&view == labelColorBtn3_) { + uiLabel->SetStyle(STYLE_TEXT_OPA, 127); // 127: opaque + } else if (&view == labelBeyondBtn1_) { + uiLabel->Resize(288, 35); // 288: width; 35: height + uiLabel->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + uiLabel->SetText("鸿蒙轻量级图形子系统,鸿蒙轻量级图形子系统 "); + uiLabel->SetLineBreakMode(UILabel::LINE_BREAK_CLIP); + } else if (&view == labelBeyondBtn2_) { + uiLabel->Resize(288, 35); // 288: width; 35: height + uiLabel->SetText("鸿蒙轻量级图形子系统,鸿蒙轻量级图形子系统 "); + uiLabel->SetLineBreakMode(UILabel::LINE_BREAK_ELLIPSIS); + } else if (&view == labelBeyondBtn3_) { + uiLabel->SetText("鸿蒙轻量级图形子系统,鸿蒙轻量级图形子系统 "); + uiLabel->SetLineBreakMode(UILabel::LINE_BREAK_MARQUEE); + } else { + ExpandClick(view, event); + } + return true; +} + +void UITestLabel::ExpandClick(UIView& view, const ClickEvent& event) const +{ + if (&view == labelLineBtn1_) { + uiLabel->Resize(288, 50); // 288: width; 50: height + uiLabel->SetFont(DEFAULT_VECTOR_FONT_FILENAME, 30); // 30:font size + uiLabel->SetLineBreakMode(UILabel::LINE_BREAK_ELLIPSIS); + } else if (&view == labelLineBtn2_) { + uiLabel->Resize(288, 100); // 288: width; 100: height + uiLabel->SetFont(DEFAULT_VECTOR_FONT_FILENAME, 30); // 30:font size + uiLabel->SetLineBreakMode(UILabel::LINE_BREAK_ELLIPSIS); + } else if (&view == labelVerAlignlBtn1_) { + uiLabel->SetAlign(UITextLanguageAlignment::TEXT_ALIGNMENT_LEFT, UITextLanguageAlignment::TEXT_ALIGNMENT_TOP); + } else if (&view == labelVerAlignlBtn2_) { + uiLabel->SetAlign(UITextLanguageAlignment::TEXT_ALIGNMENT_LEFT, UITextLanguageAlignment::TEXT_ALIGNMENT_CENTER); + } else if (&view == labelVerAlignlBtn3_) { + uiLabel->SetAlign(UITextLanguageAlignment::TEXT_ALIGNMENT_LEFT, UITextLanguageAlignment::TEXT_ALIGNMENT_BOTTOM); + } else if (&view == labelDirectionBtn1_) { + uiLabel->SetDirect(TEXT_DIRECT_LTR); + } else if (&view == labelDirectionBtn2_) { + uiLabel->SetDirect(TEXT_DIRECT_RTL); + } else if (&view == labelSizeBtn1_) { + uiLabel->SetText("鸿蒙轻量级图形子系统,鸿蒙轻量级图形子系统 "); + uiLabel->SetLineBreakMode(UILabel::LINE_BREAK_ADAPT); + } else if (&view == labelSizeBtn2_) { + uiLabel->SetText("鸿蒙轻量级图形子系统,鸿蒙轻量级图形子系统 "); + uiLabel->SetLineBreakMode(UILabel::LINE_BREAK_WRAP); + } +} +} // namespace OHOS \ No newline at end of file diff --git a/test/uitest/test_label/ui_test_label.h b/test/uitest/test_label/ui_test_label.h new file mode 100755 index 0000000..25bc732 --- /dev/null +++ b/test/uitest/test_label/ui_test_label.h @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef UI_TEST_LABEL_H +#define UI_TEST_LABEL_H + +#include "components/ui_label.h" +#include "components/ui_label_button.h" +#include "components/ui_scroll_view.h" +#include "ui_test.h" + +namespace OHOS { +class UITestLabel : public UITest, public UIView::OnClickListener { +public: + UITestLabel() {} + ~UITestLabel() {} + void SetUp() override; + void TearDown() override; + UIView* GetTestView() override; + void CreateLabels(UIViewGroup* uiViewGroup); + + /** + * @brief Test display dynamic text + */ + void UIKit_UILabel_Test_Display_001(); + void UIKit_UILabel_Test_Display_002(); + void UIKit_UILabel_Test_Display_003(); + + bool OnClick(UIView& view, const ClickEvent& event) override; + void ExpandClick(UIView& view, const ClickEvent& event) const; + +private: + UIScrollView* container_ = nullptr; + UILabel* uiLabel = nullptr; + UILabelButton* labelFontSizeBtn1_ = nullptr; + UILabelButton* labelFontSizeBtn2_ = nullptr; + UILabelButton* labelFontSizeBtn3_ = nullptr; + UILabelButton* labelHorAlignBtn1_ = nullptr; + UILabelButton* labelHorAlignBtn2_ = nullptr; + UILabelButton* labelHorAlignBtn3_ = nullptr; + UILabelButton* labelColorBtn1_ = nullptr; + UILabelButton* labelColorBtn2_ = nullptr; + UILabelButton* labelColorBtn3_ = nullptr; + UILabelButton* labelBeyondBtn1_ = nullptr; + UILabelButton* labelBeyondBtn2_ = nullptr; + UILabelButton* labelBeyondBtn3_ = nullptr; + UILabelButton* labelLineBtn1_ = nullptr; + UILabelButton* labelLineBtn2_ = nullptr; + UILabelButton* labelVerAlignlBtn1_ = nullptr; + UILabelButton* labelVerAlignlBtn2_ = nullptr; + UILabelButton* labelVerAlignlBtn3_ = nullptr; + UILabelButton* labelDirectionBtn1_ = nullptr; + UILabelButton* labelDirectionBtn2_ = nullptr; + UILabelButton* labelSizeBtn1_ = nullptr; + UILabelButton* labelSizeBtn2_ = nullptr; + + void InnerTestTitle(const char* title); + UILabelButton* SetUpButton(const char* title, int16_t x, int16_t y, UIViewGroup* uiViewGroup, + int16_t width = 80, int16_t height = 40) + { + if (uiViewGroup == nullptr) { + return nullptr; + } + UILabelButton* btn = new UILabelButton(); + btn->SetPosition(x, y, width, height); + btn->SetText(title); + btn->SetFont(DEFAULT_VECTOR_FONT_FILENAME, BUTTON_LABEL_SIZE); + btn->SetOnClickListener(this); + btn->SetStyleForState(STYLE_BORDER_RADIUS, BUTTON_STYLE_BORDER_RADIUS_VALUE, UIButton::RELEASED); + btn->SetStyleForState(STYLE_BORDER_RADIUS, BUTTON_STYLE_BORDER_RADIUS_VALUE, UIButton::PRESSED); + btn->SetStyleForState(STYLE_BORDER_RADIUS, BUTTON_STYLE_BORDER_RADIUS_VALUE, UIButton::INACTIVE); + btn->SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::RELEASED); + btn->SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::PRESSED); + btn->SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::INACTIVE); + uiViewGroup->Add(btn); + return btn; + } +}; +} // namespace OHOS +#endif // UI_TEST_LABEL_H diff --git a/test/uitest/test_layout/ui_test_advanced_layout.cpp b/test/uitest/test_layout/ui_test_advanced_layout.cpp new file mode 100755 index 0000000..f7776e2 --- /dev/null +++ b/test/uitest/test_layout/ui_test_advanced_layout.cpp @@ -0,0 +1,284 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ui_test_advanced_layout.h" +#include "common/screen.h" + +namespace OHOS { +namespace { +const int16_t TEXT_H = 20; +const int16_t TEXT_W = 250; +const int16_t TEST_VIEW_H = 40; +const int16_t TEST_VIEW_W = 40; +const int16_t GAP = 5; +const int16_t TEST_VIEW_GAP = 80; +} // namespace + +void UITestAdvancedLayout::SetUp() +{ + container_ = new UIScrollView(); + flexController_ = new GridLayout(); + fTarget_ = new FlexLayout(); + resetfBtn_ = new UILabelButton(); + marginfBtn_ = new UILabelButton(); + + horfBtn_ = new UILabelButton(); + horfRBtn_ = new UILabelButton(); + verfBtn_ = new UILabelButton(); + verfRBtn_ = new UILabelButton(); + wrapBtn_ = new UILabelButton(); + addElefBtn_ = new UILabelButton(); + majorStartBtn_ = new UILabelButton(); + majorEndBtn_ = new UILabelButton(); + majorCenterBtn_ = new UILabelButton(); + majorEvenBtn_ = new UILabelButton(); + majorAroundBtn_ = new UILabelButton(); + majorBetBtn_ = new UILabelButton(); + secStartBtn_ = new UILabelButton(); + secEndBtn_ = new UILabelButton(); + secCenterBtn_ = new UILabelButton(); + secInvalidBtn_ = new UILabelButton(); + layoutChildrenfBtn_ = new UILabelButton(); + + gridController_ = new GridLayout(); + gTarget_ = new GridLayout(); + horgBtn_ = new UILabelButton(); + horgRBtn_ = new UILabelButton(); + vergBtn_ = new UILabelButton(); + vergRBtn_ = new UILabelButton(); + incRowsBtn_ = new UILabelButton(); + decRowsBtn_ = new UILabelButton(); + incColsBtn_ = new UILabelButton(); + decColsBtn_ = new UILabelButton(); + addElegBtn_ = new UILabelButton(); + resetgBtn_ = new UILabelButton(); + margingBtn_ = new UILabelButton(); + layoutChildrengBtn_ = new UILabelButton(); + container_->Resize(Screen::GetInstance().GetWidth(), Screen::GetInstance().GetHeight() - BACK_BUTTON_HEIGHT); + positionX_ = 20; // 20: position x + positionY_ = 5; // 5: position y +} + +void UITestAdvancedLayout::TearDown() +{ + DeleteChildren(container_); + container_ = nullptr; +} + +UIView* UITestAdvancedLayout::GetTestView() +{ + UIKit_Layout_Test_FlexLayout_001(); + UIKit_Layout_Test_GridLayout_001(); + return container_; +} + +void UITestAdvancedLayout::UIKit_Layout_Test_FlexLayout_001() +{ + if (container_ != nullptr) { + UIViewGroup* uiViewGroup = new UIViewGroup(); + uiViewGroup->SetPosition(0, 0, Screen::GetInstance().GetWidth(), 420); // 420: height + container_->Add(uiViewGroup); + UILabel* label = new UILabel(); + uiViewGroup->Add(label); + // 288: width; 48: height + label->SetPosition(TEXT_DISTANCE_TO_LEFT_SIDE, TEXT_DISTANCE_TO_TOP_SIDE, 288, 48); + label->SetText("Flex布局效果"); + label->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + UIViewGroup* uiViewGroupFrame = new UIViewGroup(); + uiViewGroup->Add(uiViewGroupFrame); + uiViewGroupFrame->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE2, VIEW_DISTANCE_TO_TOP_SIDE, + Screen::GetInstance().GetWidth() - VIEW_DISTANCE_TO_LEFT_SIDE, + 100); // 100: height + uiViewGroupFrame->SetStyle(STYLE_BORDER_COLOR, Color::White().full); + uiViewGroupFrame->SetStyle(STYLE_BORDER_OPA, HALF_OPA_OPAQUE); + uiViewGroupFrame->SetStyle(STYLE_BORDER_WIDTH, VIEW_STYLE_BORDER_WIDTH); + uiViewGroupFrame->SetStyle(STYLE_BORDER_RADIUS, VIEW_STYLE_BORDER_RADIUS); + uiViewGroupFrame->SetStyle(STYLE_BACKGROUND_OPA, 0); + uiViewGroupFrame->Add(fTarget_); + + fTarget_->SetPosition(0, 0, Screen::GetInstance().GetWidth() - VIEW_DISTANCE_TO_LEFT_SIDE, 100); // 100: height + fTarget_->SetStyle(STYLE_BACKGROUND_OPA, 100); // 100: opacity + + positionY_ += 160; // 160: position y add + uiViewGroup->Add(flexController_); + flexController_->SetPosition(0, positionY_, Screen::GetInstance().GetWidth(), 250); // 250: height + flexController_->SetLayoutDirection(LAYOUT_HOR); + flexController_->SetRows(5); // 5: row + flexController_->SetCols(4); // 4: col + + SetUpButton(flexController_, resetfBtn_, "reset"); + SetUpButton(flexController_, marginfBtn_, "margin"); + SetUpButton(flexController_, horfBtn_, "hor"); + SetUpButton(flexController_, horfRBtn_, "hor_r"); + SetUpButton(flexController_, verfBtn_, "ver"); + SetUpButton(flexController_, verfRBtn_, "ver_r"); + SetUpButton(flexController_, wrapBtn_, "wrap"); + SetUpButton(flexController_, addElefBtn_, "add"); + SetUpButton(flexController_, majorStartBtn_, "M_start"); + SetUpButton(flexController_, majorEndBtn_, "M_end"); + SetUpButton(flexController_, majorCenterBtn_, "M_center"); + SetUpButton(flexController_, majorEvenBtn_, "M_even"); + SetUpButton(flexController_, majorAroundBtn_, "M_around"); + SetUpButton(flexController_, majorBetBtn_, "M_between"); + SetUpButton(flexController_, secStartBtn_, "S_start"); + SetUpButton(flexController_, secEndBtn_, "S_end"); + SetUpButton(flexController_, secCenterBtn_, "S_center"); + SetUpButton(flexController_, secInvalidBtn_, "S_invalid"); + SetUpButton(flexController_, layoutChildrenfBtn_, "布局"); + flexController_->LayoutChildren(); + positionY_ += 250; // 250: position y add + } +} + +void UITestAdvancedLayout::UIKit_Layout_Test_GridLayout_001() +{ + if (container_ != nullptr) { + UIViewGroup* uiViewGroup = new UIViewGroup(); + uiViewGroup->SetPosition(0, positionY_, Screen::GetInstance().GetWidth(), 370); // 370: height + container_->Add(uiViewGroup); + UILabel* label = new UILabel(); + uiViewGroup->Add(label); + // 288: widht; 48: height + label->SetPosition(TEXT_DISTANCE_TO_LEFT_SIDE, TEXT_DISTANCE_TO_TOP_SIDE, 288, 48); + label->SetText("Grid布局测试"); + label->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + UIViewGroup* uiViewGroupFrame = new UIViewGroup(); + uiViewGroup->Add(uiViewGroupFrame); + uiViewGroupFrame->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE2, VIEW_DISTANCE_TO_TOP_SIDE, + Screen::GetInstance().GetWidth() - VIEW_DISTANCE_TO_LEFT_SIDE, + 100); // 100: height + uiViewGroupFrame->SetStyle(STYLE_BORDER_COLOR, Color::White().full); + uiViewGroupFrame->SetStyle(STYLE_BORDER_OPA, HALF_OPA_OPAQUE); + uiViewGroupFrame->SetStyle(STYLE_BORDER_WIDTH, VIEW_STYLE_BORDER_WIDTH); + uiViewGroupFrame->SetStyle(STYLE_BORDER_RADIUS, VIEW_STYLE_BORDER_RADIUS); + uiViewGroupFrame->SetStyle(STYLE_BACKGROUND_OPA, 0); + uiViewGroupFrame->Add(gTarget_); + + gTarget_->SetPosition(0, 0, Screen::GetInstance().GetWidth() - VIEW_DISTANCE_TO_LEFT_SIDE, 100); // 100: height + uiViewGroup->Add(gridController_); + gridController_->SetPosition(0, 160, Screen::GetInstance().GetWidth(), 150); // 160: y-coordinate; 150: height + gridController_->SetLayoutDirection(LAYOUT_HOR); + gridController_->SetRows(3); // 3: row + gridController_->SetCols(4); // 4: col + + SetUpButton(gridController_, resetgBtn_, "reset"); + SetUpButton(gridController_, margingBtn_, "matgin"); + SetUpButton(gridController_, horgBtn_, "hor"); + SetUpButton(gridController_, horgRBtn_, "hor_r"); + SetUpButton(gridController_, vergBtn_, "ver"); + SetUpButton(gridController_, vergRBtn_, "ver_r"); + SetUpButton(gridController_, addElegBtn_, "add"); + SetUpButton(gridController_, incRowsBtn_, "incrows"); + SetUpButton(gridController_, decRowsBtn_, "decrows"); + SetUpButton(gridController_, incColsBtn_, "inccols"); + SetUpButton(gridController_, decColsBtn_, "deccols"); + SetUpButton(gridController_, layoutChildrengBtn_, "布局"); + gridController_->LayoutChildren(); + } +} + +void UITestAdvancedLayout::OnClickButton(const UIView& view) +{ + if (&view == resetgBtn_) { + Clear(gTarget_); + gTarget_->SetLayoutDirection(LAYOUT_HOR); + rows_ = 0; + cols_ = 0; + gTarget_->SetRows(rows_); + gTarget_->SetCols(cols_); + withMargin_ = false; + } else if (&view == margingBtn_) { + withMargin_ = true; + } else if (&view == horgBtn_) { + gTarget_->SetLayoutDirection(LAYOUT_HOR); + } else if (&view == horgRBtn_) { + gTarget_->SetLayoutDirection(LAYOUT_HOR_R); + } else if (&view == vergBtn_) { + gTarget_->SetLayoutDirection(LAYOUT_VER); + } else if (&view == vergRBtn_) { + gTarget_->SetLayoutDirection(LAYOUT_VER_R); + } else if (&view == addElegBtn_) { + AddElement(gTarget_); + } else if (&view == incRowsBtn_) { + rows_++; + gTarget_->SetRows(rows_); + } else if (&view == decRowsBtn_) { + rows_--; + gTarget_->SetRows(rows_); + } else if (&view == incColsBtn_) { + cols_++; + gTarget_->SetCols(cols_); + } else if (&view == decColsBtn_) { + cols_--; + gTarget_->SetCols(cols_); + } else if (&view == layoutChildrenfBtn_) { + fTarget_->LayoutChildren(); + fTarget_->Invalidate(); + } else if (&view == layoutChildrengBtn_) { + gTarget_->LayoutChildren(); + gTarget_->Invalidate(); + } else { + } +} + +bool UITestAdvancedLayout::OnClick(UIView& view, const ClickEvent& event) +{ + fTarget_->Invalidate(); + gTarget_->Invalidate(); + if (&view == resetfBtn_) { + Clear(fTarget_); + fTarget_->SetFlexWrap(false); + fTarget_->SetLayoutDirection(LAYOUT_HOR); + fTarget_->SetMajorAxisAlign(ALIGN_START); + fTarget_->SetSecondaryAxisAlign(ALIGN_CENTER); + withMargin_ = false; + } else if (&view == marginfBtn_) { + withMargin_ = true; + } else if (&view == horfBtn_) { + fTarget_->SetLayoutDirection(LAYOUT_HOR); + } else if (&view == horfRBtn_) { + fTarget_->SetLayoutDirection(LAYOUT_HOR_R); + } else if (&view == verfBtn_) { + fTarget_->SetLayoutDirection(LAYOUT_VER); + } else if (&view == verfRBtn_) { + fTarget_->SetLayoutDirection(LAYOUT_VER_R); + } else if (&view == wrapBtn_) { + fTarget_->SetFlexWrap(true); + } else if (&view == addElefBtn_) { + AddElement(fTarget_); + } else if (&view == majorStartBtn_) { + fTarget_->SetMajorAxisAlign(ALIGN_START); + } else if (&view == majorEndBtn_) { + fTarget_->SetMajorAxisAlign(ALIGN_END); + } else if (&view == majorCenterBtn_) { + fTarget_->SetMajorAxisAlign(ALIGN_CENTER); + } else if (&view == majorEvenBtn_) { + fTarget_->SetMajorAxisAlign(ALIGN_EVENLY); + } else if (&view == majorAroundBtn_) { + fTarget_->SetMajorAxisAlign(ALIGN_AROUND); + } else if (&view == majorBetBtn_) { + fTarget_->SetMajorAxisAlign(ALIGN_BETWEEN); + } else if (&view == secStartBtn_) { + fTarget_->SetSecondaryAxisAlign(ALIGN_START); + } else if (&view == secEndBtn_) { + fTarget_->SetSecondaryAxisAlign(ALIGN_CENTER); + } else if (&view == secCenterBtn_) { + fTarget_->SetMajorAxisAlign(ALIGN_END); + } else { + OnClickButton(view); + } + return true; +} +} // namespace OHOS \ No newline at end of file diff --git a/test/uitest/test_layout/ui_test_advanced_layout.h b/test/uitest/test_layout/ui_test_advanced_layout.h new file mode 100755 index 0000000..b67a7bb --- /dev/null +++ b/test/uitest/test_layout/ui_test_advanced_layout.h @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef UI_TEST_ADVANCED_LAYOUT_H +#define UI_TEST_ADVANCED_LAYOUT_H + +#include "components/ui_label.h" +#include "components/ui_label_button.h" +#include "components/ui_scroll_view.h" +#include "layout/flex_layout.h" +#include "layout/grid_layout.h" +#include "ui_test.h" + +namespace OHOS { +class UITestAdvancedLayout : public UITest, public UIView::OnClickListener { +public: + UITestAdvancedLayout() : withMargin_(false) {} + ~UITestAdvancedLayout() {} + void SetUp() override; + void TearDown() override; + UIView* GetTestView() override; + + /** + * @brief Test flex layout function. + */ + void UIKit_Layout_Test_FlexLayout_001(); + /** + * @brief Test grid layout function. + */ + void UIKit_Layout_Test_GridLayout_001(); + + bool OnClick(UIView& view, const ClickEvent& event) override; + void OnClickButton(const UIView& view); +private: + UIScrollView* container_ = nullptr; + GridLayout* flexController_ = nullptr; + GridLayout* gridController_ = nullptr; + FlexLayout* fTarget_ = nullptr; + GridLayout* gTarget_ = nullptr; + + UILabelButton* resetfBtn_ = nullptr; + UILabelButton* horfBtn_ = nullptr; + UILabelButton* horfRBtn_ = nullptr; + UILabelButton* verfBtn_ = nullptr; + UILabelButton* verfRBtn_ = nullptr; + UILabelButton* wrapBtn_ = nullptr; + UILabelButton* addElefBtn_ = nullptr; + UILabelButton* majorStartBtn_ = nullptr; + UILabelButton* majorEndBtn_ = nullptr; + UILabelButton* majorCenterBtn_ = nullptr; + UILabelButton* majorEvenBtn_ = nullptr; + UILabelButton* majorAroundBtn_ = nullptr; + UILabelButton* majorBetBtn_ = nullptr; + UILabelButton* secStartBtn_ = nullptr; + UILabelButton* secEndBtn_ = nullptr; + UILabelButton* secCenterBtn_ = nullptr; + UILabelButton* secInvalidBtn_ = nullptr; + UILabelButton* marginfBtn_ = nullptr; + UILabelButton* layoutChildrenfBtn_ = nullptr; + UILabelButton* horgBtn_ = nullptr; + UILabelButton* horgRBtn_ = nullptr; + UILabelButton* vergBtn_ = nullptr; + UILabelButton* vergRBtn_ = nullptr; + UILabelButton* incRowsBtn_ = nullptr; + UILabelButton* decRowsBtn_ = nullptr; + UILabelButton* incColsBtn_ = nullptr; + UILabelButton* decColsBtn_ = nullptr; + UILabelButton* addElegBtn_ = nullptr; + UILabelButton* resetgBtn_ = nullptr; + UILabelButton* margingBtn_ = nullptr; + UILabelButton* layoutChildrengBtn_ = nullptr; + + int16_t w_ = 10; // 10: width + int16_t h_ = 10; // 10: height + int16_t rows_ = 0; + int16_t cols_ = 0; + bool withMargin_; + + void SetUpButton(Layout* controller, UILabelButton* btn, const char* title) + { + controller->Add(btn); + btn->Resize(BUTTON_WIDHT2, BUTTON_HEIGHT2); + btn->SetText(title); + btn->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + btn->SetOnClickListener(this); + btn->SetStyleForState(STYLE_BORDER_RADIUS, BUTTON_STYLE_BORDER_RADIUS_VALUE, UIButton::RELEASED); + btn->SetStyleForState(STYLE_BORDER_RADIUS, BUTTON_STYLE_BORDER_RADIUS_VALUE, UIButton::PRESSED); + btn->SetStyleForState(STYLE_BORDER_RADIUS, BUTTON_STYLE_BORDER_RADIUS_VALUE, UIButton::INACTIVE); + btn->SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::RELEASED); + btn->SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::PRESSED); + btn->SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::INACTIVE); + } + void AddElement(Layout* layout) + { + UIView* view = new UIView(); + view->SetStyle(STYLE_BACKGROUND_COLOR, Color::Red().full); + view->Resize(w_++, h_++); + if (withMargin_) { + view->SetStyle(STYLE_MARGIN_LEFT, 10); // 10: margin left + view->SetStyle(STYLE_MARGIN_RIGHT, 15); // 15: margin right + view->SetStyle(STYLE_MARGIN_TOP, 20); // 20: margin top + view->SetStyle(STYLE_MARGIN_BOTTOM, 25); // 25: margin bottom + } + layout->Add(view); + } + void Clear(Layout* layout) const + { + if (layout == nullptr) { + return; + } + UIView* child = layout->GetChildrenHead(); + while (child != nullptr) { + UIView* temp = child; + child = child->GetNextSibling(); + layout->Remove(temp); + delete temp; + } + } +}; +} // namespace OHOS +#endif // UI_TEST_INPUT_EVENT_H diff --git a/test/uitest/test_layout/ui_test_basic_layout.cpp b/test/uitest/test_layout/ui_test_basic_layout.cpp new file mode 100755 index 0000000..42d1a59 --- /dev/null +++ b/test/uitest/test_layout/ui_test_basic_layout.cpp @@ -0,0 +1,249 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ui_test_basic_layout.h" +#include "common/screen.h" + +namespace OHOS { +namespace { +const int16_t ITEM_H = 50; +const int16_t TEXT_H = 20; +const int16_t TEXT_W = 250; +const int16_t TEST_VIEW_H = 40; +const int16_t TEST_VIEW_W = 40; +const int16_t GAP = 5; +const int16_t TEST_VIEW_GAP = 80; +} // namespace + +void UITestBasicLayout::SetUp() +{ + container_ = new UIScrollView(); + layout_ = new GridLayout(); + uiViewGroupFrame_ = new UIViewGroup(); + testTarget_ = new UIViewGroup(); + testTargetSmall1_ = new UIView(); + testTargetSmall2_ = new UIView(); + refTarget_ = new UIView(); + resetBtn_ = new UILabelButton(); + marginBtn_ = new UILabelButton(); + padBtn_ = new UILabelButton(); + + lCenterOfParentBtn_ = new UILabelButton(); + lLeftOfParentBtn_ = new UILabelButton(); + lRightOfParentBtn_ = new UILabelButton(); + lTopOfParentBtn_ = new UILabelButton(); + lBottomOfParentBtn_ = new UILabelButton(); + + aLeftToSiblingBtn_ = new UILabelButton(); + aRightToSiblingBtn_ = new UILabelButton(); + aTopToSiblingBtn_ = new UILabelButton(); + aBottomToSiblingBtn_ = new UILabelButton(); + aHorCenterToSiblingBtn_ = new UILabelButton(); + aVerCenterToSiblingBtn_ = new UILabelButton(); + + lLeftToSiblingBtn_ = new UILabelButton(); + lRightToSiblingBtn_ = new UILabelButton(); + lTopToSiblingBtn_ = new UILabelButton(); + lBottomToSiblingBtn_ = new UILabelButton(); + lInvalidLayoutBtn_ = new UILabelButton(); + container_->Resize(Screen::GetInstance().GetWidth(), Screen::GetInstance().GetHeight() - BACK_BUTTON_HEIGHT); +} + +void UITestBasicLayout::TearDown() +{ + DeleteChildren(container_); + container_ = nullptr; + layout_ = nullptr; + testTarget_ = nullptr; + uiViewGroupFrame_ = nullptr; + testTargetSmall1_ = nullptr; + testTargetSmall2_ = nullptr; + refTarget_ = nullptr; + resetBtn_ = nullptr; + lCenterOfParentBtn_ = nullptr; + lLeftOfParentBtn_ = nullptr; + lRightOfParentBtn_ = nullptr; + lTopOfParentBtn_ = nullptr; + lBottomOfParentBtn_ = nullptr; + aLeftToSiblingBtn_ = nullptr; + aRightToSiblingBtn_ = nullptr; + aTopToSiblingBtn_ = nullptr; + aBottomToSiblingBtn_ = nullptr; + aHorCenterToSiblingBtn_ = nullptr; + aVerCenterToSiblingBtn_ = nullptr; + lLeftToSiblingBtn_ = nullptr; + lRightToSiblingBtn_ = nullptr; + lTopToSiblingBtn_ = nullptr; + lBottomToSiblingBtn_ = nullptr; + lInvalidLayoutBtn_ = nullptr; + marginBtn_ = nullptr; + padBtn_ = nullptr; + setStyle_ = nullptr; + positionX_ = 20; // 20: position x + positionY_ = 5; // 5 position y +} + +UIView* UITestBasicLayout::GetTestView() +{ + UIKit_Layout_Test_Basic_Layout_001(); + return container_; +} + +void UITestBasicLayout::UIKit_Layout_Test_Basic_Layout_001() +{ + if (container_ != nullptr) { + UIViewGroup* uiViewGroup = new UIViewGroup(); + uiViewGroup->SetPosition(0, 0, 320, 390); // 320: width; 390: height + container_->Add(uiViewGroup); + UILabel* label = new UILabel(); + uiViewGroup->Add(label); + label->SetPosition(TEXT_DISTANCE_TO_LEFT_SIDE, TEXT_DISTANCE_TO_TOP_SIDE, 288, 48); // 288: width; 48: height + label->SetText("布局效果"); + label->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + uiViewGroup->Add(uiViewGroupFrame_); + // 288: width; 336: height + uiViewGroupFrame_->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE2, VIEW_DISTANCE_TO_TOP_SIDE, 288, 336); + uiViewGroupFrame_->SetStyle(STYLE_BORDER_COLOR, Color::White().full); + uiViewGroupFrame_->SetStyle(STYLE_BORDER_OPA, HALF_OPA_OPAQUE); + uiViewGroupFrame_->SetStyle(STYLE_BORDER_WIDTH, VIEW_STYLE_BORDER_WIDTH); + uiViewGroupFrame_->SetStyle(STYLE_BORDER_RADIUS, VIEW_STYLE_BORDER_RADIUS); + uiViewGroupFrame_->SetStyle(STYLE_BACKGROUND_OPA, 0); + uiViewGroupFrame_->Add(testTarget_); + testTarget_->SetPosition(0, 0, 96, 96); // 96: width 96:height + testTargetSmall1_->Resize(40, 40); // 40:width 40:height + testTargetSmall2_->Resize(20, 20); // 20:width 20:height + testTargetSmall1_->LayoutCenterOfParent(); + testTargetSmall2_->LayoutCenterOfParent(); + testTargetSmall1_->SetStyle(STYLE_BACKGROUND_COLOR, Color::Purple().full); + testTargetSmall2_->SetStyle(STYLE_BACKGROUND_COLOR, Color::Cyan().full); + testTarget_->SetStyle(STYLE_BACKGROUND_COLOR, Color::GetColorFromRGB(0, 182, 191).full); // 182, 191: color + uiViewGroupFrame_->Add(refTarget_); + refTarget_->Resize(VIEW_DISTANCE_TO_LEFT_SIDE, VIEW_DISTANCE_TO_TOP_SIDE); + refTarget_->SetViewId("refView"); + refTarget_->LayoutCenterOfParent(); + refTarget_->SetStyle(STYLE_BACKGROUND_COLOR, Color::GetColorFromRGB(255, 152, 0).full); // 255, 152: color + + UIViewGroup* uiViewGroup2 = new UIViewGroup(); + uiViewGroup2->SetPosition(336, 0, 600, 384); // 336: x-coordinate; 600: width; 384: height + container_->Add(uiViewGroup2); + UILabel* label2 = new UILabel(); + uiViewGroup2->Add(label2); + // 288: width; 48: height + label2->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE2, TEXT_DISTANCE_TO_TOP_SIDE, 288, 48); + label2->SetText("布局效果"); + label2->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + uiViewGroup2->Add(layout_); + layout_->SetPosition(0, VIEW_DISTANCE_TO_TOP_SIDE, 516, 296); // 516: width; 296: height + layout_->SetLayoutDirection(LAYOUT_HOR); + layout_->SetRows(5); // 5: row + layout_->SetCols(4); // 4: col + InitSetUpButton(); + layout_->LayoutChildren(); + positionY_ += ITEM_H; + } +} + +void UITestBasicLayout::InitSetUpButton() +{ + SetUpButton(resetBtn_, "重置"); + SetUpButton(marginBtn_, "margin"); + SetUpButton(padBtn_, "padding"); + SetUpButton(lCenterOfParentBtn_, "居中"); + SetUpButton(lLeftOfParentBtn_, "居左"); + SetUpButton(lRightOfParentBtn_, "居右"); + SetUpButton(lTopOfParentBtn_, "居上"); + SetUpButton(lBottomOfParentBtn_, "居下"); + SetUpButton(aLeftToSiblingBtn_, "左侧对齐兄弟"); + SetUpButton(aRightToSiblingBtn_, "右侧对齐兄弟"); + SetUpButton(aTopToSiblingBtn_, "上侧对齐兄弟"); + SetUpButton(aBottomToSiblingBtn_, "下侧对齐兄弟"); + SetUpButton(aHorCenterToSiblingBtn_, "水平居中对齐"); + SetUpButton(aVerCenterToSiblingBtn_, "竖直居中对齐"); + SetUpButton(lLeftToSiblingBtn_, "放至兄弟左侧"); + SetUpButton(lRightToSiblingBtn_, "放至兄弟右侧"); + SetUpButton(lTopToSiblingBtn_, "放至兄弟上侧"); + SetUpButton(lBottomToSiblingBtn_, "放至兄弟下侧"); + SetUpButton(lInvalidLayoutBtn_, "非法节点布局"); +} + +void UITestBasicLayout::OnClickButton(UIView& view) +{ + if (&view == aLeftToSiblingBtn_) { + testTarget_->AlignLeftToSibling("refView"); + } else if (&view == aRightToSiblingBtn_) { + testTarget_->AlignRightToSibling("refView"); + } else if (&view == aTopToSiblingBtn_) { + testTarget_->AlignTopToSibling("refView"); + } else if (&view == aBottomToSiblingBtn_) { + testTarget_->AlignBottomToSibling("refView"); + } else if (&view == aHorCenterToSiblingBtn_) { + testTarget_->AlignHorCenterToSibling("refView"); + } else if (&view == aVerCenterToSiblingBtn_) { + testTarget_->AlignVerCenterToSibling("refView"); + } else if (&view == lLeftToSiblingBtn_) { + testTarget_->LayoutLeftToSibling("refView"); + } else if (&view == lRightToSiblingBtn_) { + testTarget_->LayoutRightToSibling("refView"); + } else if (&view == lTopToSiblingBtn_) { + testTarget_->LayoutTopToSibling("refView"); + } else if (&view == lBottomToSiblingBtn_) { + testTarget_->LayoutBottomToSibling("refView"); + } else if (&view == lInvalidLayoutBtn_) { + testTarget_->AlignLeftToSibling("invalid"); + } else { + } +} + +bool UITestBasicLayout::OnClick(UIView& view, const ClickEvent& event) +{ + uiViewGroupFrame_->Invalidate(); + if (&view == resetBtn_) { + testTarget_->SetPosition(0, 0); + testTarget_->SetStyle(STYLE_MARGIN_LEFT, 0); + testTarget_->SetStyle(STYLE_MARGIN_RIGHT, 0); + testTarget_->SetStyle(STYLE_MARGIN_TOP, 0); + testTarget_->SetStyle(STYLE_MARGIN_BOTTOM, 0); + uiViewGroupFrame_->SetStyle(STYLE_PADDING_LEFT, 0); + uiViewGroupFrame_->SetStyle(STYLE_PADDING_RIGHT, 0); + uiViewGroupFrame_->SetStyle(STYLE_PADDING_TOP, 0); + uiViewGroupFrame_->SetStyle(STYLE_PADDING_BOTTOM, 0); + } else if (&view == marginBtn_) { + testTarget_->SetStyle(STYLE_MARGIN_LEFT, 10); // 10: margin left + testTarget_->SetStyle(STYLE_MARGIN_RIGHT, 10); // 10: margin right + testTarget_->SetStyle(STYLE_MARGIN_TOP, 10); // 10: margin top + testTarget_->SetStyle(STYLE_MARGIN_BOTTOM, 10); // 10: margin bottom + } else if (&view == padBtn_) { + uiViewGroupFrame_->SetStyle(STYLE_PADDING_LEFT, 15); // 15: padding left + uiViewGroupFrame_->SetStyle(STYLE_PADDING_RIGHT, 15); // 15: padding right + uiViewGroupFrame_->SetStyle(STYLE_PADDING_TOP, 15); // 15: padding top + uiViewGroupFrame_->SetStyle(STYLE_PADDING_BOTTOM, 15); // 15: padding bottom + refTarget_->LayoutCenterOfParent(); + } else if (&view == lCenterOfParentBtn_) { + testTarget_->LayoutCenterOfParent(); + } else if (&view == lLeftOfParentBtn_) { + testTarget_->LayoutLeftOfParent(); + } else if (&view == lRightOfParentBtn_) { + testTarget_->LayoutRightOfParent(); + } else if (&view == lTopOfParentBtn_) { + testTarget_->LayoutTopOfParent(); + } else if (&view == lBottomOfParentBtn_) { + testTarget_->LayoutBottomOfParent(); + } else { + OnClickButton(view); + } + uiViewGroupFrame_->Invalidate(); + return true; +} +} // namespace OHOS \ No newline at end of file diff --git a/test/uitest/test_layout/ui_test_basic_layout.h b/test/uitest/test_layout/ui_test_basic_layout.h new file mode 100755 index 0000000..d22e1d9 --- /dev/null +++ b/test/uitest/test_layout/ui_test_basic_layout.h @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef UI_TEST_BASIC_LAYOUT_H +#define UI_TEST_BASIC_LAYOUT_H + +#include "components/ui_label.h" +#include "components/ui_label_button.h" +#include "components/ui_scroll_view.h" +#include "layout/flex_layout.h" +#include "layout/grid_layout.h" +#include "ui_test.h" + +namespace OHOS { +class UITestBasicLayout : public UITest, public UIView::OnClickListener { +public: + UITestBasicLayout() {} + ~UITestBasicLayout() {} + void SetUp() override; + void TearDown() override; + UIView* GetTestView() override; + + /** + * @brief Test basic layout function. + */ + void UIKit_Layout_Test_Basic_Layout_001(); + + bool OnClick(UIView& view, const ClickEvent& event) override; + void OnClickButton(UIView& view); + void InitSetUpButton(); +private: + UIScrollView* container_ = nullptr; + GridLayout* layout_ = nullptr; + UIViewGroup* uiViewGroupFrame_ = nullptr; + UIViewGroup* testTarget_ = nullptr; + UIView* testTargetSmall1_ = nullptr; + UIView* testTargetSmall2_ = nullptr; + UIView* refTarget_ = nullptr; + UILabelButton* resetBtn_ = nullptr; + + UILabelButton* lCenterOfParentBtn_ = nullptr; + UILabelButton* lLeftOfParentBtn_ = nullptr; + UILabelButton* lRightOfParentBtn_ = nullptr; + UILabelButton* lTopOfParentBtn_ = nullptr; + UILabelButton* lBottomOfParentBtn_ = nullptr; + + UILabelButton* aLeftToSiblingBtn_ = nullptr; + UILabelButton* aRightToSiblingBtn_ = nullptr; + UILabelButton* aTopToSiblingBtn_ = nullptr; + UILabelButton* aBottomToSiblingBtn_ = nullptr; + UILabelButton* aHorCenterToSiblingBtn_ = nullptr; + UILabelButton* aVerCenterToSiblingBtn_ = nullptr; + + UILabelButton* lLeftToSiblingBtn_ = nullptr; + UILabelButton* lRightToSiblingBtn_ = nullptr; + UILabelButton* lTopToSiblingBtn_ = nullptr; + UILabelButton* lBottomToSiblingBtn_ = nullptr; + UILabelButton* lInvalidLayoutBtn_ = nullptr; + UILabelButton* marginBtn_ = nullptr; + UILabelButton* padBtn_ = nullptr; + UILabelButton* setStyle_ = nullptr; + + void SetUpButton(UILabelButton* btn, const char* title) + { + if (btn == nullptr) { + return; + } + layout_->Add(btn); + btn->Resize(BUTTON_WIDHT2, BUTTON_HEIGHT2); + btn->SetText(title); + btn->SetFont(DEFAULT_VECTOR_FONT_FILENAME, BUTTON_LABEL_SIZE); + btn->SetOnClickListener(this); + btn->SetStyleForState(STYLE_BORDER_RADIUS, BUTTON_STYLE_BORDER_RADIUS_VALUE, UIButton::RELEASED); + btn->SetStyleForState(STYLE_BORDER_RADIUS, BUTTON_STYLE_BORDER_RADIUS_VALUE, UIButton::PRESSED); + btn->SetStyleForState(STYLE_BORDER_RADIUS, BUTTON_STYLE_BORDER_RADIUS_VALUE, UIButton::INACTIVE); + btn->SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::RELEASED); + btn->SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::PRESSED); + btn->SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::INACTIVE); + } +}; +} // namespace OHOS +#endif // UI_TEST_INPUT_EVENT_H diff --git a/test/uitest/test_opacity/ui_test_opacity.cpp b/test/uitest/test_opacity/ui_test_opacity.cpp new file mode 100755 index 0000000..650a0e0 --- /dev/null +++ b/test/uitest/test_opacity/ui_test_opacity.cpp @@ -0,0 +1,492 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ui_test_opacity.h" +#include "common/screen.h" +#include "components/ui_swipe_view.h" +#include "font/ui_font.h" +#include "test_resource_config.h" + +namespace OHOS { +namespace { +const int16_t TITLE_HEIGHT = 20; +const uint16_t LABEL_WIDTH = 200; +const uint16_t LABEL_HEIGHT = 50; +const uint16_t BUTTON_WIDTH = 150; +const uint16_t BUTTON_HEIGHT = 160; +const uint16_t CENTER_X = 100; +const uint16_t CENTER_Y = 100; +const uint16_t RADIUS = 80; +static ImageAnimatorInfo g_imageAnimatorInfo[4] = { + {GREEN_IMAGE_PATH, {50, 50}, 94, 94, IMG_SRC_FILE_PATH}, + {YELLOW_IMAGE_PATH, {50, 50}, 94, 94, IMG_SRC_FILE_PATH}, + {BLUE_IMAGE_PATH, {50, 50}, 94, 94, IMG_SRC_FILE_PATH}, + {RED_IMAGE_PATH, {50, 50}, 94, 94, IMG_SRC_FILE_PATH}, +}; +static ImageAnimatorInfo g_imageAnimatorInfo2[4] = { + {GREEN_IMAGE_PATH, {200, 50}, 94, 94, IMG_SRC_FILE_PATH}, + {YELLOW_IMAGE_PATH, {200, 50}, 94, 94, IMG_SRC_FILE_PATH}, + {BLUE_IMAGE_PATH, {200, 50}, 94, 94, IMG_SRC_FILE_PATH}, + {RED_IMAGE_PATH, {200, 50}, 94, 94, IMG_SRC_FILE_PATH}, +}; +} // namespace + +void UITestOpacity::SetUp() +{ + if (container_ == nullptr) { + container_ = new UIScrollView(); + container_->SetThrowDrag(true); + container_->Resize(Screen::GetInstance().GetWidth(), Screen::GetInstance().GetHeight() - BACK_BUTTON_HEIGHT); + } +} + +void UITestOpacity::TearDown() +{ + DeleteChildren(container_); + container_ = nullptr; +} + +UIViewGroup* UITestOpacity::CreateTestCaseGroup(const char* title) const +{ + UIViewGroup* group = new UIViewGroup(); + group->SetStyle(STYLE_BACKGROUND_COLOR, Color::Black().full); + group->SetStyle(STYLE_BACKGROUND_OPA, OPA_OPAQUE); + group->SetPosition(0, 0); + UILabel* titleLabel = new UILabel(); + titleLabel->SetPosition(0, 0, Screen::GetInstance().GetWidth(), TITLE_LABEL_DEFAULT_HEIGHT); + titleLabel->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + titleLabel->SetText(title); + group->Add(titleLabel); + return group; +} + +const UIView* UITestOpacity::GetTestView() +{ + UIKitOpacityTestUILabel001(); + UIKitOpacityTestUILabel002(); + UIKitOpacityTestUIButton001(); + UIKitOpacityTestUIButton002(); + UIKitOpacityTestUILabelButton001(); + UIKitOpacityTestUIArcLabel001(); + UIKitOpacityTestUIImageView001(); + UIKitOpacityTestUIImageAnimator001(); + UIKitOpacityTestUIView001(); + UIKitOpacityTestUIViewGroup001(); + UIKitOpacityTestUIList001(); + UIKitOpacityTestUIScrollView001(); + UIKitOpacityTestUISwipeView001(); + return container_; +} + +UILabel* UITestOpacity::CreateTestCaseUILabel(const char* title, uint8_t opaScale) const +{ + UILabel* label = new UILabel(); + label->SetStyle(STYLE_BACKGROUND_COLOR, Color::Red().full); + label->SetStyle(STYLE_BACKGROUND_OPA, OPA_OPAQUE); + label->SetPosition(10, 30); // 10 width 30: height + label->Resize(LABEL_WIDTH, LABEL_HEIGHT); + label->SetFont(DEFAULT_VECTOR_FONT_FILENAME, 20); // 20:font size + label->SetText(title); + label->SetOpaScale(opaScale); // 200: opacity + return label; +} + +void UITestOpacity::UIKitOpacityTestUILabel001() +{ + if (container_ != nullptr) { + UIViewGroup* group = CreateTestCaseGroup(" display dynamic text with opacity 200 and 100"); + group->Resize(Screen::GetInstance().GetWidth(), LABEL_HEIGHT + 50); // 50 : height + group->SetViewId("UIKitOpacityTestUILabel001"); + UILabel* label = CreateTestCaseUILabel("轻量级GUI-200", 200); // 200: opacity + UILabel* label2 = CreateTestCaseUILabel("轻量级GUI-100", 100); // 100: opacity + label2->SetX(220); // 220: width + group->Add(label); + group->Add(label2); + container_->Add(group); + } +} + +void UITestOpacity::UIKitOpacityTestUILabel002() +{ + if (container_ != nullptr) { + UIViewGroup* group = CreateTestCaseGroup(" display dynamic text with opacity 256 and -1, uint8_t"); + group->Resize(Screen::GetInstance().GetWidth(), LABEL_HEIGHT + 50); // 50 : height + group->SetViewId("UIKitOpacityTestUILabel002"); + UILabel* label = CreateTestCaseUILabel("轻量级GUI-200", 256); // 256: opacity + UILabel* label2 = CreateTestCaseUILabel("轻量级GUI-100", -1); // -1: opacity + label2->SetX(220); // 220: width + group->Add(label); + group->Add(label2); + container_->Add(group); + group->LayoutBottomToSibling("UIKitOpacityTestUILabel001", 10); // 10: height + } +} + +void UITestOpacity::UIKitOpacityTestUIButton001() +{ + if (container_ != nullptr) { + UIViewGroup* group = CreateTestCaseGroup(" display UIButton with opacity 200 and 100"); + group->Resize(Screen::GetInstance().GetWidth(), BUTTON_HEIGHT); + group->SetViewId("UIKitOpacityTestUIButton001"); + + UIButton* button = new UIButton(); + button->SetPosition(10, 30, BUTTON_WIDTH, BUTTON_HEIGHT >> 1); // 10: width 30 :height + button->SetOpaScale(200); // 200: opacity + UIButton* button2 = new UIButton(); + button2->SetPosition(200, 30, BUTTON_WIDTH, BUTTON_HEIGHT >> 1); // 200: width 30 : height + button2->SetOpaScale(100); // 100: opacity + + group->Add(button); + group->Add(button2); + container_->Add(group); + group->LayoutBottomToSibling("UIKitOpacityTestUILabel002", 10); // 10: height + } +} + +UIButton* UITestOpacity::CreateTestCaseUIButton(uint8_t opaScale) const +{ + UIButton* button = new UIButton(); + button->SetStyle(STYLE_BACKGROUND_COLOR, Color::Red().full); + button->SetStyle(STYLE_BACKGROUND_OPA, OPA_OPAQUE); + button->SetPosition(10, 30, BUTTON_WIDTH, BUTTON_HEIGHT); // 10 30 x y + button->SetImageSrc(BLUE_IMAGE_PATH, YELLOW_IMAGE_PATH); + button->SetOpaScale(opaScale); // 200: opacity + return button; +} + +void UITestOpacity::UIKitOpacityTestUIButton002() +{ + if (container_ != nullptr) { + UIViewGroup* group = CreateTestCaseGroup(" display UIButton Image with opacity 200 and 100"); + group->Resize(Screen::GetInstance().GetWidth(), BUTTON_HEIGHT + 50); // 50 : height + group->SetViewId("UIKitOpacityTestUIButton002"); + + UIButton* button = CreateTestCaseUIButton(200); // 200: opacity + group->Add(button); + UIButton* button2 = CreateTestCaseUIButton(100); // 100: opacity + button2->SetPosition(200, 30); // 200: width 30 y + group->Add(button2); + container_->Add(group); + group->LayoutBottomToSibling("UIKitOpacityTestUIButton001", 10); // 10: height + } +} + +UILabelButton* UITestOpacity::CreateTestCaseUILabelButton(const char* title, uint8_t opaScale) const +{ + UILabelButton* labelButton = new UILabelButton(); + labelButton->SetPosition(10, 30, BUTTON_WIDTH, BUTTON_HEIGHT >> 1); // 10 x 30 : y + labelButton->SetText(title); + labelButton->SetOpaScale(opaScale); + return labelButton; +} + +void UITestOpacity::UIKitOpacityTestUILabelButton001() +{ + if (container_ != nullptr) { + UIViewGroup* group = CreateTestCaseGroup(" display UILabelButton with opacity 200 and 100"); + group->Resize(Screen::GetInstance().GetWidth(), BUTTON_HEIGHT); + group->SetViewId("UIKitOpacityTestUILabelButton001"); + UILabelButton* labelButton = CreateTestCaseUILabelButton("200", 200); // 200 : opacity + group->Add(labelButton); + UILabelButton* labelButton2 = CreateTestCaseUILabelButton("100", 100); // 100: opacity + labelButton2->SetX(200); // 200: width + group->Add(labelButton2); + container_->Add(group); + group->LayoutBottomToSibling("UIKitOpacityTestUIButton002", 10); // 10: height + } +} + +UIArcLabel* UITestOpacity::CreateTestCaseUIArcLabel(const char* title, uint8_t opaScale) const +{ + UIArcLabel* label = new UIArcLabel(); + label->SetArcTextCenter(CENTER_X, CENTER_Y + 20); // 20 : height + label->SetArcTextRadius(RADIUS); + label->SetArcTextAngle(270, 0); // 270: start angle, 0: end angle + label->SetFont(DEFAULT_VECTOR_FONT_FILENAME, 30); // 30: font size + label->SetStyle(STYLE_BACKGROUND_COLOR, Color::Red().full); + label->SetStyle(STYLE_BACKGROUND_OPA, OPA_OPAQUE); + label->SetText(title); + label->SetStyle(STYLE_LETTER_SPACE, 30); // 30: space + label->SetOpaScale(opaScale); // 200: opacity + return label; +} +void UITestOpacity::UIKitOpacityTestUIArcLabel001() +{ + if (container_ != nullptr) { + UIViewGroup* group = CreateTestCaseGroup(" display UIArcLabel with opacity 200 and 100"); + group->Resize(Screen::GetInstance().GetWidth(), BUTTON_HEIGHT + 50); // 50 : height + group->SetViewId("UIKitOpacityTestUIArcLabel001"); + UIArcLabel* label = CreateTestCaseUIArcLabel("01234567", 200); // 200: opacity + group->Add(label); + UIArcLabel* label2 = CreateTestCaseUIArcLabel("01234567", 100); // 100: opacity + if (label2 == nullptr) { + return; + } + label2->SetArcTextCenter(CENTER_X + 200, CENTER_Y + 20); // 200: width 20 : height + group->Add(label2); + container_->Add(group); + group->LayoutBottomToSibling("UIKitOpacityTestUILabelButton001", 10); // 10 : height + } +} + +UIImageView* UITestOpacity::CreateTestCaseUIImageView(const char* path, uint8_t opaScale) const +{ + UIImageView* imageView = new UIImageView(); + imageView->SetPosition(10, 30, 130, 130); // 10 x 30: y 130 : size + imageView->SetStyle(STYLE_BACKGROUND_COLOR, Color::Red().full); + imageView->SetStyle(STYLE_BACKGROUND_OPA, OPA_OPAQUE); + imageView->SetSrc(path); + imageView->SetOpaScale(opaScale); // 200: opacity + return imageView; +} + +void UITestOpacity::UIKitOpacityTestUIImageView001() +{ + if (container_ != nullptr) { + UIViewGroup* group = CreateTestCaseGroup(" display UIImageView with opacity 200 and 100"); + group->Resize(Screen::GetInstance().GetWidth(), BUTTON_HEIGHT + 50); // 50 : height + group->SetViewId("UIKitOpacityTestUIImageView001"); + UIImageView* imageView = CreateTestCaseUIImageView(BLUE_IMAGE_PATH, 200); // 200: opacity + group->Add(imageView); + UIImageView* imageView2 = CreateTestCaseUIImageView(BLUE_ARGB8888_IMAGE_PATH, 100); // 100: opacity + imageView2->SetX(200); // 200: width + group->Add(imageView2); + container_->Add(group); + group->LayoutBottomToSibling("UIKitOpacityTestUIArcLabel001", 10); // 10 : height + } +} + +UIImageAnimatorView* UITestOpacity::CreateTestCaseUIImageAnimator(const ImageAnimatorInfo imageAnimatorInfo[], + uint8_t opaScale) const +{ + UIImageAnimatorView* imageAnimator = new UIImageAnimatorView(); + imageAnimator->SetPosition(10, 30, 200, 200); // 10 : offset 30 : offset 200 : offset 200: offset + imageAnimator->SetOpaScale(opaScale); + imageAnimator->SetImageAnimatorSrc(imageAnimatorInfo, 4, 10); // 4: image number, 10: updating time + imageAnimator->Start(); + return imageAnimator; +} + +void UITestOpacity::UIKitOpacityTestUIImageAnimator001() +{ + if (container_ != nullptr) { + UIViewGroup* group = CreateTestCaseGroup(" display UIImageAnimator with opacity 200 and 100"); + group->Resize(Screen::GetInstance().GetWidth(), BUTTON_HEIGHT + 50); // 50: height + group->SetViewId("UIKitOpacityTestUIImageAnimator001"); + UIImageAnimatorView* imageAnimator = CreateTestCaseUIImageAnimator(g_imageAnimatorInfo, 200); // 200: opacity + group->Add(imageAnimator); + UIImageAnimatorView* imageAnimator2 = CreateTestCaseUIImageAnimator(g_imageAnimatorInfo2, 100); // 100: opacity + imageAnimator2->SetPosition(230, 30); // 230: width 30: height + group->Add(imageAnimator2); + container_->Add(group); + group->LayoutBottomToSibling("UIKitOpacityTestUIImageView001", 10); // 10: height + } +} +UIView* UITestOpacity::CreateTestCaseUIView(uint8_t opaScale) const +{ + UIView* view = new UIView(); + view->SetPosition(10, 30, BUTTON_WIDTH, BUTTON_HEIGHT); // 10: x 30: y + view->SetStyle(STYLE_BACKGROUND_COLOR, (Color::White()).full); + view->SetStyle(STYLE_BACKGROUND_OPA, OPA_OPAQUE); + view->SetOpaScale(opaScale); + return view; +} + +void UITestOpacity::UIKitOpacityTestUIView001() +{ + if (container_ != nullptr) { + UIViewGroup* group = CreateTestCaseGroup(" display UIView with opacity 200 and 100"); + group->Resize(Screen::GetInstance().GetWidth(), BUTTON_HEIGHT + 50); // 50 : height + group->SetViewId("UIKitOpacityTestUIView001"); + + UIView* view = CreateTestCaseUIView(200); // 200 : opacity + group->Add(view); + UIView* view2 = CreateTestCaseUIView(100); // 100 : opacity + view2->SetX(200); // 200: wdith + group->Add(view2); + + container_->Add(group); + group->LayoutBottomToSibling("UIKitOpacityTestUIImageAnimator001", 10); // 10 : height + } +} + +void UITestOpacity::UIKitOpacityTestUIViewGroup001() +{ + if (container_ != nullptr) { + UIViewGroup* group = CreateTestCaseGroup(" display UIViewGroup with opacity 200 and 100"); + group->Resize(Screen::GetInstance().GetWidth(), (BUTTON_HEIGHT << 1) + 100); // 100: height + group->SetViewId("UIKitOpacityTestUIViewGroup001"); + + UIViewGroup* group1 = new UIViewGroup(); + group1->SetPosition(10, 30, (BUTTON_WIDTH << 1) + 50, (BUTTON_HEIGHT << 1) + 100); // 30 y 50: width 100: height + group1->SetStyle(STYLE_BACKGROUND_COLOR, (Color::Red()).full); + UIViewGroup* group2 = new UIViewGroup(); + group2->SetPosition(170, 250, 150, 150); // 170: x 250: y 150: width 150: height + group2->SetStyle(STYLE_BACKGROUND_COLOR, (Color::White()).full); + + UILabelButton* labelButton = new UILabelButton(); + labelButton->SetPosition(10, 10, 60, 30); // 10: x y 60 wdith 30 height + labelButton->SetText("Test1"); + UILabelButton* labelButton2 = new UILabelButton(); + labelButton2->SetPosition(170, 10, 60, 30); // 170: x 10: y 60 wdith 30 height + labelButton2->SetText("Test2"); + labelButton2->SetOpaScale(100); // 100: opacity + UIButton* button = new UIButton(); + button->SetPosition(10, 60, BUTTON_WIDTH, BUTTON_HEIGHT); // 10: x 60: y + UIButton* button2 = new UIButton(); + button2->SetPosition(170, 60, BUTTON_WIDTH, BUTTON_HEIGHT); // 170: x 60: y + button2->SetImageSrc(BLUE_IMAGE_PATH, YELLOW_IMAGE_PATH); + button2->SetOpaScale(100); // 100: opacity + UIImageView* imageView = new UIImageView(); + imageView->SetPosition(10, 250, 50, 50); // 10: x 250: y 50 : size + imageView->SetSrc(BLUE_IMAGE_PATH); + imageView->SetOpaScale(100); // 100: opacity + UIImageView* imageView2 = new UIImageView(); + imageView2->SetPosition(10, 10, 50, 50); // 10: x y 50 : size + imageView2->SetSrc(YELLOW_IMAGE_PATH); + + group2->Add(imageView2); + group1->Add(labelButton); + group1->Add(labelButton2); + group1->Add(button); + group1->Add(button2); + group1->Add(imageView); + group1->Add(group2); + group2->SetOpaScale(100); // 100: opacity + group1->SetOpaScale(200); // 200: opacity + + group->Add(group1); + container_->Add(group); + group->LayoutBottomToSibling("UIKitOpacityTestUIView001", 10); // 10 : height + } +} + +UIList* UITestOpacity::CreateTestCaseUIList(uint8_t opaScale) const +{ + List* adapterData = new List(); + adapterData->PushBack("000"); + adapterData->PushBack("111"); + adapterData->PushBack("222"); + adapterData->PushBack("333"); + adapterData->PushBack("444"); + adapterData->PushBack("555"); + adapterData->PushBack("666"); + adapterData->PushBack("777"); + adapterData->PushBack("888"); + adapterData->PushBack("999"); + + TextAdapter* adapter = new TextAdapter(); + adapter->SetData(adapterData); + UIList* list = new UIList(UIList::VERTICAL); + list->SetStyle(STYLE_BACKGROUND_COLOR, Color::Red().full); + list->SetPosition(10, 30, BUTTON_WIDTH, BUTTON_HEIGHT); // 10 x 30 y + list->SetAdapter(adapter); + list->SetOpaScale(opaScale); // 200: opacity + return list; +} + +void UITestOpacity::UIKitOpacityTestUIList001() +{ + if (container_ != nullptr) { + UIViewGroup* group = CreateTestCaseGroup(" display UIList with opacity 200 and 100"); + group->Resize(Screen::GetInstance().GetWidth(), BUTTON_HEIGHT + 100); // 100: height + group->SetViewId("UIKitOpacityTestUIList001"); + UIList* list = CreateTestCaseUIList(200); // 200 : opacity + group->Add(list); + UIList* list2 = CreateTestCaseUIList(100); // 100 : opacity + list2->SetX(200); // 200: width + group->Add(list2); + container_->Add(group); + group->LayoutBottomToSibling("UIKitOpacityTestUIViewGroup001", 10); // 10 : height + } +} + +void UITestOpacity::UIKitOpacityTestUIScrollView001() +{ + if (container_ != nullptr) { + UIViewGroup* group = CreateTestCaseGroup(" display UIScrollView with opacity 200 and 100"); + group->Resize(Screen::GetInstance().GetWidth(), BUTTON_HEIGHT << 1); + group->SetViewId("UIKitOpacityTestUIScrollView001"); + + UIScrollView* group1 = new UIScrollView(); + group1->SetPosition(10, 30, (BUTTON_WIDTH << 1) + 50, (BUTTON_HEIGHT) + 100); // 10 x 30 y 50: width 100: height + group1->SetStyle(STYLE_BACKGROUND_COLOR, (Color::Red()).full); + UIScrollView* group2 = new UIScrollView(); + group2->SetPosition(170, 80, 150, 150); // 170: x 80: y 150: width 150: height + group2->SetStyle(STYLE_BACKGROUND_COLOR, (Color::White()).full); + UILabelButton* labelButton = new UILabelButton(); + labelButton->SetPosition(10, 10, 80, 40); // 10 x y 80: width 40: height + labelButton->SetText("test1"); + UILabelButton* labelButton2 = new UILabelButton(); + labelButton2->SetPosition(170, 10, 80, 40); // 170: x 10: y 80: width 40: height + labelButton2->SetText("test2"); + labelButton2->SetOpaScale(100); // 100: opacity + UIImageView* imageView = new UIImageView(); + imageView->SetPosition(10, 80, 50, 50); // 10: x 80: y 50 : size + imageView->SetSrc(BLUE_IMAGE_PATH); + imageView->SetOpaScale(100); // 100: opacity + UIImageView* imageView2 = new UIImageView(); + imageView2->SetPosition(10, 10, 50, 50); // 10: x y 50 : size + imageView2->SetSrc(YELLOW_IMAGE_PATH); + + group2->Add(imageView2); + group1->Add(labelButton); + group1->Add(labelButton2); + group1->Add(imageView); + group1->Add(group2); + group2->SetOpaScale(100); // 100: opacity + group1->SetOpaScale(200); // 200: opacity + group->Add(group1); + container_->Add(group); + group->LayoutBottomToSibling("UIKitOpacityTestUIList001", 10); // 10 : height + } +} + +void UITestOpacity::UIKitOpacityTestUISwipeView001() +{ + if (container_ != nullptr) { + UIViewGroup* group = CreateTestCaseGroup(" display UISwipeView with opacity 200 and 100"); + group->Resize(Screen::GetInstance().GetWidth(), BUTTON_HEIGHT); + group->SetViewId("UIKitOpacityTestUISwipeView001"); + + UISwipeView* swipe = new UISwipeView(UISwipeView::HORIZONTAL); + swipe->SetStyle(STYLE_BACKGROUND_COLOR, Color::Red().full); + swipe->SetPosition(10, 30, BUTTON_WIDTH << 1, BUTTON_HEIGHT); // 10 30 size + swipe->SetBlankSize(50); // 50: is blank size + group->Add(swipe); + + UILabelButton* button1 = new UILabelButton(); + button1->SetPosition(0, 0, BUTTON_WIDTH, BUTTON_HEIGHT >> 1); + button1->SetText("button1"); + swipe->Add(button1); + + UILabelButton* button2 = new UILabelButton(); + button2->SetPosition(0, 0, BUTTON_WIDTH, BUTTON_HEIGHT >> 1); + button2->SetText("button2"); + button2->SetOpaScale(200); // 200: opacity + swipe->Add(button2); + + UILabelButton* button3 = new UILabelButton(); + button3->SetPosition(0, 0, BUTTON_WIDTH, BUTTON_HEIGHT >> 1); + button3->SetText("button3"); + button3->SetOpaScale(100); // 100: opacity + swipe->Add(button3); + + swipe->SetOpaScale(200); // 200: opacity + container_->Add(group); + group->LayoutBottomToSibling("UIKitOpacityTestUIScrollView001", 10); // 10 : height + } +} +} // namespace OHOS diff --git a/test/uitest/test_opacity/ui_test_opacity.h b/test/uitest/test_opacity/ui_test_opacity.h new file mode 100755 index 0000000..4ba66a8 --- /dev/null +++ b/test/uitest/test_opacity/ui_test_opacity.h @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef UI_TEST_OPACITY_H +#define UI_TEST_OPACITY_H + +#include "components/root_view.h" +#include "components/text_adapter.h" +#include "components/ui_arc_label.h" +#include "components/ui_button.h" +#include "components/ui_image_animator.h" +#include "components/ui_image_view.h" +#include "components/ui_label.h" +#include "components/ui_label_button.h" +#include "components/ui_list.h" +#include "components/ui_scroll_view.h" +#include "ui_test.h" +namespace OHOS { +class UITestOpacity : public UITest { +public: + UITestOpacity() {} + ~UITestOpacity() {} + void SetUp() override; + void TearDown() override; + const UIView* GetTestView() override; + + /** + * @brief Test UILabel + */ + void UIKitOpacityTestUILabel001(); + + /** + * @brief Test UILabel exception value + */ + void UIKitOpacityTestUILabel002(); + + /** + * @brief Test UIButton + */ + void UIKitOpacityTestUIButton001(); + + /** + * @brief Test UIButton image + */ + void UIKitOpacityTestUIButton002(); + + /** + * @brief Test UILabelButton + */ + void UIKitOpacityTestUILabelButton001(); + + /** + * @brief Test UIArcLabel + */ + void UIKitOpacityTestUIArcLabel001(); + + /** + * @brief Test UIImageView + */ + void UIKitOpacityTestUIImageView001(); + + /** + * @brief Test UIImageAnimator + */ + void UIKitOpacityTestUIImageAnimator001(); + + /** + * @brief Test UIView + */ + void UIKitOpacityTestUIView001(); + + /** + * @brief Test UIViewGroup + */ + void UIKitOpacityTestUIViewGroup001(); + + /** + * @brief Test UIList + */ + void UIKitOpacityTestUIList001(); + + /** + * @brief Test UIScrollView + */ + void UIKitOpacityTestUIScrollView001(); + + /** + * @brief Test UISwipeView + */ + void UIKitOpacityTestUISwipeView001(); + +private: + UIViewGroup* CreateTestCaseGroup(const char* title) const; + UILabel* CreateTestCaseUILabel(const char* title, uint8_t opaScale) const; + UIButton* CreateTestCaseUIButton(uint8_t opaScale) const; + UILabelButton* CreateTestCaseUILabelButton(const char* title, uint8_t opaScale) const; + UIArcLabel* CreateTestCaseUIArcLabel(const char* title, uint8_t opaScale) const; + UIImageView* CreateTestCaseUIImageView(const char* path, uint8_t opaScale) const; + UIImageAnimatorView* CreateTestCaseUIImageAnimator(const ImageAnimatorInfo imageAnimatorInfo[], + uint8_t opaScale) const; + UIView* CreateTestCaseUIView(uint8_t opaScale) const; + UIList* CreateTestCaseUIList(uint8_t opaScale) const; + + UIScrollView* container_ = nullptr; +}; +} +#endif // UI_TEST_OPACITY_H diff --git a/test/uitest/test_picker/ui_test_ui_picker.cpp b/test/uitest/test_picker/ui_test_ui_picker.cpp new file mode 100755 index 0000000..4c95151 --- /dev/null +++ b/test/uitest/test_picker/ui_test_ui_picker.cpp @@ -0,0 +1,249 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ui_test_ui_picker.h" +#include "common/screen.h" +#include "components/ui_label.h" +#include "components/ui_label_button.h" + +#include + +namespace OHOS { +namespace { +static int16_t g_ButtonH = 80; +static int16_t g_ButtonW = 200; +static int16_t g_blank = 38; +static int16_t g_ListW = 200; +static int16_t g_ListH = 300; +static const char* g_pickerRange[] = {"A0", "B1", "C2", "D3", "E4", "F5", "G6", "H7", "I8", "J9", "K10", "L11"}; +} // namespace + +void UITestUIPicker::SetUp() +{ + if (container_ == nullptr) { + container_ = new UIScrollView(); + container_->Resize(Screen::GetInstance().GetWidth(), Screen::GetInstance().GetHeight() - BACK_BUTTON_HEIGHT); + container_->SetThrowDrag(true); + container_->SetHorizontalScrollState(false); + } +} + +void UITestUIPicker::TearDown() +{ + container_->Remove(setLoopBtn_); + container_->Remove(setLoopOffBtn_); + container_->Remove(setSelectBtn_); + container_->Remove(setLeftToRightBtn_); + container_->Remove(setRightToLeftBtn_); + container_->Remove(selectIndex_); + container_->Remove(selectTime_); + container_->Remove(picker1_); + container_->Remove(picker2_); + container_->Remove(picker3_); + DeleteChildren(container_); + setLoopBtn_ = nullptr; + setLoopOffBtn_ = nullptr; + setSelectBtn_ = nullptr; + setLeftToRightBtn_ = nullptr; + setRightToLeftBtn_ = nullptr; + selectIndex_ = nullptr; + selectTime_ = nullptr; + container_ = nullptr; + picker1_ = nullptr; + picker2_ = nullptr; + picker3_ = nullptr; + + lastX_ = 0; + lastY_ = 0; +} + +UIView* UITestUIPicker::GetTestView() +{ + UIKit_Picker_Test_Base_001(); + UIKit_Picker_Test_Base_002(); + UIKit_Picker_Test_Time_Picker_001(); + return container_; +} + +void UITestUIPicker::OnPickerStoped(UIPicker& picker) +{ + uint16_t index = picker.GetSelected(); + selectIndex_->SetText(std::to_string(index).c_str()); + selectIndex_->Invalidate(); +} + +void UITestUIPicker::OnTimePickerStoped(UITimePicker& picker) +{ + selectTime_->SetText(picker.GetSelectValue()); + selectTime_->Invalidate(); +} + +void UITestUIPicker::UIKit_Picker_Test_Base_001() +{ + if (container_ == nullptr) { + return; + } + positionX_ = VIEW_DISTANCE_TO_LEFT_SIDE2; + UILabel* label = GetTitleLabel("UIPicker动态字体 "); + container_->Add(label); + label->SetPosition(positionX_, 0); + if (picker1_ == nullptr) { + picker1_ = new UIPicker(); + } + picker1_->SetPosition(positionX_, label->GetY() + g_blank, g_ListW, g_ListH); + picker1_->SetStyle(STYLE_BACKGROUND_OPA, OPA_OPAQUE); + picker1_->SetStyle(STYLE_BACKGROUND_COLOR, Color::Green().full); + picker1_->SetFontId(16, 18); // 16:back font id 18:high light font id + picker1_->SetItemHeight(50); // 50: height + picker1_->SetTextColor(Color::Gray(), Color::Red()); + picker1_->SetDirect(UITextLanguageDirect::TEXT_DIRECT_RTL); + picker1_->SetValues(g_pickerRange, sizeof(g_pickerRange) / sizeof(g_pickerRange[0])); + picker1_->RegisterSelectedListener(this); + + positionX_ = picker1_->GetWidth(); + positionY_ = picker1_->GetY(); + CreatButtons(); + if (selectIndex_ == nullptr) { + selectIndex_ = new UILabel(); + } + selectIndex_ = GetTitleLabel("NULL"); + selectIndex_->SetPosition(positionX_ + 40, label->GetY() + g_blank); // 40: increase x-coordinate + positionY_ += label->GetY() + g_blank; + container_->Add(selectIndex_); + SetUpButton(setLoopBtn_, "开启循环 "); + SetUpButton(setLoopOffBtn_, "关闭循环 "); + SetUpButton(setSelectBtn_, "定位到第5个 "); + SetUpButton(setLeftToRightBtn_, "从左往右 "); + SetUpButton(setRightToLeftBtn_, "从右往左 "); + container_->Add(picker1_); + SetLastPos(picker1_); +} + +void UITestUIPicker::CreatButtons() +{ + if (setLoopBtn_ == nullptr) { + setLoopBtn_ = new UILabelButton(); + } + if (setLoopOffBtn_ == nullptr) { + setLoopOffBtn_ = new UILabelButton(); + } + if (setSelectBtn_ == nullptr) { + setSelectBtn_ = new UILabelButton(); + } + if (setLeftToRightBtn_ == nullptr) { + setLeftToRightBtn_ = new UILabelButton(); + } + if (setRightToLeftBtn_ == nullptr) { + setRightToLeftBtn_ = new UILabelButton(); + } +} + +void UITestUIPicker::UIKit_Picker_Test_Base_002() +{ + if (container_ == nullptr) { + return; + } + UILabel* label = GetTitleLabel("UIPicker区间数字 "); + container_->Add(label); + label->SetPosition(390, 0); // 390 x-coordinate + if (picker3_ == nullptr) { + picker3_ = new UIPicker(); + } + picker3_->SetPosition(390, g_blank, g_ListW, g_ListH); // 390: x-coordinate + picker3_->SetFontId(16, 18); // 16:back font id 18:high light font id + picker3_->SetItemHeight(50); // 50: height + picker3_->SetTextColor(Color::Gray(), Color::Red()); + picker3_->SetValues(-5, 20); // -5: start 20:end + picker3_->SetTextFormatter(new TextFormatter()); + + positionX_ = picker3_->GetWidth(); + positionY_ = picker3_->GetY(); + + container_->Add(picker3_); + SetLastPos(picker3_); +} + +void UITestUIPicker::UIKit_Picker_Test_Time_Picker_001() +{ + if (container_ == nullptr) { + return; + } + UILabel* label = GetTitleLabel("Time Picker"); + container_->Add(label); + label->SetPosition(672, 0); // 672: x-coordinate + label->SetStyle(STYLE_BACKGROUND_OPA, OPA_OPAQUE); + if (picker2_ == nullptr) { + picker2_ = new UITimePicker(); + } + picker2_->EnableSecond(true); + picker2_->SetPosition(652, label->GetY() + g_blank, g_ListW, g_ListH); // 652: x-coordinate + picker2_->SetItemHeight(50); // 50 height + picker2_->RegisterSelectedListener(this); + positionX_ = picker2_->GetWidth(); + positionY_ = picker2_->GetY(); + + if (selectTime_ == nullptr) { + selectTime_ = new UILabel(); + } + selectTime_ = GetTitleLabel("NULL"); + picker2_->RegisterSelectedListener(this); + selectTime_->SetPosition(662 + picker2_->GetWidth(), label->GetY() + g_blank); // 662: increase x-coordinate + container_->Add(selectTime_); + container_->Add(picker2_); + SetLastPos(picker2_); +} + +bool UITestUIPicker::OnClick(UIView& view, const ClickEvent& event) +{ + if (&view == setLoopBtn_) { + picker1_->SetLoopState(true); + } else if (&view == setLoopOffBtn_) { + picker1_->SetLoopState(false); + } else if (&view == setSelectBtn_) { + picker1_->SetSelected(5); // 5: index + } else if (&view == setLeftToRightBtn_) { + picker1_->SetDirect(UITextLanguageDirect::TEXT_DIRECT_LTR); + } else if (&view == setRightToLeftBtn_) { + picker1_->SetDirect(UITextLanguageDirect::TEXT_DIRECT_RTL); + } + return true; +} + +void UITestUIPicker::SetUpButton(UILabelButton* btn, const char* title) +{ + if (btn == nullptr) { + return; + } + container_->Add(btn); + btn->SetPosition(positionX_ + 30, positionY_, BUTTON_WIDHT2, BUTTON_HEIGHT2); // 30: increase x-coordinate; + positionY_ += btn->GetHeight() + 12; // 12: increase y-coordinate + btn->SetText(title); + btn->SetFont(DEFAULT_VECTOR_FONT_FILENAME, BUTTON_LABEL_SIZE); + btn->SetOnClickListener(this); + btn->SetStyleForState(STYLE_BORDER_RADIUS, BUTTON_STYLE_BORDER_RADIUS_VALUE, UIButton::RELEASED); + btn->SetStyleForState(STYLE_BORDER_RADIUS, BUTTON_STYLE_BORDER_RADIUS_VALUE, UIButton::PRESSED); + btn->SetStyleForState(STYLE_BORDER_RADIUS, BUTTON_STYLE_BORDER_RADIUS_VALUE, UIButton::INACTIVE); + btn->SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::RELEASED); + btn->SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::PRESSED); + btn->SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::INACTIVE); + container_->Invalidate(); +} + +void UITestUIPicker::SetLastPos(UIView* view) +{ + lastX_ = view->GetX(); + lastY_ = view->GetY() + view->GetHeight(); +} +} // namespace OHOS diff --git a/test/uitest/test_picker/ui_test_ui_picker.h b/test/uitest/test_picker/ui_test_ui_picker.h new file mode 100755 index 0000000..0d3e9cc --- /dev/null +++ b/test/uitest/test_picker/ui_test_ui_picker.h @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef UI_TEST_UI_PICKER_H +#define UI_TEST_UI_PICKER_H + +#include "components/text_adapter.h" +#include "components/ui_label.h" +#include "components/ui_label_button.h" +#include "components/ui_picker.h" +#include "components/ui_scroll_view.h" +#include "components/ui_time_picker.h" +#include "ui_test.h" + +namespace OHOS { +class UITestUIPicker : public UITest, UIView::OnClickListener, UIPicker::SelectedListener, + UITimePicker::SelectedListener { +public: + UITestUIPicker() {} + ~UITestUIPicker() {} + void SetUp() override; + void TearDown() override; + bool OnClick(UIView& view, const ClickEvent& event) override; + UIView* GetTestView() override; + void OnPickerStoped(UIPicker& picker) override; + void OnTimePickerStoped(UITimePicker& picker) override; + void CreatButtons(); + void UIKit_Picker_Test_Base_001(); + void UIKit_Picker_Test_Base_002(); + void UIKit_Picker_Test_Time_Picker_001(); + +private: + void SetLastPos(UIView* view); + void SetUpButton(UILabelButton* btn, const char* title); + UILabelButton* setLoopBtn_ = nullptr; + UILabelButton* setLoopOffBtn_ = nullptr; + UILabelButton* setSelectBtn_ = nullptr; + UILabelButton* setLeftToRightBtn_ = nullptr; + UILabelButton* setRightToLeftBtn_ = nullptr; + UILabel* selectIndex_ = nullptr; + UILabel* selectTime_ = nullptr; + UIScrollView* container_ = nullptr; + UIPicker* picker1_ = nullptr; + UIPicker* picker3_ = nullptr; + UITimePicker* picker2_ = nullptr; + int16_t lastX_ = 0; + int16_t lastY_ = 0; +}; +} // namespace OHOS +#endif // UI_TEST_UI_PICKER_H diff --git a/test/uitest/test_progress_bar/ui_test_box_progress.cpp b/test/uitest/test_progress_bar/ui_test_box_progress.cpp new file mode 100755 index 0000000..c08b19d --- /dev/null +++ b/test/uitest/test_progress_bar/ui_test_box_progress.cpp @@ -0,0 +1,435 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ui_test_box_progress.h" +#include "common/screen.h" +#include "components/ui_label.h" +#include "test_resource_config.h" + +namespace OHOS { +namespace { +const int16_t LABEL_HEIGHT = 29; +const int16_t BUTTON_WIDTH = 120; +const int16_t BUTTON_HEIGHT = 40; +const int16_t DELTA_X_COORDINATE = 8; +const int16_t DELTA_X_COORDINATE_2 = 336; +const int16_t DELTA_Y_COORDINATE = 12; +const int16_t DELTA_Y_COORDINATE_2 = 19; +} // namespace + +void UITestBoxProgress::SetUp() +{ + if (container_ == nullptr) { + positionX_ = 0; + positionY_ = 0; + container_ = new UIScrollView(); + container_->Resize(Screen::GetInstance().GetWidth(), Screen::GetInstance().GetHeight() - BACK_BUTTON_HEIGHT); + UIViewGroup* uiViewGroup = new UIViewGroup(); + uiViewGroup->SetPosition(positionX_, positionY_, 320, 390); // 320: width; 390: height + container_->Add(uiViewGroup); + UILabel* label = new UILabel(); + uiViewGroup->Add(label); + // 288: width; 48: height + label->SetPosition(TEXT_DISTANCE_TO_LEFT_SIDE, TEXT_DISTANCE_TO_TOP_SIDE, 288, 48); + label->SetText("UIBoxProgress效果"); + label->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + uiViewGroupFrame_ = new UIViewGroup(); + uiViewGroup->Add(uiViewGroupFrame_); + // 288: width; 336: height + uiViewGroupFrame_->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE2, TEXT_DISTANCE_TO_LEFT_SIDE, 288, 336); + uiViewGroupFrame_->SetStyle(STYLE_BORDER_COLOR, Color::White().full); + uiViewGroupFrame_->SetStyle(STYLE_BORDER_OPA, HALF_OPA_OPAQUE); + uiViewGroupFrame_->SetStyle(STYLE_BORDER_WIDTH, VIEW_STYLE_BORDER_WIDTH); + uiViewGroupFrame_->SetStyle(STYLE_BORDER_RADIUS, VIEW_STYLE_BORDER_RADIUS); + uiViewGroupFrame_->SetStyle(STYLE_BACKGROUND_OPA, 0); + + boxProgress_ = new UIBoxProgress(); + boxProgress_->SetPosition(10, 10, 100, 10); // 10: x 10: y 100: width 10: height + boxProgress_->SetValue(20); // 20: value + boxProgress_->SetBackgroundStyle(STYLE_BACKGROUND_COLOR, Color::Red().full); + boxProgress_->SetForegroundStyle(STYLE_BACKGROUND_COLOR, Color::Green().full); + boxProgress_->SetStyle(STYLE_BORDER_COLOR, Color::Yellow().full); + uiViewGroupFrame_->Add(boxProgress_); + boxProgress_->LayoutCenterOfParent(); + } + + if (scroll_ == nullptr) { + scroll_ = new UIScrollView(); + scroll_->SetPosition(336, 11); // 336: x-coordinate; 11: y-coordinate + // 624: width; 100: Adjust the visible height of scroll + scroll_->Resize(624, Screen::GetInstance().GetHeight() - 100); + container_->Add(scroll_); + positionY_ = 0; + } +} + +void UITestBoxProgress::TearDown() +{ + DeleteChildren(container_); + container_ = nullptr; + boxProgress_ = nullptr; + scroll_ = nullptr; + uiViewGroupFrame_ = nullptr; +} + +UIView* UITestBoxProgress::GetTestView() +{ + UIKit_BoxProgress_Test_UIBoxProgress_001(); + UIKit_BoxProgress_Test_SetRange_002(); + UIKit_BoxProgress_Test_SetValue_003(); + UIKit_BoxProgress_Test_SetImage_004(); + UIKit_BoxProgress_Test_SetStyle_005(); + UIKit_BoxProgress_Test_GetStyle_006(); + UIKit_BoxProgress_Test_SetCapType_007(); + UIKit_BoxProgress_Test_SetDirection_008(); + UIKit_BoxProgress_Test_EnableBackground_009(); + UIKit_BoxProgress_Test_SetStep_010(); + UIKit_BoxProgress_Test_SetValidSize_011(); + UIKit_BoxProgress_Test_SetBorderAndPadding_012(); + + return container_; +} + +void UITestBoxProgress::SetUpLabel(const char* title, int16_t x, int16_t y) const +{ + UILabel* label = new UILabel(); + scroll_->Add(label); + label->SetPosition(x, y, 288, 30); // 288: width; 30: height + label->SetText(title); + label->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); +} +void UITestBoxProgress::SetUpButton(UILabelButton* btn, const char* title, int16_t x, int16_t y) +{ + if (btn == nullptr) { + return; + } + scroll_->Add(btn); + btn->SetPosition(x, y, BUTTON_WIDHT2, BUTTON_HEIGHT2); + btn->SetText(title); + btn->SetFont(DEFAULT_VECTOR_FONT_FILENAME, BUTTON_LABEL_SIZE); + btn->SetOnClickListener(this); + btn->SetStyleForState(STYLE_BORDER_RADIUS, BUTTON_STYLE_BORDER_RADIUS_VALUE, UIButton::RELEASED); + btn->SetStyleForState(STYLE_BORDER_RADIUS, BUTTON_STYLE_BORDER_RADIUS_VALUE, UIButton::PRESSED); + btn->SetStyleForState(STYLE_BORDER_RADIUS, BUTTON_STYLE_BORDER_RADIUS_VALUE, UIButton::INACTIVE); + btn->SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::RELEASED); + btn->SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::PRESSED); + btn->SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::INACTIVE); + scroll_->Invalidate(); +} + +void UITestBoxProgress::UIKit_BoxProgress_Test_UIBoxProgress_001() +{ + resetBtn_ = new UILabelButton(); + positionX_ = VIEW_DISTANCE_TO_LEFT_SIDE2; + positionY_ = 0; + SetUpLabel("创建条形进度条:", positionX_, positionY_); + positionY_ += LABEL_HEIGHT + DELTA_X_COORDINATE; + SetUpButton(resetBtn_, "重置", positionX_, positionY_); +} + +void UITestBoxProgress::UIKit_BoxProgress_Test_SetRange_002() +{ + incMinProgressBtn_ = new UILabelButton(); + decMinProgressBtn_ = new UILabelButton(); + incMaxProgressBtn_ = new UILabelButton(); + decMaxProgressBtn_ = new UILabelButton(); + positionX_ = VIEW_DISTANCE_TO_LEFT_SIDE2; + positionY_ += BUTTON_HEIGHT + DELTA_Y_COORDINATE_2; + SetUpLabel("设置条形进度条范围:", positionX_, positionY_); + positionY_ += LABEL_HEIGHT + DELTA_X_COORDINATE; + SetUpButton(incMinProgressBtn_, "下限+", positionX_, positionY_); + positionX_ += BUTTON_WIDTH + DELTA_Y_COORDINATE; + SetUpButton(decMinProgressBtn_, "下限-", positionX_, positionY_); + positionX_ = VIEW_DISTANCE_TO_LEFT_SIDE2; + positionY_ += BUTTON_HEIGHT + DELTA_Y_COORDINATE; + SetUpButton(incMaxProgressBtn_, "上限+", positionX_, positionY_); + positionX_ += BUTTON_WIDTH + DELTA_Y_COORDINATE; + SetUpButton(decMaxProgressBtn_, "上限-", positionX_, positionY_); +} + +void UITestBoxProgress::UIKit_BoxProgress_Test_SetValue_003() +{ + incProgressBtn_ = new UILabelButton(); + decProgressBtn_ = new UILabelButton(); + positionX_ = VIEW_DISTANCE_TO_LEFT_SIDE2; + positionY_ += BUTTON_HEIGHT + DELTA_Y_COORDINATE_2; + SetUpLabel("设置条形进度条当前值:", positionX_, positionY_); + positionY_ += LABEL_HEIGHT + DELTA_X_COORDINATE; + SetUpButton(incProgressBtn_, "进度+", positionX_, positionY_); + positionX_ += BUTTON_WIDTH + DELTA_Y_COORDINATE; + SetUpButton(decProgressBtn_, "进度-", positionX_, positionY_); +} + +void UITestBoxProgress::UIKit_BoxProgress_Test_SetImage_004() +{ + imageBtn_ = new UILabelButton(); + noImageBtn_ = new UILabelButton(); + positionX_ = VIEW_DISTANCE_TO_LEFT_SIDE2; + positionY_ += BUTTON_HEIGHT + DELTA_Y_COORDINATE_2; + SetUpLabel("设置条形进度条图片:", positionX_, positionY_); + positionY_ += LABEL_HEIGHT + DELTA_X_COORDINATE; + SetUpButton(imageBtn_, "设置图片", positionX_, positionY_); + positionX_ += BUTTON_WIDTH + DELTA_Y_COORDINATE; + SetUpButton(noImageBtn_, "取消图片", positionX_, positionY_); +} + +void UITestBoxProgress::UIKit_BoxProgress_Test_SetStyle_005() +{ + setStyleBtn_ = new UILabelButton(); + positionX_ = VIEW_DISTANCE_TO_LEFT_SIDE2; + positionY_ += BUTTON_HEIGHT + DELTA_Y_COORDINATE_2; + SetUpLabel("设置条形进度条样式:", positionX_, positionY_); + positionY_ += LABEL_HEIGHT + DELTA_X_COORDINATE; + SetUpButton(setStyleBtn_, "设置样式", positionX_, positionY_); +} + +void UITestBoxProgress::UIKit_BoxProgress_Test_GetStyle_006() +{ + getStyleBtn_ = new UILabelButton(); + positionX_ = VIEW_DISTANCE_TO_LEFT_SIDE2; + positionY_ += BUTTON_HEIGHT + DELTA_Y_COORDINATE_2; + SetUpLabel("设置条形进度条样式:", positionX_, positionY_); + positionY_ += LABEL_HEIGHT + DELTA_X_COORDINATE; + SetUpButton(getStyleBtn_, "设置样式", positionX_, positionY_); +} + +void UITestBoxProgress::UIKit_BoxProgress_Test_SetCapType_007() +{ + roundCapBtn_ = new UILabelButton(); + noneCapBtn_ = new UILabelButton(); + positionX_ = DELTA_X_COORDINATE_2; + positionY_ = 0; + SetUpLabel("设置条形进度条端点:", positionX_, positionY_); + positionY_ += LABEL_HEIGHT + DELTA_X_COORDINATE; + SetUpButton(roundCapBtn_, "圆形端点", positionX_, positionY_); + positionX_ += BUTTON_WIDTH + DELTA_Y_COORDINATE; + SetUpButton(noneCapBtn_, "方形端点", positionX_, positionY_); +} + +void UITestBoxProgress::UIKit_BoxProgress_Test_SetDirection_008() +{ + dirL2RBtn_ = new UILabelButton(); + dirR2LBtn_ = new UILabelButton(); + dirT2BBtn_ = new UILabelButton(); + dirB2TBtn_ = new UILabelButton(); + positionX_ = DELTA_X_COORDINATE_2; + positionY_ += BUTTON_HEIGHT + DELTA_Y_COORDINATE_2; + SetUpLabel("设置条形进度条方向:", positionX_, positionY_); + positionY_ += LABEL_HEIGHT + DELTA_X_COORDINATE; + SetUpButton(dirL2RBtn_, "从左到右", positionX_, positionY_); + positionX_ += BUTTON_WIDTH + DELTA_Y_COORDINATE; + SetUpButton(dirR2LBtn_, "从右到左", positionX_, positionY_); + positionX_ = DELTA_X_COORDINATE_2; + positionY_ += BUTTON_HEIGHT + DELTA_Y_COORDINATE; + SetUpButton(dirT2BBtn_, "从上到下", positionX_, positionY_); + positionX_ += BUTTON_WIDTH + DELTA_Y_COORDINATE; + SetUpButton(dirB2TBtn_, "从下到上", positionX_, positionY_); +} + +void UITestBoxProgress::UIKit_BoxProgress_Test_EnableBackground_009() +{ + enableBgBtn_ = new UILabelButton(); + disableBgBtn_ = new UILabelButton(); + positionX_ = DELTA_X_COORDINATE_2; + positionY_ += BUTTON_HEIGHT + DELTA_Y_COORDINATE_2; + SetUpLabel("设置条形进度条背景:", positionX_, positionY_); + positionY_ += LABEL_HEIGHT + DELTA_X_COORDINATE; + SetUpButton(enableBgBtn_, "设置背景", positionX_, positionY_); + positionX_ += BUTTON_WIDTH + DELTA_Y_COORDINATE; + SetUpButton(disableBgBtn_, "隐藏背景", positionX_, positionY_); +} + +void UITestBoxProgress::UIKit_BoxProgress_Test_SetStep_010() +{ + stepBtn_ = new UILabelButton(); + positionX_ = DELTA_X_COORDINATE_2; + positionY_ += BUTTON_HEIGHT + DELTA_Y_COORDINATE_2; + SetUpLabel("设置条形进度条步长:", positionX_, positionY_); + positionY_ += LABEL_HEIGHT + DELTA_X_COORDINATE; + SetUpButton(stepBtn_, "步长+", positionX_, positionY_); +} + +void UITestBoxProgress::UIKit_BoxProgress_Test_SetValidSize_011() +{ + widthBtn_ = new UILabelButton(); + heightBtn_ = new UILabelButton(); + positionX_ = DELTA_X_COORDINATE_2; + positionY_ += BUTTON_HEIGHT + DELTA_Y_COORDINATE_2; + SetUpLabel("设置条形进度条有效区域宽高:", positionX_, positionY_); + positionY_ += LABEL_HEIGHT + DELTA_X_COORDINATE; + SetUpButton(heightBtn_, "box高度-", positionX_, positionY_); + positionX_ += BUTTON_WIDTH + DELTA_Y_COORDINATE; + SetUpButton(widthBtn_, "box宽度-", positionX_, positionY_); +} + +void UITestBoxProgress::UIKit_BoxProgress_Test_SetBorderAndPadding_012() +{ + borderIncBtn_ = new UILabelButton(); + borderDecBtn_ = new UILabelButton(); + paddingLeftIncBtn_ = new UILabelButton(); + paddingRightIncBtn_ = new UILabelButton(); + paddingTopIncBtn_ = new UILabelButton(); + paddingBottomIncBtn_ = new UILabelButton(); + positionX_ = DELTA_X_COORDINATE_2; + positionY_ += BUTTON_HEIGHT + DELTA_Y_COORDINATE_2; + SetUpLabel("设置边宽和内边距:", positionX_, positionY_); + positionY_ += LABEL_HEIGHT + DELTA_X_COORDINATE; + SetUpButton(borderIncBtn_, "边宽+", positionX_, positionY_); + positionX_ += BUTTON_WIDTH + DELTA_Y_COORDINATE_2; + SetUpButton(borderDecBtn_, "边宽-", positionX_, positionY_); + positionX_ = DELTA_X_COORDINATE_2; + positionY_ += BUTTON_HEIGHT + DELTA_Y_COORDINATE_2; + SetUpButton(paddingLeftIncBtn_, "左内边距+", positionX_, positionY_); + positionX_ += BUTTON_WIDTH + DELTA_Y_COORDINATE_2; + SetUpButton(paddingRightIncBtn_, "右内边距+", positionX_, positionY_); + positionX_ = DELTA_X_COORDINATE_2; + positionY_ += BUTTON_HEIGHT + DELTA_Y_COORDINATE_2; + SetUpButton(paddingTopIncBtn_, "上内边距+", positionX_, positionY_); + positionX_ += BUTTON_WIDTH + DELTA_Y_COORDINATE_2; + SetUpButton(paddingBottomIncBtn_, "下内边距+", positionX_, positionY_); +} + +bool UITestBoxProgress::OnClick(UIView& view, const ClickEvent& event) +{ + static uint16_t progress = 20; // 20: progress + static uint16_t step = 0; + static uint16_t width = 100; // 100: width + static uint16_t height = 10; // 10: height + static int16_t min = 0; + static int16_t max = 100; // 100: max value + if (&view == resetBtn_) { + boxProgress_->SetPosition(10, 10, 100, 10); // 10: x 10: y 100: width 15: height + progress = 20; // 20: progress + step = 0; + width = 100; // 100: width + height = 10; // 10: height + min = 0; + max = 100; // 100: max value + boxProgress_->SetDirection(UIBoxProgress::Direction::DIR_LEFT_TO_RIGHT); + boxProgress_->SetValidHeight(height); + boxProgress_->SetValidWidth(width); + boxProgress_->SetRange(max, min); + boxProgress_->SetValue(progress); + boxProgress_->SetStep(step); + // 182, 191: color + boxProgress_->SetBackgroundStyle(STYLE_BACKGROUND_COLOR, Color::GetColorFromRGB(0, 182, 191).full); + // 255, 152: color + boxProgress_->SetForegroundStyle(STYLE_BACKGROUND_COLOR, Color::GetColorFromRGB(255, 152, 0).full); + boxProgress_->SetImage(static_cast(nullptr)); + boxProgress_->EnableBackground(true); + boxProgress_->LayoutCenterOfParent(); + } else if (&view == incProgressBtn_) { + progress++; + boxProgress_->SetValue(progress); + } else if (&view == decProgressBtn_) { + progress--; + boxProgress_->SetValue(progress); + } else if (&view == stepBtn_) { + step++; + boxProgress_->SetStep(step); + } else if (&view == widthBtn_) { + width--; + boxProgress_->SetValidWidth(width); + } else if (&view == heightBtn_) { + height--; + boxProgress_->SetValidHeight(height); + } else if (&view == incMinProgressBtn_) { + min++; + boxProgress_->SetRange(boxProgress_->GetRangeMax(), min); + } else if (&view == decMinProgressBtn_) { + min--; + boxProgress_->SetRange(boxProgress_->GetRangeMax(), min); + } else if (&view == incMaxProgressBtn_) { + max++; + boxProgress_->SetRange(max, boxProgress_->GetRangeMin()); + } else if (&view == decMaxProgressBtn_) { + max--; + boxProgress_->SetRange(max, boxProgress_->GetRangeMin()); + } else if (&view == dirL2RBtn_) { + width = 100; // 100: width + height = 10; // 10: height + boxProgress_->Resize(width, height); + boxProgress_->SetDirection(UIBoxProgress::Direction::DIR_LEFT_TO_RIGHT); + } else if (&view == dirR2LBtn_) { + width = 100; // 100: width + height = 10; // 10: height + boxProgress_->Resize(width, height); + boxProgress_->SetDirection(UIBoxProgress::Direction::DIR_RIGHT_TO_LEFT); + } else if (&view == dirT2BBtn_) { + width = 10; // 10: width + height = 100; // 100: height + boxProgress_->Resize(width, height); + boxProgress_->SetDirection(UIBoxProgress::Direction::DIR_TOP_TO_BOTTOM); + } else if (&view == dirB2TBtn_) { + width = 10; // 10: width + height = 100; // 100: height + boxProgress_->Resize(width, height); + boxProgress_->SetDirection(UIBoxProgress::Direction::DIR_BOTTOM_TO_TOP); + } else if (&view == imageBtn_) { + boxProgress_->SetImage(RED_IMAGE_PATH, BLUE_IMAGE_PATH); + } else if (&view == noImageBtn_) { + boxProgress_->SetImage(static_cast(nullptr)); + } else if (&view == setStyleBtn_) { + Style style = StyleDefault::GetDefaultStyle(); + style.bgColor_ = Color::White(); + boxProgress_->SetBackgroundStyle(style); + style.bgColor_ = Color::Blue(); + boxProgress_->SetForegroundStyle(style); + } else if (&view == getStyleBtn_) { + boxProgress_->SetBackgroundStyle(STYLE_BACKGROUND_COLOR, Color::Red().full); + boxProgress_->SetForegroundStyle(STYLE_BACKGROUND_COLOR, Color::Yellow().full); + } else if (&view == roundCapBtn_) { + boxProgress_->SetCapType(CapType::CAP_ROUND); + } else if (&view == noneCapBtn_) { + boxProgress_->SetCapType(CapType::CAP_NONE); + } else if (&view == enableBgBtn_) { + boxProgress_->EnableBackground(true); + } else if (&view == disableBgBtn_) { + boxProgress_->EnableBackground(false); + } else if (&view == borderIncBtn_) { + int64_t bodrderWidth = boxProgress_->GetStyle(STYLE_BORDER_WIDTH); + // 20: max width; 5: step + bodrderWidth = (bodrderWidth + 5 > 20) ? 20 : (bodrderWidth + 5); + boxProgress_->SetStyle(STYLE_BORDER_WIDTH, bodrderWidth); + } else if (&view == borderDecBtn_) { + int64_t bodrderWidth = boxProgress_->GetStyle(STYLE_BORDER_WIDTH); + // 0: min width; 5: step + bodrderWidth = (bodrderWidth - 5 < 0) ? 0 : (bodrderWidth - 5); + boxProgress_->SetStyle(STYLE_BORDER_WIDTH, bodrderWidth); + } else if (&view == paddingLeftIncBtn_) { + int64_t paddingWidth = boxProgress_->GetStyle(STYLE_PADDING_LEFT); + // 20: max width; 5: step + paddingWidth = (paddingWidth + 5 > 20) ? 20 : (paddingWidth + 5); + boxProgress_->SetStyle(STYLE_PADDING_LEFT, paddingWidth); + } else if (&view == paddingRightIncBtn_) { + int64_t paddingWidth = boxProgress_->GetStyle(STYLE_PADDING_RIGHT); + // 20: max width; 5: step + paddingWidth = (paddingWidth + 5 > 20) ? 20 : (paddingWidth + 5); + boxProgress_->SetStyle(STYLE_PADDING_RIGHT, paddingWidth); + } else if (&view == paddingTopIncBtn_) { + int64_t paddingWidth = boxProgress_->GetStyle(STYLE_PADDING_TOP); + // 20: max width; 5: step + paddingWidth = (paddingWidth + 5 > 20) ? 20 : (paddingWidth + 5); + boxProgress_->SetStyle(STYLE_PADDING_TOP, paddingWidth); + } else if (&view == paddingBottomIncBtn_) { + int64_t paddingWidth = boxProgress_->GetStyle(STYLE_PADDING_BOTTOM); + // 20: max width; 5: step + paddingWidth = (paddingWidth + 5 > 20) ? 20 : (paddingWidth + 5); + boxProgress_->SetStyle(STYLE_PADDING_BOTTOM, paddingWidth); + } + container_->Invalidate(); + return true; +} +} // namespace OHOS \ No newline at end of file diff --git a/test/uitest/test_progress_bar/ui_test_box_progress.h b/test/uitest/test_progress_bar/ui_test_box_progress.h new file mode 100755 index 0000000..c4174d1 --- /dev/null +++ b/test/uitest/test_progress_bar/ui_test_box_progress.h @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef UI_TEST_BOX_PROGRESS_H +#define UI_TEST_BOX_PROGRESS_H + +#include "components/ui_box_progress.h" +#include "components/ui_label_button.h" +#include "components/ui_scroll_view.h" +#include "ui_test.h" + +namespace OHOS { +class UITestBoxProgress : public UITest, public UIView::OnClickListener { +public: + UITestBoxProgress() {} + ~UITestBoxProgress() {} + void SetUp() override; + void TearDown() override; + UIView* GetTestView() override; + + void SetUpButton(UILabelButton* btn, const char* title, int16_t x, int16_t y); + void SetUpLabel(const char* title, int16_t x, int16_t y) const; + + bool OnClick(UIView& view, const ClickEvent& event) override; + + void UIKit_BoxProgress_Test_UIBoxProgress_001(); + void UIKit_BoxProgress_Test_SetRange_002(); + void UIKit_BoxProgress_Test_SetValue_003(); + void UIKit_BoxProgress_Test_SetImage_004(); + void UIKit_BoxProgress_Test_SetStyle_005(); + void UIKit_BoxProgress_Test_GetStyle_006(); + void UIKit_BoxProgress_Test_SetCapType_007(); + void UIKit_BoxProgress_Test_SetDirection_008(); + void UIKit_BoxProgress_Test_EnableBackground_009(); + void UIKit_BoxProgress_Test_SetStep_010(); + void UIKit_BoxProgress_Test_SetValidSize_011(); + void UIKit_BoxProgress_Test_SetBorderAndPadding_012(); + +private: + UIScrollView* container_ = nullptr; + UIScrollView* scroll_ = nullptr; + UIBoxProgress* boxProgress_ = nullptr; + UIViewGroup* uiViewGroupFrame_ = nullptr; + + UILabelButton* resetBtn_ = nullptr; + UILabelButton* widthBtn_ = nullptr; + UILabelButton* heightBtn_ = nullptr; + UILabelButton* incProgressBtn_ = nullptr; + UILabelButton* decProgressBtn_ = nullptr; + UILabelButton* incMinProgressBtn_ = nullptr; + UILabelButton* decMinProgressBtn_ = nullptr; + UILabelButton* incMaxProgressBtn_ = nullptr; + UILabelButton* decMaxProgressBtn_ = nullptr; + UILabelButton* stepBtn_ = nullptr; + UILabelButton* dirL2RBtn_ = nullptr; + UILabelButton* dirR2LBtn_ = nullptr; + UILabelButton* dirT2BBtn_ = nullptr; + UILabelButton* dirB2TBtn_ = nullptr; + UILabelButton* imageBtn_ = nullptr; + UILabelButton* noImageBtn_ = nullptr; + UILabelButton* setStyleBtn_ = nullptr; + UILabelButton* getStyleBtn_ = nullptr; + UILabelButton* roundCapBtn_ = nullptr; + UILabelButton* noneCapBtn_ = nullptr; + UILabelButton* enableBgBtn_ = nullptr; + UILabelButton* disableBgBtn_ = nullptr; + UILabelButton* borderIncBtn_ = nullptr; + UILabelButton* borderDecBtn_ = nullptr; + UILabelButton* paddingLeftIncBtn_ = nullptr; + UILabelButton* paddingRightIncBtn_ = nullptr; + UILabelButton* paddingTopIncBtn_ = nullptr; + UILabelButton* paddingBottomIncBtn_ = nullptr; +}; +} // namespace OHOS +#endif // UI_TEST_BOX_PROGRESS_H diff --git a/test/uitest/test_progress_bar/ui_test_circle_progress.cpp b/test/uitest/test_progress_bar/ui_test_circle_progress.cpp new file mode 100755 index 0000000..d92343a --- /dev/null +++ b/test/uitest/test_progress_bar/ui_test_circle_progress.cpp @@ -0,0 +1,473 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ui_test_circle_progress.h" +#include "common/screen.h" +#include "components/ui_label.h" +#include "test_resource_config.h" + +namespace OHOS { +namespace { +const int16_t LABEL_HEIGHT = 29; +const int16_t BUTTON_WIDTH = 120; +const int16_t BUTTON_HEIGHT = 40; +const int16_t DELTA_X_COORDINATE = 8; +const int16_t DELTA_X_COORDINATE_2 = 336; +const int16_t DELTA_Y_COORDINATE = 12; +const int16_t DELTA_Y_COORDINATE_2 = 19; +} // namespace + +void UITestCircleProgress::SetUp() +{ + if (container_ == nullptr) { + positionX_ = 0; + positionY_ = 0; + container_ = new UIScrollView(); + container_->Resize(Screen::GetInstance().GetWidth(), Screen::GetInstance().GetHeight() - BACK_BUTTON_HEIGHT); + UIViewGroup* uiViewGroup = new UIViewGroup(); + uiViewGroup->SetPosition(positionX_, positionY_, 320, 390); // 320: width; 390: height + container_->Add(uiViewGroup); + UILabel* label = new UILabel(); + uiViewGroup->Add(label); + // 288: x-coordinate; 48: y-coordinate + label->SetPosition(TEXT_DISTANCE_TO_LEFT_SIDE, TEXT_DISTANCE_TO_TOP_SIDE, 288, 48); + label->SetText("UICircleProgress效果"); + label->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + uiViewGroupFrame_ = new UIViewGroup(); + uiViewGroup->Add(uiViewGroupFrame_); + // 288: width; 336: height + uiViewGroupFrame_->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE2, VIEW_DISTANCE_TO_TOP_SIDE, 288, 336); + uiViewGroupFrame_->SetStyle(STYLE_BORDER_COLOR, Color::White().full); + uiViewGroupFrame_->SetStyle(STYLE_BORDER_OPA, HALF_OPA_OPAQUE); + uiViewGroupFrame_->SetStyle(STYLE_BORDER_WIDTH, VIEW_STYLE_BORDER_WIDTH); + uiViewGroupFrame_->SetStyle(STYLE_BORDER_RADIUS, VIEW_STYLE_BORDER_RADIUS); + uiViewGroupFrame_->SetStyle(STYLE_BACKGROUND_OPA, 0); + + circleProgress_ = new UICircleProgress(); + circleProgress_->SetPosition(10, 10, 150, 150); // 10: x 10:y 150:width 150: height + circleProgress_->SetCenterPosition(75, 75); // 75: position x 75: position y + circleProgress_->SetRadius(50); // 50: radius + circleProgress_->SetValue(20); // 20: value + circleProgress_->SetStyle(STYLE_PADDING_LEFT, 10); // 10: value + circleProgress_->SetStyle(STYLE_PADDING_TOP, 5); // 5: value + uiViewGroupFrame_->Add(circleProgress_); + circleProgress_->LayoutCenterOfParent(); + } + + if (scroll_ == nullptr) { + scroll_ = new UIScrollView(); + scroll_->SetPosition(336, 11); // 336: x-coordinate; 11: y-coordinate + scroll_->Resize(624, container_->GetHeight() - 11); // 624: width; // 11: decrease height + container_->Add(scroll_); + positionY_ = 0; + } +} + +void UITestCircleProgress::TearDown() +{ + DeleteChildren(container_); + container_ = nullptr; + circleProgress_ = nullptr; + scroll_ = nullptr; +} + +UIView* UITestCircleProgress::GetTestView() +{ + UIKit_CircleProgress_Test_UICircleProgress_001(); + UIKit_CircleProgress_Test_SetValue_002(); + UIKit_CircleProgress_Test_SetImage_003(); + UIKit_CircleProgress_Test_SetStyle_004(); + UIKit_CircleProgress_Test_GetStyle_005(); + UIKit_CircleProgress_Test_SetCapType_006(); + UIKit_CircleProgress_Test_SetStep_007(); + UIKit_CircleProgress_Test_SetRange_008(); + UIKit_CircleProgress_Test_EnableBackground_009(); + UIKit_CircleProgress_Test_SetCenter_010(); + UIKit_CircleProgress_Test_SetRadius_011(); + UIKit_CircleProgress_Test_SetLineWidth_012(); + UIKit_CircleProgress_Test_SetImagePosition_013(); + UIKit_CircleProgress_Test_SetLineColor_014(); + UIKit_CircleProgress_Test_SetAngle_015(); + + return container_; +} + +void UITestCircleProgress::SetUpLabel(const char* title, int16_t x, int16_t y) const +{ + UILabel* label = new UILabel(); + scroll_->Add(label); + label->SetPosition(x, y, 288, 30); // 288: width; 30: height + label->SetText(title); + label->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); +} +void UITestCircleProgress::SetUpButton(UILabelButton* btn, const char* title, int16_t x, int16_t y) +{ + if (btn == nullptr) { + return; + } + scroll_->Add(btn); + btn->SetPosition(x, y, BUTTON_WIDHT2, BUTTON_HEIGHT2); + btn->SetText(title); + btn->SetFont(DEFAULT_VECTOR_FONT_FILENAME, BUTTON_LABEL_SIZE); + btn->SetOnClickListener(this); + btn->SetStyleForState(STYLE_BORDER_RADIUS, BUTTON_STYLE_BORDER_RADIUS_VALUE, UIButton::RELEASED); + btn->SetStyleForState(STYLE_BORDER_RADIUS, BUTTON_STYLE_BORDER_RADIUS_VALUE, UIButton::PRESSED); + btn->SetStyleForState(STYLE_BORDER_RADIUS, BUTTON_STYLE_BORDER_RADIUS_VALUE, UIButton::INACTIVE); + btn->SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::RELEASED); + btn->SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::PRESSED); + btn->SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::INACTIVE); + scroll_->Invalidate(); +} + +void UITestCircleProgress::UIKit_CircleProgress_Test_UICircleProgress_001() +{ + resetBtn_ = new UILabelButton(); + positionX_ = VIEW_DISTANCE_TO_LEFT_SIDE2; + positionY_ = 0; + SetUpLabel("创建环形进度条:", positionX_, positionY_); + positionY_ += LABEL_HEIGHT + DELTA_X_COORDINATE; + SetUpButton(resetBtn_, "重置", positionX_, positionY_); +} + +void UITestCircleProgress::UIKit_CircleProgress_Test_SetRange_008() +{ + incMinProgressBtn_ = new UILabelButton(); + decMinProgressBtn_ = new UILabelButton(); + incMaxProgressBtn_ = new UILabelButton(); + decMaxProgressBtn_ = new UILabelButton(); + positionX_ = VIEW_DISTANCE_TO_LEFT_SIDE2; + positionY_ += BUTTON_HEIGHT + DELTA_Y_COORDINATE_2; + SetUpLabel("设置环形进度条范围:", positionX_, positionY_); + positionY_ += LABEL_HEIGHT + DELTA_X_COORDINATE; + SetUpButton(incMinProgressBtn_, "下限+", positionX_, positionY_); + positionX_ += BUTTON_WIDTH + DELTA_Y_COORDINATE; + SetUpButton(decMinProgressBtn_, "下限-", positionX_, positionY_); + positionX_ = VIEW_DISTANCE_TO_LEFT_SIDE2; + positionY_ += BUTTON_HEIGHT + DELTA_Y_COORDINATE; + SetUpButton(incMaxProgressBtn_, "上限+", positionX_, positionY_); + positionX_ += BUTTON_WIDTH + DELTA_Y_COORDINATE; + SetUpButton(decMaxProgressBtn_, "上限-", positionX_, positionY_); +} + +void UITestCircleProgress::UIKit_CircleProgress_Test_SetValue_002() +{ + incProgressBtn_ = new UILabelButton(); + decProgressBtn_ = new UILabelButton(); + positionX_ = VIEW_DISTANCE_TO_LEFT_SIDE2; + positionY_ += BUTTON_HEIGHT + DELTA_Y_COORDINATE_2; + SetUpLabel("设置环形进度条当前值:", positionX_, positionY_); + positionY_ += LABEL_HEIGHT + DELTA_X_COORDINATE; + SetUpButton(incProgressBtn_, "进度+", positionX_, positionY_); + positionX_ += BUTTON_WIDTH + DELTA_Y_COORDINATE; + SetUpButton(decProgressBtn_, "进度-", positionX_, positionY_); +} + +void UITestCircleProgress::UIKit_CircleProgress_Test_SetImage_003() +{ + imageBtn_ = new UILabelButton(); + noImageBtn_ = new UILabelButton(); + positionX_ = VIEW_DISTANCE_TO_LEFT_SIDE2; + positionY_ += BUTTON_HEIGHT + DELTA_Y_COORDINATE_2; + SetUpLabel("设置环形进度条图片:", positionX_, positionY_); + positionY_ += LABEL_HEIGHT + DELTA_X_COORDINATE; + SetUpButton(imageBtn_, "设置图片", positionX_, positionY_); + positionX_ += BUTTON_WIDTH + DELTA_Y_COORDINATE; + SetUpButton(noImageBtn_, "取消图片", positionX_, positionY_); +} + +void UITestCircleProgress::UIKit_CircleProgress_Test_SetStyle_004() +{ + setStyleBtn_ = new UILabelButton(); + positionX_ = VIEW_DISTANCE_TO_LEFT_SIDE2; + positionY_ += BUTTON_HEIGHT + DELTA_Y_COORDINATE_2; + SetUpLabel("设置环形进度条样式:", positionX_, positionY_); + positionY_ += LABEL_HEIGHT + DELTA_X_COORDINATE; + SetUpButton(setStyleBtn_, "设置样式", positionX_, positionY_); +} + +void UITestCircleProgress::UIKit_CircleProgress_Test_GetStyle_005() +{ + getStyleBtn_ = new UILabelButton(); + positionX_ = VIEW_DISTANCE_TO_LEFT_SIDE2; + positionY_ += BUTTON_HEIGHT + DELTA_Y_COORDINATE_2; + SetUpLabel("设置环形进度条样式:", positionX_, positionY_); + positionY_ += LABEL_HEIGHT + DELTA_X_COORDINATE; + SetUpButton(getStyleBtn_, "设置样式", positionX_, positionY_); +} + +void UITestCircleProgress::UIKit_CircleProgress_Test_SetCapType_006() +{ + roundCapBtn_ = new UILabelButton(); + noneCapBtn_ = new UILabelButton(); + positionX_ = VIEW_DISTANCE_TO_LEFT_SIDE2; + positionY_ += BUTTON_HEIGHT + DELTA_Y_COORDINATE_2; + SetUpLabel("设置环形进度条端点:", positionX_, positionY_); + positionY_ += LABEL_HEIGHT + DELTA_X_COORDINATE; + SetUpButton(roundCapBtn_, "圆形端点", positionX_, positionY_); + positionX_ += BUTTON_WIDTH + DELTA_Y_COORDINATE; + SetUpButton(noneCapBtn_, "方形端点", positionX_, positionY_); +} + +void UITestCircleProgress::UIKit_CircleProgress_Test_SetStep_007() +{ + stepBtn_ = new UILabelButton(); + positionX_ = VIEW_DISTANCE_TO_LEFT_SIDE2; + positionY_ += BUTTON_HEIGHT + DELTA_Y_COORDINATE_2; + SetUpLabel("设置环形进度条步长:", positionX_, positionY_); + positionY_ += LABEL_HEIGHT + DELTA_X_COORDINATE; + SetUpButton(stepBtn_, "步长+", positionX_, positionY_); +} + +void UITestCircleProgress::UIKit_CircleProgress_Test_EnableBackground_009() +{ + enableBgBtn_ = new UILabelButton(); + disableBgBtn_ = new UILabelButton(); + positionX_ = DELTA_X_COORDINATE_2; + positionY_ = 0; + SetUpLabel("环形进度条隐藏背景:", positionX_, positionY_); + positionY_ += LABEL_HEIGHT + DELTA_X_COORDINATE; + SetUpButton(enableBgBtn_, "设置背景", positionX_, positionY_); + positionX_ += BUTTON_WIDTH + DELTA_Y_COORDINATE; + SetUpButton(disableBgBtn_, "隐藏背景", positionX_, positionY_); +} + +void UITestCircleProgress::UIKit_CircleProgress_Test_SetCenter_010() +{ + centerXBtn_ = new UILabelButton(); + centerYBtn_ = new UILabelButton(); + positionX_ = DELTA_X_COORDINATE_2; + positionY_ += BUTTON_HEIGHT + DELTA_Y_COORDINATE_2; + SetUpLabel("设置环形进度条圆心:", positionX_, positionY_); + positionY_ += LABEL_HEIGHT + DELTA_X_COORDINATE; + SetUpButton(centerXBtn_, "圆心X+", positionX_, positionY_); + positionX_ += BUTTON_WIDTH + DELTA_Y_COORDINATE; + SetUpButton(centerYBtn_, "圆心Y+", positionX_, positionY_); +} + +void UITestCircleProgress::UIKit_CircleProgress_Test_SetRadius_011() +{ + incRadiusBtn_ = new UILabelButton(); + decRadiusBtn_ = new UILabelButton(); + positionX_ = DELTA_X_COORDINATE_2; + positionY_ += BUTTON_HEIGHT + DELTA_Y_COORDINATE_2; + SetUpLabel("设置环形进度条半径:", positionX_, positionY_); + positionY_ += LABEL_HEIGHT + DELTA_X_COORDINATE; + SetUpButton(incRadiusBtn_, "半径+", positionX_, positionY_); + positionX_ += BUTTON_WIDTH + DELTA_Y_COORDINATE; + SetUpButton(decRadiusBtn_, "半径-", positionX_, positionY_); +} + +void UITestCircleProgress::UIKit_CircleProgress_Test_SetLineWidth_012() +{ + incWidthBtn_ = new UILabelButton(); + decWidthBtn_ = new UILabelButton(); + positionX_ = DELTA_X_COORDINATE_2; + positionY_ += BUTTON_HEIGHT + DELTA_Y_COORDINATE_2; + SetUpLabel("设置环形进度条线宽:", positionX_, positionY_); + positionY_ += LABEL_HEIGHT + DELTA_X_COORDINATE; + SetUpButton(incWidthBtn_, "线宽+", positionX_, positionY_); + positionX_ += BUTTON_WIDTH + DELTA_Y_COORDINATE; + SetUpButton(decWidthBtn_, "线宽-", positionX_, positionY_); +} + +void UITestCircleProgress::UIKit_CircleProgress_Test_SetImagePosition_013() +{ + imgPosXBtn_ = new UILabelButton(); + imgPosYBtn_ = new UILabelButton(); + positionX_ = DELTA_X_COORDINATE_2; + positionY_ += BUTTON_HEIGHT + DELTA_Y_COORDINATE_2; + SetUpLabel("设置环形进度条图片位置:", positionX_, positionY_); + positionY_ += LABEL_HEIGHT + DELTA_X_COORDINATE; + SetUpButton(imgPosXBtn_, "图片X+", positionX_, positionY_); + positionX_ += BUTTON_WIDTH + DELTA_Y_COORDINATE; + SetUpButton(imgPosYBtn_, "图片Y+", positionX_, positionY_); +} + +void UITestCircleProgress::UIKit_CircleProgress_Test_SetLineColor_014() +{ + lineColorBtn_ = new UILabelButton(); + positionX_ = DELTA_X_COORDINATE_2; + positionY_ += BUTTON_HEIGHT + DELTA_Y_COORDINATE_2; + SetUpLabel("设置环形进度条前景线条颜色: ", positionX_, positionY_); + positionY_ += LABEL_HEIGHT + DELTA_X_COORDINATE; + SetUpButton(lineColorBtn_, "前景绿色", positionX_, positionY_); +} + +void UITestCircleProgress::UIKit_CircleProgress_Test_SetAngle_015() +{ + incStartAngleBtn_ = new UILabelButton(); + decStartAngleBtn_ = new UILabelButton(); + incEndAngleBtn_ = new UILabelButton(); + decEndAngleBtn_ = new UILabelButton(); + swapAngleBtn_ = new UILabelButton(); + positionX_ = DELTA_X_COORDINATE_2; + positionY_ += BUTTON_HEIGHT + DELTA_Y_COORDINATE_2; + SetUpLabel("设置环形进度条角度:", positionX_, positionY_); + positionY_ += LABEL_HEIGHT + DELTA_X_COORDINATE; + SetUpButton(incStartAngleBtn_, "起始角度+", positionX_, positionY_); + positionX_ += BUTTON_WIDTH + DELTA_Y_COORDINATE; + SetUpButton(decStartAngleBtn_, "起始角度-", positionX_, positionY_); + positionX_ = DELTA_X_COORDINATE_2; + positionY_ += BUTTON_HEIGHT + DELTA_Y_COORDINATE; + SetUpButton(incEndAngleBtn_, "结束角度+", positionX_, positionY_); + positionX_ += BUTTON_WIDTH + DELTA_Y_COORDINATE; + SetUpButton(decEndAngleBtn_, "结束角度-", positionX_, positionY_); + positionX_ = DELTA_X_COORDINATE_2; + positionY_ += BUTTON_HEIGHT + DELTA_Y_COORDINATE; + SetUpButton(swapAngleBtn_, "转换方向", positionX_, positionY_); +} + +bool UITestCircleProgress::OnClick(UIView& view, const ClickEvent& event) +{ + static uint16_t progress = 20; // 20: progress + static uint16_t step = 0; + static uint16_t width = 150; // 150: width + static uint16_t height = 150; // 150: height + static int16_t min = 0; + static int16_t max = 100; // 100: max value + static Point center = {75, 75}; // 75: position x 75: position y + static Point imgPos = {0, 0}; + static int16_t startAngle = 0; + static int16_t endAngle = 360; // 360: end angle + static int16_t radius = 50; // 50: radius + + if (&view == resetBtn_) { + circleProgress_->SetPosition(10, 10, 150, 150); // 10: x 10: y 150: width 150: height + progress = 100; // 100 progress + step = 0; + width = 150; // 150: width + height = 150; // 150: height + min = 0; + max = 100; // 100: max value + center = {75, 75}; // 75: position x 75: position y + startAngle = 0; + endAngle = 360; // 360: end angle + radius = 50; // 50: radius + imgPos = {0, 0}; + + circleProgress_->SetRange(max, min); + circleProgress_->SetValue(progress); + circleProgress_->SetCenterPosition(center.x, center.y); + circleProgress_->SetRadius(radius); + circleProgress_->SetStartAngle(startAngle); + circleProgress_->SetEndAngle(endAngle); + circleProgress_->SetStep(step); + circleProgress_->SetBackgroundStyle(StyleDefault::GetProgressBackgroundStyle()); + circleProgress_->SetForegroundStyle(StyleDefault::GetProgressForegroundStyle()); + circleProgress_->SetLineColor(Color::Red()); + circleProgress_->SetForegroundStyle(STYLE_LINE_WIDTH, 40); // 40: value + circleProgress_->SetForegroundStyle(STYLE_LINE_OPA, 127); // 127: value + circleProgress_->SetBackgroundImagePosition(imgPos.x, imgPos.y); + circleProgress_->SetProgressImagePosition(imgPos.x, imgPos.y); + circleProgress_->SetImage(static_cast(nullptr)); + circleProgress_->EnableBackground(false); + circleProgress_->LayoutCenterOfParent(); + } else if (&view == incProgressBtn_) { + progress++; + circleProgress_->SetValue(progress); + } else if (&view == decProgressBtn_) { + progress--; + circleProgress_->SetValue(progress); + } else if (&view == stepBtn_) { + step++; + circleProgress_->SetStep(step); + } else if (&view == incMinProgressBtn_) { + min++; + circleProgress_->SetRange(circleProgress_->GetRangeMax(), min); + } else if (&view == decMinProgressBtn_) { + min--; + circleProgress_->SetRange(circleProgress_->GetRangeMax(), min); + } else if (&view == incMaxProgressBtn_) { + max++; + circleProgress_->SetRange(max, circleProgress_->GetRangeMin()); + } else if (&view == decMaxProgressBtn_) { + max--; + circleProgress_->SetRange(max, circleProgress_->GetRangeMin()); + } else if (&view == imageBtn_) { + circleProgress_->SetImage(RED_IMAGE_PATH, BLUE_IMAGE_PATH); + } else if (&view == noImageBtn_) { + circleProgress_->SetImage(static_cast(nullptr)); + } else if (&view == setStyleBtn_) { + Style style = StyleDefault::GetDefaultStyle(); + style.lineColor_ = Color::White(); + circleProgress_->SetBackgroundStyle(style); + style.lineColor_ = Color::Blue(); + circleProgress_->SetForegroundStyle(style); + } else if (&view == getStyleBtn_) { + circleProgress_->SetBackgroundStyle(STYLE_LINE_COLOR, Color::Red().full); + circleProgress_->SetForegroundStyle(STYLE_LINE_COLOR, Color::Yellow().full); + } else if (&view == roundCapBtn_) { + circleProgress_->SetCapType(CapType::CAP_ROUND); + } else if (&view == noneCapBtn_) { + circleProgress_->SetCapType(CapType::CAP_NONE); + } else if (&view == enableBgBtn_) { + circleProgress_->EnableBackground(true); + } else if (&view == disableBgBtn_) { + circleProgress_->EnableBackground(false); + } else if (&view == incStartAngleBtn_) { + startAngle++; + circleProgress_->SetStartAngle(startAngle); + } else if (&view == decStartAngleBtn_) { + startAngle--; + circleProgress_->SetStartAngle(startAngle); + } else if (&view == incEndAngleBtn_) { + endAngle++; + circleProgress_->SetEndAngle(endAngle); + } else if (&view == decEndAngleBtn_) { + endAngle--; + circleProgress_->SetEndAngle(endAngle); + } else if (&view == swapAngleBtn_) { + int16_t angle = startAngle; + startAngle = endAngle; + endAngle = angle; + circleProgress_->SetStartAngle(startAngle); + circleProgress_->SetEndAngle(endAngle); + } else if (&view == centerXBtn_) { + center.x++; + circleProgress_->SetCenterPosition(center.x, circleProgress_->GetCenterPosition().y); + } else if (&view == centerYBtn_) { + center.y++; + circleProgress_->SetCenterPosition(circleProgress_->GetCenterPosition().x, center.y); + } else if (&view == incRadiusBtn_) { + radius++; + circleProgress_->SetRadius(radius); + } else if (&view == decRadiusBtn_) { + radius--; + circleProgress_->SetRadius(radius); + } else if (&view == incWidthBtn_) { + circleProgress_->SetBackgroundStyle(STYLE_LINE_WIDTH, + circleProgress_->GetBackgroundStyle(STYLE_LINE_WIDTH) + 1); + circleProgress_->SetForegroundStyle(STYLE_LINE_WIDTH, + circleProgress_->GetForegroundStyle(STYLE_LINE_WIDTH) + 1); + } else if (&view == decWidthBtn_) { + circleProgress_->SetBackgroundStyle(STYLE_LINE_WIDTH, + circleProgress_->GetBackgroundStyle(STYLE_LINE_WIDTH) - 1); + circleProgress_->SetForegroundStyle(STYLE_LINE_WIDTH, + circleProgress_->GetForegroundStyle(STYLE_LINE_WIDTH) - 1); + } else if (&view == imgPosXBtn_) { + imgPos.x++; + circleProgress_->SetBackgroundImagePosition(imgPos.x, imgPos.y); + circleProgress_->SetProgressImagePosition(imgPos.x, imgPos.y); + } else if (&view == imgPosXBtn_) { + imgPos.y++; + circleProgress_->SetBackgroundImagePosition(imgPos.x, imgPos.y); + circleProgress_->SetProgressImagePosition(imgPos.x, imgPos.y); + } else if (&view == lineColorBtn_) { + circleProgress_->SetLineColor(Color::Green()); + } + container_->Invalidate(); + return true; +} +} // namespace OHOS \ No newline at end of file diff --git a/test/uitest/test_progress_bar/ui_test_circle_progress.h b/test/uitest/test_progress_bar/ui_test_circle_progress.h new file mode 100755 index 0000000..0942881 --- /dev/null +++ b/test/uitest/test_progress_bar/ui_test_circle_progress.h @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef UI_TEST_CIRCLE_PROGRESS_H +#define UI_TEST_CIRCLE_PROGRESS_H + +#include "components/ui_circle_progress.h" +#include "components/ui_label_button.h" +#include "components/ui_scroll_view.h" +#include "ui_test.h" + +namespace OHOS { +class UITestCircleProgress : public UITest, public UIView::OnClickListener { +public: + UITestCircleProgress() {} + ~UITestCircleProgress() {} + void SetUp() override; + void TearDown() override; + UIView* GetTestView() override; + + void SetUpButton(UILabelButton* btn, const char* title, int16_t x, int16_t y); + void SetUpLabel(const char* title, int16_t x, int16_t y) const; + + bool OnClick(UIView& view, const ClickEvent& event) override; + + void UIKit_CircleProgress_Test_UICircleProgress_001(); + void UIKit_CircleProgress_Test_SetValue_002(); + void UIKit_CircleProgress_Test_SetImage_003(); + void UIKit_CircleProgress_Test_SetStyle_004(); + void UIKit_CircleProgress_Test_GetStyle_005(); + void UIKit_CircleProgress_Test_SetCapType_006(); + void UIKit_CircleProgress_Test_SetStep_007(); + void UIKit_CircleProgress_Test_SetRange_008(); + void UIKit_CircleProgress_Test_EnableBackground_009(); + void UIKit_CircleProgress_Test_SetCenter_010(); + void UIKit_CircleProgress_Test_SetRadius_011(); + void UIKit_CircleProgress_Test_SetLineWidth_012(); + void UIKit_CircleProgress_Test_SetImagePosition_013(); + void UIKit_CircleProgress_Test_SetLineColor_014(); + void UIKit_CircleProgress_Test_SetAngle_015(); + +private: + UIScrollView* container_ = nullptr; + UIScrollView* scroll_ = nullptr; + UICircleProgress* circleProgress_ = nullptr; + UIViewGroup* uiViewGroupFrame_ = nullptr; + + UILabelButton* resetBtn_ = nullptr; + UILabelButton* incProgressBtn_ = nullptr; + UILabelButton* decProgressBtn_ = nullptr; + UILabelButton* incMinProgressBtn_ = nullptr; + UILabelButton* decMinProgressBtn_ = nullptr; + UILabelButton* incMaxProgressBtn_ = nullptr; + UILabelButton* decMaxProgressBtn_ = nullptr; + UILabelButton* centerXBtn_ = nullptr; + UILabelButton* centerYBtn_ = nullptr; + UILabelButton* incRadiusBtn_ = nullptr; + UILabelButton* decRadiusBtn_ = nullptr; + UILabelButton* incWidthBtn_ = nullptr; + UILabelButton* decWidthBtn_ = nullptr; + UILabelButton* incStartAngleBtn_ = nullptr; + UILabelButton* decStartAngleBtn_ = nullptr; + UILabelButton* incEndAngleBtn_ = nullptr; + UILabelButton* decEndAngleBtn_ = nullptr; + UILabelButton* swapAngleBtn_ = nullptr; + UILabelButton* stepBtn_ = nullptr; + UILabelButton* imageBtn_ = nullptr; + UILabelButton* noImageBtn_ = nullptr; + UILabelButton* setStyleBtn_ = nullptr; + UILabelButton* getStyleBtn_ = nullptr; + UILabelButton* roundCapBtn_ = nullptr; + UILabelButton* noneCapBtn_ = nullptr; + UILabelButton* enableBgBtn_ = nullptr; + UILabelButton* disableBgBtn_ = nullptr; + UILabelButton* imgPosXBtn_ = nullptr; + UILabelButton* imgPosYBtn_ = nullptr; + UILabelButton* lineColorBtn_ = nullptr; +}; +} // namespace OHOS +#endif // UI_TEST_CIRCLE_PROGRESS_H diff --git a/test/uitest/test_qrcode/ui_test_qrcode.cpp b/test/uitest/test_qrcode/ui_test_qrcode.cpp new file mode 100755 index 0000000..6361cdf --- /dev/null +++ b/test/uitest/test_qrcode/ui_test_qrcode.cpp @@ -0,0 +1,234 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ui_test_qrcode.h" +#include "common/screen.h" +#include "font/ui_font.h" + +namespace OHOS { +namespace { +const int16_t TITLE_HEIGHT = 29; +} // namespace + +void UITestQrcode::SetUp() +{ + if (container_ == nullptr) { + container_ = new UIScrollView(); + container_->SetThrowDrag(true); + container_->SetHorizontalScrollState(false); + container_->Resize(Screen::GetInstance().GetWidth(), Screen::GetInstance().GetHeight() - BACK_BUTTON_HEIGHT); + } +} + +void UITestQrcode::TearDown() +{ + DeleteChildren(container_); + container_ = nullptr; +} + +const UIView* UITestQrcode::GetTestView() +{ + UIKitUIQrcodeTestDisplay001(); + UIKitUIQrcodeTestDisplay002(); + UIKitUIQrcodeTestDisplay003(); + UIKitUIQrcodeTestDisplay004(); + UIKitUIQrcodeTestDisplay005(); + UIKitUIQrcodeTestDisplay006(); + UIKitUIQrcodeTestDisplay007(); + UIKitUIQrcodeTestDisplay008(); + UIKitUIQrcodeTestDisplay009(); + return container_; +} + +UIViewGroup* UITestQrcode::CreateTestCaseGroup(const char* title) const +{ + UIViewGroup* group = new UIViewGroup(); + group->SetStyle(STYLE_BACKGROUND_COLOR, Color::Black().full); + group->SetStyle(STYLE_BACKGROUND_OPA, OPA_OPAQUE); + group->SetPosition(0, 0); + UILabel* titleLabel = new UILabel(); + titleLabel->SetPosition(0, 0, Screen::GetInstance().GetWidth(), TITLE_HEIGHT); + titleLabel->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + titleLabel->SetText(title); + group->Add(titleLabel); + return group; +} + +void UITestQrcode::UIKitUIQrcodeTestDisplay001() +{ + if (container_ != nullptr) { + UIViewGroup* group = CreateTestCaseGroup(" display qrcode 60 * 60"); + group->Resize(Screen::GetInstance().GetWidth(), 100); // 100 : height + group->SetViewId("UIKitUIQrcodeTestDisplay001"); + + UIQrcode* qrcode = new UIQrcode(); + qrcode->SetPosition(20, 30, 60, 60); // 20 x 30 y 150 width 60 height + const char* str = "Hello\n 鸿蒙轻量级GUI"; + qrcode->SetQrcodeInfo(str); + group->Add(qrcode); + container_->Add(group); + } +} +void UITestQrcode::UIKitUIQrcodeTestDisplay002() +{ + if (container_ != nullptr) { + UIViewGroup* group = CreateTestCaseGroup(" display qrcode 120 * 250"); + group->Resize(Screen::GetInstance().GetWidth(), 300); // 300 : height + group->SetViewId("UIKitUIQrcodeTestDisplay002"); + + UIQrcode* qrcode = new UIQrcode(); + qrcode->SetPosition(20, 30); // 20 x 30 y + const char* str = "Hello\n 鸿蒙轻量级GUI"; + qrcode->SetQrcodeInfo(str); + qrcode->SetWidth(120); // 120 width + qrcode->SetHeight(250); // 250 height + group->Add(qrcode); + container_->Add(group); + group->LayoutBottomToSibling("UIKitUIQrcodeTestDisplay001", 10); // 10: height + } +} + +void UITestQrcode::UIKitUIQrcodeTestDisplay003() +{ + if (container_ != nullptr) { + UIViewGroup* group = CreateTestCaseGroup(" display qrcode 400 * 200, MAX_LENGTH"); + group->Resize(Screen::GetInstance().GetWidth(), 450); // 450 : height + group->SetViewId("UIKitUIQrcodeTestDisplay003"); + + UIQrcode* qrcode = new UIQrcode(); + qrcode->SetPosition(20, 30); // 20 x 30 y + const char* str = + "Hello\n GUIddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd \ + ddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd \ + ddddddddddddddddddddddddddddddddddddddddddddddddddddddd"; + ColorType backgroundColor = Color::Green(); + ColorType qrColor = Color::Blue(); + qrcode->SetQrcodeInfo(str, backgroundColor, qrColor); + qrcode->SetWidth(400); // 400 width + qrcode->SetHeight(200); // 200 height + group->Add(qrcode); + container_->Add(group); + group->LayoutBottomToSibling("UIKitUIQrcodeTestDisplay002", 10); // 10: height + } +} + +void UITestQrcode::UIKitUIQrcodeTestDisplay004() +{ + if (container_ != nullptr) { + UIViewGroup* group = CreateTestCaseGroup("qrcode 50 * 50, width is less than minimum qrcode size, error!"); + group->Resize(Screen::GetInstance().GetWidth(), 100); // 100 : height + group->SetViewId("UIKitUIQrcodeTestDisplay004"); + + UIQrcode* qrcode = new UIQrcode(); + qrcode->SetPosition(20, 30, 50, 50); // 20 x 30 y 50 width 50 height + const char* str = + "Hello\n GUIddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd \ + ddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd \ + dddddddddddddddddddddddddddddddddddddddddddddddddddddd"; + qrcode->SetQrcodeInfo(str); + group->Add(qrcode); + container_->Add(group); + group->LayoutBottomToSibling("UIKitUIQrcodeTestDisplay003", 10); // 10: height + } +} + +void UITestQrcode::UIKitUIQrcodeTestDisplay005() +{ + if (container_ != nullptr) { + UIViewGroup* group = CreateTestCaseGroup("qrcode 250 * 250, val length is greater than MAX_LENGTH, error!"); + group->Resize(Screen::GetInstance().GetWidth(), 300); // 300 : height + group->SetViewId("UIKitUIQrcodeTestDisplay005"); + + UIQrcode* qrcode = new UIQrcode(); + qrcode->SetPosition(20, 30, 250, 250); // 20 x 30 y 250 width 250 height + const char* str = + "Hello\n GUIddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd \ + ddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd \ + ddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"; + qrcode->SetQrcodeInfo(str); + group->Add(qrcode); + container_->Add(group); + group->LayoutBottomToSibling("UIKitUIQrcodeTestDisplay004", 10); // 10: height + } +} + +void UITestQrcode::UIKitUIQrcodeTestDisplay006() +{ + if (container_ != nullptr) { + UIViewGroup* group = CreateTestCaseGroup("qrcode 250 * 250, str = \"\" "); + group->Resize(Screen::GetInstance().GetWidth(), 300); // 300 : height + group->SetViewId("UIKitUIQrcodeTestDisplay006"); + + UIQrcode* qrcode = new UIQrcode(); + qrcode->SetPosition(20, 30, 250, 250); // 20 x 30 y 250 width 250 height + const char* str = ""; + qrcode->SetQrcodeInfo(str); + group->Add(qrcode); + container_->Add(group); + group->LayoutBottomToSibling("UIKitUIQrcodeTestDisplay005", 10); // 10: height + } +} + +void UITestQrcode::UIKitUIQrcodeTestDisplay007() +{ + if (container_ != nullptr) { + UIViewGroup* group = CreateTestCaseGroup("qrcode 250 * 250, str = \" \""); + group->Resize(Screen::GetInstance().GetWidth(), 300); // 300 : height + group->SetViewId("UIKitUIQrcodeTestDisplay007"); + + UIQrcode* qrcode = new UIQrcode(); + qrcode->SetPosition(20, 30, 250, 250); // 20 x 30 y 250 width 250 height + const char* str = " "; + qrcode->SetQrcodeInfo(str); + group->Add(qrcode); + container_->Add(group); + group->LayoutBottomToSibling("UIKitUIQrcodeTestDisplay006", 10); // 10: height + } +} + +void UITestQrcode::UIKitUIQrcodeTestDisplay008() +{ + if (container_ != nullptr) { + UIViewGroup* group = CreateTestCaseGroup("qrcode 250 * 250, str = nullptr"); + group->Resize(Screen::GetInstance().GetWidth(), 300); // 300 : height + group->SetViewId("UIKitUIQrcodeTestDisplay008"); + + UIQrcode* qrcode = new UIQrcode(); + qrcode->SetPosition(20, 30, 250, 250); // 20 x 30 y 250 width 250 height + const char* str = nullptr; + qrcode->SetQrcodeInfo(str); + group->Add(qrcode); + container_->Add(group); + group->LayoutBottomToSibling("UIKitUIQrcodeTestDisplay007", 10); // 10: height + } +} + +void UITestQrcode::UIKitUIQrcodeTestDisplay009() +{ + if (container_ != nullptr) { + UIViewGroup* group = CreateTestCaseGroup("qrcode 250 * 250, position x = -50"); + group->Resize(Screen::GetInstance().GetWidth(), 300); // 300 : height + group->SetViewId("UIKitUIQrcodeTestDisplay009"); + + UIQrcode* qrcode = new UIQrcode(); + qrcode->SetPosition(-50, 30, 250, 250); // -50 x 30 y 250 width 250 height + const char* str = "Hello GUI"; + qrcode->SetQrcodeInfo(str); + group->Add(qrcode); + container_->Add(group); + group->LayoutBottomToSibling("UIKitUIQrcodeTestDisplay008", 10); // 10: height + } +} +} // namespace OHOS \ No newline at end of file diff --git a/test/uitest/test_qrcode/ui_test_qrcode.h b/test/uitest/test_qrcode/ui_test_qrcode.h new file mode 100755 index 0000000..d9d89b9 --- /dev/null +++ b/test/uitest/test_qrcode/ui_test_qrcode.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef UI_TEST_QRCODE_H +#define UI_TEST_QRCODE_H + +#include "components/ui_qrcode.h" +#include "components/ui_scroll_view.h" +#include "ui_test.h" + +namespace OHOS { +class UITestQrcode : public UITest { +public: + UITestQrcode() {} + ~UITestQrcode() {} + void SetUp() override; + void TearDown() override; + const UIView* GetTestView() override; + /** + * @brief Test display UIQrcode + */ + void UIKitUIQrcodeTestDisplay001(); + void UIKitUIQrcodeTestDisplay002(); + void UIKitUIQrcodeTestDisplay003(); + void UIKitUIQrcodeTestDisplay004(); + void UIKitUIQrcodeTestDisplay005(); + void UIKitUIQrcodeTestDisplay006(); + void UIKitUIQrcodeTestDisplay007(); + void UIKitUIQrcodeTestDisplay008(); + void UIKitUIQrcodeTestDisplay009(); +private: + UIScrollView* container_ = nullptr; + UIViewGroup* CreateTestCaseGroup(const char* title) const; +}; +} // namespace OHOS +#endif // UI_TEST_QRCODE_H \ No newline at end of file diff --git a/test/uitest/test_render/ui_test_render.cpp b/test/uitest/test_render/ui_test_render.cpp new file mode 100755 index 0000000..ac145ad --- /dev/null +++ b/test/uitest/test_render/ui_test_render.cpp @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ui_test_render.h" +#include "common/screen.h" + +namespace OHOS { +void UITestRender::SetUp() +{ + if (container_ == nullptr) { + container_ = new UIScrollView(); + container_->Resize(Screen::GetInstance().GetWidth(), Screen::GetInstance().GetHeight() - BACK_BUTTON_HEIGHT); + } +} + +void UITestRender::TearDown() +{ + DeleteChildren(container_); + container_ = nullptr; +} + +UIView* UITestRender::GetTestView() +{ + UIKit_Render_Test_Render_001(); + UIKit_Render_Test_RenderMeasure_001(); + return container_; +} + +class TestRenderClickListener : public UIView::OnClickListener { +public: + bool OnClick(UIView& view, const ClickEvent& event) override + { + static uint8_t r = 20; + static uint8_t g = 120; + static uint8_t b = 50; + view.SetStyle(STYLE_BACKGROUND_COLOR, Color::GetColorFromRGB(r, g, b).full); + view.Invalidate(); + + r = r + 30; // 30: red add + g = g + 30; // 30: green add + b = b + 30; // 30: blue add + return true; + } +}; + +UIViewGroup* UITestRender::CreateTestCaseGroup() const +{ + UIViewGroup* group = new UIViewGroup(); + group->Resize(Screen::GetInstance().GetWidth(), 200); // 200: height + return group; +} + +UILabel* UITestRender::CreateTitleLabel() const +{ + UILabel* label = new UILabel(); + // 216: label width + label->SetPosition(TEXT_DISTANCE_TO_LEFT_SIDE, TEXT_DISTANCE_TO_TOP_SIDE, 216, TITLE_LABEL_DEFAULT_HEIGHT); + label->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + return label; +} + +void UITestRender::UIKit_Render_Test_Render_001() +{ + if (container_ == nullptr) { + return; + } + + UIViewGroup* group = CreateTestCaseGroup(); + group->SetViewId("UIKit_Render_Test_Render_001"); + group->SetPosition(0, 0); + + UILabel* label = CreateTitleLabel(); + group->Add(label); + label->SetText("UIKit绘制效果:"); + + UIView* view = new UIView(); + view->Resize(200, 50); // 200: width 50: height + view->SetPosition(50, 50); // 50: position x 50: position y + view->SetStyle(STYLE_BACKGROUND_COLOR, Color::Yellow().full); + group->Add(view); + view->SetTouchable(true); + view->SetOnClickListener(new TestRenderClickListener()); + + UIView* view1 = new UIView(); + view1->Resize(100, 50); // 100: width 50: height + view1->SetPosition(50, 120); // 50: position x 120: position y + view1->SetStyle(STYLE_BACKGROUND_COLOR, Color::Green().full); + view1->SetTouchable(true); + view1->SetOnClickListener(new TestRenderClickListener()); + group->Add(view1); + container_->Add(group); +} + +void UITestRender::UIKit_Render_Test_RenderMeasure_001() +{ + if (container_ == nullptr) { + return; + } + + UIViewGroup* group = CreateTestCaseGroup(); + group->SetViewId("UIKit_Render_Test_RenderMeasure_001"); + container_->Add(group); + + UILabel* label = CreateTitleLabel(); + group->Add(label); + // 2: half of screen width + label->Resize(Screen::GetInstance().GetWidth() / 2, TITLE_LABEL_DEFAULT_HEIGHT); + label->SetText("UIKit绘制Measure效果:"); + group->Add(label); + + testLabel_ = new UILabel(); + group->Add(testLabel_); + testLabel_->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, VIEW_DISTANCE_TO_TOP_SIDE); + testLabel_->Resize(150, 80); // 150: width 80: height + testLabel_->SetViewId("label_text"); + testLabel_->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + testLabel_->SetLineBreakMode(UILabel::LINE_BREAK_WRAP); + testLabel_->SetText("示例文字,示例文字"); + + labelButton_ = new UILabelButton(); + group->Add(labelButton_); + labelButton_->Resize(BUTTON_WIDHT3, BUTTON_HEIGHT3); + labelButton_->SetText("更新label"); + labelButton_->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + labelButton_->SetStyleForState(STYLE_BORDER_RADIUS, BUTTON_STYLE_BORDER_RADIUS_VALUE, UIButton::RELEASED); + labelButton_->SetStyleForState(STYLE_BORDER_RADIUS, BUTTON_STYLE_BORDER_RADIUS_VALUE, UIButton::PRESSED); + labelButton_->SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::RELEASED); + labelButton_->SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_PRESS, UIButton::PRESSED); + labelButton_->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, VIEW_DISTANCE_TO_TOP_SIDE); + labelButton_->SetOnClickListener(this); + labelButton_->LayoutBottomToSibling("label_text"); // 10: offset + + group->LayoutBottomToSibling("UIKit_Render_Test_Render_001", 10); // 10: offset +} + +bool UITestRender::OnClick(UIView& view, const ClickEvent& event) +{ + if (&view == labelButton_) { + testLabel_->SetFont(DEFAULT_VECTOR_FONT_FILENAME, 15); // 18: font size + } + return true; +} +} // namespace OHOS \ No newline at end of file diff --git a/test/uitest/test_render/ui_test_render.h b/test/uitest/test_render/ui_test_render.h new file mode 100755 index 0000000..94a2e8d --- /dev/null +++ b/test/uitest/test_render/ui_test_render.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef UI_TEST_RENDER_H +#define UI_TEST_RENDER_H + +#include "components/ui_label.h" +#include "components/ui_label_button.h" +#include "components/ui_scroll_view.h" +#include "ui_test.h" + +namespace OHOS { +class UITestRender : public UITest, public UIView::OnClickListener { +public: + UITestRender() : container_(nullptr) {} + ~UITestRender() {} + void SetUp() override; + void TearDown() override; + UIView* GetTestView() override; + bool OnClick(UIView& view, const ClickEvent& event) override; + + /** + * @brief Test render Function + */ + void UIKit_Render_Test_Render_001(); + + /** + * @brief Test render measure Function + */ + void UIKit_Render_Test_RenderMeasure_001(); + +private: + UIViewGroup* CreateTestCaseGroup() const; + UILabel* CreateTitleLabel() const; + UIScrollView* container_ = nullptr; + UILabel* testLabel_ = nullptr; + UILabelButton* labelButton_ = nullptr; +}; +} // namespace OHOS +#endif // UI_TEST_RENDER_H diff --git a/test/uitest/test_rotate_input/ui_test_rotate_input.cpp b/test/uitest/test_rotate_input/ui_test_rotate_input.cpp new file mode 100644 index 0000000..61c8134 --- /dev/null +++ b/test/uitest/test_rotate_input/ui_test_rotate_input.cpp @@ -0,0 +1,361 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ui_test_rotate_input.h" +#include "common/screen.h" +#include "components/ui_label.h" +#include "components/ui_label_button.h" +#include "cstdio" +#include "dock/focus_manager.h" + +#if ENABLE_ROTATE_INPUT +namespace OHOS { +static int16_t g_blank = 20; +static int16_t g_listW = 200; +static int16_t g_listH = 400; +static int16_t g_scrollH = 200; +static int16_t g_scrollW = 400; +static int16_t g_buttonH = 500; +static int16_t g_buttonW = 500; +static int16_t g_smallButtonH = 80; +static int16_t g_smallButtonW = 200; +static int16_t g_swipeH = 200; +static int16_t g_swipeW = 400; +static int16_t g_sliderW = 40; +static int16_t g_sliderH = 300; +static const char* g_pickerRange[] = { "A0", "B1", "C2", "D3", "E4", "F5", "G6", "H7", "I8", "J9", "K10", "L11" }; + +#if ENABLE_MOTOR +void Print(MotorType motorType) +{ + switch (motorType) { + case MotorType::MOTOR_TYPE_ONE: + printf("MOTOR_TYPE_ONE\n"); + break; + case MotorType::MOTOR_TYPE_TWO: + printf("MOTOR_TYPE_TWO\n"); + break; + default: + break; + } +} +#endif + +void UITestRotateInput::SetUp() +{ + if (container_ == nullptr) { + container_ = new UIScrollView(); + container_->SetOnTouchListener(this); + container_->Resize(Screen::GetInstance().GetWidth(), Screen::GetInstance().GetHeight()); + container_->SetThrowDrag(true); + container_->SetRotateFactor(20); // 20 : rotate factor for container + container_->RequestFocus(); + container_->SetReboundSize(50); // 50 : rebound size + } + if (adapterData_ == nullptr) { + adapterData_ = new List(); + adapterData_->PushBack("000"); + adapterData_->PushBack("111"); + adapterData_->PushBack("222"); + adapterData_->PushBack("333"); + adapterData_->PushBack("444"); + adapterData_->PushBack("555"); + adapterData_->PushBack("666"); + adapterData_->PushBack("777"); + adapterData_->PushBack("888"); + adapterData_->PushBack("999"); + adapterData_->PushBack("///"); + adapterData_->PushBack("***"); + adapterData_->PushBack("+++"); + adapterData_->PushBack("---"); + adapterData_->PushBack("..."); + adapterData_->PushBack("@@@"); + adapterData_->PushBack("000"); + adapterData_->PushBack("111"); + adapterData_->PushBack("222"); + adapterData_->PushBack("333"); + adapterData_->PushBack("444"); + adapterData_->PushBack("555"); + adapterData_->PushBack("666"); + adapterData_->PushBack("777"); + adapterData_->PushBack("888"); + adapterData_->PushBack("999"); + adapterData_->PushBack("///"); + adapterData_->PushBack("***"); + adapterData_->PushBack("+++"); + adapterData_->PushBack("---"); + adapterData_->PushBack("..."); + adapterData_->PushBack("@@@"); + } +#if ENABLE_MOTOR + FocusManager::GetInstance()->RegisterMotorFunc(Print); +#endif +} + +void UITestRotateInput::TearDown() +{ + container_->ClearFocus(); + container_->Remove(timePicker_); + container_->Remove(picker_); + DeleteChildren(container_); + container_ = nullptr; + if (adapterData_ != nullptr) { + adapterData_->Clear(); + delete adapterData_; + adapterData_ = nullptr; + } + if (adapter_ != nullptr) { + delete adapter_; + adapter_ = nullptr; + } + lastX_ = 0; + lastY_ = 0; + list_ = nullptr; + scroll_ = nullptr; + swipe_ = nullptr; + timePicker_ = nullptr; + slider_ = nullptr; + picker_ = nullptr; +} + +const UIView* UITestRotateInput::GetTestView() +{ + UIKit_Rotate_Event_List_001(); + UIKit_Rotate_Event_Scroll_View_002(); + UIKit_Rotate_Event_Swipe_View_003(); + UIKit_Rotate_Event_Slider_004(); + UIKit_Rotate_Event_Time_Picker_005(); + UIKit_Rotate_Event_Picker_006(); + return container_; +} + +void UITestRotateInput::UIKit_Rotate_Event_List_001() +{ + if (container_ == nullptr) { + return; + } + UILabel* label = GetTitleLabel("UIList Test"); + container_->Add(label); + label->SetPosition(0, lastY_ + g_blank); + if (adapter_ == nullptr) { + adapter_ = new TextAdapter(); + } + adapter_->SetData(adapterData_); + if (list_ == nullptr) { + list_ = new UIList(UIList::VERTICAL); + } + list_->SetStyle(STYLE_BACKGROUND_COLOR, Color::Black().full); + list_->SetPosition(0, label->GetY() + g_blank, g_listW, g_listH - 200); // 200: mean list reduce height + list_->SetStartIndex(6); // 6 : list start index + list_->SetAdapter(adapter_); + list_->SetOnTouchListener(this); + list_->SetThrowDrag(true); + list_->SetRotateFactor(10); // 10 : rotate factor + list_->SetReboundSize(50); // 50 : rebound size + container_->Add(list_); + SetLastPos(list_); +} + +void UITestRotateInput::UIKit_Rotate_Event_Scroll_View_002() +{ + if (container_ == nullptr) { + return; + } + UILabel* label = GetTitleLabel("UIScrollView Test"); + container_->Add(label); + label->SetPosition(0, lastY_ + g_blank); + if (scroll_ == nullptr) { + scroll_ = new UIScrollView(); + } + scroll_->SetStyle(STYLE_BACKGROUND_COLOR, Color::Black().full); + scroll_->SetPosition(0, label->GetY() + g_blank, g_scrollW, g_scrollH); + scroll_->SetXScrollBarVisible(true); + scroll_->SetYScrollBarVisible(true); + scroll_->SetRotateFactor(10); // 10 : rotateFactor + UIButton* btn = new UIButton(); + btn->SetPosition(0, 0, g_buttonW, g_buttonH); + scroll_->Add(btn); + scroll_->SetThrowDrag(true); + scroll_->SetOnTouchListener(this); + scroll_->SetReboundSize(50); // 50 : rebound size + SetLastPos(scroll_); + container_->Add(scroll_); +} + +void UITestRotateInput::UIKit_Rotate_Event_Swipe_View_003() +{ + if (container_ == nullptr) { + return; + } + UILabel* label = GetTitleLabel("UISwipeView Test"); + container_->Add(label); + label->SetPosition(0, lastY_ + g_blank); + if (swipe_ == nullptr) { + swipe_ = new UISwipeView(UISwipeView::HORIZONTAL); + } + swipe_->SetStyle(STYLE_BACKGROUND_COLOR, Color::Black().full); + swipe_->SetPosition(0, label->GetY() + g_blank, g_swipeW, g_swipeH); + swipe_->SetBlankSize(100); // 100 is blank size + UILabelButton* btn1 = new UILabelButton(); + UILabelButton* btn2 = new UILabelButton(); + UILabelButton* btn3 = new UILabelButton(); + UILabelButton* btn4 = new UILabelButton(); + UILabelButton* btn5 = new UILabelButton(); + UILabelButton* btn6 = new UILabelButton(); + UILabelButton* btn7 = new UILabelButton(); + btn1->SetPosition(0, 0, g_smallButtonW, g_smallButtonH); + btn2->SetPosition(0, 0, g_smallButtonW, g_smallButtonH); + btn3->SetPosition(0, 0, g_smallButtonW, g_smallButtonH); + btn4->SetPosition(0, 0, g_smallButtonW, g_smallButtonH); + btn5->SetPosition(0, 0, g_smallButtonW, g_smallButtonH); + btn6->SetPosition(0, 0, g_smallButtonW, g_smallButtonH); + btn7->SetPosition(0, 0, g_smallButtonW, g_smallButtonH); + btn1->SetText("Button 1"); + btn2->SetText("Button 2"); + btn3->SetText("Button 3"); + btn4->SetText("Button 4"); + btn5->SetText("Button 5"); + btn6->SetText("Button 6"); + btn7->SetText("Button 7"); + swipe_->Add(btn1); + swipe_->Add(btn2); + swipe_->Add(btn3); + swipe_->Add(btn4); + swipe_->Add(btn5); + swipe_->Add(btn6); + swipe_->Add(btn7); + swipe_->SetOnTouchListener(this); + swipe_->SetRotateFactor(30); // 30 : rotate factor + container_->Add(swipe_); + SetLastPos(swipe_); +} + +void UITestRotateInput::UIKit_Rotate_Event_Slider_004() +{ + if (container_ == nullptr) { + return; + } + UILabel* label = GetTitleLabel("UISlider Test"); + container_->Add(label); + label->SetPosition(0, lastY_ + g_blank); + if (slider_ == nullptr) { + slider_ = new UISlider(); + } + slider_->SetPosition(50, label->GetY() + g_blank, g_sliderW, g_sliderH); // 50: position x + slider_->SetValidHeight(200); // 200: valid height + slider_->SetValue(0); // 0: progress bar current value + slider_->SetDirection(UISlider::Direction::DIR_BOTTOM_TO_TOP); + slider_->SetStyle(STYLE_BACKGROUND_COLOR, Color::Gray().full); + slider_->SetOnTouchListener(this); + container_->Add(slider_); + SetLastPos(slider_); +} +void UITestRotateInput::UIKit_Rotate_Event_Time_Picker_005() +{ + if (container_ == nullptr) { + return; + } + UILabel* label = GetTitleLabel("UITimePicker Test"); + container_->Add(label); + label->SetPosition(0, lastY_ + g_blank); + if (timePicker_ == nullptr) { + timePicker_ = new UITimePicker(); + } + timePicker_->EnableSecond(true); + timePicker_->SetPosition(0, label->GetY() + g_blank, g_listW, g_listH); + timePicker_->SetItemHeight(50); // 50 height + + UIView* hour = timePicker_->GetChildById("hour"); + if (hour == nullptr) { + return; + } + hour->SetOnTouchListener(this); + + UIView* minute = timePicker_->GetChildById("minute"); + if (minute == nullptr) { + return; + } + minute->SetOnTouchListener(this); + + UIView* second = timePicker_->GetChildById("second"); + if (second == nullptr) { + return; + } + second->SetOnTouchListener(this); + + container_->Add(timePicker_); + SetLastPos(timePicker_); +} + +void UITestRotateInput::UIKit_Rotate_Event_Picker_006() +{ + if (container_ == nullptr) { + return; + } + UILabel* label = GetTitleLabel("UIPicker Test"); + container_->Add(label); + label->SetPosition(0, lastY_ + g_blank); + if (picker_ == nullptr) { + picker_ = new UIPicker(); + } + picker_->SetPosition(0, label->GetY() + g_blank, g_listW, g_listH); + picker_->SetItemHeight(50); // 50: height + picker_->SetValues(g_pickerRange, sizeof(g_pickerRange) / sizeof(g_pickerRange[0])); + picker_->SetOnTouchListener(this); + container_->Add(picker_); + SetLastPos(picker_); +} +bool UITestRotateInput::OnPress(UIView& view, const PressEvent& event) +{ + if (&view == container_) { + container_->RequestFocus(); + return true; + } else if (&view == list_) { + list_->RequestFocus(); + return true; + } else if (&view == scroll_) { + scroll_->RequestFocus(); + return true; + } else if (&view == swipe_) { + swipe_->RequestFocus(); + return true; + } else if (&view == slider_) { + slider_->RequestFocus(); + return true; + } else if (&view == timePicker_->GetChildById(UITimePicker::HOUR_LIST_NAME)) { + view.RequestFocus(); + return true; + } else if (&view == timePicker_->GetChildById(UITimePicker::MIN_LIST_NAME)) { + view.RequestFocus(); + return true; + } else if (&view == timePicker_->GetChildById(UITimePicker::SEC_LIST_NAME)) { + view.RequestFocus(); + return true; + } else if (&view == picker_) { + picker_->RequestFocus(); + return true; + } + return false; +} +void UITestRotateInput::SetLastPos(UIView* view) +{ + if (view == nullptr) { + return; + } + lastX_ = view->GetX(); + lastY_ = view->GetY() + view->GetHeight(); +} +} // namespace OHOS +#endif diff --git a/test/uitest/test_rotate_input/ui_test_rotate_input.h b/test/uitest/test_rotate_input/ui_test_rotate_input.h new file mode 100644 index 0000000..38e348a --- /dev/null +++ b/test/uitest/test_rotate_input/ui_test_rotate_input.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef UI_TEST_ROTATE_INPUT_H +#define UI_TEST_ROTATE_INPUT_H + +#include "components/ui_list.h" +#include "components/ui_scroll_view.h" +#include "components/ui_slider.h" +#include "components/ui_swipe_view.h" +#include "components/ui_time_picker.h" +#include "ui_test.h" + +#if ENABLE_ROTATE_INPUT +namespace OHOS { +class UITestRotateInput : public UITest, UIView::OnTouchListener { +public: + UITestRotateInput() {} + ~UITestRotateInput() {} + void SetUp() override; + void TearDown() override; + const UIView* GetTestView() override; + void UIKit_Rotate_Event_List_001(); + void UIKit_Rotate_Event_Scroll_View_002(); + void UIKit_Rotate_Event_Swipe_View_003(); + void UIKit_Rotate_Event_Slider_004(); + void UIKit_Rotate_Event_Time_Picker_005(); + void UIKit_Rotate_Event_Picker_006(); + bool OnPress(UIView& view, const PressEvent& event) override; + +private: + void SetLastPos(UIView* view); + UIScrollView* container_ = nullptr; + UIList* list_ = nullptr; + UIScrollView* scroll_ = nullptr; + UISwipeView* swipe_ = nullptr; + UITimePicker* timePicker_ = nullptr; + UISlider* slider_ = nullptr; + List* adapterData_ = nullptr; + TextAdapter* adapter_ = nullptr; + int16_t lastX_ = 0; + int16_t lastY_ = 0; + UIPicker* picker_ = nullptr; +}; +} // namespace OHOS +#endif // UI_TEST_ROTATE_INPUT_H +#endif diff --git a/test/uitest/test_screenshot/ui_test_screenshot.cpp b/test/uitest/test_screenshot/ui_test_screenshot.cpp new file mode 100755 index 0000000..a2d87dd --- /dev/null +++ b/test/uitest/test_screenshot/ui_test_screenshot.cpp @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ui_test_screenshot.h" +#if ENABLE_DEBUG +#include "common/screen.h" +#include "components/ui_image_view.h" +#include "imgdecode/cache_manager.h" +#include "test_resource_config.h" + +namespace OHOS { +void UITestScreenshot::SetUp() +{ + if (container_ == nullptr) { + container_ = new UIScrollView(); + container_->Resize(Screen::GetInstance().GetWidth(), Screen::GetInstance().GetHeight() - BACK_BUTTON_HEIGHT); + container_->SetStyle(STYLE_BACKGROUND_COLOR, Color::Gray().full); + } +} + +void UITestScreenshot::TearDown() +{ + DeleteChildren(container_); + container_ = nullptr; +} + +UIView* UITestScreenshot::GetTestView() +{ + UIKit_SCREENSHOT_TEST_001(); + return container_; +} + +class TestScreenshotOnClickListener : public UIView::OnClickListener { +public: + TestScreenshotOnClickListener(UIView* uiView) : uiView_(uiView) {} + ~TestScreenshotOnClickListener() {} + bool OnClick(UIView& view, const ClickEvent& event) override + { + UIScreenshot::GetInstance()->ScreenshotToFile("/storage/data/screenshot.bin"); + uiView_->Invalidate(); + return true; + } + +private: + UIView* uiView_; +}; + +class TestSetScreenshotOnClickListener : public UIView::OnClickListener { +public: + TestSetScreenshotOnClickListener(UIView* uiView) : uiView_(uiView) {} + ~TestSetScreenshotOnClickListener() {} + bool OnClick(UIView& view, const ClickEvent& event) override + { + static_cast(uiView_)->SetSrc("/storage/data/screenshot.bin"); + uiView_->Invalidate(); + return true; + } + +private: + UIView* uiView_; +}; + +void UITestScreenshot::UIKit_SCREENSHOT_TEST_001() +{ + const int16_t titleLableHeight = 29; + const int16_t labelButtonWidth = 80; + const int16_t labelButtonHeight = 40; + if (container_ == nullptr) { + return; + } + UILabel* label = GetTitleLabel("截屏功能测试"); + container_->Add(label); + label->SetPosition(TEXT_DISTANCE_TO_LEFT_SIDE, TEXT_DISTANCE_TO_TOP_SIDE, + Screen::GetInstance().GetWidth() - TEXT_DISTANCE_TO_LEFT_SIDE, titleLableHeight); + UILabelButton* labelButton = SetUpButton("截屏"); + container_->Add(labelButton); + labelButton->SetPosition(210, 50); // 210: position x; 50: position y + labelButton->SetWidth(labelButtonWidth); + labelButton->SetHeight(labelButtonHeight); + labelButton->SetFont(DEFAULT_VECTOR_FONT_FILENAME, BUTTON_LABEL_SIZE); + UIView::OnClickListener* clickLeftListener = new TestScreenshotOnClickListener((UIView*)labelButton); + labelButton->SetOnClickListener(clickLeftListener); + UILabel* label2 = GetTitleLabel("显示截屏图片 "); + container_->Add(label2); + label2->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, 255, // 255: y-coordinate + Screen::GetInstance().GetWidth() - TEXT_DISTANCE_TO_LEFT_SIDE, titleLableHeight); + UIImageView* imageView = new UIImageView(); + imageView->SetAutoEnable(false); + imageView->SetPosition(0, 290); // 290: y-coordinate + imageView->SetWidth(Screen::GetInstance().GetWidth()); + imageView->SetHeight(Screen::GetInstance().GetHeight() + 100); // 100: increase height + imageView->SetSrc(BLUE_RGB565_IMAGE_PATH); + Style style; + CacheEntry entry; + RetCode ret = CacheManager::GetInstance().Open(imageView->GetPath(), style, entry); + if (ret != RetCode::OK) { + return; + } + container_->Add(imageView); + UILabelButton* labelButton2 = SetUpButton("显示"); + container_->Add(labelButton2); + labelButton2->SetPosition(300, 50); // 300: position x; 50: position y + labelButton2->SetWidth(labelButtonWidth); + labelButton2->SetHeight(labelButtonHeight); + UIView::OnClickListener* clickRightListener = new TestSetScreenshotOnClickListener(static_cast(imageView)); + labelButton2->SetOnClickListener(clickRightListener); +} +} // namespace OHOS +#endif \ No newline at end of file diff --git a/test/uitest/test_screenshot/ui_test_screenshot.h b/test/uitest/test_screenshot/ui_test_screenshot.h new file mode 100755 index 0000000..4934652 --- /dev/null +++ b/test/uitest/test_screenshot/ui_test_screenshot.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef UI_TEST_SCREENSHOT_H +#define UI_TEST_SCREENSHOT_H + +#include "graphic_config.h" +#if ENABLE_DEBUG +#include "components/ui_scroll_view.h" +#include "components/ui_label.h" +#include "components/ui_label_button.h" +#include "dfx/ui_screenshot.h" +#include "ui_test.h" + +namespace OHOS { +class UITestScreenshot : public UITest { +public: + UITestScreenshot() {} + ~UITestScreenshot() {} + void SetUp() override; + void TearDown() override; + UIView* GetTestView() override; + + /** + * @brief Test screenshot Function + */ + void UIKit_SCREENSHOT_TEST_001(); + +private: + UIScrollView* container_ = nullptr; + + UILabelButton* SetUpButton(const char* title) + { + UILabelButton* btn = new UILabelButton(); + btn->SetText(title); + btn->SetFont(DEFAULT_VECTOR_FONT_FILENAME, BUTTON_LABEL_SIZE); + btn->SetStyleForState(STYLE_BORDER_RADIUS, BUTTON_STYLE_BORDER_RADIUS_VALUE, UIButton::RELEASED); + btn->SetStyleForState(STYLE_BORDER_RADIUS, BUTTON_STYLE_BORDER_RADIUS_VALUE, UIButton::PRESSED); + btn->SetStyleForState(STYLE_BORDER_RADIUS, BUTTON_STYLE_BORDER_RADIUS_VALUE, UIButton::INACTIVE); + btn->SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::RELEASED); + btn->SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::PRESSED); + btn->SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::INACTIVE); + return btn; + } +}; // namespace OHOS +} +#endif // ENABLE_DEBUG +#endif // UI_TEST_SCREENSHOT_H \ No newline at end of file diff --git a/test/uitest/test_slider/ui_test_slider.cpp b/test/uitest/test_slider/ui_test_slider.cpp new file mode 100755 index 0000000..06db563 --- /dev/null +++ b/test/uitest/test_slider/ui_test_slider.cpp @@ -0,0 +1,549 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ui_test_slider.h" +#include "common/screen.h" +#include "test_resource_config.h" + +namespace OHOS { +namespace { +const int16_t LABEL_HEIGHT = 29; +const int16_t BUTTON_WIDTH = 120; +const int16_t BUTTON_HEIGHT = 40; +const int16_t DELTA_X_COORDINATE = 8; +const int16_t DELTA_Y_COORDINATE = 12; +const int16_t DELTA_Y_COORDINATE_2 = 19; +static bool g_onChange = false; +static bool g_onRelease = false; +static bool g_onClick = false; +static bool g_onDrag = false; +static uint16_t g_progress = 20; +static uint16_t g_step = 0; +static uint16_t g_width = 50; +static uint16_t g_height = 250; +static int16_t g_min = 0; +static int16_t g_max = 100; +static int16_t g_knobWidth = 50; +} // namespace + +void TestUISliderEventListener::OnChange(int32_t progress) +{ + value_ = progress; + if (label_ == nullptr) { + if (!MallocLabel()) { + return; + } + } + if (g_onChange) { + label_->SetText("OnChange"); + } +} + +void TestUISliderEventListener::OnRelease(int32_t progress) +{ + value_ = progress; + if (label_ == nullptr) { + if (!MallocLabel()) { + return; + } + } + if (g_onRelease) { + label_->SetText("OnRelease"); + } +} + +bool TestUISliderEventListener::OnClick(UIView& view, const ClickEvent& event) +{ + if (label_ == nullptr) { + if (!MallocLabel()) { + return false; + } + } + if (g_onClick) { + label_->SetText("OnClick"); + } + return true; +} + +bool TestUISliderEventListener::MallocLabel() +{ + label_ = new UILabel(); + if (label_ == nullptr) { + return false; + } + viewGroup_->Add(label_); + label_->SetPosition(0, 0, 100, 30); // 100: width; 30: height + label_->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + label_->SetStyle(STYLE_TEXT_COLOR, Color::Black().full); + label_->SetStyle(STYLE_BACKGROUND_COLOR, Color::White().full); + label_->SetStyle(STYLE_BACKGROUND_OPA, OPA_OPAQUE); + return true; +} + +bool TestUISliderEventListener::OnDrag(UIView& view, const DragEvent& event) +{ + if (label_ == nullptr) { + if (!MallocLabel()) { + return false; + } + } + + if (g_onDrag) { + label_->SetText("OnDrag"); + } + return true; +} + +void UITestSlider::SetUp() +{ + if (container_ == nullptr) { + container_ = new UIScrollView(); + container_->Resize(Screen::GetInstance().GetWidth(), Screen::GetInstance().GetHeight() - BACK_BUTTON_HEIGHT); + positionX_ = 0; + positionY_ = 0; + UIViewGroup* uiViewGroup = new UIViewGroup(); + uiViewGroup->SetPosition(positionX_, positionY_, 320, 390); // 320: width; 390: height + container_->Add(uiViewGroup); + UILabel* label = new UILabel(); + uiViewGroup->Add(label); + // 288: width; 48: height + label->SetPosition(TEXT_DISTANCE_TO_LEFT_SIDE, TEXT_DISTANCE_TO_TOP_SIDE, 288, 48); + label->SetText("UISlider效果"); + label->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + uiViewGroupFrame_ = new UIViewGroup(); + uiViewGroup->Add(uiViewGroupFrame_); + // 288: width; 336: height + uiViewGroupFrame_->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE2, VIEW_DISTANCE_TO_TOP_SIDE, 288, 336); + uiViewGroupFrame_->SetStyle(STYLE_BORDER_COLOR, Color::White().full); + uiViewGroupFrame_->SetStyle(STYLE_BORDER_OPA, HALF_OPA_OPAQUE); + uiViewGroupFrame_->SetStyle(STYLE_BORDER_WIDTH, VIEW_STYLE_BORDER_WIDTH); + uiViewGroupFrame_->SetStyle(STYLE_BORDER_RADIUS, VIEW_STYLE_BORDER_RADIUS); + uiViewGroupFrame_->SetStyle(STYLE_BACKGROUND_OPA, 0); + + slider_ = new UISlider(); + slider_->SetPosition(10, 10, 50, 300); // 10:position x; 10: position y; 50: width; 300: height + slider_->SetValidHeight(250); // 250: valid height; + slider_->SetValue(20); // 20: progress bar current value + slider_->SetDirection(UISlider::Direction::DIR_BOTTOM_TO_TOP); + listener_ = new TestUISliderEventListener(uiViewGroupFrame_); + slider_->SetSliderEventListener(listener_); + slider_->SetOnClickListener(listener_); + slider_->SetOnDragListener(listener_); + uiViewGroupFrame_->Add(slider_); + slider_->LayoutCenterOfParent(); + } + + if (scroll_ == nullptr) { + scroll_ = new UIScrollView(); + scroll_->SetPosition(336, 11); // 336: x-coordinate; 11: y-coordinate + scroll_->Resize(624, Screen::GetInstance().GetHeight() - 70); // 624: width; 70: decrease height + container_->Add(scroll_); + positionY_ = 0; + } +} + +void UITestSlider::TearDown() +{ + DeleteChildren(container_); + container_ = nullptr; + slider_ = nullptr; + scroll_ = nullptr; + delete listener_; + listener_ = nullptr; + uiViewGroupFrame_ = nullptr; +} + +UIView* UITestSlider::GetTestView() +{ + UIKit_Slider_Test_UISlider_001(); + UIKit_Slider_Test_SetRange_002(); + UIKit_Slider_Test_SetValue_003(); + UIKit_Slider_Test_SetImage_004(); + UIKit_Slider_Test_SetStyle_005(); + UIKit_Slider_Test_GetStyle_006(); + UIKit_Slider_Test_SetKnobWidth_007(); + UIKit_Slider_Test_SetStep_008(); + UIKit_Slider_Test_SetColor_009(); + UIKit_Slider_Test_SetDirection_010(); + UIKit_Slider_Test_SetValidSize_011(); + UIKit_Slider_Test_SetRadius_012(); + UIKit_Slider_Test_SetOnChangeListener_013(); + UIKit_Slider_Test_SetOnReleaseListener_014(); + UIKit_Slider_Test_SetOnClickCallback_015(); + UIKit_Slider_Test_SetOnDragCallback_016(); + + return container_; +} + +void UITestSlider::SetUpLabel(const char* title, int16_t x, int16_t y) const +{ + UILabel* label = new UILabel(); + scroll_->Add(label); + // 288: width + label->SetPosition(x, y, 288, LABEL_HEIGHT); + label->SetText(title); + label->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); +} +void UITestSlider::SetUpButton(UILabelButton* btn, const char* title, int16_t x, int16_t y) +{ + if (btn == nullptr) { + return; + } + scroll_->Add(btn); + btn->SetPosition(x, y, BUTTON_WIDHT2, BUTTON_HEIGHT2); + btn->SetText(title); + btn->SetFont(DEFAULT_VECTOR_FONT_FILENAME, BUTTON_LABEL_SIZE); + btn->SetOnClickListener(this); + btn->SetStyleForState(STYLE_BORDER_RADIUS, BUTTON_STYLE_BORDER_RADIUS_VALUE, UIButton::RELEASED); + btn->SetStyleForState(STYLE_BORDER_RADIUS, BUTTON_STYLE_BORDER_RADIUS_VALUE, UIButton::PRESSED); + btn->SetStyleForState(STYLE_BORDER_RADIUS, BUTTON_STYLE_BORDER_RADIUS_VALUE, UIButton::INACTIVE); + btn->SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::RELEASED); + btn->SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::PRESSED); + btn->SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::INACTIVE); + scroll_->Invalidate(); +} + +void UITestSlider::UIKit_Slider_Test_UISlider_001() +{ + resetBtn_ = new UILabelButton(); + positionX_ = 24; // 24: x-coordinate + positionY_ = 0; + SetUpLabel("重置Slider:", positionX_, positionY_); + positionY_ += LABEL_HEIGHT + DELTA_X_COORDINATE; + SetUpButton(resetBtn_, "重置", positionX_, positionY_); +} + +void UITestSlider::UIKit_Slider_Test_SetRange_002() +{ + incMinProgressBtn_ = new UILabelButton(); + decMinProgressBtn_ = new UILabelButton(); + incMaxProgressBtn_ = new UILabelButton(); + decMaxProgressBtn_ = new UILabelButton(); + positionX_ = 24; // 24: x-coordinate + positionY_ += BUTTON_HEIGHT + DELTA_Y_COORDINATE_2; + SetUpLabel("设置Slider范围:", positionX_, positionY_); + positionY_ += LABEL_HEIGHT + DELTA_X_COORDINATE; + SetUpButton(incMinProgressBtn_, "下限+", positionX_, positionY_); + positionX_ += BUTTON_WIDTH + DELTA_Y_COORDINATE; + SetUpButton(decMinProgressBtn_, "下限-", positionX_, positionY_); + positionX_ = 24; // 24: x-coordinate + positionY_ += BUTTON_HEIGHT + DELTA_Y_COORDINATE; + SetUpButton(incMaxProgressBtn_, "上限+", positionX_, positionY_); + positionX_ += BUTTON_WIDTH + DELTA_Y_COORDINATE; + SetUpButton(decMaxProgressBtn_, "上限-", positionX_, positionY_); +} + +void UITestSlider::UIKit_Slider_Test_SetValue_003() +{ + incProgressBtn_ = new UILabelButton(); + decProgressBtn_ = new UILabelButton(); + positionX_ = 24; // 24: x-coordinate + positionY_ += BUTTON_HEIGHT + DELTA_Y_COORDINATE_2; + SetUpLabel("设置Slider当前值:", positionX_, positionY_); + positionY_ += LABEL_HEIGHT + DELTA_X_COORDINATE; + SetUpButton(incProgressBtn_, "进度+", positionX_, positionY_); + positionX_ += BUTTON_WIDTH + DELTA_Y_COORDINATE; + SetUpButton(decProgressBtn_, "进度-", positionX_, positionY_); +} + +void UITestSlider::UIKit_Slider_Test_SetImage_004() +{ + imageBtn_ = new UILabelButton(); + noImageBtn_ = new UILabelButton(); + positionX_ = 24; // 24: x-coordinate + positionY_ += BUTTON_HEIGHT + DELTA_Y_COORDINATE_2; + SetUpLabel("Slider设置图片:", positionX_, positionY_); + positionY_ += LABEL_HEIGHT + DELTA_X_COORDINATE; + SetUpButton(imageBtn_, "设置图片", positionX_, positionY_); + positionX_ += BUTTON_WIDTH + DELTA_Y_COORDINATE; + SetUpButton(noImageBtn_, "取消图片", positionX_, positionY_); +} + +void UITestSlider::UIKit_Slider_Test_SetStyle_005() +{ + setStyleBtn_ = new UILabelButton(); + positionX_ = 24; // 24: x-coordinate + positionY_ += BUTTON_HEIGHT + DELTA_Y_COORDINATE_2; + SetUpLabel("Slider设置样式:", positionX_, positionY_); + positionY_ += LABEL_HEIGHT + DELTA_X_COORDINATE; + SetUpButton(setStyleBtn_, "设置样式", positionX_, positionY_); +} + +void UITestSlider::UIKit_Slider_Test_GetStyle_006() +{ + getStyleBtn_ = new UILabelButton(); + positionX_ = 24; // 24: x-coordinate + positionY_ += BUTTON_HEIGHT + DELTA_Y_COORDINATE_2; + SetUpLabel("Slider设置样式:", positionX_, positionY_); + positionY_ += LABEL_HEIGHT + DELTA_X_COORDINATE; + SetUpButton(getStyleBtn_, "设置样式", positionX_, positionY_); +} + +void UITestSlider::UIKit_Slider_Test_SetKnobWidth_007() +{ + incKnobWidthBtn_ = new UILabelButton(); + decKnobWidthBtn_ = new UILabelButton(); + positionX_ = 24; // 24: x-coordinate + positionY_ += BUTTON_HEIGHT + DELTA_Y_COORDINATE_2; + SetUpLabel("设置Slider滑块宽度:", positionX_, positionY_); + positionY_ += LABEL_HEIGHT + DELTA_X_COORDINATE; + SetUpButton(incKnobWidthBtn_, "滑块宽度+", positionX_, positionY_); + positionX_ += BUTTON_WIDTH + DELTA_Y_COORDINATE; + SetUpButton(decKnobWidthBtn_, "滑块宽度-", positionX_, positionY_); +} + +void UITestSlider::UIKit_Slider_Test_SetStep_008() +{ + stepBtn_ = new UILabelButton(); + positionX_ = 24; // 24: x-coordinate + positionY_ += BUTTON_HEIGHT + DELTA_Y_COORDINATE_2; + SetUpLabel("设置Slider步长:", positionX_, positionY_); + positionY_ += LABEL_HEIGHT + DELTA_X_COORDINATE; + SetUpButton(stepBtn_, "步长+", positionX_, positionY_); +} + +void UITestSlider::UIKit_Slider_Test_SetColor_009() +{ + colorBtn_ = new UILabelButton(); + positionX_ = 336; // 336: x-coordinate + positionY_ = 0; + SetUpLabel("设置Slider颜色:", positionX_, positionY_); + positionY_ += LABEL_HEIGHT + DELTA_X_COORDINATE; + SetUpButton(colorBtn_, "设置颜色", positionX_, positionY_); +} + +void UITestSlider::UIKit_Slider_Test_SetDirection_010() +{ + dirL2RBtn_ = new UILabelButton(); + dirR2LBtn_ = new UILabelButton(); + dirT2BBtn_ = new UILabelButton(); + dirB2TBtn_ = new UILabelButton(); + positionX_ = 336; // 336: x-coordinate + positionY_ += BUTTON_HEIGHT + DELTA_Y_COORDINATE_2; + SetUpLabel("设置Slider方向:", positionX_, positionY_); + positionY_ += LABEL_HEIGHT + DELTA_X_COORDINATE; + SetUpButton(dirL2RBtn_, "从左到右", positionX_, positionY_); + positionX_ += BUTTON_WIDTH + DELTA_Y_COORDINATE; + SetUpButton(dirR2LBtn_, "从右到左", positionX_, positionY_); + positionX_ = 336; // 336: x-coordinate + positionY_ += BUTTON_HEIGHT + DELTA_Y_COORDINATE; + SetUpButton(dirT2BBtn_, "从上到下", positionX_, positionY_); + positionX_ += BUTTON_WIDTH + DELTA_Y_COORDINATE; + SetUpButton(dirB2TBtn_, "从下到上", positionX_, positionY_); +} + +void UITestSlider::UIKit_Slider_Test_SetValidSize_011() +{ + widthBtn_ = new UILabelButton(); + heightBtn_ = new UILabelButton(); + positionX_ = 336; // 336: x-coordinate + positionY_ += BUTTON_HEIGHT + DELTA_Y_COORDINATE_2; + SetUpLabel("设置Slider有效区域宽高:", positionX_, positionY_); + positionY_ += LABEL_HEIGHT + DELTA_X_COORDINATE; + SetUpButton(heightBtn_, "box高度 ", positionX_, positionY_); + positionX_ += BUTTON_WIDTH + DELTA_Y_COORDINATE; + SetUpButton(widthBtn_, "box宽度 ", positionX_, positionY_); +} + +void UITestSlider::UIKit_Slider_Test_SetRadius_012() +{ + radiusBtn_ = new UILabelButton(); + positionX_ = 336; // 336: x-coordinate + positionY_ += BUTTON_HEIGHT + DELTA_Y_COORDINATE_2; + SetUpLabel("设置lider圆角弧度:", positionX_, positionY_); + positionY_ += LABEL_HEIGHT + DELTA_X_COORDINATE; + SetUpButton(radiusBtn_, "圆角弧度", positionX_, positionY_); +} + +void UITestSlider::UIKit_Slider_Test_SetOnChangeListener_013() +{ + onChangeBtn_ = new UILabelButton(); + positionX_ = 336; // 336: x-coordinate + positionY_ += BUTTON_HEIGHT + DELTA_Y_COORDINATE_2; + SetUpLabel("Slider设置点击和滑动监听: ", positionX_, positionY_); + positionY_ += LABEL_HEIGHT + DELTA_X_COORDINATE; + SetUpButton(onChangeBtn_, "OnChange", positionX_, positionY_); +} + +void UITestSlider::UIKit_Slider_Test_SetOnReleaseListener_014() +{ + onReleaseBtn_ = new UILabelButton(); + positionX_ = 336; // 336: x-coordinate + positionY_ += BUTTON_HEIGHT + DELTA_Y_COORDINATE_2; + SetUpLabel("Slider设置滑动结束监听:", positionX_, positionY_); + positionY_ += LABEL_HEIGHT + DELTA_X_COORDINATE; + SetUpButton(onReleaseBtn_, "OnRelease", positionX_, positionY_); +} + +void UITestSlider::UIKit_Slider_Test_SetOnClickCallback_015() +{ + onClickBtn_ = new UILabelButton(); + positionX_ = 336; // 336: x-coordinate + positionY_ += BUTTON_HEIGHT + DELTA_Y_COORDINATE_2; + SetUpLabel("Slider设置点击事件回调:", positionX_, positionY_); + positionY_ += LABEL_HEIGHT + DELTA_X_COORDINATE; + SetUpButton(onClickBtn_, "OnClick回调", positionX_, positionY_); +} + +void UITestSlider::UIKit_Slider_Test_SetOnDragCallback_016() +{ + onDragBtn_ = new UILabelButton(); + positionX_ = 336; // 336: x-coordinate + positionY_ += BUTTON_HEIGHT + DELTA_Y_COORDINATE_2; + SetUpLabel("Slider设置滑动事件回调:", positionX_, positionY_); + positionY_ += LABEL_HEIGHT + DELTA_X_COORDINATE; + SetUpButton(onDragBtn_, "OnDrag回调", positionX_, positionY_); +} +bool UITestSlider::OnClick(UIView& view, const ClickEvent& event) +{ + g_progress = 20; // 20: progress + g_step = 0; + g_width = 50; // 50: width + g_height = 250; // 250: height + g_min = 0; + g_max = 100; // 100: max + g_knobWidth = 50; // 50: knobWidth + if (&view == resetBtn_) { + slider_->LayoutCenterOfParent(); + g_progress = 20; // 20: progress + g_step = 0; + g_width = 50; // 50: width + g_height = 250; // 250: height + g_knobWidth = 50; // 50: knobWidth + g_min = 0; + g_max = 100; // 100: max + slider_->SetDirection(UISlider::Direction::DIR_BOTTOM_TO_TOP); + slider_->SetValidHeight(g_height); + slider_->SetValidWidth(g_width); + slider_->SetKnobWidth(50); // 50: knob width + slider_->SetRange(g_max, g_min); + slider_->SetValue(g_progress); + slider_->SetStep(g_step); + slider_->SetBackgroundStyle(StyleDefault::GetProgressBackgroundStyle()); + slider_->SetForegroundStyle(StyleDefault::GetProgressForegroundStyle()); + slider_->SetKnobStyle(StyleDefault::GetSliderKnobStyle()); + slider_->SetImage(static_cast(nullptr), static_cast(nullptr), + static_cast(nullptr)); + slider_->EnableBackground(true); + } else if (&view == incProgressBtn_) { + g_progress++; + slider_->SetValue(g_progress); + } else if (&view == decProgressBtn_) { + g_progress--; + slider_->SetValue(g_progress); + } else if (&view == stepBtn_) { + g_step++; + slider_->SetStep(g_step); + } else { + ExpandClick1(view, event); + } + container_->Invalidate(); + return true; +} + +bool UITestSlider::ExpandClick1(UIView& view, const ClickEvent& event) +{ + if (&view == widthBtn_) { + g_width--; + slider_->SetValidWidth(g_width); + } else if (&view == heightBtn_) { + g_height--; + slider_->SetValidHeight(g_height); + } else if (&view == incMinProgressBtn_) { + g_min++; + slider_->SetRange(slider_->GetRangeMax(), g_min); + } else if (&view == decMinProgressBtn_) { + g_min--; + slider_->SetRange(slider_->GetRangeMax(), g_min); + } else if (&view == incMaxProgressBtn_) { + g_max++; + slider_->SetRange(g_max, slider_->GetRangeMin()); + } else if (&view == decMaxProgressBtn_) { + g_max--; + slider_->SetRange(g_max, slider_->GetRangeMin()); + } else if (&view == dirL2RBtn_) { + g_width = DEFAULT_HEIGHT; + g_height = DEFAULT_WIDTH; + slider_->Resize(g_width, g_height); + slider_->SetDirection(UISlider::Direction::DIR_LEFT_TO_RIGHT); + } else if (&view == dirR2LBtn_) { + g_width = DEFAULT_HEIGHT; + g_height = DEFAULT_WIDTH; + slider_->Resize(g_width, g_height); + slider_->SetDirection(UISlider::Direction::DIR_RIGHT_TO_LEFT); + } else if (&view == dirT2BBtn_) { + g_width = DEFAULT_WIDTH; + g_height = DEFAULT_HEIGHT; + slider_->Resize(g_width, g_height); + slider_->SetDirection(UISlider::Direction::DIR_TOP_TO_BOTTOM); + } else if (&view == dirB2TBtn_) { + g_width = DEFAULT_WIDTH; + g_height = DEFAULT_HEIGHT; + slider_->Resize(g_width, g_height); + slider_->SetDirection(UISlider::Direction::DIR_BOTTOM_TO_TOP); + } else { + ExpandClick2(view, event); + } + return true; +} + +bool UITestSlider::ExpandClick2(UIView& view, const ClickEvent& event) +{ + if (&view == imageBtn_) { + slider_->SetValidHeight(DEFAULT_HEIGHT); + slider_->SetValidWidth(5); // 5 valid width + slider_->SetKnobWidth(40); // 40: knob width + slider_->SetDirection(UISlider::Direction::DIR_BOTTOM_TO_TOP); + slider_->SetImage(SLIDER_BACKGROUND_IMAGE_PATH, SLIDER_INDICATOR_IMAGE_PATH, SLIDER_KNOB_IMAGE_PATH); + } else if (&view == noImageBtn_) { + slider_->SetValidHeight(g_height); + slider_->SetValidWidth(g_width); + slider_->SetKnobWidth(g_knobWidth); + slider_->SetImage(static_cast(nullptr), static_cast(nullptr), + static_cast(nullptr)); + } else if (&view == setStyleBtn_) { + Style style = StyleDefault::GetDefaultStyle(); + style.bgColor_ = Color::White(); + slider_->SetBackgroundStyle(style); + style.bgColor_ = Color::Blue(); + slider_->SetForegroundStyle(style); + style.bgColor_ = Color::Gray(); + slider_->SetKnobStyle(style); + } else if (&view == getStyleBtn_) { + slider_->SetBackgroundStyle(STYLE_BACKGROUND_COLOR, Color::Red().full); + slider_->SetForegroundStyle(STYLE_BACKGROUND_COLOR, Color::Yellow().full); + slider_->SetKnobStyle(STYLE_BACKGROUND_COLOR, Color::Green().full); + } else if (&view == incKnobWidthBtn_) { + g_knobWidth++; + slider_->SetKnobWidth(g_knobWidth); + } else if (&view == decKnobWidthBtn_) { + g_knobWidth--; + slider_->SetKnobWidth(g_knobWidth); + } else if (&view == colorBtn_) { + slider_->SetSliderColor(Color::Silver(), Color::Blue(), Color::White()); + } else if (&view == radiusBtn_) { + slider_->SetSliderRadius(DEFAULT_RADIUS, DEFAULT_RADIUS, DEFAULT_RADIUS); + } else if (&view == onChangeBtn_) { + g_onChange = true; + } else if (&view == onReleaseBtn_) { + g_onRelease = true; + } else if (&view == onClickBtn_) { + g_onClick = true; + } else if (&view == onDragBtn_) { + g_onDrag = true; + } + return true; +} +} // namespace OHOS \ No newline at end of file diff --git a/test/uitest/test_slider/ui_test_slider.h b/test/uitest/test_slider/ui_test_slider.h new file mode 100755 index 0000000..121bc7d --- /dev/null +++ b/test/uitest/test_slider/ui_test_slider.h @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef UI_TEST_SLIDER_H +#define UI_TEST_SLIDER_H + +#include "components/ui_label.h" +#include "components/ui_label_button.h" +#include "components/ui_scroll_view.h" +#include "components/ui_slider.h" +#include "ui_test.h" + +namespace OHOS { +class TestUISliderEventListener : public UISlider::UISliderEventListener, + public UIView::OnClickListener, + public UIView::OnDragListener { +public: + explicit TestUISliderEventListener(UIViewGroup* viewGroup) + { + viewGroup_ = viewGroup; + } + virtual ~TestUISliderEventListener() {} + void OnChange(int32_t progress) override; + void OnRelease(int32_t progress) override; + bool OnClick(UIView& view, const ClickEvent& event) override; + bool OnDrag(UIView& view, const DragEvent& event) override; + bool MallocLabel(); + +private: + UILabel* label_ = nullptr; + UIViewGroup* viewGroup_ = nullptr; + int32_t value_ = 0; +}; + +class UITestSlider : public UITest, public UIView::OnClickListener { +public: + UITestSlider() {} + ~UITestSlider() {} + void SetUp() override; + void TearDown() override; + UIView* GetTestView() override; + + void SetUpButton(UILabelButton* btn, const char* title, int16_t x, int16_t y); + void SetUpLabel(const char* title, int16_t x, int16_t y) const; + + bool OnClick(UIView& view, const ClickEvent& event) override; + bool ExpandClick1(UIView& view, const ClickEvent& event); + bool ExpandClick2(UIView& view, const ClickEvent& event); + + void UIKit_Slider_Test_UISlider_001(); + void UIKit_Slider_Test_SetRange_002(); + void UIKit_Slider_Test_SetValue_003(); + void UIKit_Slider_Test_SetImage_004(); + void UIKit_Slider_Test_SetStyle_005(); + void UIKit_Slider_Test_GetStyle_006(); + void UIKit_Slider_Test_SetKnobWidth_007(); + void UIKit_Slider_Test_SetStep_008(); + void UIKit_Slider_Test_SetColor_009(); + void UIKit_Slider_Test_SetDirection_010(); + void UIKit_Slider_Test_SetValidSize_011(); + void UIKit_Slider_Test_SetRadius_012(); + void UIKit_Slider_Test_SetOnChangeListener_013(); + void UIKit_Slider_Test_SetOnReleaseListener_014(); + void UIKit_Slider_Test_SetOnClickCallback_015(); + void UIKit_Slider_Test_SetOnDragCallback_016(); + +private: + static constexpr int32_t DEFAULT_VALUE = 20; + static constexpr int16_t DEFAULT_WIDTH = 50; + static constexpr int16_t DEFAULT_HEIGHT = 250; + static constexpr int16_t DEFAULT_RADIUS = 10; + static constexpr int32_t MAX_VALUE = 100; + UIScrollView* container_ = nullptr; + UIScrollView* scroll_ = nullptr; + UISlider* slider_ = nullptr; + TestUISliderEventListener* listener_ = nullptr; + UIViewGroup* uiViewGroupFrame_ = nullptr; + + UILabelButton* resetBtn_ = nullptr; + UILabelButton* widthBtn_ = nullptr; + UILabelButton* heightBtn_ = nullptr; + UILabelButton* incProgressBtn_ = nullptr; + UILabelButton* decProgressBtn_ = nullptr; + UILabelButton* incMinProgressBtn_ = nullptr; + UILabelButton* decMinProgressBtn_ = nullptr; + UILabelButton* incMaxProgressBtn_ = nullptr; + UILabelButton* decMaxProgressBtn_ = nullptr; + UILabelButton* stepBtn_ = nullptr; + UILabelButton* dirL2RBtn_ = nullptr; + UILabelButton* dirR2LBtn_ = nullptr; + UILabelButton* dirT2BBtn_ = nullptr; + UILabelButton* dirB2TBtn_ = nullptr; + UILabelButton* imageBtn_ = nullptr; + UILabelButton* noImageBtn_ = nullptr; + UILabelButton* setStyleBtn_ = nullptr; + UILabelButton* getStyleBtn_ = nullptr; + UILabelButton* incKnobWidthBtn_ = nullptr; + UILabelButton* decKnobWidthBtn_ = nullptr; + UILabelButton* colorBtn_ = nullptr; + UILabelButton* radiusBtn_ = nullptr; + UILabelButton* onChangeBtn_ = nullptr; + UILabelButton* onReleaseBtn_ = nullptr; + UILabelButton* onClickBtn_ = nullptr; + UILabelButton* onDragBtn_ = nullptr; +}; +} // namespace OHOS +#endif // UI_TEST_SLIDER_H diff --git a/test/uitest/test_texture_mapper/ui_test_texture_mapper.cpp b/test/uitest/test_texture_mapper/ui_test_texture_mapper.cpp new file mode 100755 index 0000000..685dbba --- /dev/null +++ b/test/uitest/test_texture_mapper/ui_test_texture_mapper.cpp @@ -0,0 +1,273 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ui_test_texture_mapper.h" +#include "common/screen.h" +#include "test_resource_config.h" + +namespace OHOS { +namespace { +static int16_t g_rotateStart = 0; // 0: the start angle of rotation +static int16_t g_rotateEnd = 360; // 360: the end angle of rotation +static float g_scaleStart = 1; // 1: the start ratio +static float g_scaleEnd = 0.5; // 0.5: the end ratio +static uint16_t g_durationTime = 2000; // 2000: the duration for this animator +static uint16_t g_delayTime = 0; +static EasingFunc g_easingFunc = EasingEquation::CubicEaseInOut; +static Point g_pivot = {58, 58}; // 58: the x-coordinate,; 58: the y-coordinate +} // namespace + +void UITestTextureMapper::SetUp() +{ + if (container_ == nullptr) { + container_ = new UIScrollView(); + container_->Resize(Screen::GetInstance().GetWidth(), Screen::GetInstance().GetHeight() - BACK_BUTTON_HEIGHT); + UIViewGroup* uiViewGroup = new UIViewGroup(); + uiViewGroup->SetPosition(0, 0, 320, 390); // 320: width; 390: height + container_->Add(uiViewGroup); + UILabel* label = new UILabel(); + uiViewGroup->Add(label); + // 288: x-coordinate; 48: y-coordinate + label->SetPosition(TEXT_DISTANCE_TO_LEFT_SIDE, TEXT_DISTANCE_TO_TOP_SIDE, 288, 48); + label->SetText("UITextureMapper效果"); + label->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + UIViewGroup* uiViewGroupFrame = new UIViewGroup(); + uiViewGroup->Add(uiViewGroupFrame); + // 288: x-coordinate; 336: y-coordinate + uiViewGroupFrame->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE2, VIEW_DISTANCE_TO_TOP_SIDE, 288, 336); + uiViewGroupFrame->SetStyle(STYLE_BORDER_COLOR, Color::White().full); + uiViewGroupFrame->SetStyle(STYLE_BORDER_OPA, HALF_OPA_OPAQUE); + uiViewGroupFrame->SetStyle(STYLE_BORDER_WIDTH, VIEW_STYLE_BORDER_WIDTH); + uiViewGroupFrame->SetStyle(STYLE_BORDER_RADIUS, VIEW_STYLE_BORDER_RADIUS); + uiViewGroupFrame->SetStyle(STYLE_BACKGROUND_OPA, 0); + textureMapper_ = new UITextureMapper(); + textureMapper_->SetPosition(200, 50, 200, 200); // 200:position x; 50: positon y; 200: width; 200: height + textureMapper_->SetSrc(RED_IMAGE_PATH); + textureMapper_->SetRotateStart(0); + textureMapper_->SetRotateEnd(360); // 360: the end angle of rotation + textureMapper_->SetScaleStart(1); // 1: the start ratio + textureMapper_->SetScaleEnd(0.5); // 0.5: the end ratio + textureMapper_->SetPivot(58, 58); // 58: the x-coordinate,; 58: the y-coordinate + textureMapper_->SetDurationTime(2000); // 2000: the duration for this animator + listener_ = new TestTextureMapperStopListener(uiViewGroupFrame); + uiViewGroupFrame->Add(textureMapper_); + textureMapper_->LayoutCenterOfParent(); + } + if (layout_ == nullptr) { + layout_ = new GridLayout(); + layout_->SetPosition(336, 48, 516, 232); // 336: x-coordinate; 48: y-coordinate; 516: y value; 232: height + container_->Add(layout_); + layout_->SetLayoutDirection(LAYOUT_HOR); + layout_->SetRows(4); // 4: the number of rows + layout_->SetCols(4); // 4: the number of columns + } + resetBtn_ = new UILabelButton(); + SetUpButton(resetBtn_, "重置"); +} + +void UITestTextureMapper::TearDown() +{ + DeleteChildren(container_); + container_ = nullptr; + textureMapper_ = nullptr; + layout_ = nullptr; + delete listener_; + listener_ = nullptr; +} + +UIView* UITestTextureMapper::GetTestView() +{ + UIKit_TextureMapper_Test_SetRotateEnd_001(); + UIKit_TextureMapper_Test_SetRotateStart_002(); + UIKit_TextureMapper_Test_SetScaleEnd_003(); + UIKit_TextureMapper_Test_SetScaleStart_004(); + UIKit_TextureMapper_Test_SetPivot_005(); + UIKit_TextureMapper_Test_Start_006(); + UIKit_TextureMapper_Test_Reset_007(); + UIKit_TextureMapper_Test_CancelAnimation_008(); + UIKit_TextureMapper_Test_SetAnimatorStopListener_009(); + + layout_->LayoutChildren(); + return container_; +} + +void UITestTextureMapper::SetUpButton(UILabelButton* btn, const char* title) +{ + if (btn == nullptr) { + return; + } + layout_->Add(btn); + btn->Resize(BUTTON_WIDHT2, BUTTON_HEIGHT2); + btn->SetText(title); + btn->SetFont(DEFAULT_VECTOR_FONT_FILENAME, BUTTON_LABEL_SIZE); + btn->SetOnClickListener(this); + btn->SetStyleForState(STYLE_BORDER_RADIUS, BUTTON_STYLE_BORDER_RADIUS_VALUE, UIButton::RELEASED); + btn->SetStyleForState(STYLE_BORDER_RADIUS, BUTTON_STYLE_BORDER_RADIUS_VALUE, UIButton::PRESSED); + btn->SetStyleForState(STYLE_BORDER_RADIUS, BUTTON_STYLE_BORDER_RADIUS_VALUE, UIButton::INACTIVE); + btn->SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::RELEASED); + btn->SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::PRESSED); + btn->SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::INACTIVE); +} + +void UITestTextureMapper::UIKit_TextureMapper_Test_SetRotateEnd_001() +{ + incRotateEndBtn_ = new UILabelButton(); + SetUpButton(incRotateEndBtn_, "旋转结束角度+"); + + decRotateEndBtn_ = new UILabelButton(); + SetUpButton(decRotateEndBtn_, "旋转结束角度-"); +} + +void UITestTextureMapper::UIKit_TextureMapper_Test_SetRotateStart_002() +{ + startAngleBtn_ = new UILabelButton(); + SetUpButton(startAngleBtn_, "旋转起始角度+"); +} + +void UITestTextureMapper::UIKit_TextureMapper_Test_SetScaleEnd_003() +{ + incScaleEndBtn_ = new UILabelButton(); + SetUpButton(incScaleEndBtn_, "缩放结束比例+"); + + decScaleEndBtn_ = new UILabelButton(); + SetUpButton(decScaleEndBtn_, "缩放结束比例-"); +} + +void UITestTextureMapper::UIKit_TextureMapper_Test_SetScaleStart_004() +{ + startScaleBtn_ = new UILabelButton(); + SetUpButton(startScaleBtn_, "缩放起始比例+"); +} + +void UITestTextureMapper::UIKit_TextureMapper_Test_SetPivot_005() +{ + incPivotBtn_ = new UILabelButton(); + SetUpButton(incPivotBtn_, "中心+"); + + decPivotBtn_ = new UILabelButton(); + SetUpButton(decPivotBtn_, "中心-"); + + durationTimeBtn_ = new UILabelButton(); + SetUpButton(durationTimeBtn_, "持续时间+"); + + delayTimeBtn_ = new UILabelButton(); + SetUpButton(delayTimeBtn_, "延时时间+"); + + easingBtn_ = new UILabelButton(); + SetUpButton(easingBtn_, "动效"); +} + +void UITestTextureMapper::UIKit_TextureMapper_Test_Start_006() +{ + startBtn_ = new UILabelButton(); + SetUpButton(startBtn_, "启动动画"); +} + +void UITestTextureMapper::UIKit_TextureMapper_Test_Reset_007() +{ + resetImageBtn_ = new UILabelButton(); + SetUpButton(resetImageBtn_, "显示原始图片"); +} + +void UITestTextureMapper::UIKit_TextureMapper_Test_CancelAnimation_008() +{ + cancelBtn_ = new UILabelButton(); + SetUpButton(cancelBtn_, "取消动画 "); +} + +void UITestTextureMapper::UIKit_TextureMapper_Test_SetAnimatorStopListener_009() +{ + listenerBtn_ = new UILabelButton(); + SetUpButton(listenerBtn_, "动画结束监听"); +} + +bool UITestTextureMapper::OnClick(UIView& view, const ClickEvent& event) +{ + g_rotateStart = 0; // 0: the start angle of rotation + g_rotateEnd = 360; // 360: the end angle of rotation + g_scaleStart = 1; // 1: the start ratio + g_scaleEnd = 0.5; // 0.5: the end ratio + g_durationTime = 2000; // 2000: the duration for this animator + g_delayTime = 0; + g_easingFunc = EasingEquation::CubicEaseInOut; + if (&view == resetBtn_) { + g_rotateStart = 0; // 0: the start angle of rotation + g_rotateEnd = 360; // 360: the end angle of rotation + g_scaleStart = 1; // 1: the start ratio + g_scaleEnd = 0.5; // 0.5: the end ratio + + g_durationTime = 2000; // 2000: the duration for this animator + g_delayTime = 0; + g_easingFunc = EasingEquation::CubicEaseInOut; + g_pivot = {58, 58}; // 58: the x-coordinate,; 58: the y-coordinate + textureMapper_->SetRotateStart(g_rotateStart); + textureMapper_->SetScaleStart(g_scaleStart); + textureMapper_->SetPivot(g_pivot.x, g_pivot.y); + textureMapper_->Reset(); + } else if (&view == incRotateEndBtn_) { + g_rotateEnd += 90; // 90: the end angle of rotation augmentation + textureMapper_->SetRotateEnd(g_rotateEnd); + } else if (&view == decRotateEndBtn_) { + g_rotateEnd -= 90; // 90: the end angle of rotation decrease + textureMapper_->SetRotateEnd(g_rotateEnd); + } else if (&view == durationTimeBtn_) { + g_durationTime += 200; // 200: the duration for this animator augmentation + textureMapper_->SetDurationTime(g_durationTime); + } else if (&view == delayTimeBtn_) { + g_delayTime += 200; // 200: the delay for this animator augmentation + textureMapper_->SetDelayTime(g_delayTime); + } else if (&view == easingBtn_) { + g_easingFunc = EasingEquation::SineEaseIn; + textureMapper_->SetEasingFunc(g_easingFunc); + } else if (&view == startAngleBtn_) { + g_rotateStart += 90; // 90: the start angle of rotation augmentation + textureMapper_->SetRotateStart(g_rotateStart); + } else { + ExpandClick(view, event); + } + return true; +} + +bool UITestTextureMapper::ExpandClick(UIView& view, const ClickEvent& event) +{ + if (&view == incScaleEndBtn_) { + g_scaleEnd += 0.2; // 0.2: the end ratio augmentation + textureMapper_->SetScaleEnd(g_scaleEnd); + } else if (&view == decScaleEndBtn_) { + g_scaleEnd -= 0.2; // 0.2: the end ratio decrease + textureMapper_->SetScaleEnd(g_scaleEnd); + } else if (&view == startScaleBtn_) { + g_scaleStart += 0.2; // 0.2: the start ratio augmentation + textureMapper_->SetScaleStart(g_scaleStart); + } else if (&view == incPivotBtn_) { + g_pivot.x += 5; // 5: pivot offset pixel + g_pivot.y += 5; // 5: pivot offset pixel + textureMapper_->SetPivot(g_pivot.x, g_pivot.y); + } else if (&view == decPivotBtn_) { + g_pivot.x -= 5; // 5: pivot offset pixel + g_pivot.y -= 5; // 5: pivot offset pixel + textureMapper_->SetPivot(g_pivot.x, g_pivot.y); + } else if (&view == startBtn_) { + textureMapper_->Start(); + } else if (&view == resetImageBtn_) { + textureMapper_->Reset(); + } else if (&view == cancelBtn_) { + textureMapper_->Cancel(); + } else if (&view == listenerBtn_) { + textureMapper_->SetAnimatorStopListener(listener_); + } + return true; +} +} // namespace OHOS \ No newline at end of file diff --git a/test/uitest/test_texture_mapper/ui_test_texture_mapper.h b/test/uitest/test_texture_mapper/ui_test_texture_mapper.h new file mode 100755 index 0000000..a56037a --- /dev/null +++ b/test/uitest/test_texture_mapper/ui_test_texture_mapper.h @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef UI_TEST_TEXTURE_MAPPER_H +#define UI_TEST_TEXTURE_MAPPER_H + +#include "components/ui_label.h" +#include "components/ui_label_button.h" +#include "components/ui_scroll_view.h" +#include "components/ui_texture_mapper.h" +#include "layout/grid_layout.h" +#include "ui_test.h" + +namespace OHOS { +class TestTextureMapperStopListener : public UITextureMapper::AnimatorStopListener { +public: + explicit TestTextureMapperStopListener(UIViewGroup* viewGroup) + { + if (viewGroup != nullptr) { + viewGroup_ = viewGroup; + label_ = new UILabel(); + viewGroup_->Add(label_); + label_->SetPosition(0, 0, 200, TITLE_LABEL_DEFAULT_HEIGHT); // 200: width; + label_->SetText("OnAnimatorStop"); + label_->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + label_->SetStyle(STYLE_TEXT_COLOR, Color::Black().full); + label_->SetStyle(STYLE_BACKGROUND_COLOR, Color::White().full); + label_->SetStyle(STYLE_BACKGROUND_OPA, OPA_OPAQUE); + label_->SetVisible(false); + } + } + + ~TestTextureMapperStopListener() {}; + + void OnAnimatorStop(UIView& view) override + { + label_->SetVisible(true); + label_->Invalidate(); + } +private: + UILabel* label_ = nullptr; + UIViewGroup* viewGroup_ = nullptr; +}; + +class UITestTextureMapper : public UITest, public UIView::OnClickListener { +public: + UITestTextureMapper() {} + ~UITestTextureMapper() {}; + void SetUp() override; + void TearDown() override; + UIView* GetTestView() override; + + void SetUpButton(UILabelButton* btn, const char* title); + + bool OnClick(UIView& view, const ClickEvent& event) override; + bool ExpandClick(UIView& view, const ClickEvent& event); + + void UIKit_TextureMapper_Test_SetRotateEnd_001(); + void UIKit_TextureMapper_Test_SetRotateStart_002(); + void UIKit_TextureMapper_Test_SetScaleEnd_003(); + void UIKit_TextureMapper_Test_SetScaleStart_004(); + void UIKit_TextureMapper_Test_SetPivot_005(); + void UIKit_TextureMapper_Test_Start_006(); + void UIKit_TextureMapper_Test_Reset_007(); + void UIKit_TextureMapper_Test_CancelAnimation_008(); + void UIKit_TextureMapper_Test_SetAnimatorStopListener_009(); + +private: + UIScrollView* container_ = nullptr; + GridLayout* layout_ = nullptr; + UITextureMapper* textureMapper_ = nullptr; + TestTextureMapperStopListener* listener_ = nullptr; + + UILabelButton* resetBtn_ = nullptr; + + UILabelButton* incRotateEndBtn_ = nullptr; + UILabelButton* decRotateEndBtn_ = nullptr; + UILabelButton* startAngleBtn_ = nullptr; + + UILabelButton* incScaleEndBtn_ = nullptr; + UILabelButton* decScaleEndBtn_ = nullptr; + UILabelButton* startScaleBtn_ = nullptr; + + UILabelButton* durationTimeBtn_ = nullptr; + UILabelButton* delayTimeBtn_ = nullptr; + UILabelButton* easingBtn_ = nullptr; + + UILabelButton* incPivotBtn_ = nullptr; + UILabelButton* decPivotBtn_ = nullptr; + UILabelButton* startBtn_ = nullptr; + UILabelButton* cancelBtn_ = nullptr; + UILabelButton* resetImageBtn_ = nullptr; + UILabelButton* listenerBtn_ = nullptr; +}; +} // namespace OHOS +#endif // UI_TEST_TEXTURE_MAPPER_H diff --git a/test/uitest/test_transform/ui_test_transform.cpp b/test/uitest/test_transform/ui_test_transform.cpp new file mode 100755 index 0000000..61d0753 --- /dev/null +++ b/test/uitest/test_transform/ui_test_transform.cpp @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ui_test_transform.h" +#include "common/screen.h" +#include "test_resource_config.h" + +namespace OHOS { +void UITestTransform::SetUp() +{ + if (container_ == nullptr) { + container_ = new UIScrollView(); + container_->Resize(Screen::GetInstance().GetWidth(), Screen::GetInstance().GetHeight() - BACK_BUTTON_HEIGHT); + UIViewGroup* uiViewGroup = new UIViewGroup(); + uiViewGroup->SetPosition(0, 0, 320, 390); // 320: width; 390: height + container_->Add(uiViewGroup); + UILabel* label = new UILabel(); + uiViewGroup->Add(label); + // 288: width; 48: height + label->SetPosition(TEXT_DISTANCE_TO_LEFT_SIDE, TEXT_DISTANCE_TO_TOP_SIDE, 288, 48); + label->SetText("UITransform效果"); + label->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + uiViewGroupFrame_ = new UIViewGroup(); + uiViewGroup->Add(uiViewGroupFrame_); + // 288: width; 336: height + uiViewGroupFrame_->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE2, VIEW_DISTANCE_TO_TOP_SIDE, 288, 336); + uiViewGroupFrame_->SetStyle(STYLE_BORDER_COLOR, Color::White().full); + uiViewGroupFrame_->SetStyle(STYLE_BORDER_OPA, HALF_OPA_OPAQUE); + uiViewGroupFrame_->SetStyle(STYLE_BORDER_WIDTH, VIEW_STYLE_BORDER_WIDTH); + uiViewGroupFrame_->SetStyle(STYLE_BORDER_RADIUS, VIEW_STYLE_BORDER_RADIUS); + uiViewGroupFrame_->SetStyle(STYLE_BACKGROUND_OPA, 0); + + imageView_ = new UIImageView(); + imageView_->SetPosition(150, 50, 200, 200); // 150:poistion x 50:position y 200:width 200:height + imageView_->SetSrc(BLUE_IMAGE_PATH); + uiViewGroupFrame_->Add(imageView_); + imageView_->LayoutCenterOfParent(); + } + + if (layout_ == nullptr) { + layout_ = new GridLayout(); + // 34: increase x-coordinate; 48: y-coordinate; 100: width; 150: height + layout_->SetPosition(uiViewGroupFrame_->GetWidth() + 34, 48, 100, 150); + container_->Add(layout_); + layout_->SetLayoutDirection(LAYOUT_VER); + layout_->SetRows(3); // 3:two rows + layout_->SetCols(1); // 1:three cols + } +} + +void UITestTransform::TearDown() +{ + DeleteChildren(container_); + container_ = nullptr; + layout_ = nullptr; + uiViewGroupFrame_ = nullptr; + imageView_ = nullptr; +} + +UIView* UITestTransform::GetTestView() +{ + UIKit_Transform_Test_Rotate_001(); + UIKit_Transform_Test_Scale_002(); + UIKit_Transform_Test_Translate_003(); + + layout_->LayoutChildren(); + return container_; +} + +void UITestTransform::SetUpButton(UILabelButton* btn, const char* title) +{ + if (btn == nullptr) { + return; + } + layout_->Add(btn); + btn->Resize(BUTTON_WIDHT1, BUTTON_HEIGHT1); + btn->SetText(title); + btn->SetFont(DEFAULT_VECTOR_FONT_FILENAME, BUTTON_LABEL_SIZE); + btn->SetOnClickListener(this); + btn->SetStyleForState(STYLE_BORDER_RADIUS, BUTTON_STYLE_BORDER_RADIUS_VALUE, UIButton::RELEASED); + btn->SetStyleForState(STYLE_BORDER_RADIUS, BUTTON_STYLE_BORDER_RADIUS_VALUE, UIButton::PRESSED); + btn->SetStyleForState(STYLE_BORDER_RADIUS, BUTTON_STYLE_BORDER_RADIUS_VALUE, UIButton::INACTIVE); + btn->SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::RELEASED); + btn->SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::PRESSED); + btn->SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::INACTIVE); +} + +void UITestTransform::UIKit_Transform_Test_Rotate_001() +{ + rotateBtn_ = new UILabelButton(); + SetUpButton(rotateBtn_, "旋转"); +} + +void UITestTransform::UIKit_Transform_Test_Scale_002() +{ + scaleBtn_ = new UILabelButton(); + SetUpButton(scaleBtn_, "缩放"); +} + +void UITestTransform::UIKit_Transform_Test_Translate_003() +{ + translateBtn_ = new UILabelButton(); + SetUpButton(translateBtn_, "平移"); +} + +bool UITestTransform::OnClick(UIView& view, const ClickEvent& event) +{ + Rect viewRect = imageView_->GetOrigRect(); + TransformMap transMap(viewRect); + Vector2 pivot_(58, 58); // 58:x value 58:y value + if (&view == rotateBtn_) { + transMap.Rotate(90, pivot_); // 90:degree + } else if (&view == scaleBtn_) { + transMap.Scale(Vector2(1.5, 1.5), pivot_); // 1.5:x scale 1.5:y scale + } else if (&view == translateBtn_) { + transMap.Translate(Vector2(80, -30)); // 80:x-axis translation distance -30:y-axis translation distance + } + imageView_->SetTransformMap(transMap); + + return true; +} +} // namespace OHOS \ No newline at end of file diff --git a/test/uitest/test_transform/ui_test_transform.h b/test/uitest/test_transform/ui_test_transform.h new file mode 100755 index 0000000..132d049 --- /dev/null +++ b/test/uitest/test_transform/ui_test_transform.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef UI_TEST_TRANSFORM_H +#define UI_TEST_TRANSFORM_H + +#include "components/ui_image_view.h" +#include "components/ui_label.h" +#include "components/ui_label_button.h" +#include "components/ui_scroll_view.h" +#include "layout/grid_layout.h" +#include "ui_test.h" + +namespace OHOS { +class UITestTransform : public UITest, public UIView::OnClickListener { +public: + UITestTransform() {} + ~UITestTransform() {} + void SetUp() override; + void TearDown() override; + UIView* GetTestView() override; + + void SetUpButton(UILabelButton* btn, const char* title); + + bool OnClick(UIView& view, const ClickEvent& event) override; + + void UIKit_Transform_Test_Rotate_001(); + void UIKit_Transform_Test_Scale_002(); + void UIKit_Transform_Test_Translate_003(); + +private: + UIScrollView* container_ = nullptr; + GridLayout* layout_ = nullptr; + UIImageView* imageView_ = nullptr; + UIViewGroup* uiViewGroupFrame_ = nullptr; + + UILabelButton* rotateBtn_ = nullptr; + UILabelButton* scaleBtn_ = nullptr; + UILabelButton* translateBtn_ = nullptr; +}; +} // namespace OHOS +#endif // UI_TEST_TRANSFORM_H diff --git a/test/uitest/test_ui_analog_clock/ui_test_analog_clock.cpp b/test/uitest/test_ui_analog_clock/ui_test_analog_clock.cpp new file mode 100755 index 0000000..0f52251 --- /dev/null +++ b/test/uitest/test_ui_analog_clock/ui_test_analog_clock.cpp @@ -0,0 +1,315 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ui_test_analog_clock.h" +#include "common/screen.h" +#include "components/ui_analog_clock.h" +#include "components/ui_image_view.h" +#include "components/ui_label.h" +#include "test_resource_config.h" + +namespace OHOS { +namespace { +const uint8_t BUTTON_TEXT_SIZE = 16; +const uint8_t BUTTON_WIDTH = 120; +const uint8_t BUTTON_HEIGHT = 40; +} // namespace + +void UITestAnalogClock::SetUp() +{ + if (container_ == nullptr) { + container_ = new UIScrollView(); + container_->Resize(Screen::GetInstance().GetWidth(), Screen::GetInstance().GetHeight() - BACK_BUTTON_HEIGHT); + } +} + +void UITestAnalogClock::TearDown() +{ + if (animator_ != nullptr) { + AnimatorManager::GetInstance()->Remove(animator_); + delete animator_; + delete callback_; + animator_ = nullptr; + callback_ = nullptr; + } + + if (animator2_ != nullptr) { + AnimatorManager::GetInstance()->Remove(animator2_); + delete animator2_; + delete callback2_; + animator2_ = nullptr; + callback2_ = nullptr; + } + + DeleteChildren(container_); + container_ = nullptr; +} + +UIView* UITestAnalogClock::GetTestView() +{ + UIKit_TestLineHandAnalogClock_001(); + UIKit_TestImageHandAnalogClock_002(); + return container_; +} + +class TestBtnOnClickChangeModeListener : public UIView::OnClickListener { +public: + TestBtnOnClickChangeModeListener(UIView* uiView, UIAnalogClock* clock) : uiView_(uiView), clock_(clock) {} + ~TestBtnOnClickChangeModeListener() {} + bool OnClick(UIView& view, const ClickEvent& event) override + { + if (clock_->GetWorkMode() == UIAbstractClock::ALWAYS_ON) { + clock_->SetWorkMode(UIAbstractClock::NORMAL); + } else { + clock_->SetWorkMode(UIAbstractClock::ALWAYS_ON); + } + return true; + } + +private: + UIView* uiView_; + UIAnalogClock* clock_; +}; + +class TestBtnOnClickMovePositionListener : public UIView::OnClickListener { +public: + enum class MoveType { + MOVE_LEFT, + MOVE_RIGHT, + MOVE_TOP, + MOVE_BOTTOM, + }; + TestBtnOnClickMovePositionListener(UIView* uiView, UIAnalogClock* clock, MoveType type, uint16_t grid) + : uiView_(uiView), clock_(clock), type_(type), grid_(grid) + { + } + ~TestBtnOnClickMovePositionListener() {} + bool OnClick(UIView& view, const ClickEvent& event) override + { + int16_t xPos = clock_->GetX(); + int16_t yPos = clock_->GetY(); + clock_->Invalidate(); + if (type_ == MoveType::MOVE_LEFT) { + xPos -= grid_; + } else if (type_ == MoveType::MOVE_RIGHT) { + xPos += grid_; + } else if (type_ == MoveType::MOVE_TOP) { + yPos -= grid_; + } else if (type_ == MoveType::MOVE_BOTTOM) { + yPos += grid_; + } + clock_->SetPosition(xPos, yPos); + return true; + } + +private: + UIView* uiView_; + UIAnalogClock* clock_; + MoveType type_; + uint16_t grid_; +}; + +void UITestAnalogClock::UIKit_TestLineHandAnalogClock_001() +{ + if (container_ != nullptr) { + UIViewGroup* group1 = new UIViewGroup(); + group1->SetPosition(0, 0); + group1->SetWidth(Screen::GetInstance().GetWidth()); + group1->SetHeight(Screen::GetInstance().GetHeight() + 100); // 100: height bigger value + container_->Add(group1); + + UILabel* label = new UILabel(); + group1->Add(label); + // 30: height + label->SetPosition(TEXT_DISTANCE_TO_LEFT_SIDE, TEXT_DISTANCE_TO_TOP_SIDE, Screen::GetInstance().GetWidth(), 30); + label->SetText("直线指针效果"); + label->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + + UIAnalogClock* clock1 = new UIAnalogClock(); + group1->Add(clock1); + clock1->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE2, VIEW_DISTANCE_TO_TOP_SIDE, + Screen::GetInstance().GetWidth() / 2, // 2: half of screen width + Screen::GetInstance().GetHeight()); + + UIImageView* curFace = new UIImageView(); + curFace->SetSrc(B024_002_IMAGE_PATH); + clock1->Add(curFace); + curFace->SetPosition(0, 0); + // 214: position x 3:position y 14:center x 223:center y 1: width 223:height 255: opacity + clock1->SetHandLine(UIAnalogClock::HandType::SECOND_HAND, {214, 3}, {14, 223}, Color::White(), 1, 223, 255); + // 202: position x 25:position y 25:center x 201:center y 3: width 201:height 255: opacity + clock1->SetHandLine(UIAnalogClock::HandType::MINUTE_HAND, {202, 25}, {25, 201}, Color::Green(), 3, 201, 255); + // 202: position x 95:position y 25:center x 132:center y 5: width 132: height 255: opacity + clock1->SetHandLine(UIAnalogClock::HandType::HOUR_HAND, {202, 95}, {25, 132}, Color::Red(), 5, 132, 255); + clock1->SetInitTime24Hour(0, 0, 0); + clock1->SetWorkMode(UIAnalogClock::WorkMode::NORMAL); + clock1->SetTime24Hour(10, 3, 20); // 10: hour; 3: minute: 20: second + CreateButtons001(group1, curFace, clock1); + callback_ = new ClockAnimatorCallback(clock1); + animator_ = new Animator(callback_, clock1, 0, true); + AnimatorManager::GetInstance()->Add(animator_); + animator_->Start(); + } +} + +void UITestAnalogClock::CreateButtons001(UIViewGroup* group, UIImageView* curFace, UIAnalogClock* clock) +{ + if ((group == nullptr) || (curFace == nullptr) || (clock == nullptr)) { + return; + } + UILabelButton* button1 = SetUpButton("切换模式"); + group->Add(button1); + // 58: increase x-position; 48: y-position + button1->SetPosition(curFace->GetWidth() + 58, 48, BUTTON_WIDTH, BUTTON_HEIGHT); + UIView::OnClickListener* changeModeListener = + new TestBtnOnClickChangeModeListener(static_cast(button1), clock); + button1->SetOnClickListener(changeModeListener); + + UILabelButton* button2 = SetUpButton("左移"); + group->Add(button2); + // 58: increase x-position; 58: increase y-position + button2->SetPosition(curFace->GetWidth() + 58, BUTTON_HEIGHT + 58, BUTTON_WIDTH, BUTTON_HEIGHT); + UIView::OnClickListener* clickMoveLeftListener = new TestBtnOnClickMovePositionListener( + static_cast(button2), clock, TestBtnOnClickMovePositionListener::MoveType::MOVE_LEFT, 10); // 10: grid + button2->SetOnClickListener(clickMoveLeftListener); + + UILabelButton* button3 = SetUpButton("右移"); + group->Add(button3); + // 58: increase x-position; 148: y-position + button3->SetPosition(curFace->GetWidth() + 58, 148, BUTTON_WIDTH, BUTTON_HEIGHT); + UIView::OnClickListener* clickMoveRightListener = new TestBtnOnClickMovePositionListener( + static_cast(button3), clock, TestBtnOnClickMovePositionListener::MoveType::MOVE_RIGHT, + 10); // 10: grid + button3->SetOnClickListener(clickMoveRightListener); + + UILabelButton* button4 = SetUpButton("上移"); + group->Add(button4); + // 58: increase x-position; 198: y-position + button4->SetPosition(curFace->GetWidth() + 58, 198, BUTTON_WIDTH, BUTTON_HEIGHT); + UIView::OnClickListener* clickMoveTopListener = new TestBtnOnClickMovePositionListener( + static_cast(button4), clock, TestBtnOnClickMovePositionListener::MoveType::MOVE_TOP, + 10); // 10: move value + button4->SetOnClickListener(clickMoveTopListener); + + UILabelButton* button5 = SetUpButton("下移"); + group->Add(button5); + // 58: increase x-position; 248: y-position + button5->SetPosition(curFace->GetWidth() + 58, 248, BUTTON_WIDTH, BUTTON_HEIGHT); + UIView::OnClickListener* clickMoveBottomListener = new TestBtnOnClickMovePositionListener( + static_cast(button5), clock, TestBtnOnClickMovePositionListener::MoveType::MOVE_BOTTOM, + 10); // 10: move value + button5->SetOnClickListener(clickMoveBottomListener); +} + +void UITestAnalogClock::UIKit_TestImageHandAnalogClock_002() +{ + if (container_ != nullptr) { + UIViewGroup* group2 = new UIViewGroup(); + group2->SetPosition(0, Screen::GetInstance().GetHeight() + 50); // 50: increase y-coordinate + group2->SetWidth(Screen::GetInstance().GetWidth()); + group2->SetHeight(Screen::GetInstance().GetHeight() + 100); // 100: increase y-coordinate + container_->Add(group2); + + UILabel* label = new UILabel(); + group2->Add(label); + label->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, 0, Screen::GetInstance().GetWidth(), 30); // 30: height + label->SetText("图片指针效果"); + label->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + + UIImageView* image1 = new UIImageView(); + image1->SetSrc(B024_010_IMAGE_PATH); + UIImageView* image2 = new UIImageView(); + image2->SetSrc(B024_011_IMAGE_PATH); + UIImageView* image3 = new UIImageView(); + image3->SetSrc(B024_009_IMAGE_PATH); + + UIAnalogClock* clock2 = new UIAnalogClock(); + group2->Add(clock2); + clock2->SetDragParentInstead(true); + clock2->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE2, VIEW_DISTANCE_TO_TOP_SIDE, + Screen::GetInstance().GetWidth() / 2, // 2: half of screen width + Screen::GetInstance().GetHeight()); + + UIImageView* curFace = new UIImageView(); + curFace->SetSrc(B024_002_IMAGE_PATH); + clock2->Add(curFace); + curFace->SetPosition(0, 0); + // {214, 3}: position x, y {14, 223}: center x, y + clock2->SetHandImage(UIAnalogClock::HandType::SECOND_HAND, *image1, {214, 3}, {14, 223}); + // {202, 15}: position x, y {25, 211}: center x, y + clock2->SetHandImage(UIAnalogClock::HandType::MINUTE_HAND, *image2, {202, 15}, {25, 211}); + // {202, 15}: position x, y {25, 211}: center x, y + clock2->SetHandImage(UIAnalogClock::HandType::HOUR_HAND, *image3, {202, 15}, {25, 211}); + + clock2->SetInitTime24Hour(0, 0, 0); + clock2->SetWorkMode(UIAnalogClock::WorkMode::NORMAL); + clock2->SetTime24Hour(3, 3, 15); // 3: hour; 3: minute: 15: second + CreateButtons002(group2, curFace, clock2); + callback2_ = new ClockAnimatorCallback(clock2); + animator2_ = new Animator(callback2_, clock2, 0, true); + AnimatorManager::GetInstance()->Add(animator2_); + animator2_->Start(); + } +} + +void UITestAnalogClock::CreateButtons002(UIViewGroup* group, UIImageView* curFace, UIAnalogClock* clock) +{ + if ((group == nullptr) || (curFace == nullptr) || (clock == nullptr)) { + return; + } + UILabelButton* button1 = SetUpButton("切换模式"); + group->Add(button1); + // 58: increase x-coordinate; 48: y-coordinate + button1->SetPosition(curFace->GetWidth() + 58, 48, BUTTON_WIDTH, BUTTON_HEIGHT); + UIView::OnClickListener* changeModeListener = + new TestBtnOnClickChangeModeListener(static_cast(button1), clock); + button1->SetOnClickListener(changeModeListener); + + UILabelButton* button2 = SetUpButton("左移"); + group->Add(button2); + // 58: increase x-coordinate; 98: y-coordinate + button2->SetPosition(curFace->GetWidth() + 58, 98, BUTTON_WIDTH, BUTTON_HEIGHT); + UIView::OnClickListener* clickMoveLeftListener = new TestBtnOnClickMovePositionListener( + static_cast(button2), clock, TestBtnOnClickMovePositionListener::MoveType::MOVE_LEFT, 10); // 10: grid + button2->SetOnClickListener(clickMoveLeftListener); + + UILabelButton* button3 = SetUpButton("右移"); + group->Add(button3); + // 58: increase x-coordinate; 148: y-coordinate + button3->SetPosition(curFace->GetWidth() + 58, 148, BUTTON_WIDTH, BUTTON_HEIGHT); + UIView::OnClickListener* clickMoveRightListener = new TestBtnOnClickMovePositionListener( + static_cast(button3), clock, TestBtnOnClickMovePositionListener::MoveType::MOVE_RIGHT, + 10); // 10: move value + button3->SetOnClickListener(clickMoveRightListener); + + UILabelButton* button4 = SetUpButton("上移"); + group->Add(button4); + // 58: increase x-coordinate; 198: y-coordinate + button4->SetPosition(curFace->GetWidth() + 58, 198, BUTTON_WIDTH, BUTTON_HEIGHT); + UIView::OnClickListener* clickMoveTopListener = new TestBtnOnClickMovePositionListener( + static_cast(button4), clock, TestBtnOnClickMovePositionListener::MoveType::MOVE_TOP, + 10); // 10: move value + button4->SetOnClickListener(clickMoveTopListener); + UILabelButton* button5 = SetUpButton("下移"); + group->Add(button5); + // 58: increase x-coordinate; 248: y-coordinate + button5->SetPosition(curFace->GetWidth() + 58, 248, BUTTON_WIDTH, BUTTON_HEIGHT); + /* Move bottom */ + UIView::OnClickListener* clickMoveBottomListener = new TestBtnOnClickMovePositionListener( + static_cast(button5), clock, TestBtnOnClickMovePositionListener::MoveType::MOVE_BOTTOM, + 10); // 10: move value + button5->SetOnClickListener(clickMoveBottomListener); +} +} // namespace OHOS \ No newline at end of file diff --git a/test/uitest/test_ui_analog_clock/ui_test_analog_clock.h b/test/uitest/test_ui_analog_clock/ui_test_analog_clock.h new file mode 100755 index 0000000..7e0ef0f --- /dev/null +++ b/test/uitest/test_ui_analog_clock/ui_test_analog_clock.h @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef UI_TEST_ANALOG_CLOCK_H +#define UI_TEST_ANALOG_CLOCK_H + +#include "ui_test.h" + +#include "components/ui_analog_clock.h" +#include "components/ui_label_button.h" +#include "components/ui_scroll_view.h" + +namespace OHOS { +class ClockAnimatorCallback : public AnimatorCallback { +public: + explicit ClockAnimatorCallback(UIAnalogClock* clock) : clock_(clock) {} + ~ClockAnimatorCallback() {}; + virtual void Callback(UIView* view) + { + count_++; + if ((count_ == 30) && (clock_ != nullptr)) { // 30: run every tick (~30ms) + clock_->IncOneSecond(); + count_ = 0; + } + } +private: + UIAnalogClock* clock_; + int16_t count_ = 0; +}; + +class UITestAnalogClock : public UITest { +public: + UITestAnalogClock() {} + ~UITestAnalogClock() {} + void SetUp() override; + void TearDown() override; + UIView* GetTestView() override; + void CreateButtons001(UIViewGroup* group, UIImageView* curFace, UIAnalogClock* clock); + void CreateButtons002(UIViewGroup* group, UIImageView* curFace, UIAnalogClock* clock); + + void UIKit_TestLineHandAnalogClock_001(); + void UIKit_TestImageHandAnalogClock_002(); + +private: + UIScrollView* container_ = nullptr; + ClockAnimatorCallback* callback_ = nullptr; + Animator* animator_ = nullptr; + ClockAnimatorCallback* callback2_ = nullptr; + Animator* animator2_ = nullptr; + + UILabelButton* SetUpButton(const char* title) + { + UILabelButton* btn = new UILabelButton(); + btn->Resize(BUTTON_WIDHT2, BUTTON_HEIGHT2); + btn->SetText(title); + btn->SetFont(DEFAULT_VECTOR_FONT_FILENAME, BUTTON_LABEL_SIZE); + btn->SetStyleForState(STYLE_BORDER_RADIUS, BUTTON_STYLE_BORDER_RADIUS_VALUE, UIButton::RELEASED); + btn->SetStyleForState(STYLE_BORDER_RADIUS, BUTTON_STYLE_BORDER_RADIUS_VALUE, UIButton::PRESSED); + btn->SetStyleForState(STYLE_BORDER_RADIUS, BUTTON_STYLE_BORDER_RADIUS_VALUE, UIButton::INACTIVE); + btn->SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::RELEASED); + btn->SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::PRESSED); + btn->SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::INACTIVE); + return btn; + } +}; +} +#endif // UI_TEST_ANALOG_CLOCK_H diff --git a/test/uitest/test_ui_dump_dom_tree/ui_test_dump_dom.cpp b/test/uitest/test_ui_dump_dom_tree/ui_test_dump_dom.cpp new file mode 100755 index 0000000..d1d29ff --- /dev/null +++ b/test/uitest/test_ui_dump_dom_tree/ui_test_dump_dom.cpp @@ -0,0 +1,435 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "graphic_config.h" + +#if ENABLE_DEBUG +#include "ui_test_dump_dom.h" + +#include "common/screen.h" +#include "components/ui_arc_label.h" +#include "components/ui_box_progress.h" +#include "components/ui_checkbox.h" +#include "components/ui_circle_progress.h" +#include "components/ui_image_view.h" +#include "components/ui_label.h" +#include "components/ui_label_button.h" +#include "components/ui_radio_button.h" +#include "components/ui_slider.h" +#include "components/ui_toggle_button.h" +#include "dfx/ui_dump_dom_tree.h" +#include "imgdecode/cache_manager.h" +#include "test_resource_config.h" +#include "graphic_log.h" + +namespace OHOS { +void UITestDumpDomTree::SetUp() +{ + if (container_ == nullptr) { + container_ = new UIScrollView(); + container_->Resize(Screen::GetInstance().GetWidth(), Screen::GetInstance().GetHeight() - BACK_BUTTON_HEIGHT); + container_->SetViewId("dump_scroll_view"); + } +} + +void UITestDumpDomTree::TearDown() +{ + positionY_ = 0; + DeleteChildren(container_); + container_ = nullptr; +} + +const UIView* UITestDumpDomTree::GetTestView() +{ + positionY_ = 0; + UIKit_TestDumpDom_001(); + UIKit_TestDumpDom_002(); + UIKit_TestDumpDom_003(); + UIKit_TestDumpDom_004(); + UIKit_TestDumpDom_005(); + UIKit_TestDumpDom_006(); + return container_; +} + +class TestBtnOnClickDumpDomListener : public UIView::OnClickListener { +public: + TestBtnOnClickDumpDomListener(UIView* uiView, const char* id) : uiView_(uiView), id_(id) {} + ~TestBtnOnClickDumpDomListener() {} + bool OnClick(UIView& view, const ClickEvent& event) override + { + char* tmpPtr = UIDumpDomTree::GetInstance()->DumpDomNode(id_); + if (tmpPtr != nullptr) { + cJSON_free(tmpPtr); + } + uiView_->Invalidate(); + return true; + } + +private: + UIView* uiView_; + const char* id_; +}; + +class TestBtnOnClickDumpDomTreeListener : public UIView::OnClickListener { +public: + TestBtnOnClickDumpDomTreeListener(UIView* uiView, const char* id) : uiView_(uiView), id_(id) {} + ~TestBtnOnClickDumpDomTreeListener() {} + bool OnClick(UIView& view, const ClickEvent& event) override + { + if (UIDumpDomTree::GetInstance()->DumpDomTree(id_)) { + if (id_ == nullptr) { + GRAPHIC_LOGI("dump tree from rootView success\n"); + } else { + GRAPHIC_LOGI("dump tree from id success\n"); + } + } else { + GRAPHIC_LOGI("dump tree failed\n"); + } + uiView_->Invalidate(); + return true; + } + +private: + UIView* uiView_; + const char* id_; +}; + +void UITestDumpDomTree::UIKit_TestDumpDom_001() +{ + if (container_ != nullptr) { + UILabel* label = GetTitleLabel("dump checkbox"); + container_->Add(label); + label->SetPosition(TEXT_DISTANCE_TO_LEFT_SIDE, TEXT_DISTANCE_TO_TOP_SIDE); + positionY_ += label->GetHeight(); + + GridLayout* layout = new GridLayout(); + layout->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, VIEW_DISTANCE_TO_TOP_SIDE, + // 2: half of screen width; 88: decrease screen width; 180: height + Screen::GetInstance().GetWidth() / 2 - 88, 180); + layout->SetViewId("dump_group"); + container_->Add(layout); + positionY_ += layout->GetHeight(); + layout->SetRows(3); // 3: row + layout->SetCols(3); // 3: col + CreateButtons(layout); + layout->LayoutChildren(); + } +} + +void UITestDumpDomTree::CreateButtons(GridLayout* layout) +{ + if (layout == nullptr) { + return; + } + UICheckBox* checkBox = new UICheckBox(); + layout->Add(checkBox); + checkBox->SetState(UICheckBox::SELECTED); + checkBox->SetViewId("dump_checkbox"); + + UIRadioButton* radioButton = new UIRadioButton("aaa"); + layout->Add(radioButton); + radioButton->SetState(UICheckBox::UNSELECTED); + radioButton->SetViewId("dump_radiobutton"); + + UIToggleButton* toggleButton = new UIToggleButton(); + layout->Add(toggleButton); + toggleButton->SetStyle(STYLE_BACKGROUND_OPA, 0); + toggleButton->SetViewId("dump_togglebutton"); + + UILabelButton* dumpButton1 = GetLabelButton("dump"); + layout->Add(dumpButton1); + /* dump node here */ + UIView::OnClickListener* clickDumpDomListener1 = + new TestBtnOnClickDumpDomListener(static_cast(dumpButton1), "dump_checkbox"); + dumpButton1->SetOnClickListener(clickDumpDomListener1); + + UILabelButton* dumpButton2 = GetLabelButton("dump"); + layout->Add(dumpButton2); + /* dump node here */ + UIView::OnClickListener* clickDumpDomListener2 = + new TestBtnOnClickDumpDomListener(static_cast(dumpButton2), "dump_radiobutton"); + dumpButton2->SetOnClickListener(clickDumpDomListener2); + + UILabelButton* dumpButton3 = GetLabelButton("dump"); + layout->Add(dumpButton3); + /* dump node here */ + UIView::OnClickListener* clickDumpDomListener3 = + new TestBtnOnClickDumpDomListener(static_cast(dumpButton3), "dump_togglebutton"); + dumpButton3->SetOnClickListener(clickDumpDomListener3); + + UILabelButton* dumpButton4 = GetLabelButton("dump_all "); + layout->Add(dumpButton4); + UIView::OnClickListener* clickDumpDomTreeListener1 = + new TestBtnOnClickDumpDomTreeListener(static_cast(dumpButton4), nullptr); + dumpButton4->SetOnClickListener(clickDumpDomTreeListener1); + + UILabelButton* dumpButton5 = GetLabelButton("dump_group"); + layout->Add(dumpButton5); + UIView::OnClickListener* clickDumpDomTreeListener2 = + new TestBtnOnClickDumpDomTreeListener(static_cast(dumpButton5), "dump_group"); + dumpButton5->SetOnClickListener(clickDumpDomTreeListener2); +} + +void UITestDumpDomTree::UIKit_TestDumpDom_002() +{ + if (container_ != nullptr) { + UIViewGroup* group2 = new UIViewGroup(); + group2->SetPosition(Screen::GetInstance().GetWidth() / 2, 0); // 2: half of screen width + group2->SetWidth(Screen::GetInstance().GetWidth() / 2); // 2: half of screen width + group2->SetHeight(200); // 200: height + group2->SetViewId("dump_group2"); + container_->Add(group2); + positionY_ += group2->GetHeight(); + + UILabel* label = GetTitleLabel("dump image"); + group2->Add(label); + label->SetPosition(TEXT_DISTANCE_TO_LEFT_SIDE, TEXT_DISTANCE_TO_TOP_SIDE); + + UIImageView* imageView = new UIImageView(); + imageView->SetPosition(VIEW_DISTANCE_TO_TOP_SIDE, TEXT_DISTANCE_TO_LEFT_SIDE); + imageView->SetWidth(50); // 50: width + imageView->SetHeight(50); // 50: height + imageView->SetSrc(BLUE_RGB888_IMAGE_PATH); + imageView->SetViewId("dump_image"); + group2->Add(imageView); + + UILabelButton* dumpButton1 = GetLabelButton("dump"); + dumpButton1->SetPosition(160, 73); // 160: x-coordinate, 73: y-coordinate + group2->Add(dumpButton1); + dumpButton1->SetText("dump"); + /* dump node here */ + UIView::OnClickListener* clickDumpDomListener1 = + new TestBtnOnClickDumpDomListener(static_cast(dumpButton1), "dump_image"); + dumpButton1->SetOnClickListener(clickDumpDomListener1); + } +} + +void UITestDumpDomTree::UIKit_TestDumpDom_003() +{ + if (container_ != nullptr) { + UIViewGroup* group5 = new UIViewGroup(); + group5->SetPosition(0, 260); // 260: y-coordinate + group5->SetWidth(Screen::GetInstance().GetWidth() / 2); // 2: half of screen width + group5->SetHeight(200); // 200: height + group5->SetViewId("dump_group5"); + container_->Add(group5); + positionY_ += group5->GetHeight(); + + UILabel* label = GetTitleLabel("dump slider progress"); + label->SetPosition(TEXT_DISTANCE_TO_LEFT_SIDE, TEXT_DISTANCE_TO_TOP_SIDE); + group5->Add(label); + + UISlider* slider = new UISlider(); + // 50: new width, 100: new height + slider->SetPosition(TEXT_DISTANCE_TO_LEFT_SIDE, VIEW_DISTANCE_TO_TOP_SIDE, 50, 100); + slider->SetValidHeight(250); // 250: valid height + slider->SetDirection(UISlider::Direction::DIR_BOTTOM_TO_TOP); + slider->SetKnobWidth(50); // 50: knob width + slider->SetRange(100, 0); // 100: max + slider->SetValue(50); // 50: value of progress bar + slider->SetStep(0); + slider->SetBackgroundStyle(STYLE_BACKGROUND_COLOR, Color::GetColorFromRGB(0, 182, 191).full); // 182, 191: color + slider->SetForegroundStyle(STYLE_BACKGROUND_COLOR, Color::GetColorFromRGB(255, 152, 0).full); // 255, 152: color + slider->SetKnobStyle(StyleDefault::GetSliderKnobStyle()); + slider->SetImage(static_cast(nullptr), static_cast(nullptr), + static_cast(nullptr)); + slider->EnableBackground(true); + slider->SetViewId("dump_slider"); + group5->Add(slider); + + UILabelButton* dumpButton = GetLabelButton("dump"); + dumpButton->SetPosition(120, 73); // 120: x-coordinate, 73: y-coordinate + /* dump node here */ + UIView::OnClickListener* clickDumpDomListener = + new TestBtnOnClickDumpDomListener(static_cast(dumpButton), "dump_slider"); + dumpButton->SetOnClickListener(clickDumpDomListener); + group5->Add(dumpButton); + } +} + +void UITestDumpDomTree::UIKit_TestDumpDom_004() +{ + if (container_ != nullptr) { + UIViewGroup* group4 = new UIViewGroup(); + group4->SetPosition(Screen::GetInstance().GetWidth() / 2, 260); // 2: half of screen width; 260: y-coordinate + group4->SetWidth(Screen::GetInstance().GetWidth() / 2); // 2: half of screen width + group4->SetHeight(100); // 100: height + group4->SetViewId("dump_group4"); + container_->Add(group4); + positionY_ += group4->GetHeight(); + + UILabel* label = GetTitleLabel("dump box progress"); + label->SetPosition(TEXT_DISTANCE_TO_LEFT_SIDE, TEXT_DISTANCE_TO_TOP_SIDE); + group4->Add(label); + + UIBoxProgress* boxProgress = new UIBoxProgress(); + // 48: x-coordinate, 68: y-coordinate, 90: new width, 10: new height + boxProgress->SetPosition(48, 68, 90, 10); + boxProgress->SetDirection(UIBoxProgress::Direction::DIR_LEFT_TO_RIGHT); + boxProgress->SetValidHeight(10); // 10: valid height + boxProgress->SetValidWidth(100); // 100: valid width + boxProgress->SetRange(100, 0); // 100: max + boxProgress->SetValue(20); // 20 : value of progress bar + boxProgress->SetStep(0); + // 182, 191: color + boxProgress->SetBackgroundStyle(STYLE_BACKGROUND_COLOR, Color::GetColorFromRGB(0, 182, 191).full); + // 255, 152: color + boxProgress->SetForegroundStyle(STYLE_BACKGROUND_COLOR, Color::GetColorFromRGB(255, 152, 0).full); + boxProgress->SetImage(static_cast(nullptr)); + boxProgress->EnableBackground(true); + boxProgress->SetViewId("dump_boxprogress"); + group4->Add(boxProgress); + UILabelButton* dumpButton = GetLabelButton("dump"); + dumpButton->SetPosition(160, 48); // 160: x-coordinate, 48: y-coordinate + /* dump node here */ + UIView::OnClickListener* clickDumpDomListener = + new TestBtnOnClickDumpDomListener(static_cast(dumpButton), "dump_boxprogress"); + dumpButton->SetOnClickListener(clickDumpDomListener); + group4->Add(dumpButton); + } +} + +void UITestDumpDomTree::UIKit_TestDumpDom_005() +{ + if (container_ != nullptr) { + UIViewGroup* group3 = new UIViewGroup(); + group3->SetPosition(0, 450); // 450: y-coordinate + group3->SetWidth(Screen::GetInstance().GetWidth() / 2 - VIEW_DISTANCE_TO_LEFT_SIDE); // 2: half of screen width + group3->SetHeight(500); // 500: height + group3->SetViewId("dump_group3"); + container_->Add(group3); + + UILabel* label = GetTitleLabel("dump label"); + label->SetPosition(TEXT_DISTANCE_TO_LEFT_SIDE, TEXT_DISTANCE_TO_TOP_SIDE); + group3->Add(label); + + UILabel* label2 = GetTitleLabel("UILabel"); + label2->SetFont(DEFAULT_VECTOR_FONT_FILENAME, BUTTON_LABEL_SIZE); + label2->SetPosition(48, 58, 90, 29); // 48: x position, 58: y position, 90: width, 29: height + label2->SetViewId("dump_label"); + group3->Add(label2); + + UILabelButton* labelButton = GetLabelButton("LabelButton"); + labelButton->SetPosition(48, 108); // 48: x position, 108: y position + labelButton->SetViewId("dump_label_button"); + group3->Add(labelButton); + + UIArcLabel* label3 = new UIArcLabel(); + label3->SetArcTextCenter(70, 330); // 70: x position, 330: y position + label3->SetArcTextRadius(150); // 150: radius + label3->SetArcTextAngle(0, 270); // 0: start angle, 270: end angle + label3->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + label3->SetText("012345678ABCDEF"); + label3->SetStyle(STYLE_LETTER_SPACE, 30); // 30: letter space + label3->SetViewId("dump_arc_label"); + group3->Add(label3); + + UILabelButton* dumpButton1 = GetLabelButton("dump"); + dumpButton1->SetPosition(230, 48); // 230: x-coordinate, 48: y-coordinate + /* dump node here */ + UIView::OnClickListener* clickDumpDomListener1 = + new TestBtnOnClickDumpDomListener(static_cast(dumpButton1), "dump_label"); + dumpButton1->SetOnClickListener(clickDumpDomListener1); + group3->Add(dumpButton1); + + UILabelButton* dumpButton2 = GetLabelButton("dump"); + dumpButton2->SetPosition(230, 108); // 230: x-coordinate, 108: y-coordinate + /* dump node here */ + UIView::OnClickListener* clickDumpDomListener2 = + new TestBtnOnClickDumpDomListener(static_cast(dumpButton2), "dump_label_button"); + dumpButton2->SetOnClickListener(clickDumpDomListener2); + group3->Add(dumpButton2); + + UILabelButton* dumpButton3 = GetLabelButton("dump"); + dumpButton3->SetPosition(230, 310); // 230: x position, 310: y position + /* dump node here */ + UIView::OnClickListener* clickDumpDomListener3 = + new TestBtnOnClickDumpDomListener(static_cast(dumpButton3), "dump_arc_label"); + dumpButton3->SetOnClickListener(clickDumpDomListener3); + group3->Add(dumpButton3); + } +} + +void UITestDumpDomTree::UIKit_TestDumpDom_006() +{ + if (container_ != nullptr) { + UIViewGroup* group6 = new UIViewGroup(); + // 2: half of screen width; 450: screen height + group6->SetPosition(Screen::GetInstance().GetWidth() / 2, 450); + group6->SetWidth(Screen::GetInstance().GetWidth() / 2); // 2: half of screen width + group6->SetHeight(250); // 250: height + group6->SetViewId("dump_group6"); + container_->Add(group6); + positionY_ += group6->GetHeight(); + + UILabel* label = GetTitleLabel("dump circle progress"); + label->SetPosition(TEXT_DISTANCE_TO_LEFT_SIDE, TEXT_DISTANCE_TO_TOP_SIDE); + group6->Add(label); + + UICircleProgress* circleProgress = new UICircleProgress(); + // 40: y-coordinate, 48: y-coordinate, 150: new width, 150: new height + circleProgress->SetPosition(40, 48, 150, 150); + circleProgress->SetCenterPosition(50, 50); // 50: x-coordinate, 50: y-coordinate + circleProgress->SetRadius(50); // 50: radius + circleProgress->SetValue(20); // 20: value of progress bar + circleProgress->SetStyle(STYLE_PADDING_LEFT, 10); // 10: padding left + circleProgress->SetStyle(STYLE_PADDING_TOP, 5); // 5: padding top + circleProgress->SetRange(100, 0); // 100: max + circleProgress->SetValue(50); // 50: value of progress bar + circleProgress->SetStartAngle(0); + circleProgress->SetEndAngle(360); // 360: end angle + circleProgress->SetStep(0); + circleProgress->SetBackgroundStyle(StyleDefault::GetProgressBackgroundStyle()); + circleProgress->SetForegroundStyle(StyleDefault::GetProgressForegroundStyle()); + circleProgress->SetLineColor(Color::White()); + circleProgress->EnableBackground(true); + circleProgress->SetViewId("dump_circle_progress"); + group6->Add(circleProgress); + + UILabelButton* dumpButton = GetLabelButton("dump"); + dumpButton->SetPosition(170, 73); // 170: x-coordinate, 73: y-coordinate + /* dump node here */ + UIView::OnClickListener* clickDumpDomListener = + new TestBtnOnClickDumpDomListener(static_cast(dumpButton), "dump_circle_progress"); + dumpButton->SetOnClickListener(clickDumpDomListener); + group6->Add(dumpButton); + } +} + +UILabelButton* UITestDumpDomTree::GetLabelButton(const char* buttonname) +{ + const int16_t labelButtonWidth = 120; + const int16_t labelButtonHeight = 40; + if (buttonname == nullptr) { + return nullptr; + } + UILabelButton* labelButton = new UILabelButton(); + if (labelButton == nullptr) { + return nullptr; + } + labelButton->SetPosition(0, 0); + labelButton->Resize(labelButtonWidth, labelButtonHeight); + labelButton->SetText(buttonname); + labelButton->SetFont(DEFAULT_VECTOR_FONT_FILENAME, BUTTON_LABEL_SIZE); + labelButton->SetStyleForState(STYLE_BORDER_RADIUS, BUTTON_STYLE_BORDER_RADIUS_VALUE, UIButton::RELEASED); + labelButton->SetStyleForState(STYLE_BORDER_RADIUS, BUTTON_STYLE_BORDER_RADIUS_VALUE, UIButton::PRESSED); + labelButton->SetStyleForState(STYLE_BORDER_RADIUS, BUTTON_STYLE_BORDER_RADIUS_VALUE, UIButton::INACTIVE); + labelButton->SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::RELEASED); + labelButton->SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::PRESSED); + labelButton->SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::INACTIVE); + return labelButton; +} +} // namespace OHOS +#endif // ENABLE_DEBUG diff --git a/test/uitest/test_ui_dump_dom_tree/ui_test_dump_dom.h b/test/uitest/test_ui_dump_dom_tree/ui_test_dump_dom.h new file mode 100755 index 0000000..e7dab22 --- /dev/null +++ b/test/uitest/test_ui_dump_dom_tree/ui_test_dump_dom.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GRAPHIC_LITE_UI_TEST_DUMP_DOM_H +#define GRAPHIC_LITE_UI_TEST_DUMP_DOM_H + +#include "graphic_config.h" + +#if ENABLE_DEBUG +#include "components/ui_label_button.h" +#include "components/ui_scroll_view.h" +#include "layout/grid_layout.h" +#include "ui_test.h" + +namespace OHOS { +class UITestDumpDomTree : public UITest { +public: + UITestDumpDomTree() {} + ~UITestDumpDomTree() {} + void SetUp() override; + void TearDown() override; + const UIView* GetTestView() override; + void CreateButtons(GridLayout* layout); + + void UIKit_TestDumpDom_001(); + void UIKit_TestDumpDom_002(); + void UIKit_TestDumpDom_003(); + void UIKit_TestDumpDom_004(); + void UIKit_TestDumpDom_005(); + void UIKit_TestDumpDom_006(); + UILabelButton* GetLabelButton(const char* buttonname); + +private: + UIScrollView* container_ = nullptr; +}; +} +#endif // ENABLE_DEBUG +#endif // GRAPHIC_LITE_UI_TEST_DUMP_DOM_H + diff --git a/test/uitest/test_ui_list/ui_test_ui_list.cpp b/test/uitest/test_ui_list/ui_test_ui_list.cpp new file mode 100755 index 0000000..1a53ca4 --- /dev/null +++ b/test/uitest/test_ui_list/ui_test_ui_list.cpp @@ -0,0 +1,367 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ui_test_ui_list.h" +#include "common/screen.h" +#include "components/ui_label.h" +#include "components/ui_label_button.h" +#include "font/ui_font.h" + +namespace OHOS { +namespace { + static int16_t g_buttonH = 80; + static int16_t g_buttonW = 200; + static int16_t g_blank = 20; + static int16_t g_listW = 200; + static int16_t g_listH = 400; + static int16_t g_selectPos = 150; + static int16_t g_blankSize = 250; +} + +void UITestUIList::SetUp() +{ + if (container_ == nullptr) { + container_ = new UIScrollView(); + container_->Resize(Screen::GetInstance().GetWidth(), Screen::GetInstance().GetHeight() - BACK_BUTTON_HEIGHT); + container_->SetThrowDrag(true); + } + if (adapterData1_ == nullptr) { + adapterData1_ = new List(); + adapterData1_->PushBack("abcd0"); + adapterData1_->PushBack("abcd1"); + adapterData1_->PushBack("abcd2"); + adapterData1_->PushBack("abcd3"); + } + if (adapterData2_ == nullptr) { + adapterData2_ = new List(); + adapterData2_->PushBack("000"); + adapterData2_->PushBack("111"); + adapterData2_->PushBack("222"); + adapterData2_->PushBack("333"); + adapterData2_->PushBack("444"); + adapterData2_->PushBack("555"); + adapterData2_->PushBack("666"); + adapterData2_->PushBack("777"); + adapterData2_->PushBack("888"); + adapterData2_->PushBack("999"); + } +} + +void UITestUIList::TearDown() +{ + DeleteChildren(container_); + container_ = nullptr; + if (adapterData1_ != nullptr) { + adapterData1_->Clear(); + delete adapterData1_; + adapterData1_ = nullptr; + } + if (adapterData2_ != nullptr) { + adapterData2_->Clear(); + delete adapterData2_; + adapterData2_ = nullptr; + } + if (adapter1_ != nullptr) { + delete adapter1_; + adapter1_ = nullptr; + } + if (adapter2_ != nullptr) { + delete adapter2_; + adapter2_ = nullptr; + } + if (adapter3_ != nullptr) { + delete adapter3_; + adapter3_ = nullptr; + } + if (adapter4_ != nullptr) { + delete adapter4_; + adapter4_ = nullptr; + } + setBlankBtn_ = nullptr; + setBlankOffBtn_ = nullptr; + setThrowDragBtn_ = nullptr; + setThrowDragOffBtn_ = nullptr; + scrollStateLabel_ = nullptr; + scrollSelectLabel_ = nullptr; + setRefreshBtn_ = nullptr; + setLoopBtn_ = nullptr; + setLoopOffBtn_ = nullptr; + setSelectBtn_ = nullptr; + setSelectOffBtn_ = nullptr; + setAutoAlignBtn_ = nullptr; + setAutoAlignOffBtn_ = nullptr; + lastX_ = 0; + lastY_ = 0; +} + +const UIView* UITestUIList::GetTestView() +{ + UIKit_List_Init_Test_Full_Screen_001(); + UIKit_List_Init_Test_halh_Screen_001(); + UIKit_List_Scroll_Test_Blank_Set_001(); + return container_; +} + +void UITestUIList::UIKit_List_Init_Test_Full_Screen_001() +{ + if (container_ == nullptr) { + return; + } + UILabel* label = GetTitleLabel("UILIst设置adapter数据填满list"); + container_->Add(label); + label->SetPosition(TEXT_DISTANCE_TO_LEFT_SIDE, TEXT_DISTANCE_TO_TOP_SIDE); + + if (adapter1_ == nullptr) { + adapter1_ = new TextAdapter(); + } + adapter1_->SetData(adapterData2_); + + UIList* list = new UIList(UIList::VERTICAL); + list->SetStyle(STYLE_BACKGROUND_COLOR, Color::Red().full); + list->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, VIEW_DISTANCE_TO_TOP_SIDE, g_listW, + g_listH - 200); // 200: mean list reduce height + list->SetStartIndex(5); // 5: list start index + list->GetStartIndex(); + list->SetAdapter(adapter1_); + container_->Add(list); +} + +void UITestUIList::UIKit_List_Init_Test_halh_Screen_001() +{ + if (container_ == nullptr) { + return; + } + UILabel* label = GetTitleLabel("UILIst设置adapter数据不填满list"); + container_->Add(label); + // 2: half of screen width + label->SetPosition(Screen::GetInstance().GetWidth() / 2 + TEXT_DISTANCE_TO_LEFT_SIDE, TEXT_DISTANCE_TO_TOP_SIDE); + + if (adapter2_ == nullptr) { + adapter2_ = new TextAdapter(); + } + adapter2_->SetData(adapterData1_); + + UIList* list = new UIList(UIList::VERTICAL); + list->SetStyle(STYLE_BACKGROUND_COLOR, Color::Red().full); + // 2: half of screen width + list->SetPosition(Screen::GetInstance().GetWidth() / 2 + VIEW_DISTANCE_TO_LEFT_SIDE, VIEW_DISTANCE_TO_TOP_SIDE, + g_listW, g_listH - 150); // 150: decrease height + list->SetAdapter(adapter2_); + container_->Add(list); +} + +void UITestUIList::SetControlButton() +{ + if (setBlankBtn_ == nullptr) { + setBlankBtn_ = new UILabelButton(); + } + if (setBlankOffBtn_ == nullptr) { + setBlankOffBtn_ = new UILabelButton(); + } + if (setThrowDragBtn_ == nullptr) { + setThrowDragBtn_ = new UILabelButton(); + } + if (setThrowDragOffBtn_ == nullptr) { + setThrowDragOffBtn_ = new UILabelButton(); + } + if (setRefreshBtn_ == nullptr) { + setRefreshBtn_ = new UILabelButton(); + } + if (setLoopBtn_ == nullptr) { + setLoopBtn_ = new UILabelButton(); + } + if (setLoopOffBtn_ == nullptr) { + setLoopOffBtn_ = new UILabelButton(); + } + if (setSelectBtn_ == nullptr) { + setSelectBtn_ = new UILabelButton(); + } + if (setSelectOffBtn_ == nullptr) { + setSelectOffBtn_ = new UILabelButton(); + } + if (setAutoAlignBtn_ == nullptr) { + setAutoAlignBtn_ = new UILabelButton(); + } + if (setAutoAlignOffBtn_ == nullptr) { + setAutoAlignOffBtn_ = new UILabelButton(); + } + + positionX_ += 5; // 5: increase y-coordinate + SetUpButton(setBlankBtn_, "开启blank"); + SetUpButton(setBlankOffBtn_, "关闭blank"); + SetUpButton(setThrowDragBtn_, "开启throwDrag"); + SetUpButton(setThrowDragOffBtn_, "关闭throwDrag"); + SetUpButton(setRefreshBtn_, "刷新list"); + SetUpButton(setLoopBtn_, "开启loop模式"); + SetUpButton(setLoopOffBtn_, "关闭loop模式"); + positionX_ = setBlankBtn_->GetX() + setBlankBtn_->GetWidth() + g_blank - VIEW_DISTANCE_TO_LEFT_SIDE; + positionY_ = setBlankBtn_->GetY(); + SetUpButton(setSelectBtn_, "select 150"); + SetUpButton(setSelectOffBtn_, "关闭select"); + SetUpButton(setAutoAlignBtn_, "开启自动对齐 "); + SetUpButton(setAutoAlignOffBtn_, "关闭自动对齐 "); +} + +void UITestUIList::UIKit_List_Scroll_Test_Blank_Set_001() +{ + if (container_ == nullptr) { + return; + } + UILabel* label = GetTitleLabel("UILIst设置blank和throwDrag"); + container_->Add(label); + label->SetPosition(TEXT_DISTANCE_TO_LEFT_SIDE, g_listH - 132); // 132: decrease y-coordinate + + if (adapter4_ == nullptr) { + adapter4_ = new TextAdapter(); + } + adapter4_->SetLineBreakMode(UILabel::LINE_BREAK_CLIP); + adapter4_->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + adapter4_->SetHeight(100); // 100: mean adapter height + adapter4_->SetWidth(100); // 100: mean adapter width + adapter4_->SetData(adapterData2_); + + UIList* list = new UIList(UIList::VERTICAL); + list->SetStyle(STYLE_BACKGROUND_COLOR, Color::Red().full); + list->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, g_listH - 95, g_listW, g_listH); // 95: decrease y-coordinate + list->SetAdapter(adapter4_); + list->SetScrollStateListener(this); + currentList_ = list; + + if (scrollStateLabel_ == nullptr) { + scrollStateLabel_ = new UILabel(); + } + if (scrollSelectLabel_ == nullptr) { + scrollSelectLabel_ = new UILabel(); + } + + scrollStateLabel_->SetLineBreakMode(UILabel::LINE_BREAK_ADAPT); + scrollSelectLabel_->SetLineBreakMode(UILabel::LINE_BREAK_ADAPT); + scrollStateLabel_->SetText("STOP"); + scrollSelectLabel_->SetText("NULL"); + + scrollStateLabel_->SetPosition(list->GetWidth() + 53, list->GetY()); // 53: increase x-coordinate + // 53: increase x-coordinate + scrollSelectLabel_->SetPosition(list->GetWidth() + scrollStateLabel_->GetWidth() + g_blank + 53, list->GetY()); + if (list->GetSelectView() != nullptr) { + scrollSelectLabel_->SetText(static_cast(list->GetSelectView())->GetText()); + } + positionX_ = list->GetWidth(); + positionY_ = scrollStateLabel_->GetY() + scrollStateLabel_->GetHeight(); + SetControlButton(); + + container_->Add(list); + UIView* selectBtnPos = new UIView(); + selectBtnPos->SetStyle(STYLE_BACKGROUND_OPA, OPA_OPAQUE); + selectBtnPos->SetStyle(STYLE_BACKGROUND_COLOR, Color::White().full); + selectBtnPos->SetStyle(STYLE_BORDER_WIDTH, 0); + selectBtnPos->SetStyle(STYLE_BORDER_RADIUS, 0); + selectBtnPos->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, list->GetY() + g_selectPos, 100, 1); // 100: mean view width + container_->Add(selectBtnPos); + container_->Add(scrollStateLabel_); + container_->Add(scrollSelectLabel_); + SetLastPos(list); +} + +bool UITestUIList::OnClick(UIView& view, const ClickEvent& event) +{ + if (currentList_ == nullptr) { + return true; + } + if (&view == setBlankBtn_) { + currentList_->SetScrollBlankSize(g_blankSize); + } else if (&view == setBlankOffBtn_) { + currentList_->SetScrollBlankSize(0); + } else if (&view == setThrowDragBtn_) { + currentList_->SetThrowDrag(true); + } else if (&view == setThrowDragOffBtn_) { + currentList_->SetThrowDrag(false); + } else if (&view == setRefreshBtn_) { + adapter4_->SetData(adapterData1_); + currentList_->RefreshList(); + } else if (&view == setLoopBtn_) { + currentList_->SetLoopState(true); + } else if (&view == setLoopOffBtn_) { + currentList_->SetLoopState(false); + } else if (&view == setSelectBtn_) { + currentList_->SetSelectPosition(g_selectPos); + } else if (&view == setSelectOffBtn_) { + currentList_->SetSelectPosition(0); + } else if (&view == setAutoAlignBtn_) { + currentList_->EnableAutoAlign(true); + } else if (&view == setAutoAlignOffBtn_) { + currentList_->EnableAutoAlign(false); + } + return true; +} + +void UITestUIList::OnScrollStart(int16_t index, UIView* view) +{ + if (GetScrollState() == SCROLL_STATE_STOP) { + scrollStateLabel_->SetText("STOP"); + } else { + scrollStateLabel_->SetText("MOVE"); + } + + scrollStateLabel_->Invalidate(); +} + +void UITestUIList::OnScrollEnd(int16_t index, UIView* view) +{ + if (GetScrollState() == SCROLL_STATE_STOP) { + scrollStateLabel_->SetText("STOP"); + } else { + scrollStateLabel_->SetText("MOVE"); + } + scrollStateLabel_->Invalidate(); +} +void UITestUIList::OnItemSelected(int16_t index, UIView* view) +{ + if (view != nullptr) { + scrollSelectLabel_->SetText(static_cast(view)->GetText()); + view->Invalidate(); + } else { + scrollSelectLabel_->SetText("NULL"); + } +} + +void UITestUIList::SetUpButton(UILabelButton* btn, const char* title) +{ + if (btn == nullptr) { + return; + } + container_->Add(btn); + btn->SetPosition(positionX_ + VIEW_DISTANCE_TO_LEFT_SIDE, positionY_, BUTTON_WIDHT3, BUTTON_HEIGHT3); + positionY_ += btn->GetHeight() + 10; // 10: button interval + btn->SetText(title); + btn->SetFont(DEFAULT_VECTOR_FONT_FILENAME, BUTTON_LABEL_SIZE); + btn->SetOnClickListener(this); + btn->SetStyleForState(STYLE_BORDER_RADIUS, BUTTON_STYLE_BORDER_RADIUS_VALUE, UIButton::RELEASED); + btn->SetStyleForState(STYLE_BORDER_RADIUS, BUTTON_STYLE_BORDER_RADIUS_VALUE, UIButton::PRESSED); + btn->SetStyleForState(STYLE_BORDER_RADIUS, BUTTON_STYLE_BORDER_RADIUS_VALUE, UIButton::INACTIVE); + btn->SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::RELEASED); + btn->SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::PRESSED); + btn->SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::INACTIVE); + container_->Invalidate(); +} + +void UITestUIList::SetLastPos(UIView* view) +{ + if (view == nullptr) { + return; + } + lastX_ = view->GetX(); + lastY_ = view->GetY() + view->GetHeight(); +} +} // namespace OHOS \ No newline at end of file diff --git a/test/uitest/test_ui_list/ui_test_ui_list.h b/test/uitest/test_ui_list/ui_test_ui_list.h new file mode 100755 index 0000000..f969d2e --- /dev/null +++ b/test/uitest/test_ui_list/ui_test_ui_list.h @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef UI_TEST_UI_LIST_H +#define UI_TEST_UI_LIST_H + +#include "components/text_adapter.h" +#include "components/ui_label.h" +#include "components/ui_label_button.h" +#include "components/ui_list.h" +#include "components/ui_scroll_view.h" +#include "ui_test.h" + +namespace OHOS { +class UITestUIList : public UITest, UIView::OnClickListener, ListScrollListener { +public: + UITestUIList() {} + ~UITestUIList() {} + void SetUp() override; + void TearDown() override; + const UIView* GetTestView() override; + + bool OnClick(UIView& view, const ClickEvent& event) override; + void OnScrollStart(int16_t index, UIView* view) override; + void OnScrollEnd(int16_t index, UIView* view) override; + void OnItemSelected(int16_t index, UIView* view) override; + void UIKit_List_Init_Test_Full_Screen_001(); + void UIKit_List_Init_Test_halh_Screen_001(); + void UIKit_List_Scroll_Test_Blank_Set_001(); + +private: + void SetLastPos(UIView* view); + void SetUpButton(UILabelButton* btn, const char* title); + void SetControlButton(); + UILabelButton* setBlankBtn_ = nullptr; + UILabelButton* setBlankOffBtn_ = nullptr; + UILabelButton* setThrowDragBtn_ = nullptr; + UILabelButton* setThrowDragOffBtn_ = nullptr; + UILabelButton* setRefreshBtn_ = nullptr; + UILabelButton* setLoopBtn_ = nullptr; + UILabelButton* setLoopOffBtn_ = nullptr; + UILabelButton* setSelectBtn_ = nullptr; + UILabelButton* setSelectOffBtn_ = nullptr; + UILabelButton* setAutoAlignBtn_ = nullptr; + UILabelButton* setAutoAlignOffBtn_ = nullptr; + UILabel* scrollStateLabel_ = nullptr; + UILabel* scrollSelectLabel_ = nullptr; + UIScrollView* container_ = nullptr; + List* adapterData1_ = nullptr; + List* adapterData2_ = nullptr; + TextAdapter* adapter1_ = nullptr; + TextAdapter* adapter2_ = nullptr; + TextAdapter* adapter3_ = nullptr; + TextAdapter* adapter4_ = nullptr; + UIList* currentList_ = nullptr; + UIList* currentList2_ = nullptr; + UILabel* lastSelectLabel_ = nullptr; + int16_t lastX_ = 0; + int16_t lastY_ = 0; +}; +} // namespace OHOS +#endif // UI_TEST_UI_LIST_H diff --git a/test/uitest/test_ui_list_view/ui_test_list_layout.cpp b/test/uitest/test_ui_list_view/ui_test_list_layout.cpp new file mode 100755 index 0000000..f391a7a --- /dev/null +++ b/test/uitest/test_ui_list_view/ui_test_list_layout.cpp @@ -0,0 +1,242 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ui_test_list_layout.h" +#include "common/screen.h" +#include "components/ui_label.h" +#include "components/ui_label_button.h" +#include "layout/list_layout.h" + +namespace OHOS { +namespace { + static int16_t g_buttonH = 80; + static int16_t g_buttonW = 200; + static int16_t g_blank = 20; + static int16_t g_gap = 8; +} + +void UITestListLayout::SetUp() +{ + if (container_ == nullptr) { + container_ = new UIScrollView(); + container_->Resize(Screen::GetInstance().GetWidth(), Screen::GetInstance().GetHeight() - BACK_BUTTON_HEIGHT); + container_->SetThrowDrag(true); + } +} + +void UITestListLayout::TearDown() +{ + DeleteChildren(container_); + container_ = nullptr; + positionX_ = 0; + positionY_ = 0; + listDirect_ = nullptr; +} + +const UIView* UITestListLayout::GetTestView() +{ + UIKit_List_Vertical_Add_Test_Auto_Size_001(); + UIKit_List_Vertical_Remove_Test_Auto_Size_001(); + UIKit_List_Vertical_Remove_Test_Auto_Size_002(); + UIKit_List_Hor_Add_Test_Auto_Size_001(); + UIKit_List_Hor_Remove_Test_Auto_Size_001(); + UIKit_List_Hor_Remove_Test_Auto_Size_002(); + return container_; +} + +void UITestListLayout::UIKit_List_Vertical_Add_Test_Auto_Size_001() +{ + if (container_ == nullptr) { + return; + } + UILabel* label = GetTitleLabel("UILIst高度自适应 Add Insert"); + container_->Add(label); + label->SetPosition(TEXT_DISTANCE_TO_LEFT_SIDE, positionY_ + g_blank); + + ListLayout* list = new ListLayout(); + list->SetDirection(ListLayout::VERTICAL); + list->SetStyle(STYLE_BACKGROUND_COLOR, Color::Red().full); + list->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, label->GetY() + TITLE_LABEL_DEFAULT_HEIGHT + g_gap); + list->Resize(Screen::GetInstance().GetWidth() - 100, 0); // 100: list reduce width + container_->Add(list); + + UILabelButton* button1 = new UILabelButton(); + button1->SetPosition(0, 0, g_buttonW, g_buttonH); + button1->SetText("button1"); + list->Add(button1); + UILabelButton* button2 = new UILabelButton(); + button2->SetPosition(0, 0, g_buttonW, g_buttonH); + button2->SetText("button2"); + list->Insert(button1, button2); + + if (listDirect_ == nullptr) { + listDirect_ = new UILabel(); + } + listDirect_->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + listDirect_->SetPosition(list->GetX(), list->GetY(), 100, 100); // 100: list dir width and height + if (list->GetDirection() == ListLayout::VERTICAL) { + listDirect_->SetText("VERTICAL"); + } else { + listDirect_->SetText("HROIZONTAL"); + } + + container_->Add(listDirect_); + + SetLastPos(list); +} + +void UITestListLayout::UIKit_List_Vertical_Remove_Test_Auto_Size_001() +{ + if (container_ == nullptr) { + return; + } + UILabel* label = GetTitleLabel("UILIst高度自适应 Remove"); + container_->Add(label); + label->SetPosition(TEXT_DISTANCE_TO_LEFT_SIDE, positionY_ + g_blank); + + ListLayout* list = new ListLayout(ListLayout::VERTICAL); + list->SetStyle(STYLE_BACKGROUND_COLOR, Color::Red().full); + + list->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, label->GetY() + TITLE_LABEL_DEFAULT_HEIGHT + g_gap, + Screen::GetInstance().GetWidth() - 100, 0); // 100: list reduce width + container_->Add(list); + UILabelButton* button1 = new UILabelButton(); + button1->SetPosition(0, 0, g_buttonW, g_buttonH); + button1->SetText("button1"); + list->Add(button1); + UILabelButton* button2 = new UILabelButton(); + button2->SetPosition(0, 0, g_buttonW, g_buttonH); + button2->SetText("button2"); + list->Add(button2); + list->Remove(button1); + SetLastPos(list); +} + +void UITestListLayout::UIKit_List_Vertical_Remove_Test_Auto_Size_002() +{ + if (container_ == nullptr) { + return; + } + UILabel* label = GetTitleLabel("UILIst高度自适应 RemoveAll"); + container_->Add(label); + label->SetPosition(TEXT_DISTANCE_TO_LEFT_SIDE, positionY_ + g_blank); + + ListLayout* list = new ListLayout(ListLayout::VERTICAL); + list->SetStyle(STYLE_BACKGROUND_COLOR, Color::Red().full); + + list->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, label->GetY() + TITLE_LABEL_DEFAULT_HEIGHT + g_gap, + Screen::GetInstance().GetWidth() - 100, 0); // 100: list reduce width + container_->Add(list); + UILabelButton* button1 = new UILabelButton(); + button1->SetPosition(0, 0, g_buttonW, g_buttonH); + button1->SetText("button1"); + list->Add(button1); + UILabelButton* button2 = new UILabelButton(); + button2->SetPosition(0, 0, g_buttonW, g_buttonH); + button2->SetText("button2"); + list->Add(button2); + list->RemoveAll(); + SetLastPos(list); +} + +void UITestListLayout::UIKit_List_Hor_Add_Test_Auto_Size_001() +{ + if (container_ == nullptr) { + return; + } + UILabel* label = GetTitleLabel("UILIst宽度自适应 Add Insert"); + container_->Add(label); + label->SetPosition(TEXT_DISTANCE_TO_LEFT_SIDE, positionY_ + g_blank); + + ListLayout* list = new ListLayout(ListLayout::HORIZONTAL); + list->SetStyle(STYLE_BACKGROUND_COLOR, Color::Red().full); + + list->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, label->GetY() + TITLE_LABEL_DEFAULT_HEIGHT + g_gap, 0, + Screen::GetInstance().GetHeight()); + container_->Add(list); + UILabelButton* button1 = new UILabelButton(); + button1->SetPosition(0, 0, g_buttonW, g_buttonH); + button1->SetText("button1"); + list->Add(button1); + UILabelButton* button2 = new UILabelButton(); + button2->SetPosition(0, 0, g_buttonW, g_buttonH); + button2->SetText("button2"); + list->Insert(button1, button2); + SetLastPos(list); +} + +void UITestListLayout::UIKit_List_Hor_Remove_Test_Auto_Size_001() +{ + if (container_ == nullptr) { + return; + } + UILabel* label = GetTitleLabel("UILIst宽度自适应 Remove"); + container_->Add(label); + label->SetPosition(TEXT_DISTANCE_TO_LEFT_SIDE, positionY_ + g_blank); + + ListLayout* list = new ListLayout(ListLayout::HORIZONTAL); + list->SetStyle(STYLE_BACKGROUND_COLOR, Color::Red().full); + + list->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, label->GetY() + TITLE_LABEL_DEFAULT_HEIGHT + g_gap, 0, + Screen::GetInstance().GetHeight()); + container_->Add(list); + UILabelButton* button1 = new UILabelButton(); + button1->SetPosition(0, 0, g_buttonW, g_buttonH); + button1->SetText("button1"); + list->Add(button1); + UILabelButton* button2 = new UILabelButton(); + button2->SetPosition(0, 0, g_buttonW, g_buttonH); + button2->SetText("button2"); + list->Add(button2); + list->Remove(button1); + SetLastPos(list); +} + +void UITestListLayout::UIKit_List_Hor_Remove_Test_Auto_Size_002() +{ + if (container_ == nullptr) { + return; + } + UILabel* label = GetTitleLabel("UILIst宽度自适应 RemoveAll"); + container_->Add(label); + label->SetPosition(TEXT_DISTANCE_TO_LEFT_SIDE, positionY_ + g_blank); + + ListLayout* list = new ListLayout(ListLayout::HORIZONTAL); + list->SetStyle(STYLE_BACKGROUND_COLOR, Color::Red().full); + + list->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, label->GetY() + TITLE_LABEL_DEFAULT_HEIGHT + g_gap, 0, + Screen::GetInstance().GetHeight()); + container_->Add(list); + UILabelButton* button1 = new UILabelButton(); + button1->SetPosition(0, 0, g_buttonW, g_buttonH); + button1->SetText("button1"); + list->Add(button1); + UILabelButton* button2 = new UILabelButton(); + button2->SetPosition(0, 0, g_buttonW, g_buttonH); + button2->SetText("button2"); + list->Add(button2); + list->RemoveAll(); + SetLastPos(list); +} + +void UITestListLayout::SetLastPos(UIView* view) +{ + if (view == nullptr) { + return; + } + positionX_ = view->GetX(); + positionY_ = view->GetY() + view->GetHeight(); +} +} // namespace OHOS \ No newline at end of file diff --git a/test/uitest/test_ui_list_view/ui_test_list_layout.h b/test/uitest/test_ui_list_view/ui_test_list_layout.h new file mode 100755 index 0000000..3599d93 --- /dev/null +++ b/test/uitest/test_ui_list_view/ui_test_list_layout.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef UI_TEST_UI_LIST_VIEW_H +#define UI_TEST_UI_LIST_VIEW_H + +#include "components/ui_label.h" +#include "components/ui_scroll_view.h" +#include "ui_test.h" + +namespace OHOS { +class UITestListLayout : public UITest { +public: + UITestListLayout() {} + ~UITestListLayout() {} + void SetUp() override; + void TearDown() override; + const UIView* GetTestView() override; + + void UIKit_List_Vertical_Add_Test_Auto_Size_001(); + void UIKit_List_Vertical_Remove_Test_Auto_Size_001(); + void UIKit_List_Vertical_Remove_Test_Auto_Size_002(); + void UIKit_List_Hor_Add_Test_Auto_Size_001(); + void UIKit_List_Hor_Remove_Test_Auto_Size_001(); + void UIKit_List_Hor_Remove_Test_Auto_Size_002(); + +private: + void SetLastPos(UIView* view); + UIScrollView* container_ = nullptr; + int16_t lastX_ = 0; + int16_t lastY_ = 0; + UILabel* listDirect_ = nullptr; +}; +} // namespace OHOS +#endif // UI_TEST_INPUT_EVENT_H diff --git a/test/uitest/test_ui_scroll_view/ui_test_ui_scroll_view.cpp b/test/uitest/test_ui_scroll_view/ui_test_ui_scroll_view.cpp new file mode 100755 index 0000000..a54998a --- /dev/null +++ b/test/uitest/test_ui_scroll_view/ui_test_ui_scroll_view.cpp @@ -0,0 +1,318 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ui_test_ui_scroll_view.h" +#include "common/screen.h" +#include "components/ui_label_button.h" + +namespace OHOS { +namespace { +static int16_t g_buttonH = 400; +static int16_t g_buttonW = 400; +static int16_t g_smallButtonH = 40; +static int16_t g_smallButtonW = 40; +static int16_t g_blank = 20; +static int16_t g_scrollH = 300; +static int16_t g_scrollW = 300; +static UILabel* g_scrollSateLabel = nullptr; +const int16_t DELTA_Y_COORDINATE = 19; +const int16_t DELTA_Y_COORDINATE_2 = 37; +} // namespace + +class TestOnScrollListener : public UIScrollView::OnScrollListener { + void OnScrollStart() override + { + g_scrollSateLabel->SetText("moving"); + g_scrollSateLabel->Invalidate(); + } + + void OnScrollEnd() override + { + g_scrollSateLabel->SetText("stop"); + g_scrollSateLabel->Invalidate(); + } +}; + +static TestOnScrollListener* g_listener = nullptr; + +void UITestUIScrollView::SetUp() +{ + if (container_ == nullptr) { + container_ = new UIScrollView(); + container_->Resize(Screen::GetInstance().GetWidth(), Screen::GetInstance().GetHeight() - BACK_BUTTON_HEIGHT); + container_->SetThrowDrag(true); + } + if (g_scrollSateLabel == nullptr) { + g_scrollSateLabel = GetTitleLabel("stop"); + g_scrollSateLabel->SetPosition(0, 0, 100, TITLE_LABEL_DEFAULT_HEIGHT); // 100: width + g_scrollSateLabel->SetLineBreakMode(UILabel::LINE_BREAK_ADAPT); + } + + if (g_listener == nullptr) { + g_listener = new TestOnScrollListener(); + } +} + +void UITestUIScrollView::TearDown() +{ + container_->Remove(g_scrollSateLabel); + DeleteChildren(container_); + container_ = nullptr; + if (g_scrollSateLabel != nullptr) { + delete g_scrollSateLabel; + g_scrollSateLabel = nullptr; + } + if (g_listener != nullptr) { + delete g_listener; + g_listener = nullptr; + } + lastX_ = 0; + lastY_ = 0; +} + +const UIView* UITestUIScrollView::GetTestView() +{ + UIKit_UIScrollView_Test_bar_001(); + UIKit_UIScrollView_Test_bar_002(); + UIKit_UIScrollView_Test_bar_003(); + UIKit_UIScrollView_Test_bar_004(); + UIKit_UIScrollView_Test_Scrollable_Set_001(); + UIKit_UIScrollView_Test_Scrollable_Set_002(); + UIKit_UIScrollView_Test_Scroll_Blank_Set_001(); + UIKit_UIScrollView_Test_Scroll_listener_001(); + return container_; +} + +void UITestUIScrollView::UIKit_UIScrollView_Test_bar_001() +{ + if (container_ == nullptr) { + return; + } + positionX_ = TEXT_DISTANCE_TO_LEFT_SIDE; + positionY_ = TEXT_DISTANCE_TO_TOP_SIDE; + UILabel* label = GetTitleLabel("UIScrollView设置游标不可见 "); + label->SetPosition(positionX_, positionY_); + container_->Add(label); + positionY_ += DELTA_Y_COORDINATE_2; + + UIScrollView* scroll = new UIScrollView(); + scroll->SetStyle(STYLE_BACKGROUND_COLOR, Color::Red().full); + scroll->SetPosition(positionX_, positionY_, g_scrollW, g_scrollH); + scroll->SetXScrollBarVisible(false); + scroll->SetYScrollBarVisible(false); + container_->Add(scroll); + UILabelButton* button1 = new UILabelButton(); + button1->SetPosition(0, 0, g_buttonW, g_buttonH); + button1->SetText("button1"); + scroll->Add(button1); + SetLastPos(scroll); + positionY_ += g_scrollH; +} + +void UITestUIScrollView::UIKit_UIScrollView_Test_bar_002() +{ + if (container_ == nullptr) { + return; + } + UILabel* label = GetTitleLabel("UIScrollView设置游标可见 "); + container_->Add(label); + positionY_ += DELTA_Y_COORDINATE; + label->SetPosition(positionX_, positionY_); + positionY_ += DELTA_Y_COORDINATE_2; + + UIScrollView* scroll = new UIScrollView(); + scroll->SetStyle(STYLE_BACKGROUND_COLOR, Color::Red().full); + scroll->SetPosition(positionX_, positionY_, g_scrollW, g_scrollH); + scroll->SetXScrollBarVisible(true); + scroll->SetYScrollBarVisible(true); + container_->Add(scroll); + UIButton* button1 = new UIButton(); + button1->SetPosition(0, 0, g_smallButtonW, g_smallButtonH); + scroll->Add(button1); + SetLastPos(scroll); + positionY_ += g_scrollH; +} + +void UITestUIScrollView::UIKit_UIScrollView_Test_bar_003() +{ + if (container_ == nullptr) { + return; + } + UILabel* label = GetTitleLabel("UIScrollView游标滑动状态 "); + container_->Add(label); + positionY_ += DELTA_Y_COORDINATE; + label->SetPosition(positionX_, positionY_); + positionY_ += DELTA_Y_COORDINATE_2; + + UIScrollView* scroll = new UIScrollView(); + scroll->SetStyle(STYLE_BACKGROUND_COLOR, Color::Red().full); + scroll->SetPosition(positionX_, positionY_, g_scrollW, g_scrollH); + scroll->SetXScrollBarVisible(true); + scroll->SetYScrollBarVisible(true); + container_->Add(scroll); + UILabelButton* button1 = new UILabelButton(); + button1->SetText("button1"); + button1->SetPosition(0, 0, g_buttonW, g_buttonH); + UILabelButton* button2 = new UILabelButton(); + button2->SetText("button2"); + button2->SetPosition(0, g_buttonH, g_buttonW, g_buttonH); + scroll->Add(button1); + scroll->Add(button2); + SetLastPos(scroll); + positionY_ += g_scrollH; +} + +void UITestUIScrollView::UIKit_UIScrollView_Test_bar_004() +{ + if (container_ == nullptr) { + return; + } + UILabel* label = GetTitleLabel("UIScrollView设置游标宽度为20"); + container_->Add(label); + positionY_ += DELTA_Y_COORDINATE; + label->SetPosition(positionX_, positionY_); + positionY_ += DELTA_Y_COORDINATE_2; + + UIScrollView* scroll = new UIScrollView(); + scroll->SetStyle(STYLE_BACKGROUND_COLOR, Color::Red().full); + scroll->SetPosition(positionX_, positionY_, g_scrollW, g_scrollH); + scroll->SetXScrollBarVisible(true); + scroll->SetYScrollBarVisible(true); + scroll->SetScrollbarWidth(20); // 20: means scroll bar width + container_->Add(scroll); + UIButton* button1 = new UIButton(); + button1->SetPosition(0, 0, g_smallButtonW, g_smallButtonH); + scroll->Add(button1); + SetLastPos(scroll); + positionY_ += g_scrollH; +} + +void UITestUIScrollView::UIKit_UIScrollView_Test_Scrollable_Set_001() +{ + if (container_ == nullptr) { + return; + } + positionX_ = Screen::GetInstance().GetWidth() / 2 + TEXT_DISTANCE_TO_LEFT_SIDE; // 2: half of screen width + positionY_ = TEXT_DISTANCE_TO_TOP_SIDE; + UILabel* label = GetTitleLabel("UIScrollView设置仅垂直方向可滑动 "); + container_->Add(label); + label->SetPosition(positionX_, positionY_); + positionY_ += DELTA_Y_COORDINATE_2; + + UIScrollView* scroll = new UIScrollView(); + scroll->SetStyle(STYLE_BACKGROUND_COLOR, Color::Red().full); + scroll->SetPosition(positionX_, positionY_, g_scrollW, g_scrollH); + scroll->SetScrollBlankSize(20); // 20: means scroll blank size + scroll->SetHorizontalScrollState(false); + scroll->SetVerticalScrollState(true); + scroll->SetXScrollBarVisible(true); + scroll->SetYScrollBarVisible(true); + container_->Add(scroll); + UILabelButton* button1 = new UILabelButton(); + button1->SetPosition(0, 0, g_buttonW, g_buttonH); + button1->SetText("button1"); + scroll->Add(button1); + SetLastPos(scroll); + positionY_ += g_scrollH; +} + +void UITestUIScrollView::UIKit_UIScrollView_Test_Scrollable_Set_002() +{ + if (container_ == nullptr) { + return; + } + UILabel* label = GetTitleLabel("UIScrollView设置仅垂直方向不可滑动 "); + container_->Add(label); + positionY_ += DELTA_Y_COORDINATE; + label->SetPosition(positionX_, positionY_); + positionY_ += DELTA_Y_COORDINATE_2; + + UIScrollView* scroll = new UIScrollView(); + scroll->SetStyle(STYLE_BACKGROUND_COLOR, Color::Red().full); + scroll->SetPosition(positionX_, positionY_, g_scrollW, g_scrollH); + scroll->SetHorizontalScrollState(true); + scroll->SetVerticalScrollState(false); + scroll->SetXScrollBarVisible(true); + scroll->SetYScrollBarVisible(true); + container_->Add(scroll); + UILabelButton* button1 = new UILabelButton(); + button1->SetPosition(0, 0, g_buttonW, g_buttonH); + button1->SetText("button1"); + scroll->Add(button1); + SetLastPos(scroll); + positionY_ += g_scrollH; +} + +void UITestUIScrollView::UIKit_UIScrollView_Test_Scroll_Blank_Set_001() +{ + if (container_ == nullptr) { + return; + } + UILabel* label = GetTitleLabel("UIScrollView设置高为50的可滑动空白区域 "); + container_->Add(label); + positionY_ += DELTA_Y_COORDINATE; + label->SetPosition(positionX_, positionY_); + positionY_ += DELTA_Y_COORDINATE_2; + + UIScrollView* scroll = new UIScrollView(); + scroll->SetStyle(STYLE_BACKGROUND_COLOR, Color::Red().full); + scroll->SetPosition(positionX_, positionY_, g_scrollW, g_scrollH); + scroll->SetScrollBlankSize(50); // (50): means scroll blank size + scroll->SetXScrollBarVisible(true); + scroll->SetYScrollBarVisible(true); + container_->Add(scroll); + UILabelButton* button1 = new UILabelButton(); + button1->SetPosition(0, 0, g_buttonW, g_buttonH); + button1->SetText("button1"); + scroll->Add(button1); + SetLastPos(scroll); + positionY_ += g_scrollH; +} + +void UITestUIScrollView::UIKit_UIScrollView_Test_Scroll_listener_001() +{ + if (container_ == nullptr) { + return; + } + UILabel* label = GetTitleLabel("UIScrollView设置滑动状态监听 "); + container_->Add(label); + positionY_ += DELTA_Y_COORDINATE; + label->SetPosition(positionX_, positionY_); + positionY_ += DELTA_Y_COORDINATE_2; + + UIScrollView* scroll = new UIScrollView(); + scroll->SetStyle(STYLE_BACKGROUND_COLOR, Color::Red().full); + scroll->SetPosition(positionX_, positionY_, g_scrollW, g_scrollH); + container_->Add(scroll); + UILabelButton* button1 = new UILabelButton(); + button1->SetPosition(0, 0, g_buttonW, g_buttonH); + button1->SetText("button1"); + scroll->Add(button1); + scroll->RegisterScrollListener(g_listener); + g_scrollSateLabel->SetPosition(positionX_ + scroll->GetWidth() + 10, scroll->GetY()); // 10: increase width + container_->Add(g_scrollSateLabel); + + SetLastPos(scroll); +} + +void UITestUIScrollView::SetLastPos(UIView* view) +{ + if (view == nullptr) { + return; + } + lastX_ = view->GetX(); + lastY_ = view->GetY() + view->GetHeight(); +} +} // namespace OHOS \ No newline at end of file diff --git a/test/uitest/test_ui_scroll_view/ui_test_ui_scroll_view.h b/test/uitest/test_ui_scroll_view/ui_test_ui_scroll_view.h new file mode 100755 index 0000000..693b189 --- /dev/null +++ b/test/uitest/test_ui_scroll_view/ui_test_ui_scroll_view.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef UI_TEST_UI_SCROLL_VIEW_H +#define UI_TEST_UI_SCROLL_VIEW_H + +#include "components/ui_label.h" +#include "components/ui_scroll_view.h" +#include "ui_test.h" + +namespace OHOS { +class UITestUIScrollView : public UITest { +public: + UITestUIScrollView() {} + ~UITestUIScrollView() {} + void SetUp() override; + void TearDown() override; + const UIView* GetTestView() override; + + void UIKit_UIScrollView_Test_bar_001(); + void UIKit_UIScrollView_Test_bar_002(); + void UIKit_UIScrollView_Test_bar_003(); + void UIKit_UIScrollView_Test_bar_004(); + void UIKit_UIScrollView_Test_Scrollable_Set_001(); + void UIKit_UIScrollView_Test_Scrollable_Set_002(); + void UIKit_UIScrollView_Test_Scroll_Blank_Set_001(); + void UIKit_UIScrollView_Test_Scroll_listener_001(); + +private: + void SetLastPos(UIView* view); + UIScrollView* container_ = nullptr; + int16_t lastX_ = 0; + int16_t lastY_ = 0; +}; +} // namespace OHOS +#endif // UI_TEST_UI_SCROLL_VIEW_H diff --git a/test/uitest/test_ui_swipe_view/ui_test_ui_swipe_view.cpp b/test/uitest/test_ui_swipe_view/ui_test_ui_swipe_view.cpp new file mode 100755 index 0000000..94c218e --- /dev/null +++ b/test/uitest/test_ui_swipe_view/ui_test_ui_swipe_view.cpp @@ -0,0 +1,366 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ui_test_ui_swipe_view.h" + +#include + +#include "common/screen.h" +#include "components/ui_label.h" +#include "components/ui_swipe_view.h" + +namespace OHOS { +namespace { +static int16_t g_buttonH = 80; +static int16_t g_buttonW = 300; +static int16_t g_blank = 20; +static int16_t g_swipeH = 200; +static int16_t g_swipeW = 400; +static int16_t g_deltaCoordinateY = 19; +static int16_t g_deltaCoordinateY2 = 37; +} // namespace + +void UITestUISwipeView::SetUp() +{ + if (container_ == nullptr) { + container_ = new UIScrollView(); + container_->Resize(Screen::GetInstance().GetWidth(), Screen::GetInstance().GetHeight() - BACK_BUTTON_HEIGHT); + container_->SetThrowDrag(true); + container_->SetHorizontalScrollState(false); + } +} + +void UITestUISwipeView::TearDown() +{ + DeleteChildren(container_); + container_ = nullptr; + addBtnInHead_ = nullptr; + addBtnInTail_ = nullptr; + addBtnInMid_ = nullptr; + removeHeadBtn_ = nullptr; + removeMidBtn_ = nullptr; + removeAllBtn_ = nullptr; + lastX_ = 0; + lastY_ = 0; +} + +const UIView* UITestUISwipeView::GetTestView() +{ + UIKit_Swipe_View_Test_Horizontal_001(); + UIKit_Swipe_View_Test_Horizontal_002(); + UIKit_Swipe_View_Test_Align_001(UISwipeView::ALIGN_LEFT); + UIKit_Swipe_View_Test_Align_001(UISwipeView::ALIGN_CENTER); + UIKit_Swipe_View_Test_Align_001(UISwipeView::ALIGN_RIGHT); + + UIKit_Swipe_View_Test_Ver_001(); + UIKit_Swipe_View_Test_Ver_002(); + UIKit_Swipe_View_Test_Remove_001(); + + return container_; +} + +void UITestUISwipeView::UIKit_Swipe_View_Test_Horizontal_001() +{ + if (container_ == nullptr) { + return; + } + UILabel* label = GetTitleLabel("UISwipeView水平滑动"); + container_->Add(label); + positionX_ = TEXT_DISTANCE_TO_LEFT_SIDE; + positionY_ = TEXT_DISTANCE_TO_TOP_SIDE; + label->SetPosition(positionX_, positionY_); + positionY_ += g_deltaCoordinateY2; + + UISwipeView* swipe = new UISwipeView(UISwipeView::HORIZONTAL); + swipe->SetStyle(STYLE_BACKGROUND_COLOR, Color::Red().full); + swipe->SetPosition(positionX_, positionY_, g_swipeW, g_swipeH); + swipe->SetBlankSize(100); // 100: is blank size + container_->Add(swipe); + UILabelButton* button1 = new UILabelButton(); + button1->SetPosition(0, 0, g_buttonW, g_buttonH); + button1->SetText("button1"); + swipe->Add(button1); + UILabelButton* button2 = new UILabelButton(); + button2->SetPosition(0, 0, g_buttonW, g_buttonH); + button2->SetText("button2"); + swipe->Add(button2); + SetLastPos(swipe); + positionY_ += g_swipeH; +} + +void UITestUISwipeView::UIKit_Swipe_View_Test_Horizontal_002() +{ + if (container_ == nullptr) { + return; + } + UILabel* label = GetTitleLabel("UISwipeView循环水平滑动"); + container_->Add(label); + positionY_ += g_deltaCoordinateY; + label->SetPosition(positionX_, positionY_); + positionY_ += g_deltaCoordinateY2; + + UISwipeView* swipe = new UISwipeView(UISwipeView::HORIZONTAL); + swipe->SetStyle(STYLE_BACKGROUND_COLOR, Color::Red().full); + swipe->SetPosition(positionX_, positionY_, g_swipeW, g_swipeH); + swipe->SetLoopState(true); + swipe->SetAnimatorTime(100); // 100: mean animator drag time(ms) + container_->Add(swipe); + UILabelButton* button1 = new UILabelButton(); + button1->SetPosition(0, 0, g_buttonW, g_buttonH); + button1->SetText("button1"); + swipe->Add(button1); + UILabelButton* button2 = new UILabelButton(); + button2->SetPosition(0, 0, g_buttonW, g_buttonH); + button2->SetText("button2"); + swipe->Add(button2); + UILabelButton* button3 = new UILabelButton(); + button3->SetPosition(0, 0, g_buttonW, g_buttonH); + button3->SetText("button3"); + swipe->Add(button3); + SetLastPos(swipe); + positionY_ += g_swipeH; +} + +void UITestUISwipeView::UIKit_Swipe_View_Test_Ver_001() +{ + if (container_ == nullptr) { + return; + } + positionX_ = Screen::GetInstance().GetWidth() / 2 + TEXT_DISTANCE_TO_LEFT_SIDE; // 2: half of screen width + positionY_ = TEXT_DISTANCE_TO_TOP_SIDE; + UILabel* label = GetTitleLabel("UISwipeView垂直滑动"); + container_->Add(label); + positionY_ += g_deltaCoordinateY; + label->SetPosition(positionX_, positionY_); + positionY_ += g_deltaCoordinateY2; + + UISwipeView* swipe = new UISwipeView(UISwipeView::VERTICAL); + swipe->SetStyle(STYLE_BACKGROUND_COLOR, Color::Red().full); + swipe->SetPosition(positionX_, positionY_, g_swipeH, g_swipeW); + container_->Add(swipe); + UILabelButton* button1 = new UILabelButton(); + button1->SetPosition(0, 0, g_buttonH, g_buttonW); + button1->SetText("button1"); + swipe->Add(button1); + UILabelButton* button2 = new UILabelButton(); + button2->SetPosition(0, 0, g_buttonH, g_buttonW); + button2->SetText("button2"); + swipe->Add(button2); + SetLastPos(swipe); + positionY_ += g_swipeW + g_deltaCoordinateY; +} + +void UITestUISwipeView::UIKit_Swipe_View_Test_Ver_002() +{ + if (container_ == nullptr) { + return; + } + + UILabel* label = GetTitleLabel("UISwipeView循环垂直滑动"); + container_->Add(label); + label->SetPosition(positionX_, positionY_); + positionY_ += g_deltaCoordinateY2; + + UISwipeView* swipe = new UISwipeView(UISwipeView::VERTICAL); + swipe->SetStyle(STYLE_BACKGROUND_COLOR, Color::Red().full); + swipe->SetPosition(positionX_, positionY_, g_swipeH, g_swipeW); + swipe->SetLoopState(true); + swipe->SetAnimatorTime(100); // 100: is animator drag time(ms) + container_->Add(swipe); + UILabelButton* button1 = new UILabelButton(); + button1->SetPosition(0, 0, g_buttonH, g_buttonW); + button1->SetText("button1"); + swipe->Add(button1); + UILabelButton* button2 = new UILabelButton(); + button2->SetPosition(0, 0, g_buttonH, g_buttonW); + button2->SetText("button2"); + swipe->Add(button2); + UILabelButton* button3 = new UILabelButton(); + button3->SetPosition(0, 0, g_buttonH, g_buttonW); + button3->SetText("button3"); + swipe->Add(button3); + SetLastPos(swipe); + positionY_ += g_swipeW; +} + +void UITestUISwipeView::UIKit_Swipe_View_Test_Remove_001() +{ + if (container_ == nullptr) { + return; + } + UILabel* label = GetTitleLabel("UISwipeView增加和删除子节点"); + container_->Add(label); + positionY_ += g_deltaCoordinateY; + label->SetPosition(positionX_, positionY_); + positionY_ += g_deltaCoordinateY2; + + UISwipeView* swipe = new UISwipeView(UISwipeView::HORIZONTAL); + swipe->SetStyle(STYLE_BACKGROUND_COLOR, Color::Red().full); + swipe->SetPosition(positionX_, positionY_, g_swipeH, g_swipeW); + swipe->SetLoopState(true); + swipe->SetAnimatorTime(100); // 100: mean animator drag time(ms) + currentSwipe_ = swipe; + container_->Add(swipe); + + if (addBtnInHead_ == nullptr) { + addBtnInHead_ = new UILabelButton(); + } + if (addBtnInTail_ == nullptr) { + addBtnInTail_ = new UILabelButton(); + } + if (addBtnInMid_ == nullptr) { + addBtnInMid_ = new UILabelButton(); + } + if (removeHeadBtn_ == nullptr) { + removeHeadBtn_ = new UILabelButton(); + } + if (removeMidBtn_ == nullptr) { + removeMidBtn_ = new UILabelButton(); + } + if (removeAllBtn_ == nullptr) { + removeAllBtn_ = new UILabelButton(); + } + + positionX_ = positionX_ + swipe->GetWidth() + 20; // 20: is interval between button and swipe + SetUpButton(addBtnInHead_, "增加至头部 "); + SetUpButton(addBtnInTail_, "增加至尾部 "); + SetUpButton(addBtnInMid_, "增加至头部后 "); + SetUpButton(removeHeadBtn_, "删除头部节点 "); + SetUpButton(removeMidBtn_, "删除中间节点 "); + SetUpButton(removeAllBtn_, "删除全部节点 "); + + SetLastPos(swipe); +} + +void UITestUISwipeView::UIKit_Swipe_View_Test_Align_001(UISwipeView::AlignMode alignMode) +{ + static uint8_t divNum = 4; + if (container_ == nullptr) { + return; + } + + const char* leftTitle = "UISwipeView 子组件左对齐"; + const char* centerTitle = "UISwipeView 子组件Center对齐"; + const char* rightTitle = "UISwipeView 子组件右对齐"; + UILabel* label; + if (alignMode == UISwipeView::ALIGN_LEFT) { + label = GetTitleLabel(leftTitle); + } else if (alignMode == UISwipeView::ALIGN_RIGHT) { + label = GetTitleLabel(rightTitle); + } else { + label = GetTitleLabel(centerTitle); + } + + container_->Add(label); + label->SetPosition(positionX_, lastY_ + g_blank); + + UISwipeView* swipe = new UISwipeView(UISwipeView::HORIZONTAL); + swipe->SetStyle(STYLE_BACKGROUND_COLOR, Color::Red().full); + // 100: offset + swipe->SetPosition(positionX_, label->GetY() + g_blank + g_deltaCoordinateY, g_swipeW - 100, g_swipeH); + swipe->SetLoopState(true); + swipe->SetAnimatorTime(100); // 100: mean animator drag time(ms) + swipe->SetAlignMode(alignMode); + container_->Add(swipe); + UILabelButton* button1 = new UILabelButton(); + button1->SetPosition(0, 0, g_buttonW / divNum, g_buttonH / 2); // 2: half + button1->SetText("button1"); + swipe->Add(button1); + UILabelButton* button2 = new UILabelButton(); + button2->SetPosition(0, 0, g_buttonW / divNum, g_buttonH / 2); // 2: half + button2->SetText("button2"); + swipe->Add(button2); + UILabelButton* button3 = new UILabelButton(); + button3->SetPosition(0, 0, g_buttonW / divNum, g_buttonH / 2); // 2: half + button3->SetText("button3"); + swipe->Add(button3); + + UILabel* tmpLabel = nullptr; + const char* tmpOkTitle = "The result of GetAlignMode is OK."; + const char* tmpErrTitle = "The result of GetAlignMode is Error."; + uint8_t tmpAlignMode = swipe->GetAlignMode(); + if (tmpAlignMode == alignMode) { + tmpLabel = GetTitleLabel(tmpOkTitle); + } else { + tmpLabel = GetTitleLabel(tmpErrTitle); + } + tmpLabel->SetPosition(positionX_, 100, 250, 25); // 100: y, 250:width, 25:height + swipe->Add(tmpLabel); + + SetLastPos(swipe); +} + +bool UITestUISwipeView::OnClick(UIView& view, const ClickEvent& event) +{ + if (currentSwipe_ == nullptr) { + return true; + } + if (&view == addBtnInHead_) { + UILabelButton* btn = new UILabelButton(); + btn->SetPosition(0, 0, g_buttonH, g_buttonW); + btn->SetText(std::to_string(btnNum_).c_str()); + currentSwipe_->Insert(nullptr, btn); + } else if (&view == addBtnInTail_) { + UILabelButton* btn = new UILabelButton(); + btn->SetPosition(0, 0, g_buttonH, g_buttonW); + btn->SetText(std::to_string(btnNum_).c_str()); + currentSwipe_->Add(btn); + } else if (&view == addBtnInMid_) { + UILabelButton* btn = new UILabelButton(); + btn->SetPosition(0, 0, g_buttonH, g_buttonW); + btn->SetText(std::to_string(btnNum_).c_str()); + currentSwipe_->Insert(currentSwipe_->GetChildrenHead(), btn); + } else if (&view == removeHeadBtn_) { + currentSwipe_->Remove(currentSwipe_->GetChildrenHead()); + } else if (&view == removeMidBtn_) { + UIView* view = currentSwipe_->GetViewByIndex(1); + currentSwipe_->Remove(view); + } else if (&view == removeAllBtn_) { + currentSwipe_->RemoveAll(); + } + currentSwipe_->Invalidate(); + btnNum_++; + return true; +} + +void UITestUISwipeView::SetUpButton(UILabelButton* btn, const char* title) +{ + if (btn == nullptr) { + return; + } + container_->Add(btn); + btn->SetPosition(positionX_, positionY_, BUTTON_WIDHT2, BUTTON_HEIGHT2); + positionY_ += btn->GetHeight() + 10; // 10: increase y-coordinate + btn->SetText(title); + btn->SetFont(DEFAULT_VECTOR_FONT_FILENAME, BUTTON_LABEL_SIZE); + btn->SetOnClickListener(this); + btn->SetStyleForState(STYLE_BORDER_RADIUS, BUTTON_STYLE_BORDER_RADIUS_VALUE, UIButton::RELEASED); + btn->SetStyleForState(STYLE_BORDER_RADIUS, BUTTON_STYLE_BORDER_RADIUS_VALUE, UIButton::PRESSED); + btn->SetStyleForState(STYLE_BORDER_RADIUS, BUTTON_STYLE_BORDER_RADIUS_VALUE, UIButton::INACTIVE); + btn->SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::RELEASED); + btn->SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::PRESSED); + btn->SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::INACTIVE); + container_->Invalidate(); +} + +void UITestUISwipeView::SetLastPos(UIView* view) +{ + if (view == nullptr) { + return; + } + lastX_ = view->GetX(); + lastY_ = view->GetY() + view->GetHeight(); +} +} // namespace OHOS diff --git a/test/uitest/test_ui_swipe_view/ui_test_ui_swipe_view.h b/test/uitest/test_ui_swipe_view/ui_test_ui_swipe_view.h new file mode 100755 index 0000000..116cf08 --- /dev/null +++ b/test/uitest/test_ui_swipe_view/ui_test_ui_swipe_view.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef UI_TEST_UI_SWIPE_VIEW_H +#define UI_TEST_UI_SWIPE_VIEW_H + +#include "components/ui_label_button.h" +#include "components/ui_scroll_view.h" +#include "components/ui_swipe_view.h" +#include "ui_test.h" + +namespace OHOS { +class UITestUISwipeView : public UITest, UIView::OnClickListener { +public: + UITestUISwipeView() {} + ~UITestUISwipeView() {} + void SetUp() override; + void TearDown() override; + const UIView* GetTestView() override; + bool OnClick(UIView& view, const ClickEvent& event) override; + + void UIKit_Swipe_View_Test_Horizontal_001(); + void UIKit_Swipe_View_Test_Horizontal_002(); + void UIKit_Swipe_View_Test_Ver_001(); + void UIKit_Swipe_View_Test_Ver_002(); + void UIKit_Swipe_View_Test_Remove_001(); + void UIKit_Swipe_View_Test_Align_001(UISwipeView::AlignMode alignMode); + +private: + void SetUpButton(UILabelButton* btn, const char* title); + void SetLastPos(UIView* view); + UIScrollView* container_ = nullptr; + UISwipeView* currentSwipe_ = nullptr; + UILabelButton* addBtnInHead_ = nullptr; + UILabelButton* addBtnInTail_ = nullptr; + UILabelButton* addBtnInMid_ = nullptr; + UILabelButton* removeHeadBtn_ = nullptr; + UILabelButton* removeMidBtn_ = nullptr; + UILabelButton* removeAllBtn_ = nullptr; + + int16_t lastX_ = 0; + int16_t lastY_ = 0; + int16_t btnNum_ = 0; +}; +} // namespace OHOS +#endif // UI_TEST_UI_SWIPE_VIEW_H diff --git a/test/uitest/test_vector_font/ui_test_vector_font.cpp b/test/uitest/test_vector_font/ui_test_vector_font.cpp new file mode 100755 index 0000000..906756b --- /dev/null +++ b/test/uitest/test_vector_font/ui_test_vector_font.cpp @@ -0,0 +1,322 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ui_test_vector_font.h" +#if ENABLE_VECTOR_FONT +#include + +#include "common/screen.h" +#include "components/ui_label.h" +#if ENABLE_MULTI_FONT +#include "font/ui_multi_font_manager.h" +#endif +namespace { +const int16_t GAP = 5; +const int16_t TITLE_HEIGHT = 29; +const uint16_t LABEL_WIDTH = 400; +const uint16_t LABEL_HEIGHT = 50; +const uint16_t BUF_SIZE = 200; +const uint16_t LANGUAGE_FILE_ID_MAX = 9; +const char* HYQIHEI_65S = "HYQiHei-65S.otf"; +const char* NOTONASKHARABIC_REGULAR = "NotoNaskhArabic-Regular.ttf"; +const char* ROBOTOCONDENSED_REGULAR = "RobotoCondensed-Regular.ttf"; +const char* NOTOSANSCJKJP_REGULAR = "NotoSansCJKjp-Regular.otf"; +const char* NOTOSANSTHAI_REGULAR = "NotoSansThai-Regular.ttf"; +const char* NOTOSANSHEBREW_REGULAR = "NotoSansHebrew-Regular.ttf"; +const char* NOTOSANSDEVANAGARI_REGULAR = "NotoSansDevanagari-Regular.otf"; +const char* NOTOSANSMYANMAR_CONDENSED = "NotoSansMyanmar-Condensed.ttf"; +const char* NOTOSANSBENGALI_REGULAR = "NotoSansBengali-Regular.ttf"; +} // namespace +namespace OHOS { +void UITestVectorFont::SetUp() +{ + if (container_ == nullptr) { + container_ = new UIScrollView(); + container_->SetThrowDrag(true); + container_->SetHorizontalScrollState(false); + container_->Resize(Screen::GetInstance().GetWidth(), Screen::GetInstance().GetHeight() - BACK_BUTTON_HEIGHT); + positionX_ = 50; // 50: init position x + positionY_ = 5; // 5: init position y + } +} + +void UITestVectorFont::TearDown() +{ + DeleteChildren(container_); + container_ = nullptr; +} + +void UITestVectorFont::InnerTestTitle(const char* title) +{ + UILabel* titleLabel = new UILabel(); + UIFont::GetInstance()->RegisterFontInfo(DEFAULT_VECTOR_FONT_FILENAME); + titleLabel->SetPosition(TEXT_DISTANCE_TO_LEFT_SIDE, positionY_, Screen::GetInstance().GetWidth(), + TITLE_LABEL_DEFAULT_HEIGHT); + titleLabel->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + titleLabel->SetText(title); + container_->Add(titleLabel); + positionY_ += TITLE_HEIGHT + GAP; +} + +UIView* UITestVectorFont::GetTestView() +{ + Font_FontEngine_Vector_Test_CH_Lang_001(); + Font_FontEngine_Vector_Test_JA_Lang_001(); + Font_FontEngine_Vector_Test_JA_Lang_002(); + Font_FontEngine_Vector_Test_KR_Lang_001(); + Font_FontEngine_Vector_Test_VI_Lang_001(); +#if ENABLE_MULTI_FONT + Font_FontEngine_MultiLanguage_Test_JA_Lang_001(); + Font_FontEngine_MultiLanguage_Test_JA_Lang_002(); + Font_FontEngine_MultiLanguage_Test_KR_Lang_001(); + Font_FontEngine_MultiLanguage_Test_VI_Lang_001(); + Font_FontEngine_MultiLanguage_Test_CS_Lang_001(); + Font_FontEngine_MultiLanguage_Test_EL_Lang_001(); + Font_FontEngine_MultiLanguage_Test_RO_Lang_001(); +#endif + return container_; +} + +void UITestVectorFont::Font_FontEngine_Vector_Test_CH_Lang_001() +{ + if (container_ != nullptr) { + InnerTestTitle("CH Language"); + UILabel* label = new UILabel(); + UIFont::GetInstance()->RegisterFontInfo(HYQIHEI_65S); + label->SetPosition(positionX_, positionY_); + label->Resize(LABEL_WIDTH, LABEL_HEIGHT); + label->SetLineBreakMode(UILabel::LINE_BREAK_MARQUEE); + label->SetFont(HYQIHEI_65S, 24); // 24 : size + label->SetText("测试中文"); + container_->Add(label); + positionY_ += LABEL_HEIGHT + GAP; + } +} + +void UITestVectorFont::Font_FontEngine_Vector_Test_JA_Lang_001() +{ + if (container_ != nullptr) { + InnerTestTitle("JA Language"); + UILabel* label = new UILabel(); + UIFont::GetInstance()->RegisterFontInfo(NOTOSANSCJKJP_REGULAR); + label->SetPosition(positionX_, positionY_); + label->Resize(LABEL_WIDTH, LABEL_HEIGHT); + label->SetLineBreakMode(UILabel::LINE_BREAK_MARQUEE); + label->SetFont(NOTOSANSCJKJP_REGULAR, 24); // 24 : size + label->SetText("ランニン"); + container_->Add(label); + positionY_ += LABEL_HEIGHT + GAP; + } +} + +void UITestVectorFont::Font_FontEngine_Vector_Test_JA_Lang_002() +{ + if (container_ != nullptr) { + InnerTestTitle("JA Language"); + UILabel* label = new UILabel(); + UIFont::GetInstance()->RegisterFontInfo(NOTOSANSCJKJP_REGULAR); + label->SetPosition(positionX_, positionY_); + label->Resize(LABEL_WIDTH, LABEL_HEIGHT); + label->SetLineBreakMode(UILabel::LINE_BREAK_MARQUEE); + label->SetFont(NOTOSANSCJKJP_REGULAR, 30); // 30 : size + label->SetText("ランニン"); + container_->Add(label); + positionY_ += LABEL_HEIGHT + GAP; + } +} + +void UITestVectorFont::Font_FontEngine_Vector_Test_KR_Lang_001() +{ + if (container_ != nullptr) { + InnerTestTitle("KR Language"); + UILabel* label = new UILabel(); + label->SetPosition(positionX_, positionY_); + label->Resize(LABEL_WIDTH, LABEL_HEIGHT); + label->SetLineBreakMode(UILabel::LINE_BREAK_MARQUEE); + UIFont::GetInstance()->RegisterFontInfo(NOTOSANSCJKJP_REGULAR); + label->SetFont(NOTOSANSCJKJP_REGULAR, 24); // 24 : size + label->SetText("韩语리기"); + container_->Add(label); + positionY_ += LABEL_HEIGHT + GAP; + } +} + +void UITestVectorFont::Font_FontEngine_Vector_Test_VI_Lang_001() +{ + if (container_ != nullptr) { + InnerTestTitle("VI Language"); + UIFont::GetInstance()->RegisterFontInfo(NOTOSANSCJKJP_REGULAR); + UIFont::GetInstance()->RegisterFontInfo(HYQIHEI_65S); + UILabel* label = new UILabel(); + label->SetPosition(positionX_, positionY_); + label->Resize(LABEL_WIDTH, LABEL_HEIGHT); + label->SetLineBreakMode(UILabel::LINE_BREAK_MARQUEE); + label->SetFont(ROBOTOCONDENSED_REGULAR, 24); // 24 : size + label->SetText("Kết thúc tập luyện?"); + container_->Add(label); + positionY_ += LABEL_HEIGHT + GAP; + } +} + +#if ENABLE_MULTI_FONT +void UITestVectorFont::Font_FontEngine_MultiLanguage_Test_JA_Lang_001() +{ + if (container_ != nullptr) { + InnerTestTitle("CH+JA Language"); + UIFont::GetInstance()->RegisterFontInfo(NOTOSANSCJKJP_REGULAR); + UIFont::GetInstance()->RegisterFontInfo(HYQIHEI_65S); + UIFont::GetInstance()->RegisterFontInfo(ROBOTOCONDENSED_REGULAR); + uint8_t findPath[] = {UIFont::GetInstance()->GetFontId(HYQIHEI_65S), + UIFont::GetInstance()->GetFontId(ROBOTOCONDENSED_REGULAR)}; + UIMultiFontManager::GetInstance()->SetSearchFontList(UIFont::GetInstance()->GetFontId(HYQIHEI_65S), findPath, + sizeof(findPath)); + UILabel* label = new UILabel(); + label->SetPosition(positionX_, positionY_); + label->Resize(LABEL_WIDTH, LABEL_HEIGHT); + label->SetLineBreakMode(UILabel::LINE_BREAK_MARQUEE); + label->SetFont(HYQIHEI_65S, 24); // 24 : size + label->SetText("日语ランニン"); + container_->Add(label); + positionY_ += LABEL_HEIGHT + GAP; + } +} + +void UITestVectorFont::Font_FontEngine_MultiLanguage_Test_JA_Lang_002() +{ + if (container_ != nullptr) { + InnerTestTitle("UnRegister JA Language"); + UILabel* label = new UILabel(); + label->SetPosition(positionX_, positionY_); + label->Resize(LABEL_WIDTH, LABEL_HEIGHT); + label->SetLineBreakMode(UILabel::LINE_BREAK_MARQUEE); + label->SetFont(NOTOSANSCJKJP_REGULAR, 24); // 24 : size + label->SetText("韩语日语리기ランニン"); + container_->Add(label); + positionY_ += LABEL_HEIGHT + GAP; + } +} + +void UITestVectorFont::Font_FontEngine_MultiLanguage_Test_KR_Lang_001() +{ + if (container_ != nullptr) { + InnerTestTitle("CH+KR Language"); + UIFont::GetInstance()->RegisterFontInfo(NOTOSANSCJKJP_REGULAR); + UIFont::GetInstance()->RegisterFontInfo(HYQIHEI_65S); + UIFont::GetInstance()->RegisterFontInfo(ROBOTOCONDENSED_REGULAR); + uint8_t findPath[] = {UIFont::GetInstance()->GetFontId(HYQIHEI_65S), + UIFont::GetInstance()->GetFontId(ROBOTOCONDENSED_REGULAR)}; + UIMultiFontManager::GetInstance()->SetSearchFontList(UIFont::GetInstance()->GetFontId(NOTOSANSCJKJP_REGULAR), + findPath, sizeof(findPath)); + UILabel* label = new UILabel(); + label->SetPosition(positionX_, positionY_); + label->Resize(LABEL_WIDTH, LABEL_HEIGHT); + label->SetLineBreakMode(UILabel::LINE_BREAK_MARQUEE); + label->SetFont(NOTOSANSCJKJP_REGULAR, 24); // 24 : size + label->SetText("韩语리기"); + container_->Add(label); + positionY_ += LABEL_HEIGHT + GAP; + } +} + +void UITestVectorFont::Font_FontEngine_MultiLanguage_Test_VI_Lang_001() +{ + if (container_ != nullptr) { + InnerTestTitle("CH+VI Language"); + UIFont::GetInstance()->RegisterFontInfo(NOTOSANSCJKJP_REGULAR); + UIFont::GetInstance()->RegisterFontInfo(HYQIHEI_65S); + UIFont::GetInstance()->RegisterFontInfo(ROBOTOCONDENSED_REGULAR); + uint8_t findPath4[] = {UIFont::GetInstance()->GetFontId(HYQIHEI_65S), + UIFont::GetInstance()->GetFontId(ROBOTOCONDENSED_REGULAR)}; + UIMultiFontManager::GetInstance()->SetSearchFontList(UIFont::GetInstance()->GetFontId(NOTOSANSCJKJP_REGULAR), + findPath4, sizeof(findPath4)); + UILabel* label = new UILabel(); + label->SetPosition(positionX_, positionY_); + label->Resize(LABEL_WIDTH, LABEL_HEIGHT); + label->SetLineBreakMode(UILabel::LINE_BREAK_MARQUEE); + label->SetFont(NOTOSANSCJKJP_REGULAR, 24); // 24 : size + label->SetText("越南语Kết thúc tập luyện?"); + container_->Add(label); + positionY_ += LABEL_HEIGHT + GAP; + } +} + +void UITestVectorFont::Font_FontEngine_MultiLanguage_Test_CS_Lang_001() +{ + if (container_ != nullptr) { + InnerTestTitle("CH+CS Language"); + UIFont::GetInstance()->RegisterFontInfo(NOTOSANSCJKJP_REGULAR); + UIFont::GetInstance()->RegisterFontInfo(HYQIHEI_65S); + UIFont::GetInstance()->RegisterFontInfo(ROBOTOCONDENSED_REGULAR); + uint8_t findPath4[] = {UIFont::GetInstance()->GetFontId(HYQIHEI_65S), + UIFont::GetInstance()->GetFontId(ROBOTOCONDENSED_REGULAR)}; + UIMultiFontManager::GetInstance()->SetSearchFontList(UIFont::GetInstance()->GetFontId(NOTOSANSCJKJP_REGULAR), + findPath4, sizeof(findPath4)); + UILabel* label = new UILabel(); + label->SetPosition(positionX_, positionY_); + label->Resize(LABEL_WIDTH, LABEL_HEIGHT); + label->SetLineBreakMode(UILabel::LINE_BREAK_MARQUEE); + label->SetFont(NOTOSANSCJKJP_REGULAR, 24); // 24 : size + label->SetText("捷克语sledování tepové frekvence"); + container_->Add(label); + positionY_ += LABEL_HEIGHT + GAP; + } +} + +void UITestVectorFont::Font_FontEngine_MultiLanguage_Test_EL_Lang_001() +{ + if (container_ != nullptr) { + InnerTestTitle("CH+EL Language"); + UIFont::GetInstance()->RegisterFontInfo(NOTOSANSCJKJP_REGULAR); + UIFont::GetInstance()->RegisterFontInfo(HYQIHEI_65S); + UIFont::GetInstance()->RegisterFontInfo(ROBOTOCONDENSED_REGULAR); + uint8_t findPath4[] = {UIFont::GetInstance()->GetFontId(HYQIHEI_65S), + UIFont::GetInstance()->GetFontId(ROBOTOCONDENSED_REGULAR)}; + UIMultiFontManager::GetInstance()->SetSearchFontList(UIFont::GetInstance()->GetFontId(NOTOSANSCJKJP_REGULAR), + findPath4, sizeof(findPath4)); + UILabel* label = new UILabel(); + label->SetPosition(positionX_, positionY_); + label->Resize(LABEL_WIDTH, LABEL_HEIGHT); + label->SetLineBreakMode(UILabel::LINE_BREAK_MARQUEE); + label->SetFont(NOTOSANSCJKJP_REGULAR, 24); // 24 : size + label->SetText("西班牙España"); + container_->Add(label); + positionY_ += LABEL_HEIGHT + GAP; + } +} + +void UITestVectorFont::Font_FontEngine_MultiLanguage_Test_RO_Lang_001() +{ + if (container_ != nullptr) { + InnerTestTitle("RO Language"); + UIFont::GetInstance()->RegisterFontInfo(NOTOSANSCJKJP_REGULAR); + UIFont::GetInstance()->RegisterFontInfo(HYQIHEI_65S); + UIFont::GetInstance()->RegisterFontInfo(ROBOTOCONDENSED_REGULAR); + uint8_t findPath4[] = {UIFont::GetInstance()->GetFontId(HYQIHEI_65S), + UIFont::GetInstance()->GetFontId(ROBOTOCONDENSED_REGULAR)}; + UIMultiFontManager::GetInstance()->SetSearchFontList(UIFont::GetInstance()->GetFontId(NOTOSANSCJKJP_REGULAR), + findPath4, sizeof(findPath4)); + UILabel* label = new UILabel(); + label->SetPosition(positionX_, positionY_); + label->Resize(LABEL_WIDTH, LABEL_HEIGHT); + label->SetLineBreakMode(UILabel::LINE_BREAK_MARQUEE); + label->SetFont(NOTOSANSCJKJP_REGULAR, 24); // 24 : size + label->SetText("罗马尼亚Română"); + container_->Add(label); + positionY_ += LABEL_HEIGHT + GAP; + } +} +#endif +} // namespace OHOS +#endif // ENABLE_VECTOR_FONT diff --git a/test/uitest/test_vector_font/ui_test_vector_font.h b/test/uitest/test_vector_font/ui_test_vector_font.h new file mode 100755 index 0000000..931b714 --- /dev/null +++ b/test/uitest/test_vector_font/ui_test_vector_font.h @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef UI_TEST_VECTOR_FONT_H +#define UI_TEST_VECTOR_FONT_H + +#include "graphic_config.h" +#if ENABLE_VECTOR_FONT +#include "ui_test.h" +#include "components/ui_scroll_view.h" +#include "font/ui_font.h" +#include + +namespace OHOS { +class UITestVectorFont : public UITest { +public: + UITestVectorFont() {} + ~UITestVectorFont() {} + void SetUp() override; + void TearDown() override; + UIView* GetTestView() override; + void Font_FontEngine_Vector_Test_CH_Lang_001(); + void Font_FontEngine_Vector_Test_JA_Lang_001(); + void Font_FontEngine_Vector_Test_JA_Lang_002(); + void Font_FontEngine_Vector_Test_KR_Lang_001(); + void Font_FontEngine_Vector_Test_VI_Lang_001(); +#if ENABLE_MULTI_FONT + /** + * @brief Test JA language + */ + void Font_FontEngine_MultiLanguage_Test_JA_Lang_001(); + + /** + * @brief Test UnRegister JA language + */ + void Font_FontEngine_MultiLanguage_Test_JA_Lang_002(); + + /** + * @brief Test KR language + */ + void Font_FontEngine_MultiLanguage_Test_KR_Lang_001(); + + /** + * @brief Test VI language + */ + void Font_FontEngine_MultiLanguage_Test_VI_Lang_001(); + + /** + * @brief Test CS language + */ + void Font_FontEngine_MultiLanguage_Test_CS_Lang_001(); + + /** + * @brief Test EL language + */ + void Font_FontEngine_MultiLanguage_Test_EL_Lang_001(); + + /** + * @brief Test RO language + */ + void Font_FontEngine_MultiLanguage_Test_RO_Lang_001(); +#endif +private: + UIScrollView* container_ = nullptr; + void InnerTestTitle(const char* title); + int16_t xPos = 50; // 50: x initial position + int16_t yPos = 5; // 5 : y initial position +}; +} // namespace OHOS +#endif // ENABLE_VECTOR_FONT +#endif // UI_TEST_VECTOR_FONT_H diff --git a/test/uitest/test_video/ui_test_video.cpp b/test/uitest/test_video/ui_test_video.cpp new file mode 100755 index 0000000..132c758 --- /dev/null +++ b/test/uitest/test_video/ui_test_video.cpp @@ -0,0 +1,333 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ui_test_video.h" +#include "securec.h" +#include "test_resource_config.h" +#ifndef VERSION_LITE +namespace OHOS { +void UITestVideo::SetUp() +{ + if (container_ == nullptr) { + container_ = new UIScrollView(); + container_->Resize(Screen::GetInstance().GetWidth(), Screen::GetInstance().GetHeight() - 30); // 30:offset + } +} + +void UITestVideo::TearDown() +{ + if (container_ != nullptr) { + container_->RemoveAll(); + delete container_; + container_ = nullptr; + } + if (video_ != nullptr) { + video_->Stop(); + video_->Reset(); + delete video_; + video_ = nullptr; + } + if (infoLabel_ != nullptr) { + delete infoLabel_; + infoLabel_ = nullptr; + } + if (testVideoListener_ != nullptr) { + delete testVideoListener_; + testVideoListener_ = nullptr; + } + DeleteTestButtons(); +} + +void UITestVideo::DeleteTestButtons() +{ + if (loopButton_ != nullptr) { + delete loopButton_; + loopButton_ = nullptr; + } + if (totleTimeButton_ != nullptr) { + delete totleTimeButton_; + totleTimeButton_ = nullptr; + } + if (volButtonAdd_ != nullptr) { + delete volButtonAdd_; + volButtonAdd_ = nullptr; + } + if (volButtonSub_ != nullptr) { + delete volButtonSub_; + volButtonSub_ = nullptr; + } + if (rewindButtonAdd_ != nullptr) { + delete rewindButtonAdd_; + rewindButtonAdd_ = nullptr; + } + if (rewindButtonSub_ != nullptr) { + delete rewindButtonSub_; + rewindButtonSub_ = nullptr; + } + if (playButton_ != nullptr) { + delete playButton_; + playButton_ = nullptr; + } + if (pauseButton_ != nullptr) { + delete pauseButton_; + pauseButton_ = nullptr; + } + if (stopButton_ != nullptr) { + delete stopButton_; + stopButton_ = nullptr; + } + if (resetButton_ != nullptr) { + delete resetButton_; + resetButton_ = nullptr; + } +} + +void UITestVideo::SetupTestButton(UILabelButton* button, const char* text) +{ + if (button != nullptr) { + int16_t buttonWidth = 60; // 60:button width + int16_t buttonHeight = 40; // 40:button height + button->SetText(text); + button->Resize(buttonWidth, buttonHeight); + button->SetOnClickListener(this); + } +} + +void UITestVideo::InitTestButtons() +{ + int16_t posX1 = Screen::GetInstance().GetWidth() - 160; // 160:posX1 offset + int16_t posX2 = Screen::GetInstance().GetWidth() - 80; // 80:posX2 offset + + loopButton_ = new UILabelButton(); + loopButton_->SetPosition(posX1, 50); // 50:position y + SetupTestButton(loopButton_, "loop"); + + totleTimeButton_ = new UILabelButton(); + totleTimeButton_->SetPosition(posX2, 50); // 50:position y + SetupTestButton(totleTimeButton_, "time"); + + volButtonAdd_ = new UILabelButton(); + volButtonAdd_->SetPosition(posX1, 100); // 100:position y + SetupTestButton(volButtonAdd_, "vol+"); + + volButtonSub_ = new UILabelButton(); + volButtonSub_->SetPosition(posX2, 100); // 100:position y + SetupTestButton(volButtonSub_, "vol-"); + + rewindButtonAdd_ = new UILabelButton(); + rewindButtonAdd_->SetPosition(posX1, 150); // 150:position y + SetupTestButton(rewindButtonAdd_, "pos+"); + + rewindButtonSub_ = new UILabelButton(); + rewindButtonSub_->SetPosition(posX2, 150); // 150:position y + SetupTestButton(rewindButtonSub_, "pos-"); + + playButton_ = new UILabelButton(); + playButton_->SetPosition(posX1, 200); // 200:position y + SetupTestButton(playButton_, "play"); + + pauseButton_ = new UILabelButton(); + pauseButton_->SetPosition(posX2, 200); // 200:position y + SetupTestButton(pauseButton_, "pause"); + + stopButton_ = new UILabelButton(); + stopButton_->SetPosition(posX1, 250); // 250:position y + SetupTestButton(stopButton_, "stop"); + + resetButton_ = new UILabelButton(); + resetButton_->SetPosition(posX2, 250); // 250:position y + SetupTestButton(resetButton_, "reset"); +} + +UIView* UITestVideo::GetTestView() +{ + if (video_ == nullptr) { + UIKit_UIVideo_Test_SetVideo_001(); + } + return container_; +} + +void UITestVideo::UIKit_UIVideo_Test_SetVideo_001() +{ + if (container_ != nullptr) { + if (video_ == nullptr) { + video_ = new UIVideo(); + } + + infoLabel_ = new UILabel; + infoLabel_->SetPosition(20, 60); // 20:position x 60:position y + infoLabel_->Resize(200, 40); // 200:label width 40:label height + + if (!video_->SetSrc(VIDEO_SOURCE_DIRECTORY)) { + infoLabel_->SetText("SetSrc error"); + return; + } + video_->SetPosition(0, 0); + video_->Resize(Screen::GetInstance().GetWidth(), Screen::GetInstance().GetHeight()); + if (!video_->Prepare()) { + infoLabel_->SetText("Prepare error"); + return; + } + + testVideoListener_ = new TestVideoListener(); + testVideoListener_->SetInfoLabel(infoLabel_); + video_->SetVideoPlayerListener(testVideoListener_); + + InitTestButtons(); + container_->Add(video_); + container_->Add(infoLabel_); + container_->Add(loopButton_); + container_->Add(totleTimeButton_); + container_->Add(volButtonAdd_); + container_->Add(volButtonSub_); + container_->Add(rewindButtonAdd_); + container_->Add(rewindButtonSub_); + container_->Add(playButton_); + container_->Add(pauseButton_); + container_->Add(stopButton_); + container_->Add(resetButton_); + + if (!video_->Play()) { + infoLabel_->SetText("Play error"); + return; + } + video_->ShowController(true); + } +} + +void UITestVideo::DealClickLoopButton() const +{ + if ((video_ == nullptr) || (infoLabel_ == nullptr)) { + return; + } + if (video_->IsSingleLooping()) { + if (video_->EnableSingleLooping(false)) { + infoLabel_->SetText("single"); + } else { + infoLabel_->SetText("single error"); + } + } else { + if (video_->EnableSingleLooping(true)) { + infoLabel_->SetText("loop"); + } else { + infoLabel_->SetText("loop error"); + } + } +} + +bool UITestVideo::OnClick(UIView &view, const ClickEvent& event) +{ + if ((video_ == nullptr) || (infoLabel_ == nullptr)) { + return true; + } + if (&view == loopButton_) { + DealClickLoopButton(); + } else if (&view == totleTimeButton_) { + int64_t duration = 0; + if (video_->GetDuration(duration)) { + char buf[10] = {0}; // 10:buf size + if (sprintf_s(buf, sizeof(buf), "%lld", duration) < 0) { + return true; + } + infoLabel_->SetText(&buf[0]); + } else { + infoLabel_->SetText("GetDuration error"); + } + } else if (&view == volButtonAdd_) { + if (video_->SetVolume(0.8, 0.8)) { // 0.8:left volume 0.8:right volume + infoLabel_->SetText("volume 0.8"); + } else { + infoLabel_->SetText("SetVolume error"); + } + } else if (&view == volButtonSub_) { + if (video_->SetVolume(0, 0)) { + infoLabel_->SetText("volume 0"); + } else { + infoLabel_->SetText("SetVolume error"); + } + } else { + return OnClick1(view, event); + } + return true; +} + +bool UITestVideo::OnClick1(UIView &view, const ClickEvent& event) +{ + if ((video_ == nullptr) || (infoLabel_ == nullptr)) { + return true; + } + if (&view == rewindButtonAdd_) { + int64_t time = 0; + if (video_->GetCurrentTime(time)) { + if (video_->Rewind(time + 3000)) { // 3000:rewind add ms + infoLabel_->SetText("Rewind + 3000"); + } else { + infoLabel_->SetText("Rewind error"); + } + } else { + infoLabel_->SetText("GetCurrentTime error"); + } + } else if (&view == rewindButtonSub_) { + int64_t time = 0; + if (video_->GetCurrentTime(time)) { + if (video_->Rewind(time - 3000)) { // 3000:rewind sub ms + infoLabel_->SetText("Rewind - 3000"); + } else { + infoLabel_->SetText("Rewind error"); + } + } else { + infoLabel_->SetText("GetCurrentTime error"); + } + } else { + return OnClick2(view, event); + } + return true; +} + +bool UITestVideo::OnClick2(UIView &view, const ClickEvent& event) +{ + if ((video_ == nullptr) || (infoLabel_ == nullptr)) { + return true; + } + if (&view == playButton_) { + if (video_->Play()) { + infoLabel_->SetText("Play"); + } else { + infoLabel_->SetText("Play error"); + } + } else if (&view == pauseButton_) { + if (video_->Pause()) { + infoLabel_->SetText("Pause"); + } else { + infoLabel_->SetText("Pause error"); + } + } else if (&view == stopButton_) { + if (video_->Stop()) { + infoLabel_->SetText("Stop"); + } else { + infoLabel_->SetText("Stop error"); + } + } else if (&view == resetButton_) { + if (video_->Reset()) { + infoLabel_->SetText("Reset"); + } else { + infoLabel_->SetText("Reset error"); + } + } + return true; +} +} // namespace OHOS + +#endif // VERSION_LITE diff --git a/test/uitest/test_video/ui_test_video.h b/test/uitest/test_video/ui_test_video.h new file mode 100755 index 0000000..b47f56b --- /dev/null +++ b/test/uitest/test_video/ui_test_video.h @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef UI_TEST_VIDEO_H +#define UI_TEST_VIDEO_H + +#include "components/ui_label_button.h" +#include "components/ui_scroll_view.h" +#include "components/ui_video.h" +#include "ui_test.h" + +#ifndef VERSION_LITE +namespace OHOS { +class UITestVideo : public UITest, public UIView::OnClickListener { +public: + UITestVideo() {} + ~UITestVideo() {} + void SetUp() override; + void TearDown() override; + UIView* GetTestView() override; + bool OnClick(UIView &view, const ClickEvent& event) override; + bool OnClick1(UIView &view, const ClickEvent& event); + bool OnClick2(UIView &view, const ClickEvent& event); + void DealClickLoopButton() const; + void InitTestButtons(); + void SetupTestButton(UILabelButton* button, const char* text); + void DeleteTestButtons(); + void UIKit_UIVideo_Test_SetVideo_001(); + + class TestVideoListener : public UIVideo::VideoPlayerListener { + public: + TestVideoListener() {} + ~TestVideoListener() {} + + void OnPlaybackComplete() override + { + if (infoLabel_ != nullptr) { + infoLabel_->SetText("OnPlaybackComplete"); + } + } + + void OnError(int32_t errorType, int32_t errorCode) override + { + if (infoLabel_ != nullptr) { + infoLabel_->SetText("OnError"); + } + } + + void OnInfo(int type, int extra) override + { + if (infoLabel_ != nullptr) { + infoLabel_->SetText("OnInfo"); + } + } + + void OnVideoSizeChanged(int width, int height) override + { + if (infoLabel_ != nullptr) { + infoLabel_->SetText("OnVideoSizeChanged"); + } + } + + void OnRewindToComplete() override + { + if (infoLabel_ != nullptr) { + infoLabel_->SetText("OnRewindToComplete"); + } + } + + void OnPlaybackPause() override + { + if (infoLabel_ != nullptr) { + infoLabel_->SetText("OnPlaybackPause"); + } + } + + void OnPlaybackPlay() override + { + if (infoLabel_ != nullptr) { + infoLabel_->SetText("OnPlaybackPlay"); + } + } + + void OnPlaybackStop() override + { + if (infoLabel_ != nullptr) { + infoLabel_->SetText("OnPlaybackStop"); + } + } + + void SetInfoLabel(UILabel* label) + { + infoLabel_ = label; + } + + private: + UILabel* infoLabel_ = nullptr; + }; + +private: + UIScrollView* container_ = nullptr; + UIVideo* video_ = nullptr; + UILabel* infoLabel_ = nullptr; + UILabelButton* loopButton_ = nullptr; + UILabelButton* volButtonAdd_ = nullptr; + UILabelButton* volButtonSub_ = nullptr; + UILabelButton* rewindButtonAdd_ = nullptr; + UILabelButton* rewindButtonSub_ = nullptr; + UILabelButton* pauseButton_ = nullptr; + UILabelButton* playButton_ = nullptr; + UILabelButton* stopButton_ = nullptr; + UILabelButton* resetButton_ = nullptr; + UILabelButton* totleTimeButton_ = nullptr; + TestVideoListener* testVideoListener_ = nullptr; +}; +} // namespace OHOS +#endif // VERSION_LITE +#endif // UI_TEST_VIDEO_H diff --git a/test/uitest/test_view_percent/ui_test_view_percent.cpp b/test/uitest/test_view_percent/ui_test_view_percent.cpp new file mode 100755 index 0000000..6ebc7c6 --- /dev/null +++ b/test/uitest/test_view_percent/ui_test_view_percent.cpp @@ -0,0 +1,254 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ui_test_view_percent.h" + +#include +#include +#include +#include "common/screen.h" +#include "components/ui_label.h" +#include "components/ui_label_button.h" +#include "layout/grid_layout.h" + +namespace OHOS { +namespace { + static int16_t g_widthPercent = 100; + const uint16_t LABEL_HEIGHT = 60; + const uint16_t DEFAULT_WIDITH = 80; + const uint16_t DEFAULT_HEIGHT = 60; + const uint16_t DEFAULT_ELEMENT_COUNT = 10; + const uint16_t DEFAULT_PERCENT = 10; + const uint16_t MAX_PERCENT = 100; + const float DEFAULT_PERCENT_INTERVAL = 100.0f; + const int16_t TEXT_H = 20; + const int16_t GAP = 10; + static int16_t g_view1WidthPercent = DEFAULT_PERCENT; + static int16_t g_view1HeightPercent = DEFAULT_PERCENT; + static int16_t g_view2WidthPercent = DEFAULT_PERCENT; + static int16_t g_view2HeightPercent = DEFAULT_PERCENT; +} + +void UITestViewPercent::SetUp() +{ + if (container_ == nullptr) { + InitContainer(); + } + viewGroup = new UIViewGroup(); + view1_ = new UIButton(); + view2_ = new UIView(); + view3_ = new UIView(); + resetfBtn_ = new UILabelButton(); + BiggerBtn_ = new UILabelButton(); + SmallerBtn_ = new UILabelButton(); + positionX_ = 20; // default start x-coordinate as 20 + positionY_ = 5; // default start y-coordinate as 5 +} + +void UITestViewPercent::TearDown() +{ + DeleteChildren(container_); + container_ = nullptr; +} + +UIView* UITestViewPercent::GetTestView() +{ + InitView(); + return container_; +} + +void UITestViewPercent::InitContainer() +{ + container_ = new UIScrollView(); + container_->SetThrowDrag(true); + container_->SetHorizontalScrollState(false); + container_->Resize(Screen::GetInstance().GetWidth(), Screen::GetInstance().GetHeight() - BACK_BUTTON_HEIGHT); +} + +void UITestViewPercent::InitView() +{ + if (container_ == nullptr) { + InitContainer(); + } + + UILabel* label = new UILabel(); + container_->Add(label); + positionY_ = TEXT_DISTANCE_TO_TOP_SIDE; + label->SetPosition(TEXT_DISTANCE_TO_LEFT_SIDE, positionY_, + Screen::GetInstance().GetWidth(), TITLE_LABEL_DEFAULT_HEIGHT); + label->SetText("Flex布局效果 "); + label->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + positionY_ += (TEXT_H + GAP); + + flexLayout_ = new FlexLayout(); + container_->Add(flexLayout_); + InitFlexLayout(); + positionY_ += flexLayout_->GetHeight() + GAP; + + InitViewGroup(); + positionY_ += viewGroup->GetHeight() + GAP; + + GridLayout* gridLayout = new GridLayout(); + container_->Add(gridLayout); + gridLayout->Resize(260, 60); // 260: width; 60: height + gridLayout->SetLayoutDirection(LAYOUT_HOR); + gridLayout->SetPosition(0, positionY_); // 260: width; 60: height + // 2: half of screen width; 2: half of gridLayout + gridLayout->LayoutLeftOfParent(Screen::GetInstance().GetWidth() / 2 - gridLayout->GetWidth() / 2); + gridLayout->SetRows(1); // set gridlayout row as 1 + gridLayout->SetCols(3); // set gridlayout column as 3 + + SetUpButton(*gridLayout, *resetfBtn_, "reset"); + SetUpButton(*gridLayout, *BiggerBtn_, "Bigger"); + SetUpButton(*gridLayout, *SmallerBtn_, "Smaller"); + gridLayout->LayoutChildren(true); + container_->LayoutChildren(true); +} + +void UITestViewPercent::InitFlexLayout() +{ + positionY_ = VIEW_DISTANCE_TO_TOP_SIDE; + flexLayout_->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, positionY_); + flexLayout_->SetWidthPercent(0.9f); // set default width percent as 0.9 + flexLayout_->SetHeightPercent(0.9f); // set default height percent as 0.9 + flexLayout_->SetStyle(STYLE_BORDER_COLOR, Color::White().full); + flexLayout_->SetStyle(STYLE_BORDER_OPA, HALF_OPA_OPAQUE); + flexLayout_->SetStyle(STYLE_BORDER_WIDTH, VIEW_STYLE_BORDER_WIDTH); + flexLayout_->SetStyle(STYLE_BORDER_RADIUS, VIEW_STYLE_BORDER_RADIUS); + flexLayout_->SetStyle(STYLE_BACKGROUND_OPA, 0); + for (int16_t i = 1; i <= DEFAULT_ELEMENT_COUNT; i++) { + AddElement(i); + } + flexLayout_->SetFlexWrap(1); + flexLayout_->SetLayoutDirection(LAYOUT_HOR); + flexLayout_->SetMajorAxisAlign(ALIGN_START); + flexLayout_->SetSecondaryAxisAlign(ALIGN_CENTER); + flexLayout_->LayoutChildren(); +} + +void UITestViewPercent::InitViewGroup() const +{ + container_->Add(viewGroup); + viewGroup->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, positionY_); + viewGroup->SetWidthPercent(0.9f); // set default width percent as 0.9 + viewGroup->SetHeight(viewGroup->GetParent()->GetHeight()); + viewGroup->SetStyle(STYLE_BORDER_COLOR, Color::White().full); + viewGroup->SetStyle(STYLE_BORDER_OPA, HALF_OPA_OPAQUE); + viewGroup->SetStyle(STYLE_BORDER_WIDTH, VIEW_STYLE_BORDER_WIDTH); + viewGroup->SetStyle(STYLE_BORDER_RADIUS, VIEW_STYLE_BORDER_RADIUS); + viewGroup->SetStyle(STYLE_BACKGROUND_OPA, 0); + viewGroup->Add(view1_); + view1_->SetViewId("view1"); + view1_->SetPositionPercent(0.1, 0.1); // set view1 position percent x-percent=0.1, y-percent=0.1 + view1_->SetStyle(STYLE_BACKGROUND_COLOR, Color::Green().full); + view1_->SetStyle(STYLE_BACKGROUND_OPA, OPA_OPAQUE); + view1_->SetStyle(STYLE_BORDER_COLOR, Color::Black().full); + view1_->SetStyle(STYLE_BORDER_OPA, OPA_OPAQUE); + view1_->SetStyle(STYLE_BORDER_WIDTH, 1); // set border width 1 + view1_->SetStyle(STYLE_BORDER_RADIUS, 5); // set border radius 5 + view1_->SetHeightPercent(g_view1WidthPercent / DEFAULT_PERCENT_INTERVAL); + view1_->SetWidthPercent(g_view1HeightPercent / DEFAULT_PERCENT_INTERVAL); + + viewGroup->Add(view2_); + view2_->SetViewId("view2"); + view2_->SetStyle(STYLE_BACKGROUND_COLOR, Color::Blue().full); + view2_->SetStyle(STYLE_BACKGROUND_OPA, OPA_OPAQUE); + view2_->SetStyle(STYLE_BORDER_COLOR, Color::Blue().full); + view2_->SetStyle(STYLE_BORDER_OPA, OPA_OPAQUE); + view2_->SetStyle(STYLE_BORDER_WIDTH, 1); // set border width 1 + view2_->SetStyle(STYLE_BORDER_RADIUS, 5); // set border radius 5 + view2_->SetPositionPercent( + 0.3, 0.1, /* set view2 position percent x-percent=0.3, y-percent=0.1 */ + g_view2WidthPercent / DEFAULT_PERCENT_INTERVAL, g_view2HeightPercent / DEFAULT_PERCENT_INTERVAL); + + viewGroup->Add(view3_); + view3_->SetViewId("view3"); + view3_->SetStyle(STYLE_BACKGROUND_COLOR, Color::Red().full); + view3_->SetStyle(STYLE_BACKGROUND_OPA, OPA_OPAQUE); + view3_->SetStyle(STYLE_BORDER_COLOR, Color::Red().full); + view3_->SetStyle(STYLE_BORDER_OPA, OPA_OPAQUE); + view3_->SetStyle(STYLE_BORDER_WIDTH, 1); // set border width 1 + view3_->SetStyle(STYLE_BORDER_RADIUS, 5); // set border radius 5 + view3_->SetXPercent(0.5); // set view3 x-percent=0.5 + view3_->SetYPercent(0.1); // set view3 , y-percent=0.1 + view3_->SetWidthPercent(g_view2WidthPercent / DEFAULT_PERCENT_INTERVAL); + view3_->SetHeightPercent(g_view2HeightPercent / DEFAULT_PERCENT_INTERVAL); +} + +void UITestViewPercent::AddElement(int16_t index) +{ + if (flexLayout_ == nullptr) { + InitView(); + } + + UILabel* label = new UILabel(); + flexLayout_->Add(label); + float percent = DEFAULT_PERCENT / DEFAULT_PERCENT_INTERVAL; + label->SetWidthPercent(percent); + label->SetHeightPercent(percent); + label->SetText(std::to_string(index).c_str()); + label->SetFont("HYQiHei-65S.otf", 20); // set default font size as 20 + label->SetStyle(STYLE_TEXT_COLOR, Color::Black().full); + label->SetStyle(STYLE_BACKGROUND_COLOR, Color::White().full); + label->SetStyle(STYLE_BACKGROUND_OPA, OPA_OPAQUE); + label->SetStyle(STYLE_BORDER_COLOR, Color::Black().full); + label->SetStyle(STYLE_BORDER_OPA, OPA_OPAQUE); + label->SetStyle(STYLE_BORDER_WIDTH, 1); // set border width 1 + label->SetStyle(STYLE_BORDER_RADIUS, 5); // set border radius 5 + label->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER); +} + +bool UITestViewPercent::OnClick(UIView& view, const ClickEvent& event) +{ + if (&view == resetfBtn_) { + g_view1HeightPercent = DEFAULT_PERCENT; + g_view1WidthPercent = DEFAULT_PERCENT; + g_view2HeightPercent = DEFAULT_PERCENT; + g_view2WidthPercent = DEFAULT_PERCENT; + view1_->ResizePercent(g_view1WidthPercent / DEFAULT_PERCENT_INTERVAL, + g_view1HeightPercent / DEFAULT_PERCENT_INTERVAL); + view2_->ResizePercent(g_view2WidthPercent / DEFAULT_PERCENT_INTERVAL, + g_view2HeightPercent / DEFAULT_PERCENT_INTERVAL); + g_widthPercent = DEFAULT_PERCENT; + flexLayout_->SetWidthPercent(g_widthPercent / DEFAULT_PERCENT_INTERVAL); + } else if (&view == BiggerBtn_) { + if (g_widthPercent < DEFAULT_PERCENT) { + g_widthPercent++; + flexLayout_->SetWidthPercent(g_widthPercent / DEFAULT_PERCENT_INTERVAL); + } + g_view1HeightPercent++; + g_view1WidthPercent++; + g_view2HeightPercent++; + g_view2WidthPercent++; + view1_->ResizePercent(g_view1WidthPercent / DEFAULT_PERCENT_INTERVAL, + g_view1HeightPercent / DEFAULT_PERCENT_INTERVAL); + view2_->ResizePercent(g_view2WidthPercent / DEFAULT_PERCENT_INTERVAL, + g_view2HeightPercent / DEFAULT_PERCENT_INTERVAL); + } else if (&view == SmallerBtn_) { + g_view1HeightPercent--; + g_view1WidthPercent--; + g_view2HeightPercent--; + g_view2WidthPercent--; + view1_->ResizePercent(g_view1WidthPercent / DEFAULT_PERCENT_INTERVAL, + g_view1HeightPercent / DEFAULT_PERCENT_INTERVAL); + view2_->ResizePercent(g_view2WidthPercent / DEFAULT_PERCENT_INTERVAL, + g_view2HeightPercent / DEFAULT_PERCENT_INTERVAL); + flexLayout_->SetWidthPercent((g_widthPercent--) / DEFAULT_PERCENT_INTERVAL); + } + view2_->GetParent()->Invalidate(); + return true; +} +} + diff --git a/test/uitest/test_view_percent/ui_test_view_percent.h b/test/uitest/test_view_percent/ui_test_view_percent.h new file mode 100755 index 0000000..591652e --- /dev/null +++ b/test/uitest/test_view_percent/ui_test_view_percent.h @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef UI_TEST_VIEW_PERCENT_H +#define UI_TEST_VIEW_PERCENT_H + +#include +#include "components/ui_label_button.h" +#include "components/ui_scroll_view.h" +#include "components/ui_view.h" +#include "components/ui_view_group.h" +#include "graphic_config.h" +#include "layout/flex_layout.h" +#include "layout/layout.h" +#include "ui_test.h" + +namespace OHOS { +class UITestViewPercent : public UITest, public UIView::OnClickListener { +public: + UITestViewPercent() {} + ~UITestViewPercent() {} + void SetUp() override; + void TearDown() override; + UIView* GetTestView() override; + bool OnClick(UIView& view, const ClickEvent& event) override; + +private: + void SetUpButton(Layout& controller, UILabelButton& btn, const std::string title) + { + controller.Add(&btn); + btn.Resize(BUTTON_WIDHT1, BUTTON_HEIGHT1); + btn.SetText(title.c_str()); + btn.SetFont(DEFAULT_VECTOR_FONT_FILENAME, BUTTON_LABEL_SIZE); + btn.SetOnClickListener(this); + btn.SetStyleForState(STYLE_BORDER_RADIUS, BUTTON_STYLE_BORDER_RADIUS_VALUE, UIButton::RELEASED); + btn.SetStyleForState(STYLE_BORDER_RADIUS, BUTTON_STYLE_BORDER_RADIUS_VALUE, UIButton::PRESSED); + btn.SetStyleForState(STYLE_BORDER_RADIUS, BUTTON_STYLE_BORDER_RADIUS_VALUE, UIButton::INACTIVE); + btn.SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::RELEASED); + btn.SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::PRESSED); + btn.SetStyleForState(STYLE_BACKGROUND_COLOR, BUTTON_STYLE_BACKGROUND_COLOR_VALUE, UIButton::INACTIVE); + } + void InitView(); + void InitFlexLayout(); + void InitViewGroup() const; + void InitContainer(); + void AddElement(int16_t index); + UIScrollView* container_ = nullptr; + FlexLayout* flexLayout_ = nullptr; + UIViewGroup* viewGroup = nullptr; + UIView* view1_ = nullptr; + UIView* view2_ = nullptr; + UIView* view3_ = nullptr; + UILabelButton* resetfBtn_ = nullptr; + UILabelButton* BiggerBtn_ = nullptr; + UILabelButton* SmallerBtn_ = nullptr; +}; +} // namespace OHOS +#endif // UI_TEST_VIEW_PERCENT_H + diff --git a/test/uitest/test_view_scale_rotate/ui_test_view_scale_rotate.cpp b/test/uitest/test_view_scale_rotate/ui_test_view_scale_rotate.cpp new file mode 100755 index 0000000..71262f3 --- /dev/null +++ b/test/uitest/test_view_scale_rotate/ui_test_view_scale_rotate.cpp @@ -0,0 +1,213 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ui_test_view_scale_rotate.h" +#include "common/screen.h" +#include "components/ui_label.h" +#include "test_resource_config.h" + +namespace OHOS { +void UITestViewScaleRotate::SetUp() +{ + if (container_ == nullptr) { + container_ = new UIScrollView(); + container_->Resize(Screen::GetInstance().GetWidth(), Screen::GetInstance().GetHeight() - BACK_BUTTON_HEIGHT); + } + + if (list_ == nullptr) { + list_ = new ListLayout(UISwipeView::VERTICAL); + uint16_t width = Screen::GetInstance().GetWidth(); + uint16_t height = Screen::GetInstance().GetHeight(); + list_->SetPosition(VIEW_DISTANCE_TO_LEFT_SIDE, 0, width, height + 1); + container_->Add(list_); + } +} + +void UITestViewScaleRotate::TearDown() +{ + animator_.Stop(); + DeleteChildren(container_); + container_ = nullptr; + list_ = nullptr; +} + +UIView* UITestViewScaleRotate::GetTestView() +{ + UIKit_View_Scale_Rotate_Test_Label_001(); + UIKit_View_Scale_Rotate_Test_LabelButton_002(); + UIKit_View_Scale_Rotate_Test_Canvas_003(); + UIKit_View_Scale_Rotate_Test_UIBoxProgress_004(); + UIKit_View_Scale_Rotate_Test_UICircleProgress_005(); + UIKit_View_Scale_Rotate_Test_UIDigitalClock_006(); + UIKit_View_Scale_Rotate_Test_Group_007(); + + animator_.Start(); + return container_; +} + +void UITestViewScaleRotate::Callback(UIView* view) +{ + angleValue_++; + + if (scaleValue_.x_ < 0.2f) { + scaleStep_ = 0.01f; + } else if (scaleValue_.x_ > 1.5f) { + scaleStep_ = -0.01f; + } + scaleValue_.x_ += scaleStep_; + scaleValue_.y_ += scaleStep_; + + button1_->Rotate(angleValue_, VIEW_CENTER); + button1_->Scale(scaleValue_, VIEW_CENTER); + group1_->Rotate(angleValue_, GROUP_CENTER); + group1_->Scale(scaleValue_, GROUP_CENTER); + canvas_->Rotate(angleValue_, GROUP_CENTER); + canvas_->Scale(scaleValue_, GROUP_CENTER); + boxProgress_->Rotate(angleValue_, VIEW_CENTER); + boxProgress_->Scale(scaleValue_, VIEW_CENTER); + circleProgress_->Rotate(angleValue_, VIEW_CENTER); + circleProgress_->Scale(scaleValue_, VIEW_CENTER); + dClock_->Rotate(angleValue_, VIEW_CENTER); + dClock_->Scale(scaleValue_, VIEW_CENTER); + label_->Rotate(angleValue_, VIEW_CENTER); + label_->Scale(scaleValue_, VIEW_CENTER); +} + +void UITestViewScaleRotate::SetUpLabel(const char* title) const +{ + UILabel* label = new UILabel(); + label->SetPosition(TEXT_DISTANCE_TO_LEFT_SIDE, TEXT_DISTANCE_TO_TOP_SIDE, + Screen::GetInstance().GetWidth(), TITLE_LABEL_DEFAULT_HEIGHT); + label->SetText(title); + label->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + list_->Add(label); +} + +void UITestViewScaleRotate::UIKit_View_Scale_Rotate_Test_Label_001() +{ + SetUpLabel("UILabel组件旋转 "); + UIViewGroup* group = new UIViewGroup(); + group->SetPosition(0, 0, GROUP_WIDHT, GROUP_HEIGHT); + label_ = new UILabel(); + label_->SetFont(DEFAULT_VECTOR_FONT_FILENAME, FONT_DEFAULT_SIZE); + label_->SetText("Test UILabel"); + label_->SetPosition(100, 100, 200, 50); // 100: x 100: y 200: width 50: height + group->Add(label_); + list_->Add(group); +} + +void UITestViewScaleRotate::UIKit_View_Scale_Rotate_Test_LabelButton_002() +{ + SetUpLabel("UILabelButton组件旋转 "); + UIViewGroup* group = new UIViewGroup(); + group->SetPosition(0, 0, GROUP_WIDHT, GROUP_HEIGHT); + list_->Add(group); + button1_ = new UILabelButton(); + button1_->SetPosition(100, 100, 200, 50); // 100: x 100: y 200: width 50: height + button1_->SetText("Test UILabelButton"); + group->Add(button1_); +} + +void UITestViewScaleRotate::UIKit_View_Scale_Rotate_Test_Canvas_003() +{ + SetUpLabel("UICanvas组件旋转 "); + UIViewGroup* group = new UIViewGroup(); + group->SetPosition(0, 0, GROUP_WIDHT, GROUP_HEIGHT); + list_->Add(group); + canvas_ = new UICanvas(); + group->Add(canvas_); + + canvas_->SetPosition(0, 0, GROUP_WIDHT, GROUP_HEIGHT); + canvas_->SetStyle(STYLE_BACKGROUND_COLOR, Color::Gray().full); + + Paint paint; + paint.SetStrokeWidth(20); // 20: StrokeWidth + canvas_->DrawLine({150, 150}, {300, 300}, paint); // 150: x 150: y 300: x 300: y + + paint.SetStrokeColor(Color::Red()); + canvas_->DrawCurve({100, 50}, {150, 50}, {150, 50}, {150, 100}, paint); + canvas_->DrawRect({10, 10}, 50, 50, paint); // 10: x 10: y 50: width 50: height +} + +void UITestViewScaleRotate::UIKit_View_Scale_Rotate_Test_UIBoxProgress_004() +{ + SetUpLabel("UIBoxProgress组件旋转 "); + UIViewGroup* group = new UIViewGroup(); + group->SetPosition(0, 0, GROUP_WIDHT, GROUP_HEIGHT); + list_->Add(group); + boxProgress_ = new UIBoxProgress(); + + boxProgress_ = new UIBoxProgress(); + boxProgress_->EnableBackground(true); + boxProgress_->SetPosition(100, 100, 200, 50); // 100: x 100:y 200:width 50: height + boxProgress_->SetValue(80); // 80: value + boxProgress_->SetBackgroundStyle(STYLE_BACKGROUND_OPA, OPA_OPAQUE); + boxProgress_->SetBackgroundStyle(STYLE_BACKGROUND_COLOR, Color::Red().full); + group->Add(boxProgress_); +} + +void UITestViewScaleRotate::UIKit_View_Scale_Rotate_Test_UICircleProgress_005() +{ + SetUpLabel("UICircleProgress组件旋转 "); + UIViewGroup* group = new UIViewGroup(); + group->SetPosition(0, 0, GROUP_WIDHT, GROUP_HEIGHT); + list_->Add(group); + circleProgress_ = new UICircleProgress(); + circleProgress_->SetPosition(10, 10, 150, 150); // 10: x 10: y 150: width 150: height + circleProgress_->SetCenterPosition(75, 75); // 75: position x 75: position y + circleProgress_->SetRadius(50); // 50: radius + circleProgress_->SetValue(20); // 20: value + circleProgress_->SetStyle(STYLE_BACKGROUND_OPA, OPA_OPAQUE); + circleProgress_->SetStyle(STYLE_BACKGROUND_COLOR, Color::Yellow().full); + group->Add(circleProgress_); +} +void UITestViewScaleRotate::UIKit_View_Scale_Rotate_Test_UIDigitalClock_006() +{ + SetUpLabel("UIDigitalClock组件旋转 "); + UIViewGroup* group = new UIViewGroup(); + group->SetPosition(0, 0, GROUP_WIDHT, GROUP_HEIGHT); + list_->Add(group); + dClock_ = new UIDigitalClock(); + dClock_->SetPosition(10, 10, 150, 150); // 10: x 10:y 150: width 150: height + dClock_->SetFont(DEFAULT_VECTOR_FONT_FILENAME, 30); // 30: font size + dClock_->SetTime24Hour(7, 25, 50); // 7: hour, 25: minute, 50: second + group->Add(dClock_); +} + +void UITestViewScaleRotate::UIKit_View_Scale_Rotate_Test_Group_007() +{ + SetUpLabel("UIViewGroup组件旋转 "); + UIViewGroup* group = new UIViewGroup(); + group->SetPosition(0, 0, GROUP_WIDHT, GROUP_HEIGHT); + list_->Add(group); + + group1_ = new UIViewGroup(); + group1_->SetPosition(0, 0, GROUP_WIDHT, GROUP_HEIGHT); + group1_->SetStyle(STYLE_BACKGROUND_OPA, OPA_OPAQUE); + group1_->SetStyle(STYLE_BACKGROUND_COLOR, Color::Yellow().full); + group->Add(group1_); + + UISlider* slider = new UISlider(); + slider->SetPosition(10, 10, 200, 50); // 10: x 10: y 200: width 50: height + slider->SetValue(50); // 50: value + slider->SetDirection(UISlider::Direction::DIR_LEFT_TO_RIGHT); + group1_->Add(slider); + + UILabelButton* button = new UILabelButton(); + button->SetPosition(10, 100, 200, 50); // 10: x 100:y 200: width 50: height + button->SetText("test ViewGroup"); + group1_->Add(button); +} +} // namespace OHOS \ No newline at end of file diff --git a/test/uitest/test_view_scale_rotate/ui_test_view_scale_rotate.h b/test/uitest/test_view_scale_rotate/ui_test_view_scale_rotate.h new file mode 100755 index 0000000..17a6145 --- /dev/null +++ b/test/uitest/test_view_scale_rotate/ui_test_view_scale_rotate.h @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef UI_TEST_VIEW_SCALE_ROTATE_H +#define UI_TEST_VIEW_SCALE_ROTATE_H + +#include "components/ui_arc_label.h" +#include "components/ui_box_progress.h" +#include "components/ui_button.h" +#include "components/ui_canvas.h" +#include "components/ui_circle_progress.h" +#include "components/ui_digital_clock.h" +#include "components/ui_image_view.h" +#include "components/ui_label_button.h" +#include "components/ui_scroll_view.h" +#include "components/ui_slider.h" +#include "components/ui_swipe_view.h" +#include "layout/list_layout.h" +#include "ui_test.h" + +namespace OHOS { +class UITestViewScaleRotate : public UITest, public AnimatorCallback { +public: + UITestViewScaleRotate() + : animator_(this, nullptr, 1000, true) // 1000: the animator duration time is 1000ms + { + AnimatorManager::GetInstance()->Add(&animator_); + } + virtual ~UITestViewScaleRotate() + { + AnimatorManager::GetInstance()->Remove(&animator_); + } + void SetUp() override; + void TearDown() override; + UIView* GetTestView() override; + void Callback(UIView* view) override; + + void UIKit_View_Scale_Rotate_Test_Label_001(); + void UIKit_View_Scale_Rotate_Test_LabelButton_002(); + void UIKit_View_Scale_Rotate_Test_Canvas_003(); + void UIKit_View_Scale_Rotate_Test_UIBoxProgress_004(); + void UIKit_View_Scale_Rotate_Test_UICircleProgress_005(); + void UIKit_View_Scale_Rotate_Test_UIDigitalClock_006(); + void UIKit_View_Scale_Rotate_Test_Group_007(); + +private: + const int GROUP_WIDHT = 454; + const int GROUP_HEIGHT = 300; + const Vector2 VIEW_CENTER = {75, 75}; + const Vector2 GROUP_CENTER = {200, 200}; + + void SetUpLabel(const char* title) const; + + UIScrollView* container_ = nullptr; + ListLayout* list_ = nullptr; + int16_t angleValue_ = 0; + Vector2 scaleValue_ = {1.0f, 1.0f}; + float scaleStep_ = 0.01f; + + Animator animator_; + UILabel* label_ = nullptr; + UICanvas* canvas_ = nullptr; + UILabelButton* button1_ = nullptr; + UIBoxProgress* boxProgress_ = nullptr; + UICircleProgress* circleProgress_ = nullptr; + UIDigitalClock* dClock_ = nullptr; + UIViewGroup* group1_ = nullptr; +}; +} // namespace OHOS +#endif // UI_TEST_BOX_PROGRESS_H diff --git a/test/unittest/BUILD.gn b/test/unittest/BUILD.gn new file mode 100755 index 0000000..87ff009 --- /dev/null +++ b/test/unittest/BUILD.gn @@ -0,0 +1,287 @@ +# Copyright (c) 2020-2021 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("//build/lite/config/test.gni") + +group("lite_graphic_ui_test") { + if (ohos_build_type == "debug") { + deps = [ + ":graphic_test_animator", + ":graphic_test_axis", + ":graphic_test_button", + ":graphic_test_canvas", + ":graphic_test_clock", + ":graphic_test_common", + ":graphic_test_dfx", + ":graphic_test_dialog", + ":graphic_test_events", + ":graphic_test_font", + ":graphic_test_image", + ":graphic_test_label", + ":graphic_test_layout", + ":graphic_test_picker", + ":graphic_test_progress", + ":graphic_test_qrcode", + ":graphic_test_render", + ":graphic_test_themes", + ":graphic_test_video", + ":graphic_test_view", + ] + } +} + +config("graphic_test_config") { + include_dirs = [ + "../framework/include", + "//foundation/graphic/ui/frameworks", + "//foundation/graphic/surface/interfaces/kits", + "//third_party/bounds_checking_function/include", + "//third_party/freetype/include", + "//kernel/liteos_a/kernel/include/", + "//kernel/liteos_a/kernel/common", + "//foundation/distributedschedule/samgr_lite/interfaces/kits/registry", + "//foundation/distributedschedule/samgr_lite/interfaces/kits/samgr", + "//foundation/distributedschedule/samgr_lite/interfaces/kits/communication/broadcast", + "//foundation/multimedia/media_lite/interfaces/kits/player_lite", + "//foundation/multimedia/utils/lite/include", + ] + ldflags = [ + "-lstdc++", + "-Wl,-rpath-link=$ohos_root_path/$root_out_dir", + ] +} + +if (ohos_build_type == "debug") { + commonDeps = [ "//foundation/graphic/ui:ui" ] + unittest("graphic_test_animator") { + output_extension = "bin" + output_dir = "$root_out_dir/test/unittest/graphic" + configs = [ ":graphic_test_config" ] + deps = commonDeps + sources = [ + "animator/animator_unit_test.cpp", + "animator/easing_equation_unit_test.cpp", + "animator/interpolation_unit_test.cpp", + ] + } + + unittest("graphic_test_common") { + output_extension = "bin" + output_dir = "$root_out_dir/test/unittest/graphic" + configs = [ ":graphic_test_config" ] + deps = commonDeps + sources = [ + "common/hardware_acceleration_unit_test.cpp", + "common/screen_unit_test.cpp", + "common/text_unit_test.cpp", + ] + } + + unittest("graphic_test_events") { + output_extension = "bin" + output_dir = "$root_out_dir/test/unittest/graphic" + configs = [ ":graphic_test_config" ] + deps = commonDeps + sources = [ + "events/cancel_event_unit_test.cpp", + "events/click_event_unit_test.cpp", + "events/drag_event_unit_test.cpp", + "events/event_bubble_unit_test.cpp", + "events/event_unit_test.cpp", + "events/key_event_unit_test.cpp", + "events/long_press_event_unit_test.cpp", + "events/press_event_unit_test.cpp", + "events/release_event_unit_test.cpp", + "events/virtual_device_event_unit_test.cpp", + ] + } + + unittest("graphic_test_layout") { + output_extension = "bin" + output_dir = "$root_out_dir/test/unittest/graphic" + configs = [ ":graphic_test_config" ] + deps = commonDeps + sources = [ "layout/list_layout_unit_test.cpp" ] + } + + unittest("graphic_test_render") { + output_extension = "bin" + output_dir = "$root_out_dir/test/unittest/graphic" + configs = [ ":graphic_test_config" ] + deps = commonDeps + sources = [ "render/render_uni_test.cpp" ] + } + + unittest("graphic_test_themes") { + output_extension = "bin" + output_dir = "$root_out_dir/test/unittest/graphic" + configs = [ ":graphic_test_config" ] + deps = commonDeps + sources = [ "themes/theme_manager_unit_test.cpp" ] + } + + unittest("graphic_test_button") { + output_extension = "bin" + output_dir = "$root_out_dir/test/unittest/graphic" + configs = [ ":graphic_test_config" ] + deps = commonDeps + sources = [ + "components/ui_button_unit_test.cpp", + "components/ui_checkbox_unit_test.cpp", + "components/ui_label_button_unit_test.cpp", + "components/ui_radio_button_unit_test.cpp", + "components/ui_repeat_button_unit_test.cpp", + "components/ui_toggle_button_unit_test.cpp", + ] + } + + unittest("graphic_test_progress") { + output_extension = "bin" + output_dir = "$root_out_dir/test/unittest/graphic" + configs = [ ":graphic_test_config" ] + deps = commonDeps + sources = [ + "components/ui_abstract_progress_unit_test.cpp", + "components/ui_box_progress_unit_test.cpp", + "components/ui_circle_progress_unit_test.cpp", + ] + } + + unittest("graphic_test_clock") { + output_extension = "bin" + output_dir = "$root_out_dir/test/unittest/graphic" + configs = [ ":graphic_test_config" ] + deps = commonDeps + sources = [ + "components/ui_analog_clock_unit_test.cpp", + "components/ui_digital_clock_unit_test.cpp", + ] + } + + unittest("graphic_test_label") { + output_extension = "bin" + output_dir = "$root_out_dir/test/unittest/graphic" + configs = [ ":graphic_test_config" ] + deps = commonDeps + sources = [ + "components/ui_arc_label_unit_test.cpp", + "components/ui_label_unit_test.cpp", + ] + } + + unittest("graphic_test_axis") { + output_extension = "bin" + output_dir = "$root_out_dir/test/unittest/graphic" + configs = [ ":graphic_test_config" ] + deps = commonDeps + sources = [ "components/ui_axis_unit_test.cpp" ] + } + + unittest("graphic_test_canvas") { + output_extension = "bin" + output_dir = "$root_out_dir/test/unittest/graphic" + configs = [ ":graphic_test_config" ] + deps = commonDeps + sources = [ + "components/ui_canvas_unit_test.cpp", + "components/ui_chart_unit_test.cpp", + "components/ui_slider_unit_test.cpp", + ] + } + + unittest("graphic_test_image") { + output_extension = "bin" + output_dir = "$root_out_dir/test/unittest/graphic" + configs = [ ":graphic_test_config" ] + deps = commonDeps + sources = [ + "components/ui_image_animator_unit_test.cpp", + "components/ui_image_unit_test.cpp", + "image/image_load_unit_test.cpp", + ] + } + + unittest("graphic_test_picker") { + output_extension = "bin" + output_dir = "$root_out_dir/test/unittest/graphic" + configs = [ ":graphic_test_config" ] + deps = commonDeps + sources = [ + "components/ui_picker_unit_test.cpp", + "components/ui_time_picker_unit_test.cpp", + ] + } + + unittest("graphic_test_qrcode") { + output_extension = "bin" + output_dir = "$root_out_dir/test/unittest/graphic" + configs = [ ":graphic_test_config" ] + deps = commonDeps + sources = [ "components/ui_qrcode_unit_test.cpp" ] + } + + unittest("graphic_test_view") { + output_extension = "bin" + output_dir = "$root_out_dir/test/unittest/graphic" + configs = [ ":graphic_test_config" ] + deps = commonDeps + sources = [ + "components/ui_scroll_view_unit_test.cpp", + "components/ui_surface_view_unit_test.cpp", + "components/ui_swipe_view_unit_test.cpp", + "components/ui_view_group_unit_test.cpp", + "components/ui_view_unit_test.cpp", + ] + } + + unittest("graphic_test_video") { + output_extension = "bin" + output_dir = "$root_out_dir/test/unittest/graphic" + configs = [ ":graphic_test_config" ] + sources = [ "components/ui_video_unit_test.cpp" ] + deps = commonDeps + deps += [ + "//foundation/multimedia/media_lite/frameworks/player_lite:player_lite", + ] + } + + unittest("graphic_test_dfx") { + output_extension = "bin" + output_dir = "$root_out_dir/test/unittest/graphic" + configs = [ ":graphic_test_config" ] + deps = commonDeps + sources = [ "dfx/event_injector_unit_test.cpp" ] + } + + unittest("graphic_test_dialog") { + output_extension = "bin" + output_dir = "$root_out_dir/test/unittest/graphic" + configs = [ ":graphic_test_config" ] + deps = commonDeps + sources = [ "components/ui_dialog_unit_test.cpp" ] + } + + unittest("graphic_test_font") { + output_extension = "bin" + output_dir = "$root_out_dir/test/unittest/graphic" + configs = [ ":graphic_test_config" ] + deps = commonDeps + sources = [ "font/ui_font_unit_test.cpp" ] + } + + unittest("graphic_test_rotate") { + output_extension = "bin" + output_dir = "$root_out_dir/test/unittest/graphic" + configs = [ ":graphic_test_config" ] + deps = commonDeps + sources = [ "rotate/ui_rotate_input_unit_test.cpp" ] + } +} diff --git a/test/unittest/animator/animator_unit_test.cpp b/test/unittest/animator/animator_unit_test.cpp new file mode 100755 index 0000000..8d4103d --- /dev/null +++ b/test/unittest/animator/animator_unit_test.cpp @@ -0,0 +1,229 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "animator/animator.h" + +#include +#include +#include "animator/easing_equation.h" +#include "common/task_manager.h" + +using namespace testing::ext; +namespace OHOS { +namespace { + const int16_t START_POS = 0; + const int16_t END_POS = 100; + const uint16_t TIME = 300; +} +class TestAnimatorCallback : public AnimatorCallback { +public: + explicit TestAnimatorCallback(UIView* view) : view_(view), animator_(nullptr) {} + + virtual ~TestAnimatorCallback() + { + if (animator_ != nullptr) { + delete animator_; + animator_ = nullptr; + } + } + + bool Init() + { + if (animator_ == nullptr) { + animator_ = new Animator(this, view_, TIME, false); + if (animator_ == nullptr) { + return false; + } + return true; + } + return false; + } + + virtual void Callback(UIView* view) override + { + int16_t pos = EasingEquation::LinearEaseNone(START_POS, END_POS, animator_->GetRunTime(), animator_->GetTime()); + view_->SetX(pos); + } + + virtual void OnStop(UIView& view) override + { + EXPECT_EQ(view_->GetX(), END_POS); + } + + Animator* GetAnimator() const + { + return animator_; + } +protected: + UIView* view_; + Animator* animator_; +}; + + +class AnimatorTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + static Animator* animator_; +}; + +Animator* AnimatorTest::animator_ = nullptr; + +void AnimatorTest::SetUpTestCase(void) +{ + if (animator_ == nullptr) { + animator_ = new Animator(); + } +} + +void AnimatorTest::TearDownTestCase(void) +{ + if (animator_ != nullptr) { + delete animator_; + animator_ = nullptr; + } +} + +/** + * @tc.name: AnimatorGetState_001 + * @tc.desc: Verify Start function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMQM + */ +HWTEST_F(AnimatorTest, AnimatorGetState_001, TestSize.Level0) +{ + if (animator_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + animator_->Start(); + EXPECT_EQ(animator_->GetState(), Animator::START); + animator_->Stop(); + EXPECT_EQ(animator_->GetState(), Animator::STOP); + animator_->Pause(); + EXPECT_EQ(animator_->GetState(), Animator::PAUSE); + animator_->Resume(); + EXPECT_EQ(animator_->GetState(), Animator::START); +} + +/** + * @tc.name: AnimatorSetState_001 + * @tc.desc: Verify SetState function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMQM + */ +HWTEST_F(AnimatorTest, AnimatorSetState_001, TestSize.Level0) +{ + if (animator_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + animator_->SetState(Animator::START); + EXPECT_EQ(animator_->GetState(), Animator::START); + animator_->SetState(Animator::STOP); + EXPECT_EQ(animator_->GetState(), Animator::STOP); + animator_->SetState(Animator::PAUSE); + EXPECT_EQ(animator_->GetState(), Animator::PAUSE); +} + +/** + * @tc.name: AnimatorSetTime_001 + * @tc.desc: Verify SetTime function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMQM + */ +HWTEST_F(AnimatorTest, AnimatorSetTime_001, TestSize.Level0) +{ + if (animator_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + animator_->SetTime(TIME); + EXPECT_EQ(animator_->GetTime(), TIME); +} + +/** + * @tc.name: AnimatorSetRunTime_001 + * @tc.desc: Verify SetRunTime function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMQM + */ +HWTEST_F(AnimatorTest, AnimatorSetRunTime_001, TestSize.Level0) +{ + if (animator_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + animator_->SetRunTime(TIME); + EXPECT_EQ(animator_->GetRunTime(), TIME); +} + +/** + * @tc.name: AnimatorIsRepeat_001 + * @tc.desc: Verify IsRepeat function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMQM + */ +HWTEST_F(AnimatorTest, AnimatorIsRepeat_001, TestSize.Level0) +{ + if (animator_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + EXPECT_EQ(animator_->IsRepeat(), false); +} + +/** + * @tc.name: AnimatorManagerAddAndRemove_001 + * @tc.desc: Verify AddAndRemove function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMQM + */ +HWTEST_F(AnimatorTest, AnimatorManagerAddAndRemove_001, TestSize.Level1) +{ + UIView* view = new UIView(); + view->SetX(START_POS); + auto callback = new TestAnimatorCallback(view); + if (!callback->Init()) { + EXPECT_NE(0, 0); + return; + } + Animator* animator = callback->GetAnimator(); + AnimatorManager::GetInstance()->Init(); + AnimatorManager::GetInstance()->Add(animator); + animator->Start(); + EXPECT_EQ(animator->GetState(), Animator::START); + TaskManager::GetInstance()->SetTaskRun(true); + while (1) { + TaskManager::GetInstance()->TaskHandler(); + if (animator->GetState() == Animator::STOP) { + break; + } + } + + AnimatorManager::GetInstance()->Remove(animator); + view->SetX(START_POS); + animator->Start(); + EXPECT_EQ(animator->GetState(), Animator::START); + for (uint16_t i = 0; i < TIME; i++) { + TaskManager::GetInstance()->TaskHandler(); + } + EXPECT_EQ(view->GetX(), START_POS); + TaskManager::GetInstance()->SetTaskRun(false); + TaskManager::GetInstance()->Remove(AnimatorManager::GetInstance()); + delete callback; + delete view; +} +} // namespace OHOS \ No newline at end of file diff --git a/test/unittest/animator/easing_equation_unit_test.cpp b/test/unittest/animator/easing_equation_unit_test.cpp new file mode 100755 index 0000000..20e6574 --- /dev/null +++ b/test/unittest/animator/easing_equation_unit_test.cpp @@ -0,0 +1,340 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "animator/easing_equation.h" + +#include +#include + +using namespace testing::ext; +namespace OHOS { +namespace { + const int16_t START_POS = 0; + const int16_t END_POS = 100; + const int16_t DURATION_TIME = 4; +} + +class EasingEquationTest : public testing::Test { +public: + static void SetUpTestCase(void) {} + static void TearDownTestCase(void) {} +}; + +/** + * @tc.name: EasingEquationBackEaseIn_001 + * @tc.desc: Verify BackEaseIn and GetHeight function. + * @tc.type: FUNC + * @tc.require: AR000DSMQM + */ +HWTEST_F(EasingEquationTest, EasingEquationBackEaseIn_001, TestSize.Level1) +{ + const double overShoot = 2; + EasingEquation::SetBackOvershoot(overShoot); + uint16_t i = 0; + EXPECT_EQ(EasingEquation::BackEaseIn(START_POS, END_POS, i++, DURATION_TIME), 0); + EXPECT_EQ(EasingEquation::BackEaseIn(START_POS, END_POS, i++, DURATION_TIME), -7); // -7:the value for 1th s; + EXPECT_EQ(EasingEquation::BackEaseIn(START_POS, END_POS, i++, DURATION_TIME), -12); // -12:the value for 2th s; + EXPECT_EQ(EasingEquation::BackEaseIn(START_POS, END_POS, i++, DURATION_TIME), 14); // 14:the value for 3th s; + EXPECT_EQ(EasingEquation::BackEaseIn(START_POS, END_POS, i++, DURATION_TIME), END_POS); +} + +/** + * @tc.name: EasingEquationBackEaseOut_001 + * @tc.desc: Verify BackEaseOut and GetHeight function. + * @tc.type: FUNC + * @tc.require: AR000DSMQM + */ +HWTEST_F(EasingEquationTest, EasingEquationBackEaseOut_001, TestSize.Level1) +{ + uint16_t i = 0; + EXPECT_EQ(EasingEquation::BackEaseOut(START_POS, END_POS, i++, DURATION_TIME), 0); + EXPECT_EQ(EasingEquation::BackEaseOut(START_POS, END_POS, i++, DURATION_TIME), 85); // 85:the value for 1th s; + EXPECT_EQ(EasingEquation::BackEaseOut(START_POS, END_POS, i++, DURATION_TIME), 112); // 112:the value for 2th s; + EXPECT_EQ(EasingEquation::BackEaseOut(START_POS, END_POS, i++, DURATION_TIME), 107); // 107:the value for 3th s; + EXPECT_EQ(EasingEquation::BackEaseOut(START_POS, END_POS, i++, DURATION_TIME), END_POS); +} + +/** + * @tc.name: EasingEquationBackEaseInOut_001 + * @tc.desc: Verify BackEaseInOut and GetHeight function. + * @tc.type: FUNC + * @tc.require: AR000DSMQM + */ +HWTEST_F(EasingEquationTest, EasingEquationBackEaseInOut_001, TestSize.Level1) +{ + uint16_t i = 0; + EXPECT_EQ(EasingEquation::BackEaseInOut(START_POS, END_POS, i++, DURATION_TIME), 0); + EXPECT_EQ(EasingEquation::BackEaseInOut(START_POS, END_POS, i++, DURATION_TIME), -6); // -6:the value for 1th s; + EXPECT_EQ(EasingEquation::BackEaseInOut(START_POS, END_POS, i++, DURATION_TIME), 50); // 50:the value for 2th s; + EXPECT_EQ(EasingEquation::BackEaseInOut(START_POS, END_POS, i++, DURATION_TIME), 106); // 106:the value for 3th s; + EXPECT_EQ(EasingEquation::BackEaseInOut(START_POS, END_POS, i++, DURATION_TIME), END_POS); +} + +/** + * @tc.name: EasingEquationCircEaseIn_001 + * @tc.desc: Verify CircEaseIn and GetHeight function. + * @tc.type: FUNC + * @tc.require: AR000DSMQM + */ +HWTEST_F(EasingEquationTest, EasingEquationCircEaseIn_001, TestSize.Level1) +{ + uint16_t i = 0; + EXPECT_EQ(EasingEquation::CircEaseIn(START_POS, END_POS, i++, DURATION_TIME), 0); + EXPECT_EQ(EasingEquation::CircEaseIn(START_POS, END_POS, i++, DURATION_TIME), 3); // 3:the value for 1th s; + EXPECT_EQ(EasingEquation::CircEaseIn(START_POS, END_POS, i++, DURATION_TIME), 13); // 13:the value for 2th s; + EXPECT_EQ(EasingEquation::CircEaseIn(START_POS, END_POS, i++, DURATION_TIME), 33); // 33:the value for 3th s; + EXPECT_EQ(EasingEquation::CircEaseIn(START_POS, END_POS, i++, DURATION_TIME), END_POS); +} + +/** + * @tc.name: EasingEquationCircEaseOut_001 + * @tc.desc: Verify CircEaseOut and GetHeight function. + * @tc.type: FUNC + * @tc.require: AR000DSMQM + */ +HWTEST_F(EasingEquationTest, EasingEquationCircEaseOut_001, TestSize.Level1) +{ + uint16_t i = 0; + EXPECT_EQ(EasingEquation::CircEaseOut(START_POS, END_POS, i++, DURATION_TIME), 0); + EXPECT_EQ(EasingEquation::CircEaseOut(START_POS, END_POS, i++, DURATION_TIME), 66); // 66:the value for 1th s; + EXPECT_EQ(EasingEquation::CircEaseOut(START_POS, END_POS, i++, DURATION_TIME), 86); // 86:the value for 2th s; + EXPECT_EQ(EasingEquation::CircEaseOut(START_POS, END_POS, i++, DURATION_TIME), 96); // 96:the value for 3th s; + EXPECT_EQ(EasingEquation::CircEaseOut(START_POS, END_POS, i++, DURATION_TIME), END_POS); +} + +/** + * @tc.name: EasingEquationCircEaseInOut_001 + * @tc.desc: Verify CircEaseInOut and GetHeight function. + * @tc.type: FUNC + * @tc.require: AR000DSMQM + */ +HWTEST_F(EasingEquationTest, EasingEquationCircEaseInOut_001, TestSize.Level1) +{ + uint16_t i = 0; + EXPECT_EQ(EasingEquation::CircEaseInOut(START_POS, END_POS, i++, DURATION_TIME), 0); + EXPECT_EQ(EasingEquation::CircEaseInOut(START_POS, END_POS, i++, DURATION_TIME), 6); // 6:the value for 1th s; + EXPECT_EQ(EasingEquation::CircEaseInOut(START_POS, END_POS, i++, DURATION_TIME), 50); // 50:the value for 2th s; + EXPECT_EQ(EasingEquation::CircEaseInOut(START_POS, END_POS, i++, DURATION_TIME), 93); // 93:the value for 3th s; + EXPECT_EQ(EasingEquation::CircEaseInOut(START_POS, END_POS, i++, DURATION_TIME), END_POS); +} + +/** + * @tc.name: EasingEquationCubicEaseIn_001 + * @tc.desc: Verify CubicEaseIn and GetHeight function. + * @tc.type: FUNC + * @tc.require: AR000DSMQM + */ +HWTEST_F(EasingEquationTest, EasingEquationCubicEaseIn_001, TestSize.Level1) +{ + uint16_t i = 0; + EXPECT_EQ(EasingEquation::CubicEaseIn(START_POS, END_POS, i++, DURATION_TIME), 0); + EXPECT_EQ(EasingEquation::CubicEaseIn(START_POS, END_POS, i++, DURATION_TIME), 1); // 1:the value for 1th s; + EXPECT_EQ(EasingEquation::CubicEaseIn(START_POS, END_POS, i++, DURATION_TIME), 12); // 12:the value for 2th s; + EXPECT_EQ(EasingEquation::CubicEaseIn(START_POS, END_POS, i++, DURATION_TIME), 42); // 42:the value for 3th s; + EXPECT_EQ(EasingEquation::CubicEaseIn(START_POS, END_POS, i++, DURATION_TIME), END_POS); +} + +/** + * @tc.name: EasingEquationCubicEaseOut_001 + * @tc.desc: Verify CubicEaseOut and GetHeight function. + * @tc.type: FUNC + * @tc.require: AR000DSMQM + */ +HWTEST_F(EasingEquationTest, EasingEquationCubicEaseOut_001, TestSize.Level1) +{ + uint16_t i = 0; + EXPECT_EQ(EasingEquation::CubicEaseOut(START_POS, END_POS, i++, DURATION_TIME), 0); + EXPECT_EQ(EasingEquation::CubicEaseOut(START_POS, END_POS, i++, DURATION_TIME), 57); // 57:the value for 1th s; + EXPECT_EQ(EasingEquation::CubicEaseOut(START_POS, END_POS, i++, DURATION_TIME), 87); // 87:the value for 2th s; + EXPECT_EQ(EasingEquation::CubicEaseOut(START_POS, END_POS, i++, DURATION_TIME), 98); // 98:the value for 3th s; + EXPECT_EQ(EasingEquation::CubicEaseOut(START_POS, END_POS, i++, DURATION_TIME), END_POS); +} + +/** + * @tc.name: EasingEquationCubicEaseInOut_001 + * @tc.desc: Verify CubicEaseInOut and GetHeight function. + * @tc.type: FUNC + * @tc.require: AR000DSMQM + */ +HWTEST_F(EasingEquationTest, EasingEquationCubicEaseInOut_001, TestSize.Level1) +{ + uint16_t i = 0; + EXPECT_EQ(EasingEquation::CubicEaseInOut(START_POS, END_POS, i++, DURATION_TIME), 0); + EXPECT_EQ(EasingEquation::CubicEaseInOut(START_POS, END_POS, i++, DURATION_TIME), 6); // 6:the value for 1th s; + EXPECT_EQ(EasingEquation::CubicEaseInOut(START_POS, END_POS, i++, DURATION_TIME), 50); // 50:the value for 2th s; + EXPECT_EQ(EasingEquation::CubicEaseInOut(START_POS, END_POS, i++, DURATION_TIME), 93); // 93:the value for 3th s; + EXPECT_EQ(EasingEquation::CubicEaseInOut(START_POS, END_POS, i++, DURATION_TIME), END_POS); +} + +/** + * @tc.name: EasingEquationLinearEaseNone_001 + * @tc.desc: Verify LinearEaseNone and GetHeight function. + * @tc.type: FUNC + * @tc.require: AR000DSMQM + */ +HWTEST_F(EasingEquationTest, EasingEquationLinearEaseNone_001, TestSize.Level1) +{ + uint16_t i = 0; + EXPECT_EQ(EasingEquation::LinearEaseNone(START_POS, END_POS, i++, DURATION_TIME), 0); + EXPECT_EQ(EasingEquation::LinearEaseNone(START_POS, END_POS, i++, DURATION_TIME), 25); // 25:the value for 1th s; + EXPECT_EQ(EasingEquation::LinearEaseNone(START_POS, END_POS, i++, DURATION_TIME), 50); // 50:the value for 2th s; + EXPECT_EQ(EasingEquation::LinearEaseNone(START_POS, END_POS, i++, DURATION_TIME), 75); // 75:the value for 3th s; + EXPECT_EQ(EasingEquation::LinearEaseNone(START_POS, END_POS, i++, DURATION_TIME), END_POS); +} + +/** + * @tc.name: EasingEquationQuadEaseIn_001 + * @tc.desc: Verify QuadEaseIn and GetHeight function. + * @tc.type: FUNC + * @tc.require: AR000DSMQM + */ +HWTEST_F(EasingEquationTest, EasingEquationQuadEaseIn_001, TestSize.Level1) +{ + uint16_t i = 0; + EXPECT_EQ(EasingEquation::QuadEaseIn(START_POS, END_POS, i++, DURATION_TIME), 0); + EXPECT_EQ(EasingEquation::QuadEaseIn(START_POS, END_POS, i++, DURATION_TIME), 6); // 6:the value for 1th s; + EXPECT_EQ(EasingEquation::QuadEaseIn(START_POS, END_POS, i++, DURATION_TIME), 25); // 25:the value for 2th s; + EXPECT_EQ(EasingEquation::QuadEaseIn(START_POS, END_POS, i++, DURATION_TIME), 56); // 56:the value for 3th s; + EXPECT_EQ(EasingEquation::QuadEaseIn(START_POS, END_POS, i++, DURATION_TIME), END_POS); +} + +/** + * @tc.name: EasingEquationQuadEaseOut_001 + * @tc.desc: Verify QuadEaseOut and GetHeight function. + * @tc.type: FUNC + * @tc.require: AR000DSMQM + */ +HWTEST_F(EasingEquationTest, EasingEquationQuadEaseOut_001, TestSize.Level1) +{ + uint16_t i = 0; + EXPECT_EQ(EasingEquation::QuadEaseOut(START_POS, END_POS, i++, DURATION_TIME), 0); + EXPECT_EQ(EasingEquation::QuadEaseOut(START_POS, END_POS, i++, DURATION_TIME), 43); // 43:the value for 1th s; + EXPECT_EQ(EasingEquation::QuadEaseOut(START_POS, END_POS, i++, DURATION_TIME), 75); // 75:the value for 2th s; + EXPECT_EQ(EasingEquation::QuadEaseOut(START_POS, END_POS, i++, DURATION_TIME), 93); // 93:the value for 3th s; + EXPECT_EQ(EasingEquation::QuadEaseOut(START_POS, END_POS, i++, DURATION_TIME), END_POS); +} + +/** + * @tc.name: EasingEquationQuadEaseInOut_001 + * @tc.desc: Verify QuadEaseInOut and GetHeight function. + * @tc.type: FUNC + * @tc.require: AR000DSMQM + */ +HWTEST_F(EasingEquationTest, EasingEquationQuadEaseInOut_001, TestSize.Level1) +{ + uint16_t i = 0; + EXPECT_EQ(EasingEquation::QuadEaseInOut(START_POS, END_POS, i++, DURATION_TIME), 0); + EXPECT_EQ(EasingEquation::QuadEaseInOut(START_POS, END_POS, i++, DURATION_TIME), 12); // 12:the value for 1th s; + EXPECT_EQ(EasingEquation::QuadEaseInOut(START_POS, END_POS, i++, DURATION_TIME), 50); // 50:the value for 2th s; + EXPECT_EQ(EasingEquation::QuadEaseInOut(START_POS, END_POS, i++, DURATION_TIME), 87); // 87:the value for 3th s; + EXPECT_EQ(EasingEquation::QuadEaseInOut(START_POS, END_POS, i++, DURATION_TIME), END_POS); +} + +/** + * @tc.name: EasingEquationQuintEaseIn_001 + * @tc.desc: Verify QuintEaseIn and GetHeight function. + * @tc.type: FUNC + * @tc.require: AR000DSMQM + */ +HWTEST_F(EasingEquationTest, EasingEquationQuintEaseIn_001, TestSize.Level1) +{ + uint16_t i = 0; + EXPECT_EQ(EasingEquation::QuintEaseIn(START_POS, END_POS, i++, DURATION_TIME), 0); + EXPECT_EQ(EasingEquation::QuintEaseIn(START_POS, END_POS, i++, DURATION_TIME), 0); // 0:the value for 1th s; + EXPECT_EQ(EasingEquation::QuintEaseIn(START_POS, END_POS, i++, DURATION_TIME), 3); // 3:the value for 2th s; + EXPECT_EQ(EasingEquation::QuintEaseIn(START_POS, END_POS, i++, DURATION_TIME), 23); // 23:the value for 3th s; + EXPECT_EQ(EasingEquation::QuintEaseIn(START_POS, END_POS, i++, DURATION_TIME), END_POS); +} + +/** + * @tc.name: EasingEquationQuintEaseOut_001 + * @tc.desc: Verify QuintEaseOut and GetHeight function. + * @tc.type: FUNC + * @tc.require: AR000DSMQM + */ +HWTEST_F(EasingEquationTest, EasingEquationQuintEaseOut_001, TestSize.Level1) +{ + uint16_t i = 0; + EXPECT_EQ(EasingEquation::QuintEaseOut(START_POS, END_POS, i++, DURATION_TIME), 0); + EXPECT_EQ(EasingEquation::QuintEaseOut(START_POS, END_POS, i++, DURATION_TIME), 76); // 76:the value for 1th s; + EXPECT_EQ(EasingEquation::QuintEaseOut(START_POS, END_POS, i++, DURATION_TIME), 96); // 96:the value for 2th s; + EXPECT_EQ(EasingEquation::QuintEaseOut(START_POS, END_POS, i++, DURATION_TIME), 99); // 99:the value for 3th s; + EXPECT_EQ(EasingEquation::QuintEaseOut(START_POS, END_POS, i++, DURATION_TIME), END_POS); +} + +/** + * @tc.name: EasingEquationQuintEaseInOut_001 + * @tc.desc: Verify QuintEaseInOut and GetHeight function. + * @tc.type: FUNC + * @tc.require: AR000DSMQM + */ +HWTEST_F(EasingEquationTest, EasingEquationQuintEaseInOut_001, TestSize.Level1) +{ + uint16_t i = 0; + EXPECT_EQ(EasingEquation::QuintEaseInOut(START_POS, END_POS, i++, DURATION_TIME), 0); + EXPECT_EQ(EasingEquation::QuintEaseInOut(START_POS, END_POS, i++, DURATION_TIME), 1); // 1:the value for 1th s; + EXPECT_EQ(EasingEquation::QuintEaseInOut(START_POS, END_POS, i++, DURATION_TIME), 50); // 50:the value for 2th s; + EXPECT_EQ(EasingEquation::QuintEaseInOut(START_POS, END_POS, i++, DURATION_TIME), 98); // 87:the value for 3th s; + EXPECT_EQ(EasingEquation::QuintEaseInOut(START_POS, END_POS, i++, DURATION_TIME), END_POS); +} + +/** + * @tc.name: EasingEquationSineEaseIn_001 + * @tc.desc: Verify SineEaseIn and GetHeight function. + * @tc.type: FUNC + * @tc.require: AR000DSMQM + */ +HWTEST_F(EasingEquationTest, EasingEquationSineEaseIn_001, TestSize.Level1) +{ + uint16_t i = 0; + EXPECT_EQ(EasingEquation::SineEaseIn(START_POS, END_POS, i++, DURATION_TIME), 0); + EXPECT_EQ(EasingEquation::SineEaseIn(START_POS, END_POS, i++, DURATION_TIME), 7); // 7:the value for 1th s; + EXPECT_EQ(EasingEquation::SineEaseIn(START_POS, END_POS, i++, DURATION_TIME), 29); // 29:the value for 2th s; + EXPECT_EQ(EasingEquation::SineEaseIn(START_POS, END_POS, i++, DURATION_TIME), 60); // 60:the value for 3th s; + EXPECT_EQ(EasingEquation::SineEaseIn(START_POS, END_POS, i++, DURATION_TIME), END_POS); +} + +/** + * @tc.name: EasingEquationSineEaseOut_001 + * @tc.desc: Verify SineEaseOut and GetHeight function. + * @tc.type: FUNC + * @tc.require: AR000DSMQM + */ +HWTEST_F(EasingEquationTest, EasingEquationSineEaseOut_001, TestSize.Level1) +{ + uint16_t i = 0; + EXPECT_EQ(EasingEquation::SineEaseOut(START_POS, END_POS, i++, DURATION_TIME), 0); + EXPECT_EQ(EasingEquation::SineEaseOut(START_POS, END_POS, i++, DURATION_TIME), 37); // 37:the value for 1th s; + EXPECT_EQ(EasingEquation::SineEaseOut(START_POS, END_POS, i++, DURATION_TIME), 70); // 70:the value for 2th s; + EXPECT_EQ(EasingEquation::SineEaseOut(START_POS, END_POS, i++, DURATION_TIME), 92); // 92:the value for 3th s; + EXPECT_EQ(EasingEquation::SineEaseOut(START_POS, END_POS, i++, DURATION_TIME), END_POS); +} + +/** + * @tc.name: EasingEquationSineEaseInOut_001 + * @tc.desc: Verify SineEaseInOut and GetHeight function. + * @tc.type: FUNC + * @tc.require: AR000DSMQM + */ +HWTEST_F(EasingEquationTest, EasingEquationSineEaseInOut_001, TestSize.Level1) +{ + uint16_t i = 0; + EXPECT_EQ(EasingEquation::SineEaseInOut(START_POS, END_POS, i++, DURATION_TIME), 0); + EXPECT_EQ(EasingEquation::SineEaseInOut(START_POS, END_POS, i++, DURATION_TIME), 14); // 14:the value for 1th s; + EXPECT_EQ(EasingEquation::SineEaseInOut(START_POS, END_POS, i++, DURATION_TIME), 50); // 50:the value for 2th s; + EXPECT_EQ(EasingEquation::SineEaseInOut(START_POS, END_POS, i++, DURATION_TIME), 85); // 85:the value for 3th s; + EXPECT_EQ(EasingEquation::SineEaseInOut(START_POS, END_POS, i++, DURATION_TIME), END_POS); +} +} // namespace OHOS diff --git a/test/unittest/animator/interpolation_unit_test.cpp b/test/unittest/animator/interpolation_unit_test.cpp new file mode 100755 index 0000000..a8d5a59 --- /dev/null +++ b/test/unittest/animator/interpolation_unit_test.cpp @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "animator/interpolation.h" + +#include +#include + +using namespace testing::ext; +namespace OHOS { +namespace { + constexpr int16_t U1 = 512; + constexpr int16_t U2 = 256; + constexpr int16_t U3 = 128; + constexpr int16_t U4 = 512; +} +class InterpolationTest : public testing::Test { +public: + static void SetUpTestCase(void) {} + static void TearDownTestCase(void) {} +}; + +/** + * @tc.name: InterpolationGetBezierInterpolation_001 + * @tc.desc: Verify GetBezierInterpolation function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMQM + */ +HWTEST_F(InterpolationTest, InterpolationGetBezierInterpolation_001, TestSize.Level1) +{ + int16_t ret = Interpolation::GetBezierInterpolation(200, U1, U2, U3, U4); // 200:current change rate; + EXPECT_EQ(ret, 379); // 379:current change rate; + ret = Interpolation::GetBezierInterpolation(500, U1, U2, U3, U4); // 500:current change rate; + EXPECT_EQ(ret, 273); // 273:current change rate; + ret = Interpolation::GetBezierInterpolation(700, U1, U2, U3, U4); // 700:current change rate; + EXPECT_EQ(ret, 289); // 289:current change rate; + ret = Interpolation::GetBezierInterpolation(900, U1, U2, U3, U4); // 900:current change rate; + EXPECT_EQ(ret, 394); // 394:current change rate; +} +} // namespace OHOS \ No newline at end of file diff --git a/test/unittest/common/hardware_acceleration_unit_test.cpp b/test/unittest/common/hardware_acceleration_unit_test.cpp new file mode 100644 index 0000000..9c39f1a --- /dev/null +++ b/test/unittest/common/hardware_acceleration_unit_test.cpp @@ -0,0 +1,291 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "common/graphic_startup.h" +#include "common/task_manager.h" +#include "components/root_view.h" +#include "components/ui_image_view.h" +#include "components/ui_label.h" +#include "components/ui_texture_mapper.h" +#include "components/ui_view_group.h" +#include "dock/screen_device.h" +#include "dock/screen_device_proxy.h" +#include "style.h" +#include "test_resource_config.h" +#include "window/window.h" + +#include +#include + +using namespace testing::ext; +namespace OHOS { +class TestDevice : public ScreenDevice { +public: + TestDevice() {} + virtual ~TestDevice() {} + + bool HardwareFill(const Rect& fillArea, + uint32_t color, + OpacityType opa, + uint8_t* dst, + uint32_t dstStride, + ColorMode dstColorMode) override + { + fillIn_ = true; + return false; + } + + bool HardwareBlend(const uint8_t* src, + const Rect& srcRect, + uint32_t srcStride, + uint32_t srcLineNumber, + ColorMode srcColorMode, + uint32_t color, + OpacityType opa, + uint8_t* dst, + uint32_t dstStride, + ColorMode dstColorMode, + uint32_t x, + uint32_t y) override + { + blendIn_ = true; + return false; + } + + bool HardwareTransform(const uint8_t* src, + ColorMode srcColorMode, + const Rect& srcRect, + const Matrix3& transformMatrix, + OpacityType opa, + uint32_t color, + const Rect& mask, + uint8_t* dst, + uint32_t dstStride, + ColorMode dstColorMode, + const TransformOption& option) override + { + transformIn_ = true; + return false; + } + + bool fillIn_ = false; + bool blendIn_ = false; + bool transformIn_ = false; +}; + +static TestDevice* g_testDevice = nullptr; + +class HardwareAccelerationTest : public testing::Test { +public: + static void SetUpTestCase(void) + { + GraphicStartUp::Init(); + g_testDevice = new TestDevice; + ScreenDeviceProxy::GetInstance()->SetDevice(g_testDevice); + } + static void TearDownTestCase(void) + { + delete g_testDevice; + g_testDevice = nullptr; + ScreenDeviceProxy::GetInstance()->SetDevice(nullptr); + } + void SetUp() + { + g_testDevice->fillIn_ = false; + g_testDevice->blendIn_ = false; + g_testDevice->transformIn_ = false; + } + void TearDown() {} + + static void CreateDefaultWindow(RootView* rootView, int x, int y) + { + WindowConfig config = {}; + config.rect = rootView->GetRect(); + config.rect.SetPosition(x, y); + Window* window = Window::CreateWindow(config); + if (window != nullptr) { + window->BindRootView(rootView); + } + } +}; + +/** + * @tc.name: HardwareFill_001 + * @tc.desc: Verify HardwareFill function, equal. + * Need set ENABLE_HARDWARE_ACCELERATION to 1. + * @tc.type: FUNC + * @tc.require: SR000ERCR6 + */ +#if ENABLE_HARDWARE_ACCELERATION +HWTEST_F(HardwareAccelerationTest, HardwareFill_001, TestSize.Level0) +{ + RootView* rootView = RootView::GetInstance(); + rootView->SetWidth(600); // 600: width + rootView->SetHeight(300); // 300: height + rootView->SetPosition(0, 0); + rootView->SetStyle(STYLE_BACKGROUND_COLOR, Color::Olive().full); + rootView->Invalidate(); + + HardwareAccelerationTest::CreateDefaultWindow(rootView, 0, 0); + + TaskManager::GetInstance()->TaskHandler(); + EXPECT_EQ(g_testDevice->fillIn_, true); + Window::DestoryWindow(rootView->GetBoundWindow()); +} +#endif + +/** + * @tc.name: HardwareBlend_001 + * @tc.desc: Verify HardwareBlend function, equal. + * Need set ENABLE_HARDWARE_ACCELERATION to 1. + * @tc.type: FUNC + * @tc.require: AR000EVI3I + */ +#if ENABLE_HARDWARE_ACCELERATION +HWTEST_F(HardwareAccelerationTest, HardwareBlend_001, TestSize.Level0) +{ + RootView* rootView = RootView::GetInstance(); + rootView->SetWidth(600); // 600: width + rootView->SetHeight(300); // 300: height + rootView->SetPosition(0, 0); + rootView->SetStyle(STYLE_BACKGROUND_COLOR, Color::Olive().full); + + UIImageView* imageView = new UIImageView; + imageView->SetPosition(200, 50, 200, 200); // 200: x; 50: y; 200: width; 200: height + imageView->SetSrc(RED_IMAGE_PATH); + + rootView->Add(imageView); + rootView->Invalidate(); + + HardwareAccelerationTest::CreateDefaultWindow(rootView, 0, 0); + + TaskManager::GetInstance()->TaskHandler(); + EXPECT_EQ(g_testDevice->blendIn_, true); + Window::DestoryWindow(rootView->GetBoundWindow()); +} +#endif + +/** + * @tc.name: HardwareBlend_002 + * @tc.desc: Verify HardwareBlend function for text, equal. + * Need set ENABLE_HARDWARE_ACCELERATION and ENABLE_HARDWARE_ACCELERATION_FOR_TEXT to 1. + * @tc.type: FUNC + * @tc.require: AR000EVI3I + */ +#if ENABLE_HARDWARE_ACCELERATION && ENABLE_HARDWARE_ACCELERATION_FOR_TEXT +HWTEST_F(HardwareAccelerationTest, HardwareBlend_002, TestSize.Level0) +{ + RootView* rootView = RootView::GetInstance(); + rootView->SetWidth(600); // 600: width + rootView->SetHeight(300); // 300: height + rootView->SetPosition(0, 0); + rootView->SetStyle(STYLE_BACKGROUND_COLOR, Color::Olive().full); + + UILabel* label = new UILabel(); + // 288: x-coordinate; 48: y-coordinate + label->SetPosition(0, 0, 288, 48); + label->SetText("HardwareBlend_002 测试"); + label->SetFont(DEFAULT_VECTOR_FONT_FILENAME, 18); // 18 : font size + + rootView->Add(label); + rootView->Invalidate(); + + HardwareAccelerationTest::CreateDefaultWindow(rootView, 0, 0); + + TaskManager::GetInstance()->TaskHandler(); + EXPECT_EQ(g_testDevice->blendIn_, true); + Window::DestoryWindow(rootView->GetBoundWindow()); +} +#endif + +/** + * @tc.name: HardwareTransform_001 + * @tc.desc: Verify HardwareTransform function, equal. + * Need set ENABLE_HARDWARE_ACCELERATION to 1. + * @tc.type: FUNC + * @tc.require: AR000EVI3I + */ +#if ENABLE_HARDWARE_ACCELERATION +HWTEST_F(HardwareAccelerationTest, HardwareTransform_001, TestSize.Level0) +{ + RootView* rootView = RootView::GetInstance(); + rootView->SetWidth(600); // 600: width + rootView->SetHeight(300); // 300: height + rootView->SetPosition(0, 0); + rootView->SetStyle(STYLE_BACKGROUND_COLOR, Color::Olive().full); + + UIImageView* imageView = new UIImageView; + imageView->SetPosition(200, 50, 200, 200); // 200: x; 50: y; 200: width; 200: height + imageView->SetSrc(RED_IMAGE_PATH); + Rect viewRect = imageView->GetOrigRect(); + TransformMap transMap(viewRect); + Vector2 pivot_(58, 58); // 58:x value 58:y value + transMap.Rotate(90, pivot_); // 90:degree + transMap.Scale(Vector2(1.5, 1.5), pivot_); // 1.5:x scale 1.5:y scale + imageView->SetTransformMap(transMap); + + rootView->Add(imageView); + rootView->Invalidate(); + + HardwareAccelerationTest::CreateDefaultWindow(rootView, 0, 0); + + TaskManager::GetInstance()->TaskHandler(); + EXPECT_EQ(g_testDevice->transformIn_, true); + Window::DestoryWindow(rootView->GetBoundWindow()); +} +#endif + +/** + * @tc.name: HardwareTransform_002 + * @tc.desc: Verify HardwareTransform function in abnormal scene, equal. + * Need set ENABLE_HARDWARE_ACCELERATION to 1. + * @tc.type: FUNC + * @tc.require: AR000EVI3I + */ +#if ENABLE_HARDWARE_ACCELERATION +HWTEST_F(HardwareAccelerationTest, HardwareTransform_002, TestSize.Level0) +{ + // set device to null + ScreenDeviceProxy::GetInstance()->SetDevice(nullptr); + + RootView* rootView = RootView::GetInstance(); + rootView->SetWidth(600); // 600: width + rootView->SetHeight(300); // 300: height + rootView->SetPosition(0, 0); + rootView->SetStyle(STYLE_BACKGROUND_COLOR, Color::Olive().full); + + UIImageView* imageView = new UIImageView; + imageView->SetPosition(200, 50, 200, 200); // 200: x; 50: y; 200: width; 200: height + imageView->SetSrc(RED_IMAGE_PATH); + Rect viewRect = imageView->GetOrigRect(); + TransformMap transMap(viewRect); + Vector2 pivot_(58, 58); // 58:x value 58:y value + transMap.Rotate(90, pivot_); // 90:degree + transMap.Scale(Vector2(1.5, 1.5), pivot_); // 1.5:x scale 1.5:y scale + imageView->SetTransformMap(transMap); + + rootView->Add(imageView); + rootView->Invalidate(); + + HardwareAccelerationTest::CreateDefaultWindow(rootView, 0, 0); + + TaskManager::GetInstance()->TaskHandler(); + EXPECT_EQ(g_testDevice->fillIn_, false); + EXPECT_EQ(g_testDevice->blendIn_, false); + EXPECT_EQ(g_testDevice->transformIn_, false); + Window::DestoryWindow(rootView->GetBoundWindow()); +} +#endif +} // namespace OHOS \ No newline at end of file diff --git a/test/unittest/common/screen_unit_test.cpp b/test/unittest/common/screen_unit_test.cpp new file mode 100755 index 0000000..c451b59 --- /dev/null +++ b/test/unittest/common/screen_unit_test.cpp @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "common/screen.h" + +#include +#include + +using namespace testing::ext; +namespace OHOS { +class ScreenTest : public testing::Test { +public: + static void SetUpTestCase(void) {} + static void TearDownTestCase(void) {} +}; + +/** + * @tc.name: ScreenGetWidth_001 + * @tc.desc: Verify GetWidth function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQ9 + */ +HWTEST_F(ScreenTest, ScreenGetWidth_001, TestSize.Level0) +{ + EXPECT_EQ(Screen::GetInstance().GetWidth(), HORIZONTAL_RESOLUTION); +} + +/** + * @tc.name: ScreenGetHeight_001 + * @tc.desc: Verify GetHeight function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQ9 + */ +HWTEST_F(ScreenTest, ScreenGetHeight_001, TestSize.Level0) +{ + EXPECT_EQ(Screen::GetInstance().GetHeight(), VERTICAL_RESOLUTION); +} +} // namespace OHOS \ No newline at end of file diff --git a/test/unittest/common/text_unit_test.cpp b/test/unittest/common/text_unit_test.cpp new file mode 100755 index 0000000..cafb789 --- /dev/null +++ b/test/unittest/common/text_unit_test.cpp @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "common/text.h" + +#include +#include + +using namespace testing::ext; +namespace OHOS { +class TextTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + static Text* text_; +}; + +Text* TextTest::text_ = nullptr; + +void TextTest::SetUpTestCase(void) +{ + if (text_ == nullptr) { + text_ = new Text(); + } +} + +void TextTest::TearDownTestCase(void) +{ + if (text_ != nullptr) { + delete text_; + text_ = nullptr; + } +} + +/** + * @tc.name: TextSetText_001 + * @tc.desc: Verify SetText function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMQ1 + */ +HWTEST_F(TextTest, TextSetText_001, TestSize.Level0) +{ + if (text_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + const char* text = "unit test text"; + text_->SetText(text); + EXPECT_EQ(strcmp(text_->GetText(), text), 0); +} + +/** + * @tc.name: TextSetDirect_001 + * @tc.desc: Verify SetDirect function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMQ1 + */ +HWTEST_F(TextTest, TextSetDirect_001, TestSize.Level0) +{ + if (text_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + UITextLanguageDirect direct = UITextLanguageDirect::TEXT_DIRECT_LTR; + text_->SetDirect(direct); + EXPECT_EQ(text_->GetDirect(), direct); + direct = UITextLanguageDirect::TEXT_DIRECT_RTL; + text_->SetDirect(direct); + EXPECT_EQ(text_->GetDirect(), direct); +} + +/** + * @tc.name: TextSetAlign_001 + * @tc.desc: Verify SetAlign function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMQ1 + */ +HWTEST_F(TextTest, TextSetAlign_001, TestSize.Level0) +{ + if (text_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + text_->SetAlign(TEXT_ALIGNMENT_LEFT, TEXT_ALIGNMENT_TOP); + EXPECT_EQ(text_->IsNeedRefresh(), true); + EXPECT_EQ(text_->GetHorAlign(), TEXT_ALIGNMENT_LEFT); + EXPECT_EQ(text_->GetVerAlign(), TEXT_ALIGNMENT_TOP); +} + +/** + * @tc.name: TextSetExpand_001 + * @tc.desc: Verify SetExpand function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMQ1 + */ +HWTEST_F(TextTest, TextSetExpand_001, TestSize.Level0) +{ + if (text_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + EXPECT_EQ(text_->IsExpandWidth(), false); + text_->SetExpandWidth(true); + EXPECT_EQ(text_->IsExpandWidth(), true); + + EXPECT_EQ(text_->IsExpandHeight(), false); + text_->SetExpandHeight(true); + EXPECT_EQ(text_->IsExpandHeight(), true); +} +} // namespace OHOS \ No newline at end of file diff --git a/test/unittest/components/ui_abstract_progress_unit_test.cpp b/test/unittest/components/ui_abstract_progress_unit_test.cpp new file mode 100755 index 0000000..d1ef2da --- /dev/null +++ b/test/unittest/components/ui_abstract_progress_unit_test.cpp @@ -0,0 +1,209 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "components/ui_abstract_progress.h" + +#include +#include + +using namespace testing::ext; +namespace OHOS { +namespace { + const int16_t MAX_VALUE = 80; + const int16_t MEDIAN_VALUE = 50; + const int16_t MIN_VALUE = 20; +} +class UIAbsatrctProgressTest : public testing::Test { +public: + UIAbsatrctProgressTest() : abstractProgress_(nullptr) {} + virtual ~UIAbsatrctProgressTest() {} + static void SetUpTestCase(); + static void TearDownTestCase(); + void SetUp(); + void TearDown(); + UIAbstractProgress* abstractProgress_; +}; + +void UIAbsatrctProgressTest::SetUpTestCase() +{ +} + +void UIAbsatrctProgressTest::TearDownTestCase() +{ +} + +void UIAbsatrctProgressTest::SetUp() +{ + if (abstractProgress_ == nullptr) { + abstractProgress_ = new UIAbstractProgress(); + } +} + +void UIAbsatrctProgressTest::TearDown() +{ + if (abstractProgress_ != nullptr) { + delete abstractProgress_; + abstractProgress_ = nullptr; + } +} +/** + * @tc.name: UIAbsatrctProgressGetViewType_001 + * @tc.desc: Verify GetViewType function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMQG + */ +HWTEST_F(UIAbsatrctProgressTest, UIAbsatrctProgressGetViewType_001, TestSize.Level0) +{ + if (abstractProgress_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + EXPECT_EQ(abstractProgress_->GetViewType(), UI_ABSTRACT_PROGRESS); +} + +/** + * @tc.name: UIAbsatrctProgressSetValue_001 + * @tc.desc: Verify SetValue function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMQG + */ +HWTEST_F(UIAbsatrctProgressTest, UIAbsatrctProgressSetValue_001, TestSize.Level0) +{ + if (abstractProgress_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + abstractProgress_->SetRange(MAX_VALUE, MIN_VALUE); + EXPECT_EQ(abstractProgress_->GetRangeMin(), MIN_VALUE); + EXPECT_EQ(abstractProgress_->GetRangeMax(), MAX_VALUE); + + abstractProgress_->SetValue(MEDIAN_VALUE); + EXPECT_EQ(abstractProgress_->GetValue(), MEDIAN_VALUE); + + abstractProgress_->SetValue(MAX_VALUE + 1); + EXPECT_EQ(abstractProgress_->GetValue(), MAX_VALUE); + + abstractProgress_->SetValue(MIN_VALUE - 1); + EXPECT_EQ(abstractProgress_->GetValue(), MIN_VALUE); +} + +/** + * @tc.name: UIAbsatrctProgressSetValue_002 + * @tc.desc: Verify SetValue function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMQG + */ +HWTEST_F(UIAbsatrctProgressTest, UIAbsatrctProgressSetValue_002, TestSize.Level0) +{ + if (abstractProgress_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + abstractProgress_->SetRange(MAX_VALUE, MIN_VALUE); + EXPECT_EQ(abstractProgress_->GetRangeMin(), MIN_VALUE); + EXPECT_EQ(abstractProgress_->GetRangeMax(), MAX_VALUE); + + abstractProgress_->SetRange(MIN_VALUE, MAX_VALUE); + EXPECT_EQ(abstractProgress_->GetRangeMin(), MIN_VALUE); + EXPECT_EQ(abstractProgress_->GetRangeMax(), MAX_VALUE); +} + +/** + * @tc.name: UIAbsatrctProgressSetStep_001 + * @tc.desc: Verify SetStep function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMQG + */ +HWTEST_F(UIAbsatrctProgressTest, UIAbsatrctProgressSetStep_001, TestSize.Level0) +{ + if (abstractProgress_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + abstractProgress_->SetStep(MEDIAN_VALUE); + EXPECT_EQ(abstractProgress_->GetStep(), MEDIAN_VALUE); +} + +/** + * @tc.name: UIAbsatrctProgressSetBackgroundStyle_001 + * @tc.desc: Verify SetBackgroundStyle function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMQG + */ +HWTEST_F(UIAbsatrctProgressTest, UIAbsatrctProgressSetBackgroundStyle_001, TestSize.Level0) +{ + if (abstractProgress_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + Style style; + style.imageOpa_ = OPA_TRANSPARENT; + style.lineOpa_ = OPA_TRANSPARENT; + style.borderRadius_ = 1; + + abstractProgress_->SetBackgroundStyle(style); + EXPECT_EQ(abstractProgress_->GetBackgroundStyle().imageOpa_, OPA_TRANSPARENT); + EXPECT_EQ(abstractProgress_->GetBackgroundStyle().lineOpa_, OPA_TRANSPARENT); + EXPECT_EQ(abstractProgress_->GetBackgroundStyle().borderRadius_, 1); + + abstractProgress_->SetBackgroundStyle(STYLE_BACKGROUND_COLOR, Color::Silver().full); + EXPECT_EQ(abstractProgress_->GetBackgroundStyle(STYLE_BACKGROUND_COLOR), Color::Silver().full); +} + +/** + * @tc.name: UIAbsatrctProgressSetForegroundStyle_001 + * @tc.desc: Verify SetForegroundStyle function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMQG + */ +HWTEST_F(UIAbsatrctProgressTest, UIAbsatrctProgressSetForegroundStyle_001, TestSize.Level0) +{ + if (abstractProgress_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + Style style; + style.imageOpa_ = OPA_TRANSPARENT; + style.lineOpa_ = OPA_TRANSPARENT; + style.borderRadius_ = 1; + + abstractProgress_->SetForegroundStyle(style); + EXPECT_EQ(abstractProgress_->GetForegroundStyle().imageOpa_, OPA_TRANSPARENT); + EXPECT_EQ(abstractProgress_->GetForegroundStyle().lineOpa_, OPA_TRANSPARENT); + EXPECT_EQ(abstractProgress_->GetForegroundStyle().borderRadius_, 1); + + abstractProgress_->SetForegroundStyle(STYLE_BACKGROUND_COLOR, Color::White().full); + EXPECT_EQ(abstractProgress_->GetForegroundStyle(STYLE_BACKGROUND_COLOR), Color::White().full); +} + +/** + * @tc.name: UIAbsatrctProgressSetCapType_001 + * @tc.desc: Verify SetCapType function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMQG + */ +HWTEST_F(UIAbsatrctProgressTest, UIAbsatrctProgressSetCapType_001, TestSize.Level0) +{ + if (abstractProgress_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + EXPECT_EQ(abstractProgress_->GetForegroundStyle(STYLE_LINE_CAP), CapType::CAP_NONE); + EXPECT_EQ(abstractProgress_->GetBackgroundStyle(STYLE_LINE_CAP), CapType::CAP_NONE); + abstractProgress_->SetCapType(CapType::CAP_ROUND); + EXPECT_EQ(abstractProgress_->GetForegroundStyle(STYLE_LINE_CAP), CapType::CAP_ROUND); + EXPECT_EQ(abstractProgress_->GetBackgroundStyle(STYLE_LINE_CAP), CapType::CAP_ROUND); +} +} // namespace OHOS diff --git a/test/unittest/components/ui_analog_clock_unit_test.cpp b/test/unittest/components/ui_analog_clock_unit_test.cpp new file mode 100755 index 0000000..baed925 --- /dev/null +++ b/test/unittest/components/ui_analog_clock_unit_test.cpp @@ -0,0 +1,232 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "components/ui_analog_clock.h" + +#include +#include + +using namespace testing::ext; + +namespace OHOS { +namespace { + const uint8_t CURRENT_HOUR = 14; + const uint8_t CURRENT_MINUTE = 40; + const uint8_t CURRENT_SECOND = 30; + const uint8_t INIT_HOUR = 6; + const uint8_t INIT_MINUTE = 0; + const uint8_t INIT_SECOND = 0; + const uint8_t HALF_DAY_IN_HOUR = 12; +} + +class UIAnalogClockTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + static UIAnalogClock* clock_; +}; + +UIAnalogClock* UIAnalogClockTest::clock_ = nullptr; + +void UIAnalogClockTest::SetUpTestCase() +{ + if (clock_ == nullptr) { + clock_ = new UIAnalogClock(); + } +} + +void UIAnalogClockTest::TearDownTestCase() +{ + if (clock_ != nullptr) { + delete clock_; + clock_ = nullptr; + } +} + +/** + * @tc.name: UIAnalogClockGetViewType_001 + * @tc.desc: Verify GetWidth and GetHeight function. + * @tc.type: FUNC + * @tc.require: AR000DSMQ1 + */ +HWTEST_F(UIAnalogClockTest, UIAnalogClockGetViewType_001, TestSize.Level1) +{ + if (clock_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + EXPECT_EQ(clock_->GetViewType(), UI_ANALOG_CLOCK); +} + +/** + * @tc.name: UIAnalogClockSetPosition_001 + * @tc.desc: Verify GetWidth and GetHeight function. + * @tc.type: FUNC + * @tc.require: AR000DSMQ1 + */ +HWTEST_F(UIAnalogClockTest, UIAnalogClockSetPosition_001, TestSize.Level1) +{ + if (clock_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + clock_->SetPosition(0, 0); + EXPECT_EQ(clock_->GetX(), 0); + EXPECT_EQ(clock_->GetY(), 0); +} + +/** + * @tc.name: UIAnalogClockSetTime24Hour_001 + * @tc.desc: Verify SetTime24Hour function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQA + */ +HWTEST_F(UIAnalogClockTest, UIAnalogClockSetTime24Hour_001, TestSize.Level0) +{ + if (clock_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + clock_->SetTime24Hour(CURRENT_HOUR, CURRENT_MINUTE, CURRENT_SECOND); + EXPECT_EQ(clock_->GetCurrentHour(), CURRENT_HOUR); + EXPECT_EQ(clock_->GetCurrentMinute(), CURRENT_MINUTE); + EXPECT_EQ(clock_->GetCurrentSecond(), CURRENT_SECOND); +} + +/** + * @tc.name: UIAnalogClockSetTime12Hour_001 + * @tc.desc: Verify SetTime12Hour am function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQA + */ +HWTEST_F(UIAnalogClockTest, UIAnalogClockSetTime12Hour_001, TestSize.Level1) +{ + if (clock_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + clock_->SetTime12Hour(CURRENT_HOUR, CURRENT_MINUTE, CURRENT_SECOND, true); + EXPECT_EQ(clock_->GetCurrentHour(), CURRENT_HOUR - HALF_DAY_IN_HOUR); + EXPECT_EQ(clock_->GetCurrentMinute(), CURRENT_MINUTE); + EXPECT_EQ(clock_->GetCurrentSecond(), CURRENT_SECOND); +} + +/** + * @tc.name: UIAnalogClockSetTime12Hour_002 + * @tc.desc: Verify SetTime12Hour pm function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQA + */ +HWTEST_F(UIAnalogClockTest, UIAnalogClockSetTime12Hour_002, TestSize.Level0) +{ + if (clock_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + clock_->SetTime12Hour(CURRENT_HOUR, CURRENT_MINUTE, CURRENT_SECOND, false); + EXPECT_EQ(clock_->GetCurrentHour(), CURRENT_HOUR); + EXPECT_EQ(clock_->GetCurrentMinute(), CURRENT_MINUTE); + EXPECT_EQ(clock_->GetCurrentSecond(), CURRENT_SECOND); +} + +/** + * @tc.name: UIAnalogClockSetHandLine_001 + * @tc.desc: Verify SetHandLine function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQA + */ +HWTEST_F(UIAnalogClockTest, UIAnalogClockSetHandLine_001, TestSize.Level0) +{ + if (clock_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + Point position = { 214, 3 }; // { 214, 3 } : the position of the line endpoint close + Point center = { 14, 223 }; // { 14, 223 }: the rotation center of this clock hand + + clock_->SetHandLine(UIAnalogClock::HandType::SECOND_HAND, + position, center, Color::White(), 1, 223, OPA_OPAQUE); // 223:the height of this line + EXPECT_EQ(clock_->GetHandPosition(UIAnalogClock::HandType::SECOND_HAND).x, position.x); + EXPECT_EQ(clock_->GetHandPosition(UIAnalogClock::HandType::SECOND_HAND).y, position.y); + EXPECT_EQ(clock_->GetHandRotateCenter(UIAnalogClock::HandType::SECOND_HAND).x, center.x); + EXPECT_EQ(clock_->GetHandRotateCenter(UIAnalogClock::HandType::SECOND_HAND).y, center.y); +} + +/** + * @tc.name: UIAnalogClockSetInitTime24Hour_001 + * @tc.desc: Verify SetInitTime24Hour function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQA + */ +HWTEST_F(UIAnalogClockTest, UIAnalogClockSetInitTime24Hour_001, TestSize.Level0) +{ + if (clock_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + const uint8_t hour = 9; + + clock_->SetInitTime24Hour(INIT_HOUR, INIT_MINUTE, INIT_SECOND); + EXPECT_EQ(clock_->GetCurrentHour(), INIT_HOUR); + EXPECT_EQ(clock_->GetCurrentMinute(), INIT_MINUTE); + EXPECT_EQ(clock_->GetCurrentSecond(), INIT_SECOND); + EXPECT_EQ(clock_->GetHandInitAngle(UIAnalogClock::HandType::HOUR_HAND), SEMICIRCLE_IN_DEGREE); + EXPECT_EQ(clock_->GetHandCurrentAngle(UIAnalogClock::HandType::HOUR_HAND), SEMICIRCLE_IN_DEGREE); + + clock_->SetTime24Hour(hour, 0, 0); + EXPECT_EQ(clock_->GetHandInitAngle(UIAnalogClock::HandType::HOUR_HAND), SEMICIRCLE_IN_DEGREE); + EXPECT_EQ(clock_->GetHandCurrentAngle(UIAnalogClock::HandType::HOUR_HAND), THREE_QUARTER_IN_DEGREE); +} + +/** + * @tc.name: UIAnalogClockSetInitTime12Hour_001 + * @tc.desc: Verify SetInitTime12Hour am function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQA + */ +HWTEST_F(UIAnalogClockTest, UIAnalogClockSetInitTime12Hour_001, TestSize.Level0) +{ + if (clock_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + clock_->SetInitTime12Hour(INIT_HOUR, INIT_MINUTE, INIT_SECOND, true); + EXPECT_EQ(clock_->GetCurrentHour(), INIT_HOUR); + EXPECT_EQ(clock_->GetCurrentMinute(), INIT_MINUTE); + EXPECT_EQ(clock_->GetCurrentSecond(), INIT_SECOND); + EXPECT_EQ(clock_->GetHandInitAngle(UIAnalogClock::HandType::HOUR_HAND), SEMICIRCLE_IN_DEGREE); + EXPECT_EQ(clock_->GetHandCurrentAngle(UIAnalogClock::HandType::HOUR_HAND), SEMICIRCLE_IN_DEGREE); +} + +/** + * @tc.name: UIAnalogClockSetInitTime12Hour_002 + * @tc.desc: Verify SetInitTime12Hour pm function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQA + */ +HWTEST_F(UIAnalogClockTest, UIAnalogClockSetInitTime12Hour_002, TestSize.Level0) +{ + if (clock_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + clock_->SetInitTime12Hour(INIT_HOUR, INIT_MINUTE, INIT_SECOND, false); + EXPECT_EQ(clock_->GetCurrentHour(), INIT_HOUR + HALF_DAY_IN_HOUR); + EXPECT_EQ(clock_->GetCurrentMinute(), INIT_MINUTE); + EXPECT_EQ(clock_->GetCurrentSecond(), INIT_SECOND); + EXPECT_EQ(clock_->GetHandInitAngle(UIAnalogClock::HandType::HOUR_HAND), SEMICIRCLE_IN_DEGREE); + EXPECT_EQ(clock_->GetHandCurrentAngle(UIAnalogClock::HandType::HOUR_HAND), SEMICIRCLE_IN_DEGREE); +} +} diff --git a/test/unittest/components/ui_arc_label_unit_test.cpp b/test/unittest/components/ui_arc_label_unit_test.cpp new file mode 100755 index 0000000..457da6d --- /dev/null +++ b/test/unittest/components/ui_arc_label_unit_test.cpp @@ -0,0 +1,217 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "components/ui_arc_label.h" + +#include +#include + +using namespace testing::ext; + +namespace OHOS { +class UIArcLabelTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + static UIArcLabel* arcLabel_; +}; +UIArcLabel* UIArcLabelTest::arcLabel_ = nullptr; + +void UIArcLabelTest::SetUpTestCase(void) +{ + if (arcLabel_ == nullptr) { + arcLabel_ = new UIArcLabel(); + } +} + +void UIArcLabelTest::TearDownTestCase(void) +{ + if (arcLabel_ != nullptr) { + delete arcLabel_; + arcLabel_ = nullptr; + } +} +/** + * @tc.name: UIArcLabelGetViewType_001 + * @tc.desc: Verify GetViewType function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMPM + */ +HWTEST_F(UIArcLabelTest, UIArcLabelGetViewType_001, TestSize.Level0) +{ + if (arcLabel_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + EXPECT_EQ(arcLabel_->GetViewType(), UI_ARC_LABEL); +} + +/** + * @tc.name: UIArcLabelGetWidth_001 + * @tc.desc: Verify GetWidth function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMPM + */ +HWTEST_F(UIArcLabelTest, UIArcLabelGetWidth_001, TestSize.Level0) +{ + if (arcLabel_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + const int16_t width = 100; + arcLabel_->SetWidth(width); + EXPECT_EQ(arcLabel_->GetWidth(), width); +} + +/** + * @tc.name: UIArcLabelGetHeight_001 + * @tc.desc: Verify GetHeight function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMPM + */ +HWTEST_F(UIArcLabelTest, UIArcLabelGetHeight_001, TestSize.Level0) +{ + if (arcLabel_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + const int16_t height = 100; + arcLabel_->SetHeight(height); + EXPECT_EQ(arcLabel_->GetHeight(), height); +} + +/** + * @tc.name: UIArcLabelSetText_001 + * @tc.desc: Verify SetText function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMPM + */ +HWTEST_F(UIArcLabelTest, UIArcLabelSetText_001, TestSize.Level0) +{ + if (arcLabel_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + const char* text = "abc"; + arcLabel_->SetText(text); + EXPECT_EQ(strcmp(arcLabel_->GetText(), text), 0); +} + +/** + * @tc.name: UIArcLabelSetAlign_001 + * @tc.desc: Verify SetAlign function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMPM + */ +HWTEST_F(UIArcLabelTest, UIArcLabelSetAlign_001, TestSize.Level0) +{ + if (arcLabel_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + UITextLanguageAlignment horizontalAlign = TEXT_ALIGNMENT_LEFT; + arcLabel_->SetAlign(horizontalAlign); + EXPECT_EQ(arcLabel_->GetHorAlign(), horizontalAlign); +} + +/** + * @tc.name: UIArcLabelSetArcTextCenter_001 + * @tc.desc: Verify SetArcTextCenter function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMPM + */ +HWTEST_F(UIArcLabelTest, UIArcLabelSetArcTextCenter_001, TestSize.Level0) +{ + if (arcLabel_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + const int16_t posX = 10; + const int16_t posY = 20; + arcLabel_->SetArcTextCenter(posX, posY); + EXPECT_EQ(arcLabel_->GetArcTextCenter().x, posX); + EXPECT_EQ(arcLabel_->GetArcTextCenter().y, posY); +} + +/** + * @tc.name: UIArcLabelSetArcTextRadius_001 + * @tc.desc: Verify SetArcTextRadius function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMPM + */ +HWTEST_F(UIArcLabelTest, UIArcLabelSetArcTextRadius_001, TestSize.Level0) +{ + if (arcLabel_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + const int16_t radius = 1; + arcLabel_->SetArcTextRadius(radius); + EXPECT_EQ(arcLabel_->GetArcTextRadius(), radius); +} + +/** + * @tc.name: UIArcLabelSetArcTextAngle_001 + * @tc.desc: Verify SetArcTextAngle function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMPM + */ +HWTEST_F(UIArcLabelTest, UIArcLabelSetArcTextAngle_001, TestSize.Level0) +{ + if (arcLabel_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + const int16_t endAngle = 10; + arcLabel_->SetArcTextAngle(0, endAngle); + EXPECT_EQ(arcLabel_->GetArcTextStartAngle(), 0); + EXPECT_EQ(arcLabel_->GetArcTextEndAngle(), endAngle); +} + +/** + * @tc.name: UIArcLabelSetArcTextOrientation_001 + * @tc.desc: Verify SetArcTextOrientation function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMPM + */ +HWTEST_F(UIArcLabelTest, UIArcLabelSetArcTextOrientation_001, TestSize.Level0) +{ + if (arcLabel_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + arcLabel_->SetArcTextOrientation(UIArcLabel::TextOrientation::INSIDE); + EXPECT_EQ(arcLabel_->GetArcTextOrientation(), + UIArcLabel::TextOrientation::INSIDE); + arcLabel_->SetArcTextOrientation(UIArcLabel::TextOrientation::OUTSIDE); + EXPECT_EQ(arcLabel_->GetArcTextOrientation(), + UIArcLabel::TextOrientation::OUTSIDE); +} + +/** + * @tc.name: UIArcLabelGetDirect_001 + * @tc.desc: Verify GetDirect function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMPM + */ +HWTEST_F(UIArcLabelTest, UIArcLabelGetDirect_001, TestSize.Level0) +{ + if (arcLabel_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + EXPECT_EQ(arcLabel_->GetDirect(), UITextLanguageDirect(0)); +} +} \ No newline at end of file diff --git a/test/unittest/components/ui_axis_unit_test.cpp b/test/unittest/components/ui_axis_unit_test.cpp new file mode 100755 index 0000000..9c65d2a --- /dev/null +++ b/test/unittest/components/ui_axis_unit_test.cpp @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "components/ui_axis.h" + +#include +#include + +using namespace testing::ext; + +namespace OHOS { +namespace { + const int16_t LARGER_VALUE = 10; + const int16_t SMALLER_VALUE = 2; +} +class UIAxisTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + static UIXAxis* axisX_; + static UIYAxis* axisY_; +}; +UIXAxis* UIAxisTest::axisX_ = nullptr; +UIYAxis* UIAxisTest::axisY_ = nullptr; + +void UIAxisTest::SetUpTestCase(void) +{ + if (axisX_ == nullptr) { + axisX_ = new UIXAxis(); + } + if (axisY_ == nullptr) { + axisY_ = new UIYAxis(); + } +} + +void UIAxisTest::TearDownTestCase(void) +{ + if (axisX_ != nullptr) { + delete axisX_; + axisX_ = nullptr; + } + if (axisY_ != nullptr) { + delete axisY_; + axisY_ = nullptr; + } +} +/** + * @tc.name: UIAxisGetViewType_001 + * @tc.desc: Verify GetViewType function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQ8 + */ +HWTEST_F(UIAxisTest, UIAxisGetViewType_001, TestSize.Level1) +{ + if (axisX_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + EXPECT_EQ(axisX_->GetViewType(), UI_AXIS); +} + +/** + * @tc.name: UIAxisSetLineColor_001 + * @tc.desc: Verify SetLineColor function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQ8 + */ +HWTEST_F(UIAxisTest, UIAxisSetLineColor_001, TestSize.Level0) +{ + if (axisY_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + axisY_->SetLineColor(Color::Gray()); + EXPECT_EQ(axisY_->GetStyle(STYLE_LINE_COLOR), Color::Gray().full); +} + +/** + * @tc.name: UIXAxisSetDataRange_001 + * @tc.desc: Verify SetDataRange function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQ8 + */ +HWTEST_F(UIAxisTest, UIXAxisSetDataRange_001, TestSize.Level0) +{ + if (axisX_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + EXPECT_EQ(axisX_->SetDataRange(SMALLER_VALUE, LARGER_VALUE), true); +} + +/** + * @tc.name: UIXAxisSetDataRange_002 + * @tc.desc: Verify SetDataRange function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQ8 + */ +HWTEST_F(UIAxisTest, UIXAxisSetDataRange_002, TestSize.Level0) +{ + if (axisX_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + EXPECT_EQ(axisX_->SetDataRange(LARGER_VALUE, SMALLER_VALUE), false); +} + +/** + * @tc.name: UIYAxisSetDataRange_001 + * @tc.desc: Verify SetDataRange function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQ8 + */ +HWTEST_F(UIAxisTest, UIYAxisSetDataRange_001, TestSize.Level0) +{ + if (axisY_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + EXPECT_EQ(axisY_->SetDataRange(SMALLER_VALUE, LARGER_VALUE), true); +} + +/** + * @tc.name: UIYAxisSetDataRange_002 + * @tc.desc: Verify SetDataRange function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQ8 + */ +HWTEST_F(UIAxisTest, UIYAxisSetDataRange_002, TestSize.Level0) +{ + if (axisY_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + EXPECT_EQ(axisY_->SetDataRange(LARGER_VALUE, SMALLER_VALUE), false); +} +} // namespace OHOS \ No newline at end of file diff --git a/test/unittest/components/ui_box_progress_unit_test.cpp b/test/unittest/components/ui_box_progress_unit_test.cpp new file mode 100755 index 0000000..4c18e4f --- /dev/null +++ b/test/unittest/components/ui_box_progress_unit_test.cpp @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "components/ui_box_progress.h" + +#include +#include + +using namespace testing::ext; +namespace OHOS { +namespace { + const int16_t MAX = 500; + const int16_t MIN = 100; + const int16_t WIDTH = 100; + const int16_t HEIGHT = 100; +} + +class UIBoxProgressTest : public testing::Test { +public: + UIBoxProgressTest() : boxProgress_(nullptr) {} + virtual ~UIBoxProgressTest() {} + static void SetUpTestCase(); + static void TearDownTestCase(); + void SetUp(); + void TearDown(); + UIBoxProgress* boxProgress_; +}; + +void UIBoxProgressTest::SetUpTestCase() +{ +} + +void UIBoxProgressTest::TearDownTestCase() +{ +} + +void UIBoxProgressTest::SetUp() +{ + if (boxProgress_ == nullptr) { + boxProgress_ = new UIBoxProgress(); + } +} + +void UIBoxProgressTest::TearDown() +{ + if (boxProgress_ != nullptr) { + delete boxProgress_; + boxProgress_ = nullptr; + } +} + +/** + * @tc.name: UIBoxProgressGetViewType_001 + * @tc.desc: Verify GetViewType function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMQG + */ +HWTEST_F(UIBoxProgressTest, UIBoxProgressGetViewType_001, TestSize.Level0) +{ + if (boxProgress_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + EXPECT_EQ(boxProgress_->GetViewType(), UI_BOX_PROGRESS); +} + +/** + * @tc.name: UIBoxProgressSetDirection_001 + * @tc.desc: Verify SetDirection function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMQG + */ +HWTEST_F(UIBoxProgressTest, UIBoxProgressSetDirection_001, TestSize.Level0) +{ + if (boxProgress_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + UIBoxProgress::Direction direction = UIBoxProgress::Direction::DIR_BOTTOM_TO_TOP; + boxProgress_->SetDirection(direction); + EXPECT_EQ(boxProgress_->GetDirection(), direction); + direction = UIBoxProgress::Direction::DIR_LEFT_TO_RIGHT; + boxProgress_->SetDirection(direction); + EXPECT_EQ(boxProgress_->GetDirection(), direction); + direction = UIBoxProgress::Direction::DIR_RIGHT_TO_LEFT; + boxProgress_->SetDirection(direction); + EXPECT_EQ(boxProgress_->GetDirection(), direction); + direction = UIBoxProgress::Direction::DIR_TOP_TO_BOTTOM; + boxProgress_->SetDirection(direction); + EXPECT_EQ(boxProgress_->GetDirection(), direction); +} + +/** + * @tc.name: UIBoxProgressSetValidWidth_001 + * @tc.desc: Verify SetValidWidth function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMQG + */ +HWTEST_F(UIBoxProgressTest, UIBoxProgressSetValidWidth_001, TestSize.Level0) +{ + if (boxProgress_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + const int16_t valueWidth = 10; + boxProgress_->Resize(WIDTH, HEIGHT); + EXPECT_EQ(boxProgress_->GetValidWidth(), WIDTH); + boxProgress_->SetValidWidth(valueWidth); + EXPECT_EQ(boxProgress_->GetValidWidth(), valueWidth); +} + +/** + * @tc.name: UIBoxProgressSetValidHeight_001 + * @tc.desc: Verify SetValidHeight function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMQG + */ +HWTEST_F(UIBoxProgressTest, UIBoxProgressSetValidHeight_001, TestSize.Level0) +{ + if (boxProgress_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + const int16_t valueHeight = 10; + boxProgress_->Resize(WIDTH, HEIGHT); + EXPECT_EQ(boxProgress_->GetValidHeight(), HEIGHT); + boxProgress_->SetValidHeight(valueHeight); + EXPECT_EQ(boxProgress_->GetValidHeight(), valueHeight); +} + +/** + * @tc.name: UIBoxProgressSetValue_001 + * @tc.desc: Verify SetValue function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMQG + */ +HWTEST_F(UIBoxProgressTest, UIBoxProgressSetValue_001, TestSize.Level0) +{ + if (boxProgress_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + boxProgress_->SetRange(MAX, MIN); + + int16_t value = MIN - 1; + boxProgress_->SetValue(value); + EXPECT_EQ(boxProgress_->GetValue(), MIN); + + value = MAX + 1; + boxProgress_->SetValue(value); + EXPECT_EQ(boxProgress_->GetValue(), MAX); +} + +/** + * @tc.name: UIBoxProgressSetWidth_001 + * @tc.desc: Verify SetWidth function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMQG + */ +HWTEST_F(UIBoxProgressTest, UIBoxProgressSetWidth_001, TestSize.Level0) +{ + if (boxProgress_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + + boxProgress_->SetWidth(WIDTH); + EXPECT_EQ(boxProgress_->GetWidth(), WIDTH); + EXPECT_EQ(boxProgress_->GetValidWidth(), WIDTH); +} + +/** + * @tc.name: UIBoxProgressSetHeight_001 + * @tc.desc: Verify SetHeight function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMQG + */ +HWTEST_F(UIBoxProgressTest, UIBoxProgressSetHeight_001, TestSize.Level0) +{ + if (boxProgress_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + + boxProgress_->SetHeight(HEIGHT); + EXPECT_EQ(boxProgress_->GetHeight(), HEIGHT); + EXPECT_EQ(boxProgress_->GetValidHeight(), HEIGHT); +} +} // namespace OHOS \ No newline at end of file diff --git a/test/unittest/components/ui_button_unit_test.cpp b/test/unittest/components/ui_button_unit_test.cpp new file mode 100755 index 0000000..58249c3 --- /dev/null +++ b/test/unittest/components/ui_button_unit_test.cpp @@ -0,0 +1,340 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "components/ui_button.h" + +#include +#include +#include "test_resource_config.h" + +using namespace testing::ext; + +namespace OHOS { +namespace { + const Point INIT_POS = { 10, 12 }; +} +class UIButtonTest : public testing::Test { +public: + UIButtonTest() : button_(nullptr) {} + virtual ~UIButtonTest() {} + static void SetUpTestCase() {} + static void TearDownTestCase() {} + void TearDown(); + void SetUp(); + UIButton* button_; +}; + +void UIButtonTest::SetUp() +{ + if (button_ == nullptr) { + button_ = new UIButton(); + } +} + +void UIButtonTest::TearDown() +{ + if (button_ != nullptr) { + delete button_; + button_ = nullptr; + } +} + +/** + * @tc.name: UIButtonGetViewType_001 + * @tc.desc: Verify GetViewType function. + * @tc.type: FUNC + * @tc.require: SR000DRSH1 + */ +HWTEST_F(UIButtonTest, UIButtonGetViewType_001, TestSize.Level1) +{ + if (button_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + EXPECT_EQ(button_->GetViewType(), UI_BUTTON); +} + +/** + * @tc.name: UIButtonSetImageSrc_001 + * @tc.desc: Verify SetImageSrc function. + * @tc.type: FUNC + * @tc.require: SR000DRSH1 + */ +HWTEST_F(UIButtonTest, UIButtonSetImageSrc_001, TestSize.Level0) +{ + if (button_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + button_->SetImageSrc(BLUE_RGB888_IMAGE_PATH, BLUE_RGB565_IMAGE_PATH); + ASSERT_TRUE(button_->GetCurImageSrc()); + + if (button_->GetCurImageSrc()->GetPath() == nullptr) { + EXPECT_NE(0, 0); + return; + } + EXPECT_EQ(strcmp(button_->GetCurImageSrc()->GetPath(), BLUE_RGB888_IMAGE_PATH), 0); + PressEvent event(INIT_POS); + button_->OnPressEvent(event); + if (button_->GetCurImageSrc()->GetPath() == nullptr) { + EXPECT_NE(0, 0); + return; + } + EXPECT_EQ(strcmp(button_->GetCurImageSrc()->GetPath(), BLUE_RGB565_IMAGE_PATH), 0); +} + +/** + * @tc.name:UIButtonSetImagePosition_001 + * @tc.desc: Verify SetImagePosition function. + * @tc.type: FUNC + * @tc.require: SR000DRSH1 + */ +HWTEST_F(UIButtonTest, UIButtonSetImagePosition_001, TestSize.Level0) +{ + if (button_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + button_->SetImagePosition(INIT_POS.x, INIT_POS.y); + EXPECT_EQ(button_->GetImageX(), INIT_POS.x); + EXPECT_EQ(button_->GetImageY(), INIT_POS.y); +} + +/** + * @tc.name: UIButtonSetStyle_001 + * @tc.desc: Verify SetStyle function. + * @tc.type: FUNC + * @tc.require: SR000DRSH1 + */ +HWTEST_F(UIButtonTest, UIButtonSetStyle_001, TestSize.Level0) +{ + if (button_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + button_->SetStateForStyle(UIButton::ButtonState::PRESSED); + button_->SetStyle(STYLE_BACKGROUND_COLOR, Color::Blue().full); + EXPECT_EQ(button_->GetStyle(STYLE_BACKGROUND_COLOR), Color::Blue().full); + EXPECT_EQ(button_->GetStyleForState(STYLE_BACKGROUND_COLOR, UIButton::ButtonState::PRESSED), Color::Blue().full); +} + +/** + * @tc.name: UIButtonSetStyle_002 + * @tc.desc: Verify SetStyle function. + * @tc.type: FUNC + * @tc.require: SR000DRSH1 + */ +HWTEST_F(UIButtonTest, UIButtonSetStyle_002, TestSize.Level0) +{ + if (button_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + button_->SetStateForStyle(UIButton::ButtonState::INACTIVE); + button_->SetStyle(STYLE_BACKGROUND_COLOR, Color::Red().full); + EXPECT_EQ(button_->GetStyle(STYLE_BACKGROUND_COLOR), Color::Red().full); + EXPECT_EQ(button_->GetStyleForState(STYLE_BACKGROUND_COLOR, UIButton::ButtonState::INACTIVE), Color::Red().full); +} + +/** + * @tc.name: UIButtonSetStyle_003 + * @tc.desc: Verify SetStyle function. + * @tc.type: FUNC + * @tc.require: SR000DRSH1 + */ +HWTEST_F(UIButtonTest, UIButtonSetStyle_003, TestSize.Level0) +{ + if (button_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + button_->SetStateForStyle(UIButton::ButtonState::RELEASED); + button_->SetStyle(STYLE_BACKGROUND_COLOR, Color::Green().full); + EXPECT_EQ(button_->GetStyle(STYLE_BACKGROUND_COLOR), Color::Green().full); + EXPECT_EQ(button_->GetStyleForState(STYLE_BACKGROUND_COLOR, UIButton::ButtonState::RELEASED), Color::Green().full); +} + +/** + * @tc.name: UIButtonIsTouchable_001 + * @tc.desc: Verify IsTouchable function, equal. + * @tc.type: FUNC + * @tc.require: SR000DRSH1 + */ +HWTEST_F(UIButtonTest, UIButtonIsTouchable_001, TestSize.Level0) +{ + if (button_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + button_->Enable(); + EXPECT_EQ(button_->IsTouchable(), true); + button_->Disable(); + EXPECT_EQ(button_->IsTouchable(), false); +} + +/** + * @tc.name: UIButtonSetSize_001 + * @tc.desc: Verify SetSize function, equal. + * @tc.type: FUNC + * @tc.require: SR000DRSH1 + */ +HWTEST_F(UIButtonTest, UIButtonSetSize_001, TestSize.Level0) +{ + if (button_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + const int16_t releasedWidth = 5; + const int16_t releasedHeight = 25; + const int16_t paddingdLeft = 10; + const int16_t paddingdTop = 20; + const int16_t borderWidth = 2; + const int16_t posX = 50; + const int16_t posY = 100; + button_->SetStyleForState(STYLE_PADDING_LEFT, paddingdLeft, UIButton::ButtonState::RELEASED); + button_->SetStyleForState(STYLE_BORDER_WIDTH, borderWidth, UIButton::ButtonState::RELEASED); + button_->SetStyleForState(STYLE_PADDING_TOP, paddingdTop, UIButton::ButtonState::RELEASED); + + ReleaseEvent releaseEvent(INIT_POS); + button_->OnReleaseEvent(releaseEvent); +button_->SetPosition(posX, posY); + button_->SetWidth(releasedWidth); + button_->SetHeight(releasedHeight); + EXPECT_EQ(button_->GetWidth(), releasedWidth); + EXPECT_EQ(button_->GetHeight(), releasedHeight); + EXPECT_EQ(button_->GetContentRect().GetWidth(), releasedWidth); + EXPECT_EQ(button_->GetContentRect().GetHeight(), releasedHeight); + EXPECT_EQ(button_->GetContentRect().GetX(), posX + paddingdLeft + borderWidth); + EXPECT_EQ(button_->GetContentRect().GetY(), posY + paddingdTop + borderWidth); +} + +/** + * @tc.name: UIButtonSetSize_002 + * @tc.desc: Verify SetSize function, equal. + * @tc.type: FUNC + * @tc.require: SR000DRSH1 + */ +HWTEST_F(UIButtonTest, UIButtonSetSize_002, TestSize.Level0) +{ + if (button_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + const int16_t pressWidth = 5; + const int16_t pressHeight = 25; + const int16_t paddingdLeft = 10; + const int16_t paddingdTop = 20; + const int16_t borderWidth = 2; + const int16_t posX = 50; + const int16_t posY = 100; + button_->SetStyleForState(STYLE_PADDING_LEFT, paddingdLeft, UIButton::ButtonState::PRESSED); + button_->SetStyleForState(STYLE_BORDER_WIDTH, borderWidth, UIButton::ButtonState::PRESSED); + button_->SetStyleForState(STYLE_PADDING_TOP, paddingdTop, UIButton::ButtonState::PRESSED); + + PressEvent pressEvent(INIT_POS); + button_->OnPressEvent(pressEvent); + button_->SetPosition(posX, posY); + button_->SetWidth(pressWidth); + button_->SetHeight(pressHeight); + EXPECT_EQ(button_->GetWidth(), pressWidth); + EXPECT_EQ(button_->GetHeight(), pressHeight); + EXPECT_EQ(button_->GetContentRect().GetWidth(), pressWidth); + EXPECT_EQ(button_->GetContentRect().GetHeight(), pressHeight); + EXPECT_EQ(button_->GetContentRect().GetX(), posX + paddingdLeft + borderWidth); + EXPECT_EQ(button_->GetContentRect().GetY(), posY + paddingdTop + borderWidth); +} + +/** + * @tc.name: UIButtonSetSize_003 + * @tc.desc: Verify SetSize function, equal. + * @tc.type: FUNC + * @tc.require: SR000DRSH1 + */ +HWTEST_F(UIButtonTest, UIButtonSetSize_003, TestSize.Level0) +{ + if (button_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + const int16_t inactiveWidth = 5; + const int16_t inactiveHeight = 25; + const int16_t paddingdLeft = 10; + const int16_t paddingdTop = 20; + const int16_t borderWidth = 2; + const int16_t posX = 50; + const int16_t posY = 100; + button_->SetStyleForState(STYLE_PADDING_LEFT, paddingdLeft, UIButton::ButtonState::INACTIVE); + button_->SetStyleForState(STYLE_BORDER_WIDTH, borderWidth, UIButton::ButtonState::INACTIVE); + button_->SetStyleForState(STYLE_PADDING_TOP, paddingdTop, UIButton::ButtonState::INACTIVE); + + button_->Disable(); + button_->SetPosition(posX, posY); + button_->SetWidth(inactiveWidth); + button_->SetHeight(inactiveHeight); + EXPECT_EQ(button_->GetWidth(), inactiveWidth); + EXPECT_EQ(button_->GetHeight(), inactiveHeight); + EXPECT_EQ(button_->GetContentRect().GetWidth(), inactiveWidth); + EXPECT_EQ(button_->GetContentRect().GetHeight(), inactiveHeight); + EXPECT_EQ(button_->GetContentRect().GetX(), posX + paddingdLeft + borderWidth); + EXPECT_EQ(button_->GetContentRect().GetY(), posY + paddingdTop + borderWidth); +} + +/** + * @tc.name: UIButtonSetStyleForState_001 + * @tc.desc: Verify SetStyleForState function, equal. + * @tc.type: FUNC + * @tc.require: SR000DRSH1 + */ +HWTEST_F(UIButtonTest, UIButtonSetStyleForState_001, TestSize.Level0) +{ + if (button_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + button_->SetStyleForState(STYLE_BACKGROUND_COLOR, Color::Red().full, UIButton::ButtonState::RELEASED); + EXPECT_EQ(button_->GetStyleForState(STYLE_BACKGROUND_COLOR, UIButton::ButtonState::RELEASED), + Color::Red().full); + button_->SetStyleForState(STYLE_BACKGROUND_COLOR, Color::Green().full, UIButton::ButtonState::PRESSED); + EXPECT_EQ(button_->GetStyleForState(STYLE_BACKGROUND_COLOR, UIButton::ButtonState::PRESSED), + Color::Green().full); + button_->SetStyleForState(STYLE_BACKGROUND_COLOR, Color::Yellow().full, UIButton::ButtonState::INACTIVE); + EXPECT_EQ(button_->GetStyleForState(STYLE_BACKGROUND_COLOR, UIButton::ButtonState::INACTIVE), + Color::Yellow().full); +} + +/** + * @tc.name: UIButtonSetStateForStyle_001 + * @tc.desc: Verify SetStyle function. + * @tc.type: FUNC + * @tc.require: SR000DRSH1 + */ +HWTEST_F(UIButtonTest, UIButtonSetStateForStyle_001, TestSize.Level0) +{ + if (button_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + button_->SetStyleForState(STYLE_BACKGROUND_COLOR, Color::Red().full, UIButton::ButtonState::RELEASED); + EXPECT_EQ(button_->GetStyleForState(STYLE_BACKGROUND_COLOR, UIButton::ButtonState::RELEASED), + Color::Red().full); + button_->SetStyleForState(STYLE_BACKGROUND_COLOR, Color::Green().full, UIButton::ButtonState::PRESSED); + EXPECT_EQ(button_->GetStyleForState(STYLE_BACKGROUND_COLOR, UIButton::ButtonState::PRESSED), + Color::Green().full); + button_->SetStateForStyle(UIButton::ButtonState::RELEASED); + EXPECT_EQ(button_->GetStyle(STYLE_BACKGROUND_COLOR), Color::Red().full); + button_->SetStateForStyle(UIButton::ButtonState::PRESSED); + EXPECT_EQ(button_->GetStyle(STYLE_BACKGROUND_COLOR), Color::Green().full); +} +} // namespace OHOS \ No newline at end of file diff --git a/test/unittest/components/ui_canvas_unit_test.cpp b/test/unittest/components/ui_canvas_unit_test.cpp new file mode 100755 index 0000000..7ee1aeb --- /dev/null +++ b/test/unittest/components/ui_canvas_unit_test.cpp @@ -0,0 +1,567 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "components/root_view.h" +#include "components/ui_canvas.h" +#include "components/ui_view_group.h" + +#include +#include + +using namespace testing::ext; +namespace OHOS { +namespace { + const int16_t POS_X = 50; + const int16_t POS_Y = 100; + const uint16_t WIDTH = 100; + const uint16_t HEIGHT = 100; + const int16_t START1_X = 10; + const int16_t START1_Y = 20; + const int16_t LINE1_X = 40; + const int16_t LINE1_Y = 100; + const int16_t LINE2_X = 50; + const int16_t LINE2_Y = 120; + const int16_t CENTER_X = 150; + const int16_t CENTER_Y = 150; + const int16_t RADIUS = 50; + const int16_t START_ANGLE = 30; + const int16_t END_ANGLE = 250; + const int16_t RECT_X = 250; + const int16_t RECT_Y = 50; + const int16_t RECT_WIDTH = 100; + const int16_t RECT_HEIGHT = 50; +} + +class TestUICanvas : public UICanvas { +public: + TestUICanvas() {} + virtual ~TestUICanvas() {} + + const UICanvasPath* GetPath() const + { + return path_; + } + + Point GetStartPos() const + { + if (path_ != nullptr) { + return path_->startPos_; + } else { + return { COORD_MIN, COORD_MIN }; + } + } + + Point GetEndPos() const + { + if (path_ != nullptr) { + return path_->points_.Tail()->data_; + } else { + return { COORD_MIN, COORD_MIN }; + } + } +}; + +class UICanvasTest : public testing::Test { +public: + static void SetUpTestCase() {} + static void TearDownTestCase() {} + void SetUp(); + void TearDown(); + static Paint* paint_; + static TestUICanvas* canvas_; +}; + +Paint* UICanvasTest::paint_ = nullptr; +TestUICanvas* UICanvasTest::canvas_ = nullptr; + +void UICanvasTest::SetUp() +{ + if (paint_ == nullptr) { + paint_ = new Paint(); + } + if (canvas_ == nullptr) { + canvas_ = new TestUICanvas(); + } +} + +void UICanvasTest::TearDown() +{ + if (paint_ != nullptr) { + delete paint_; + paint_ = nullptr; + } + if (canvas_ != nullptr) { + delete canvas_; + canvas_ = nullptr; + } +} +/** + * @tc.name: UICanvasSetPaintStyle_001 + * @tc.desc: Verify SetStyle function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMPV + */ +HWTEST_F(UICanvasTest, UICanvasSetPaintStyle_001, TestSize.Level1) +{ + if (paint_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + Paint::PaintStyle paintStyle = Paint::PaintStyle::FILL_STYLE; + + paint_->SetStyle(Paint::PaintStyle::FILL_STYLE); + EXPECT_EQ(paint_->GetStyle(), paintStyle); +} + +/** + * @tc.name: UICanvasSetStrokeWidth_001 + * @tc.desc: Verify SetStrokeWidth function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMPV + */ +HWTEST_F(UICanvasTest, UICanvasSetStrokeWidth_001, TestSize.Level0) +{ + if (paint_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + paint_->SetStrokeWidth(WIDTH); + EXPECT_EQ(paint_->GetStrokeWidth(), WIDTH); +} + +/** + * @tc.name: UICanvasSetStrokeColor_001 + * @tc.desc: Verify SetStrokeColor function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMPV + */ +HWTEST_F(UICanvasTest, UICanvasSetStrokeColor_001, TestSize.Level0) +{ + if (paint_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + ColorType color = Color::Red(); + + paint_->SetStrokeColor(color); + EXPECT_EQ(paint_->GetStrokeColor().full, color.full); +} + +/** + * @tc.name: UICanvasSetFillColor_001 + * @tc.desc: Verify SetFillColor function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMPV + */ +HWTEST_F(UICanvasTest, UICanvasSetFillColor_001, TestSize.Level0) +{ + if (paint_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + ColorType color = Color::Red(); + + paint_->SetFillColor(color); + EXPECT_EQ(paint_->GetFillColor().full, color.full); +} + +/** + * @tc.name: UICanvasSetOpacity_001 + * @tc.desc: Verify SetOpacity function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMPV + */ +HWTEST_F(UICanvasTest, UICanvasSetOpacity_001, TestSize.Level0) +{ + if (paint_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + paint_->SetOpacity(OPA_OPAQUE); + EXPECT_EQ(paint_->GetOpacity(), OPA_OPAQUE); +} + +/** + * @tc.name: UICanvasSetStartPosition_001 + * @tc.desc: Verify SetStartPosition function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMPV + */ +HWTEST_F(UICanvasTest, UICanvasSetStartPosition_001, TestSize.Level0) +{ + if (canvas_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + canvas_->SetStartPosition({ POS_X, POS_Y }); + EXPECT_EQ(canvas_->GetStartPosition().x, POS_X); + EXPECT_EQ(canvas_->GetStartPosition().y, POS_Y); +} + +/** + * @tc.name: UICanvasDrawLine_001 + * @tc.desc: Verify DrawLine function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMPV + */ +HWTEST_F(UICanvasTest, UICanvasDrawLine_001, TestSize.Level0) +{ + if (canvas_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + RootView* rootView = RootView::GetInstance(); + UIViewGroup* viewGroup = static_cast(rootView); + Paint paint; + Point startPoint = { 0, 50 }; + Point endPoint = { 50, 100 }; + + viewGroup->SetPosition(0, 0); + viewGroup->SetWidth(WIDTH); + viewGroup->SetHeight(HEIGHT); + canvas_->DrawLine(startPoint, endPoint, paint); + viewGroup->Add(canvas_); + EXPECT_EQ(canvas_->GetStartPosition().x, endPoint.x); + EXPECT_EQ(canvas_->GetStartPosition().y, endPoint.y); + viewGroup->Remove(canvas_); +} + +/** + * @tc.name: UICanvasDrawCurve_001 + * @tc.desc: Verify DrawCurve function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMPV + */ +HWTEST_F(UICanvasTest, UICanvasDrawCurve_001, TestSize.Level0) +{ + if (canvas_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + RootView* rootView = RootView::GetInstance(); + UIViewGroup* viewGroup = static_cast(rootView); + Paint paint; + Point startPoint = { 100, 100 }; + Point endPoint = { 150, 200 }; + Point control1Point = { 150, 100 }; + Point control2Point = { 150, 100 }; + + viewGroup->SetPosition(0, 0); + viewGroup->SetWidth(WIDTH); + viewGroup->SetHeight(HEIGHT); + canvas_->DrawCurve(startPoint, control1Point, control2Point, endPoint, paint); + viewGroup->Add(canvas_); + EXPECT_EQ(canvas_->GetStartPosition().x, endPoint.x); + EXPECT_EQ(canvas_->GetStartPosition().y, endPoint.y); + viewGroup->Remove(canvas_); +} + +/** + * @tc.name: UICanvasBeginPath_001 + * @tc.desc: Verify BeginPath function, equal. + * @tc.type: FUNC + * @tc.require: AR000EVN8V + */ +HWTEST_F(UICanvasTest, UICanvasBeginPath_001, TestSize.Level0) +{ + if (canvas_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + + EXPECT_EQ(canvas_->GetPath(), nullptr); + canvas_->BeginPath(); + EXPECT_NE(canvas_->GetPath(), nullptr); +} + +/** + * @tc.name: UICanvasMoveTo_001 + * @tc.desc: Verify MoveTo function, equal. + * @tc.type: FUNC + * @tc.require: AR000EVN8V + */ +HWTEST_F(UICanvasTest, UICanvasMoveTo_001, TestSize.Level0) +{ + if (canvas_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + canvas_->BeginPath(); + canvas_->MoveTo({ START1_X, START1_Y }); + Point start = canvas_->GetStartPos(); + EXPECT_EQ(start.x, START1_X); + EXPECT_EQ(start.y, START1_Y); + + Point end = canvas_->GetEndPos(); + EXPECT_EQ(end.x, START1_X); + EXPECT_EQ(end.y, START1_Y); +} + +/** + * @tc.name: UICanvasLineTo_001 + * @tc.desc: Verify LineTo function, equal. + * @tc.type: FUNC + * @tc.require: AR000EVN8V + */ +HWTEST_F(UICanvasTest, UICanvasLineTo_001, TestSize.Level0) +{ + if (canvas_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + canvas_->BeginPath(); + canvas_->LineTo({ LINE1_X, LINE1_Y }); + Point start = canvas_->GetStartPos(); + EXPECT_EQ(start.x, LINE1_X); + EXPECT_EQ(start.y, LINE1_Y); + + Point end = canvas_->GetEndPos(); + EXPECT_EQ(end.x, LINE1_X); + EXPECT_EQ(end.y, LINE1_Y); +} + +/** + * @tc.name: UICanvasLineTo_002 + * @tc.desc: Verify LineTo function, equal. + * @tc.type: FUNC + * @tc.require: AR000EVN8V + */ +HWTEST_F(UICanvasTest, UICanvasLineTo_002, TestSize.Level0) +{ + if (canvas_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + canvas_->BeginPath(); + canvas_->MoveTo({ START1_X, START1_Y }); + canvas_->LineTo({ LINE1_X, LINE1_Y }); + Point start = canvas_->GetStartPos(); + EXPECT_EQ(start.x, START1_X); + EXPECT_EQ(start.y, START1_Y); + + Point end = canvas_->GetEndPos(); + EXPECT_EQ(end.x, LINE1_X); + EXPECT_EQ(end.y, LINE1_Y); + + canvas_->LineTo({ LINE2_X, LINE2_Y }); + end = canvas_->GetEndPos(); + EXPECT_EQ(end.x, LINE2_X); + EXPECT_EQ(end.y, LINE2_Y); +} + +/** + * @tc.name: UICanvasArcTo_001 + * @tc.desc: Verify ArcTo function, equal. + * @tc.type: FUNC + * @tc.require: AR000EVN8V + */ +HWTEST_F(UICanvasTest, UICanvasArcTo_001, TestSize.Level0) +{ + if (canvas_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + canvas_->BeginPath(); + canvas_->ArcTo({ CENTER_X, CENTER_Y }, RADIUS, START_ANGLE, END_ANGLE); + + float sinma = RADIUS * Sin(START_ANGLE); + float cosma = RADIUS * Sin(QUARTER_IN_DEGREE - START_ANGLE); + Point start = canvas_->GetStartPos(); + EXPECT_EQ(start.x, MATH_ROUND(CENTER_X + sinma)); + EXPECT_EQ(start.y, MATH_ROUND(CENTER_Y - cosma)); + + sinma = RADIUS * Sin(END_ANGLE); + cosma = RADIUS * Sin(QUARTER_IN_DEGREE - END_ANGLE); + Point end = canvas_->GetEndPos(); + EXPECT_EQ(end.x, MATH_ROUND(CENTER_X + sinma)); + EXPECT_EQ(end.y, MATH_ROUND(CENTER_Y - cosma)); +} + +/** + * @tc.name: UICanvasArcTo_002 + * @tc.desc: Verify ArcTo function, equal. + * @tc.type: FUNC + * @tc.require: AR000EVN8V + */ +HWTEST_F(UICanvasTest, UICanvasArcTo_002, TestSize.Level0) +{ + if (canvas_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + canvas_->BeginPath(); + canvas_->ArcTo({ CENTER_X, CENTER_Y }, RADIUS, START_ANGLE, START_ANGLE + CIRCLE_IN_DEGREE + QUARTER_IN_DEGREE); + + float sinma = RADIUS * Sin(START_ANGLE); + float cosma = RADIUS * Sin(QUARTER_IN_DEGREE - START_ANGLE); + Point start = canvas_->GetStartPos(); + EXPECT_EQ(start.x, MATH_ROUND(CENTER_X + sinma)); + EXPECT_EQ(start.y, MATH_ROUND(CENTER_Y - cosma)); + + sinma = RADIUS * Sin(END_ANGLE); + cosma = RADIUS * Sin(QUARTER_IN_DEGREE - END_ANGLE); + Point end = canvas_->GetEndPos(); + EXPECT_EQ(end.x, start.x); + EXPECT_EQ(end.y, start.y); +} + +/** + * @tc.name: UICanvasArcTo_003 + * @tc.desc: Verify ArcTo function, equal. + * @tc.type: FUNC + * @tc.require: AR000EVN8V + */ +HWTEST_F(UICanvasTest, UICanvasArcTo_003, TestSize.Level0) +{ + if (canvas_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + canvas_->BeginPath(); + canvas_->MoveTo({ START1_X, START1_Y }); + canvas_->ArcTo({ CENTER_X, CENTER_Y }, RADIUS, START_ANGLE, END_ANGLE); + + Point start = canvas_->GetStartPos(); + EXPECT_EQ(start.x, START1_X); + EXPECT_EQ(start.y, START1_Y); + + float sinma = RADIUS * Sin(END_ANGLE); + float cosma = RADIUS * Sin(QUARTER_IN_DEGREE - END_ANGLE); + Point end = canvas_->GetEndPos(); + EXPECT_EQ(end.x, MATH_ROUND(CENTER_X + sinma)); + EXPECT_EQ(end.y, MATH_ROUND(CENTER_Y - cosma)); +} + +/** + * @tc.name: UICanvasAddRect_001 + * @tc.desc: Verify AddRect function, equal. + * @tc.type: FUNC + * @tc.require: AR000EVN8V + */ +HWTEST_F(UICanvasTest, UICanvasAddRect_001, TestSize.Level0) +{ + if (canvas_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + canvas_->BeginPath(); + canvas_->AddRect({ RECT_X, RECT_Y }, RECT_HEIGHT, RECT_WIDTH); + + Point start = canvas_->GetStartPos(); + EXPECT_EQ(start.x, RECT_X); + EXPECT_EQ(start.y, RECT_Y); + + Point end = canvas_->GetEndPos(); + EXPECT_EQ(end.x, RECT_X); + EXPECT_EQ(end.y, RECT_Y); +} + +/** + * @tc.name: UICanvasAddRect_002 + * @tc.desc: Verify AddRect function, equal. + * @tc.type: FUNC + * @tc.require: AR000EVN8V + */ +HWTEST_F(UICanvasTest, UICanvasAddRect_002, TestSize.Level0) +{ + if (canvas_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + canvas_->BeginPath(); + canvas_->MoveTo({ START1_X, START1_Y }); + canvas_->LineTo({ LINE1_X, LINE1_Y }); + canvas_->AddRect({ RECT_X, RECT_Y }, RECT_HEIGHT, RECT_WIDTH); + + Point start = canvas_->GetStartPos(); + EXPECT_EQ(start.x, RECT_X); + EXPECT_EQ(start.y, RECT_Y); + + Point end = canvas_->GetEndPos(); + EXPECT_EQ(end.x, RECT_X); + EXPECT_EQ(end.y, RECT_Y); +} + +/** + * @tc.name: UICanvasClosePath_001 + * @tc.desc: Verify ClosePath function, equal. + * @tc.type: FUNC + * @tc.require: AR000EVN8V + */ +HWTEST_F(UICanvasTest, UICanvasClosePath_001, TestSize.Level0) +{ + if (canvas_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + canvas_->BeginPath(); + canvas_->MoveTo({ START1_X, START1_Y }); + canvas_->LineTo({ LINE1_X, LINE1_Y }); + canvas_->ClosePath(); + + Point start = canvas_->GetStartPos(); + EXPECT_EQ(start.x, START1_X); + EXPECT_EQ(start.y, START1_Y); + + Point end = canvas_->GetEndPos(); + EXPECT_EQ(end.x, START1_X); + EXPECT_EQ(end.y, START1_Y); +} + +/** + * @tc.name: UICanvasClosePath_002 + * @tc.desc: Verify ClosePath function, equal. + * @tc.type: FUNC + * @tc.require: SR000EVN8R + */ +HWTEST_F(UICanvasTest, UICanvasClosePath_002, TestSize.Level0) +{ + if (canvas_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + + if (paint_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + + canvas_->BeginPath(); + canvas_->MoveTo({ START1_X, START1_Y }); + canvas_->LineTo({ LINE1_X, LINE1_Y }); + canvas_->ClosePath(); + canvas_->DrawPath(*paint_); + + Point start = canvas_->GetStartPos(); + EXPECT_EQ(start.x, START1_X); + EXPECT_EQ(start.y, START1_Y); + + Point end = canvas_->GetEndPos(); + EXPECT_EQ(end.x, START1_X); + EXPECT_EQ(end.y, START1_Y); + + canvas_->BeginPath(); + canvas_->LineTo({ LINE2_X, LINE2_Y }); + canvas_->ClosePath(); + + start = canvas_->GetStartPos(); + EXPECT_EQ(start.x, LINE2_X); + EXPECT_EQ(start.y, LINE2_Y); + + end = canvas_->GetEndPos(); + EXPECT_EQ(end.x, LINE2_X); + EXPECT_EQ(end.y, LINE2_Y); +} +} // namespace OHOS \ No newline at end of file diff --git a/test/unittest/components/ui_chart_unit_test.cpp b/test/unittest/components/ui_chart_unit_test.cpp new file mode 100755 index 0000000..98a89b1 --- /dev/null +++ b/test/unittest/components/ui_chart_unit_test.cpp @@ -0,0 +1,491 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "components/ui_chart.h" +#include +#include + +using namespace testing::ext; + +namespace OHOS { +namespace { + const uint16_t ARRAY_SIZE = 5; + const uint16_t RADIUS = 5; + const uint16_t WIDTH = 10; + const Point POINT_ARRAY[ARRAY_SIZE] = { {0, 2478}, {1, 2600}, {2, 3000}, {3, 3200}, {4, 3500} }; +} + +class UIChartDataSerialTest : public testing::Test { +public: + UIChartDataSerialTest() : chartDataSerial_(nullptr), chart_(nullptr) {} + ~UIChartDataSerialTest() {} + static void SetUpTestCase(void) {} + static void TearDownTestCase(void) {} + void SetUp(void); + void TearDowm(void); + UIChartDataSerial* chartDataSerial_; + UIChartPillar* chart_; +}; + +void UIChartDataSerialTest::SetUp(void) +{ + if (chartDataSerial_ == nullptr) { + chartDataSerial_ = new UIChartDataSerial(); + chartDataSerial_->SetMaxDataCount(ARRAY_SIZE); + } + if (chart_ == nullptr) { + chart_ = new UIChartPillar(); + } +} + +void UIChartDataSerialTest::TearDowm(void) +{ + if (chartDataSerial_ != nullptr) { + delete chartDataSerial_; + chartDataSerial_ = nullptr; + } + if (chart_ != nullptr) { + delete chart_; + chart_ = nullptr; + } +} + +void InitPointStyle(UIChartDataSerial::PointStyle& pointStyle) +{ + pointStyle.fillColor = Color::Red(); + pointStyle.radius = RADIUS; + pointStyle.strokeColor = Color::White(); + pointStyle.strokeWidth = WIDTH; +} + +/** + * @tc.name: UIChartDataSerialSetMaxDataCount_001 + * @tc.desc: Verify SetMaxDataCount function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQ8 + */ +HWTEST_F(UIChartDataSerialTest, UIChartDataSerialSetMaxDataCount_001, TestSize.Level0) +{ + if (chartDataSerial_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + EXPECT_EQ(chartDataSerial_->SetMaxDataCount(1), true); +} + +/** + * @tc.name: UIChartDataSerialAddPoints_001 + * @tc.desc: Verify AddPoints function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQ8 + */ +HWTEST_F(UIChartDataSerialTest, UIChartDataSerialAddPoints_001, TestSize.Level0) +{ + if (chartDataSerial_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + bool ret = chartDataSerial_->AddPoints(nullptr, 0); + EXPECT_EQ(ret, false); + ret = chartDataSerial_->AddPoints(POINT_ARRAY, ARRAY_SIZE); + EXPECT_EQ(ret, true); + ret = chartDataSerial_->AddPoints(POINT_ARRAY, 0); + EXPECT_EQ(ret, false); +} + +/** + * @tc.name: UIChartDataSerialGetPoint_001 + * @tc.desc: Verify GetPoint function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQ8 + */ +HWTEST_F(UIChartDataSerialTest, UIChartDataSerialGetPoint_001, TestSize.Level1) +{ + if (chartDataSerial_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + Point point; + + bool ret = chartDataSerial_->AddPoints(POINT_ARRAY, ARRAY_SIZE); + EXPECT_EQ(ret, true); + ret = chartDataSerial_->GetPoint(1, point); + EXPECT_EQ(ret, true); + EXPECT_EQ(point.x, POINT_ARRAY[1].x); + EXPECT_EQ(point.y, POINT_ARRAY[1].y); +} + +/** + * @tc.name: UIChartDataSerialModifyPoint_001 + * @tc.desc: Verify ModifyPoint function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQ8 + */ +HWTEST_F(UIChartDataSerialTest, UIChartDataSerialModifyPoint_001, TestSize.Level0) +{ + if (chartDataSerial_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + Point point = { 1, 200 }; // 200 : y posetion + Point point1; + + bool ret = chartDataSerial_->AddPoints(POINT_ARRAY, ARRAY_SIZE); + EXPECT_EQ(ret, true); + ret = chartDataSerial_->ModifyPoint(ARRAY_SIZE + 1, point); + EXPECT_EQ(ret, false); + ret = chartDataSerial_->ModifyPoint(1, point); + EXPECT_EQ(ret, true); + ret = chartDataSerial_->GetPoint(1, point1); + EXPECT_EQ(ret, true); + EXPECT_EQ(point1.x, point.x); + EXPECT_EQ(point1.y, point.y); +} + +/** + * @tc.name: UIChartDataSerialClearData_001 + * @tc.desc: Verify ClearData function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQ8 + */ +HWTEST_F(UIChartDataSerialTest, UIChartDataSerialClearData_001, TestSize.Level0) +{ + if (chartDataSerial_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + Point point; + + bool ret = chartDataSerial_->AddPoints(POINT_ARRAY, ARRAY_SIZE); + EXPECT_EQ(ret, true); + chartDataSerial_->ClearData(); + ret = chartDataSerial_->GetPoint(1, point); + EXPECT_EQ(ret, false); +} + +/** + * @tc.name: UIChartDataSerialGetDataCount_001 + * @tc.desc: Verify GetDataCount function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQ8 + */ +HWTEST_F(UIChartDataSerialTest, UIChartDataSerialGetDataCount_001, TestSize.Level0) +{ + if (chartDataSerial_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + bool ret = chartDataSerial_->AddPoints(POINT_ARRAY, ARRAY_SIZE); + EXPECT_EQ(ret, true); + uint16_t count = chartDataSerial_->GetDataCount(); + EXPECT_EQ(count, ARRAY_SIZE); + chartDataSerial_->ClearData(); + count = chartDataSerial_->GetDataCount(); + EXPECT_EQ(count, 0); +} + +/** + * @tc.name: UIChartDataSerialEnableGradient_001 + * @tc.desc: Verify EnableGradient function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQ8 + */ +HWTEST_F(UIChartDataSerialTest, UIChartDataSerialEnableGradient_001, TestSize.Level1) +{ + if (chartDataSerial_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + chartDataSerial_->EnableGradient(true); + EXPECT_EQ(chartDataSerial_->IsGradient(), true); +} + +/** + * @tc.name: UIChartDataSerialEnableSmooth_001 + * @tc.desc: Verify EnableSmooth function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQ8 + */ +HWTEST_F(UIChartDataSerialTest, UIChartDataSerialEnableSmooth_001, TestSize.Level1) +{ + if (chartDataSerial_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + chartDataSerial_->EnableSmooth(true); + bool ret = chartDataSerial_->IsSmooth(); + EXPECT_EQ(ret, true); +} + +/** + * @tc.name: UIChartDataSerialGetPeakIndex_001 + * @tc.desc: Verify GetPeakIndex function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQ8 + */ +HWTEST_F(UIChartDataSerialTest, UIChartDataSerialGetPeakIndex_001, TestSize.Level1) +{ + if (chartDataSerial_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + bool ret = chartDataSerial_->AddPoints(POINT_ARRAY, ARRAY_SIZE); + EXPECT_EQ(ret, true); + uint16_t index = chartDataSerial_->GetPeakIndex(); + EXPECT_EQ(index, 4); + int16_t value = chartDataSerial_->GetPeakData(); + EXPECT_EQ(value, 3500); +} + +/** + * @tc.name: UIChartDataSerialGetValleyIndex_001 + * @tc.desc: Verify GetValleyIndex function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQ8 + */ +HWTEST_F(UIChartDataSerialTest, UIChartDataSerialGetValleyIndex_001, TestSize.Level0) +{ + if (chartDataSerial_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + bool ret = chartDataSerial_->AddPoints(POINT_ARRAY, ARRAY_SIZE); + EXPECT_EQ(ret, true); + uint16_t index = chartDataSerial_->GetValleyIndex(); + EXPECT_EQ(index, 0); +} + +/** + * @tc.name: UIChartDataSerialGetLatestIndex_001 + * @tc.desc: Verify GetLatestIndex function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQ8 + */ +HWTEST_F(UIChartDataSerialTest, UIChartDataSerialGetLatestIndex_001, TestSize.Level0) +{ + if (chartDataSerial_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + bool ret = chartDataSerial_->AddPoints(POINT_ARRAY, ARRAY_SIZE); + EXPECT_EQ(ret, true); + uint16_t index = chartDataSerial_->GetLatestIndex(); + EXPECT_EQ(index, ARRAY_SIZE - 1); +} + +/** + * @tc.name: UIChartDataSerialSetLastPointIndex_001 + * @tc.desc: Verify SetLastPointIndex function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQ8 + */ +HWTEST_F(UIChartDataSerialTest, UIChartDataSerialSetLastPointIndex_001, TestSize.Level1) +{ + if (chartDataSerial_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + bool ret = chartDataSerial_->AddPoints(POINT_ARRAY, ARRAY_SIZE); + EXPECT_EQ(ret, true); + uint16_t index = chartDataSerial_->GetLastPointIndex(); + EXPECT_EQ(index, 0); + const uint16_t lastPointIndex = 4; + chartDataSerial_->SetLastPointIndex(lastPointIndex); + EXPECT_EQ(chartDataSerial_->GetLastPointIndex(), lastPointIndex); +} + +/** + * @tc.name: UIChartDataSerialSetLineColor_001 + * @tc.desc: Verify SetLineColor function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQ8 + */ +HWTEST_F(UIChartDataSerialTest, UIChartDataSerialSetLineColor_001, TestSize.Level0) +{ + if (chartDataSerial_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + ColorType color = Color::Red(); + chartDataSerial_->SetLineColor(color); + EXPECT_EQ(chartDataSerial_->GetLineColor().full, color.full); +} + +/** + * @tc.name: UIChartDataSerialSetFillColor_001 + * @tc.desc: Verify SetFillColor function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQ8 + */ +HWTEST_F(UIChartDataSerialTest, UIChartDataSerialSetFillColor_001, TestSize.Level0) +{ + if (chartDataSerial_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + ColorType color = Color::Red(); + chartDataSerial_->SetFillColor(color); + EXPECT_EQ(chartDataSerial_->GetFillColor().full, color.full); +} + +/** + * @tc.name: UIChartDataSerialHidePoint_001 + * @tc.desc: Verify HidePoint function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQ8 + */ +HWTEST_F(UIChartDataSerialTest, UIChartDataSerialHidePoint_001, TestSize.Level0) +{ + if (chartDataSerial_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + const uint16_t index = 3; + const uint16_t count = 5; + chartDataSerial_->HidePoint(index, count); + EXPECT_EQ(chartDataSerial_->GetHideIndex(), index); + EXPECT_EQ(chartDataSerial_->GetHideCount(), count); +} + +/** + * @tc.name: UIChartDataSerialSetHeadPointStyle_001 + * @tc.desc: Verify SetHeadPointStyle function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQ8 + */ +HWTEST_F(UIChartDataSerialTest, UIChartDataSerialSetHeadPointStyle_001, TestSize.Level0) +{ + if (chartDataSerial_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + UIChartDataSerial::PointStyle pointStyle; + InitPointStyle(pointStyle); + + chartDataSerial_->SetHeadPointStyle(pointStyle); + UIChartDataSerial::PointStyle pointStyle1 = chartDataSerial_->GetHeadPointStyle(); + EXPECT_EQ(pointStyle1.fillColor.full, pointStyle.fillColor.full); + EXPECT_EQ(pointStyle1.strokeColor.full, pointStyle.strokeColor.full); + EXPECT_EQ(pointStyle1.strokeWidth, pointStyle.strokeWidth); + EXPECT_EQ(pointStyle1.radius, pointStyle.radius); +} + +/** + * @tc.name: UIChartDataSerialSetTopPointStyle_001 + * @tc.desc: Verify SetTopPointStyle function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQ8 + */ +HWTEST_F(UIChartDataSerialTest, UIChartDataSerialSetTopPointStyle_001, TestSize.Level0) +{ + if (chartDataSerial_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + UIChartDataSerial::PointStyle pointStyle; + InitPointStyle(pointStyle); + + chartDataSerial_->SetTopPointStyle(pointStyle); + UIChartDataSerial::PointStyle pointStyle1 = chartDataSerial_->GetTopPointStyle(); + EXPECT_EQ(pointStyle1.fillColor.full, pointStyle.fillColor.full); + EXPECT_EQ(pointStyle1.strokeColor.full, pointStyle.strokeColor.full); + EXPECT_EQ(pointStyle1.strokeWidth, pointStyle.strokeWidth); + EXPECT_EQ(pointStyle1.radius, pointStyle.radius); +} + +/** + * @tc.name: UIChartDataSerialSetBottomPointStyle_001 + * @tc.desc: Verify SetBottomPointStyle function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQ8 + */ +HWTEST_F(UIChartDataSerialTest, UIChartDataSerialSetBottomPointStyle_001, TestSize.Level0) +{ + if (chartDataSerial_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + UIChartDataSerial::PointStyle pointStyle; + InitPointStyle(pointStyle); + + chartDataSerial_->SetBottomPointStyle(pointStyle); + UIChartDataSerial::PointStyle pointStyle1 = chartDataSerial_->GetBottomPointStyle(); + EXPECT_EQ(pointStyle1.fillColor.full, pointStyle.fillColor.full); + EXPECT_EQ(pointStyle1.strokeColor.full, pointStyle.strokeColor.full); + EXPECT_EQ(pointStyle1.strokeWidth, pointStyle.strokeWidth); + EXPECT_EQ(pointStyle1.radius, pointStyle.radius); +} + +/** + * @tc.name: UIChartAddDataSerial_001 + * @tc.desc: Verify AddDataSerial function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQ8 + */ +HWTEST_F(UIChartDataSerialTest, UIChartAddDataSerial_001, TestSize.Level0) +{ + if (chart_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + bool ret = chart_->AddDataSerial(nullptr); + EXPECT_EQ(ret, false); + ret = chart_->AddDataSerial(chartDataSerial_); + EXPECT_EQ(ret, true); + ret = chart_->AddDataSerial(chartDataSerial_); + EXPECT_EQ(ret, false); +} + +/** + * @tc.name: UIChartDeleteDataSerial_001 + * @tc.desc: Verify DeleteDataSerial function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQ8 + */ +HWTEST_F(UIChartDataSerialTest, UIChartDeleteDataSerial_001, TestSize.Level0) +{ + if (chart_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + bool ret = chart_->DeleteDataSerial(nullptr); + EXPECT_EQ(ret, false); + ret = chart_->DeleteDataSerial(chartDataSerial_); + EXPECT_EQ(ret, false); + ret = chart_->AddDataSerial(chartDataSerial_); + EXPECT_EQ(ret, true); + ret = chart_->DeleteDataSerial(chartDataSerial_); + EXPECT_EQ(ret, true); +} + +/** + * @tc.name: UIChartClearDataSerial_001 + * @tc.desc: Verify ClearDataSerial function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQ8 + */ +HWTEST_F(UIChartDataSerialTest, UIChartClearDataSerial_001, TestSize.Level0) +{ + if (chart_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + bool ret = chart_->AddDataSerial(chartDataSerial_); + EXPECT_EQ(ret, true); + chart_->ClearDataSerial(); + ret = chart_->DeleteDataSerial(chartDataSerial_); + EXPECT_EQ(ret, false); +} +} \ No newline at end of file diff --git a/test/unittest/components/ui_checkbox_unit_test.cpp b/test/unittest/components/ui_checkbox_unit_test.cpp new file mode 100755 index 0000000..9b4b4bc --- /dev/null +++ b/test/unittest/components/ui_checkbox_unit_test.cpp @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "components/ui_checkbox.h" + +#include +#include + +using namespace testing::ext; + +namespace OHOS { +class UICheckBoxTest : public testing::Test { +public: + static void SetUpTestCase(); + static void TearDownTestCase(); + static UICheckBox* checkBox_; +}; + +UICheckBox* UICheckBoxTest::checkBox_ = nullptr; + +void UICheckBoxTest::SetUpTestCase() +{ + if (checkBox_ == nullptr) { + checkBox_ = new UICheckBox(); + } +} + +void UICheckBoxTest::TearDownTestCase() +{ + if (checkBox_ != nullptr) { + delete checkBox_; + checkBox_ = nullptr; + } +} + +/** + * @tc.name: UICheckBoxGetViewType_001 + * @tc.desc: Verify SetState function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMQC + */ +HWTEST_F(UICheckBoxTest, UICheckBoxGetViewType_001, TestSize.Level0) +{ + if (checkBox_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + EXPECT_EQ(checkBox_->GetViewType(), UI_CHECK_BOX); +} + +/** + * @tc.name: UICheckBoxOnClickEvent_001 + * @tc.desc: Verify OnClickEvent function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQ8 + */ +HWTEST_F(UICheckBoxTest, UICheckBoxOnClickEvent_001, TestSize.Level0) +{ + if (checkBox_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + ClickEvent event({0, 0}); + checkBox_->OnClickEvent(event); + EXPECT_EQ(checkBox_->GetState(), UICheckBox::SELECTED); +} + +/** + * @tc.name: UICheckBoxSetState_001 + * @tc.desc: Verify SetState function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQ8 + */ +HWTEST_F(UICheckBoxTest, UICheckBoxSetState_001, TestSize.Level0) +{ + if (checkBox_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + checkBox_->SetState(UICheckBox::UICheckBoxState::SELECTED); + EXPECT_EQ(checkBox_->GetState(), UICheckBox::UICheckBoxState::SELECTED); + checkBox_->SetState(UICheckBox::UICheckBoxState::UNSELECTED); + EXPECT_EQ(checkBox_->GetState(), UICheckBox::UICheckBoxState::UNSELECTED); +} +} // namespace OHOS \ No newline at end of file diff --git a/test/unittest/components/ui_circle_progress_unit_test.cpp b/test/unittest/components/ui_circle_progress_unit_test.cpp new file mode 100755 index 0000000..977c31a --- /dev/null +++ b/test/unittest/components/ui_circle_progress_unit_test.cpp @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "components/ui_circle_progress.h" + +#include +#include + +using namespace testing::ext; +namespace OHOS { +namespace { + constexpr uint16_t MAX_ANGLE_VALUE = 360; + constexpr uint16_t MIN_ANGLE_VALUE = 0; +} +class UICircleProgressTest : public testing::Test { +public: + static void SetUpTestCase(); + static void TearDownTestCase(); + static UICircleProgress* circleProgress_; +}; + +UICircleProgress* UICircleProgressTest::circleProgress_ = nullptr; + +void UICircleProgressTest::SetUpTestCase() +{ + if (circleProgress_ == nullptr) { + circleProgress_ = new UICircleProgress(); + } +} + +void UICircleProgressTest::TearDownTestCase() +{ + if (circleProgress_ != nullptr) { + delete circleProgress_; + circleProgress_ = nullptr; + } +} + +/** + * @tc.name: UICircleProgressGetViewType_001 + * @tc.desc: Verify GetViewType function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMQG + */ +HWTEST_F(UICircleProgressTest, UICircleProgressGetViewType_001, TestSize.Level0) +{ + if (circleProgress_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + EXPECT_EQ(circleProgress_->GetViewType(), UI_CIRCLE_PROGRESS); +} + +/** + * @tc.name: UICircleProgressSetRadius_001 + * @tc.desc: Verify SetRadius function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMQG + */ +HWTEST_F(UICircleProgressTest, UICircleProgressSetRadius_001, TestSize.Level0) +{ + if (circleProgress_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + const uint16_t radius = 1; + + circleProgress_->SetRadius(radius); + EXPECT_EQ(circleProgress_->GetRadius(), radius); +} + +/** + * @tc.name: UICircleProgressSetCenterPosition_001 + * @tc.desc: Verify SetCenterPosition function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMQG + */ +HWTEST_F(UICircleProgressTest, UICircleProgressSetCenterPosition_001, TestSize.Level0) +{ + if (circleProgress_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + const int16_t presetLength = 100; + const int16_t centerX = 50; + const int16_t centerY = 50; + + circleProgress_->Resize(presetLength, presetLength); + circleProgress_->SetCenterPosition(centerX, centerY); + EXPECT_EQ(circleProgress_->GetCenterPosition().x, centerX); + EXPECT_EQ(circleProgress_->GetCenterPosition().y, centerY); +} + +/** + * @tc.name: UICircleProgressSetLineColor_001 + * @tc.desc: Verify SetLineColor function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMQG + */ +HWTEST_F(UICircleProgressTest, UICircleProgressSetLineColor_001, TestSize.Level0) +{ + if (circleProgress_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + circleProgress_->SetLineColor(Color::Red()); + EXPECT_EQ(circleProgress_->GetForegroundStyle(STYLE_LINE_COLOR), Color::Red().full); +} + +/** + * @tc.name: UICircleProgressSetAngle_001 + * @tc.desc: Verify SetAngle function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMQG + */ +HWTEST_F(UICircleProgressTest, UICircleProgressSetStartAngle_001, TestSize.Level0) +{ + if (circleProgress_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + const int16_t startAngle = 0; + const int16_t endAngle = 90; + + EXPECT_EQ(circleProgress_->GetStartAngle(), MIN_ANGLE_VALUE); + circleProgress_->SetStartAngle(startAngle); + EXPECT_EQ(circleProgress_->GetStartAngle(), startAngle); + + EXPECT_EQ(circleProgress_->GetEndAngle(), MAX_ANGLE_VALUE); + circleProgress_->SetEndAngle(endAngle); + EXPECT_EQ(circleProgress_->GetEndAngle(), endAngle); +} +} // namespace OHOS \ No newline at end of file diff --git a/test/unittest/components/ui_dialog_unit_test.cpp b/test/unittest/components/ui_dialog_unit_test.cpp new file mode 100755 index 0000000..06caf4b --- /dev/null +++ b/test/unittest/components/ui_dialog_unit_test.cpp @@ -0,0 +1,228 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "components/root_view.h" +#include "components/ui_dialog.h" + +#include +#include + +#if ENABLE_WINDOW +#if ENABLE_DEBUG +using namespace testing::ext; +namespace OHOS { +namespace { + const int16_t POS_X = 50; + const int16_t POS_Y = 100; + const uint16_t WIDTH = 100; + const uint16_t HEIGHT = 100; +} + +class UIDialogTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + static UIDialog* dialog_; + void SetUp(void); + void TearDown(void); +}; + +UIDialog* UIDialogTest::dialog_ = nullptr; +UIView::OnClickListener* listener_ = nullptr; + +void UIDialogTest::SetUpTestCase(void) +{ + listener_ = new UIView::OnClickListener(); +} + +void UIDialogTest::SetUp(void) +{ + if (dialog_ != nullptr) { + delete dialog_; + } + dialog_ = new UIDialog(); +} + + +void UIDialogTest::TearDown(void) +{ + if (dialog_ != nullptr) { + delete dialog_; + dialog_ = nullptr; + } +} + +void UIDialogTest::TearDownTestCase(void) +{ + if (listener_ != nullptr) { + delete listener_; + listener_ = nullptr; + } +} + +/** + * @tc.name: UIDialogSetTitle_001 + * @tc.desc: Verify SetTitle function, equal. + * @tc.type: FUNC + * @tc.require: AR000F4E5F + */ +HWTEST_F(UIDialogTest, UIDialogSetTitle_001, TestSize.Level0) +{ + const char* title1 = "title1"; + dialog_->SetTitle(title1); + EXPECT_EQ(dialog_->GetTitle(), title1); + const char* title2 = "title2"; + dialog_->SetTitle(title2); + EXPECT_EQ(dialog_->GetTitle(), title2); +} + +/** + * @tc.name: UIDialogSetTitle_002 + * @tc.desc: Verify SetTitle function, equal. + * @tc.type: FUNC + * @tc.require: AR000F4E5F + */ +HWTEST_F(UIDialogTest, UIDialogSetTitle_002, TestSize.Level0) +{ + dialog_->SetTitle(nullptr); + EXPECT_EQ(dialog_->GetTitle(), nullptr); + const char* title = "title"; + dialog_->SetTitle(title); + dialog_->SetTitle(nullptr); + EXPECT_EQ(dialog_->GetTitle(), title); +} + +/** + * @tc.name: UIDialogSetText_001 + * @tc.desc: Verify SetText function, equal. + * @tc.type: FUNC + * @tc.require: AR000F4E5F + */ +HWTEST_F(UIDialogTest, UIDialogSetText_001, TestSize.Level0) +{ + const char* text1 = "text1"; + dialog_->SetText(text1); + EXPECT_EQ(dialog_->GetText(), text1); + const char* text2 = "text2"; + dialog_->SetText(text2); + EXPECT_EQ(dialog_->GetText(), text2); +} + +/** + * @tc.name: UIDialogSetText_002 + * @tc.desc: Verify SetText function, equal. + * @tc.type: FUNC + * @tc.require: AR000F4E5F + */ +HWTEST_F(UIDialogTest, UIDialogSetText_002, TestSize.Level0) +{ + dialog_->SetText(nullptr); + EXPECT_EQ(dialog_->GetText(), nullptr); + const char* text = "text"; + dialog_->SetText(text); + dialog_->SetText(nullptr); + EXPECT_EQ(dialog_->GetText(), text); +} + +/** + * @tc.name: UIDialogSetButton_001 + * @tc.desc: Verify SetButton function, equal. + * @tc.type: FUNC + * @tc.require: AR000F4E5F + */ +HWTEST_F(UIDialogTest, UIDialogSetButton_001, TestSize.Level0) +{ + const char* buttonText = "button"; + dialog_->SetButton(UIDialog::DialogButtonType::BUTTON_LEFT, buttonText, listener_); + const char* buttonText0 = dialog_->GetButtonText(UIDialog::DialogButtonType::BUTTON_LEFT); + ASSERT_TRUE(buttonText0); + if (strcmp(buttonText0, buttonText) != 0) { + EXPECT_EQ(1, 0); + } + EXPECT_EQ(dialog_->GetButtonListener(UIDialog::DialogButtonType::BUTTON_LEFT), listener_); +} + +/** + * @tc.name: UIDialogSetButtonColor_001 + * @tc.desc: Verify SetButtonColor function, equal. + * @tc.type: FUNC + * @tc.require: AR000F4E5F + */ +HWTEST_F(UIDialogTest, UIDialogSetButtonColor_001, TestSize.Level0) +{ + dialog_->SetButton(UIDialog::DialogButtonType::BUTTON_LEFT, "button", listener_); + ColorType color = Color::Red(); + dialog_->SetButtonColor(UIDialog::DialogButtonType::BUTTON_LEFT, color); + EXPECT_EQ(dialog_->GetButtonColor(UIDialog::DialogButtonType::BUTTON_LEFT).full, color.full); +} + +/** + * @tc.name: UIDialogSetOnCancelListener_001 + * @tc.desc: Verify SetOnCancelListener function, equal. + * @tc.type: FUNC + * @tc.require: AR000F4E5F + */ +HWTEST_F(UIDialogTest, UIDialogSetOnCancelListener_001, TestSize.Level0) +{ + dialog_->SetOnCancelListener(nullptr); + EXPECT_EQ(dialog_->GetOnCancelListener(), nullptr); + dialog_->SetOnCancelListener(listener_); + EXPECT_EQ(dialog_->GetOnCancelListener(), listener_); +} + +/** + * @tc.name: UIDialogEnableAutoCancel_001 + * @tc.desc: Verify EnableAutoCancel function, equal. + * @tc.type: FUNC + * @tc.require: AR000F4E5F + */ +HWTEST_F(UIDialogTest, UIDialogEnableAutoCancel_001, TestSize.Level0) +{ + dialog_->EnableAutoCancel(true); + EXPECT_EQ(dialog_->GetEnableAutoCancel(), true); + dialog_->EnableAutoCancel(false); + EXPECT_EQ(dialog_->GetEnableAutoCancel(), false); +} + +/** + * @tc.name: UIDialog_001 + * @tc.desc: Verify UIDialog function, equal. + * @tc.type: FUNC + * @tc.require: SR000F3PED + */ +HWTEST_F(UIDialogTest, UIDialog_001, TestSize.Level0) +{ + const char* title = "title"; + dialog_->SetTitle(title); + EXPECT_EQ(dialog_->GetTitle(), title); + const char* text = "text"; + dialog_->SetText(text); + EXPECT_EQ(dialog_->GetText(), text); + const char* buttonText = "button"; + dialog_->SetButton(UIDialog::DialogButtonType::BUTTON_LEFT, buttonText, listener_); + const char* buttonText0 = dialog_->GetButtonText(UIDialog::DialogButtonType::BUTTON_LEFT); + ASSERT_TRUE(buttonText0); + if (strcmp(buttonText0, buttonText) != 0) { + EXPECT_EQ(1, 0); + } + EXPECT_EQ(dialog_->GetButtonListener(UIDialog::DialogButtonType::BUTTON_LEFT), listener_); + dialog_->SetOnCancelListener(listener_); + EXPECT_EQ(dialog_->GetOnCancelListener(), listener_); + dialog_->EnableAutoCancel(true); + EXPECT_EQ(dialog_->GetEnableAutoCancel(), true); +} +} // namespace OHOS +#endif // ENABLE_DEBUG +#endif // ENABLE_WINDOW \ No newline at end of file diff --git a/test/unittest/components/ui_digital_clock_unit_test.cpp b/test/unittest/components/ui_digital_clock_unit_test.cpp new file mode 100644 index 0000000..0a13153 --- /dev/null +++ b/test/unittest/components/ui_digital_clock_unit_test.cpp @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "components/ui_digital_clock.h" +#include +#include + +using namespace testing::ext; + +namespace OHOS { +namespace { + const uint8_t CURRENT_HOUR = 14; + const uint8_t CURRENT_MINUTE = 40; + const uint8_t CURRENT_SECOND = 30; + const uint8_t HALF_DAY_IN_HOUR = 12; +} + +class UIDigitalClockTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + static UIDigitalClock* digitalClock_; +}; + +UIDigitalClock* UIDigitalClockTest::digitalClock_ = nullptr; + +void UIDigitalClockTest::SetUpTestCase(void) +{ + if (digitalClock_ == nullptr) { + digitalClock_ = new UIDigitalClock(); + } +} + +void UIDigitalClockTest::TearDownTestCase(void) +{ + if (digitalClock_ != nullptr) { + delete digitalClock_; + digitalClock_ = nullptr; + } +} + +/** + * @tc.name: UIDigitalClockGetViewType_001 + * @tc.desc: Verify GetViewType function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQ4 + */ +HWTEST_F(UIDigitalClockTest, UIDigitalClockGetViewType_001, TestSize.Level1) +{ + if (digitalClock_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + EXPECT_EQ(digitalClock_->GetViewType(), UI_DIGITAL_CLOCK); +} + +/** + * @tc.name: UIDigitalClockSetFontId_001 + * @tc.desc: Verify SetFontId function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQ4 + */ +HWTEST_F(UIDigitalClockTest, UIDigitalClockSetFontId_001, TestSize.Level1) +{ + if (digitalClock_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + const uint8_t fontId = 10; + digitalClock_->SetFontId(fontId); + EXPECT_EQ(digitalClock_->GetStyle(STYLE_TEXT_FONT), fontId); +} + +/** + * @tc.name: UIDigitalClockSetDisplayMode_001 + * @tc.desc: Verify SetDisplayMode function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQ4 + */ +HWTEST_F(UIDigitalClockTest, UIDigitalClockSetDisplayMode_001, TestSize.Level0) +{ + if (digitalClock_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + digitalClock_->SetDisplayMode(UIDigitalClock::DisplayMode::DISPLAY_12_HOUR_NO_SECONDS); + EXPECT_EQ(digitalClock_->GetDisplayMode(), UIDigitalClock::DisplayMode::DISPLAY_12_HOUR_NO_SECONDS); + digitalClock_->SetDisplayMode(UIDigitalClock::DisplayMode::DISPLAY_24_HOUR_NO_SECONDS); + EXPECT_EQ(digitalClock_->GetDisplayMode(), UIDigitalClock::DisplayMode::DISPLAY_24_HOUR_NO_SECONDS); + digitalClock_->SetDisplayMode(UIDigitalClock::DisplayMode::DISPLAY_12_HOUR); + EXPECT_EQ(digitalClock_->GetDisplayMode(), UIDigitalClock::DisplayMode::DISPLAY_12_HOUR); + digitalClock_->SetDisplayMode(UIDigitalClock::DisplayMode::DISPLAY_24_HOUR); + EXPECT_EQ(digitalClock_->GetDisplayMode(), UIDigitalClock::DisplayMode::DISPLAY_24_HOUR); +} + +/** + * @tc.name: UIDigitalClockSetOpacity_001 + * @tc.desc: Verify SetOpacity function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQ4 + */ +HWTEST_F(UIDigitalClockTest, UIDigitalClockSetOpacity_001, TestSize.Level0) +{ + if (digitalClock_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + digitalClock_->SetOpacity(OPA_OPAQUE); + EXPECT_EQ(digitalClock_->GetOpacity(), OPA_OPAQUE); +} + +/** + * @tc.name: UIDigitalClockSetTime24Hour_001 + * @tc.desc: Verify SetTime24Hour function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQ4 + */ +HWTEST_F(UIDigitalClockTest, UIDigitalClockSetTime24Hour_001, TestSize.Level0) +{ + if (digitalClock_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + digitalClock_->SetTime24Hour(CURRENT_HOUR, CURRENT_MINUTE, CURRENT_SECOND); + EXPECT_EQ(digitalClock_->GetCurrentHour(), CURRENT_HOUR); + EXPECT_EQ(digitalClock_->GetCurrentMinute(), CURRENT_MINUTE); + EXPECT_EQ(digitalClock_->GetCurrentSecond(), CURRENT_SECOND); +} + +/** + * @tc.name: UIDigitalClockSetTime12Hour_001 + * @tc.desc: Verify SetTime12Hour function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQ4 + */ +HWTEST_F(UIDigitalClockTest, UIDigitalClockSetTime12Hour_001, TestSize.Level0) +{ + if (digitalClock_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + digitalClock_->SetTime12Hour(CURRENT_HOUR, CURRENT_MINUTE, CURRENT_SECOND, true); + EXPECT_EQ(digitalClock_->GetCurrentHour(), CURRENT_HOUR - HALF_DAY_IN_HOUR); + EXPECT_EQ(digitalClock_->GetCurrentMinute(), CURRENT_MINUTE); + EXPECT_EQ(digitalClock_->GetCurrentSecond(), CURRENT_SECOND); +} + +/** + * @tc.name: UIDigitalClockSetTime12Hour_002 + * @tc.desc: Verify SetTime12Hour function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQ4 + */ +HWTEST_F(UIDigitalClockTest, UIDigitalClockSetTime12Hour_002, TestSize.Level0) +{ + if (digitalClock_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + digitalClock_->SetTime12Hour(CURRENT_HOUR, CURRENT_MINUTE, CURRENT_SECOND, false); + EXPECT_EQ(digitalClock_->GetCurrentHour(), CURRENT_HOUR); + EXPECT_EQ(digitalClock_->GetCurrentMinute(), CURRENT_MINUTE); + EXPECT_EQ(digitalClock_->GetCurrentSecond(), CURRENT_SECOND); +} +} \ No newline at end of file diff --git a/test/unittest/components/ui_image_animator_unit_test.cpp b/test/unittest/components/ui_image_animator_unit_test.cpp new file mode 100644 index 0000000..f0769f0 --- /dev/null +++ b/test/unittest/components/ui_image_animator_unit_test.cpp @@ -0,0 +1,367 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "components/ui_image_animator.h" + +#include +#include +#include "file.h" +#include "test_resource_config.h" + +using namespace testing::ext; +namespace OHOS { +namespace { + const Point INIT_POS = { 50, 50 }; + const int16_t INIT_WIDTH = 94; + const int16_t INIT_HEIGHT = 94; + const uint8_t IMAGE_SIZE = 3; +} + +class UIImageAnimatorViewTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + static UIImageAnimatorView* imageAnimator_; +}; + +UIImageAnimatorView* UIImageAnimatorViewTest::imageAnimator_ = nullptr; + +void UIImageAnimatorViewTest::SetUpTestCase(void) +{ + if (imageAnimator_ == nullptr) { + imageAnimator_ = new UIImageAnimatorView(); + } +} + +void UIImageAnimatorViewTest::TearDownTestCase(void) +{ + if (imageAnimator_ != nullptr) { + delete imageAnimator_; + imageAnimator_ = nullptr; + } +} + +static ImageAnimatorInfo g_imageAnimatorInfo[IMAGE_SIZE] = { + { BLUE_RGB888_IMAGE_PATH, INIT_POS, INIT_WIDTH, INIT_HEIGHT, IMG_SRC_FILE_PATH }, + { BLUE_ARGB8888_IMAGE_PATH, INIT_POS, INIT_WIDTH, INIT_HEIGHT, IMG_SRC_FILE_PATH }, + { BLUE_RGB565_IMAGE_PATH, INIT_POS, INIT_WIDTH, INIT_HEIGHT, IMG_SRC_FILE_PATH }, +}; + +/** + * @tc.name: UIImageAnimatorViewGetViewType_001 + * @tc.desc: Verify GetViewType function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMQL + */ +HWTEST_F(UIImageAnimatorViewTest, UIImageAnimatorViewGetViewType_001, TestSize.Level0) +{ + if (imageAnimator_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + EXPECT_EQ(imageAnimator_->GetViewType(), UI_IMAGE_ANIMATOR_VIEW); +} + +/** + * @tc.name: UIImageAnimatorViewSetTimeOfPause_001 + * @tc.desc: Verify SetTimeOfPause function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMQL + */ +HWTEST_F(UIImageAnimatorViewTest, UIImageAnimatorViewSetTimeOfPause_001, TestSize.Level0) +{ + if (imageAnimator_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + uint16_t timeOfPause = 1; + + imageAnimator_->SetTimeOfPause(timeOfPause); + EXPECT_EQ(imageAnimator_->GetTimeOfPause(), timeOfPause); +} + +/** + * @tc.name: UIImageAnimatorViewSetTimeOfUpdate_001 + * @tc.desc: Verify SetTimeOfUpdate function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMQL + */ +HWTEST_F(UIImageAnimatorViewTest, UIImageAnimatorViewSetTimeOfUpdate_001, TestSize.Level0) +{ + if (imageAnimator_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + const uint16_t animatorTime = 300; + + imageAnimator_->SetTimeOfUpdate(animatorTime); + EXPECT_EQ(imageAnimator_->GetTimeOfUpdate(), animatorTime); +} + +/** + * @tc.name: UIImageAnimatorViewSetImageAnimatorSrc_001 + * @tc.desc: Verify SetImageAnimatorSrc function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMQL + */ +HWTEST_F(UIImageAnimatorViewTest, UIImageAnimatorViewSetImageAnimatorSrc_001, TestSize.Level0) +{ + if (imageAnimator_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + const uint8_t imageSize = IMAGE_SIZE; + + imageAnimator_->SetImageAnimatorSrc(g_imageAnimatorInfo, imageSize); + EXPECT_EQ(imageAnimator_->GetImageAnimatorImageNum(), imageSize); +} + +/** + * @tc.name: UIImageAnimatorViewSetImageAnimatorSrc_002 + * @tc.desc: Verify SetImageAnimatorSrc function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMPV + */ +HWTEST_F(UIImageAnimatorViewTest, UIImageAnimatorViewSetImageAnimatorSrc_002, TestSize.Level0) +{ + if (imageAnimator_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + ImageAnimatorInfo* info = &g_imageAnimatorInfo[0]; + imageAnimator_->SetImageAnimatorSrc(g_imageAnimatorInfo, IMAGE_SIZE); + + EXPECT_EQ(imageAnimator_->GetImageAnimatorSrc(), info); +} + +/** + * @tc.name: UIImageAnimatorViewSetSizeFixed_001 + * @tc.desc: Verify SetSizeFixed function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMPV + */ +HWTEST_F(UIImageAnimatorViewTest, UIImageAnimatorViewSetSizeFixed_001, TestSize.Level0) +{ + if (imageAnimator_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + bool fixed = true; + + imageAnimator_->SetSizeFixed(fixed); + EXPECT_EQ(imageAnimator_->IsSizeFixed(), fixed); +} + +/** + * @tc.name: UIImageAnimatorViewSetRepeat_001 + * @tc.desc: Verify SetRepeat function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMPV + */ +HWTEST_F(UIImageAnimatorViewTest, UIImageAnimatorViewSetRepeat_001, TestSize.Level0) +{ + if (imageAnimator_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + bool repeat = true; + + imageAnimator_->SetRepeat(repeat); + EXPECT_EQ(imageAnimator_->IsRepeat(), repeat); +} + +/** + * @tc.name: UIImageAnimatorViewSetReverse_001 + * @tc.desc: Verify SetReverse function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMPV + */ +HWTEST_F(UIImageAnimatorViewTest, UIImageAnimatorViewSetReverse_001, TestSize.Level0) +{ + if (imageAnimator_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + bool reverse = true; + + imageAnimator_->SetReverse(reverse); + EXPECT_EQ(imageAnimator_->IsReverse(), reverse); +} + +/** + * @tc.name: UIImageAnimatorViewSetStartPosition_001 + * @tc.desc: Verify SetStartPosition function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMPV + */ +HWTEST_F(UIImageAnimatorViewTest, UIImageAnimatorViewSetStartPosition_001, TestSize.Level0) +{ + if (imageAnimator_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + uint8_t state = Animator::STOP; + + EXPECT_EQ(imageAnimator_->GetState(), state); +} + +/** + * @tc.name: UIImageAnimatorViewStart_010 + * @tc.desc: Verify Start function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMPV + */ +HWTEST_F(UIImageAnimatorViewTest, UIImageAnimatorViewStart_001, TestSize.Level0) +{ + if (imageAnimator_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + imageAnimator_->Start(); + EXPECT_EQ(imageAnimator_->GetState(), Animator::START); +} + +/** + * @tc.name: UIImageAnimatorViewStop_001 + * @tc.desc: Verify Stop function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMPV + */ +HWTEST_F(UIImageAnimatorViewTest, UIImageAnimatorViewStop_001, TestSize.Level0) +{ + if (imageAnimator_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + imageAnimator_->Stop(); + EXPECT_EQ(imageAnimator_->GetState(), Animator::STOP); +} + +/** + * @tc.name: UIImageAnimatorViewPause_001 + * @tc.desc: Verify Pause function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMPV + */ +HWTEST_F(UIImageAnimatorViewTest, UIImageAnimatorViewPause_001, TestSize.Level0) +{ + if (imageAnimator_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + imageAnimator_->Pause(); + EXPECT_EQ(imageAnimator_->GetState(), Animator::PAUSE); +} + +/** + * @tc.name: UIImageAnimatorViewResume_001 + * @tc.desc: Verify Resume function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMPV + */ +HWTEST_F(UIImageAnimatorViewTest, UIImageAnimatorViewResume_001, TestSize.Level0) +{ + if (imageAnimator_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + imageAnimator_->Resume(); + EXPECT_EQ(imageAnimator_->GetState(), Animator::START); +} + +/** + * @tc.name: UIImageAnimatorViewSetFillMode_001 + * @tc.desc: Verify SetFillMode function, equal. + * @tc.type: FUNC + * @tc.require: SR000F3PEF + */ +HWTEST_F(UIImageAnimatorViewTest, UIImageAnimatorViewSetFillMode_001, TestSize.Level0) +{ + if (imageAnimator_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + + imageAnimator_->SetImageAnimatorSrc(g_imageAnimatorInfo, IMAGE_SIZE); + imageAnimator_->SetFillMode(true); + imageAnimator_->SetReverse(false); + imageAnimator_->Start(); + imageAnimator_->Stop(); + EXPECT_STREQ(imageAnimator_->GetPath(), g_imageAnimatorInfo[2].imagePath); // 2: the last image +} + +/** + * @tc.name: UIImageAnimatorViewSetFillMode_002 + * @tc.desc: Verify SetFillMode function, equal. + * @tc.type: FUNC + * @tc.require: AR000F4E5J + */ +HWTEST_F(UIImageAnimatorViewTest, UIImageAnimatorViewSetFillMode_002, TestSize.Level0) +{ + if (imageAnimator_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + + imageAnimator_->SetImageAnimatorSrc(g_imageAnimatorInfo, IMAGE_SIZE); + imageAnimator_->SetFillMode(true); + imageAnimator_->SetReverse(true); + imageAnimator_->Start(); + imageAnimator_->Stop(); + EXPECT_STREQ(imageAnimator_->GetPath(), g_imageAnimatorInfo[0].imagePath); +} + +/** + * @tc.name: UIImageAnimatorViewSetFillMode_003 + * @tc.desc: Verify SetFillMode function, equal. + * @tc.type: FUNC + * @tc.require: AR000F4E5J + */ +HWTEST_F(UIImageAnimatorViewTest, UIImageAnimatorViewSetFillMode_003, TestSize.Level0) +{ + if (imageAnimator_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + + imageAnimator_->SetImageAnimatorSrc(g_imageAnimatorInfo, IMAGE_SIZE); + imageAnimator_->SetFillMode(false); + imageAnimator_->SetReverse(false); + imageAnimator_->Start(); + imageAnimator_->Stop(); + EXPECT_STREQ(imageAnimator_->GetPath(), g_imageAnimatorInfo[0].imagePath); +} + +/** + * @tc.name: UIImageAnimatorViewSetFillMode_004 + * @tc.desc: Verify SetFillMode function, equal. + * @tc.type: FUNC + * @tc.require: AR000F4E5J + */ +HWTEST_F(UIImageAnimatorViewTest, UIImageAnimatorViewSetFillMode_004, TestSize.Level0) +{ + if (imageAnimator_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + + imageAnimator_->SetImageAnimatorSrc(g_imageAnimatorInfo, IMAGE_SIZE); + imageAnimator_->SetFillMode(false); + imageAnimator_->SetReverse(true); + imageAnimator_->Start(); + imageAnimator_->Stop(); + EXPECT_STREQ(imageAnimator_->GetPath(), g_imageAnimatorInfo[2].imagePath); // 2: the last image +} +} // namespace OHOS diff --git a/test/unittest/components/ui_image_unit_test.cpp b/test/unittest/components/ui_image_unit_test.cpp new file mode 100644 index 0000000..3e074a0 --- /dev/null +++ b/test/unittest/components/ui_image_unit_test.cpp @@ -0,0 +1,409 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "components/ui_image_view.h" +#include +#include +#include "components/root_view.h" +#include "components/ui_view_group.h" +#include "test_resource_config.h" + +using namespace testing::ext; + +namespace OHOS { +class UIImageViewTest : public testing::Test { +public: + UIImageViewTest() : imageView_(nullptr) {} + ~UIImageViewTest() {} + static void SetUpTestCase(void) {} + static void TearDownTestCase(void) {} + void SetUp(void); + void TearDown(void); + UIImageView* imageView_; +}; + +void UIImageViewTest::SetUp(void) +{ + if (imageView_ == nullptr) { + imageView_ = new UIImageView(); + } +} + +void UIImageViewTest::TearDown(void) +{ + if (imageView_ != nullptr) { + delete imageView_; + imageView_ = nullptr; + } +} + +/** + * @tc.name: UIImageViewGetViewType_001 + * @tc.desc: Verify GetViewType function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMQ1 + */ +HWTEST_F(UIImageViewTest, UIImageViewGetViewType_001, TestSize.Level0) +{ + if (imageView_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + EXPECT_EQ(imageView_->GetViewType(), UI_IMAGE_VIEW); +} + +/** + * @tc.name: UIImageViewSetPosition_001 + * @tc.desc: Verify SetPosition function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMQ1 + */ +HWTEST_F(UIImageViewTest, UIImageViewSetPosition_001, TestSize.Level0) +{ + if (imageView_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + const int16_t initPosX = 10; + const int16_t initPosY = 10; + const int16_t initWidth = 100; + const int16_t initHeight = 150; + + imageView_->SetPosition(initPosX, initPosY); + imageView_->SetHeight(initWidth); + imageView_->SetWidth(initHeight); + + EXPECT_EQ(imageView_->GetX(), initPosX); + EXPECT_EQ(imageView_->GetY(), initPosY); + EXPECT_EQ(imageView_->GetHeight(), initWidth); + EXPECT_EQ(imageView_->GetWidth(), initHeight); + + imageView_->SetPosition(0, 0, 0, 0); + EXPECT_EQ(imageView_->GetX(), 0); + EXPECT_EQ(imageView_->GetY(), 0); + EXPECT_EQ(imageView_->GetWidth(), 0); + EXPECT_EQ(imageView_->GetHeight(), 0); +} + +#ifndef VERSION_LITE +/** + * @tc.name: UIImageViewSetSrc_001 + * @tc.desc: Verify SetSrc function, correct gif path, equal. + * @tc.type: FUNC + * @tc.require: SR000F3PEO + */ +HWTEST_F(UIImageViewTest, UIImageViewSetSrc_001, TestSize.Level0) +{ + if (imageView_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + const char* strPath1 = static_cast(GIF_IMAGE_PATH1); + imageView_->SetSrc(strPath1); + EXPECT_EQ(imageView_->GetPath(), nullptr); +} + +/** + * @tc.name: UIImageViewSetSrc_002 + * @tc.desc: Verify SetSrc function, error gif path, equal. + * @tc.type: FUNC + * @tc.require: AR000F3R70 + */ +HWTEST_F(UIImageViewTest, UIImageViewSetSrc_002, TestSize.Level0) +{ + if (imageView_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + const char* strPathError = static_cast(GIF_IMAGE_PATH_ERROR); + imageView_->SetSrc(strPathError); + EXPECT_EQ(imageView_->GetPath(), nullptr); +} + +/** + * @tc.name: UIImageViewSetSrc_003 + * @tc.desc: Verify SetSrc function, tif image format path, equal. + * @tc.type: FUNC + * @tc.require: AR000F3R70 + */ +HWTEST_F(UIImageViewTest, UIImageViewSetSrc_003, TestSize.Level0) +{ + if (imageView_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + const char* strPath = static_cast(TIF_IMAGE_PATH); + imageView_->SetSrc(strPath); + EXPECT_STREQ(imageView_->GetPath(), strPath); +} +#endif + +/** + * @tc.name: UIImageViewSetSrc_004 + * @tc.desc: Verify SetSrc function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMQ1 + */ +HWTEST_F(UIImageViewTest, UIImageViewSetSrc_004, TestSize.Level0) +{ + if (imageView_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + char* srcPath = nullptr; + imageView_->SetSrc(srcPath); + EXPECT_EQ(imageView_->GetPath(), srcPath); + const char* strPath2 = static_cast(BLUE_RGB888_IMAGE_PATH); + imageView_->SetSrc(strPath2); + EXPECT_STREQ(imageView_->GetPath(), strPath2); + ImageInfo* srcPath2 = nullptr; + imageView_->SetSrc(srcPath2); + EXPECT_EQ(imageView_->GetImageInfo(), srcPath2); +} + +/** + * @tc.name: UIImageViewSetAutoEnable_001 + * @tc.desc: Verify SetAutoEnable function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMQ1 + */ +HWTEST_F(UIImageViewTest, UIImageViewSetAutoEnable_001, TestSize.Level0) +{ + if (imageView_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + imageView_->SetAutoEnable(true); + EXPECT_EQ(imageView_->GetAutoEnable(), true); +} + +/** + * @tc.name: UIImageViewSetParent_001 + * @tc.desc: Verify SetParent function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMQ1 + */ +HWTEST_F(UIImageViewTest, UIImageViewSetParent_001, TestSize.Level0) +{ + if (imageView_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + UIView uiView; + imageView_->SetParent(nullptr); + EXPECT_EQ(imageView_->GetParent(), nullptr); + imageView_->SetParent(&uiView); + EXPECT_NE(imageView_->GetParent(), nullptr); +} + +/** + * @tc.name: UIImageViewSetNextSibling_001 + * @tc.desc: Verify SetNextSibling function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMQ1 + */ +HWTEST_F(UIImageViewTest, UIImageViewSetNextSibling_001, TestSize.Level0) +{ + if (imageView_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + UIView uiView; + imageView_->SetNextSibling(nullptr); + EXPECT_EQ(imageView_->GetNextSibling(), nullptr); + imageView_->SetNextSibling(&uiView); + EXPECT_NE(imageView_->GetNextSibling(), nullptr); +} + +/** + * @tc.name: UIImageViewSetVisible_001 + * @tc.desc: Verify SetVisible function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMQ1 + */ +HWTEST_F(UIImageViewTest, UIImageViewSetVisible_001, TestSize.Level0) +{ + if (imageView_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + imageView_->SetVisible(true); + EXPECT_EQ(imageView_->IsVisible(), true); + imageView_->SetVisible(false); + EXPECT_EQ(imageView_->IsVisible(), false); +} + +/** + * @tc.name: UIImageViewSetTouchable_001 + * @tc.desc: Verify SetTouchable function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMQ1 + */ +HWTEST_F(UIImageViewTest, UIImageViewSetTouchable_001, TestSize.Level0) +{ + if (imageView_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + imageView_->SetTouchable(true); + EXPECT_EQ(imageView_->IsTouchable(), true); + imageView_->SetTouchable(false); + EXPECT_EQ(imageView_->IsTouchable(), false); +} + +/** + * @tc.name: UIImageViewSetDraggable_001 + * @tc.desc: Verify SetDraggable function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMQ1 + */ +HWTEST_F(UIImageViewTest, UIImageViewSetDraggable_001, TestSize.Level0) +{ + if (imageView_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + imageView_->SetDraggable(true); + EXPECT_EQ(imageView_->IsDraggable(), true); + imageView_->SetDraggable(false); + EXPECT_EQ(imageView_->IsDraggable(), false); +} + +/** + * @tc.name: UIImageViewSetDragParentInstead_001 + * @tc.desc: Verify SetDragParentInstead function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMQ1 + */ +HWTEST_F(UIImageViewTest, UIImageViewSetDragParentInstead_001, TestSize.Level0) +{ + if (imageView_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + imageView_->SetDragParentInstead(true); + EXPECT_EQ(imageView_->IsDragParentInstead(), true); + imageView_->SetDragParentInstead(false); + EXPECT_EQ(imageView_->IsDragParentInstead(), false); +} + +/** + * @tc.name: UIImageViewResize_001 + * @tc.desc: Verify Resize function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMQ1 + */ +HWTEST_F(UIImageViewTest, UIImageViewResize_001, TestSize.Level0) +{ + if (imageView_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + const int16_t currPosX = 100; + const int16_t currPosY = 300; + + imageView_->Resize(currPosX, currPosY); + EXPECT_EQ(imageView_->GetWidth(), currPosX); + EXPECT_EQ(imageView_->GetHeight(), currPosY); +} + +/** + * @tc.name: UIImageViewSetListener_001 + * @tc.desc: Verify SetListener function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMQ1 + */ +HWTEST_F(UIImageViewTest, UIImageViewSetListener_001, TestSize.Level0) +{ + if (imageView_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + imageView_->SetOnDragListener(nullptr); + imageView_->SetOnClickListener(nullptr); + imageView_->SetOnLongPressListener(nullptr); + + EXPECT_EQ(imageView_->GetOnDragListener(), nullptr); + EXPECT_EQ(imageView_->GetOnClickListener(), nullptr); + EXPECT_EQ(imageView_->GetOnLongPressListener(), nullptr); +} + +/** + * @tc.name: UIImageViewSetViewId_001 + * @tc.desc: Verify SetViewId function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMQ1 + */ +HWTEST_F(UIImageViewTest, UIImageViewSetViewId_001, TestSize.Level0) +{ + if (imageView_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + imageView_->SetViewId(nullptr); + EXPECT_EQ(imageView_->GetViewId(), nullptr); +} + +/** + * @tc.name: UIImageViewSetViewIndex_001 + * @tc.desc: Verify SetViewIndex function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMQ1 + */ +HWTEST_F(UIImageViewTest, UIImageViewSetViewIndex_001, TestSize.Level0) +{ + if (imageView_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + const int16_t index = 101; + imageView_->SetViewIndex(index); + EXPECT_EQ(imageView_->GetViewIndex(), index); +} + +/** + * @tc.name: UIImageViewSetBlurLevel_001 + * @tc.desc: Verify SetBlurLevel function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMQ1 + */ +HWTEST_F(UIImageViewTest, UIImageViewSetBlurLevel_001, TestSize.Level0) +{ + if (imageView_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + BlurLevel level = LEVEL0; + imageView_->SetBlurLevel(level); + EXPECT_EQ(imageView_->GetBlurLevel(), level); +} + +/** + * @tc.name: UIImageViewSetTransformAlgorithm_001 + * @tc.desc: Verify SetTransformAlgorithm function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMQ1 + */ +HWTEST_F(UIImageViewTest, UIImageViewSetTransformAlgorithm_001, TestSize.Level0) +{ + if (imageView_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + TransformAlgorithm algorithm = NEAREST_NEIGHBOR; + imageView_->SetTransformAlgorithm(algorithm); + EXPECT_EQ(imageView_->GetTransformAlgorithm(), algorithm); +} +} // namespace OHOS \ No newline at end of file diff --git a/test/unittest/components/ui_label_button_unit_test.cpp b/test/unittest/components/ui_label_button_unit_test.cpp new file mode 100644 index 0000000..835e993 --- /dev/null +++ b/test/unittest/components/ui_label_button_unit_test.cpp @@ -0,0 +1,207 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "components/ui_label_button.h" + +#include +#include +#include "font/ui_font.h" + +using namespace testing::ext; +namespace OHOS { +class UILabelButtonTest : public testing::Test { +public: + static void SetUpTestCase(); + static void TearDownTestCase(); + static UILabelButton* labelBtn_; +}; + +UILabelButton* UILabelButtonTest::labelBtn_ = nullptr; + +void UILabelButtonTest::SetUpTestCase() +{ + if (labelBtn_ == nullptr) { + labelBtn_ = new UILabelButton(); + } +} + +void UILabelButtonTest::TearDownTestCase() +{ + if (labelBtn_ != nullptr) { + delete labelBtn_; + labelBtn_ = nullptr; + } +} + +/** + * @tc.name: UILabelButtonGetViewType_001 + * @tc.desc: Verify GetViewType function. + * @tc.type: FUNC + * @tc.require: AR000EEMQ5 + */ +HWTEST_F(UILabelButtonTest, UILabelButtonGetViewType_001, TestSize.Level1) +{ + if (labelBtn_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + EXPECT_EQ(labelBtn_->GetViewType(), UI_LABEL_BUTTON); +} + +/** + * @tc.name: UILabelButtonSetText_001 + * @tc.desc: Verify SetText function. + * @tc.type: FUNC + * @tc.require: AR000EEMQ5 + */ +HWTEST_F(UILabelButtonTest, UILabelButtonSetText_001, TestSize.Level1) +{ + if (labelBtn_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + const char* text = "abc"; + + labelBtn_->SetText(text); + if (labelBtn_->GetText() == nullptr) { + EXPECT_NE(0, 0); + return; + } + EXPECT_EQ(strcmp(labelBtn_->GetText(), text), 0); +} +/** + * @tc.name: UILabelButtonSetLablePosition_001 + * @tc.desc: Verify SetLablePosition function. + * @tc.type: FUNC + * @tc.require: AR000EEMQ5 + */ +HWTEST_F(UILabelButtonTest, UILabelButtonSetLablePosition_001, TestSize.Level0) +{ + if (labelBtn_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + const int16_t posX = 10; + const int16_t posY = 20; + + labelBtn_->SetLablePosition(posX, posY); + EXPECT_EQ(labelBtn_->GetLabelPosition().x, posX); + EXPECT_EQ(labelBtn_->GetLabelPosition().y, posY); +} + +/** + * @tc.name: UILabelButtonSetAlign_001 + * @tc.desc: Verify SetAlign function. + * @tc.type: FUNC + * @tc.require: AR000EEMQ5 + */ +HWTEST_F(UILabelButtonTest, UILabelButtonSetAlign_001, TestSize.Level0) +{ + if (labelBtn_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + labelBtn_->SetAlign(UITextLanguageAlignment::TEXT_ALIGNMENT_RIGHT); + EXPECT_EQ(labelBtn_->GetAlign(), UITextLanguageAlignment::TEXT_ALIGNMENT_RIGHT); +} + +/** + * @tc.name: UILabelButtonSetDirect_001 + * @tc.desc: Verify SetDirect function. + * @tc.type: FUNC + * @tc.require: AR000EEMQ5 + */ +HWTEST_F(UILabelButtonTest, UILabelButtonSetDirect_001, TestSize.Level0) +{ + if (labelBtn_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + labelBtn_->SetDirect(UITextLanguageDirect::TEXT_DIRECT_RTL); + EXPECT_EQ(labelBtn_->GetDirect(), UITextLanguageDirect::TEXT_DIRECT_RTL); +} + +/** + * @tc.name: UILabelButtonSetLabelStyle_001 + * @tc.desc: Verify SetLabelStyle function. + * @tc.type: FUNC + * @tc.require: AR000EEMQ5 + */ +HWTEST_F(UILabelButtonTest, UILabelButtonSetLabelStyle_001, TestSize.Level0) +{ + if (labelBtn_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + Style style; + style.borderWidth_ = 1; + + labelBtn_->SetLabelStyle(style); + EXPECT_EQ(labelBtn_->GetLabelStyle().borderWidth_, style.borderWidth_); +} + +/** + * @tc.name: UILabelButtonSetLabelStyle_002 + * @tc.desc: Verify SetLabelStyle function. + * @tc.type: FUNC + * @tc.require: AR000EEMQ5 + */ +HWTEST_F(UILabelButtonTest, UILabelButtonSetLabelStyle_002, TestSize.Level1) +{ + if (labelBtn_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + labelBtn_->SetLabelStyle(STYLE_BORDER_OPA, OPA_TRANSPARENT); + EXPECT_EQ(labelBtn_->GetLabelStyle(STYLE_BORDER_OPA), OPA_TRANSPARENT); +} + +/** + * @tc.name: UILabelButtonSetTextColor_001 + * @tc.desc: Verify SetTextColor function. + * @tc.type: FUNC + * @tc.require: AR000EEMQ5 + */ +HWTEST_F(UILabelButtonTest, UILabelButtonSetTextColor_001, TestSize.Level0) +{ + if (labelBtn_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + labelBtn_->SetTextColor(Color::Gray()); + EXPECT_EQ(labelBtn_->GetLabelStyle().textColor_.full, Color::Gray().full); +} + +/** + * @tc.name: UILabelButtonSetFont_001 + * @tc.desc: Verify SetFontId function. + * @tc.type: FUNC + * @tc.require: AR000EEMQ5 + */ +HWTEST_F(UILabelButtonTest, UILabelButtonSetFontId_001, TestSize.Level0) +{ + if (labelBtn_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + const uint8_t fontId = 16; + labelBtn_->SetFontId(fontId); + if (!UIFont::GetInstance()->IsVectorFont()) { + EXPECT_EQ(labelBtn_->GetFontId(), fontId); + } else { + EXPECT_EQ(labelBtn_->GetFontId(), 0); + } +} +} // namespace OHOS \ No newline at end of file diff --git a/test/unittest/components/ui_label_unit_test.cpp b/test/unittest/components/ui_label_unit_test.cpp new file mode 100755 index 0000000..9a94ce0 --- /dev/null +++ b/test/unittest/components/ui_label_unit_test.cpp @@ -0,0 +1,227 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "components/ui_label.h" + +#include +#include + +using namespace testing::ext; +namespace OHOS { +namespace { + const int16_t INIT_WIDTH = 100; + const int16_t INIT_HEIGHT = 150; +} + +class UILabelTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + static UILabel* label_; +}; + +UILabel* UILabelTest::label_ = nullptr; + +void UILabelTest::SetUpTestCase(void) +{ + if (label_ == nullptr) { + label_ = new UILabel(); + } +} + +void UILabelTest::TearDownTestCase(void) +{ + if (label_ != nullptr) { + delete label_; + label_ = nullptr; + } +} + +/** + * @tc.name: UILabelGetViewType_001 + * @tc.desc: Verify GetViewType and GetHeight function. + * @tc.type: FUNC + * @tc.require: AR000DSMQ1 + */ +HWTEST_F(UILabelTest, UILabelGetViewType_001, TestSize.Level0) +{ + if (label_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + EXPECT_EQ(label_->GetViewType(), UI_LABEL); +} + +/** + * @tc.name: UILabelResize_001 + * @tc.desc: Verify Resize function. + * @tc.type: FUNC + * @tc.require: AR000DSMQ1 + */ +HWTEST_F(UILabelTest, UILabelResize_001, TestSize.Level0) +{ + if (label_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + label_->Resize(INIT_WIDTH, INIT_HEIGHT); + EXPECT_EQ(label_->GetWidth(), INIT_WIDTH); + EXPECT_EQ(label_->GetHeight(), INIT_HEIGHT); +} + +/** + * @tc.name: UILabelSetText_001 + * @tc.desc: Verify SetText function. + * @tc.type: FUNC + * @tc.require: AR000DSMQ1 + */ +HWTEST_F(UILabelTest, UILabelSetText_001, TestSize.Level0) +{ + if (label_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + const char* text = "abc"; + label_->Resize(INIT_WIDTH, INIT_HEIGHT); + label_->SetText(text); + + const char* text0 = label_->GetText(); + ASSERT_TRUE(text0); + EXPECT_EQ(strcmp(text0, text), 0); +} + +/** + * @tc.name: UILabelSetLineBreakMode_001 + * @tc.desc: Verify SetLineBreakMode function. + * @tc.type: FUNC + * @tc.require: AR000DSMQ1 + */ +HWTEST_F(UILabelTest, UILabelSetLineBreakMode_001, TestSize.Level0) +{ + if (label_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + const uint8_t lineBreakMode = UILabel::LINE_BREAK_ELLIPSIS; + + label_->SetLineBreakMode(lineBreakMode); + EXPECT_EQ(label_->GetLineBreakMode(), lineBreakMode); +} + +/** + * @tc.name: UILabelSetTextColor_001 + * @tc.desc: Verify SetTextColor function. + * @tc.type: FUNC + * @tc.require: AR000DSMQ1 + */ +HWTEST_F(UILabelTest, UILabelSetTextColor_001, TestSize.Level0) +{ + if (label_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + ColorType color = Color::White(); + + label_->SetTextColor(color); + EXPECT_EQ(label_->GetTextColor().full, color.full); +} + +/** + * @tc.name: UILabelSetLongMode_001 + * @tc.desc: Verify SetLongMode function. + * @tc.type: FUNC + * @tc.require: AR000DSMQ1 + */ +HWTEST_F(UILabelTest, UILabelSetLongMode_001, TestSize.Level0) +{ + if (label_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + label_->SetLineBreakMode(UILabel::LINE_BREAK_ADAPT); + EXPECT_EQ(label_->GetLineBreakMode(), UILabel::LINE_BREAK_ADAPT); +} + +/** + * @tc.name: UILabelSetAlign_001 + * @tc.desc: Verify SetAlign function. + * @tc.type: FUNC + * @tc.require: AR000DSMQ1 + */ +HWTEST_F(UILabelTest, UILabelSetAlign_001, TestSize.Level0) +{ + if (label_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + label_->SetAlign(UITextLanguageAlignment::TEXT_ALIGNMENT_RIGHT, UITextLanguageAlignment::TEXT_ALIGNMENT_BOTTOM); + EXPECT_EQ(label_->GetHorAlign(), UITextLanguageAlignment::TEXT_ALIGNMENT_RIGHT); + EXPECT_EQ(label_->GetVerAlign(), UITextLanguageAlignment::TEXT_ALIGNMENT_BOTTOM); +} + +/** + * @tc.name: UILabelSetDirect_001 + * @tc.desc: Verify SetDirect function. + * @tc.type: FUNC + * @tc.require: AR000DSMQ1 + */ +HWTEST_F(UILabelTest, UILabelSetDirect_001, TestSize.Level0) +{ + if (label_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + label_->SetDirect(UITextLanguageDirect::TEXT_DIRECT_RTL); + EXPECT_EQ(label_->GetDirect(), UITextLanguageDirect::TEXT_DIRECT_RTL); +} + +/** + * @tc.name: UILabelSetRollStartPos_001 + * @tc.desc: Verify SetRollStartPos function. + * @tc.type: FUNC + * @tc.require: AR000DSMQ1 + */ +HWTEST_F(UILabelTest, UILabelSetRollStartPos_001, TestSize.Level0) +{ + if (label_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + const int16_t rollStartPos = 50; + + label_->SetRollStartPos(rollStartPos); + EXPECT_EQ(label_->GetRollStartPos(), rollStartPos); +} + +/** + * @tc.name: UILabelSetFont_001 + * @tc.desc: Verify SetFont function. + * @tc.type: FUNC + * @tc.require: AR000DSMQ1 + */ +HWTEST_F(UILabelTest, UILabelSetFont_001, TestSize.Level0) +{ + if (label_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + uint8_t fontId = label_->GetFontId(); + + const uint8_t fontSize = 20; // 20: font size for test + label_->SetFont("error_font_name", fontSize); + + EXPECT_EQ(label_->GetFontId(), fontId); +} +} // namespace OHOS \ No newline at end of file diff --git a/test/unittest/components/ui_picker_unit_test.cpp b/test/unittest/components/ui_picker_unit_test.cpp new file mode 100755 index 0000000..118af92 --- /dev/null +++ b/test/unittest/components/ui_picker_unit_test.cpp @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "components/ui_picker.h" +#include +#include +#include "style.h" + +using namespace testing::ext; +namespace OHOS { +class UIPickerTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + static UIPicker* picker_; +}; + +UIPicker* UIPickerTest::picker_ = nullptr; + +void UIPickerTest::SetUpTestCase(void) +{ + if (picker_ == nullptr) { + picker_ = new UIPicker(); + } +} + +void UIPickerTest::TearDownTestCase(void) +{ + if (picker_ != nullptr) { + delete picker_; + picker_ = nullptr; + } +} + +/** + * @tc.name: UIPickerGetViewType_001 + * @tc.desc: Verify GetViewType function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQ6 + */ +HWTEST_F(UIPickerTest, UIPickerGetViewType_001, TestSize.Level0) +{ + if (picker_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + EXPECT_EQ(picker_->GetViewType(), UI_PICKER); +} + +/** + * @tc.name: UIPickerGetHighlightFontId_001 + * @tc.desc: Verify GetHighlightFontId function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQ6 + */ +HWTEST_F(UIPickerTest, UIPickerGetHighlightFontId_001, TestSize.Level0) +{ + if (picker_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + uint8_t highlightFontId = StyleDefault::GetPickerHighlightStyle().font_; + EXPECT_EQ(picker_->GetHighlightFontId(), highlightFontId); +} + +/** + * @tc.name: UIPickerSetFontId_001 + * @tc.desc: Verify SetFontId function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQ6 + */ +HWTEST_F(UIPickerTest, UIPickerSetFontId_001, TestSize.Level0) +{ + if (picker_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + const uint8_t backgroundFontId = 16; + const uint8_t highlightFontId = 18; + + picker_->SetFontId(backgroundFontId, highlightFontId); + EXPECT_EQ(picker_->GetBackgroundFontId(), backgroundFontId); + EXPECT_EQ(picker_->GetHighlightFontId(), highlightFontId); +} + +/** + * @tc.name: UIPickerSetTextColor_001 + * @tc.desc: Verify SetTextColor function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQ6 + */ +HWTEST_F(UIPickerTest, UIPickerSetTextColor_001, TestSize.Level0) +{ + if (picker_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + ColorType backgroundColor; + ColorType highlightColor; + backgroundColor.alpha = OPA_OPAQUE; + highlightColor.alpha = OPA_OPAQUE; + + picker_->SetTextColor(backgroundColor, highlightColor); + EXPECT_EQ(picker_->GetBackgroundTextColor().alpha, backgroundColor.alpha); + EXPECT_EQ(picker_->GetHighlightTextColor().alpha, highlightColor.alpha); +} + +/** + * @tc.name: UIPickerSetWidth_001 + * @tc.desc: Verify SetWidth function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQ6 + */ +HWTEST_F(UIPickerTest, UIPickerSetWidth_001, TestSize.Level0) +{ + if (picker_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + const int16_t width = 10; + const int16_t height = 20; + + picker_->SetWidth(width); + picker_->SetHeight(height); + EXPECT_EQ(picker_->GetWidth(), width); + EXPECT_EQ(picker_->GetHeight(), height); +} + + +/** + * @tc.name: UIPickerSetSelected_001 + * @tc.desc: Verify SetSelected function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQ6 + */ +HWTEST_F(UIPickerTest, UIPickerSetSelected_001, TestSize.Level0) +{ + if (picker_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + const uint16_t index = 30; + const int16_t itemHeight = 10; + const int16_t width = 100; + const int16_t height = 100; + const int16_t startValue= 0; + const int16_t endValue = 100; + picker_->SetPosition(0, 0, width, height); + picker_->SetItemHeight(itemHeight); + picker_->SetValues(startValue, endValue); + const char* value[1]; + + value[0] = "abc"; + picker_->SetValues(value, 1); + picker_->SetValues(startValue, endValue); + picker_->SetSelected(index); + EXPECT_EQ(picker_->GetSelected(), index); +} +} diff --git a/test/unittest/components/ui_qrcode_unit_test.cpp b/test/unittest/components/ui_qrcode_unit_test.cpp new file mode 100644 index 0000000..e2a3663 --- /dev/null +++ b/test/unittest/components/ui_qrcode_unit_test.cpp @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "components/ui_qrcode.h" + +#include +#include + +using namespace testing::ext; +namespace OHOS { +namespace { + const int16_t WIDTH = 100; + const int16_t HEIGHT = 100; +} +class UIQrcodeTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + static UIQrcode* qrcode_; +}; + +UIQrcode* UIQrcodeTest::qrcode_ = nullptr; + +void UIQrcodeTest::SetUpTestCase() +{ + if (qrcode_ == nullptr) { + qrcode_ = new UIQrcode(); + } +} + +void UIQrcodeTest::TearDownTestCase() +{ + if (qrcode_ != nullptr) { + delete qrcode_; + qrcode_ = nullptr; + } +} + +/** + * @tc.name: Graphic_UIQrcodeTest_Test_GetViewType_001 + * @tc.desc: Verify GetViewType function. + * @tc.type: FUNC + * @tc.require: AR000F4E5M + */ +HWTEST_F(UIQrcodeTest, Graphic_UIQrcodeTest_Test_GetViewType_001, TestSize.Level0) +{ + if (qrcode_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + EXPECT_EQ(qrcode_->GetViewType(), UI_QRCODE); +} + +/** + * @tc.name: Graphic_UIQrcodeTest_Test_SetWidth_001 + * @tc.desc: Verify SetWidth function. + * @tc.type: FUNC + * @tc.require: AR000F4E5M + */ +HWTEST_F(UIQrcodeTest, Graphic_UIQrcodeTest_Test_SetWidth_001, TestSize.Level0) +{ + if (qrcode_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + qrcode_->SetWidth(WIDTH); + EXPECT_EQ(qrcode_->GetWidth(), WIDTH); +} + +/** + * @tc.name: Graphic_UIQrcodeTest_Test_SetHeight_001 + * @tc.desc: Verify SetHeight function. + * @tc.type: FUNC + * @tc.require: SR000F3PEF + */ +HWTEST_F(UIQrcodeTest, Graphic_UIQrcodeTest_Test_SetHeight_001, TestSize.Level0) +{ + if (qrcode_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + qrcode_->SetHeight(HEIGHT); + EXPECT_EQ(qrcode_->GetHeight(), HEIGHT); +} +} // namespace OHOS diff --git a/test/unittest/components/ui_radio_button_unit_test.cpp b/test/unittest/components/ui_radio_button_unit_test.cpp new file mode 100755 index 0000000..56feff1 --- /dev/null +++ b/test/unittest/components/ui_radio_button_unit_test.cpp @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "components/ui_radio_button.h" + +#include +#include +#include "components/root_view.h" +using namespace testing::ext; +namespace OHOS { +class UIRadioButtonTest : public testing::Test { +public: + static void SetUpTestCase(); + static void TearDownTestCase(); + static UIRadioButton* radioBtn_; +}; + +UIRadioButton* UIRadioButtonTest::radioBtn_ = nullptr; + +void UIRadioButtonTest::SetUpTestCase() +{ + if (radioBtn_ == nullptr) { + radioBtn_ = new UIRadioButton(); + } +} + +void UIRadioButtonTest::TearDownTestCase() +{ + if (radioBtn_ != nullptr) { + delete radioBtn_; + radioBtn_ = nullptr; + } +} + +/** + * @tc.name: UIRadioButtonGetViewType_001 + * @tc.desc: Verify GetViewType function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMQB + */ +HWTEST_F(UIRadioButtonTest, UIRadioButtonGetViewType_001, TestSize.Level0) +{ + if (radioBtn_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + EXPECT_EQ(radioBtn_->GetViewType(), UI_RADIO_BUTTON); +} + +/** + * @tc.name: UIRadioButtonGetName_001 + * @tc.desc: Verify GetName function, equal. + * @tc.type: FUNC + * @tc.require: AR000F4E5I + */ +HWTEST_F(UIRadioButtonTest, UIRadioButtonGetName_001, TestSize.Level0) +{ + if (radioBtn_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + const char* name = "group"; + radioBtn_->SetName(name); + if (radioBtn_->GetName() == nullptr) { + EXPECT_NE(0, 0); + return; + } + EXPECT_EQ(strcmp(radioBtn_->GetName(), name), 0); +} + +/** + * @tc.name: UIRadioButtonSetName_001 + * @tc.desc: Verify SetName function, equal. + * @tc.type: FUNC + * @tc.require: SR000F3PEE + */ +HWTEST_F(UIRadioButtonTest, UIRadioButtonSetName_001, TestSize.Level0) +{ + if (radioBtn_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + const char* name1 = "group1"; + const char* name2 = "group2"; + UIRadioButton* radioBtn1 = new UIRadioButton(name1); + UIRadioButton* radioBtn2 = new UIRadioButton(); + radioBtn2->SetName(name1); + UIRadioButton* radioBtn3 = new UIRadioButton(name1); + UIRadioButton* radioBtn4 = new UIRadioButton(name2); + UIRadioButton* radioBtn5 = new UIRadioButton(name2); + UIViewGroup* view = static_cast(RootView::GetInstance()); + view->Add(radioBtn1); + view->Add(radioBtn2); + view->Add(radioBtn3); + view->Add(radioBtn4); + view->Add(radioBtn5); + + ClickEvent event({0, 0}); + radioBtn2->OnClickEvent(event); + EXPECT_EQ(radioBtn1->GetState(), UICheckBox::UICheckBoxState::UNSELECTED); + EXPECT_EQ(radioBtn2->GetState(), UICheckBox::UICheckBoxState::SELECTED); + EXPECT_EQ(radioBtn3->GetState(), UICheckBox::UICheckBoxState::UNSELECTED); + EXPECT_EQ(radioBtn4->GetState(), UICheckBox::UICheckBoxState::UNSELECTED); + EXPECT_EQ(radioBtn5->GetState(), UICheckBox::UICheckBoxState::UNSELECTED); + + radioBtn4->OnClickEvent(event); + EXPECT_EQ(radioBtn1->GetState(), UICheckBox::UICheckBoxState::UNSELECTED); + EXPECT_EQ(radioBtn2->GetState(), UICheckBox::UICheckBoxState::SELECTED); + EXPECT_EQ(radioBtn3->GetState(), UICheckBox::UICheckBoxState::UNSELECTED); + EXPECT_EQ(radioBtn4->GetState(), UICheckBox::UICheckBoxState::SELECTED); + EXPECT_EQ(radioBtn5->GetState(), UICheckBox::UICheckBoxState::UNSELECTED); + + radioBtn1->OnClickEvent(event); + EXPECT_EQ(radioBtn1->GetState(), UICheckBox::UICheckBoxState::SELECTED); + EXPECT_EQ(radioBtn2->GetState(), UICheckBox::UICheckBoxState::UNSELECTED); + EXPECT_EQ(radioBtn3->GetState(), UICheckBox::UICheckBoxState::UNSELECTED); + EXPECT_EQ(radioBtn4->GetState(), UICheckBox::UICheckBoxState::SELECTED); + EXPECT_EQ(radioBtn5->GetState(), UICheckBox::UICheckBoxState::UNSELECTED); + + view->Remove(radioBtn1); + view->Remove(radioBtn2); + view->Remove(radioBtn3); + view->Remove(radioBtn4); + view->Remove(radioBtn5); + delete radioBtn1; + delete radioBtn2; + delete radioBtn3; + delete radioBtn4; + delete radioBtn5; +} +} // namespace OHOS \ No newline at end of file diff --git a/test/unittest/components/ui_repeat_button_unit_test.cpp b/test/unittest/components/ui_repeat_button_unit_test.cpp new file mode 100755 index 0000000..224fe16 --- /dev/null +++ b/test/unittest/components/ui_repeat_button_unit_test.cpp @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "components/ui_repeat_button.h" + +#include +#include + +using namespace testing::ext; +namespace OHOS { +class UIRepeatButtonTest : public testing::Test { +public: + static void SetUpTestCase(); + static void TearDownTestCase(); + static UIRepeatButton* repeatBtn_; +}; + +UIRepeatButton* UIRepeatButtonTest::repeatBtn_ = nullptr; + +void UIRepeatButtonTest::SetUpTestCase() +{ + if (repeatBtn_ == nullptr) { + repeatBtn_ = new UIRepeatButton(); + } +} + +void UIRepeatButtonTest::TearDownTestCase() +{ + if (repeatBtn_ != nullptr) { + delete repeatBtn_; + repeatBtn_ = nullptr; + } +} + +/** + * @tc.name: UIRepeatButtonGetViewType_001 + * @tc.desc: Verify GetViewType function, equal. + * @tc.type: FUNC + * @tc.require: SR000DRSH1 + */ +HWTEST_F(UIRepeatButtonTest, UIRepeatButtonTestGetViewType_001, TestSize.Level0) +{ + if (repeatBtn_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + EXPECT_EQ(repeatBtn_->GetViewType(), UI_REPEAT_BUTTON); +} + +/** + * @tc.name: UIRepeatButtonSetInterval_001 + * @tc.desc: Verify SetInterval function, equal. + * @tc.type: FUNC + * @tc.require: SR000DRSH1 + */ +HWTEST_F(UIRepeatButtonTest, UIRepeatButtonSetInterval_001, TestSize.Level0) +{ + if (repeatBtn_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + const uint16_t interval = 10; + repeatBtn_->SetInterval(interval); + EXPECT_EQ(repeatBtn_->GetInterval(), interval); +} +} // namespace OHOS \ No newline at end of file diff --git a/test/unittest/components/ui_scroll_view_unit_test.cpp b/test/unittest/components/ui_scroll_view_unit_test.cpp new file mode 100755 index 0000000..03f4ba6 --- /dev/null +++ b/test/unittest/components/ui_scroll_view_unit_test.cpp @@ -0,0 +1,205 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "components/ui_scroll_view.h" +#include +#include + +using namespace testing::ext; +namespace OHOS { +namespace { + constexpr uint8_t DEFAULT_BAR_WIDTH = 5; +} + +class ScrollViewTest : public testing::Test { +public: + static void SetUpTestCase(); + static void TearDownTestCase(); + static UIScrollView* scrollView_; +}; + +UIScrollView* ScrollViewTest::scrollView_ = nullptr; + +void ScrollViewTest::SetUpTestCase() +{ + if (scrollView_ == nullptr) { + scrollView_ = new UIScrollView(); + } +} + +void ScrollViewTest::TearDownTestCase() +{ + if (scrollView_ != nullptr) { + delete scrollView_; + scrollView_ = nullptr; + } +} + +/** + * @tc.name: UIScrollViewSetScrollState_001 + * @tc.desc: Verify SetScrollState function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMQE + */ +HWTEST_F(ScrollViewTest, UIScrollViewSetScrollState_001, TestSize.Level0) +{ + UIScrollView::OnScrollListener* scrollListener = new UIScrollView::OnScrollListener(); + if (scrollListener == nullptr) { + EXPECT_NE(0, 0); + return; + } + uint8_t state = UIScrollView::OnScrollListener::SCROLL_STATE_MOVE; + scrollListener->SetScrollState(state); + EXPECT_EQ(scrollListener->GetScrollState(), state); + + state = UIScrollView::OnScrollListener::SCROLL_STATE_STOP; + scrollListener->SetScrollState(state); + EXPECT_EQ(scrollListener->GetScrollState(), state); + delete scrollListener; +} + +/** + * @tc.name: UIScrollViewGetViewType_001 + * @tc.desc: Verify GetViewType function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMQE + */ +HWTEST_F(ScrollViewTest, UIScrollViewGetViewType_001, TestSize.Level0) +{ + if (scrollView_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + EXPECT_EQ(scrollView_->GetViewType(), UI_SCROLL_VIEW); +} + +/** + * @tc.name: UIScrollViewSetHorizontalScrollState_001 + * @tc.desc: Verify SetHorizontalScrollState function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMQE + */ +HWTEST_F(ScrollViewTest, UIScrollViewSetHorizontalScrollState_001, TestSize.Level0) +{ + if (scrollView_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + scrollView_->SetHorizontalScrollState(false); + EXPECT_EQ(scrollView_->GetHorizontalScrollState(), false); + + scrollView_->SetHorizontalScrollState(true); + EXPECT_EQ(scrollView_->GetHorizontalScrollState(), true); +} + +/** + * @tc.name: UIScrollViewSetVerticalScrollState_001 + * @tc.desc: Verify SetVerticalScrollState function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMQE + */ +HWTEST_F(ScrollViewTest, UIScrollViewSetVerticalScrollState_001, TestSize.Level0) +{ + if (scrollView_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + scrollView_->SetVerticalScrollState(false); + EXPECT_EQ(scrollView_->GetVerticalScrollState(), false); + + scrollView_->SetVerticalScrollState(true); + EXPECT_EQ(scrollView_->GetVerticalScrollState(), true); +} + +/** + * @tc.name: UIScrollViewSetMaxScrollDistance_001 + * @tc.desc: Verify SetMaxScrollDistance function, equal. + * @tc.type: FUNC + * @tc.require: AR000E2RJK + */ +HWTEST_F(ScrollViewTest, UIScrollViewSetMaxScrollDistance_001, TestSize.Level0) +{ + if (scrollView_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + const uint16_t distance = 10; + scrollView_->SetMaxScrollDistance(distance); + EXPECT_EQ(scrollView_->GetMaxScrollDistance(), distance); +} + +/** + * @tc.name: UIScrollViewSetDragACCLevel_001 + * @tc.desc: Verify SetDragACCLevel function, equal. + * @tc.type: FUNC + * @tc.require: SR000E2RJJ + */ +HWTEST_F(ScrollViewTest, UIScrollViewSetDragACCLevel_001, TestSize.Level0) +{ + if (scrollView_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + const uint16_t value = 10; + scrollView_->SetDragACCLevel(value); + EXPECT_EQ(scrollView_->GetDragACCLevel(), value); +} + +/** + * @tc.name: UIScrollViewSetSwipeACCLevel_001 + * @tc.desc: Verify SetSwipeACCLevel function, equal. + * @tc.type: FUNC + * @tc.require: AR000F4E5P + */ +HWTEST_F(ScrollViewTest, UIScrollViewSetSwipeACCLevel_001, TestSize.Level0) +{ + if (scrollView_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + const uint16_t value = 10; + scrollView_->SetSwipeACCLevel(value); + EXPECT_EQ(scrollView_->GetSwipeACCLevel(), value); +} + +/** + * @tc.name: UIScrollViewMoveChildByOffset_001 + * @tc.desc: Verify MoveChildByOffset function, equal. + * @tc.type: FUNC + * @tc.require: SR000F3PEM + */ +HWTEST_F(ScrollViewTest, UIScrollViewMoveChildByOffset_001, TestSize.Level0) +{ + if (scrollView_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + UIView* view = new UIView(); + EXPECT_EQ(view->GetX(), 0); + EXPECT_EQ(view->GetY(), 0); + + scrollView_->Add(view); + + const int16_t offsetX = 10; + const int16_t offsetY = 20; + scrollView_->SetThrowDrag(true); + scrollView_->MoveChildByOffset(offsetX, offsetY); + EXPECT_EQ(view->GetX(), offsetX); + EXPECT_EQ(view->GetY(), offsetY); + + scrollView_->Remove(view); + delete view; +} +} // namespace OHOS diff --git a/test/unittest/components/ui_slider_unit_test.cpp b/test/unittest/components/ui_slider_unit_test.cpp new file mode 100755 index 0000000..1c3c153 --- /dev/null +++ b/test/unittest/components/ui_slider_unit_test.cpp @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "components/ui_slider.h" +#include +#include + +using namespace testing::ext; +namespace OHOS { +class UISliderTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + static UISlider* slider_; +}; + +UISlider* UISliderTest::slider_ = nullptr; + +void UISliderTest::SetUpTestCase(void) +{ + if (slider_ == nullptr) { + slider_ = new UISlider(); + } +} + +void UISliderTest::TearDownTestCase(void) +{ + if (slider_ != nullptr) { + delete slider_; + slider_ = nullptr; + } +} + +/** + * @tc.name:UISliderGetViewType_001 + * @tc.desc: Verify GetViewType function, equal. + * @tc.type: FUNC + * @tc.require: NA + */ +HWTEST_F(UISliderTest, UISliderGetViewType_001, TestSize.Level0) +{ + if (slider_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + EXPECT_EQ(slider_->GetViewType(), UI_SLIDER); +} + +/** + * @tc.name:UISliderSetKnobWidth_001 + * @tc.desc: Verify SetKnobWidth function, equal. + * @tc.type: FUNC + * @tc.require: NA + */ +HWTEST_F(UISliderTest, UISliderSetKnobWidth_001, TestSize.Level0) +{ + if (slider_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + const int16_t width = 10; + + slider_->SetKnobWidth(width); + EXPECT_EQ(slider_->GetKnobWidth(), width); +} + +/** + * @tc.name:UISliderSetKnobStyle_001 + * @tc.desc: Verify SetKnobStyle function, equal. + * @tc.type: FUNC + * @tc.require: NA + */ +HWTEST_F(UISliderTest, UISliderSetKnobStyle_001, TestSize.Level0) +{ + if (slider_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + slider_->SetKnobStyle(STYLE_BACKGROUND_COLOR, Color::Gray().full); + EXPECT_EQ(slider_->GetKnobStyle().bgColor_.full, Color::Gray().full); +} +} // namespace OHOS \ No newline at end of file diff --git a/test/unittest/components/ui_surface_view_unit_test.cpp b/test/unittest/components/ui_surface_view_unit_test.cpp new file mode 100755 index 0000000..5640027 --- /dev/null +++ b/test/unittest/components/ui_surface_view_unit_test.cpp @@ -0,0 +1,183 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "components/ui_surface_view.h" + +#include +#include + +using namespace testing::ext; +namespace OHOS { +namespace { + const int16_t INIT_POS_X = 10; + const int16_t INIT_POS_Y = 20; + const int16_t INIT_WIDTH = 50; + const int16_t INIT_HEIGHT = 30; +} + +class UISurfaceViewTest : public testing::Test { +public: + UISurfaceViewTest() : surface_(nullptr) {} + virtual ~UISurfaceViewTest() {} + static void SetUpTestCase(); + static void TearDownTestCase(); + void SetUp(); + void TearDown(); + UISurfaceView* surface_; +}; + +void UISurfaceViewTest::SetUpTestCase() +{ +} + +void UISurfaceViewTest::TearDownTestCase() +{ +} + +void UISurfaceViewTest::SetUp() +{ + if (surface_ == nullptr) { + surface_ = new UISurfaceView(); + } +} + +void UISurfaceViewTest::TearDown() +{ + if (surface_ != nullptr) { + delete surface_; + surface_ = nullptr; + } +} +/** + * @tc.name: UISurfaceViewSetPosition_001 + * @tc.desc: Verify SetPosition function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQJ + */ +HWTEST_F(UISurfaceViewTest, UISurfaceViewSetPosition_001, TestSize.Level0) +{ + if (surface_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + surface_->SetPosition(INIT_POS_X, INIT_POS_Y); + EXPECT_EQ(surface_->GetX(), INIT_POS_X); + EXPECT_EQ(surface_->GetY(), INIT_POS_Y); + + surface_->SetPosition(INIT_POS_X + 1, INIT_POS_Y + 1, INIT_WIDTH, INIT_HEIGHT); + EXPECT_EQ(surface_->GetX(), INIT_POS_X + 1); + EXPECT_EQ(surface_->GetY(), INIT_POS_Y + 1); + EXPECT_EQ(surface_->GetWidth(), INIT_WIDTH); + EXPECT_EQ(surface_->GetHeight(), INIT_HEIGHT); +} + +/** + * @tc.name: UISurfaceViewResize_001 + * @tc.desc: Verify Resize function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQJ + */ +HWTEST_F(UISurfaceViewTest, UISurfaceViewResize_001, TestSize.Level0) +{ + if (surface_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + surface_->Resize(INIT_WIDTH, INIT_HEIGHT); + EXPECT_EQ(surface_->GetWidth(), INIT_WIDTH); + EXPECT_EQ(surface_->GetHeight(), INIT_HEIGHT); +} + +/** + * @tc.name: UISurfaceViewSetX_001 + * @tc.desc: Verify SetX function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQJ + */ +HWTEST_F(UISurfaceViewTest, UISurfaceViewSetX_001, TestSize.Level0) +{ + if (surface_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + surface_->SetX(INIT_POS_X); + EXPECT_EQ(surface_->GetX(), INIT_POS_X); +} + +/** + * @tc.name: UISurfaceViewSetY_001 + * @tc.desc: Verify SetY function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQJ + */ +HWTEST_F(UISurfaceViewTest, UISurfaceViewSetY_001, TestSize.Level0) +{ + if (surface_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + surface_->SetY(INIT_POS_Y); + EXPECT_EQ(surface_->GetY(), INIT_POS_Y); +} + +/** + * @tc.name: UISurfaceViewSetWidth_001 + * @tc.desc: Verify SetWidth function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQJ + */ +HWTEST_F(UISurfaceViewTest, UISurfaceViewSetWidth_001, TestSize.Level0) +{ + if (surface_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + surface_->SetWidth(INIT_WIDTH); + EXPECT_EQ(surface_->GetWidth(), INIT_WIDTH); +} + +/** + * @tc.name: UISurfaceViewSetHeight_001 + * @tc.desc: Verify SetHeight function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQJ + */ +HWTEST_F(UISurfaceViewTest, UISurfaceViewSetHeight_001, TestSize.Level0) +{ + if (surface_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + surface_->SetHeight(INIT_HEIGHT); + EXPECT_EQ(surface_->GetHeight(), INIT_HEIGHT); +} + +/** + * @tc.name: UISurfaceViewSetVisible_001 + * @tc.desc: Verify SetVisible function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQJ + */ +HWTEST_F(UISurfaceViewTest, UISurfaceViewSetVisible_001, TestSize.Level0) +{ + if (surface_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + bool visible = surface_->IsVisible(); + surface_->SetVisible(!visible); + EXPECT_EQ(surface_->IsVisible(), !visible); +} +} // namespace OHOS \ No newline at end of file diff --git a/test/unittest/components/ui_swipe_view_unit_test.cpp b/test/unittest/components/ui_swipe_view_unit_test.cpp new file mode 100755 index 0000000..3318e8c --- /dev/null +++ b/test/unittest/components/ui_swipe_view_unit_test.cpp @@ -0,0 +1,316 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "components/ui_swipe_view.h" + +#include +#include +#include "common/screen.h" + +using namespace testing::ext; +namespace OHOS { +namespace { + constexpr uint8_t HORIZONTAL = 0; + constexpr uint8_t VERTICAL = 1; + const uint16_t DEFAULT_WIDTH = 100; +} +class UISwipeViewTest : public testing::Test { +public: + UISwipeViewTest() : swipeView_(nullptr) {} + virtual ~UISwipeViewTest() {} + static void SetUpTestCase() {} + static void TearDownTestCase() {} + void SetUp(); + void TearDown(); + UISwipeView* swipeView_; +}; + +void UISwipeViewTest::SetUp() +{ + if (swipeView_ == nullptr) { + swipeView_ = new UISwipeView(); + } +} + +void UISwipeViewTest::TearDown() +{ + if (swipeView_ != nullptr) { + delete swipeView_; + swipeView_ = nullptr; + } +} +/** + * @tc.name: UISwipeViewGetViewType_001 + * @tc.desc: Verify GetViewType function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQF + */ +HWTEST_F(UISwipeViewTest, UISwipeViewGetViewType_001, TestSize.Level0) +{ + if (swipeView_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + EXPECT_EQ(swipeView_->GetViewType(), UI_SWIPE_VIEW); +} + +/** + * @tc.name: UISwipeViewSetDirection_001 + * @tc.desc: Verify SetDirection function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQF + */ +HWTEST_F(UISwipeViewTest, UISwipeViewSetDirection_001, TestSize.Level0) +{ + if (swipeView_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + swipeView_->SetDirection(HORIZONTAL); + EXPECT_EQ(swipeView_->GetDirection(), HORIZONTAL); + + swipeView_->SetDirection(VERTICAL); + EXPECT_EQ(swipeView_->GetDirection(), VERTICAL); +} + +/** + * @tc.name: UISwipeViewAdd_001 + * @tc.desc: Verify Add function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQF + */ +HWTEST_F(UISwipeViewTest, UISwipeViewAdd_001, TestSize.Level0) +{ + if (swipeView_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + UIView* view = new UIView(); + if (view == nullptr) { + EXPECT_NE(0, 0); + return; + } + swipeView_->Add(view); + EXPECT_EQ(view, swipeView_->GetChildrenHead()); + EXPECT_EQ(view->GetParent(), swipeView_); + swipeView_->Remove(view); + delete view; +} + +/** + * @tc.name: UISwipeViewInsert_001 + * @tc.desc: Verify Insert function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQF + */ +HWTEST_F(UISwipeViewTest, UISwipeViewInsert_001, TestSize.Level0) +{ + if (swipeView_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + UIView* preView = new UIView(); + if (preView == nullptr) { + EXPECT_NE(0, 0); + return; + } + UIView* view = new UIView(); + if (view == nullptr) { + delete preView; + EXPECT_NE(0, 0); + return; + } + swipeView_->Add(preView); + swipeView_->Insert(preView, view); + EXPECT_EQ(view, preView->GetNextSibling()); + + swipeView_->Remove(preView); + swipeView_->Remove(view); + delete view; + delete preView; +} + +/** + * @tc.name: UISwipeViewRemove_001 + * @tc.desc: Verify Remove function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQF + */ +HWTEST_F(UISwipeViewTest, UISwipeViewRemove_001, TestSize.Level0) +{ + if (swipeView_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + UIView* view = new UIView(); + if (view == nullptr) { + EXPECT_NE(0, 0); + return; + } + swipeView_->Add(view); + swipeView_->Remove(view); + EXPECT_EQ(nullptr, swipeView_->GetChildrenHead()); + EXPECT_EQ(nullptr, view->GetParent()); + + delete view; +} + +/** + * @tc.name: UISwipeViewSetCurrentPage_001 + * @tc.desc: Verify SetCurrentPage function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQF + */ +HWTEST_F(UISwipeViewTest, UISwipeViewSetCurrentPage_001, TestSize.Level0) +{ + if (swipeView_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + const int16_t initPosX = 10; + const int16_t initPosY = 20; + const int16_t initWidth = 50; + const int16_t initHeight = 30; + const uint16_t size = 100; + const uint16_t time = 100; + + swipeView_->SetPosition(initPosX, initPosY, initWidth, initHeight); + swipeView_->SetLoopState(true); + swipeView_->SetAnimatorTime(time); + swipeView_->SetBlankSize(size); + UIViewGroup* view = new UIViewGroup(); + if (view == nullptr) { + EXPECT_NE(0, 0); + return; + } + UIViewGroup* view2 = new UIViewGroup(); + if (view2 == nullptr) { + delete view; + EXPECT_NE(0, 0); + return; + } + + view->SetPosition(initPosX, initPosY, initWidth, initHeight); + swipeView_->Add(view); + view2->SetPosition(initPosX, initPosY, initWidth, initHeight); + swipeView_->Add(view2); + uint16_t index = 1; + swipeView_->SetCurrentPage(1); + EXPECT_EQ(swipeView_->GetCurrentPage(), 1); + + swipeView_->Remove(view); + swipeView_->Remove(view2); + delete view; + delete view2; +} + +class OnTestSwipeListener : public UISwipeView::OnSwipeListener { +public: + OnTestSwipeListener() {} + virtual ~OnTestSwipeListener() {} + virtual void OnSwipe(UISwipeView& view) override + { + view.SetWidth(DEFAULT_WIDTH); + return; + } +}; + +class TestUISwipeView : public UISwipeView { +public: + void TriggerStopAnimator() + { + StopAnimator(); + } +}; + +/** + * @tc.name: UISwipeViewSetOnSwipeListener_001 + * @tc.desc: Verify SetOnSwipeListener function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQF + */ +HWTEST_F(UISwipeViewTest, UISwipeViewSetOnSwipeListener_001, TestSize.Level0) +{ + if (swipeView_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + auto testSwipeLis = new OnTestSwipeListener(); + + EXPECT_EQ(swipeView_->GetOnSwipeListener(), nullptr); + swipeView_->SetOnSwipeListener(testSwipeLis); + EXPECT_EQ(swipeView_->GetOnSwipeListener(), testSwipeLis); + + EXPECT_NE(swipeView_->GetWidth(), DEFAULT_WIDTH); + static_cast(swipeView_)->TriggerStopAnimator(); + EXPECT_EQ(swipeView_->GetWidth(), DEFAULT_WIDTH); + delete testSwipeLis; +} + +/** + * @tc.name: UISwipeViewGetViewByIndex_001 + * @tc.desc: Verify GetViewByIndex function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQF + */ +HWTEST_F(UISwipeViewTest, UISwipeViewGetViewByIndex_001, TestSize.Level0) +{ + if (swipeView_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + swipeView_->SetDirection(HORIZONTAL); + const int16_t initWidth = 50; + const int16_t initHeight = 30; + + swipeView_->SetStyle(STYLE_BACKGROUND_COLOR, Color::Red().full); + swipeView_->SetPosition(0, 0, Screen::GetInstance().GetWidth(), Screen::GetInstance().GetHeight()); + swipeView_->SetLoopState(true); + UIView* view1 = new UIView(); + view1->SetPosition(0, 0, initWidth, initHeight); + swipeView_->Add(view1); + UIView* view2 = new UIView(); + view2->SetPosition(0, 0, initWidth, initHeight); + swipeView_->Add(view2); + UIView* view3 = new UIView(); + view3->SetPosition(0, 0, initWidth, initHeight); + swipeView_->Add(view3); + + UIView* view = swipeView_->GetViewByIndex(1); + EXPECT_EQ(view2, view); + swipeView_->Remove(view1); + swipeView_->Remove(view2); + swipeView_->Remove(view3); + delete view1; + delete view2; + delete view3; +} + +/** + * @tc.name: Graphic_UISwipeView_Test_SetAlignMode_001 + * @tc.desc: Verify SetAlignMode function, equal. + * @tc.type: FUNC + * @tc.require: AR000EVTV4 + */ +HWTEST_F(UISwipeViewTest, Graphic_UISwipeView_Test_SetAlignMode_001, TestSize.Level0) +{ + if (swipeView_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + swipeView_->SetAlignMode(UISwipeView::ALIGN_LEFT); + EXPECT_EQ(swipeView_->GetAlignMode(), UISwipeView::ALIGN_LEFT); +} +} // namespace OHOS \ No newline at end of file diff --git a/test/unittest/components/ui_time_picker_unit_test.cpp b/test/unittest/components/ui_time_picker_unit_test.cpp new file mode 100755 index 0000000..c75c9f5 --- /dev/null +++ b/test/unittest/components/ui_time_picker_unit_test.cpp @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "components/ui_time_picker.h" +#include +#include + +using namespace testing::ext; +namespace OHOS { +class UITimePickerTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + static UITimePicker* timePicker_; +}; + +UITimePicker* UITimePickerTest::timePicker_ = nullptr; + +void UITimePickerTest::SetUpTestCase(void) +{ + if (timePicker_ == nullptr) { + timePicker_ = new UITimePicker(); + } +} + +void UITimePickerTest::TearDownTestCase(void) +{ + if (timePicker_ != nullptr) { + delete timePicker_; + timePicker_ = nullptr; + } +} +/** + * @tc.name: UITimePickerGetViewType_001 + * @tc.desc: Verify GetViewType function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQ7 + */ +HWTEST_F(UITimePickerTest, UITimePickerGetViewType_001, TestSize.Level0) +{ + if (timePicker_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + EXPECT_EQ(timePicker_->GetViewType(), UI_TIME_PICKER); +} + +/** + * @tc.name: UITimePickerSetWidth_001 + * @tc.desc: Verify SetWidth function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQ7 + */ +HWTEST_F(UITimePickerTest, UITimePickerSetWidth_001, TestSize.Level0) +{ + if (timePicker_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + const int16_t width = 50; + + timePicker_->SetWidth(width); + EXPECT_EQ(timePicker_->GetWidth(), width); +} + +/** + * @tc.name: UITimePickerSetHeight_001 + * @tc.desc: Verify SetHeight function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQ7 + */ +HWTEST_F(UITimePickerTest, UITimePickerSetHeight_001, TestSize.Level0) +{ + if (timePicker_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + const int16_t height = 50; + + timePicker_->SetHeight(height); + EXPECT_EQ(timePicker_->GetHeight(), height); +} +} // namespace OHOS \ No newline at end of file diff --git a/test/unittest/components/ui_toggle_button_unit_test.cpp b/test/unittest/components/ui_toggle_button_unit_test.cpp new file mode 100755 index 0000000..d08f2f6 --- /dev/null +++ b/test/unittest/components/ui_toggle_button_unit_test.cpp @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "components/ui_toggle_button.h" + +#include +#include + +using namespace testing::ext; +namespace OHOS { +class UIToggleButtonTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + static UIToggleButton* toggleBtn_; +}; + +UIToggleButton* UIToggleButtonTest::toggleBtn_ = nullptr; + +void UIToggleButtonTest::SetUpTestCase(void) +{ + if (toggleBtn_ == nullptr) { + toggleBtn_ = new UIToggleButton(); + } +} + +void UIToggleButtonTest::TearDownTestCase(void) +{ + if (toggleBtn_ != nullptr) { + delete toggleBtn_; + toggleBtn_ = nullptr; + } +} + +/** + * @tc.name: UIToggleButtonGetViewType_001 + * @tc.desc: Verify GetViewType function. + * @tc.type: FUNC + * @tc.require: AR000DSMQA + */ +HWTEST_F(UIToggleButtonTest, UIToggleButtonGetViewType_001, TestSize.Level0) +{ + if (toggleBtn_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + EXPECT_EQ(toggleBtn_->GetViewType(), UI_TOGGLE_BUTTON); +} + +/** + * @tc.name: UIToggleButtonSetState_001 + * @tc.desc: Verify SetState function. + * @tc.type: FUNC + * @tc.require: AR000F4E5H + */ +HWTEST_F(UIToggleButtonTest, UIToggleButtonSetState_001, TestSize.Level0) +{ + if (toggleBtn_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + toggleBtn_->SetState(true); + EXPECT_EQ(toggleBtn_->GetState(), true); + + toggleBtn_->SetState(false); + EXPECT_EQ(toggleBtn_->GetState(), false); +} +} // namespace OHOS \ No newline at end of file diff --git a/test/unittest/components/ui_video_unit_test.cpp b/test/unittest/components/ui_video_unit_test.cpp new file mode 100755 index 0000000..3dfd423 --- /dev/null +++ b/test/unittest/components/ui_video_unit_test.cpp @@ -0,0 +1,457 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "components/ui_video.h" + +#include +#include + +using namespace testing::ext; +namespace OHOS { +namespace { + const int8_t STATE_PLAY = 1; + const int8_t STATE_PAUSE = 2; + const int8_t STATE_STOP = 3; + const int8_t STATE_COMPLETE = 4; + const int8_t STATE_REWIND = 5; + const int8_t STATE_ERROR = 6; + const int8_t STATE_INFO = 7; + const int8_t STATE_RESIZE = 8; +} +static int8_t g_state = 0; + +class TestVideoListener : public UIVideo::VideoPlayerListener { +public: + TestVideoListener() {} + ~TestVideoListener() {} + + void OnRewindToComplete() override + { + EXPECT_EQ(g_state, STATE_REWIND); + } + + void OnPlaybackPause() override + { + EXPECT_EQ(g_state, STATE_PAUSE); + } + + void OnPlaybackPlay() override + { + EXPECT_EQ(g_state, STATE_PLAY); + } + + void OnPlaybackStop() override + { + EXPECT_EQ(g_state, STATE_STOP); + } + + void OnPlaybackComplete() override + { + EXPECT_EQ(g_state, STATE_ERROR); + } + + void OnError(int32_t errorType, int32_t errorCode) override + { + EXPECT_EQ(g_state, STATE_COMPLETE); + } + + void OnInfo(int type, int extra) override + { + EXPECT_EQ(g_state, STATE_INFO); + } + + void OnVideoSizeChanged(int width, int height) override + { + EXPECT_EQ(g_state, STATE_RESIZE); + } +}; + +class UIVideoTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + static UIVideo* video_; + static TestVideoListener* listener_; + const char* videoPath_ = "/user/data/video.mp4"; +}; + +UIVideo* UIVideoTest::video_ = nullptr; +TestVideoListener* UIVideoTest::listener_ = nullptr; + +void UIVideoTest::SetUpTestCase(void) +{ + if (listener_ == nullptr) { + listener_ = new TestVideoListener(); + } +} + +void UIVideoTest::TearDownTestCase(void) +{ + if (video_ != nullptr) { + delete video_; + video_ = nullptr; + } + if (listener_ != nullptr) { + delete listener_; + listener_ = nullptr; + } +} + +/** + * @tc.name: UIVideoSetSrc_001 + * @tc.desc: Verify SetSrc function, abnormal situation. + * @tc.type: FUNC + * @tc.require: SR000F3PEN + */ +HWTEST_F(UIVideoTest, UIVideoSetSrc_001, TestSize.Level0) +{ + if (video_ == nullptr) { + video_ = new UIVideo(); + } + + const char* src = nullptr; + EXPECT_EQ(video_->SetSrc(src), false); + + src = "not exit path"; + EXPECT_EQ(video_->SetSrc(src), false); + EXPECT_EQ(video_->GetSrc(), src); + + video_->Reset(); + delete video_; + video_ = nullptr; +} + +/** + * @tc.name: VideoSetSrc_002 + * @tc.desc: Verify SetSrc function, normal situation. + * @tc.type: FUNC + * @tc.require: AR000F4E5Q + */ +HWTEST_F(UIVideoTest, UIVideoSetSrc_002, TestSize.Level0) +{ + if (video_ == nullptr) { + video_ = new UIVideo(); + } + + if (video_->SetSrc(videoPath_)) { + EXPECT_EQ(video_->GetSrc(), videoPath_); + } + video_->Reset(); + delete video_; + video_ = nullptr; +} + +/** + * @tc.name: VideoPrepare_001 + * @tc.desc: Verify Prepare function, abnormal situation. + * @tc.type: FUNC + * @tc.require: AR000F4E5Q + */ +HWTEST_F(UIVideoTest, UIVideoPrepare_001, TestSize.Level0) +{ + if (video_ == nullptr) { + video_ = new UIVideo(); + } + + video_->SetSrc(nullptr); + EXPECT_EQ(video_->Prepare(), false); + video_->SetSrc("not exist path"); + EXPECT_EQ(video_->Prepare(), false); + + video_->Reset(); + delete video_; + video_ = nullptr; +} + +/** + * @tc.name: VideoPrepare_002 + * @tc.desc: Verify Prepare function, normal situation. + * @tc.type: FUNC + * @tc.require: AR000F4E5Q + */ +HWTEST_F(UIVideoTest, UIVideoPrepare_002, TestSize.Level0) +{ + if (video_ == nullptr) { + video_ = new UIVideo(); + } + + if (!video_->SetSrc(videoPath_)) { + return; + } + if (listener_ != nullptr) { + video_->SetVideoPlayerListener(listener_); + } + + g_state = STATE_RESIZE; + EXPECT_EQ(video_->Prepare(), true); + + video_->Reset(); + delete video_; + video_ = nullptr; +} + +/** + * @tc.name: VideoPlay_001 + * @tc.desc: Verify Play function, abnormal situation. + * @tc.type: FUNC + * @tc.require: AR000F4E5Q + */ +HWTEST_F(UIVideoTest, UIVideoPlay_001, TestSize.Level0) +{ + if (video_ == nullptr) { + video_ = new UIVideo(); + } + + video_->SetSrc("not exist path"); + video_->Prepare(); + EXPECT_EQ(video_->Play(), false); + + video_->Reset(); + delete video_; + video_ = nullptr; +} + +/** + * @tc.name: VideoPlay_002 + * @tc.desc: Verify Play function, normal situation. + * @tc.type: FUNC + * @tc.require: AR000F4E5Q + */ +HWTEST_F(UIVideoTest, UIVideoPlay_002, TestSize.Level0) +{ + if (video_ == nullptr) { + video_ = new UIVideo(); + } + + if (!video_->SetSrc(videoPath_)) { + return; + } + if (listener_ != nullptr) { + video_->SetVideoPlayerListener(listener_); + } + + g_state = STATE_RESIZE; + video_->Prepare(); + + g_state = STATE_PLAY; + EXPECT_EQ(video_->Play(), true); + + g_state = STATE_PAUSE; + video_->Pause(); + + g_state = STATE_PLAY; + EXPECT_EQ(video_->Play(), true); + g_state = STATE_STOP; + video_->Stop(); + g_state = STATE_PLAY; + EXPECT_EQ(video_->Play(), false); + + video_->Reset(); + delete video_; + video_ = nullptr; +} + +/** + * @tc.name: VideoIsPlaying_001 + * @tc.desc: Verify IsPlaying function, equal. + * @tc.type: FUNC + * @tc.require: AR000F4E5Q + */ +HWTEST_F(UIVideoTest, UIVideoIsPlaying_001, TestSize.Level0) +{ + if (video_ == nullptr) { + video_ = new UIVideo(); + } + + if (!video_->SetSrc(videoPath_)) { + return; + } + video_->Prepare(); + EXPECT_EQ(video_->IsPlaying(), false); + + video_->Play(); + EXPECT_EQ(video_->IsPlaying(), true); + + video_->Reset(); + delete video_; + video_ = nullptr; +} + +/** + * @tc.name: VideoIsSingleLooping_001 + * @tc.desc: Verify IsSingleLooping function, equal. + * @tc.type: FUNC + * @tc.require: AR000F4E5Q + */ +HWTEST_F(UIVideoTest, UIVideoIsSingleLooping_001, TestSize.Level0) +{ + if (video_ == nullptr) { + video_ = new UIVideo(); + } + + if (!video_->SetSrc(videoPath_)) { + return; + } + video_->Prepare(); + video_->Play(); + EXPECT_EQ(video_->IsSingleLooping(), false); + video_->EnableSingleLooping(true); + EXPECT_EQ(video_->IsSingleLooping(), true); + + video_->EnableSingleLooping(false); + EXPECT_EQ(video_->IsSingleLooping(), false); + video_->Reset(); + delete video_; + video_ = nullptr; +} + +/** + * @tc.name: VideoRewind_001 + * @tc.desc: Verify Rewind function, equal. + * @tc.type: FUNC + * @tc.require: AR000F4E5Q + */ +HWTEST_F(UIVideoTest, UIVideoRewind_001, TestSize.Level0) +{ + if (video_ == nullptr) { + video_ = new UIVideo(); + } + + if (!video_->SetSrc(videoPath_)) { + return; + } + if (listener_ != nullptr) { + video_->SetVideoPlayerListener(listener_); + } + + g_state = STATE_RESIZE; + video_->Prepare(); + g_state = STATE_PLAY; + video_->Play(); + int64_t currentTime = 0; + video_->GetCurrentTime(currentTime); + int64_t rewindTime = currentTime + 3000; // 3000:rewind milli second + + g_state = STATE_REWIND; + video_->Rewind(rewindTime); + video_->GetCurrentTime(currentTime); + EXPECT_EQ(currentTime, rewindTime); + + video_->Reset(); + delete video_; + video_ = nullptr; +} + +/** + * @tc.name: VideoPause_001 + * @tc.desc: Verify Pause function, equal. + * @tc.type: FUNC + * @tc.require: AR000F4E5Q + */ +HWTEST_F(UIVideoTest, UIVideoPause_001, TestSize.Level0) +{ + if (video_ == nullptr) { + video_ = new UIVideo(); + } + + if (!video_->SetSrc(videoPath_)) { + return; + } + if (listener_ != nullptr) { + video_->SetVideoPlayerListener(listener_); + } + + g_state = STATE_RESIZE; + video_->Prepare(); + g_state = STATE_PLAY; + video_->Play(); + + g_state = STATE_PAUSE; + EXPECT_EQ(video_->Pause(), true); + + video_->Reset(); + EXPECT_EQ(video_->Pause(), false); + + video_->Reset(); + delete video_; + video_ = nullptr; +} + +/** + * @tc.name: VideoStop_001 + * @tc.desc: Verify Stop function, equal. + * @tc.type: FUNC + * @tc.require: AR000F4E5Q + */ +HWTEST_F(UIVideoTest, UIVideoStop_001, TestSize.Level0) +{ + if (video_ == nullptr) { + video_ = new UIVideo(); + } + + if (!video_->SetSrc(videoPath_)) { + return; + } + if (listener_ != nullptr) { + video_->SetVideoPlayerListener(listener_); + } + + g_state = STATE_RESIZE; + video_->Prepare(); + g_state = STATE_PLAY; + video_->Play(); + + g_state = STATE_STOP; + EXPECT_EQ(video_->Stop(), true); + + video_->Reset(); + EXPECT_EQ(video_->Stop(), false); + + video_->Reset(); + delete video_; + video_ = nullptr; +} + +/** + * @tc.name: VideoReset_001 + * @tc.desc: Verify Reset function, equal. + * @tc.type: FUNC + * @tc.require: AR000F4E5Q + */ +HWTEST_F(UIVideoTest, UIVideoReset_001, TestSize.Level0) +{ + if (video_ == nullptr) { + video_ = new UIVideo(); + } + + if (!video_->SetSrc(videoPath_)) { + return; + } + if (listener_ != nullptr) { + video_->SetVideoPlayerListener(listener_); + } + + g_state = STATE_RESIZE; + video_->Prepare(); + g_state = STATE_PLAY; + video_->Play(); + + EXPECT_EQ(video_->Reset(), true); + + video_->Reset(); + delete video_; + video_ = nullptr; +} +} // namespace OHOS \ No newline at end of file diff --git a/test/unittest/components/ui_view_group_unit_test.cpp b/test/unittest/components/ui_view_group_unit_test.cpp new file mode 100755 index 0000000..05fc896 --- /dev/null +++ b/test/unittest/components/ui_view_group_unit_test.cpp @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "components/ui_view_group.h" +#include +#include + +using namespace testing::ext; +namespace OHOS { +class UIViewGroupTest : public testing::Test { +public: + UIViewGroupTest() : viewGroup_(nullptr) {} + ~UIViewGroupTest() {} + static void SetUpTestCase(void) {} + static void TearDownTestCase(void) {} + void SetUp(void); + void TearDown(void); + UIViewGroup* viewGroup_; +}; + +void UIViewGroupTest::SetUp(void) +{ + if (viewGroup_ == nullptr) { + viewGroup_ = new UIViewGroup(); + } +} + +void UIViewGroupTest::TearDown(void) +{ + if (viewGroup_ != nullptr) { + delete viewGroup_; + viewGroup_ = nullptr; + } +} + +/** + * @tc.name: UIViewGroupAdd_001 + * @tc.desc: Verify Add function. + * @tc.type: FUNC + * @tc.require: AR000EEMQF + */ +HWTEST_F(UIViewGroupTest, UIViewGroupAdd_001, TestSize.Level0) +{ + if (viewGroup_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + UIView* view = new UIView(); + if (view == nullptr) { + EXPECT_EQ(1, 0); + return; + } + viewGroup_->Add(view); + EXPECT_EQ(view, viewGroup_->GetChildrenHead()); + EXPECT_EQ(view->GetParent(), viewGroup_); + + delete view; +} + +/** + * @tc.name: UIViewGroupInsert_001 + * @tc.desc: Verify Insert function. + * @tc.type: FUNC + * @tc.require: AR000EEMQF + */ +HWTEST_F(UIViewGroupTest, UIViewGroupInsert_001, TestSize.Level0) +{ + if (viewGroup_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + UIView* preView = new UIView(); + if (preView == nullptr) { + EXPECT_EQ(1, 0); + return; + } + UIView* view = new UIView(); + + viewGroup_->Add(preView); + viewGroup_->Insert(preView, view); + EXPECT_EQ(view, preView->GetNextSibling()); + + delete preView; + delete view; +} + +/** + * @tc.name: UIViewGroupRemove_001 + * @tc.desc: Verify Remove function. + * @tc.type: FUNC + * @tc.require: AR000EEMQF + */ +HWTEST_F(UIViewGroupTest, UIViewGroupRemove_001, TestSize.Level0) +{ + if (viewGroup_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + UIView* view = new UIView(); + if (view == nullptr) { + EXPECT_EQ(1, 0); + return; + } + viewGroup_->Add(view); + viewGroup_->Remove(view); + EXPECT_EQ(nullptr, viewGroup_->GetChildrenHead()); + EXPECT_EQ(nullptr, view->GetParent()); + + delete view; +} + +/** + * @tc.name: UIViewGroupRemoveAll_001 + * @tc.desc: Verify RemoveAll function. + * @tc.type: FUNC + * @tc.require: AR000EEMQF + */ +HWTEST_F(UIViewGroupTest, UIViewGroupRemoveAll_001, TestSize.Level0) +{ + if (viewGroup_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + UIView* view = new UIView(); + UIView* view2 = new UIView(); + + viewGroup_->Add(view); + viewGroup_->RemoveAll(); + EXPECT_EQ(nullptr, viewGroup_->GetChildrenHead()); + + delete view; + delete view2; +} + +/** + * @tc.name: UIViewGroupGetViewType_001 + * @tc.desc: Verify GetViewType function. + * @tc.type: FUNC + * @tc.require: AR000EEMQF + */ +HWTEST_F(UIViewGroupTest, UIViewGroupGetViewType_001, TestSize.Level0) +{ + if (viewGroup_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + EXPECT_EQ(viewGroup_->GetViewType(), UI_VIEW_GROUP); +} +} // namespace OHOS \ No newline at end of file diff --git a/test/unittest/components/ui_view_unit_test.cpp b/test/unittest/components/ui_view_unit_test.cpp new file mode 100755 index 0000000..ac10310 --- /dev/null +++ b/test/unittest/components/ui_view_unit_test.cpp @@ -0,0 +1,1040 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "components/ui_view_group.h" + +#include +#include + +using namespace testing::ext; +namespace OHOS { +namespace { + const int16_t POS_X = 10; + const int16_t POS_Y = 20; + const int16_t DEFAULE_WIDTH = 100; + const int16_t DEFAULE_HEIGHT = 100; + const int16_t BUTTON_WIDTH = 20; + const int16_t BUTTON_HEIGHT = 20; + const float POS_X_PERCENT = 0.1; + const float POS_Y_PERCENT = 0.2; + const float DEFAULT_WIDTH_PERCENT = 0.3; + const float DEFAULT_HEIGHT_PERCENT = 0.4; + const float INVAILD_PERCENT_ZERO = 0.0f; + const float INVAILD_PERCENT_ONE = 1.0f; + const int16_t DEFAULE_ANGLE = 45; + const Vector2 VIEW_CENTER = {50, 50}; + const Vector2 SCALE_VALUE = {0.5f, 0.5f}; + const Vector2 POLYGON1 = {0, 0}; + const Vector2 POLYGON2 = {70, 70}; + const Vector2 POLYGON3 = {0, 140}; + const Vector2 POLYGON4 = {-70, 70}; +} + +class UIViewTest : public testing::Test { +public: + UIViewTest() : view_(nullptr) {} + virtual ~UIViewTest() {} + static void SetUpTestCase() {} + static void TearDownTestCase() {} + void SetUp(); + void TearDown(); + UIView* view_; +}; + +void UIViewTest::SetUp() +{ + if (view_ == nullptr) { + view_ = new UIView(); + } +} + +void UIViewTest::TearDown() +{ + if (view_ != nullptr) { + delete view_; + view_ = nullptr; + } +} + +/** + * @tc.name: UIViewSetOnDragListener_001 + * @tc.desc: Verify SetOnDragListener function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQF + */ +HWTEST_F(UIViewTest, UIViewSetOnDragListener_001, TestSize.Level0) +{ + if (view_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + UIView::OnDragListener* listener = new UIView::OnDragListener(); + view_->SetOnDragListener(listener); + EXPECT_EQ(view_->GetOnDragListener(), listener); + delete listener; +} + +/** + * @tc.name: UIViewSetOnClickListener_001 + * @tc.desc: Verify SetOnClickListener function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQF + */ +HWTEST_F(UIViewTest, UIViewSetOnClickListener_001, TestSize.Level0) +{ + if (view_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + UIView::OnClickListener* listener = new UIView::OnClickListener(); + view_->SetOnClickListener(listener); + EXPECT_EQ(view_->GetOnClickListener(), listener); + delete listener; +} + +/** + * @tc.name: UIViewSetOnLongPressListener_001 + * @tc.desc: Verify SetOnLongPressListener function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQF + */ +HWTEST_F(UIViewTest, UIViewSetOnLongPressListener_001, TestSize.Level0) +{ + if (view_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + UIView::OnLongPressListener* listener = new UIView::OnLongPressListener(); + view_->SetOnLongPressListener(listener); + EXPECT_EQ(view_->GetOnLongPressListener(), listener); + delete listener; +} + +/** + * @tc.name: UIViewSetOnTouchListener_001 + * @tc.desc: Verify SetOnTouchListener function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQF + */ +HWTEST_F(UIViewTest, UIViewSetOnTouchListener_001, TestSize.Level0) +{ + if (view_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + UIView::OnTouchListener* listener = new UIView::OnTouchListener(); + view_->SetOnTouchListener(listener); + EXPECT_EQ(view_->GetTouchListener(), listener); + delete listener; +} + +/** + * @tc.name: UIViewSetParent_001 + * @tc.desc: Verify SetParent function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQF + */ +HWTEST_F(UIViewTest, UIViewSetParent_001, TestSize.Level0) +{ + if (view_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + UIViewGroup* viewGroup = new UIViewGroup(); + if (viewGroup == nullptr) { + EXPECT_NE(0, 0); + return; + } + view_->SetParent(viewGroup); + EXPECT_EQ(view_->GetParent(), viewGroup); + + delete viewGroup; +} + +/** + * @tc.name: UIViewSetNextSibling_001 + * @tc.desc: Verify SetNextSibling function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQF + */ +HWTEST_F(UIViewTest, UIViewSetNextSibling_001, TestSize.Level0) +{ + if (view_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + UIView* tempView = new UIView(); + if (tempView == nullptr) { + EXPECT_NE(0, 0); + return; + } + view_->SetNextSibling(tempView); + EXPECT_EQ(view_->GetNextSibling(), tempView); + + delete tempView; +} + +/** + * @tc.name: UIViewSetVisible_001 + * @tc.desc: Verify SetVisible function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQF + */ +HWTEST_F(UIViewTest, UIViewSetVisible_001, TestSize.Level0) +{ + if (view_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + view_->SetVisible(true); + EXPECT_EQ(view_->IsVisible(), true); +} + +/** + * @tc.name: UIViewSetTouchable_001 + * @tc.desc: Verify SetTouchable function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQF + */ +HWTEST_F(UIViewTest, UIViewSetTouchable_001, TestSize.Level0) +{ + if (view_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + view_->SetTouchable(true); + EXPECT_EQ(view_->IsTouchable(), true); +} + +/** + * @tc.name: UIViewSetDraggable_001 + * @tc.desc: Verify SetDraggable function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQF + */ +HWTEST_F(UIViewTest, UIViewSetDraggable_001, TestSize.Level0) +{ + if (view_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + view_->SetDraggable(true); + EXPECT_EQ(view_->IsDraggable(), true); +} + +/** + * @tc.name: UIViewSetDragParentInstead_001 + * @tc.desc: Verify SetDragParentInstead function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQF + */ +HWTEST_F(UIViewTest, UIViewSetDragParentInstead_001, TestSize.Level0) +{ + if (view_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + view_->SetDragParentInstead(true); + EXPECT_EQ(view_->IsDragParentInstead(), true); +} + +/** + * @tc.name: UIViewResizeVisibleArea_001 + * @tc.desc: Verify ResizeVisibleArea function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQF + */ +HWTEST_F(UIViewTest, UIViewResizeVisibleArea_001, TestSize.Level0) +{ + if (view_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + view_->ResizeVisibleArea(POS_X, POS_Y, DEFAULE_WIDTH, DEFAULE_HEIGHT); + + EXPECT_EQ(view_->GetVisibleRect().GetWidth(), DEFAULE_WIDTH); + EXPECT_EQ(view_->GetVisibleRect().GetHeight(), DEFAULE_HEIGHT); +} + +/** + * @tc.name: UIViewSetWidth_001 + * @tc.desc: Verify SetWidth function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQF + */ +HWTEST_F(UIViewTest, UIViewSetWidth_001, TestSize.Level0) +{ + if (view_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + view_->SetWidth(0); + EXPECT_EQ(view_->GetWidth(), 0); + view_->SetWidth(DEFAULE_WIDTH); + EXPECT_EQ(view_->GetWidth(), DEFAULE_WIDTH); +} + +/** + * @tc.name: UIViewSetHeight_001 + * @tc.desc: Verify SetHeight function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQF + */ +HWTEST_F(UIViewTest, UIViewSetHeight_001, TestSize.Level0) +{ + if (view_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + view_->SetHeight(0); + EXPECT_EQ(view_->GetHeight(), 0); + view_->SetHeight(DEFAULE_HEIGHT); + EXPECT_EQ(view_->GetHeight(), DEFAULE_HEIGHT); +} + +/** + * @tc.name: UIViewResize_001 + * @tc.desc: Verify Resize function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQF + */ +HWTEST_F(UIViewTest, UIViewResize_001, TestSize.Level0) +{ + if (view_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + view_->Resize(0, 0); + EXPECT_EQ(view_->GetHeight(), 0); + EXPECT_EQ(view_->GetWidth(), 0); + view_->Resize(DEFAULE_WIDTH, DEFAULE_HEIGHT); + EXPECT_EQ(view_->GetHeight(), DEFAULE_HEIGHT); + EXPECT_EQ(view_->GetWidth(), DEFAULE_WIDTH); +} + +/** + * @tc.name: UIViewSetX_001 + * @tc.desc: Verify SetX function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQF + */ +HWTEST_F(UIViewTest, UIViewSetX_001, TestSize.Level0) +{ + if (view_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + view_->SetX(0); + EXPECT_EQ(view_->GetX(), 0); + view_->SetX(POS_X); + EXPECT_EQ(view_->GetX(), POS_X); +} + +/** + * @tc.name: UIViewSetY_001 + * @tc.desc: Verify SetY function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQF + */ +HWTEST_F(UIViewTest, UIViewSetY_001, TestSize.Level0) +{ + if (view_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + view_->SetY(0); + EXPECT_EQ(view_->GetY(), 0); + view_->SetY(POS_Y); + EXPECT_EQ(view_->GetY(), POS_Y); +} + +/** + * @tc.name: UIViewSetPosition_001 + * @tc.desc: Verify SetPosition function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQF + */ +HWTEST_F(UIViewTest, UIViewSetPosition_001, TestSize.Level0) +{ + if (view_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + view_->SetPosition(0, 0); + EXPECT_EQ(view_->GetX(), 0); + EXPECT_EQ(view_->GetY(), 0); + view_->SetPosition(POS_X, POS_Y); + EXPECT_EQ(view_->GetX(), POS_X); + EXPECT_EQ(view_->GetY(), POS_Y); +} + +/** + * @tc.name: UIViewSetPosition_002 + * @tc.desc: Verify SetPosition function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQF + */ +HWTEST_F(UIViewTest, UIViewSetPosition_002, TestSize.Level0) +{ + if (view_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + view_->SetPosition(0, 0, 0, 0); + EXPECT_EQ(view_->GetHeight(), 0); + EXPECT_EQ(view_->GetWidth(), 0); + EXPECT_EQ(view_->GetX(), 0); + EXPECT_EQ(view_->GetY(), 0); + view_->SetPosition(POS_X, POS_Y, DEFAULE_WIDTH, DEFAULE_HEIGHT); + EXPECT_EQ(view_->GetHeight(), DEFAULE_HEIGHT); + EXPECT_EQ(view_->GetWidth(), DEFAULE_WIDTH); + EXPECT_EQ(view_->GetX(), POS_X); + EXPECT_EQ(view_->GetY(), POS_Y); +} + +/** + * @tc.name: UIViewSetWidthPercent_001 + * @tc.desc: Verify SetWidthPercent function, equal. + * @tc.type: FUNC + * @tc.require: SR000F3PE8 + */ +HWTEST_F(UIViewTest, UIViewSetWidthPercent_001, TestSize.Level0) +{ + if (view_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + view_->SetPosition(POS_X, POS_Y, DEFAULE_WIDTH, DEFAULE_HEIGHT); + EXPECT_EQ(view_->GetHeight(), DEFAULE_HEIGHT); + EXPECT_EQ(view_->GetWidth(), DEFAULE_WIDTH); + EXPECT_EQ(view_->GetX(), POS_X); + EXPECT_EQ(view_->GetY(), POS_Y); + + // Not has parent, SetWidthPercent failed + view_->SetWidthPercent(POS_X_PERCENT); + EXPECT_EQ(view_->GetWidth(), DEFAULE_WIDTH); + + // Parent does not SetWidth, view SetWidthPercent failed + UIViewGroup* viewGroup = new UIViewGroup(); + viewGroup->Add(view_); + view_->SetWidthPercent(POS_X_PERCENT); + EXPECT_EQ(view_->GetWidth(), DEFAULE_WIDTH); + + viewGroup->SetWidth(DEFAULE_WIDTH); + viewGroup->SetHeight(DEFAULE_HEIGHT); + + // Input invaild percent, SetWidthPercent failed + view_->SetWidthPercent(INVAILD_PERCENT_ZERO); + EXPECT_EQ(view_->GetWidth(), DEFAULE_WIDTH); + view_->SetWidthPercent(INVAILD_PERCENT_ONE); + EXPECT_EQ(view_->GetWidth(), DEFAULE_WIDTH); + + view_->SetWidthPercent(POS_X_PERCENT); + EXPECT_EQ(view_->GetWidth(), (int16_t)(DEFAULE_WIDTH * POS_X_PERCENT)); + + delete viewGroup; +} + +/** + * @tc.name: UIViewSetHeightPercent_001 + * @tc.desc: Verify SetHeightPercent function, equal. + * @tc.type: FUNC + * @tc.require: AR000F4770 + */ +HWTEST_F(UIViewTest, UIViewSetHeightPercent_001, TestSize.Level0) +{ + if (view_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + view_->SetPosition(POS_X, POS_Y, DEFAULE_WIDTH, DEFAULE_HEIGHT); + EXPECT_EQ(view_->GetHeight(), DEFAULE_HEIGHT); + EXPECT_EQ(view_->GetWidth(), DEFAULE_WIDTH); + EXPECT_EQ(view_->GetX(), POS_X); + EXPECT_EQ(view_->GetY(), POS_Y); + + // Not has parent, SetHeightPercent failed + view_->SetHeightPercent(POS_Y_PERCENT); + EXPECT_EQ(view_->GetHeight(), DEFAULE_HEIGHT); + + // Parent does not SetHeight, view SetHeightPercent failed + UIViewGroup* viewGroup = new UIViewGroup(); + viewGroup->Add(view_); + view_->SetHeightPercent(POS_Y_PERCENT); + EXPECT_EQ(view_->GetHeight(), DEFAULE_HEIGHT); + + viewGroup->SetWidth(DEFAULE_WIDTH); + viewGroup->SetHeight(DEFAULE_HEIGHT); + + // Input invaild percent, SetHeightPercent failed + view_->SetHeightPercent(INVAILD_PERCENT_ZERO); + EXPECT_EQ(view_->GetHeight(), DEFAULE_HEIGHT); + view_->SetHeightPercent(INVAILD_PERCENT_ONE); + EXPECT_EQ(view_->GetHeight(), DEFAULE_HEIGHT); + + view_->SetHeightPercent(POS_Y_PERCENT); + EXPECT_EQ(view_->GetHeight(), (int16_t)(DEFAULE_HEIGHT * POS_Y_PERCENT)); + + delete viewGroup; +} + +/** + * @tc.name: UIViewResizePercent_001 + * @tc.desc: Verify ResizePercent function, equal. + * @tc.type: FUNC + * @tc.require: AR000F4770 + */ +HWTEST_F(UIViewTest, UIViewResizePercent_001, TestSize.Level0) +{ + if (view_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + view_->SetPosition(POS_X, POS_Y, DEFAULE_WIDTH, DEFAULE_HEIGHT); + EXPECT_EQ(view_->GetHeight(), DEFAULE_HEIGHT); + EXPECT_EQ(view_->GetWidth(), DEFAULE_WIDTH); + EXPECT_EQ(view_->GetX(), POS_X); + EXPECT_EQ(view_->GetY(), POS_Y); + + // Not has parent, ResizePercent failed + view_->ResizePercent(POS_X_PERCENT, POS_Y_PERCENT); + EXPECT_EQ(view_->GetHeight(), DEFAULE_HEIGHT); + EXPECT_EQ(view_->GetWidth(), DEFAULE_WIDTH); + + // Parent does not SetWidth and SetHeight, view ResizePercent failed + UIViewGroup* viewGroup = new UIViewGroup(); + viewGroup->Add(view_); + view_->ResizePercent(POS_X_PERCENT, POS_Y_PERCENT); + EXPECT_EQ(view_->GetHeight(), DEFAULE_HEIGHT); + EXPECT_EQ(view_->GetWidth(), DEFAULE_WIDTH); + + viewGroup->SetWidth(DEFAULE_WIDTH); + viewGroup->SetHeight(DEFAULE_HEIGHT); + + // Input invaild percent, ResizePercent failed + view_->ResizePercent(INVAILD_PERCENT_ZERO, POS_Y_PERCENT); + EXPECT_EQ(view_->GetHeight(), DEFAULE_HEIGHT); + EXPECT_EQ(view_->GetWidth(), DEFAULE_WIDTH); + view_->ResizePercent(POS_X_PERCENT, INVAILD_PERCENT_ONE); + EXPECT_EQ(view_->GetHeight(), DEFAULE_HEIGHT); + EXPECT_EQ(view_->GetWidth(), DEFAULE_WIDTH); + + view_->ResizePercent(POS_X_PERCENT, POS_Y_PERCENT); + EXPECT_EQ(view_->GetHeight(), (int16_t)(DEFAULE_HEIGHT * POS_Y_PERCENT)); + EXPECT_EQ(view_->GetWidth(), (int16_t)(DEFAULE_WIDTH * POS_X_PERCENT)); + + delete viewGroup; +} + +/** + * @tc.name: UIViewSetXPercent_001 + * @tc.desc: Verify SetXPercent function, equal. + * @tc.type: FUNC + * @tc.require: AR000F4770 + */ +HWTEST_F(UIViewTest, UIViewSetXPercent_001, TestSize.Level0) +{ + if (view_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + view_->SetPosition(POS_X, POS_Y, DEFAULE_WIDTH, DEFAULE_HEIGHT); + EXPECT_EQ(view_->GetHeight(), DEFAULE_HEIGHT); + EXPECT_EQ(view_->GetWidth(), DEFAULE_WIDTH); + EXPECT_EQ(view_->GetX(), POS_X); + EXPECT_EQ(view_->GetY(), POS_Y); + + // Not has parent, SetXPercent failed + view_->SetXPercent(POS_X_PERCENT); + EXPECT_EQ(view_->GetX(), POS_X); + + // Parent does not SetWidth, view SetXPercent failed + UIViewGroup* viewGroup = new UIViewGroup(); + viewGroup->Add(view_); + view_->SetXPercent(POS_X_PERCENT); + EXPECT_EQ(view_->GetX(), POS_X); + + viewGroup->SetWidth(DEFAULE_WIDTH); + viewGroup->SetHeight(DEFAULE_HEIGHT); + + // Input invaild percent, SetXPercent failed + view_->SetXPercent(INVAILD_PERCENT_ZERO); + EXPECT_EQ(view_->GetX(), POS_X); + view_->SetXPercent(INVAILD_PERCENT_ONE); + EXPECT_EQ(view_->GetX(), POS_X); + + view_->SetXPercent(POS_X_PERCENT); + EXPECT_EQ(view_->GetX(), (int16_t)(POS_X_PERCENT * DEFAULE_WIDTH)); + + delete viewGroup; +} + +/** + * @tc.name: UIViewSetYPercent_001 + * @tc.desc: Verify SetYPercent function, equal. + * @tc.type: FUNC + * @tc.require: AR000F4770 + */ +HWTEST_F(UIViewTest, UIViewSetYPercent_001, TestSize.Level0) +{ + if (view_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + view_->SetPosition(POS_X, POS_Y, DEFAULE_WIDTH, DEFAULE_HEIGHT); + EXPECT_EQ(view_->GetHeight(), DEFAULE_HEIGHT); + EXPECT_EQ(view_->GetWidth(), DEFAULE_WIDTH); + EXPECT_EQ(view_->GetX(), POS_X); + EXPECT_EQ(view_->GetY(), POS_Y); + + // Not has parent, SetYPercent failed + view_->SetYPercent(POS_Y_PERCENT); + EXPECT_EQ(view_->GetY(), POS_Y); + + // Parent does not SetHeight, view SetYPercent failed + UIViewGroup* viewGroup = new UIViewGroup(); + viewGroup->Add(view_); + view_->SetYPercent(POS_Y_PERCENT); + EXPECT_EQ(view_->GetY(), POS_Y); + + viewGroup->SetWidth(DEFAULE_WIDTH); + viewGroup->SetHeight(DEFAULE_HEIGHT); + + // Input invaild percent, SetYPercent failed + view_->SetYPercent(INVAILD_PERCENT_ZERO); + EXPECT_EQ(view_->GetY(), POS_Y); + view_->SetYPercent(INVAILD_PERCENT_ONE); + EXPECT_EQ(view_->GetY(), POS_Y); + + view_->SetYPercent(POS_Y_PERCENT); + EXPECT_EQ(view_->GetY(), (int16_t)(POS_Y_PERCENT * DEFAULE_HEIGHT)); + + delete viewGroup; +} + +/** + * @tc.name: UIViewSetPositionPercent_001 + * @tc.desc: Verify SetPositionPercent function, equal. + * @tc.type: FUNC + * @tc.require: AR000F4770 + */ +HWTEST_F(UIViewTest, UIViewSetPositionPercent_001, TestSize.Level0) +{ + if (view_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + view_->SetPosition(POS_X, POS_Y, DEFAULE_WIDTH, DEFAULE_HEIGHT); + EXPECT_EQ(view_->GetHeight(), DEFAULE_HEIGHT); + EXPECT_EQ(view_->GetWidth(), DEFAULE_WIDTH); + EXPECT_EQ(view_->GetX(), POS_X); + EXPECT_EQ(view_->GetY(), POS_Y); + + // Not has parent, SetPositionPercent failed + view_->SetPositionPercent(POS_X_PERCENT, POS_Y_PERCENT); + EXPECT_EQ(view_->GetX(), POS_X); + EXPECT_EQ(view_->GetY(), POS_Y); + + // Parent does not SetWidth, view SetPositionPercent failed + UIViewGroup* viewGroup = new UIViewGroup(); + viewGroup->Add(view_); + view_->SetPositionPercent(POS_X_PERCENT, POS_Y_PERCENT); + EXPECT_EQ(view_->GetX(), POS_X); + EXPECT_EQ(view_->GetY(), POS_Y); + + viewGroup->SetWidth(DEFAULE_WIDTH); + viewGroup->SetHeight(DEFAULE_HEIGHT); + + // Input invaild percent, SetPositionPercent failed + view_->SetPositionPercent(INVAILD_PERCENT_ZERO, INVAILD_PERCENT_ONE); + EXPECT_EQ(view_->GetX(), POS_X); + EXPECT_EQ(view_->GetY(), POS_Y); + + view_->SetPositionPercent(POS_X_PERCENT, POS_Y_PERCENT); + EXPECT_EQ(view_->GetX(), (int16_t)(POS_X_PERCENT * DEFAULE_WIDTH)); + EXPECT_EQ(view_->GetY(), (int16_t)(POS_Y_PERCENT * DEFAULE_HEIGHT)); + + delete viewGroup; +} + +/** + * @tc.name: Graphic_UIView_Test_SetPositionPercent_002 + * @tc.desc: Verify SetPositionPercent function, equal. + * @tc.type: FUNC + * @tc.require: AR000F4770 + */ +HWTEST_F(UIViewTest, UIViewSetPositionPercent_002, TestSize.Level0) +{ + if (view_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + view_->SetPosition(POS_X, POS_Y, DEFAULE_WIDTH, DEFAULE_HEIGHT); + EXPECT_EQ(view_->GetHeight(), DEFAULE_HEIGHT); + EXPECT_EQ(view_->GetWidth(), DEFAULE_WIDTH); + EXPECT_EQ(view_->GetX(), POS_X); + EXPECT_EQ(view_->GetY(), POS_Y); + + // Not has parent, SetPositionPercent failed + view_->SetPositionPercent(POS_X_PERCENT, POS_Y_PERCENT, DEFAULT_WIDTH_PERCENT, DEFAULT_HEIGHT_PERCENT); + EXPECT_EQ(view_->GetHeight(), DEFAULE_HEIGHT); + EXPECT_EQ(view_->GetWidth(), DEFAULE_WIDTH); + EXPECT_EQ(view_->GetX(), POS_X); + EXPECT_EQ(view_->GetY(), POS_Y); + + // Parent does not SetWidth, view SetPositionPercent failed + UIViewGroup* viewGroup = new UIViewGroup(); + viewGroup->Add(view_); + view_->SetPositionPercent(POS_X_PERCENT, POS_Y_PERCENT, DEFAULT_WIDTH_PERCENT, DEFAULT_HEIGHT_PERCENT); + EXPECT_EQ(view_->GetHeight(), DEFAULE_HEIGHT); + EXPECT_EQ(view_->GetWidth(), DEFAULE_WIDTH); + EXPECT_EQ(view_->GetX(), POS_X); + EXPECT_EQ(view_->GetY(), POS_Y); + + viewGroup->SetWidth(DEFAULE_WIDTH); + viewGroup->SetHeight(DEFAULE_HEIGHT); + + // Input invaild percent, SetPositionPercent failed + view_->SetPositionPercent(INVAILD_PERCENT_ZERO, INVAILD_PERCENT_ONE, DEFAULT_WIDTH_PERCENT, DEFAULT_HEIGHT_PERCENT); + EXPECT_EQ(view_->GetHeight(), DEFAULE_HEIGHT); + EXPECT_EQ(view_->GetWidth(), DEFAULE_WIDTH); + EXPECT_EQ(view_->GetX(), POS_X); + EXPECT_EQ(view_->GetY(), POS_Y); + + + view_->SetPositionPercent(POS_X_PERCENT, POS_Y_PERCENT, DEFAULT_WIDTH_PERCENT, DEFAULT_HEIGHT_PERCENT); + EXPECT_EQ(view_->GetHeight(), (int16_t)(DEFAULE_HEIGHT * DEFAULT_HEIGHT_PERCENT)); + EXPECT_EQ(view_->GetWidth(), (int16_t)(DEFAULE_WIDTH * DEFAULT_WIDTH_PERCENT)); + EXPECT_EQ(view_->GetX(), (int16_t)(POS_X_PERCENT * DEFAULE_WIDTH)); + EXPECT_EQ(view_->GetY(), (int16_t)(POS_Y_PERCENT * DEFAULE_HEIGHT)); + + delete viewGroup; +} + +/** + * @tc.name: UIViewSetViewId_001 + * @tc.desc: Verify SetViewId function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQF + */ +HWTEST_F(UIViewTest, UIViewSetViewId_001, TestSize.Level0) +{ + if (view_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + const char* id = "graphic_unittest_view"; + + view_->SetViewId(id); + EXPECT_EQ(memcmp(view_->GetViewId(), id, strlen(id)), 0); +} + +/** + * @tc.name: UIViewSetViewIndex_001 + * @tc.desc: Verify SetViewIndex function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQF + */ +HWTEST_F(UIViewTest, UIViewSetViewIndex_001, TestSize.Level0) +{ + if (view_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + int16_t index = 1; + + view_->SetViewIndex(index); + EXPECT_EQ(view_->GetViewIndex(), index); +} + +/** + * @tc.name: UIViewGetViewType_001 + * @tc.desc: Verify GetViewType function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQF + */ +HWTEST_F(UIViewTest, UIViewGetViewType_001, TestSize.Level0) +{ + if (view_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + EXPECT_EQ(view_->GetViewType(), UI_NUMBER_MAX); +} + +/** + * @tc.name: UIViewSetOpaScale_001 + * @tc.desc: Verify SetOpaScale function, equal. + * @tc.type: FUNC + * @tc.require: AR000F4E5D + */ +HWTEST_F(UIViewTest, UIViewSetOpaScale_001, TestSize.Level0) +{ + if (view_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + uint8_t opaScale = OPA_OPAQUE; + + view_->SetOpaScale(OPA_OPAQUE); + EXPECT_EQ(view_->GetOpaScale(), OPA_OPAQUE); +} + +/** + * @tc.name: UIViewSetOpaScale_002 + * @tc.desc: Verify SetOpaScale function, equal. + * @tc.type: FUNC + * @tc.require: SR000F3PEB + */ +HWTEST_F(UIViewTest, UIViewSetOpaScale_002, TestSize.Level0) +{ + if (view_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + uint8_t opaScale = OPA_OPAQUE; + + view_->SetOpaScale(OPA_OPAQUE); + view_->SetStyle(STYLE_BACKGROUND_OPA, opaScale >> 1); + view_->SetStyle(STYLE_BORDER_OPA, opaScale >> 1); + EXPECT_EQ(view_->GetOpaScale(), OPA_OPAQUE); +} + +/** + * @tc.name: UIViewSetStyle_001 + * @tc.desc: Verify SetStyle function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQF + */ +HWTEST_F(UIViewTest, UIViewSetStyle_001, TestSize.Level0) +{ + if (view_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + Style style; + style.borderOpa_ = OPA_OPAQUE; + view_->SetStyle(style); + EXPECT_EQ(view_->GetStyleConst().borderOpa_, OPA_OPAQUE); + view_->SetStyle(STYLE_LINE_OPA, OPA_OPAQUE); + EXPECT_EQ(view_->GetStyle(STYLE_LINE_OPA), OPA_OPAQUE); +} + +/** + * @tc.name: UIViewLayoutOfParent_001 + * @tc.desc: Verify LayoutOfParent function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQF + */ +HWTEST_F(UIViewTest, UIViewLayoutOfParent_001, TestSize.Level0) +{ + if (view_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + UIViewGroup* viewGroup = new UIViewGroup(); + viewGroup->SetPosition(0, 0, DEFAULE_WIDTH, DEFAULE_HEIGHT); + view_->SetPosition(POS_X, POS_Y, BUTTON_WIDTH, BUTTON_HEIGHT); + viewGroup->Add(view_); + int16_t centerX = (DEFAULE_WIDTH- BUTTON_WIDTH) / 2; // 2: center of x; + int16_t centerY = (DEFAULE_HEIGHT- BUTTON_HEIGHT) / 2; // 2: center of y; + EXPECT_EQ(view_->GetX(), POS_X); + EXPECT_EQ(view_->GetY(), POS_Y); + view_->LayoutCenterOfParent(); + EXPECT_EQ(view_->GetX(), centerX); + EXPECT_EQ(view_->GetY(), centerY); + view_->LayoutLeftOfParent(); + EXPECT_EQ(view_->GetX(), 0); + EXPECT_EQ(view_->GetY(), centerY); + view_->LayoutRightOfParent(); + EXPECT_EQ(view_->GetX(), DEFAULE_WIDTH- BUTTON_WIDTH); + EXPECT_EQ(view_->GetY(), centerY); + view_->LayoutTopOfParent(); + EXPECT_EQ(view_->GetX(), DEFAULE_WIDTH- BUTTON_WIDTH); + EXPECT_EQ(view_->GetY(), 0); + view_->LayoutBottomOfParent(); + EXPECT_EQ(view_->GetX(), DEFAULE_WIDTH- BUTTON_WIDTH); + EXPECT_EQ(view_->GetY(), DEFAULE_HEIGHT- BUTTON_HEIGHT); + + delete viewGroup; +} + +/** + * @tc.name: UIViewAlignToSibling_001 + * @tc.desc: Verify AlignToSibling function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQF + */ +HWTEST_F(UIViewTest, UIViewAlignToSibling_001, TestSize.Level0) +{ + if (view_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + UIViewGroup* viewGroup = new UIViewGroup(); + viewGroup->SetPosition(0, 0, DEFAULE_WIDTH, DEFAULE_HEIGHT); + view_->SetPosition(POS_X, POS_Y, BUTTON_WIDTH, BUTTON_HEIGHT); + viewGroup->Add(view_); + int16_t centerX = (DEFAULE_WIDTH- BUTTON_WIDTH) / 2; // 2: center of x; + int16_t centerY = (DEFAULE_HEIGHT- BUTTON_HEIGHT) / 2; // 2: center of y; + UIView* view2 = new UIView(); + view2->SetPosition(centerX, centerY, BUTTON_WIDTH, BUTTON_HEIGHT); + view2->SetViewId("refView"); + viewGroup->Add(view2); + EXPECT_EQ(view_->GetX(), POS_X); + EXPECT_EQ(view_->GetY(), POS_Y); + view_->AlignLeftToSibling("refView"); + EXPECT_EQ(view_->GetX(), centerX); + EXPECT_EQ(view_->GetY(), POS_Y); + view_->AlignRightToSibling("refView"); + EXPECT_EQ(view_->GetX(), centerX); + EXPECT_EQ(view_->GetY(), POS_Y); + view_->AlignTopToSibling("refView"); + EXPECT_EQ(view_->GetX(), centerX); + EXPECT_EQ(view_->GetY(), centerY); + view_->AlignBottomToSibling("refView"); + EXPECT_EQ(view_->GetX(), centerX); + EXPECT_EQ(view_->GetY(), centerY); + view_->AlignHorCenterToSibling("refView"); + EXPECT_EQ(view_->GetX(), centerX); + EXPECT_EQ(view_->GetY(), centerY); + view_->AlignVerCenterToSibling("refView"); + EXPECT_EQ(view_->GetX(), centerX); + EXPECT_EQ(view_->GetY(), centerY); + + delete view2; + delete viewGroup; +} + +/** + * @tc.name: UIViewLayoutToSibling_001 + * @tc.desc: Verify LayoutToSibling function, equal. + * @tc.type: FUNC + * @tc.require: AR000EEMQF + */ +HWTEST_F(UIViewTest, UIViewLayoutToSibling_001, TestSize.Level0) +{ + if (view_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + UIViewGroup* viewGroup = new UIViewGroup(); + viewGroup->SetPosition(0, 0, DEFAULE_WIDTH, DEFAULE_HEIGHT); + view_->SetPosition(POS_X, POS_Y, BUTTON_WIDTH, BUTTON_HEIGHT); + viewGroup->Add(view_); + int16_t centerX = (DEFAULE_WIDTH- BUTTON_WIDTH) / 2; // 2: center of x; + int16_t centerY = (DEFAULE_HEIGHT- BUTTON_HEIGHT) / 2; // 2: center of y; + UIView* view2 = new UIView(); + view2->SetPosition(centerX, centerY, BUTTON_WIDTH, BUTTON_HEIGHT); + view2->SetViewId("refView"); + viewGroup->Add(view2); + EXPECT_EQ(view_->GetX(), POS_X); + EXPECT_EQ(view_->GetY(), POS_Y); + view_->LayoutLeftToSibling("refView"); + EXPECT_EQ(view_->GetX(), BUTTON_WIDTH); + EXPECT_EQ(view_->GetY(), POS_Y); + view_->LayoutRightToSibling("refView"); + EXPECT_EQ(view_->GetX(), centerX + BUTTON_WIDTH); + EXPECT_EQ(view_->GetY(), POS_Y); + view_->LayoutTopToSibling("refView"); + EXPECT_EQ(view_->GetX(), centerX + BUTTON_WIDTH); + EXPECT_EQ(view_->GetY(), BUTTON_HEIGHT); + view_->LayoutBottomToSibling("refView"); + EXPECT_EQ(view_->GetX(), centerX + BUTTON_WIDTH); + EXPECT_EQ(view_->GetY(), centerY + BUTTON_HEIGHT); + + delete view2; + delete viewGroup; +} + +/** + * @tc.name: Graphic_UIView_Test_Rotate_001 + * @tc.desc: Verify Rotate function, equal. + * @tc.type: FUNC + * @tc.require: SR000F3PEC + */ +HWTEST_F(UIViewTest, Graphic_UIView_Test_Rotate_001, TestSize.Level0) +{ + if (view_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + view_->SetPosition(0, 0, DEFAULE_WIDTH, DEFAULE_HEIGHT); + view_->Rotate(DEFAULE_ANGLE, {0, 0}); + TransformMap& trans = view_->GetTransformMap(); + EXPECT_EQ(view_->GetOrigRect(), trans.GetTransMapRect()); +} + +/** + * @tc.name: Graphic_UIView_Test_Rotate_002 + * @tc.desc: Verify Rotate function, equal. + * @tc.type: FUNC + * @tc.require: AR000F4E5E + */ +HWTEST_F(UIViewTest, Graphic_UIView_Test_Rotate_002, TestSize.Level0) +{ + if (view_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + view_->SetPosition(0, 0, DEFAULE_WIDTH, DEFAULE_HEIGHT); + view_->Rotate(DEFAULE_ANGLE, {0, 0}); + Polygon polygon = view_->GetTransformMap().GetPolygon(); + + EXPECT_EQ(POLYGON1, polygon[0]); // 0: polygon num + EXPECT_EQ(POLYGON2, polygon[1]); // 1: polygon num + EXPECT_EQ(POLYGON3, polygon[2]); // 2: polygon num + EXPECT_EQ(POLYGON4, polygon[3]); // 3: polygon num +} + +/** + * @tc.name: Graphic_UIView_Test_Scale_001 + * @tc.desc: Verify Scale function, equal. + * @tc.type: FUNC + * @tc.require: SR000ERCQH + */ +HWTEST_F(UIViewTest, Graphic_UIView_Test_Scale_001, TestSize.Level0) +{ + if (view_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + view_->SetPosition(0, 0, DEFAULE_WIDTH, DEFAULE_HEIGHT); + view_->Scale(SCALE_VALUE, VIEW_CENTER); + TransformMap& trans = view_->GetTransformMap(); + EXPECT_EQ(view_->GetOrigRect(), trans.GetTransMapRect()); +} + +/** + * @tc.name: Graphic_UIView_Test_Scale_002 + * @tc.desc: Verify Scale function, equal. + * @tc.type: FUNC + * @tc.require: AR000EVI2T + */ +HWTEST_F(UIViewTest, Graphic_UIView_Test_Scale_002, TestSize.Level0) +{ + if (view_ == nullptr) { + EXPECT_NE(0, 0); + return; + } + view_->SetPosition(0, 0, DEFAULE_WIDTH, DEFAULE_HEIGHT); + view_->Scale(SCALE_VALUE, {0, 0}); + Polygon polygon = view_->GetTransformMap().GetPolygon(); + EXPECT_EQ(0, polygon[0].x_); + EXPECT_EQ(0, polygon[0].y_); + EXPECT_EQ((DEFAULE_WIDTH - 1) >> 1, polygon[2].x_); // 2: polygon num + EXPECT_EQ((DEFAULE_WIDTH - 1) >> 1, polygon[2].x_); // 2: polygon num +} +} // namespace OHOS diff --git a/test/unittest/dfx/event_injector_unit_test.cpp b/test/unittest/dfx/event_injector_unit_test.cpp new file mode 100755 index 0000000..0d76103 --- /dev/null +++ b/test/unittest/dfx/event_injector_unit_test.cpp @@ -0,0 +1,443 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "dfx/event_injector.h" + +#if ENABLE_DEBUG +#include "common/graphic_startup.h" +#include "common/screen.h" +#include "components/root_view.h" +#include "common/task_manager.h" +#include "common/input_device_manager.h" +#include "core/render_manager.h" +#include "dock/screen_device_proxy.h" +#include "components/ui_label_button.h" +#include "components/ui_scroll_view.h" +#include +#include "file.h" +#include "font/ui_font.h" +#include +#include "graphic_config.h" +#include "graphic_log.h" +#include "imgdecode/cache_manager.h" +#include "layout/grid_layout.h" +#include +#include +#include "window/window.h" + +using namespace testing::ext; + +enum TestEventInjectorFlag { + FLAG0, + FLAG1 +}; + +namespace { +uint8_t REGISTER_POINT_FLAG = FLAG0; +uint8_t REGISTER_KEY_FLAG = FLAG0; +uint8_t UNREGISTER_POINT_FLAG = FLAG0; +uint8_t UNREGISTER_KEY_FLAG = FLAG0; +uint8_t CLICK_FLAG = FLAG0; +uint8_t LONG_PRESS_FLAG = FLAG0; +uint8_t DRAG_FLAG = FLAG0; +uint8_t KEY_FLAG = FLAG0; +uint8_t MAX_LOOP = 200; +} + +namespace OHOS { +class TestEventInjectorView : public UIView, public RootView::OnKeyActListener { +public: + bool OnLongPressEvent(const LongPressEvent& event) override + { + LONG_PRESS_FLAG = FLAG1; + return true; + } + + bool OnClickEvent(const ClickEvent& event) override + { + CLICK_FLAG = FLAG1; + return true; + } + + bool OnDragEvent(const DragEvent& event) override + { + DRAG_FLAG = FLAG1; + return true; + } + + bool OnKeyAct(UIView& view, const KeyEvent& event) override + { + KEY_FLAG = FLAG1; + return true; + } +}; + +class EventInjectorTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + static void TestApp(); + static void SetUpTestview(TestEventInjectorView* testView, bool touchable, bool draggable); + static void* MainTask(void* args); + static void DeleteChildren(UIView* view); + + static pthread_t mainTaskThread_; + static bool isRepeat_; + static RootView* rootView_; + static GridLayout* layout_; + static TestEventInjectorView* clickView_; + static TestEventInjectorView* dragView_; + static TestEventInjectorView* longPressView_; + static TestEventInjectorView* keyView_; + static Window* window_; +}; + +pthread_t EventInjectorTest::mainTaskThread_ = -1; +bool EventInjectorTest::isRepeat_ = true; +RootView* EventInjectorTest::rootView_ = nullptr; +GridLayout* EventInjectorTest::layout_ = nullptr; +TestEventInjectorView* EventInjectorTest::clickView_ = nullptr; +TestEventInjectorView* EventInjectorTest::dragView_ = nullptr; +TestEventInjectorView* EventInjectorTest::longPressView_ = nullptr; +TestEventInjectorView* EventInjectorTest::keyView_ = nullptr; +Window* EventInjectorTest::window_ = nullptr; + +static void InitHal() +{ + ScreenDevice* display = new ScreenDevice(); + ScreenDeviceProxy::GetInstance()->SetDevice(display); + ScreenDeviceProxy::GetInstance()->SetScreenSize(HORIZONTAL_RESOLUTION, VERTICAL_RESOLUTION); +} + +void EventInjectorTest::SetUpTestCase(void) +{ + GraphicStartUp::Init(); + InitHal(); + TestApp(); + if (pthread_create(&mainTaskThread_, nullptr, MainTask, nullptr) != 0) { + return; + } +} + +void* EventInjectorTest::MainTask(void* args) +{ + while (isRepeat_) { + /* Periodically call TaskHandler(). It could be done in a timer interrupt or an OS task too. */ + OHOS::TaskManager::GetInstance()->TaskHandler(); + usleep(1000 * 10); /* 1000 * 10:10ms Just to let the system breathe */ + } + return nullptr; +} + +void EventInjectorTest::TestApp() +{ + WindowConfig config = {}; + config.rect.SetRect(0, 0, Screen::GetInstance().GetWidth() - 1, Screen::GetInstance().GetHeight() - 1); + window_ = Window::CreateWindow(config); + if (window_ == nullptr) { + GRAPHIC_LOGE("Create window false!"); + return; + } + window_->BindRootView(RootView::GetInstance()); + + if (EventInjector::GetInstance()->RegisterEventInjector(EventDataType::POINT_TYPE)) { + REGISTER_POINT_FLAG = FLAG1; + } + if (EventInjector::GetInstance()->RegisterEventInjector(EventDataType::KEY_TYPE)) { + REGISTER_KEY_FLAG = FLAG1; + } + EventInjector::GetInstance()->SetWindowId(window_->GetWindowId()); + rootView_ = RootView::GetInstance(); + rootView_->SetPosition(0, 0, Screen::GetInstance().GetWidth(), Screen::GetInstance().GetHeight()); + layout_ = new GridLayout(); + layout_->SetPosition(0, 0, Screen::GetInstance().GetWidth(), Screen::GetInstance().GetHeight()); + rootView_->Add(layout_); + layout_->SetLayoutDirection(LAYOUT_VER); + layout_->SetRows(4); /* 4:rows */ + layout_->SetCols(1); + clickView_ = new TestEventInjectorView(); + SetUpTestview(clickView_, true, false); + + longPressView_ = new TestEventInjectorView(); + SetUpTestview(longPressView_, true, false); + + dragView_ = new TestEventInjectorView(); + SetUpTestview(dragView_, true, true); + + keyView_ = new TestEventInjectorView(); + RootView::GetInstance()->SetOnKeyActListener(keyView_); + SetUpTestview(keyView_, true, false); + + layout_->LayoutChildren(); + rootView_->Invalidate(); +} + +void EventInjectorTest::SetUpTestview(TestEventInjectorView* testView, bool touchable, bool draggable) +{ + layout_->Add(testView); + testView->Resize(HORIZONTAL_RESOLUTION, VERTICAL_RESOLUTION / 5); /* 5:ratio */ + testView->SetTouchable(touchable); + testView->SetDraggable(draggable); +} + +void EventInjectorTest::DeleteChildren(UIView* view) +{ + if (view == nullptr) { + return; + } + while (view != nullptr) { + UIView* tempView = view; + view = view->GetNextSibling(); + if (tempView->IsViewGroup()) { + DeleteChildren(static_cast(tempView)->GetChildrenHead()); + } + if (tempView->GetParent()) { + static_cast(tempView->GetParent())->Remove(tempView); + } + delete tempView; + } +} + +void EventInjectorTest::TearDownTestCase(void) +{ + isRepeat_ = false; + pthread_join(mainTaskThread_, nullptr); + Window::DestoryWindow(window_); + DeleteChildren(layout_); + layout_ = nullptr; + + EventInjector::GetInstance()->UnregisterEventInjector(EventDataType::POINT_TYPE); + EventInjector::GetInstance()->UnregisterEventInjector(EventDataType::KEY_TYPE); +} + +/** + * @tc.name: Graphic_EventInjectorTest_Test_RegisterEventInjector_001 + * @tc.desc: Verify RegisterEventInjector function, equal. + * @tc.type: FUNC + * @tc.require: SR000F74SS + */ +HWTEST_F(EventInjectorTest, Graphic_EventInjectorTest_Test_RegisterEventInjector_001, TestSize.Level0) +{ + EXPECT_EQ(REGISTER_POINT_FLAG, FLAG1); + EXPECT_EQ(REGISTER_KEY_FLAG, FLAG1); +} + +/** + * @tc.name: Graphic_EventInjectorTest_Test_SetClickEvent_001 + * @tc.desc: Verify SetClickEvent function, equal. + * @tc.type: FUNC + * @tc.require: AR000F74ST + */ +HWTEST_F(EventInjectorTest, Graphic_EventInjectorTest_Test_SetClickEvent_001, TestSize.Level2) +{ + if (clickView_ == nullptr) { + ADD_FAILURE(); + return; + } + CLICK_FLAG = FLAG0; + /* 2:ratio, 2:ratio */ + Point clickPoint = {static_cast(clickView_->GetRect().GetX() + clickView_->GetWidth() / 2), + static_cast(clickView_->GetRect().GetY() + clickView_->GetHeight() / 2)}; + bool ret = EventInjector::GetInstance()->SetClickEvent(clickPoint); + EXPECT_EQ(ret, true); + uint8_t loop = 0; + while ((loop < MAX_LOOP) && !CLICK_FLAG) { + loop++; + usleep(10000); /* 10000:10ms */ + } + EXPECT_EQ(CLICK_FLAG, FLAG1); +} + +/** + * @tc.name: Graphic_EventInjectorTest_Test_SetDragEvent_001 + * @tc.desc: Verify SetDragEvent function, equal. + * @tc.type: FUNC + * @tc.require: AR000F74ST + */ +HWTEST_F(EventInjectorTest, Graphic_EventInjectorTest_Test_SetDragEvent_001, TestSize.Level2) +{ + if (dragView_ == nullptr) { + ADD_FAILURE(); + return; + } + DRAG_FLAG = FLAG0; + /* 4:ratio, 2:ratio */ + Point startPoint = {static_cast(dragView_->GetRect().GetX() + dragView_->GetWidth() / 4), + static_cast(dragView_->GetRect().GetY() + dragView_->GetHeight() / 2)}; + /* 2:ratio, 2:ratio */ + Point endPoint = {static_cast(dragView_->GetRect().GetX() + dragView_->GetWidth() / 2), + static_cast(dragView_->GetRect().GetY() + dragView_->GetHeight() / 2)}; + bool ret = EventInjector::GetInstance()->SetDragEvent(startPoint, endPoint, 100); /* 100:drag time (ms) */ + EXPECT_EQ(ret, true); + uint8_t loop = 0; + while ((loop < MAX_LOOP) && !DRAG_FLAG) { + loop++; + usleep(10000); /* 10000:10ms */ + } + EXPECT_EQ(DRAG_FLAG, FLAG1); +} + +/** + * @tc.name: Graphic_EventInjectorTest_Test_SetDragEvent_002 + * @tc.desc: Verify SetDragEvent function, abnormal branch, SetDragEvent failure(dragTime is too short), equal. + * @tc.type: FUNC + * @tc.require: AR000F74ST + */ +HWTEST_F(EventInjectorTest, Graphic_EventInjectorTest_Test_SetDragEvent_002, TestSize.Level0) +{ + if (dragView_ == nullptr) { + ADD_FAILURE(); + return; + } + /* 4:ratio, 2:ratio */ + Point startPoint = {static_cast(dragView_->GetRect().GetX() + dragView_->GetWidth() / 4), + static_cast(dragView_->GetRect().GetY() + dragView_->GetHeight() / 2)}; + /* 2:ratio, 2:ratio */ + Point endPoint = {static_cast(dragView_->GetRect().GetX() + dragView_->GetWidth() / 2), + static_cast(dragView_->GetRect().GetY() + dragView_->GetHeight() / 2)}; + bool ret = EventInjector::GetInstance()->SetDragEvent(startPoint, endPoint, INDEV_READ_PERIOD); + EXPECT_EQ(ret, false); +} + +/** + * @tc.name: Graphic_EventInjectorTest_Test_SetDragEvent_003 + * @tc.desc: Verify SetDragEvent function, abnormal branch, SetDragEvent failure(dragTime is too long), equal. + * @tc.type: FUNC + * @tc.require: AR000F74ST + */ +HWTEST_F(EventInjectorTest, Graphic_EventInjectorTest_Test_SetDragEvent_003, TestSize.Level0) +{ + if (dragView_ == nullptr) { + ADD_FAILURE(); + return; + } + /* 4:ratio, 2:ratio */ + Point startPoint = {static_cast(dragView_->GetRect().GetX() + dragView_->GetWidth() / 4), + static_cast(dragView_->GetRect().GetY() + dragView_->GetHeight() / 2)}; + /* 2:ratio, 2:ratio */ + Point endPoint = {static_cast(dragView_->GetRect().GetX() + dragView_->GetWidth() / 2), + static_cast(dragView_->GetRect().GetY() + dragView_->GetHeight() / 2)}; + bool ret = EventInjector::GetInstance()->SetDragEvent(startPoint, endPoint, 6000); /* 6000:drag time (ms) */ + EXPECT_EQ(ret, false); +} + +/** + * @tc.name: Graphic_EventInjectorTest_Test_SetLongPressEvent_001 + * @tc.desc: Verify SetLongPressEvent function, equal. + * @tc.type: FUNC + * @tc.require: AR000F74ST + */ +HWTEST_F(EventInjectorTest, Graphic_EventInjectorTest_Test_SetLongPressEvent_001, TestSize.Level2) +{ + if (longPressView_ == nullptr) { + ADD_FAILURE(); + return; + } + sleep(2); /* 2:2s */ + LONG_PRESS_FLAG = FLAG0; + /* 2:ratio, 2:ratio */ + Point longPressPoint = {static_cast(longPressView_->GetRect().GetX() + longPressView_->GetWidth() / 2), + static_cast(longPressView_->GetRect().GetY() + longPressView_->GetHeight() / 2)}; + bool ret = EventInjector::GetInstance()->SetLongPressEvent(longPressPoint); + EXPECT_EQ(ret, true); + uint8_t loop = 0; + while ((loop < MAX_LOOP) && !LONG_PRESS_FLAG) { + loop++; + usleep(10000); /* 10000:10ms */ + } + EXPECT_EQ(LONG_PRESS_FLAG, FLAG1); +} + +/** + * @tc.name: Graphic_EventInjectorTest_Test_SetLongPressEvent_002 + * @tc.desc: Verify SetLongPressEvent function, abnormal branch, SetLongPressEvent failure, equal. + * @tc.type: FUNC + * @tc.require: AR000F74ST + */ +HWTEST_F(EventInjectorTest, Graphic_EventInjectorTest_Test_SetLongPressEvent_002, TestSize.Level0) +{ + if (longPressView_ == nullptr) { + ADD_FAILURE(); + return; + } + /* 2:ratio, 2:ratio */ + Point longPressPoint = {static_cast(longPressView_->GetRect().GetX() + longPressView_->GetWidth() / 2), + static_cast(longPressView_->GetRect().GetY() + longPressView_->GetHeight() / 2)}; + /* 20:loop */ + for (uint8_t i = 0; i < 20; i++) { + EventInjector::GetInstance()->SetLongPressEvent(longPressPoint); + } + bool ret = EventInjector::GetInstance()->SetLongPressEvent(longPressPoint); + EXPECT_EQ(ret, false); +} + +/** + * @tc.name: Graphic_EventInjectorTest_Test_SetKeyEvent_001 + * @tc.desc: Verify SetKeyEvent function, equal. + * @tc.type: FUNC + * @tc.require: AR000F74ST + */ +HWTEST_F(EventInjectorTest, Graphic_EventInjectorTest_Test_SetKeyEvent_001, TestSize.Level2) +{ + /* 26:keyId */ + uint16_t keyId = 26; + EventInjector::GetInstance()->SetKeyEvent(keyId, InputDevice::STATE_PRESS); + uint8_t loop = 0; + while ((loop < MAX_LOOP) && !KEY_FLAG) { + loop++; + usleep(10000); /* 10000:10ms */ + } + EXPECT_EQ(KEY_FLAG, FLAG1); +} + +/** + * @tc.name: Graphic_EventInjectorTest_Test_SetKeyEvent_002 + * @tc.desc: Verify SetKeyEvent function, abnormal branch, SetKeyEvent failure, equal. + * @tc.type: FUNC + * @tc.require: AR000F74ST + */ +HWTEST_F(EventInjectorTest, Graphic_EventInjectorTest_Test_SetKeyEvent_002, TestSize.Level0) +{ + /* 26:keyId */ + uint16_t keyId = 26; + /* 200:loop */ + for (uint8_t i = 0; i < 200; i++) { + EventInjector::GetInstance()->SetKeyEvent(keyId, InputDevice::STATE_PRESS); + } + bool ret = EventInjector::GetInstance()->SetKeyEvent(keyId, InputDevice::STATE_PRESS); + EXPECT_EQ(ret, false); +} + +/** + * @tc.name: Graphic_EventInjectorTest_Test_UnregisterEventInjector_001 + * @tc.desc: Verify UnregisterEventInjector function, equal. + * @tc.type: FUNC + * @tc.require: AR000F74ST + */ +HWTEST_F(EventInjectorTest, Graphic_EventInjectorTest_Test_UnregisterEventInjector_001, TestSize.Level0) +{ + EventInjector::GetInstance()->UnregisterEventInjector(EventDataType::POINT_TYPE); + if (!EventInjector::GetInstance()->IsEventInjectorRegistered(EventDataType::POINT_TYPE)) { + UNREGISTER_POINT_FLAG = FLAG1; + } + EventInjector::GetInstance()->UnregisterEventInjector(EventDataType::KEY_TYPE); + if (!EventInjector::GetInstance()->IsEventInjectorRegistered(EventDataType::KEY_TYPE)) { + UNREGISTER_KEY_FLAG = FLAG1; + } + EXPECT_EQ(UNREGISTER_POINT_FLAG, FLAG1); + EXPECT_EQ(UNREGISTER_KEY_FLAG, FLAG1); +} +} +#endif // ENABLE_DEBUG \ No newline at end of file diff --git a/test/unittest/events/cancel_event_unit_test.cpp b/test/unittest/events/cancel_event_unit_test.cpp new file mode 100755 index 0000000..f0d33fa --- /dev/null +++ b/test/unittest/events/cancel_event_unit_test.cpp @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "events/cancel_event.h" + +#include +#include + +using namespace testing::ext; +namespace OHOS { +namespace { + const Point INIT_POS = { 100, 200 }; +} +class CancelEventTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + static CancelEvent* cancelEvent_; +}; + +CancelEvent* CancelEventTest::cancelEvent_ = nullptr; + +void CancelEventTest::SetUpTestCase(void) +{ + if (cancelEvent_ == nullptr) { + cancelEvent_ = new CancelEvent(INIT_POS); + } +} + +void CancelEventTest::TearDownTestCase(void) +{ + if (cancelEvent_ != nullptr) { + delete cancelEvent_; + cancelEvent_ = nullptr; + } +} + +/** + * @tc.name: CancelEventConstructor_001 + * @tc.desc: Verify Constructor function, equal. + * @tc.type: FUNC + * @tc.require: SR000DRSH4 + */ +HWTEST_F(CancelEventTest, CancelEventConstructor_001, TestSize.Level0) +{ + if (cancelEvent_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + EXPECT_EQ(cancelEvent_->GetCurrentPos().x, INIT_POS.x); + EXPECT_EQ(cancelEvent_->GetCurrentPos().y, INIT_POS.y); +} +} // namespace OHOS diff --git a/test/unittest/events/click_event_unit_test.cpp b/test/unittest/events/click_event_unit_test.cpp new file mode 100755 index 0000000..b338b63 --- /dev/null +++ b/test/unittest/events/click_event_unit_test.cpp @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "events/click_event.h" + +#include +#include + +using namespace testing::ext; +namespace OHOS { +namespace { + const Point INIT_POS = { 100, 200 }; +} +class ClickEventTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + static ClickEvent* clickEvent_; +}; + +ClickEvent* ClickEventTest::clickEvent_ = nullptr; + +void ClickEventTest::SetUpTestCase(void) +{ + if (clickEvent_ == nullptr) { + clickEvent_ = new ClickEvent(INIT_POS); + } +} + +void ClickEventTest::TearDownTestCase(void) +{ + if (clickEvent_ != nullptr) { + delete clickEvent_; + clickEvent_ = nullptr; + } +} +/** + * @tc.name: ClickEventConstructor_001 + * @tc.desc: Verify Constructor function, equal. + * @tc.type: FUNC + * @tc.require: SR000DRSH4 + */ +HWTEST_F(ClickEventTest, ClickEventConstructor_001, TestSize.Level0) +{ + if (clickEvent_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + EXPECT_EQ(clickEvent_->GetCurrentPos().x, INIT_POS.x); + EXPECT_EQ(clickEvent_->GetCurrentPos().y, INIT_POS.y); +} +} // namespace OHOS diff --git a/test/unittest/events/drag_event_unit_test.cpp b/test/unittest/events/drag_event_unit_test.cpp new file mode 100755 index 0000000..a85e811 --- /dev/null +++ b/test/unittest/events/drag_event_unit_test.cpp @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "events/drag_event.h" + +#include +#include + +using namespace testing::ext; +namespace OHOS { +namespace { + const Point NEW_POS = { 100, 200 }; + const Point LAST_POS = { 150, 250 }; + const Point TOTAL_LEN = { 50, 150 }; + constexpr uint8_t DIRECTION_LEFT_TO_RIGHT = 0; + constexpr uint8_t DIRECTION_RIGHT_TO_LEFT = 1; + constexpr uint8_t DIRECTION_TOP_TO_BOTTOM = 2; + constexpr uint8_t DIRECTION_BOTTOM_TO_TOP = 3; +} + +class DragEventTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + static DragEvent* dragEvent_; +}; + +DragEvent* DragEventTest::dragEvent_ = nullptr; + +void DragEventTest::SetUpTestCase(void) +{ + if (dragEvent_ == nullptr) { + dragEvent_ = new DragEvent(NEW_POS, LAST_POS, TOTAL_LEN); + } +} + +void DragEventTest::TearDownTestCase(void) +{ + if (dragEvent_ != nullptr) { + delete dragEvent_; + dragEvent_ = nullptr; + } +} +/** + * @tc.name: DragEventConstructor_001 + * @tc.desc: Verify Constructor function, equal. + * @tc.type: FUNC + * @tc.require: SR000DRSH4 + */ +HWTEST_F(DragEventTest, DragEventConstructor_001, TestSize.Level0) +{ + if (dragEvent_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + EXPECT_EQ(dragEvent_->GetCurrentPos().x, NEW_POS.x); + EXPECT_EQ(dragEvent_->GetCurrentPos().y, NEW_POS.y); +} + +/** + * @tc.name: DragEventGetLastPoint_001 + * @tc.desc: Verify GetLastPoint function, equal. + * @tc.type: FUNC + * @tc.require: SR000DRSH4 + */ +HWTEST_F(DragEventTest, DragEventGetLastPoint_001, TestSize.Level0) +{ + if (dragEvent_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + EXPECT_EQ(dragEvent_->GetLastPoint().x, LAST_POS.x); + EXPECT_EQ(dragEvent_->GetLastPoint().y, LAST_POS.y); +} + +/** + * @tc.name: DragEventGetStartPoint_001 + * @tc.desc: Verify GetStartPoint function, equal. + * @tc.type: FUNC + * @tc.require: SR000DRSH4 + */ +HWTEST_F(DragEventTest, DragEventGetStartPoint_001, TestSize.Level0) +{ + if (dragEvent_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + EXPECT_EQ(dragEvent_->GetStartPoint().x, NEW_POS.x - TOTAL_LEN.x); + EXPECT_EQ(dragEvent_->GetStartPoint().y, NEW_POS.y - TOTAL_LEN.y); +} + +/** + * @tc.name: DragEventSetPreLastPoint_001 + * @tc.desc: Verify SetPreLastPoint function, equal. + * @tc.type: FUNC + * @tc.require: SR000DRSH4 + */ +HWTEST_F(DragEventTest, DragEventSetPreLastPoint_001, TestSize.Level0) +{ + if (dragEvent_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + const Point preLastPos = { 20, 40 }; + dragEvent_->SetPreLastPoint(preLastPos); + EXPECT_EQ(dragEvent_->GetPreLastPoint().x, preLastPos.x); + EXPECT_EQ(dragEvent_->GetPreLastPoint().y, preLastPos.y); +} + +/** + * @tc.name: DragEventGetDragDirection_001 + * @tc.desc: Verify GetDragDirection function, equal. + * @tc.type: FUNC + * @tc.require: SR000DRSH4 + */ +HWTEST_F(DragEventTest, DragEventGetDragDirection_001, TestSize.Level0) +{ + if (dragEvent_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + Point startPos = dragEvent_->GetStartPoint(); + Point currentPos = dragEvent_->GetCurrentPos(); + if (MATH_ABS(currentPos.x - startPos.x) >= MATH_ABS(currentPos.y - startPos.y)) { + if (currentPos.x > startPos.x) { + EXPECT_EQ(dragEvent_->GetDragDirection(), DIRECTION_LEFT_TO_RIGHT); + } else { + EXPECT_EQ(dragEvent_->GetDragDirection(), DIRECTION_RIGHT_TO_LEFT); + } + } else { + if (currentPos.y > startPos.y) { + EXPECT_EQ(dragEvent_->GetDragDirection(), DIRECTION_TOP_TO_BOTTOM); + } else { + EXPECT_EQ(dragEvent_->GetDragDirection(), DIRECTION_BOTTOM_TO_TOP); + } + } +} + +/** + * @tc.name: DragEventGetDeltaX_001 + * @tc.desc: Verify GetDeltaX function, equal. + * @tc.type: FUNC + * @tc.require: SR000DRSH4 + */ +HWTEST_F(DragEventTest, DragEventGetDeltaX_001, TestSize.Level0) +{ + if (dragEvent_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + EXPECT_EQ(dragEvent_->GetDeltaX(), NEW_POS.x - LAST_POS.x); +} + +/** + * @tc.name: DragEventGetDeltaY_001 + * @tc.desc: Verify GetDeltaY function, equal. + * @tc.type: FUNC + * @tc.require: SR000DRSH4 + */ +HWTEST_F(DragEventTest, DragEventGetDeltaY_001, TestSize.Level0) +{ + if (dragEvent_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + EXPECT_EQ(dragEvent_->GetDeltaY(), NEW_POS.y - LAST_POS.y); +} +} // namespace OHOS diff --git a/test/unittest/events/event_bubble_unit_test.cpp b/test/unittest/events/event_bubble_unit_test.cpp new file mode 100755 index 0000000..5c95a8d --- /dev/null +++ b/test/unittest/events/event_bubble_unit_test.cpp @@ -0,0 +1,438 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#if ENABLE_DEBUG +#include "common/graphic_startup.h" +#include "common/screen.h" +#include "components/root_view.h" +#include "common/task_manager.h" +#include "common/input_device_manager.h" +#include "core/render_manager.h" +#include "dock/screen_device_proxy.h" +#include "components/ui_label_button.h" +#include "components/ui_scroll_view.h" +#include +#include "file.h" +#include "font/ui_font.h" +#include +#include "graphic_config.h" +#include "graphic_log.h" +#include "imgdecode/cache_manager.h" +#include "layout/grid_layout.h" +#include + +using namespace testing::ext; + +enum TestEventFlag { + FLAG0, + FLAG1 +}; + +namespace { +uint8_t REGISTER_POINT_FLAG = FLAG0; +uint8_t REGISTER_KEY_FLAG = FLAG0; +uint8_t CLICK_FLAG = FLAG0; +uint8_t LONG_PRESS_FLAG = FLAG0; +uint8_t PRESS_FLAG = FLAG0; +uint8_t DRAG_FLAG = FLAG0; +uint8_t KEY_FLAG = FLAG0; +} + +namespace OHOS { +class TestEventBubbleView : public UIView, public RootView::OnKeyActListener { +public: + bool OnLongPressEvent(const LongPressEvent& event) override + { + longPressEventFlag_ = FLAG1; + return true; + } + + bool OnClickEvent(const ClickEvent& event) override + { + clickEventFlag_ = FLAG1; + return true; + } + + bool OnDragEvent(const DragEvent& event) override + { + dragEventFlag_ = FLAG1; + return true; + } + + bool OnKeyAct(UIView& view, const KeyEvent& event) override + { + KEY_FLAG = FLAG1; + return true; + } + +private: + bool longPressEventFlag_; + bool clickEventFlag_; + bool dragEventFlag_; +}; + +class EventBubbleTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + static void TestApp(); + static void SetUpTestview(TestEventBubbleView *testView, bool touchable, bool draggable); + static void DeleteChildren(UIView* view); + + static RootView* rootView_; + static GridLayout* layout_; + static TestEventBubbleView* clickView_; + static TestEventBubbleView* dragView_; + static TestEventBubbleView* longPressView_; + static TestEventBubbleView* keyView_; + static TestEventBubbleView* unTouchView_; +}; + +class TestOnClickListener : public UIView::OnClickListener { +public: + explicit TestOnClickListener(bool isConsume) : isConsume_(isConsume) {} + virtual ~TestOnClickListener() {} + virtual bool OnClick(UIView& view, const ClickEvent& event) + { + CLICK_FLAG = FLAG1; + return isConsume_; + } + +private: + bool isConsume_; +}; + +class TestOnLongPressListener : public UIView::OnLongPressListener { +public: + explicit TestOnLongPressListener(bool isConsume) : isConsume_(isConsume) {} + virtual ~TestOnLongPressListener() {} + virtual bool OnLongPress(UIView& view, const LongPressEvent& event) + { + LONG_PRESS_FLAG = FLAG1; + return isConsume_; + } + +private: + bool isConsume_; +}; + + +class TestOnTouchListener : public UIView::OnTouchListener { +public: + explicit TestOnTouchListener(bool isConsume) : isConsume_(isConsume) + { + } + virtual ~TestOnTouchListener() {} + virtual bool OnPress(UIView& view, const PressEvent& event) + { + PRESS_FLAG = FLAG1; + return isConsume_; + } + +private: + bool isConsume_; +}; + +class TestOnDragListener : public UIView::OnDragListener { +public: + explicit TestOnDragListener(bool isConsume) : isConsume_(isConsume) + { + } + virtual ~TestOnDragListener() {} + virtual bool OnDrag(DragEvent& event) + { + DRAG_FLAG = FLAG1; + return isConsume_; + } + +private: + bool isConsume_; +}; + +RootView* EventBubbleTest::rootView_ = nullptr; +GridLayout* EventBubbleTest::layout_ = nullptr; +TestEventBubbleView* EventBubbleTest::clickView_ = nullptr; +TestEventBubbleView* EventBubbleTest::dragView_ = nullptr; +TestEventBubbleView* EventBubbleTest::longPressView_ = nullptr; +TestEventBubbleView* EventBubbleTest::keyView_ = nullptr; +TestEventBubbleView* EventBubbleTest::unTouchView_ = nullptr; + +static void InitHal() +{ + ScreenDevice* display = new ScreenDevice(); + ScreenDeviceProxy::GetInstance()->SetDevice(display); + ScreenDeviceProxy::GetInstance()->SetScreenSize(HORIZONTAL_RESOLUTION, VERTICAL_RESOLUTION); +} + +void EventBubbleTest::SetUpTestCase(void) +{ + GraphicStartUp::Init(); + InitHal(); + TestApp(); +} + +void EventBubbleTest::TestApp() +{ + rootView_ = RootView::GetInstance(); + rootView_->SetTouchable(true); + rootView_->SetPosition(0, 0, Screen::GetInstance().GetWidth(), Screen::GetInstance().GetHeight()); + layout_ = new GridLayout(); + layout_->SetPosition(0, 0, Screen::GetInstance().GetWidth(), Screen::GetInstance().GetHeight()); + rootView_->Add(layout_); + layout_->SetLayoutDirection(LAYOUT_VER); + layout_->SetRows(6); /* 6:rows */ + layout_->SetCols(1); + + clickView_ = new TestEventBubbleView(); + SetUpTestview(clickView_, true, false); + + longPressView_ = new TestEventBubbleView(); + SetUpTestview(longPressView_, true, false); + + dragView_ = new TestEventBubbleView(); + SetUpTestview(dragView_, true, true); + + unTouchView_ = new TestEventBubbleView(); + SetUpTestview(unTouchView_, false, false); + + keyView_ = new TestEventBubbleView(); + RootView::GetInstance()->SetOnKeyActListener(keyView_); + SetUpTestview(keyView_, true, false); + + layout_->LayoutChildren(); + rootView_->Invalidate(); +} + +void EventBubbleTest::SetUpTestview(TestEventBubbleView *testView, bool touchable, bool draggable) +{ + layout_->Add(testView); + testView->Resize(HORIZONTAL_RESOLUTION, VERTICAL_RESOLUTION / 7); /* 7:ratio */ + testView->SetTouchable(touchable); + testView->SetDraggable(draggable); +} + +void EventBubbleTest::DeleteChildren(UIView* view) +{ + if (view == nullptr) { + return; + } + while (view != nullptr) { + UIView* tempView = view; + view = view->GetNextSibling(); + if (tempView->IsViewGroup()) { + DeleteChildren(static_cast(tempView)->GetChildrenHead()); + } + if (tempView->GetParent()) { + static_cast(tempView->GetParent())->Remove(tempView); + } + delete tempView; + } +} + +void EventBubbleTest::TearDownTestCase(void) +{ + DeleteChildren(layout_); + layout_ = nullptr; +} + +/** + * @tc.name: Graphic_EventBubbleTest_Test_GetExtraMsg_SetExtraMsg_007 + * @tc.desc: Verify GetExtraMsg, SetExtraMsg function, equal. + * @tc.type: FUNC + * @tc.require: AR000F4E5C + */ +HWTEST_F(EventBubbleTest, Graphic_EventBubbleTest_Test_GetExtraMsg_SetExtraMsg_007, TestSize.Level0) +{ + /* test for GetExtraMsg */ + TestEventBubbleView* view = new TestEventBubbleView(); + EXPECT_EQ(view->GetExtraMsg(), nullptr); + + /* test for SetExtraMsg */ + UIView::ViewExtraMsg *extraMsg = new UIView::ViewExtraMsg(); + extraMsg->elementPtr = malloc(sizeof(char)); + view->SetExtraMsg(extraMsg); + EXPECT_EQ(view->GetExtraMsg()->elementPtr, extraMsg->elementPtr); + free(extraMsg->elementPtr); + delete extraMsg; + delete view; +} + +/** + * @tc.name: Graphic_EventBubbleTest_Test_UIView_GetTargetView_008 + * @tc.desc: Verify UIView::GetTargetView equal. + * @tc.type: FUNC + * @tc.require: AR000F4E5C + */ +HWTEST_F(EventBubbleTest, Graphic_EventBubbleTest_Test_UIViewGroup_GetTargetView_008, TestSize.Level0) +{ + if ((clickView_ == nullptr) || (rootView_ == nullptr)) { + return; + } + /* test for touchable view */ + /* 2:ratio, 2:ratio */ + Point clickPoint = {(int16_t)(clickView_->GetRect().GetX() + clickView_->GetWidth() / 2), + (int16_t)(clickView_->GetRect().GetY() + clickView_->GetHeight() / 2)}; + UIView* curView = nullptr; + UIView* targetView = nullptr; + rootView_->GetTargetView(clickPoint, &curView, &targetView); + EXPECT_EQ(curView, clickView_); + EXPECT_EQ(targetView, clickView_); + + /* test for unTouchable view */ + if (unTouchView_ == nullptr) { + return; + } + /* 2:ratio, 2:ratio */ + Point clickPoint2 = {(int16_t)(unTouchView_->GetRect().GetX() + unTouchView_->GetWidth() / 2), + (int16_t)(unTouchView_->GetRect().GetY() + unTouchView_->GetHeight() / 2)}; + curView = nullptr; + targetView = nullptr; + rootView_->GetTargetView(clickPoint2, &curView, &targetView); + + EXPECT_EQ(curView, rootView_); + EXPECT_EQ(targetView, unTouchView_); +} + +/** + * @tc.name: Graphic_EventBubbleTest_Test_UIView_GetTargetView_009 + * @tc.desc: Verify UIView::GetTargetView equal. + * @tc.type: FUNC + * @tc.require: AR000F4E5C + */ +HWTEST_F(EventBubbleTest, Graphic_EventBubbleTest_Test_UIView_GetTargetView_009, TestSize.Level0) +{ + if ((clickView_ == nullptr) || (rootView_ == nullptr)) { + return; + } + /* test for touchable view */ + /* 2:ratio, 2:ratio */ + Point clickPoint = {(int16_t)(clickView_->GetRect().GetX() + clickView_->GetWidth() / 2), + (int16_t)(clickView_->GetRect().GetY() + clickView_->GetHeight() / 2)}; + UIView* curView = nullptr; + UIView* targetView = nullptr; + clickView_->GetTargetView(clickPoint, &curView, &targetView); + EXPECT_EQ(curView, clickView_); + EXPECT_EQ(targetView, clickView_); + + /* test for unTouchable view */ + if (unTouchView_ == nullptr) { + return; + } + /* 2:ratio, 2:ratio */ + Point clickPoint2 = {(int16_t)(unTouchView_->GetRect().GetX() + unTouchView_->GetWidth() / 2), + (int16_t)(unTouchView_->GetRect().GetY() + unTouchView_->GetHeight() / 2)}; + curView = nullptr; + targetView = nullptr; + unTouchView_->GetTargetView(clickPoint2, &curView, &targetView); + + EXPECT_EQ(curView, nullptr); + EXPECT_EQ(targetView, unTouchView_); +} + +/** + * @tc.name: Graphic_EventBubbleTest_Test_UIView_OnClick_010 + * @tc.desc: Verify UIView::OnClick equal. + * @tc.type: FUNC + * @tc.require: SR000F3PEA + */ +HWTEST_F(EventBubbleTest, Graphic_EventBubbleTest_Test_UIView_OnClick_010, TestSize.Level0) +{ + /* 2:ratio, 2:ratio */ + Point clickPoint = {(int16_t)(clickView_->GetRect().GetX() + clickView_->GetWidth() / 2), + (int16_t)(clickView_->GetRect().GetY() + clickView_->GetHeight() / 2)}; + ClickEvent clickEvent(clickPoint); + + /* test for not comsumed view */ + TestOnClickListener* clickListener = new TestOnClickListener(false); + bool ret = clickListener->OnClick(*clickView_, clickEvent); + EXPECT_EQ(ret, false); + EXPECT_EQ(CLICK_FLAG, true); + delete clickListener; + + /* test for comsumed view */ + CLICK_FLAG = false; + TestOnClickListener* clickListener2 = new TestOnClickListener(true); + ret = clickListener->OnClick(*clickView_, clickEvent); + EXPECT_EQ(ret, true); + EXPECT_EQ(CLICK_FLAG, true); + delete clickListener2; +} + +/** + * @tc.name: Graphic_EventBubbleTest_Test_UIView_OnLongPress_011 + * @tc.desc: Verify UIView::OnLongPress equal. + * @tc.type: FUNC + * @tc.require: AR000F4E5C + */ +HWTEST_F(EventBubbleTest, Graphic_EventBubbleTest_Test_UIView_OnLongPress_011, TestSize.Level0) +{ + /* 2:ratio, 2:ratio */ + Point longPressPoint = {(int16_t)(longPressView_->GetRect().GetX() + longPressView_->GetWidth() / 2), + (int16_t)(longPressView_->GetRect().GetY() + longPressView_->GetHeight() / 2)}; + uint32_t pressTimeStamp = 2; /* 2:second */ + LongPressEvent longPressEvent(longPressPoint, pressTimeStamp); + + /* test for not comsumed view */ + TestOnLongPressListener* longPressListener = new TestOnLongPressListener(false); + bool ret = longPressListener->OnLongPress(*clickView_, longPressEvent); + EXPECT_EQ(ret, false); + EXPECT_EQ(LONG_PRESS_FLAG, true); + delete longPressListener; + + /* test for comsumed view */ + LONG_PRESS_FLAG = false; + TestOnLongPressListener* longPressListener2 = new TestOnLongPressListener(true); + ret = longPressListener2->OnLongPress(*clickView_, longPressEvent); + EXPECT_EQ(ret, true); + EXPECT_EQ(LONG_PRESS_FLAG, true); + delete longPressListener2; +} + +/** + * @tc.name: Graphic_EventBubbleTest_Test_UIView_OnDrag_012 + * @tc.desc: Verify UIView::OnDrag equal. + * @tc.type: FUNC + * @tc.require: AR000F4E5C + */ +HWTEST_F(EventBubbleTest, Graphic_EventBubbleTest_Test_UIView_OnDrag_012, TestSize.Level0) +{ + /* 2:ratio, 2:ratio */ + Point dragCurPoint = {(int16_t)(dragView_->GetRect().GetX() + dragView_->GetWidth() / 2), + (int16_t)(dragView_->GetRect().GetY() + dragView_->GetHeight() / 2)}; + /* 3:ratio, 3:ratio */ + Point dragLastPoint = {(int16_t)(dragView_->GetRect().GetX() + dragView_->GetWidth() / 3), + (int16_t)(dragView_->GetRect().GetY() + dragView_->GetHeight() / 3)}; + Point dragLen; + dragLen.x = dragCurPoint.x - dragLastPoint.x; + dragLen.y = dragCurPoint.y - dragLastPoint.y; + DragEvent dragEvent(dragCurPoint, dragLastPoint, dragLen); + + /* test for not comsumed view */ + TestOnDragListener* dragListener = new TestOnDragListener(false); + bool ret = dragListener->OnDrag(dragEvent); + EXPECT_EQ(ret, false); + EXPECT_EQ(DRAG_FLAG, true); + delete dragListener; + + /* test for comsumed view */ + LONG_PRESS_FLAG = false; + TestOnDragListener* dragListener2 = new TestOnDragListener(true); + ret = dragListener2->OnDrag(dragEvent); + EXPECT_EQ(ret, true); + EXPECT_EQ(DRAG_FLAG, true); + delete dragListener2; +} +} // namespace OHOS +#endif // ENABLE_DEBUG diff --git a/test/unittest/events/event_unit_test.cpp b/test/unittest/events/event_unit_test.cpp new file mode 100755 index 0000000..4955f41 --- /dev/null +++ b/test/unittest/events/event_unit_test.cpp @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "events/event.h" + +#include +#include + +using namespace testing::ext; +namespace OHOS { +namespace { + const Point INIT_POS = { 100, 200 }; + const TimeType TIME_STAMP = 10; +} + +class EventTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + static Event* event_; +}; + +Event* EventTest::event_ = nullptr; + +void EventTest::SetUpTestCase(void) +{ + if (event_ == nullptr) { + event_ = new Event(INIT_POS); + } +} + +void EventTest::TearDownTestCase(void) +{ + if (event_ != nullptr) { + delete event_; + event_ = nullptr; + } +} +/** + * @tc.name: EventConstructor_001 + * @tc.desc: Verify Constructor function, equal. + * @tc.type: FUNC + * @tc.require: SR000DRSH4 + */ +HWTEST_F(EventTest, EventConstructor_001, TestSize.Level0) +{ + if (event_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + EXPECT_EQ(event_->GetCurrentPos().x, INIT_POS.x); + EXPECT_EQ(event_->GetCurrentPos().y, INIT_POS.y); +} + +/** + * @tc.name: EventSetTimeStamp_001 + * @tc.desc: Verify SetTimeStamp function, equal. + * @tc.type: FUNC + * @tc.require: SR000DRSH4 + */ +HWTEST_F(EventTest, EventSetTimeStamp_001, TestSize.Level0) +{ + if (event_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + event_->SetTimeStamp(TIME_STAMP); + EXPECT_EQ(event_->GetTimeStamp(), TIME_STAMP); +} +} // namespace OHOS \ No newline at end of file diff --git a/test/unittest/events/key_event_unit_test.cpp b/test/unittest/events/key_event_unit_test.cpp new file mode 100755 index 0000000..781ab39 --- /dev/null +++ b/test/unittest/events/key_event_unit_test.cpp @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "events/key_event.h" + +#include +#include + +using namespace testing::ext; +namespace OHOS { +namespace { + const uint16_t KEY_ID = 10; + const uint16_t STATE = 20; +} + +class KeyEventTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + static KeyEvent* keyEvent_; +}; + +KeyEvent* KeyEventTest::keyEvent_ = nullptr; + +void KeyEventTest::SetUpTestCase(void) +{ + if (keyEvent_ == nullptr) { + keyEvent_ = new KeyEvent(KEY_ID, STATE); + } +} + +void KeyEventTest::TearDownTestCase(void) +{ + if (keyEvent_ != nullptr) { + delete keyEvent_; + keyEvent_ = nullptr; + } +} + +/** + * @tc.name: KeyEventGetKeyId_001 + * @tc.desc: Verify GetKeyId function, equal. + * @tc.type: FUNC + * @tc.require: SR000DRSH4 + */ +HWTEST_F(KeyEventTest, KeyEventGetKeyId_001, TestSize.Level0) +{ + if (keyEvent_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + EXPECT_EQ(keyEvent_->GetKeyId(), KEY_ID); +} + +/** + * @tc.name: KeyEventGetState_001 + * @tc.desc: Verify GetState function, equal. + * @tc.type: FUNC + * @tc.require: SR000DRSH4 + */ +HWTEST_F(KeyEventTest, KeyEventGetState_001, TestSize.Level0) +{ + if (keyEvent_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + EXPECT_EQ(keyEvent_->GetState(), STATE); +} +} // namespace OHOS \ No newline at end of file diff --git a/test/unittest/events/long_press_event_unit_test.cpp b/test/unittest/events/long_press_event_unit_test.cpp new file mode 100755 index 0000000..d90a5d4 --- /dev/null +++ b/test/unittest/events/long_press_event_unit_test.cpp @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "events/long_press_event.h" + +#include +#include + +using namespace testing::ext; +namespace OHOS { +namespace { + const Point INIT_POS = {100, 200}; + const TimeType TIME_STAMP = 10; +} + +class LongPressEventTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + static LongPressEvent* longPressEvent_; +}; + +LongPressEvent* LongPressEventTest::longPressEvent_ = nullptr; + +void LongPressEventTest::SetUpTestCase(void) +{ + if (longPressEvent_ == nullptr) { + longPressEvent_ = new LongPressEvent(INIT_POS, TIME_STAMP); + } +} + +void LongPressEventTest::TearDownTestCase(void) +{ + if (longPressEvent_ != nullptr) { + delete longPressEvent_; + longPressEvent_ = nullptr; + } +} +/** + * @tc.name: LongPressEventConstructor_001 + * @tc.desc: Verify GetCurrentPos function, equal. + * @tc.type: FUNC + * @tc.require: SR000DRSH4 + */ +HWTEST_F(LongPressEventTest, LongPressEventConstructor_001, TestSize.Level0) +{ + if (longPressEvent_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + EXPECT_EQ(longPressEvent_->GetCurrentPos().x, INIT_POS.x); + EXPECT_EQ(longPressEvent_->GetCurrentPos().y, INIT_POS.y); +} + +/** + * @tc.name: LongPressEventConstructor_002 + * @tc.desc: Verify GetCurrentPos function, equal. + * @tc.type: FUNC + * @tc.require: SR000DRSH4 + */ +HWTEST_F(LongPressEventTest, LongPressEventConstructor_002, TestSize.Level0) +{ + if (longPressEvent_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + EXPECT_EQ(longPressEvent_->GetTimeStamp(), TIME_STAMP); +} +} // namespace OHOS diff --git a/test/unittest/events/press_event_unit_test.cpp b/test/unittest/events/press_event_unit_test.cpp new file mode 100755 index 0000000..036ec47 --- /dev/null +++ b/test/unittest/events/press_event_unit_test.cpp @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "events/press_event.h" + +#include +#include + +using namespace testing::ext; +namespace OHOS { +namespace { + const Point INIT_POS = { 100, 200 }; +} +class PressEventTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + static PressEvent* pressEvent_; +}; + +PressEvent* PressEventTest::pressEvent_ = nullptr; + +void PressEventTest::SetUpTestCase(void) +{ + if (pressEvent_ == nullptr) { + pressEvent_ = new PressEvent(INIT_POS); + } +} + +void PressEventTest::TearDownTestCase(void) +{ + if (pressEvent_ != nullptr) { + delete pressEvent_; + pressEvent_ = nullptr; + } +} + +/** + * @tc.name: PressEventConstructor_001 + * @tc.desc: Verify Constructor function, equal. + * @tc.type: FUNC + * @tc.require: SR000DRSH4 + */ +HWTEST_F(PressEventTest, PressEventConstructor_001, TestSize.Level0) +{ + if (pressEvent_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + EXPECT_EQ(pressEvent_->GetCurrentPos().x, INIT_POS.x); + EXPECT_EQ(pressEvent_->GetCurrentPos().y, INIT_POS.y); +} +} // namespace OHOS \ No newline at end of file diff --git a/test/unittest/events/release_event_unit_test.cpp b/test/unittest/events/release_event_unit_test.cpp new file mode 100755 index 0000000..2b4875e --- /dev/null +++ b/test/unittest/events/release_event_unit_test.cpp @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "events/release_event.h" + +#include +#include + +using namespace testing::ext; +namespace OHOS { +namespace { + const Point INIT_POS = { 100, 200 }; +} +class ReleaseEventTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + static ReleaseEvent* releaseEvent_; +}; + +ReleaseEvent* ReleaseEventTest::releaseEvent_ = nullptr; + +void ReleaseEventTest::SetUpTestCase(void) +{ + if (releaseEvent_ == nullptr) { + releaseEvent_ = new ReleaseEvent(INIT_POS); + } +} + +void ReleaseEventTest::TearDownTestCase(void) +{ + if (releaseEvent_ != nullptr) { + delete releaseEvent_; + releaseEvent_ = nullptr; + } +} + +/** + * @tc.name: ReleaseEventConstructor_001 + * @tc.desc: Verify GetCurrentPos function, equal. + * @tc.type: FUNC + * @tc.require: SR000DRSH4 + */ +HWTEST_F(ReleaseEventTest, ReleaseEventConstructor_001, TestSize.Level0) +{ + EXPECT_EQ(releaseEvent_->GetCurrentPos().x, INIT_POS.x); + EXPECT_EQ(releaseEvent_->GetCurrentPos().y, INIT_POS.y); +} +} // namespace OHOS \ No newline at end of file diff --git a/test/unittest/events/virtual_device_event_unit_test.cpp b/test/unittest/events/virtual_device_event_unit_test.cpp new file mode 100755 index 0000000..f1560af --- /dev/null +++ b/test/unittest/events/virtual_device_event_unit_test.cpp @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "events/virtual_device_event.h" + +#include +#include + +using namespace testing::ext; +namespace OHOS { +namespace { + const uint16_t TYPE = 10; + const uint16_t VALUE = 20; +} + +class VirtualDeviceEventTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + static VirtualDeviceEvent* virtualDeviceEvent_; +}; + +VirtualDeviceEvent* VirtualDeviceEventTest::virtualDeviceEvent_ = nullptr; + +void VirtualDeviceEventTest::SetUpTestCase(void) +{ + if (virtualDeviceEvent_ == nullptr) { + virtualDeviceEvent_ = new VirtualDeviceEvent(TYPE, VALUE); + } +} + +void VirtualDeviceEventTest::TearDownTestCase(void) +{ + if (virtualDeviceEvent_ != nullptr) { + delete virtualDeviceEvent_; + virtualDeviceEvent_ = nullptr; + } +} +/** + * @tc.name: VirtualDeviceEventConstructor_001 + * @tc.desc: Verify Constructor function, equal. + * @tc.type: FUNC + * @tc.require: SR000DRSH4 + */ +HWTEST_F(VirtualDeviceEventTest, VirtualDeviceEventConstructor_001, TestSize.Level0) +{ + if (virtualDeviceEvent_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + EXPECT_EQ(virtualDeviceEvent_->GetType(), TYPE); +} + +/** + * @tc.name: VirtualDeviceEventConstructor_002 + * @tc.desc: Verify Constructor function, equal. + * @tc.type: FUNC + * @tc.require: SR000DRSH4 + */ +HWTEST_F(VirtualDeviceEventTest, VirtualDeviceEventConstructor_002, TestSize.Level0) +{ + if (virtualDeviceEvent_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + EXPECT_EQ(virtualDeviceEvent_->GetState(), VALUE); +} +} // namespace OHOS \ No newline at end of file diff --git a/test/unittest/font/ui_font_unit_test.cpp b/test/unittest/font/ui_font_unit_test.cpp new file mode 100755 index 0000000..01c4459 --- /dev/null +++ b/test/unittest/font/ui_font_unit_test.cpp @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "font/ui_font.h" +#include "font/ui_font_vector.h" + +#include +#include + +using namespace testing::ext; +namespace OHOS { +namespace { + constexpr uint8_t FONT_ERROR_RET = 0xFF; +} +class UIFontTest : public testing::Test { +public: + UIFontTest() {} + virtual ~UIFontTest() {} + + static void SetUpTestCase() {}; + static void TearDownTestCase() {}; +}; + +/** + * @tc.name: Graphic_Font_Test_GetInstance_001 + * @tc.desc: Verify UIFont::GetInstance function, not nullptr. + * @tc.type: FUNC + * @tc.require: SR000F3PEK + */ +HWTEST_F(UIFontTest, Graphic_Font_Test_GetInstance_001, TestSize.Level0) +{ + UIFont* font = UIFont::GetInstance(); + EXPECT_NE(font, nullptr); +} + +/** + * @tc.name: Graphic_Font_Test_GetInstance_002 + * @tc.desc: Verify UIFont::GetInstance function, equal. + * @tc.type: FUNC + * @tc.require: SR000F3PEK + */ +HWTEST_F(UIFontTest, Graphic_Font_Test_GetInstance_002, TestSize.Level0) +{ + UIFont* font = UIFont::GetInstance(); + bool ret = UIFont::GetInstance()->IsVectorFont(); + EXPECT_EQ(ret, true); +} + +/** + * @tc.name: Graphic_Font_Test_RegisterFontInfo_001 + * @tc.desc: Verify UIFont::RegisterFontInfo function, error font name. + * @tc.type: FUNC + * @tc.require: AR000F3R7C + */ +HWTEST_F(UIFontTest, Graphic_Font_Test_RegisterFontInfo_001, TestSize.Level0) +{ + uint8_t ret = UIFont::GetInstance()->RegisterFontInfo("error"); + EXPECT_EQ(ret, FONT_ERROR_RET); +} + +/** + * @tc.name: Graphic_Font_Test_RegisterFontInfo_002 + * @tc.desc: Verify UIFont::RegisterFontInfo function, error font file path. + * @tc.type: FUNC + * @tc.require: AR000F3R7C + */ +HWTEST_F(UIFontTest, Graphic_Font_Test_RegisterFontInfo_002, TestSize.Level0) +{ + uint8_t ret = UIFont::GetInstance()->RegisterFontInfo("ui-font.ttf"); + EXPECT_EQ(ret, FONT_ERROR_RET); +} + +/** + * @tc.name: Graphic_Font_Test_UnregisterFontInfo_001 + * @tc.desc: Verify UIFont::UnregisterFontInfo function, error font name. + * @tc.type: FUNC + * @tc.require: AR000F3R7C + */ +HWTEST_F(UIFontTest, Graphic_Font_Test_UnregisterFontInfo_001, TestSize.Level0) +{ + uint8_t ret = UIFont::GetInstance()->UnregisterFontInfo("error font name"); + EXPECT_EQ(ret, FONT_ERROR_RET); +} + +/** + * @tc.name: Graphic_Font_Test_UnregisterFontInfo_002 + * @tc.desc: Verify UIFont::UnregisterFontInfo function, unregister fontsTable. + * @tc.type: FUNC + * @tc.require: AR000F3R7C + */ +HWTEST_F(UIFontTest, Graphic_Font_Test_UnregisterFontInfo_002, TestSize.Level0) +{ + const UITextLanguageFontParam* fontsTable = nullptr; + uint8_t ret = UIFont::GetInstance()->UnregisterFontInfo(fontsTable, 0); + EXPECT_EQ(ret, 0); +} +} // namespace OHOS \ No newline at end of file diff --git a/test/unittest/image/image_load_unit_test.cpp b/test/unittest/image/image_load_unit_test.cpp new file mode 100755 index 0000000..166d557 --- /dev/null +++ b/test/unittest/image/image_load_unit_test.cpp @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "imgdecode/image_load.h" + +#include "file.h" +#include "mem_api.h" +#include "test_resource_config.h" + +#include +#include + +using namespace testing::ext; + +namespace { +constexpr uint8_t MAX_FILE_NUM = 2; +} + +namespace OHOS { +class ImageTest : public testing::Test { +public: + static void SetUpTestCase(); + static void TearDownTestCase(); + + static void ResetFds(); + static void InitImageInfo(ImageInfo& info); + + static int32_t imgResFds_[MAX_FILE_NUM]; + static ImageInfo imgInfos_[MAX_FILE_NUM]; + static uint32_t sizes_[MAX_FILE_NUM]; +}; + +int32_t ImageTest::imgResFds_[MAX_FILE_NUM]; +ImageInfo ImageTest::imgInfos_[MAX_FILE_NUM]; +uint32_t ImageTest::sizes_[MAX_FILE_NUM]; + +void ImageTest::SetUpTestCase() +{ + const char* paths[] = { + IMAGE_DIR "A001_001.bin", + IMAGE_DIR "A001_002.bin" + }; + for (int i = 0; i < MAX_FILE_NUM; i++) { + imgResFds_[i] = open(paths[i], O_RDONLY); + struct stat fileStat; + fstat(imgResFds_[i], &fileStat); + sizes_[i] = fileStat.st_size - sizeof(ImageHeader); + read(imgResFds_[i], &imgInfos_[i].header, sizeof(ImageHeader)); + } +} + +void ImageTest::TearDownTestCase() +{ + for (int i = 0; i < MAX_FILE_NUM; i++) { + close(imgResFds_[i]); + } +} + +void ImageTest::ResetFds() +{ + for (int i = 0; i < MAX_FILE_NUM; i++) { + lseek(imgResFds_[i], sizeof(ImageHeader), SEEK_SET); + } +} + +/** + * @tc.name: Graphic_Image_Test_GetImageInfo_001 + * @tc.desc: Verify ImageLoad::GetImageInfo function, invalid param. + * @tc.type: FUNC + * @tc.require: AR000F74ST + */ +HWTEST_F(ImageTest, Graphic_Image_Test_GetImageInfo_001, TestSize.Level0) +{ + /* test invalid fd */ + bool ret = ImageLoad::GetImageInfo(-1, sizes_[0], imgInfos_[0]); + EXPECT_EQ(ret, false); + + /* test invalid size */ + ret = ImageLoad::GetImageInfo(imgResFds_[0], 0, imgInfos_[0]); + EXPECT_EQ(ret, false); +} + +/** + * @tc.name: Graphic_Image_Test_GetImageInfo_002 + * @tc.desc: Verify ImageLoad::GetImageInfo function, invalid compress mode. + * @tc.type: FUNC + * @tc.require: SR000ERCR7 + */ +HWTEST_F(ImageTest, Graphic_Image_Test_GetImageInfo_002, TestSize.Level0) +{ + ResetFds(); + + auto info = imgInfos_[0]; + info.header.compressMode = COMPRESS_MODE_BLOCK_ALG; + bool ret = ImageLoad::GetImageInfo(imgResFds_[0], sizes_[0], info); + EXPECT_EQ(ret, false); +} + +/** + * @tc.name: Graphic_Image_Test_GetImageInfo_003 + * @tc.desc: Verify ImageLoad::GetImageInfo function, multi-images. + * @tc.type: FUNC + * @tc.require: AR000F74ST + */ +HWTEST_F(ImageTest, Graphic_Image_Test_GetImageInfo_003, TestSize.Level0) +{ + ResetFds(); + + for (int32_t i = 0; i < MAX_FILE_NUM; i++) { + bool success = ImageLoad::GetImageInfo(imgResFds_[i], sizes_[i], imgInfos_[i]); + if (success) { + ImageCacheFree(imgInfos_[i]); + } else { + EXPECT_EQ(success, true); + } + } +} +} // namespace OHOS \ No newline at end of file diff --git a/test/unittest/layout/list_layout_unit_test.cpp b/test/unittest/layout/list_layout_unit_test.cpp new file mode 100755 index 0000000..e873c36 --- /dev/null +++ b/test/unittest/layout/list_layout_unit_test.cpp @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "layout/list_layout.h" + +#include +#include + +using namespace testing::ext; +namespace OHOS { +class ListLayoutTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + static ListLayout* listLayout_; +}; + +ListLayout* ListLayoutTest::listLayout_ = nullptr; + +void ListLayoutTest::SetUpTestCase(void) +{ + if (listLayout_ == nullptr) { + listLayout_ = new ListLayout(); + } +} + +void ListLayoutTest::TearDownTestCase(void) +{ + if (listLayout_ != nullptr) { + delete listLayout_; + listLayout_ = nullptr; + } +} +/** + * @tc.name: ListLayoutSetDirection_001 + * @tc.desc: Verify SetDirection function, equal. + * @tc.type: FUNC + * @tc.require: AR000DSMR7 + */ +HWTEST_F(ListLayoutTest, ListLayoutSetDirection_001, TestSize.Level0) +{ + if (listLayout_ == nullptr) { + EXPECT_EQ(1, 0); + return; + } + EXPECT_EQ(listLayout_->GetDirection(), 1); + + listLayout_->SetDirection(0); + EXPECT_EQ(listLayout_->GetDirection(), 0); +} +} // namespace OHOS \ No newline at end of file diff --git a/test/unittest/render/render_uni_test.cpp b/test/unittest/render/render_uni_test.cpp new file mode 100644 index 0000000..45bae1f --- /dev/null +++ b/test/unittest/render/render_uni_test.cpp @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include "common/graphic_startup.h" +#include "common/task_manager.h" +#include "components/root_view.h" +#include "components/ui_view.h" +#include "components/ui_view_group.h" +#include "window/window.h" + +using namespace testing::ext; +namespace OHOS { +static uint16_t g_measureCount = 0; +class RenderTest : public testing::Test { +public: + RenderTest() {} + virtual ~RenderTest() {} + + static void SetUpTestCase() + { + GraphicStartUp::Init(); + } + static void TearDownTestCase() {} + + static void CreateDefaultWindow(RootView* rootView, int x, int y) + { + WindowConfig config = {}; + config.rect = rootView->GetRect(); + config.rect.SetPosition(x, y); + Window* window = Window::CreateWindow(config); + if (window != nullptr) { + window->BindRootView(rootView); + } + } + + static void DestoryWindow(RootView* rootView) + { + Window::DestoryWindow(rootView->GetBoundWindow()); + } +}; + +class UITestView : public UIView { +public: + UITestView() {} + virtual ~UITestView() {} + + void ReMeasure() override + { + g_measureCount++; + } +}; + +class UITestViewGroup : public UIViewGroup { +public: + UITestViewGroup() {} + virtual ~UITestViewGroup() {} + + void ReMeasure() override + { + g_measureCount++; + } +}; + +/** + * @tc.name: Graphic_RenderTest_Test_Measuer_001 + * @tc.desc: Verity measure call when render + * @tc.type: FUNC + * @tc.require: SR000FH555 + */ +HWTEST_F(RenderTest, Graphic_RenderTest_Test_Measuer_001, TestSize.Level0) +{ + RootView* rootView = RootView::GetWindowRootView(); + rootView->SetWidth(600); // 600: width + rootView->SetHeight(500); // 500: height + rootView->SetPosition(0, 0); + UITestView* view1 = new UITestView(); + UITestViewGroup* vg1 = new UITestViewGroup(); + rootView->Add(vg1); + vg1->Add(view1); + vg1->Invalidate(); + rootView->Add(vg1); + rootView->Invalidate(); + g_measureCount = 0; + + RenderTest::CreateDefaultWindow(rootView, 0, 0); + TaskManager::GetInstance()->TaskHandler(); + EXPECT_EQ(g_measureCount, 2); // 2: measure view + rootView->RemoveAll(); + delete view1; + delete vg1; + RenderTest::DestoryWindow(rootView); + RootView::DestoryWindowRootView(rootView); +} + +/** + * @tc.name: Graphic_RenderTest_Test_Measuer_02 + * @tc.desc: Verity measure call when view invisible + * @tc.type: FUNC + * @tc.require: AR000FH556 + */ +HWTEST_F(RenderTest, Graphic_RenderTest_Test_Measuer_02, TestSize.Level0) +{ + RootView* rootView = RootView::GetWindowRootView(); + rootView->SetWidth(600); // 600: width + rootView->SetHeight(500); // 500: height + rootView->SetPosition(0, 0); + UITestView* view1 = new UITestView(); + UITestViewGroup* vg1 = new UITestViewGroup(); + rootView->Add(vg1); + vg1->Add(view1); + // invisible view not need to measure + view1->SetVisible(false); + rootView->Invalidate(); + g_measureCount = 0; + + RenderTest::CreateDefaultWindow(rootView, 0, 0); + TaskManager::GetInstance()->TaskHandler(); + EXPECT_EQ(g_measureCount, 1); + + rootView->RemoveAll(); + delete view1; + delete vg1; + RenderTest::DestoryWindow(rootView); + RootView::DestoryWindowRootView(rootView); +} +} // namespace OHOS diff --git a/test/unittest/rotate/ui_rotate_input_unit_test.cpp b/test/unittest/rotate/ui_rotate_input_unit_test.cpp new file mode 100644 index 0000000..11f894e --- /dev/null +++ b/test/unittest/rotate/ui_rotate_input_unit_test.cpp @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "components/ui_abstract_progress.h" +#include "components/ui_list.h" +#include "components/ui_picker.h" +#include "components/ui_scroll_view.h" +#include "components/ui_slider.h" +#include "components/ui_swipe_view.h" +#include "components/ui_time_picker.h" +#include "dock/focus_manager.h" +#include +#include + +using namespace testing::ext; +namespace OHOS { +class UIRotateInputTest : public testing::Test { +public: + UIRotateInputTest() {} + virtual ~UIRotateInputTest() {} + + static void SetUpTestCase(){}; + static void TearDownTestCase(){}; +}; + +/** + * @tc.name: Graphic_RotateInput_Test_SetRotateFactor_001 + * @tc.desc: Verify UIRotateInput::SetRotateFactor function. + * @tc.type: FUNC + * @tc.require: SR000ERCQF + */ +HWTEST_F(UIRotateInputTest, Graphic_RotateInput_Test_SetRotateFactor_001, TestSize.Level0) +{ + UIScrollView* scroll = new UIScrollView(); + scroll->SetRotateFactor(10); // 10 : rotate factor + EXPECT_EQ(scroll->GetRotateFactor(), 10); + delete scroll; +} + +/** + * @tc.name: Graphic_RotateInput_Test_SetRotateFactor_002 + * @tc.desc: Verify UIRotateInput::SetRotateFactor function. + * @tc.type: FUNC + * @tc.require: SR000ERCQF + */ +HWTEST_F(UIRotateInputTest, Graphic_RotateInput_Test_SetRotateFactor_002, TestSize.Level0) +{ + UISwipeView* swipe = new UISwipeView(); + swipe->SetRotateFactor(10); // 10 : rotate factor + EXPECT_EQ(swipe->GetRotateFactor(), 10); + delete swipe; +} + +/** + * @tc.name: Graphic_RotateInput_Test_SetRotateFactor_003 + * @tc.desc: Verify UIRotateInput::SetRotateFactor function. + * @tc.type: FUNC + * @tc.require: SR000ERCQF + */ +HWTEST_F(UIRotateInputTest, Graphic_RotateInput_Test_SetRotateFactor_003, TestSize.Level0) +{ + UIList* list = new UIList(); + list->SetRotateFactor(10); // 10 : rotate factor + EXPECT_EQ(list->GetRotateFactor(), 10); + delete list; +} + +/** + * @tc.name: Graphic_RotateInput_Test_SetRotateFactor_004 + * @tc.desc: Verify UIRotateInput::SetRotateFactor function. + * @tc.type: FUNC + * @tc.require: SR000ERCQF + */ +HWTEST_F(UIRotateInputTest, Graphic_RotateInput_Test_SetRotateFactor_004, TestSize.Level0) +{ + UIList* list = new UIList(); + list->SetRotateFactor(20); // 10 : rotate factor + EXPECT_NE(list->GetRotateFactor(), 10); + delete list; +} + +/** + * @tc.name: Graphic_RotateInput_Test_SetRotateFactor_005 + * @tc.desc: Verify UIRotateInput::SetRotateFactor function. + * @tc.type: FUNC + * @tc.require: AR000EVI2R + */ +HWTEST_F(UIRotateInputTest, Graphic_RotateInput_Test_SetRotateFactor_005, TestSize.Level0) +{ + UISlider* slider = new UISlider(); + slider->SetRotateFactor(10); // 10 : rotate factor + EXPECT_EQ(slider->GetRotateFactor(), 10); + delete slider; +} + +/** + * @tc.name: Graphic_RotateInput_Test_RequestFocus_001 + * @tc.desc: Verify UIRotateInput::RequestFocus function. + * @tc.type: FUNC + * @tc.require: SR000FH553 + */ +HWTEST_F(UIRotateInputTest, Graphic_RotateInput_Test_RequestFocus_001, TestSize.Level0) +{ + UIScrollView* scroll = new UIScrollView(); + scroll->RequestFocus(); + EXPECT_EQ(scroll, FocusManager::GetInstance()->GetFocusedView()); + delete scroll; +} +/** + * @tc.name: Graphic_RotateInput_Test_RequestFocus_002 + * @tc.desc: Verify UIRotateInput::RequestFocus function. + * @tc.type: FUNC + * @tc.require: AR000FH554 + */ +HWTEST_F(UIRotateInputTest, Graphic_RotateInput_Test_RequestFocus_002, TestSize.Level0) +{ + UITimePicker* timePicker = new UITimePicker(); + timePicker->RequestFocus(); + EXPECT_EQ(timePicker, FocusManager::GetInstance()->GetFocusedView()); + delete timePicker; +} +/** + * @tc.name: Graphic_RotateInput_Test_RequestFocus_003 + * @tc.desc: Verify UIRotateInput::RequestFocus function. + * @tc.type: FUNC + * @tc.require: AR000FH554 + */ +HWTEST_F(UIRotateInputTest, Graphic_RotateInput_Test_RequestFocus_003, TestSize.Level0) +{ + UISwipeView* swipe = new UISwipeView(); + swipe->RequestFocus(); + EXPECT_EQ(swipe, FocusManager::GetInstance()->GetFocusedView()); + delete swipe; +} +/** + * @tc.name: Graphic_RotateInput_Test_RequestFocus_004 + * @tc.desc: Verify UIList::RequestFocus function. + * @tc.type: FUNC + * @tc.require: SR000ERCQG + */ +HWTEST_F(UIRotateInputTest, Graphic_RotateInput_Test_RequestFocus_004, TestSize.Level0) +{ + UIList* list = new UIList(); + list->RequestFocus(); + EXPECT_EQ(list, FocusManager::GetInstance()->GetFocusedView()); + delete list; +} +/** + * @tc.name: Graphic_RotateInput_Test_RequestFocus_005 + * @tc.desc: Verify UIRotateInput::RequestFocus function. + * @tc.type: FUNC + * @tc.require: AR000EVI2S + */ +HWTEST_F(UIRotateInputTest, Graphic_RotateInput_Test_RequestFocus_005, TestSize.Level0) +{ + UISlider* slider = new UISlider(); + slider->RequestFocus(); + EXPECT_EQ(slider, FocusManager::GetInstance()->GetFocusedView()); + delete slider; +} +} + diff --git a/test/unittest/themes/theme_manager_unit_test.cpp b/test/unittest/themes/theme_manager_unit_test.cpp new file mode 100755 index 0000000..660e0b2 --- /dev/null +++ b/test/unittest/themes/theme_manager_unit_test.cpp @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "themes/theme_manager.h" + +#include +#include + +using namespace testing::ext; +namespace OHOS { +class ThemeManagerTest : public testing::Test { +public: + static void SetUpTestCase(void) {} + static void TearDownTestCase(void) {} +}; + +/** + * @tc.name: ThemeManagerSetCurrent_001 + * @tc.desc: Verify SetCurrent function, equal. + * @tc.type: FUNC + * @tc.require: AR000DTCI5 + */ +HWTEST_F(ThemeManagerTest, ThemeManagerSetCurrent_001, TestSize.Level0) +{ + Theme* theme = new Theme(); + if (theme == nullptr) { + EXPECT_EQ(1, 0); + return; + } + ThemeManager::GetInstance().SetCurrent(theme); + EXPECT_EQ(ThemeManager::GetInstance().GetCurrent(), theme); + + delete theme; +} +} // namespace OHOS \ No newline at end of file diff --git a/tools/qt/simulator/config/faces/default/B024_002.bin b/tools/qt/simulator/config/faces/default/B024_002.bin new file mode 100644 index 0000000000000000000000000000000000000000..ea466367b38998d8b0aef6a713bf239636dd8d86 GIT binary patch literal 824472 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

#`{zv(L%2*Ts=Q&zaHnzn@ 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%0A9++Xq53V3 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�s(;urZ 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�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 literal 0 HcmV?d00001 diff --git a/tools/qt/simulator/config/faces/default/B024_009.bin b/tools/qt/simulator/config/faces/default/B024_009.bin new file mode 100644 index 0000000000000000000000000000000000000000..853ae68e7f80782dc4b9cbe6bd3572ac41fad833 GIT binary patch literal 63008 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 B!%YAH literal 0 HcmV?d00001 diff --git a/tools/qt/simulator/config/faces/default/B024_010.bin b/tools/qt/simulator/config/faces/default/B024_010.bin new file mode 100644 index 0000000000000000000000000000000000000000..3b298f790f85290cbd5ae9369d367aa7c782ecd0 GIT binary patch literal 63008 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 literal 0 HcmV?d00001 diff --git a/tools/qt/simulator/config/faces/default/B024_011.bin b/tools/qt/simulator/config/faces/default/B024_011.bin new file mode 100644 index 0000000000000000000000000000000000000000..9923a94a787c18c0902abe266d27d86872ac3b7c GIT binary patch literal 63008 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 literal 0 HcmV?d00001 diff --git a/tools/qt/simulator/config/images/blue.png b/tools/qt/simulator/config/images/blue.png new file mode 100644 index 0000000000000000000000000000000000000000..aa46de8f2974203d366c860f7300594a06baeaf0 GIT binary patch literal 2877 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 literal 0 HcmV?d00001 diff --git a/tools/qt/simulator/config/images/blue_compress.bin b/tools/qt/simulator/config/images/blue_compress.bin new file mode 100644 index 0000000000000000000000000000000000000000..93409c52866c2a9a1b67a3c924333ba0449f6758 GIT binary patch literal 4056 zcmb`KONfqP6vvObnHh7%xQ`igF|JdTNytnZlaiE;%VNw5h%7#QJC605Rd7t_J-)FwwX6ozftMfk3`JLNymsXKc`;8yj*{vedYyV8v z`H*D0US8clNI-t-m9^&zK7L`N`P&(rjDGwwdlv@RiP=0!y_Lni~^-&x5OS?u*%K0?RSaOZ~Q(?lwO+_l^F&VZ}&@9$=7ZfUkTWgATQGx*ugkArplvCd79C;XlSSL@s_v$Y5; z$K05*wWe$15^UQz)j59!Ec>$bDpC-NNw z=P~IYv(*ch{dzt*w!(B@f%kgoZLsXuYnfyBgYz-azh-MaSoZ7Vmt$v{?sM=y{(2WI z`}Mh#WA}h-aqgel8UV|FeZEGv$912A_4%p~!Ld(YCz11Z9R}xXL?tuyO6*e0%C%b=^G3%qULG}$}gCfDOvQO`?1 zWPNY4@7LzX+j0A&g{EYH0)#5*|){Y<9smMDbcP!zyuqO*|&vx zz+C;Sa@Ugl;+bltW{%gp*GmhyRftXaESU+%=a$&*Rr~Nu2T9x_Y52dGA7{B)>}-2S0nVS_t21Jr0fYG3FQNopt#c8acj4hqJ7J<1?A>?la-0ycfoh zpYJ_>?x~%}dKGzgQoQ_D8KHK5_Y6}zzn^wcJHNq(sGZ+z{FbAdDZl+_j^*Bpw6=0@ rOWHGYZ_$3lkbApspmyJnHnY`JkBBz=B+Qw?)(bjmBbHCid*QUF5-9P#p zI&c$17u~?A0uC3A!Z8q#_H~DF=*qb3y8N$*oKd)Es4GH0qvYJkzr@gsDKm$|3_T_LbUTcnb0hBr?}g~a zjIa%5?xXs&Pfh~1M-c35#X@ZY*(Mzh0?X2g<&H(dSwOZ)go7Y6DZ@xIb1xWTwn-s_ zz(Nq%#xN4jK(leZa6q|<&&LcSnaCxvLfIzNIUp=^C*~K!NH|ntDBA?V0cFLG3?s=b zz2KmN@+SkrGIi@YT5>pCF4frvMaFIz35xe(pj!rzbE>$mBe5gtbsFa3qKa&T`i{;F z^B~iUu?pLuu+Ux7k86g}SZHPX#${!{p91B;KYX!BL~~)=fUwXc!@Lb6$*{t<0g;&l z4I|<3VzvQcA;V08hIx=FW*ZQhG0HF!&hP&cofNH&ep^`2^Dx34nl-W+#W;Dt0^7Bk1W3!DULrYr+(Duqaqp_4HGnmyh;C`-h_|;cL zhMPZEnJOYv*fuqh*&0T!ANmTkv2HRYZNtoJqF-thIw`ywrm$@$lMHWU)YU@uWQrA3 z9b{~!q_>x-akH8R=ucFnUM=6TQh*FiEMoIAjw}S9fgM1$yU>Z1CbQMwpKb@iDMaQ}zN14UQyPw{rse*x;|rpi9FqNaft&-p zhVj>a+VeNTITah);dj6ZRqfgT-Qjr3jO+HkaN_iaj(RUXq3RO*zYCm1nQ_DGhZCnO z*Qh%AGT6bJCPp%S5{fe?cB2clCQJaj48ONT%r@Dg+|uR%}*L5HADi5F3#ymZ-v zojMfM!Ba@5po?}1L`sD&g%}kCp|8g-Pv(vLa@S#5cixx%vCO>vF+215y*K}Mwoo7% zl@}8l1Rwwb2tWV=5P$##AOHafw1~h7VqC{ny^Nh9{zBZ*BI8(f6B-fhC;mgs#5W;h z1t!ZZBJ-8_ko~9;1JNqW>?8iAGJiM#cqRvFwztY;7LhLsVV2aqAY zzk@|&eh?ptjnhf|t(uJBOvIMC29`?NT*@iVo6IsFh~?1Mh}%R> zT;@>u5K|fYEbk+^lX+23!mJZl+A8s~wk2Z8nNsd#P7|NhcAU63(b{*QH8HpM+gGRR zYrm~t6W8abO`ERVmCT-PQ$HUZhKve8#!D*5crt5tKQo}Y^A6QZ-nt->xkb#UR;Kgs-YMMedsX*#Km2yh#8Agjk#v5_Scrs(UpOMF3<>~P! ds3r(N00Izz00bZa0SG_<0uX=z1RxMa;2%{nY>fZ_ literal 0 HcmV?d00001 diff --git a/tools/qt/simulator/config/images/green.png b/tools/qt/simulator/config/images/green.png new file mode 100644 index 0000000000000000000000000000000000000000..216cf279739029aed1261173ca79fa0ea53fcaa5 GIT binary patch literal 2381 zcmb_ee{2&~9509rCS(f;1o^S^77zp1yK8%Q?YVBuuCNK^$JQ_iIJn-u>kispxx1~m zMIs<-#xDuV_$8YIS@BiGT-=0-! zGWD*Lyfd|#p2;jP2hE%y`+!*wh9TNy%E;2g9KQf)h!6OskP~e?avVh@!HE`99>NoL zfnq7QTmc2;Q@nin0^T8@S(!+N&O(A9&^Sa720|*UJJA>~3){vpjv_ILw!n#I8wQb? zo@t0nRsceoX^barc7$=5N!mtO>2ZjKARRbi$4M(jkSsy76obTnD72;sBAcHxA#Mxy zoM^G8g;^YrL?Y&h)hsK1oOC!KhXuD-Fo?j^vXI8mrg0)a%2|G&= zolp02!@T z#ws#`1+xo25d(rvz&OT6VhrPe7ATRniFOqE*RD$r$cjf6V9u?Nj_0{t(-c{h0&qgj zpZGSC=XTMggQhWynKWb{lOLNasj$Xn@r_gf7RFlv35h9=<#=OLI8okE0}#;ou=Iq4 zKA-EIw2@)}NuQ#UPM9i-T7*+TwjU<{nJUD)b6(|2pYFQN0vsYm`!ENv)0oKFD42gv z#H>D1q!^peE&^D+$6QZnmS$}>mLj_}`y#H#x%px))0wI_u1ztI$&Ypy~RZl7}C)=xke&bSJ z>32k`d-}#tx9@MczIrm6s%+c)r98V0tKYwGd9sjN`Fq>hEnly1?-<+Oy1G9#e&pdnJaD8;;&E52l z50m~bSm*Aa(bBry^Ukl?P5VyO_8NIxJ3rxE+81LE%+YI|$H(m+uAgq0v}yb0?0s#g zSC7E2^{J9%xp7U!kb_ClZ!R8`e)@jJg{A%rH)e0Nv~Tt4wa4bpJv8~~j?^ltt?A;1 ziXkr_9g`B>G_Ycu0K@&i--=dcY|oAzz!xB2S5yF+W6t`FUlHU84j zrrYPs->YuDG$RV?avLKZFSY!2ZAAOwO=I^~-@KD?ZT7X3NsiXB^I8_KT$=LM#pJa| u(LuK|ukIdS_u#Wz9J#C``&3Qy1C!~u%wcnaYu6YbJbCUZIeXupxAY%-T2jCO literal 0 HcmV?d00001 diff --git a/tools/qt/simulator/config/images/ic_arrow_right.png b/tools/qt/simulator/config/images/ic_arrow_right.png new file mode 100644 index 0000000000000000000000000000000000000000..b7159bb6cfd27e2fb3e120c925c89208ae59e52b GIT binary patch literal 293 zcmV+=0owkFP)Px#-$_J4R7ef&lu-`CAPk1Vcn}lgtJ&glytA9}S#M(SZ&I>$mQj2al7&+Kez3M7 zgg$x(mJFnn_RGp|?W7ctBi2{UgYykmjew0FK-MTwjPZ$$nKy?PSj#eYJpWjUYHByfvNISKxuI0WNRFx)(zqysh4(83wgrYMmt>K zJG1|W8@{B*l_LXkmk_e6AP=Mp@Px#;z>k7R9Fe^mO&1JFc3wlQLo_6E4cMA9>wFZ;tHO?uwuytOx(KE#QJKO1S@0~ zHI;;E@-wAFJD&%nEedOMBjCN?pbC}9r9S^MWr1LZ`6b$-8(N_|%HM(+bV4m^&_2J5 zt|GJ5Z%yGLRyV0)cd?+@zs!g5QBAg$FO| zk;kLlN@q!y3*~;tlewIAvhr!QlFxVDQP-X6kMKYM0R#|0009ILKmY**5I_I{1Q0*~ z0R#|0009ILKmY**5I_I{1Q0*~0R#|0;4lJ9k>#JwQx=DLk1^G{T*>(lxtU1U1)j5s z%)biGxe@WWl6MyYPmBDrldIF*>c?At_8{HsB-kzW-4Ox2J77NF=c#UaU+GoDzjoI@ z+7iRCI{b&@ndps0&g@?}V&6|?`SZy^1KIfG#gmf!p#qO0-G{!d2I_hx5P1aCvK}Je zA_t6h_uKCQ*qutjP$x6^LG*VONTgS9{36lqvQCLs_v~JXYnZ` zfNbcp9&>y)XXeTTQ%Y9vWOQUxMQzmOj%YoNbMvJ?6(*+Zrl|578)PX6Iom<$89T4V z?Uc5|xom;De`0CJEVZT9q%7WQ^-*fOTPSB&U%AQF{g?YMHd@fiYopDRWpfa=J@?>5 zR%TnVs$L@-{WAzsut;$Kz8wN(>7`N3UbE{CWNdw2Yg@JeBW`_Fw_VrPE;-7UbGDr= z?y0*d#nz7jY<-Qo+t}9i^lQV`*QmRFXxlNbZ?UZ#tA1^D*I;~h7`D|5kSc!WEju;g zx3Jyqisut5R@JVj-|`?xS+LbjFMcE2-L91+t!?g?#qAo#T3+jes$Fu7`DB9Aw%?bP z#hdM;F)wbNtsTo{O9h)-%JOz>e6_g0%5-KsXrJ@IEi%U1Y=?7hs~*19zW7v6;60wvF1uiGyt<&$+o0yxn;WI?uI2V)|7u%T zlFthR;b6zOLreL+hFuKQJ@%J^DKrUo3T#sFoa-&U$@fZY#u%RRlHPJmg$C{_-G;HM zo)E5kM8)q1Ilfp=UbmCY^LQ$kr~Ny8_jm^mMF0T=5I_I{1Q0*~0R#|0009ILKmY** t5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILK;TdU{{h7l1oi*` literal 0 HcmV?d00001 diff --git a/tools/qt/simulator/config/images/image_animator_1.bin b/tools/qt/simulator/config/images/image_animator_1.bin new file mode 100644 index 0000000000000000000000000000000000000000..f07b10c2190313fb27416ec55915fa6299919913 GIT binary patch literal 40376 zcmeI#Jx<(E7zI#I#g>S&0Q&3#3A*egidam9#0I9Jp%?*sph*#h$Dlzly8OtHt0~B_ z-CWzcmXAI?%hO_-zOVOTBS3%v0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FoHyfs4y=aXMC~yM5|sf7+ih4t|Ykxm2FG)?8^(P8(!<}2awk*z-zPmIJDM;_rk8fRtG;`9n@`D zw)1V5f9H}u3R@Fq^1f~R?d!O?$}bw*f3)43#_IUK9%7m)Z6#vx#kr^fKF?Wj3)*kzQuov&<&8DbmYq zdzRV6Hbr`wZO<~B*rrG?v+Y@C6WbJ#2@oJafB*pk1PBlyK!5-N0t5&UAV7cs0RjXF e5FkK+009C72oNAZfB*pk1PBlyK!Cu1CU6Ifxr5sP literal 0 HcmV?d00001 diff --git a/tools/qt/simulator/config/images/image_animator_2.bin b/tools/qt/simulator/config/images/image_animator_2.bin new file mode 100644 index 0000000000000000000000000000000000000000..c0d1f14863700f5ee292c298e17c7a81d10082b3 GIT binary patch literal 40376 zcmeI(KThjF5XW&x6ja;?6bd?e8m<5mblic80wJK_D2P8@sx)aRfaDG|DIyrgSVVyt zJ7jlK5P$O`K<4cPKAHFPdcCjc6vc`D5grI2fB*srAbr^4#ghAp;H z0?VsI-!9MA<}K&4C~Z4WU=9{ytLr?)!H=smt{p;PwTKM_R>MMUZPV%;jIa%{wMk1L zguvP~(5B_8A>Y=8Ef;(q@@u!cf(uI;WcEbE?S{FxDl%VswTJ80g}Pgo%nfqh`u?l4 z241at0hxmt=k)> zJ8jsNl-wU0u)o#}tI2?#wwS>ttLi{^vw_-j+R9PYvF?Ilx%@&7MQqrr zt^Q}yr@Me>%U2$(ZMAxk|B?Q;+dK1Bs|wg+8Ik+`lGH%}0R#|0009IL zKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1kwaP E0WTEoJ^%m! literal 0 HcmV?d00001 diff --git a/tools/qt/simulator/config/images/image_animator_3.bin b/tools/qt/simulator/config/images/image_animator_3.bin new file mode 100644 index 0000000000000000000000000000000000000000..572e4ad17b7f66353fba7e14bc5d26ca8b579c9e GIT binary patch literal 40376 zcmeI(y>in)5WsPHD5!WJGNzz&<%TA&02AnV2dWe#d=xwi38YKqCJlu{{0=lOJY-po zCO3+1@4TzHVB&wGnaHP;Jo?RL@3g0DCQb8Re}x|e5I_I{1Q0*~0R#|0009ILKmY** z5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~fjfcCjigOhccuItC9uAg<4$S`irn;v>zHbv`fw`5iVF5iZLx(2;3>bF zz+2R<*lKjWD97F;4{r&#Z~L^7S5w(oUDf(VW~ap#{_RES@_Sw0**g0s9Y|b!G=!h& zQkb86&1l}dju5`;uxe!y+Gq5mbl0g77Hp|_>EiD& z>S{kIU*Z?yj7%U83)p5zBMYz{3FkO1w>*j!cec*{7!1?Q;LW_a#rAr7B7PNT{; zf&B4-j%=Hm_c#H;D;2Dl_hK~KAfa7-@!NRXC6i!d`ARjws;ZgG1=d4&d@b{%;nvPe z7Ut2MX-UslKl(a(`eiW6N*^`AhSHLco^I<6dTeQ{TN%v?9^0{g_v#&~KZ9 z1I^gkY|OrFc2s-KrbFf}1*dSS+D2bzIxCJDNYs?vLgnV;gEnAtH6g}Q<+V=rz8U7h zly~*6bOz-C0p)F84|$0U0l{k3JR3&RrV17~Xf|Ea3jx7u)qF@yk5&Z>9L#5S2)O~i zYE^8jJP$gU+Y%EV1ll{9c-zC+YYz83j z#Rv!qR(Codok0k#bJP~wF7&(q!jgXEUAx@2Q(7F@%X9kmb^l6d(ya`bUW6oA-^%>I zR%BhV`|G@=?iNbx{>ho>1Vr9eQwhU^KCJGWwu#HX1MC2HC$=ONbJeay2dJgs>1nh~ zL6%@sd3`Zyj?7CniO#>HGr8Jr%}?8IOJ4))Z}Vx}!7a}N%P0NcOHBk2KmY**5I_I{ z1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#~EZw3AV D+m{hs literal 0 HcmV?d00001 diff --git a/tools/qt/simulator/config/images/jpeg.jpg b/tools/qt/simulator/config/images/jpeg.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a026e486f57de704ac5e9344ea20bb9973935e1d GIT binary patch literal 5764 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 literal 0 HcmV?d00001 diff --git a/tools/qt/simulator/config/images/png_1_palette.png b/tools/qt/simulator/config/images/png_1_palette.png new file mode 100644 index 0000000000000000000000000000000000000000..1916a31dd0648340f9cae5725849feb2b7b0838a GIT binary patch literal 332 zcmeAS@N?(olHy`uVBq!ia0vp^DL`z)$P6S`eeCT8QjEnx?oJHr&dIz4a#+$GeH|GX zHuiJ>Nn{1`*#dk*T(8ZU<5XS!E2;T9P=vFjNcgiIB;-#`$FBoi+m?$FZ|Jb_@BIU_vRwD3vH68EM?{hOWf)b$(4{0 zVJW)wTIajLwksOUp|}2@_ek)_nV#mI5Wu!;%{9fwgL57Gjy5Vvp3LUtDdJh&V07RB a0|V1UBNuUrNhUzAF?hQAxvXBAhLkP3*0GClC%e&g4Os_c6eh+JGeWnlA&E=I zmMp1cxt8HEj4%j^^1OfV=X*cSIp6utcYdFfVP}IDURo5Am=H| zd!!eh3P*$k`Wm2iM0V}eaNR-PL;?T}IsC`o+^3%Ro~26!03g)!A8_o`1+ER!3M#t1=~GdWYF)!s;G?EUppv?2HQI!gRBiPvekIztwtj7T zc>xz6>s1;=OMsyhRAgNV+O$?kL}Hn54tACf@cQoSIk?hIX4~kH!NKh2krA`>eu8km zKo9<@k7Gds?3`(4B_3qW0WIKk)#QHZ%oo@oG3En#((CX)2ghX@QA=loj-zWm^82>Z zKA#BM1}Ln0gqUPp&z~%TgAca6wy*BGU*4@+Sngak;UZsROu(jG_|h=r)xg8UagkVr zw}aa2gWTG#J|6Oj;sN?NHYX7 zfMab|5PzjL75t0O@aKZ0+Wquz9nZ-h6%1!YAToA!onKq;Da#;itd0^I;oM=?l~4sm z7L3%AlC$qyjJW%$PdCcGO78Aw`AX8LY>=38ivb7k&-#xt5e+(GjK8eoO7nagqAz+o zsfYoO(YUw>ETnNjH6 z89K^$DDK<6x9@>dy1|+|;UB)C)?D?Y0b4iTQZONp(j}lLUT>zr)?)fK8ox0#JLAuD zB$(Yn@1`!&W58K(L%-3zEm5#Qs|l5?+dc|IEj?YyP%U@Xoab-{J|r@J2tPLDlQGsE zEpoq_iDx($zPk`YG{FQWt}(u|R1yH$LF4Wrt{F(ofIhz3OMp$MPZ_2_z}(6|*n1VE z_!y2Qqp=F8G<=Cml;mu1Fh*;Og#M8$G;a!-=s>W;ELdS7$C9ou(SZQt>9pZs_MCvR zwKVEIT{TS`j;~V9-YAUw$JS100;>|bxYxR{G}>3VRKK2->8S`mTY}LWJs0DEuaUN+ zLG5~JU7zA7Bs9XH6Pn$(KG(w@jp8~8PX->|%LSc{3Ht%Oc0yQz8rWSKs&){OBc`XD z{k8vnpVJd!;oIGT0KGuEMFiMSBJ>x=T%o8bCp&}OH*iHv zBh&1n#xyZfs>CrzTzh?ch*o1AnxaS>1=87pkD|$GNCMA24FjjH|5w2^@WSL9+i{g`AXkm~#xfoSnrjgVa4i&Nc zAUdT5l;W}VyunZ2?AHj}b2A)3jrl~^B->x)54_a4X{L$Mm`!$m?>WNbBu}VD7LIkuHr^89R8X& zy~5pQ!l+g@sKolv-sUn4)}FQKl5E?bg$kT1on1n24}Ss54&snONAo9MteKUli{wk} z%!p6a(=2Au6I5!beADUQCGOx;Si>@>bq9k zXqTvd3{F8=Vn#28erzbFRq7|Slw>O(S?d%}wh9VTo|G7fw!~+7SUCsU$j=X|7=msx zH9YIW(KB1x4^!%R-}N9|c*?*>CfAca-!LV^4EQ~Coz{-1(ZLc4y2{y{T9YVdw0 zpu_jAuLRPqw6WaoMnj^R>iipLEL{oZO_Z-H!hnGC>9%vGf7FkB z`vI?<)McLNl0$>KPU7;Qdj_P2+9PtFKvIibAFfJg!P*w{`tz~+AxXz&u^~Ni<@2WB zv@w|Mpb_8_p>OURX_BMu_+*?g@P2j#?BQZ|8NXi+3|

cyP-h;<;<{snm;6#+%J)dF^>q;~BC8v;rh(GFdl5|G zUZVMA3%=w*q0McEkOV literal 0 HcmV?d00001 diff --git a/tools/qt/simulator/config/images/png_2_palette.png b/tools/qt/simulator/config/images/png_2_palette.png new file mode 100644 index 0000000000000000000000000000000000000000..886c0b30185e25b52eac87b4dd83e02e6ff79f6b GIT binary patch literal 440 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 literal 0 HcmV?d00001 diff --git a/tools/qt/simulator/config/images/png_4_palette.png b/tools/qt/simulator/config/images/png_4_palette.png new file mode 100644 index 0000000000000000000000000000000000000000..37cc111e0455dab614313467d4184d166f5e4af1 GIT binary patch literal 585 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 literal 0 HcmV?d00001 diff --git a/tools/qt/simulator/config/images/red.png b/tools/qt/simulator/config/images/red.png new file mode 100644 index 0000000000000000000000000000000000000000..4cd1dd32cdb48f57f15eb493a8096bb3949eb342 GIT binary patch literal 3375 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 PFOArHjaQjxblQIa04Fp; literal 0 HcmV?d00001 diff --git a/tools/qt/simulator/config/images/red_compress.bin b/tools/qt/simulator/config/images/red_compress.bin new file mode 100644 index 0000000000000000000000000000000000000000..439b89d04e700b3877b2ebd9ed8b2e89625bbc7e GIT binary patch literal 5512 zcmb7I&u^_s3+9XW1nF zj(+&g_ACq8NX8=!Aod_WKzxBXjhIDTNBoCaLIlJ;#3JHf#1+Kv2x7@0ml^}o9&c$+ zAb$cekGOB=!(cpr-|{?9T;ez4`Ax`8BBl}d#C9(a7{S! zS)XlscnIyHs&fsTEz)@=>TLEpyf)RBj@rNl)*&9TSvpV09Q)}N`Jqn3`B{s0;ry5m z-=WlRVFX85hd3{t92wQ8AL^(LT(D+^5*W0<^cvpF!#GHPi-9K9=$SLFr(q!4Y!A4X@+9JPJfn^>Twe>EOoV2<)d9q_Yhf)u$ios0|#8 z;s_VXlg`GN`^al}FB@>aa9;9=|5$uuf zoucYP_>48ce^dM+v{aKG$X6Syuwoe~}>l}3(C z!)j84W1IJ2W5r$aq_Y|rv=2z5d@ra`<71-4KPmoB;Q2r6^Ij`&^QCZ^)%{E7yYS}W zkv{SIwALV9I9!_$uf7vr>fnt!!0km$$xj#T6tG8o#2y3obdT6Gz@~S*i-!xqF7&9o z0PJFq*hOIf?-84DFL$`N?{~=a+WM)}r~iKLOF#GfOpo`4&#|BW(NDkYwLiZB4;v6^ z&Azk4v#@tl?+c&HysongM>xIDdcQ*#o^ax6Jn6k}J42H>@A{A-rRKed6=I;U;? zvvE}8>dH7T~SuJ39*{G|Hm&^W>{ z;*K2ARga6Zh-1gDj7hUx_-Vevfrt@PuSHVg9BHPywA~!1?;tJ=d zysx+K6SLq9>bguD+njLL>(CFcsp)(e|17ZU#kao0{pMGQWogh~VH*5Z-o8aG1A82i z_M1b%qs@KvO<}3=d6j#UIMpACJiKnL?@YU6?hSJn&X1T zim)qc@cM_PYq@dc(sw4~&LQ`%aIZ!#$IJZ)jl+CVafBJf0Yn-X$6_8oy?vM47xC=p zlZa_Vr|*ABc5|9^E^)+@#IY*sFU>hd>;TA12HiG literal 0 HcmV?d00001 diff --git a/tools/qt/simulator/config/images/sliderBackground.bin b/tools/qt/simulator/config/images/sliderBackground.bin new file mode 100644 index 0000000000000000000000000000000000000000..8dc9e5eb9c0ce8648ef64f702f9a4074b820eceb GIT binary patch literal 4968 zcmeI0yA8k~3`CtG65HfAKz{p=gNUFYHY5^K7D(|53ZJOVJ!748BE4Lab9UbQC8e~+ z7{{hQRNJ-Q^b8>ctBJQ3`EhRU<>w_U?!S0%#hCbkfK=G0O=PNoJD0`b=ss|T@i8QTse2nS@M(b zW4v!Ad%x!KEzfy3Zow@ufB_6(00S7n00uCC0SsUO0~o--KMbs0m$N&u`v2Uaxl8lj HUD}sl@EOoy literal 0 HcmV?d00001 diff --git a/tools/qt/simulator/config/images/sliderKnob.bin b/tools/qt/simulator/config/images/sliderKnob.bin new file mode 100644 index 0000000000000000000000000000000000000000..8ad509117913963e760a3cf565bf3f7de7b65b9a GIT binary patch literal 6408 zcmcJSZA_JA7{{q7NDPAB-|RpZ_O88&yotgA>`NOu))y^Iq?P8%sf0G7Hc*+VTjEyV zTAGt|Y5F;*lXp1sCRl@N%WSKy+REA*)&F<)T6Ihg@Hc)pZntXAxrt#;a&mIXzP-Em44>=$acp$> zA1{FWhx=Pv($do4nIjN;blxTqZ{rv()>q<|rTvcy`OjBYR)@MfzVX6oDw%=a?u%>J ztbSFx3IuW#-EhQ@_#;|A!jHI83*;OA=ZtVGXxLU)KRVR^`&4#g`S#WM!9SaKG&M?R zp#Vp05F_%D+P40q>Etlqkq6Wk`Ad!v)BJ69wQq^TL@Yd_$&#Zxw>LIPH-046)b~vS zy%G+QaW>zFLq`k z^FhnL{jx=$d`RHV!G~~x6LAI94K+^Qkqh{)WU#OM(oAf7*-P(ndOE!^AdrV}ffL-^ zv89gg7Z$!!Q@z29em`XB;j&2{J|NI<=pR-m@S~?t-_!+uE@=JaV5=Xtz34dc*^!g7 zNxykmAiu~nIKd5A-QKM^^}eLz%&8$Sy8V#p>umd4Hj4#%EpH#*YLd;N7FQ*)nmOj-SOh*x%E2(Ti?BWJdb?EWY%Z$oRVJgWM!Xm$sexV$cuU zUUc+yw2jLq^}%}~vOcW8*xf;YDLnM?M~A%V_Cx0Qk;BJjbFsjCggYd%zt9WmiS$N# zPkJK!@gZ-l-173^iz9=fU*oYK zZ4ZAdR4!lsvTWoD)Fi&+4ugZmR@3mnk8U6GMx_tYYYI24U%y4~-M^yQi*aqDe(ReJ z(oN0MQvw1ud7Ik1z+$UU;Q>F(L*6p@nmBVO7QxXEKk~PN-c$9`xkR82=L*y;J%t*G z^qoG12YfBgybp=-wcb3JdG~03sr~e+;ll?Hd|Fez`VEH{d!82P zU6}$pBe~%_F9SI@M{6CRyUdyMtw=zIGqH!g)G4~qNsP>I z*qqLH5(y9F02~tq@|N1-ywnXgvV?2_yYmI|2_69Md}D2%fGm61iw<<5Gahc*HaXzP zx!_6N)0<`s*uV$mCO(}b1cXcn%=yL|GRU%rz4#1WoI4huNqov-9MOeN<4I0qkNm`T zh6CJZw JfjuCm@h_xf>p1`b literal 0 HcmV?d00001 diff --git a/tools/qt/simulator/config/images/yellow.png b/tools/qt/simulator/config/images/yellow.png new file mode 100644 index 0000000000000000000000000000000000000000..78f7bb28e9a84b8c288862fd1af7e02b7d096d4c GIT binary patch literal 2376 zcmb_e32YQq7~X<{qD76=a#1HoHJ?%>`RCGFhqISdtT2jk( zx^eAd{t|VGGn3&YkDlcv7tjYhK8V)ol2ZddmMaA+;sV8@*M?p{a0o?2-i8(!orKeu z21>;AN*Ux;X6190rJR*VQ&W)S00Rj;KxL7D$L&>^fDH}vGVod(#!(~;QA=&;JS`xy z#F>qxNisl;dK1Qx1`A@g>PeG<$O#IUKlw2Y6KlV#z2LR>|p% z!tYD#@kG&z>L`bbBDvict>jnu0G!3D& z$jrRNL5^tM7+|6Z&LEgDiZ@y?(rn-{3(K*Xg{EmUX)@9-+C0p*SQg=WV%;NIHB~%h zglJkY^Au*`T>=Jp$itcqBxW{SS%EeRCWBx>k^kbRNp4AYN<6GNJ#suFEiGG?1knv2 zD7g#gBN>h~6KOR;fqGK2eb|0Xx~RY&S40+49#|f^a*IgVaSY38i^7I-ni~L*Mux@v z6!g)g57GKd03>~YONL;IB&dE?2J?zx@gM3!d^GV2TmE3_1`1dOBW%7ESWK9}8jP_1 ztboxjK`@#PE{gzQ_wJEC!m|bgVR*n4iw^sBk8ol}mT*uVdU-R-T%`}PUmVJ2r*MAK5skXY!q2_lx%4~HBq2^T`W!Ad-kbms@(n=i`ygo7E zw~M->di?U{_Y#CksXnxL!L5d~&Bt3CVv>L4`+mt9bYEd#zg09gmEOOzbL*sp8O?(`j-E(^~-P=?bf9cOc z`p%o1xCd=nHo7Epc6=ZVW`L-ELXK6$?gN5dsp3L>L5>`Nog~q0zf(-)$6JsH) zAe32M@p<{&7oV%C*(iLmeca%U4Ldq}n`DT%w&6p0cEVuyF8880V*7vJyZ2%H literal 0 HcmV?d00001 diff --git a/tools/qt/simulator/default_resource/videoplayer_mute.png b/tools/qt/simulator/default_resource/videoplayer_mute.png new file mode 100644 index 0000000000000000000000000000000000000000..635d427d4a95525ba436737aeb533c6ea423e111 GIT binary patch literal 509 zcmVPx$w@E}nR9Fe^mOW0xKoEsvT4)mqL6r`{5hx-mQcwmCfv6F8fQzJ03lgND2RalG zNEFl{=*;)buC>|NjxGN{;gO#9*YlpoGrR6|T5BWVMIa2r37CSex75tgkkf?sMMSW7 z4V;6GQc7{q10t8*fu)gPkH~L;Y*b2s9>z&-5$tuzs}Yb_L;`tr0+xyJ$X^)WiXAKe z2}EuUhl!tq`Caf0MBWEuAS)zb#S&$-1J<6+`VQ?CB;6ngrt}ZMfd0&^SKB~z29j){ zI@nZ81WwE=ozfnHBk%y?NYJm(?5n#B%%$bTK5NQ}LOJ02Ug#$Y(54I#L)MfR)mWMU zdG|!b$2RjpHSE76k%lyfEOtSmW83$YsTSvNJg^Rwgip@HpY*ROzYq5G=wE_upak7x z{++hG;XJJjSqf*MFBV!Q<|B~BpCgZg%m^6gD!(CEbsTQdS1G=N=pcf4Oy5>OwUNN_ zoI&Q^fc{bZr7&QHA~8phX8?gO!d^zlhP-hRB9x7QTOEx?A`+ko{-p?;h)E(;vh^5m ztem(2n=S)m_wLXY-nPx%07*naR9Fecm%UEIFcgIeLt#RU1&Ngb2^J)nnY!@;yhFFH43&6=z5y@53os&v zu80|=!pPrJmhye614&DhR+RWkr;dG(?>)60+eudDw9#nPfO-N>!5-KSpja6M+)-YE z>m*4&DJw0hVvx8A=Nz1Y4bTU-;0c&mQ_L1P28ZAcT!Jn_o+%eM60dx@4*I=`hynxeKA*Qi^9|;`JxkKeYUt01^r!hlpb4;%Z8eJ^T*Pn+yOFCH^JLKpV zd^9@ruGmh&k*nal0PmK$=ECEg92d2@ex`y-5BS>5TN5deCh9f?3>4rs%9~8xc0|Iv=o^s&;%>!`J0jv8MnOQ_<2Z>(m&6?04&G@ UA}Tls+W-In07*qoM6N<$f(g+9bpQYW literal 0 HcmV?d00001 diff --git a/tools/qt/simulator/default_resource/videoplayer_play.png b/tools/qt/simulator/default_resource/videoplayer_play.png new file mode 100644 index 0000000000000000000000000000000000000000..f28e73a42d863957f88239f30aa5d10d1c497dab GIT binary patch literal 706 zcmV;z0zLhSP)Px%d`Uz>R9Fecmp^C}K@`SsC0qm{q>KRz8w80uihvok1a{^AB2R;raWWvt)PAyP3T`+y~#y?#}nU_iko(=55wVw0^(e zg}MrEgC(#SK+`!1ct`sJ>}Of_i#BRmj6vW9IFGW!#Q`T{O3~(p6990;M*9( zYl4R^7U!$+u;)Rmt-eEyUa60V4!tXOtJG1sp??<~gS(ZkSl8Y(1u{_M>r6F=n^J&l=EDHkrT7;om(T zU9JWib#JWTV$B4$!Qm)!@p%x9YMfX9*}0--)HS?Aur1kx#l5|$UBJN;@Dj&Y6Py`y z$plcz)689qxjk%a7ke_N1O>(yz&Ep8$n1ci&yotJhqI#I1?9V_NWRgHm zrM!Z;P91O*5e%l@DKG(xiKg}g2M@rlgy6<9ffGl-GRjJ(iR~fYAnPx-`AI}URCod9n`ul`R}{x(*oReF0*FL{Y*CaD3~EFo!J3L#Q%e<%#;QrBd}#W? zsx?j1HdgHiKQuuS;}(r2me#1C)@o2FH4q2^(XdHq5W%u2i>$Kr|DX6K!(+C`ykTa- zByZk!m-9RK-gCF}?B<#IS6W)?>Ez@TVQ+6Agu@?)9}cBLp>VddvvXqhlarI96B837 z(MYJxJp-QE4rK6)`B;fspJODX9G&J;PczAg5 z-TNQkA)M;x;^Lxob#eb7qPoK6veE6{Q-o1PMBO@bvWp{RVp0{!1MvwLD*ZVD7wk$XxAYh@J zo0|_KK@pu$V40VfS5ata=&;^|jC^Ub0;;R4-B6WNP~vM*axM_Cud%VQ{_54Mb$NMt zy++ERH&}Xly4S8Gg)QA!^od+vctQgQ6*}%&gD*&0>RaRE6MQLmX zxU#RWucNTAu>9!Jqs@j&*?>$sU$b_cIB_C0IXQWyr>Cbs6fuZeR}dE$SAs&A)EQCF ziy0}PqN2jr-QE2&wC@m|Mu>9lL$&)~$YHGntKWTefWRIDY(iDrEG9j3wwn@?aMx4CQT50fc|{ z>{%wPd=#pB)9&57A2c;Jy)%@sCEkaHg*jfiawP>#BMiMn-O{B?a||iLpaN=YYPPfF z>+9>Qwr<^8VlE3Wm%`Xn+_-ULZDeF*ENencOw2WXX)E--WSb{EOWqdX$-)AntdyCu zlR+P&P6g1~)8Mhqv)WsG$!kJVR?1A-$v_s(_nOyQ6~KO;T^+4G4W6~Oc})=UHD#vk zWFQNf#9??pO#vL?agf7)o?V@_S$k~|x}H&XGLVH#WD|#-rT`9aIl|*0$6ES%vCvZP z$v_q|kxd+TiUK&X;n<$TTaNI=fo=7kEMy`Z88tbkD1fs%R2L=3_OPNB8Qy9NC)YJu z$V4_WYNo4J0O#|Z(sEWO=fvju^XHjZ-6@4-{@i z1*f#aOHxmAv6H7SFTw!?-;eM=Gj86zd8fFz_ydV?B?l)XS;@@EiuddG{^%-R zN={A22;4JBOicWxr>E!4p+koPWK4QmQe=fu_9wdn7pjQ@m#(LUw7>%lR+3MhI(56Y zwswz?kB{0&%PES$N_GV_AH>ADZZBshrlbWU=@u0g^-XJQ>$P*|&PAJ&mbSP8E7_?4 zE-#OB(?VNdOFqL+QOy4R`+tA&;>Fi%)~s=rOBS+{ne0@6ANs~OGIxJ+$;x!>gM)+j z6%`c~+`oTc@8X8BnUa;vWTyg@+^aJ-vZcZ~I5;d!Nl7`|-Q9iq;K74h=k`)$BQx1C zGE_KY(ZDJfIkf>{abr_{ovS?B;)$P;nd}M}94A{+06`$QcSc4=mKaLPJ(o06HXz16tx9>ve^P4*D()bV6{P?;94}O)f zbVV}b^NIHM_Fu6iwrMut$w*c*lS!ojIwA6gu4p{Z)(^9?dJJeXf`Wp6z!cRacQ<2Y z78nI)l>+F235|5p4^QUqP^;@2jNC9bOio8cL~Pr+b7!@&!kZe7Y-A*>;B~4}029Xi zxD0)xC;jkD%}9ukWvxcxS~&Oj_y2>9ku(T$wxy+IOn5#UXR?uztU`Y3b$f0X!4Ulj zf4jce*x28M0H&U~H3qlVJ?PV0;cjyoh9}?DG-n#Ws;cT!Y?FIp9pGER)mB{t_<-)G z^uVMO;!HCU$HN9l4l-N~!;@}FcruZVi~>AmRV#ouXqyi(K^UY5Cb|l>$2Q;D@{cTJ zA{!YsS*9p}j>i~?6ri1W(F0R6pe$oYVH0$IS69~=T~5?G6+ng1$lDdooj4|ab(gz*c* zUSpm<=jmxpKr)s0rJw(J)dbwt1oCjqwzxa`f5&vG!;a=zELF z#N7H|LQ@g!9p>yOGlC-K^hZ;I5-_HoyPwSoO5l0UemJSA1YXbCPiMu7;60rX>4%3s l$GP+Iv^GWXnYDjD|3B}XPLHUVQ91ws002ovPDHLkV1nLrd|Chi literal 0 HcmV?d00001 diff --git a/tools/qt/simulator/default_resource/videoplayer_volume.png b/tools/qt/simulator/default_resource/videoplayer_volume.png new file mode 100644 index 0000000000000000000000000000000000000000..7224ec6239b8eb3d471b88764c3b6e8ad585ff27 GIT binary patch literal 786 zcmV+t1MU2YP)Px%%t=H+R9Fe^mQRR{VHC#ioiR+BDT7g(s7aD!HY8%2#0KThf~+KE!N%Hx28Hrx zCy^93%0d!}*pOmD8QEEAGGhp%k;V*OzvsK>z21AjKfc?{YMN8eJ?A{{bIyCd_ulXO zZlN$~lP56IPry010lWv>$9%0cX#~f>zP!!QZUM3%U|HU}n2zFh@Dj)dV}c9<{rRZM zxnBIt3~BNbMH|5vYa;hOSy?Eswj3M*)4hhc0Xzn>GoJsiM_dQG!5<*IS8|yHTr$lK ze9>=0+yQ=rVKCqGsUA@axC>+|JP4YVU|A=ykoB*T%DMgF25?u6KU?M@c7R9VNfr9Y zWuL%$72umhV5tL`Zd*y_2JlUOGvXD-zxAA&`e|*g!aXOO=f%Xa0pK=)k6^3U$&>r< ze6Q!>il86JmWZzdGfc1sUyaUUvDjwL1sVUeggxM^TDsJF(tn57{0*o9jFWS>D<@9Qc&0=-n;0oe!81!4nK z6p^6OuJmGEEOYd<@SaAF{QTi5n)7 z6x4$gz`qgIBlTUtn%*rBL3`Mb{34Lu4EfwKg?hn!QwJ<{fTV`#_e52tSMlN`O)icm zi@-B*C079=wE7?L6UcVuts6HTo9>RbcJB1JHLbM~162GTYOSBtPwW%;3xeAoFf^%B QPyhe`07*qoM6N<$f(h4gy#N3J literal 0 HcmV?d00001 diff --git a/tools/qt/simulator/drivers/config.h b/tools/qt/simulator/drivers/config.h new file mode 100755 index 0000000..9d86adf --- /dev/null +++ b/tools/qt/simulator/drivers/config.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GRAPHIC_LITE_CONFIG_H +#define GRAPHIC_LITE_CONFIG_H + +#define USE_MOUSE 1 +#define USE_MOUSEWHEEL 1 + +#endif // GRAPHIC_LITE_CONFIG_H diff --git a/tools/qt/simulator/drivers/display/gui_thread.cpp b/tools/qt/simulator/drivers/display/gui_thread.cpp new file mode 100755 index 0000000..fb64777 --- /dev/null +++ b/tools/qt/simulator/drivers/display/gui_thread.cpp @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "gui_thread.h" + +namespace OHOS { +GUIThread::GUIThread(QObject* parent) : QThread(parent) +{ + monitor_ = Monitor::GetInstance(); +} + +GUIThread::~GUIThread() +{ + monitor_ = nullptr; +} + +void GUIThread::run() +{ + if (monitor_ != nullptr) { + monitor_->GUILoopStart(); + } +} + +void GUIThread::Quit() +{ + if (monitor_ != nullptr) { + monitor_->GUILoopQuit(); + } +} +} // namespace OHOS diff --git a/tools/qt/simulator/drivers/display/gui_thread.h b/tools/qt/simulator/drivers/display/gui_thread.h new file mode 100755 index 0000000..4a5bccc --- /dev/null +++ b/tools/qt/simulator/drivers/display/gui_thread.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GRAPHIC_LITE_GUI_THREAD_H +#define GRAPHIC_LITE_GUI_THREAD_H + +#include +#include +#include "common/task_manager.h" +#include "monitor.h" + +namespace OHOS { +class GUIThread : public QThread { + Q_OBJECT +public: + GUIThread(QObject* parent); + virtual ~GUIThread(); + void run() override; + void Quit(); + Monitor* GetMonitor() + { + return monitor_; + }; + +private: + Monitor* monitor_; +}; +} // namespace OHOS + +#endif // GRAPHIC_LITE_GUI_THREAD_H diff --git a/tools/qt/simulator/drivers/display/monitor.cpp b/tools/qt/simulator/drivers/display/monitor.cpp new file mode 100755 index 0000000..394266c --- /dev/null +++ b/tools/qt/simulator/drivers/display/monitor.cpp @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "monitor.h" +#include "common/graphic_startup.h" +#include "common/image_decode_ability.h" +#include "common/input_device_manager.h" +#include "dock/screen_device_proxy.h" +#include "font/ui_font.h" +#include "font/ui_font_header.h" +#include "font/ui_font_vector.h" +#include "mousewheel_input.h" +#include "mouse_input.h" + +namespace OHOS { +void Monitor::Flush(int16_t x1, int16_t y1, int16_t x2, int16_t y2, const uint8_t* buffer, ColorMode mode) +{ + if (buffer == nullptr) { + ScreenDeviceProxy::GetInstance()->OnFlushReady(); + return; + } + + if ((x1 < 0) || (y1 < 0) || (x2 > HORIZONTAL_RESOLUTION - 1) || (y2 > VERTICAL_RESOLUTION - 1)) { + ScreenDeviceProxy::GetInstance()->OnFlushReady(); + return; + } + + int32_t x, y; + if (mode == ARGB8888) { + const Color32* tmp = reinterpret_cast(buffer); + for (y = y1; y <= y2; y++) { + for (x = x1; x <= x2; x++) { + tftFb_[y * HORIZONTAL_RESOLUTION + x] = tmp->full; + tmp++; + } + } + } + ScreenDeviceProxy::GetInstance()->OnFlushReady(); +} + +void Monitor::InitHal() +{ + ScreenDeviceProxy::GetInstance()->SetScreenSize(HORIZONTAL_RESOLUTION, VERTICAL_RESOLUTION); +#if ENABLE_FRAME_BUFFER + ScreenDeviceProxy::GetInstance()->SetFramebuffer(reinterpret_cast(tftFb_), ARGB8888, + HORIZONTAL_RESOLUTION); +#endif + ScreenDeviceProxy::GetInstance()->SetAnimatorbuffer(reinterpret_cast(animaterBuffer_), ARGB8888, + HORIZONTAL_RESOLUTION); + Monitor* display = Monitor::GetInstance(); + ScreenDeviceProxy::GetInstance()->SetDevice(display); + +#if USE_MOUSE + MouseInput* mouse = MouseInput::GetInstance(); + InputDeviceManager::GetInstance()->Add(mouse); +#endif + +#if USE_MOUSEWHEEL && ENABLE_ROTATE_INPUT + MousewheelInput* mousewheel = MousewheelInput::GetInstance(); + InputDeviceManager::GetInstance()->Add(mousewheel); +#endif +} + +void Monitor::RenderFinish() +{ + UpdatePaint(tftFb_, HORIZONTAL_RESOLUTION, VERTICAL_RESOLUTION); +} + +// assuming below are the memory pool +static uint8_t g_fontPsramBaseAddr[OHOS::MIN_FONT_PSRAM_LENGTH]; + +void Monitor::InitFontEngine() +{ + GraphicStartUp::InitFontEngine(reinterpret_cast(g_fontPsramBaseAddr), MIN_FONT_PSRAM_LENGTH, + VECTOR_FONT_DIR, DEFAULT_VECTOR_FONT_FILENAME); +} + +void Monitor::InitImageDecodeAbility() +{ + uint32_t imageType = IMG_SUPPORT_BITMAP | OHOS::IMG_SUPPORT_JPEG | OHOS::IMG_SUPPORT_PNG; + ImageDecodeAbility::GetInstance().SetImageDecodeAbility(imageType); +} + +void Monitor::GUILoopStart() const +{ + Sleep(GUI_REFR_PERIOD); +} + +void Monitor::InitGUI() +{ + for (uint32_t i = 0; i < HORIZONTAL_RESOLUTION * VERTICAL_RESOLUTION; i++) { + tftFb_[i] = defaultColor_; + } + UpdatePaint(tftFb_, HORIZONTAL_RESOLUTION, VERTICAL_RESOLUTION); +} + +void Monitor::GUILoopQuit() const {} +} // namespace OHOS diff --git a/tools/qt/simulator/drivers/display/monitor.h b/tools/qt/simulator/drivers/display/monitor.h new file mode 100755 index 0000000..2683f28 --- /dev/null +++ b/tools/qt/simulator/drivers/display/monitor.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GRAPHIC_LITE_MONITOR_H +#define GRAPHIC_LITE_MONITOR_H + +#include +#include "dock/screen_device.h" +#include "font/ui_font_header.h" + +namespace OHOS { +class Monitor : public QObject, public ScreenDevice { + Q_OBJECT +public: + Monitor() : defaultColor_ (0x44) {} + virtual ~Monitor() {} + static Monitor* GetInstance() + { + static Monitor instance; + return &instance; + } + void Flush(int16_t x1, int16_t y1, int16_t x2, int16_t y2, const uint8_t* buffer, ColorMode mode) override; + void InitHal(); + void InitFontEngine(); + void InitImageDecodeAbility(); + void InitGUI(); + void GUILoopStart() const; + void GUIRefresh(); + void GUILoopQuit() const; + void RenderFinish() override; +signals: + void UpdatePaintSignal(uint32_t* tftFb, uint32_t imgWidth, uint32_t imgHeight); + +protected: + void UpdatePaint(uint32_t* tftFb, uint32_t imgWidth, uint32_t imgHeight) + { + emit UpdatePaintSignal(tftFb, imgWidth, imgHeight); + } + +private: + static constexpr uint32_t GUI_REFR_PERIOD = 50; /* ms */ + uint8_t fontPsramBaseAddr_[MIN_FONT_PSRAM_LENGTH]; + uint32_t tftFb_[HORIZONTAL_RESOLUTION * VERTICAL_RESOLUTION]; + uint32_t animaterBuffer_[HORIZONTAL_RESOLUTION * VERTICAL_RESOLUTION]; + uint32_t defaultColor_; +}; +} // namespace OHOS + +#endif // GRAPHIC_LITE_MONITOR_H diff --git a/tools/qt/simulator/drivers/display/task_thread.cpp b/tools/qt/simulator/drivers/display/task_thread.cpp new file mode 100755 index 0000000..31baedd --- /dev/null +++ b/tools/qt/simulator/drivers/display/task_thread.cpp @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "task_thread.h" + +namespace OHOS { +void TaskThread::run() +{ + taskQuitQry = false; + while (!taskQuitQry) { + TaskManager::GetInstance()->TaskHandler(); + Sleep(DEFAULT_TASK_PERIOD); + } +} + +void TaskThread::Quit() +{ + taskQuitQry = true; +} +} // namespace OHOS diff --git a/tools/qt/simulator/drivers/display/task_thread.h b/tools/qt/simulator/drivers/display/task_thread.h new file mode 100755 index 0000000..6ee4bf8 --- /dev/null +++ b/tools/qt/simulator/drivers/display/task_thread.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GRAPHIC_LITE_TASK_THREAD_H +#define GRAPHIC_LITE_TASK_THREAD_H + +#include +#include +#include "common/task_manager.h" + +namespace OHOS { +class TaskThread : public QThread { +public: + TaskThread() {} + virtual ~TaskThread() {} + void run() override; + void Quit(); + +private: + volatile bool taskQuitQry = false; +}; +} // namespace OHOS + +#endif // GRAPHIC_LITE_TASK_THREAD_H diff --git a/tools/qt/simulator/drivers/indev/mouse_input.cpp b/tools/qt/simulator/drivers/indev/mouse_input.cpp new file mode 100755 index 0000000..ef754ed --- /dev/null +++ b/tools/qt/simulator/drivers/indev/mouse_input.cpp @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "mouse_input.h" +#include +#include + +namespace OHOS { +#if USE_MOUSE +bool MouseInput::Read(DeviceData& data) +{ + data.point.x = lastX_; + data.point.y = lastY_; + data.state = leftButtonDown_ ? STATE_PRESS : STATE_RELEASE; + return false; +} + +void MouseInput::MouseHandler(QMouseEvent* event) +{ + if (event == nullptr) { + return; + } + switch (event->type()) { + case QMouseEvent::Type::MouseButtonRelease: + if (event->button() == Qt::LeftButton) { + leftButtonDown_ = false; + } + break; + case QMouseEvent::Type::MouseButtonPress: + if (event->button() == Qt::LeftButton) { + leftButtonDown_ = true; + lastX_ = event->x(); + lastY_ = event->y(); + } + break; + case QMouseEvent::Type::MouseMove: + lastX_ = event->x(); + lastY_ = event->y(); + break; + default: + break; + } +} +#endif +} // namespace OHOS diff --git a/tools/qt/simulator/drivers/indev/mouse_input.h b/tools/qt/simulator/drivers/indev/mouse_input.h new file mode 100755 index 0000000..7b821ab --- /dev/null +++ b/tools/qt/simulator/drivers/indev/mouse_input.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GRAPHIC_LITE_MOUSE_INPUT_H +#define GRAPHIC_LITE_MOUSE_INPUT_H + +#include +#include "config.h" +#include "dock/pointer_input_device.h" + +namespace OHOS { +#if USE_MOUSE +class MouseInput : public PointerInputDevice { +public: + MouseInput() : leftButtonDown_(false), lastX_(0), lastY_(0) {}; + virtual ~MouseInput() {}; + static MouseInput* GetInstance() + { + static MouseInput mouseInput; + return &mouseInput; + } + bool Read(DeviceData& data) override; + void MouseHandler(QMouseEvent* event); + +private: + bool leftButtonDown_; + int16_t lastX_; + int16_t lastY_; +}; +#endif // USE_MOUSE +} // namespace OHOS +#endif // GRAPHIC_LITE_MOUSE_INPUT_H diff --git a/tools/qt/simulator/drivers/indev/mousewheel_input.cpp b/tools/qt/simulator/drivers/indev/mousewheel_input.cpp new file mode 100755 index 0000000..026d618 --- /dev/null +++ b/tools/qt/simulator/drivers/indev/mousewheel_input.cpp @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "mousewheel_input.h" +#include +#include +#include + +static int16_t g_rotate = 0; + +namespace OHOS { +#if USE_MOUSEWHEEL +#if ENABLE_ROTATE_INPUT +bool MousewheelInput::Read(DeviceData &data) +{ + data.rotate = g_rotate; + g_rotate = 0; + return false; +} +void MousewheelInput::MousewheelHandler(QWheelEvent *event) +{ + g_rotate += -event->delta() / 120; // 120 : Qt use 120 as one mousewheel step value +} +#endif +#endif +} // namespace OHOS diff --git a/tools/qt/simulator/drivers/indev/mousewheel_input.h b/tools/qt/simulator/drivers/indev/mousewheel_input.h new file mode 100755 index 0000000..0553eca --- /dev/null +++ b/tools/qt/simulator/drivers/indev/mousewheel_input.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2020-2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef MOUSEWHEEL_INPUT_H +#define MOUSEWHEEL_INPUT_H + +#include +#include "config.h" +#include "dock/rotate_input_device.h" + +namespace OHOS { +#if USE_MOUSEWHEEL +#if ENABLE_ROTATE_INPUT +class MousewheelInput : public RotateInputDevice { +public: + MousewheelInput() {} + virtual ~MousewheelInput() {} + static MousewheelInput* GetInstance() + { + static MousewheelInput mousewheelInput; + return &mousewheelInput; + } + bool Read(DeviceData& data) override; + void MousewheelHandler(QWheelEvent* event); +}; +#endif // USE_MOUSEWHEEL +#endif +} // namespace OHOS +#endif // MOUSEWHEEL_INPUT_H diff --git a/tools/qt/simulator/font/LICENSE.txt b/tools/qt/simulator/font/LICENSE.txt new file mode 100644 index 0000000..e730679 --- /dev/null +++ b/tools/qt/simulator/font/LICENSE.txt @@ -0,0 +1,96 @@ +Copyright -2020 Adobe (http://www.adobe.com/), with Reserved Font +Name 'Source'. Source is a trademark of Adobe in the United States +and/or other countries. + +This Font Software is licensed under the SIL Open Font License, +Version 1.1. + +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font +creation efforts of academic and linguistic communities, and to +provide a free and open framework in which fonts may be shared and +improved in partnership with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply to +any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software +components as distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, +deleting, or substituting -- in part or in whole -- any of the +components of the Original Version, by changing formats or by porting +the Font Software to a new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, +modify, redistribute, and sell modified and unmodified copies of the +Font Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, in +Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the +corresponding Copyright Holder. This restriction only applies to the +primary font name as presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created using +the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/tools/qt/simulator/font/SourceHanSansSC-Regular.otf b/tools/qt/simulator/font/SourceHanSansSC-Regular.otf new file mode 100644 index 0000000000000000000000000000000000000000..88e3a354619a3908990a26455d27327b31e1b188 GIT binary patch literal 17281372 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

-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%0DeFr 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 odqtvu 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%0DIc2EM&j~86fu&7ZBCSk)9rDWNx{w*> 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 eW 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�%Y{2deWp^HD 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 z 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