├── gpu ├── README.md ├── imgs │ └── leaves.png ├── src │ ├── wgpu_impl │ │ └── shaders │ │ │ ├── clear_texture.wgsl │ │ │ ├── copy_texture.wgsl │ │ │ └── alpha_triangles.wgsl │ └── error.rs └── Cargo.toml ├── algo ├── README.md ├── src │ └── lib.rs └── Cargo.toml ├── core ├── README.md └── src │ ├── context.rs │ ├── builtin_widgets │ ├── icon_miss.svg │ ├── void.rs │ ├── painting_style.rs │ ├── container.rs │ ├── clip.rs │ ├── foreground.rs │ ├── image_widget.rs │ ├── opacity.rs │ ├── text_align.rs │ ├── text_style.rs │ ├── disabled.rs │ ├── ignore_pointer.rs │ ├── track_widget_id.rs │ └── constrained_box.rs │ ├── animation │ └── progress.rs │ ├── events │ ├── device_id.rs │ ├── listener_impl_helper.rs │ ├── pointers │ │ └── from_mouse.rs │ ├── ime_pre_edit.rs │ └── wheel.rs │ ├── animation.rs │ ├── ticker.rs │ ├── local_sender.rs │ └── state │ └── watcher.rs ├── macros ├── README.md ├── src │ ├── pipe_macro.rs │ ├── distinct_pipe_macro.rs │ ├── util.rs │ ├── fn_widget_macro.rs │ ├── asset │ │ ├── basic.rs │ │ └── svg.rs │ └── lerp_derive.rs └── Cargo.toml ├── painter ├── README.md ├── src │ ├── text │ │ └── Lato-Regular.ttf │ ├── lib.rs │ └── style.rs └── Cargo.toml ├── ribir ├── README.md └── src │ ├── platform.rs │ ├── backends.rs │ ├── backends │ ├── mock_backend.rs │ └── wgpu_backend.rs │ └── lib.rs ├── widgets ├── README.md ├── src │ ├── label.rs │ ├── layout.rs │ ├── lib.rs │ ├── transform_box.rs │ └── layout │ │ └── sized_box.rs └── Cargo.toml ├── docs ├── zh │ ├── assets │ ├── practice_todos_app │ │ └── improving_styles_and_animations.md │ ├── get_started │ │ └── try_it.md │ ├── guide │ │ └── README.md │ └── understanding_ribir │ │ └── widget_in_depth.md ├── en │ ├── assets │ │ ├── box_counter.gif │ │ ├── todos-demo.gif │ │ └── todos-widgets.png │ ├── practice_todos_app │ │ ├── improving_styles_and_animations.md │ │ └── _category_.json │ ├── get_started │ │ ├── _category_.json │ │ └── try_it.md │ └── understanding_ribir │ │ └── _category_.json └── readme.md ├── .github ├── workflows │ ├── ISSUE_TEMPLATE │ │ ├── config.yml │ │ └── documentation.md │ ├── dispatch-other-repo.yml │ ├── alpha-version.yml │ └── patch-version.yml ├── ISSUE_TEMPLATE │ ├── others.md │ ├── feature_request.md │ ├── bug_report.md │ └── documentation.md ├── dependabot.yml └── pull_request_template.md ├── cli ├── src │ ├── util │ │ └── mod.rs │ └── program_check.rs ├── template │ └── index.html ├── Cargo.toml └── README.md ├── examples ├── todos │ ├── src │ │ ├── main.rs │ │ ├── lib.rs │ │ └── todos.rs │ ├── README.md │ └── Cargo.toml ├── counter │ ├── src │ │ ├── main.rs │ │ └── lib.rs │ ├── README.md │ ├── ci │ │ ├── bundle-windows.toml │ │ ├── bundle-linux.toml │ │ └── bundle-macos.toml │ └── Cargo.toml ├── messages │ ├── src │ │ ├── main.rs │ │ └── lib.rs │ ├── README.md │ └── Cargo.toml ├── storybook │ ├── src │ │ ├── main.rs │ │ └── lib.rs │ ├── README.md │ └── Cargo.toml ├── wordle_game │ ├── src │ │ ├── main.rs │ │ └── lib.rs │ ├── wordle_win.png │ ├── wordle_guess.png │ ├── README.md │ └── Cargo.toml ├── Logo.ico ├── pomodoro │ ├── icon.ico │ ├── static │ │ ├── icon.png │ │ ├── play.svg │ │ ├── minimize.svg │ │ ├── pause.svg │ │ ├── full.svg │ │ ├── skip_next.svg │ │ ├── close.svg │ │ ├── mini.svg │ │ ├── volume_up.svg │ │ ├── volume_off.svg │ │ ├── pin.svg │ │ └── pin_off.svg │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── attachments │ ├── 3DDD-1.png │ ├── 3DDD-2.png │ ├── 3DDD-3.png │ └── 3DDD-4.png ├── README.md └── animation_demo.rs ├── tests ├── assets_conflict │ ├── dir1 │ │ └── test.txt │ └── dir2 │ │ └── test.txt ├── assets │ ├── test1.svg │ └── fill_with_gradient.svg ├── benches │ ├── widgets_bench.rs │ ├── text_bench.rs │ └── example_bench.rs ├── path_child_test.rs ├── Cargo.toml └── declare_builder_test.rs ├── fonts ├── DejaVuSans.ttf ├── GaramondNo8-Reg.ttf ├── material-search.ttf ├── NotoSerifSC-Bold.你好世界.otf └── Nunito-VariableFont_wght.ttf ├── static ├── hero-banner.png ├── wordle-wasm.png ├── counter_demo.gif ├── theme-switch.gif └── logo.svg ├── .vscode └── settings.json ├── dev-helper ├── src │ └── lib.rs ├── Cargo.toml └── readme.md ├── themes ├── material │ ├── src │ │ ├── fonts │ │ │ ├── Roboto-Medium.ttf │ │ │ └── Roboto-Regular.ttf │ │ ├── classes │ │ │ ├── disabled_cls.rs │ │ │ ├── avatar_cls.rs │ │ │ ├── tooltips_cls.rs │ │ │ ├── badge_cls.rs │ │ │ ├── input_cls.rs │ │ │ ├── menu_cls.rs │ │ │ └── divider_cls.rs │ │ └── classes.rs │ ├── icons │ │ ├── arrow_drop_down_FILL0_wght400_GRAD0_opsz48.svg │ │ ├── menu_FILL0_wght400_GRAD0_opsz48.svg │ │ ├── add_FILL0_wght400_GRAD0_opsz48.svg │ │ ├── done_FILL0_wght400_GRAD0_opsz48.svg │ │ ├── expand_more_FILL0_wght400_GRAD0_opsz48.svg │ │ ├── arrow_back_FILL0_wght400_GRAD0_opsz48.svg │ │ ├── check_FILL0_wght400_GRAD0_opsz48.svg │ │ ├── arrow_forward_FILL0_wght400_GRAD0_opsz48.svg │ │ ├── chevron_right_FILL0_wght400_GRAD0_opsz48.svg │ │ ├── text_caret.svg │ │ ├── home_FILL0_wght400_GRAD0_opsz48.svg │ │ ├── close_FILL0_wght400_GRAD0_opsz48.svg │ │ ├── login_FILL0_wght400_GRAD0_opsz48.svg │ │ ├── file_download_FILL0_wght400_GRAD0_opsz48.svg │ │ ├── logout_FILL0_wght400_GRAD0_opsz48.svg │ │ ├── delete_FILL0_wght400_GRAD0_opsz48.svg │ │ ├── grade_FILL0_wght400_GRAD0_opsz48.svg │ │ ├── star_FILL0_wght400_GRAD0_opsz48.svg │ │ ├── more_vert_FILL0_wght400_GRAD0_opsz48.svg │ │ ├── refresh_FILL0_wght400_GRAD0_opsz48.svg │ │ ├── search_FILL0_wght400_GRAD0_opsz48.svg │ │ ├── miss_icon.svg │ │ ├── sms_FILL0_wght400_GRAD0_opsz48.svg │ │ ├── add_circle_FILL0_wght400_GRAD0_opsz48.svg │ │ ├── more_horiz_FILL0_wght400_GRAD0_opsz48.svg │ │ ├── check_circle_FILL0_wght400_GRAD0_opsz48.svg │ │ ├── cancel_FILL0_wght400_GRAD0_opsz48.svg │ │ ├── favorite_FILL0_wght400_GRAD0_opsz48.svg │ │ ├── info_FILL0_wght400_GRAD0_opsz48.svg │ │ ├── account_circle_FILL0_wght400_GRAD0_opsz48.svg │ │ └── settings_FILL0_wght400_GRAD0_opsz48.svg │ └── Cargo.toml └── ribir_slim │ ├── src │ ├── classes.rs │ └── slim.rs │ └── Cargo.toml ├── test_cases ├── ribir_gpu │ └── gpu_backend │ │ └── tests │ │ ├── smoke_wgpu.png │ │ ├── clip_layers_wgpu.png │ │ ├── two_img_brush_wgpu.png │ │ ├── draw_bundle_svg_wgpu.png │ │ ├── draw_partial_img_wgpu.png │ │ ├── draw_svg_gradient_wgpu.png │ │ ├── multi_draw_phase_wgpu.png │ │ ├── transform_img_brush_wgpu.png │ │ └── stroke_include_border_wgpu.png ├── todos │ └── tests │ │ ├── todos_with_default_by_wgpu.png │ │ └── todos_with_material_by_wgpu.png ├── counter │ └── tests │ │ ├── counter_with_default_by_wgpu.png │ │ ├── counter_with_material_by_wgpu.png │ │ └── counter_with_default_by_wgpu_diff_0.png ├── assets_test │ └── fix_draw_svg_not_apply_alpha_wgpu.png ├── storybook │ └── tests │ │ ├── storybook_with_default_by_wgpu.png │ │ └── storybook_with_material_by_wgpu.png ├── pomodoro │ └── tests │ │ ├── full_pomodoro_with_default_by_wgpu.png │ │ ├── full_pomodoro_with_material_by_wgpu.png │ │ ├── mini_pomodoro_with_default_by_wgpu.png │ │ └── mini_pomodoro_with_material_by_wgpu.png ├── ribir_widgets │ ├── list │ │ └── tests │ │ │ ├── list_with_default_by_wgpu.png │ │ │ └── list_with_material_by_wgpu.png │ ├── badge │ │ └── tests │ │ │ ├── badge_with_default_by_wgpu.png │ │ │ └── badge_with_material_by_wgpu.png │ ├── buttons │ │ └── tests │ │ │ ├── fab_with_default_by_wgpu.png │ │ │ ├── button_with_default_by_wgpu.png │ │ │ ├── fab_with_material_by_wgpu.png │ │ │ ├── button_with_material_by_wgpu.png │ │ │ ├── mini_fab_with_default_by_wgpu.png │ │ │ ├── large_fab_with_default_by_wgpu.png │ │ │ ├── large_fab_with_material_by_wgpu.png │ │ │ ├── mini_fab_with_material_by_wgpu.png │ │ │ ├── filled_button_with_default_by_wgpu.png │ │ │ ├── filled_button_with_material_by_wgpu.png │ │ │ ├── outlined_button_with_default_by_wgpu.png │ │ │ └── outlined_button_with_material_by_wgpu.png │ ├── icon │ │ └── tests │ │ │ ├── icons_with_default_by_wgpu.png │ │ │ ├── icons_with_material_by_wgpu.png │ │ │ ├── keep_icon_visual_with_default_by_wgpu.png │ │ │ └── keep_icon_visual_with_material_by_wgpu.png │ ├── path │ │ └── tests │ │ │ ├── circle40_with_default_by_wgpu.png │ │ │ ├── circle40_with_material_by_wgpu.png │ │ │ ├── circle40_kit_with_default_by_wgpu.png │ │ │ ├── circle40_kit_with_material_by_wgpu.png │ │ │ ├── fill_circle40_with_default_by_wgpu.png │ │ │ ├── fill_circle40_with_material_by_wgpu.png │ │ │ ├── stroke_circle40_with_default_by_wgpu.png │ │ │ └── stroke_circle40_with_material_by_wgpu.png │ ├── scrollbar │ │ └── test │ │ │ ├── init_with_default_by_wgpu.png │ │ │ ├── init_with_material_by_wgpu.png │ │ │ ├── scrolled_with_default_by_wgpu.png │ │ │ └── scrolled_with_material_by_wgpu.png │ ├── switch │ │ └── tests │ │ │ ├── switch_with_default_by_wgpu.png │ │ │ └── switch_with_material_by_wgpu.png │ ├── tabs │ │ └── tests │ │ │ ├── primary_top_with_default_by_wgpu.png │ │ │ ├── primary_bottom_with_default_by_wgpu.png │ │ │ ├── primary_left_with_default_by_wgpu.png │ │ │ ├── primary_left_with_material_by_wgpu.png │ │ │ ├── primary_right_with_default_by_wgpu.png │ │ │ ├── primary_right_with_material_by_wgpu.png │ │ │ ├── primary_top_with_material_by_wgpu.png │ │ │ ├── secondary_left_with_default_by_wgpu.png │ │ │ ├── secondary_top_with_default_by_wgpu.png │ │ │ ├── secondary_top_with_material_by_wgpu.png │ │ │ ├── primary_bottom_with_material_by_wgpu.png │ │ │ ├── secondary_bottom_with_default_by_wgpu.png │ │ │ ├── secondary_left_with_material_by_wgpu.png │ │ │ ├── secondary_right_with_default_by_wgpu.png │ │ │ ├── secondary_right_with_material_by_wgpu.png │ │ │ └── secondary_bottom_with_material_by_wgpu.png │ ├── avatar │ │ └── tests │ │ │ ├── label_avatar_with_default_by_wgpu.png │ │ │ ├── label_avatar_with_material_by_wgpu.png │ │ │ ├── widget_avatar_with_default_by_wgpu.png │ │ │ └── widget_avatar_with_material_by_wgpu.png │ ├── checkbox │ │ └── tests │ │ │ ├── checkbox_with_default_by_wgpu.png │ │ │ └── checkbox_with_material_by_wgpu.png │ ├── radio │ │ └── tests │ │ │ ├── radio_widget_with_default_by_wgpu.png │ │ │ └── radio_widget_with_material_by_wgpu.png │ ├── slider │ │ └── tests │ │ │ ├── slider_widgets_with_default_by_wgpu.png │ │ │ └── slider_widgets_with_material_by_wgpu.png │ └── progress │ │ └── tests │ │ ├── progress_widget_with_default_by_wgpu.png │ │ └── progress_widget_with_material_by_wgpu.png ├── wordle_game │ └── tests │ │ ├── wordle_game_with_default_by_wgpu.png │ │ └── wordle_game_with_material_by_wgpu.png ├── ribir_core │ └── builtin_widgets │ │ ├── border │ │ └── tests │ │ │ ├── all_borders.png │ │ │ ├── left_borders.png │ │ │ ├── top_borders.png │ │ │ ├── bottom_borders.png │ │ │ ├── right_borders.png │ │ │ ├── top_left_borders.png │ │ │ ├── top_right_borders.png │ │ │ ├── bottom_left_borders.png │ │ │ ├── right_bottom_borders.png │ │ │ ├── left_and_right_borders.png │ │ │ ├── top_and_bottom_borders.png │ │ │ ├── bottom_left_and_top_borders.png │ │ │ ├── top_left_and_right_borders.png │ │ │ └── right_bottom_and_left_borders.png │ │ ├── svg │ │ └── tests │ │ │ └── svgs_smoke_wgpu.png │ │ ├── padding │ │ └── tests │ │ │ └── padding_draw.png │ │ ├── smooth_layout │ │ └── tests │ │ │ ├── smooth_x.png │ │ │ ├── smooth_y.png │ │ │ ├── smooth_pos.png │ │ │ ├── smooth_height.png │ │ │ ├── smooth_width.png │ │ │ ├── smooth_size_from_5.png │ │ │ ├── smooth_size_from_50p.png │ │ │ └── smooth_size_from_real.png │ │ ├── clip_boundary │ │ └── tests │ │ │ └── clip_boundary.png │ │ ├── text │ │ └── tests │ │ │ ├── h1_with_default_by_wgpu.png │ │ │ ├── h1_with_material_by_wgpu.png │ │ │ ├── text_clip_with_default_by_wgpu.png │ │ │ ├── text_clip_with_material_by_wgpu.png │ │ │ ├── default_text_with_default_by_wgpu.png │ │ │ ├── default_text_with_material_by_wgpu.png │ │ │ ├── middle_baseline_with_default_by_wgpu.png │ │ │ └── middle_baseline_with_material_by_wgpu.png │ │ ├── color_filter │ │ └── tests │ │ │ ├── color_filter_with_default_by_wgpu.png │ │ │ └── color_filter_with_material_by_wgpu.png │ │ ├── box_shadow │ │ └── tests │ │ │ ├── box_shadow_basic_with_default_by_wgpu.png │ │ │ └── box_shadow_basic_with_material_by_wgpu.png │ │ ├── backdrop_filter │ │ └── tests │ │ │ ├── backdrop_filter_with_default_by_wgpu.png │ │ │ └── backdrop_filter_with_material_by_wgpu.png │ │ └── filter_widget │ │ └── tests │ │ ├── filter_drop_shadow_with_default_by_wgpu.png │ │ └── filter_drop_shadow_with_material_by_wgpu.png └── messages │ └── messages │ └── tests │ ├── messages_with_default_by_wgpu.png │ └── messages_with_material_by_wgpu.png ├── .cargo └── config.toml ├── geom ├── Cargo.toml └── src │ └── lib.rs ├── rustfmt.toml ├── .gitignore ├── LICENSE └── changelog.config.js /gpu/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /algo/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /core/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /macros/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /painter/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ribir/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /widgets/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/zh/assets: -------------------------------------------------------------------------------- 1 | ../en/assets -------------------------------------------------------------------------------- /.github/workflows/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cli/src/util/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod cargo_settings; 2 | -------------------------------------------------------------------------------- /examples/todos/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { todos::run(); } 2 | -------------------------------------------------------------------------------- /examples/counter/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { counter::run(); } 2 | -------------------------------------------------------------------------------- /examples/messages/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { messages::run() } 2 | -------------------------------------------------------------------------------- /tests/assets_conflict/dir1/test.txt: -------------------------------------------------------------------------------- 1 | This is test file from dir1 -------------------------------------------------------------------------------- /tests/assets_conflict/dir2/test.txt: -------------------------------------------------------------------------------- 1 | This is test file from dir2 -------------------------------------------------------------------------------- /examples/storybook/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { storybook::run() } 2 | -------------------------------------------------------------------------------- /examples/wordle_game/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { wordle_game::run() } 2 | -------------------------------------------------------------------------------- /examples/Logo.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/examples/Logo.ico -------------------------------------------------------------------------------- /fonts/DejaVuSans.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/fonts/DejaVuSans.ttf -------------------------------------------------------------------------------- /gpu/imgs/leaves.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/gpu/imgs/leaves.png -------------------------------------------------------------------------------- /static/hero-banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/static/hero-banner.png -------------------------------------------------------------------------------- /static/wordle-wasm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/static/wordle-wasm.png -------------------------------------------------------------------------------- /fonts/GaramondNo8-Reg.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/fonts/GaramondNo8-Reg.ttf -------------------------------------------------------------------------------- /fonts/material-search.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/fonts/material-search.ttf -------------------------------------------------------------------------------- /static/counter_demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/static/counter_demo.gif -------------------------------------------------------------------------------- /static/theme-switch.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/static/theme-switch.gif -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.rustfmt.extraArgs": [ 3 | "+nightly" 4 | ], 5 | } -------------------------------------------------------------------------------- /examples/pomodoro/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/examples/pomodoro/icon.ico -------------------------------------------------------------------------------- /docs/en/assets/box_counter.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/docs/en/assets/box_counter.gif -------------------------------------------------------------------------------- /docs/en/assets/todos-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/docs/en/assets/todos-demo.gif -------------------------------------------------------------------------------- /docs/en/assets/todos-widgets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/docs/en/assets/todos-widgets.png -------------------------------------------------------------------------------- /examples/attachments/3DDD-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/examples/attachments/3DDD-1.png -------------------------------------------------------------------------------- /examples/attachments/3DDD-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/examples/attachments/3DDD-2.png -------------------------------------------------------------------------------- /examples/attachments/3DDD-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/examples/attachments/3DDD-3.png -------------------------------------------------------------------------------- /examples/attachments/3DDD-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/examples/attachments/3DDD-4.png -------------------------------------------------------------------------------- /fonts/NotoSerifSC-Bold.你好世界.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/fonts/NotoSerifSC-Bold.你好世界.otf -------------------------------------------------------------------------------- /dev-helper/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod image_test; 2 | pub use image_test::*; 3 | mod unit_test_describe; 4 | mod widget_test; 5 | -------------------------------------------------------------------------------- /examples/pomodoro/static/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/examples/pomodoro/static/icon.png -------------------------------------------------------------------------------- /examples/wordle_game/wordle_win.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/examples/wordle_game/wordle_win.png -------------------------------------------------------------------------------- /fonts/Nunito-VariableFont_wght.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/fonts/Nunito-VariableFont_wght.ttf -------------------------------------------------------------------------------- /painter/src/text/Lato-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/painter/src/text/Lato-Regular.ttf -------------------------------------------------------------------------------- /examples/wordle_game/wordle_guess.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/examples/wordle_game/wordle_guess.png -------------------------------------------------------------------------------- /docs/zh/practice_todos_app/improving_styles_and_animations.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | --- 4 | 5 | # 改进样式和动画 6 | 7 | > 即将推出 8 | -------------------------------------------------------------------------------- /themes/material/src/fonts/Roboto-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/themes/material/src/fonts/Roboto-Medium.ttf -------------------------------------------------------------------------------- /themes/material/src/fonts/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/themes/material/src/fonts/Roboto-Regular.ttf -------------------------------------------------------------------------------- /themes/ribir_slim/src/classes.rs: -------------------------------------------------------------------------------- 1 | use ribir_core::prelude::Classes; 2 | 3 | pub fn initd_classes() -> Classes { Classes::default() } 4 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Ribir Examples 2 | 3 | To run the examples, you can use the following commands: 4 | 5 | ``` sh 6 | cargo run -p storybook 7 | ``` 8 | -------------------------------------------------------------------------------- /test_cases/ribir_gpu/gpu_backend/tests/smoke_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_gpu/gpu_backend/tests/smoke_wgpu.png -------------------------------------------------------------------------------- /test_cases/todos/tests/todos_with_default_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/todos/tests/todos_with_default_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/todos/tests/todos_with_material_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/todos/tests/todos_with_material_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/counter/tests/counter_with_default_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/counter/tests/counter_with_default_by_wgpu.png -------------------------------------------------------------------------------- /docs/en/practice_todos_app/improving_styles_and_animations.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | --- 4 | 5 | # Improving styles and animations 6 | 7 | > coming soon 8 | -------------------------------------------------------------------------------- /examples/pomodoro/static/play.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /test_cases/assets_test/fix_draw_svg_not_apply_alpha_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/assets_test/fix_draw_svg_not_apply_alpha_wgpu.png -------------------------------------------------------------------------------- /test_cases/counter/tests/counter_with_material_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/counter/tests/counter_with_material_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_gpu/gpu_backend/tests/clip_layers_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_gpu/gpu_backend/tests/clip_layers_wgpu.png -------------------------------------------------------------------------------- /themes/material/icons/arrow_drop_down_FILL0_wght400_GRAD0_opsz48.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test_cases/ribir_gpu/gpu_backend/tests/two_img_brush_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_gpu/gpu_backend/tests/two_img_brush_wgpu.png -------------------------------------------------------------------------------- /test_cases/storybook/tests/storybook_with_default_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/storybook/tests/storybook_with_default_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/storybook/tests/storybook_with_material_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/storybook/tests/storybook_with_material_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/counter/tests/counter_with_default_by_wgpu_diff_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/counter/tests/counter_with_default_by_wgpu_diff_0.png -------------------------------------------------------------------------------- /test_cases/pomodoro/tests/full_pomodoro_with_default_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/pomodoro/tests/full_pomodoro_with_default_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/pomodoro/tests/full_pomodoro_with_material_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/pomodoro/tests/full_pomodoro_with_material_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/pomodoro/tests/mini_pomodoro_with_default_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/pomodoro/tests/mini_pomodoro_with_default_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/pomodoro/tests/mini_pomodoro_with_material_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/pomodoro/tests/mini_pomodoro_with_material_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_gpu/gpu_backend/tests/draw_bundle_svg_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_gpu/gpu_backend/tests/draw_bundle_svg_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_gpu/gpu_backend/tests/draw_partial_img_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_gpu/gpu_backend/tests/draw_partial_img_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_gpu/gpu_backend/tests/draw_svg_gradient_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_gpu/gpu_backend/tests/draw_svg_gradient_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_gpu/gpu_backend/tests/multi_draw_phase_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_gpu/gpu_backend/tests/multi_draw_phase_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_widgets/list/tests/list_with_default_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_widgets/list/tests/list_with_default_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/wordle_game/tests/wordle_game_with_default_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/wordle_game/tests/wordle_game_with_default_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_core/builtin_widgets/border/tests/all_borders.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_core/builtin_widgets/border/tests/all_borders.png -------------------------------------------------------------------------------- /test_cases/ribir_core/builtin_widgets/border/tests/left_borders.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_core/builtin_widgets/border/tests/left_borders.png -------------------------------------------------------------------------------- /test_cases/ribir_core/builtin_widgets/border/tests/top_borders.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_core/builtin_widgets/border/tests/top_borders.png -------------------------------------------------------------------------------- /test_cases/ribir_core/builtin_widgets/svg/tests/svgs_smoke_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_core/builtin_widgets/svg/tests/svgs_smoke_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_gpu/gpu_backend/tests/transform_img_brush_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_gpu/gpu_backend/tests/transform_img_brush_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_widgets/badge/tests/badge_with_default_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_widgets/badge/tests/badge_with_default_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_widgets/buttons/tests/fab_with_default_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_widgets/buttons/tests/fab_with_default_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_widgets/icon/tests/icons_with_default_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_widgets/icon/tests/icons_with_default_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_widgets/icon/tests/icons_with_material_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_widgets/icon/tests/icons_with_material_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_widgets/list/tests/list_with_material_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_widgets/list/tests/list_with_material_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/wordle_game/tests/wordle_game_with_material_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/wordle_game/tests/wordle_game_with_material_by_wgpu.png -------------------------------------------------------------------------------- /themes/material/icons/menu_FILL0_wght400_GRAD0_opsz48.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/others.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Others 3 | about: Create issue by using blank template 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /examples/pomodoro/static/minimize.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /test_cases/messages/messages/tests/messages_with_default_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/messages/messages/tests/messages_with_default_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/messages/messages/tests/messages_with_material_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/messages/messages/tests/messages_with_material_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_core/builtin_widgets/border/tests/bottom_borders.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_core/builtin_widgets/border/tests/bottom_borders.png -------------------------------------------------------------------------------- /test_cases/ribir_core/builtin_widgets/border/tests/right_borders.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_core/builtin_widgets/border/tests/right_borders.png -------------------------------------------------------------------------------- /test_cases/ribir_core/builtin_widgets/padding/tests/padding_draw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_core/builtin_widgets/padding/tests/padding_draw.png -------------------------------------------------------------------------------- /test_cases/ribir_core/builtin_widgets/smooth_layout/tests/smooth_x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_core/builtin_widgets/smooth_layout/tests/smooth_x.png -------------------------------------------------------------------------------- /test_cases/ribir_core/builtin_widgets/smooth_layout/tests/smooth_y.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_core/builtin_widgets/smooth_layout/tests/smooth_y.png -------------------------------------------------------------------------------- /test_cases/ribir_gpu/gpu_backend/tests/stroke_include_border_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_gpu/gpu_backend/tests/stroke_include_border_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_widgets/badge/tests/badge_with_material_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_widgets/badge/tests/badge_with_material_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_widgets/buttons/tests/button_with_default_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_widgets/buttons/tests/button_with_default_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_widgets/buttons/tests/fab_with_material_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_widgets/buttons/tests/fab_with_material_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_widgets/path/tests/circle40_with_default_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_widgets/path/tests/circle40_with_default_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_widgets/path/tests/circle40_with_material_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_widgets/path/tests/circle40_with_material_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_widgets/scrollbar/test/init_with_default_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_widgets/scrollbar/test/init_with_default_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_widgets/scrollbar/test/init_with_material_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_widgets/scrollbar/test/init_with_material_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_widgets/switch/tests/switch_with_default_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_widgets/switch/tests/switch_with_default_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_widgets/switch/tests/switch_with_material_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_widgets/switch/tests/switch_with_material_by_wgpu.png -------------------------------------------------------------------------------- /themes/material/icons/add_FILL0_wght400_GRAD0_opsz48.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test_cases/ribir_core/builtin_widgets/border/tests/top_left_borders.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_core/builtin_widgets/border/tests/top_left_borders.png -------------------------------------------------------------------------------- /test_cases/ribir_core/builtin_widgets/border/tests/top_right_borders.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_core/builtin_widgets/border/tests/top_right_borders.png -------------------------------------------------------------------------------- /test_cases/ribir_core/builtin_widgets/smooth_layout/tests/smooth_pos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_core/builtin_widgets/smooth_layout/tests/smooth_pos.png -------------------------------------------------------------------------------- /test_cases/ribir_widgets/buttons/tests/button_with_material_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_widgets/buttons/tests/button_with_material_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_widgets/buttons/tests/mini_fab_with_default_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_widgets/buttons/tests/mini_fab_with_default_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_widgets/tabs/tests/primary_top_with_default_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_widgets/tabs/tests/primary_top_with_default_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_core/builtin_widgets/border/tests/bottom_left_borders.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_core/builtin_widgets/border/tests/bottom_left_borders.png -------------------------------------------------------------------------------- /test_cases/ribir_core/builtin_widgets/border/tests/right_bottom_borders.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_core/builtin_widgets/border/tests/right_bottom_borders.png -------------------------------------------------------------------------------- /test_cases/ribir_core/builtin_widgets/clip_boundary/tests/clip_boundary.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_core/builtin_widgets/clip_boundary/tests/clip_boundary.png -------------------------------------------------------------------------------- /test_cases/ribir_core/builtin_widgets/smooth_layout/tests/smooth_height.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_core/builtin_widgets/smooth_layout/tests/smooth_height.png -------------------------------------------------------------------------------- /test_cases/ribir_core/builtin_widgets/smooth_layout/tests/smooth_width.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_core/builtin_widgets/smooth_layout/tests/smooth_width.png -------------------------------------------------------------------------------- /test_cases/ribir_widgets/avatar/tests/label_avatar_with_default_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_widgets/avatar/tests/label_avatar_with_default_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_widgets/buttons/tests/large_fab_with_default_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_widgets/buttons/tests/large_fab_with_default_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_widgets/buttons/tests/large_fab_with_material_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_widgets/buttons/tests/large_fab_with_material_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_widgets/buttons/tests/mini_fab_with_material_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_widgets/buttons/tests/mini_fab_with_material_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_widgets/checkbox/tests/checkbox_with_default_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_widgets/checkbox/tests/checkbox_with_default_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_widgets/checkbox/tests/checkbox_with_material_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_widgets/checkbox/tests/checkbox_with_material_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_widgets/path/tests/circle40_kit_with_default_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_widgets/path/tests/circle40_kit_with_default_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_widgets/path/tests/circle40_kit_with_material_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_widgets/path/tests/circle40_kit_with_material_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_widgets/path/tests/fill_circle40_with_default_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_widgets/path/tests/fill_circle40_with_default_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_widgets/path/tests/fill_circle40_with_material_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_widgets/path/tests/fill_circle40_with_material_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_widgets/radio/tests/radio_widget_with_default_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_widgets/radio/tests/radio_widget_with_default_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_widgets/radio/tests/radio_widget_with_material_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_widgets/radio/tests/radio_widget_with_material_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_widgets/scrollbar/test/scrolled_with_default_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_widgets/scrollbar/test/scrolled_with_default_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_widgets/scrollbar/test/scrolled_with_material_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_widgets/scrollbar/test/scrolled_with_material_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_widgets/tabs/tests/primary_bottom_with_default_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_widgets/tabs/tests/primary_bottom_with_default_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_widgets/tabs/tests/primary_left_with_default_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_widgets/tabs/tests/primary_left_with_default_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_widgets/tabs/tests/primary_left_with_material_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_widgets/tabs/tests/primary_left_with_material_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_widgets/tabs/tests/primary_right_with_default_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_widgets/tabs/tests/primary_right_with_default_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_widgets/tabs/tests/primary_right_with_material_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_widgets/tabs/tests/primary_right_with_material_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_widgets/tabs/tests/primary_top_with_material_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_widgets/tabs/tests/primary_top_with_material_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_widgets/tabs/tests/secondary_left_with_default_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_widgets/tabs/tests/secondary_left_with_default_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_widgets/tabs/tests/secondary_top_with_default_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_widgets/tabs/tests/secondary_top_with_default_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_widgets/tabs/tests/secondary_top_with_material_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_widgets/tabs/tests/secondary_top_with_material_by_wgpu.png -------------------------------------------------------------------------------- /themes/material/icons/done_FILL0_wght400_GRAD0_opsz48.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /themes/material/icons/expand_more_FILL0_wght400_GRAD0_opsz48.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test_cases/ribir_core/builtin_widgets/border/tests/left_and_right_borders.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_core/builtin_widgets/border/tests/left_and_right_borders.png -------------------------------------------------------------------------------- /test_cases/ribir_core/builtin_widgets/border/tests/top_and_bottom_borders.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_core/builtin_widgets/border/tests/top_and_bottom_borders.png -------------------------------------------------------------------------------- /test_cases/ribir_core/builtin_widgets/text/tests/h1_with_default_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_core/builtin_widgets/text/tests/h1_with_default_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_core/builtin_widgets/text/tests/h1_with_material_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_core/builtin_widgets/text/tests/h1_with_material_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_widgets/avatar/tests/label_avatar_with_material_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_widgets/avatar/tests/label_avatar_with_material_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_widgets/avatar/tests/widget_avatar_with_default_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_widgets/avatar/tests/widget_avatar_with_default_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_widgets/avatar/tests/widget_avatar_with_material_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_widgets/avatar/tests/widget_avatar_with_material_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_widgets/buttons/tests/filled_button_with_default_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_widgets/buttons/tests/filled_button_with_default_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_widgets/icon/tests/keep_icon_visual_with_default_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_widgets/icon/tests/keep_icon_visual_with_default_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_widgets/path/tests/stroke_circle40_with_default_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_widgets/path/tests/stroke_circle40_with_default_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_widgets/path/tests/stroke_circle40_with_material_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_widgets/path/tests/stroke_circle40_with_material_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_widgets/slider/tests/slider_widgets_with_default_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_widgets/slider/tests/slider_widgets_with_default_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_widgets/tabs/tests/primary_bottom_with_material_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_widgets/tabs/tests/primary_bottom_with_material_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_widgets/tabs/tests/secondary_bottom_with_default_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_widgets/tabs/tests/secondary_bottom_with_default_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_widgets/tabs/tests/secondary_left_with_material_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_widgets/tabs/tests/secondary_left_with_material_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_widgets/tabs/tests/secondary_right_with_default_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_widgets/tabs/tests/secondary_right_with_default_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_widgets/tabs/tests/secondary_right_with_material_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_widgets/tabs/tests/secondary_right_with_material_by_wgpu.png -------------------------------------------------------------------------------- /themes/material/icons/arrow_back_FILL0_wght400_GRAD0_opsz48.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /themes/material/icons/check_FILL0_wght400_GRAD0_opsz48.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test_cases/ribir_core/builtin_widgets/smooth_layout/tests/smooth_size_from_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_core/builtin_widgets/smooth_layout/tests/smooth_size_from_5.png -------------------------------------------------------------------------------- /test_cases/ribir_widgets/buttons/tests/filled_button_with_material_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_widgets/buttons/tests/filled_button_with_material_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_widgets/buttons/tests/outlined_button_with_default_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_widgets/buttons/tests/outlined_button_with_default_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_widgets/buttons/tests/outlined_button_with_material_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_widgets/buttons/tests/outlined_button_with_material_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_widgets/icon/tests/keep_icon_visual_with_material_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_widgets/icon/tests/keep_icon_visual_with_material_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_widgets/progress/tests/progress_widget_with_default_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_widgets/progress/tests/progress_widget_with_default_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_widgets/slider/tests/slider_widgets_with_material_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_widgets/slider/tests/slider_widgets_with_material_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_widgets/tabs/tests/secondary_bottom_with_material_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_widgets/tabs/tests/secondary_bottom_with_material_by_wgpu.png -------------------------------------------------------------------------------- /themes/material/icons/arrow_forward_FILL0_wght400_GRAD0_opsz48.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /themes/material/icons/chevron_right_FILL0_wght400_GRAD0_opsz48.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [env] 2 | CARGO_WORKSPACE_DIR = {value = "", relative = true} 3 | [alias] 4 | run-wasm = "run --package cli -- run-wasm --template ./cli/template" 5 | bundle = "run --package cli -- bundle" 6 | -------------------------------------------------------------------------------- /test_cases/ribir_core/builtin_widgets/border/tests/bottom_left_and_top_borders.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_core/builtin_widgets/border/tests/bottom_left_and_top_borders.png -------------------------------------------------------------------------------- /test_cases/ribir_core/builtin_widgets/border/tests/top_left_and_right_borders.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_core/builtin_widgets/border/tests/top_left_and_right_borders.png -------------------------------------------------------------------------------- /test_cases/ribir_core/builtin_widgets/smooth_layout/tests/smooth_size_from_50p.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_core/builtin_widgets/smooth_layout/tests/smooth_size_from_50p.png -------------------------------------------------------------------------------- /test_cases/ribir_widgets/progress/tests/progress_widget_with_material_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_widgets/progress/tests/progress_widget_with_material_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_core/builtin_widgets/border/tests/right_bottom_and_left_borders.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_core/builtin_widgets/border/tests/right_bottom_and_left_borders.png -------------------------------------------------------------------------------- /test_cases/ribir_core/builtin_widgets/smooth_layout/tests/smooth_size_from_real.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_core/builtin_widgets/smooth_layout/tests/smooth_size_from_real.png -------------------------------------------------------------------------------- /test_cases/ribir_core/builtin_widgets/text/tests/text_clip_with_default_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_core/builtin_widgets/text/tests/text_clip_with_default_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_core/builtin_widgets/text/tests/text_clip_with_material_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_core/builtin_widgets/text/tests/text_clip_with_material_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_core/builtin_widgets/text/tests/default_text_with_default_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_core/builtin_widgets/text/tests/default_text_with_default_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_core/builtin_widgets/text/tests/default_text_with_material_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_core/builtin_widgets/text/tests/default_text_with_material_by_wgpu.png -------------------------------------------------------------------------------- /examples/pomodoro/static/pause.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /test_cases/ribir_core/builtin_widgets/text/tests/middle_baseline_with_default_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_core/builtin_widgets/text/tests/middle_baseline_with_default_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_core/builtin_widgets/text/tests/middle_baseline_with_material_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_core/builtin_widgets/text/tests/middle_baseline_with_material_by_wgpu.png -------------------------------------------------------------------------------- /themes/material/icons/text_caret.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /examples/pomodoro/static/full.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/pomodoro/static/skip_next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/storybook/README.md: -------------------------------------------------------------------------------- 1 | # Story Book 2 | 3 | A widgets workshop. 4 | 5 | You can run with: 6 | 7 | ``` sh 8 | cargo run -p storybook 9 | ``` 10 | 11 | or run in web: 12 | ``` sh 13 | cargo run-wasm -p storybook 14 | ``` -------------------------------------------------------------------------------- /test_cases/ribir_core/builtin_widgets/color_filter/tests/color_filter_with_default_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_core/builtin_widgets/color_filter/tests/color_filter_with_default_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_core/builtin_widgets/color_filter/tests/color_filter_with_material_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_core/builtin_widgets/color_filter/tests/color_filter_with_material_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_core/builtin_widgets/box_shadow/tests/box_shadow_basic_with_default_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_core/builtin_widgets/box_shadow/tests/box_shadow_basic_with_default_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_core/builtin_widgets/box_shadow/tests/box_shadow_basic_with_material_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_core/builtin_widgets/box_shadow/tests/box_shadow_basic_with_material_by_wgpu.png -------------------------------------------------------------------------------- /themes/material/icons/home_FILL0_wght400_GRAD0_opsz48.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test_cases/ribir_core/builtin_widgets/backdrop_filter/tests/backdrop_filter_with_default_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_core/builtin_widgets/backdrop_filter/tests/backdrop_filter_with_default_by_wgpu.png -------------------------------------------------------------------------------- /algo/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::needless_lifetimes)] 2 | 3 | mod cow_rc; 4 | mod frame_cache; 5 | pub use cow_rc::{CowArc, Substr}; 6 | pub use frame_cache::*; 7 | mod resource; 8 | pub use resource::*; 9 | mod sc; 10 | pub use sc::*; 11 | -------------------------------------------------------------------------------- /test_cases/ribir_core/builtin_widgets/backdrop_filter/tests/backdrop_filter_with_material_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_core/builtin_widgets/backdrop_filter/tests/backdrop_filter_with_material_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_core/builtin_widgets/filter_widget/tests/filter_drop_shadow_with_default_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_core/builtin_widgets/filter_widget/tests/filter_drop_shadow_with_default_by_wgpu.png -------------------------------------------------------------------------------- /test_cases/ribir_core/builtin_widgets/filter_widget/tests/filter_drop_shadow_with_material_by_wgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RibirX/Ribir/HEAD/test_cases/ribir_core/builtin_widgets/filter_widget/tests/filter_drop_shadow_with_material_by_wgpu.png -------------------------------------------------------------------------------- /themes/material/icons/close_FILL0_wght400_GRAD0_opsz48.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /widgets/src/label.rs: -------------------------------------------------------------------------------- 1 | use ribir_core::prelude::*; 2 | 3 | pub struct Label(pub PipeValue>); 4 | 5 | impl Label { 6 | #[inline] 7 | pub fn new(str: impl RInto>, K>) -> Self { Self(str.r_into()) } 8 | } 9 | -------------------------------------------------------------------------------- /examples/pomodoro/static/close.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /examples/counter/README.md: -------------------------------------------------------------------------------- 1 | # Counter 2 | 3 | Exampling how to increase count or decrease count via buttons. 4 | 5 | You can run with: 6 | 7 | ``` sh 8 | cargo run -p counter 9 | ``` 10 | or run in web: 11 | ``` sh 12 | cargo run-wasm -p counter 13 | ``` -------------------------------------------------------------------------------- /examples/pomodoro/static/mini.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/en/get_started/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Getting Started", 3 | "position": 2, 4 | "link": { 5 | "type": "generated-index", 6 | "description": "This guide will help you get started with Ribir, covering all the basics you need to know." 7 | } 8 | } -------------------------------------------------------------------------------- /themes/material/icons/login_FILL0_wght400_GRAD0_opsz48.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/messages/README.md: -------------------------------------------------------------------------------- 1 | # Messages 2 | 3 | Exampling a message ui mainly demonstrates the use of `Tabs` and `Lists`. 4 | 5 | You can run with: 6 | 7 | ``` sh 8 | cargo run -p messages 9 | ``` 10 | 11 | or run in web: 12 | ``` sh 13 | cargo run-wasm -p messages 14 | ``` -------------------------------------------------------------------------------- /themes/material/icons/file_download_FILL0_wght400_GRAD0_opsz48.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /themes/material/icons/logout_FILL0_wght400_GRAD0_opsz48.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/en/practice_todos_app/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Practice Todos App", 3 | "position": 3, 4 | "link": { 5 | "type": "generated-index", 6 | "description": "This guide will help you learn Ribir by walking you through a full example of a Todos application." 7 | } 8 | } -------------------------------------------------------------------------------- /themes/material/src/classes/disabled_cls.rs: -------------------------------------------------------------------------------- 1 | use ribir_core::prelude::*; 2 | 3 | pub(super) fn init(classes: &mut Classes) { 4 | classes.insert( 5 | DISABLED, 6 | style_class! { 7 | filter: Filter::grayscale(1.), 8 | opacity: 0.38, 9 | }, 10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /widgets/src/layout.rs: -------------------------------------------------------------------------------- 1 | pub mod text_clamp; 2 | pub use text_clamp::*; 3 | mod sized_box; 4 | pub use sized_box::*; 5 | pub mod no_affected_parent_size; 6 | pub use no_affected_parent_size::*; 7 | mod fractionally; 8 | pub use fractionally::*; 9 | mod linear; 10 | pub use linear::*; 11 | -------------------------------------------------------------------------------- /examples/counter/ci/bundle-windows.toml: -------------------------------------------------------------------------------- 1 | [bundle] 2 | "productName" = "Counter" 3 | "version" = "1.0.0" 4 | "identifier" = "com.ribir.counter" 5 | # Keep CI simple: produce an MSI on Windows 6 | "targets" = ["msi"] 7 | "icon" = ["../Logo.ico"] 8 | "resources" = [] 9 | "externalBin" = [] 10 | -------------------------------------------------------------------------------- /examples/counter/ci/bundle-linux.toml: -------------------------------------------------------------------------------- 1 | [bundle] 2 | "productName" = "Counter" 3 | "version" = "1.0.0" 4 | "identifier" = "com.ribir.counter" 5 | # Keep CI simple: produce an AppImage on Linux 6 | "targets" = ["appimage"] 7 | "icon" = ["../Logo.ico"] 8 | "resources" = [] 9 | "externalBin" = [] 10 | -------------------------------------------------------------------------------- /examples/counter/ci/bundle-macos.toml: -------------------------------------------------------------------------------- 1 | [bundle] 2 | "productName" = "Counter" 3 | "version" = "1.0.0" 4 | "identifier" = "com.ribir.counter" 5 | # Keep CI simple: only build an app bundle on macOS 6 | "targets" = ["app"] 7 | "icon" = ["../Logo.ico"] 8 | "resources" = [] 9 | "externalBin" = [] 10 | -------------------------------------------------------------------------------- /ribir/src/platform.rs: -------------------------------------------------------------------------------- 1 | //! For platform-specific code. 2 | 3 | #[cfg(target_os = "macos")] 4 | mod macos; 5 | #[cfg(target_os = "macos")] 6 | pub use macos::register_platform_app_events_handlers; 7 | 8 | #[cfg(not(target_os = "macos"))] 9 | pub fn register_platform_app_events_handlers() {} 10 | -------------------------------------------------------------------------------- /gpu/src/wgpu_impl/shaders/clear_texture.wgsl: -------------------------------------------------------------------------------- 1 | @vertex 2 | fn vs_main(@location(0) pos: vec2) -> @builtin(position) vec4 { 3 | return vec4(pos * vec2(2., -2.) + vec2(-1., 1.), 0., 1.); 4 | } 5 | 6 | @fragment 7 | fn fs_main() -> @location(0) vec4 { 8 | return vec4(0., 0., 0., 0.); 9 | } -------------------------------------------------------------------------------- /themes/material/icons/delete_FILL0_wght400_GRAD0_opsz48.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /themes/material/icons/grade_FILL0_wght400_GRAD0_opsz48.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /themes/material/icons/star_FILL0_wght400_GRAD0_opsz48.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ribir/src/backends.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "wgpu")] 2 | mod wgpu_backend; 3 | #[cfg(feature = "wgpu")] 4 | pub(crate) use wgpu_backend::WgpuBackend as Backend; 5 | 6 | #[cfg(not(any(feature = "wgpu")))] 7 | mod mock_backend; 8 | #[cfg(not(any(feature = "wgpu")))] 9 | pub(crate) use mock_backend::MockBackend as Backend; 10 | -------------------------------------------------------------------------------- /themes/ribir_slim/src/slim.rs: -------------------------------------------------------------------------------- 1 | use ribir_core::prelude::*; 2 | 3 | pub const SIZE_16: Size = Size::new(16.0, 16.0); 4 | pub const SIZE_24: Size = Size::new(24.0, 24.0); 5 | pub const SIZE_32: Size = Size::new(32.0, 32.0); 6 | pub const SIZE_48: Size = Size::new(48.0, 48.0); 7 | pub const SIZE_64: Size = Size::new(64.0, 64.0); 8 | -------------------------------------------------------------------------------- /examples/todos/README.md: -------------------------------------------------------------------------------- 1 | # Todos 2 | 3 | Example of a to-do list application. Showing CURD of states and auto update the view. 4 | 5 | ![todos](../../docs/en/assets/todos-demo.gif) 6 | 7 | You can run with: 8 | 9 | ``` sh 10 | cargo run -p todos 11 | ``` 12 | or run in web: 13 | ``` sh 14 | cargo run-wasm -p todos 15 | ``` -------------------------------------------------------------------------------- /docs/en/understanding_ribir/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Understanding Ribir", 3 | "position": 3, 4 | "link": { 5 | "type": "generated-index", 6 | "description": "This guide will walk you through the key concepts and features of the Ribir framework, helping you to build efficient and effective user interfaces." 7 | } 8 | } -------------------------------------------------------------------------------- /gpu/src/error.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug)] 2 | pub enum Error { 3 | /// atlas is full unable to store the texture and the texture is grow to its 4 | /// limit, but the texture is good for store in the atlas if it's not store 5 | /// too many others. 6 | TextureSpaceLimit, 7 | /// The image is too large to good for the atlas store. 8 | LargeImageAvoid, 9 | } 10 | -------------------------------------------------------------------------------- /themes/material/icons/more_vert_FILL0_wght400_GRAD0_opsz48.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/pomodoro/static/volume_up.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /themes/material/icons/refresh_FILL0_wght400_GRAD0_opsz48.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/readme.md: -------------------------------------------------------------------------------- 1 | # Ribir Documentation 2 | 3 | Please place all asset files in the 'assets' subdirectory within the respective language directory. This helps us manage the versions of assets that correspond to the documentation when we build the website. 4 | 5 | Since all assets are linked to the same folder, if you need an image for a specific language, create a new image with the language code as a suffix. Please avoid overwriting the original image. -------------------------------------------------------------------------------- /core/src/context.rs: -------------------------------------------------------------------------------- 1 | mod painting_ctx; 2 | pub use painting_ctx::PaintingCtx; 3 | mod layout_ctx; 4 | mod visual_ctx; 5 | mod widget_ctx; 6 | pub use layout_ctx::*; 7 | pub use visual_ctx::*; 8 | pub use widget_ctx::*; 9 | pub(crate) mod build_ctx; 10 | pub use build_ctx::BuildCtx; 11 | pub mod app_ctx; 12 | pub use app_ctx::*; 13 | mod build_variant; 14 | #[cfg(feature = "test-utils")] 15 | pub use app_ctx::test_utils; 16 | pub use build_variant::*; 17 | -------------------------------------------------------------------------------- /themes/material/icons/search_FILL0_wght400_GRAD0_opsz48.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /geom/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors.workspace = true 3 | categories.workspace = true 4 | description.workspace = true 5 | documentation.workspace = true 6 | edition.workspace = true 7 | homepage.workspace = true 8 | keywords.workspace = true 9 | license.workspace = true 10 | name = "ribir_geom" 11 | readme.workspace = true 12 | repository = "https://github.com/RibirX/Ribir/geom" 13 | version.workspace = true 14 | 15 | [dependencies] 16 | euclid.workspace = true 17 | -------------------------------------------------------------------------------- /examples/messages/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod messages; 2 | pub use messages::messages; 3 | use ribir::prelude::*; 4 | 5 | #[cfg_attr(target_arch = "wasm32", wasm_bindgen::prelude::wasm_bindgen)] 6 | pub fn run() { 7 | #[cfg(target_arch = "wasm32")] 8 | std::panic::set_hook(Box::new(console_error_panic_hook::hook)); 9 | 10 | App::run(messages) 11 | .with_app_theme(material::purple::light) 12 | .with_size(Size::new(400., 600.)) 13 | .with_title("Messages"); 14 | } 15 | -------------------------------------------------------------------------------- /examples/pomodoro/static/volume_off.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /core/src/builtin_widgets/icon_miss.svg: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /themes/material/icons/miss_icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /painter/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::needless_lifetimes)] 2 | 3 | //! A 2d logic painter, generate the paint command 4 | pub mod color; 5 | pub mod filter; 6 | mod painter; 7 | pub mod path; 8 | pub mod path_builder; 9 | pub use path::*; 10 | mod text; 11 | pub use text::*; 12 | 13 | pub use crate::{ 14 | color::{Color, GradientStop, LightnessTone}, 15 | filter::*, 16 | painter::*, 17 | }; 18 | pub mod image; 19 | mod style; 20 | pub use style::*; 21 | 22 | pub use crate::image::PixelImage; 23 | mod svg; 24 | pub use svg::Svg; 25 | -------------------------------------------------------------------------------- /tests/assets/test1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | checkbox--checked 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /macros/src/pipe_macro.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::quote_spanned; 3 | use syn::spanned::Spanned; 4 | 5 | use crate::{error::result_to_token_stream, symbol_process::*, watch_macro::*}; 6 | 7 | pub fn gen_code(input: TokenStream, refs_ctx: Option<&mut DollarRefsCtx>) -> TokenStream { 8 | let span = input.span(); 9 | let res = process_watch_body(input, refs_ctx).map(|WatchBody { upstream, map_handler }| { 10 | quote_spanned! {span => Pipe::new(#upstream.box_it(), #map_handler) } 11 | }); 12 | result_to_token_stream(res) 13 | } 14 | -------------------------------------------------------------------------------- /ribir/src/backends/mock_backend.rs: -------------------------------------------------------------------------------- 1 | use crate::winit_shell_wnd::WinitBackend; 2 | 3 | pub struct MockBackend; 4 | 5 | impl WinitBackend for MockBackend { 6 | fn new(_: &winit::window::Window) -> Self { Self } 7 | 8 | fn on_resize(&mut self, _: ribir_core::prelude::DeviceSize) {} 9 | 10 | fn begin_frame(&mut self) {} 11 | 12 | fn draw_commands( 13 | &mut self, _: ribir_core::prelude::DeviceRect, _: Vec, 14 | _: ribir_core::prelude::Color, 15 | ) { 16 | } 17 | 18 | fn end_frame(&mut self) {} 19 | } 20 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | error_on_line_overflow = true 2 | error_on_unformatted = false 3 | fn_single_line = true 4 | use_small_heuristics = "Max" 5 | tab_spaces = 2 6 | style_edition = "2024" 7 | wrap_comments = true 8 | merge_derives = false 9 | 10 | unstable_features = true 11 | format_code_in_doc_comments = true 12 | format_macro_bodies = true 13 | format_macro_matchers = true 14 | format_strings = true 15 | imports_granularity = "Crate" 16 | group_imports = "StdExternalCrate" 17 | normalize_doc_attributes = true 18 | fn_params_layout = "Compressed" 19 | chain_width = 50 -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "cargo" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /cli/template/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Ribir 7 | 12 | 13 | 14 | 15 |
16 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /themes/material/icons/sms_FILL0_wght400_GRAD0_opsz48.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /themes/material/icons/add_circle_FILL0_wght400_GRAD0_opsz48.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /themes/material/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors.workspace = true 3 | categories.workspace = true 4 | description.workspace = true 5 | documentation.workspace = true 6 | edition.workspace = true 7 | homepage.workspace = true 8 | keywords.workspace = true 9 | license.workspace = true 10 | name = "ribir_material" 11 | readme.workspace = true 12 | repository = "https://github.com/RibirX/Ribir/themes/material" 13 | version.workspace = true 14 | 15 | [dependencies] 16 | ribir_core = {path = "../../core", version = "0.4.0-alpha.53" } 17 | ribir_widgets = {path = "../../widgets", version = "0.4.0-alpha.53" } 18 | -------------------------------------------------------------------------------- /ribir/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub use ribir_core as core; 2 | #[cfg(feature = "widgets")] 3 | pub use ribir_widgets as widgets; 4 | pub mod app; 5 | mod backends; 6 | 7 | #[cfg(not(target_arch = "wasm32"))] 8 | pub mod clipboard; 9 | mod winit_shell_wnd; 10 | #[cfg(feature = "material")] 11 | pub use ribir_material as material; 12 | 13 | mod platform; 14 | pub use platform::*; 15 | pub mod prelude { 16 | pub use ribir_core::prelude::*; 17 | 18 | #[cfg(feature = "material")] 19 | pub use super::material; 20 | #[cfg(feature = "widgets")] 21 | pub use super::widgets::prelude::*; 22 | pub use crate::app::*; 23 | } 24 | -------------------------------------------------------------------------------- /themes/material/icons/more_horiz_FILL0_wght400_GRAD0_opsz48.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /themes/material/icons/check_circle_FILL0_wght400_GRAD0_opsz48.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /themes/ribir_slim/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors.workspace = true 3 | categories.workspace = true 4 | description.workspace = true 5 | documentation.workspace = true 6 | edition.workspace = true 7 | homepage.workspace = true 8 | keywords.workspace = true 9 | license.workspace = true 10 | name = "ribir_slim" 11 | readme.workspace = true 12 | repository = "https://github.com/RibirX/Ribir/themes/ribir_slim" 13 | version.workspace = true 14 | publish = false 15 | 16 | [dependencies] 17 | ribir_core = {path = "../../core", version = "0.4.0-alpha.53" } 18 | ribir_widgets = {path = "../../widgets", version = "0.4.0-alpha.53" } 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | .git-old/** 13 | 14 | #Added by cargo 15 | # 16 | #already existing elements were commented out 17 | 18 | /target 19 | #Cargo.lock 20 | .vscode/* 21 | 22 | **/.DS_Store 23 | **/.log/** 24 | /test_cases/**/*_actual.png 25 | /test_cases/**/*_diff.png -------------------------------------------------------------------------------- /themes/material/icons/cancel_FILL0_wght400_GRAD0_opsz48.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /algo/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors.workspace = true 3 | categories.workspace = true 4 | description.workspace = true 5 | documentation.workspace = true 6 | edition.workspace = true 7 | homepage.workspace = true 8 | keywords.workspace = true 9 | license.workspace = true 10 | name = "ribir_algo" 11 | readme.workspace = true 12 | repository = "https://github.com/RibirX/Ribir/algo" 13 | version.workspace = true 14 | 15 | [dependencies] 16 | triomphe.workspace = true 17 | ahash.workspace = true 18 | log.workspace = true 19 | serde = {workspace = true, features = ["derive", "rc"]} 20 | 21 | [dev-dependencies] 22 | scoped_threadpool.workspace = true 23 | -------------------------------------------------------------------------------- /macros/src/distinct_pipe_macro.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::quote_spanned; 3 | use syn::spanned::Spanned; 4 | 5 | use crate::{error::result_to_token_stream, symbol_process::*, watch_macro::*}; 6 | 7 | pub fn gen_code(input: TokenStream, refs_ctx: Option<&mut DollarRefsCtx>) -> TokenStream { 8 | let span = input.span(); 9 | let res = process_watch_body(input, refs_ctx).map(|WatchBody { upstream, map_handler }| { 10 | quote_spanned! {span => 11 | Pipe::new(#upstream.box_it(), #map_handler) 12 | .transform(|s| s.distinct_until_changed().box_it()) 13 | } 14 | }); 15 | result_to_token_stream(res) 16 | } 17 | -------------------------------------------------------------------------------- /tests/benches/widgets_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{Bencher, Criterion, criterion_group, criterion_main}; 2 | use ribir::{core::test_helper::*, prelude::*}; 3 | 4 | fn widget_bench(b: &mut Bencher, w: GenWidget) { 5 | let wnd = TestWindow::from_widget(w); 6 | b.iter(|| wnd.draw_frame()); 7 | AppCtx::remove_wnd(wnd.id()) 8 | } 9 | 10 | fn widgets_bench_one_by_one(c: &mut Criterion) { 11 | c.bench_function("checkbox", |b| { 12 | widget_bench(b, fn_widget!(@Checkbox { checked: true, indeterminate: true }).r_into()); 13 | }); 14 | } 15 | 16 | criterion_group!(widgets_benches, widgets_bench_one_by_one); 17 | criterion_main!(widgets_benches); 18 | -------------------------------------------------------------------------------- /cli/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cli" 3 | version = "0.4.0-alpha.53" 4 | edition = "2021" 5 | publish = false 6 | 7 | [dependencies] 8 | xshell = "0.2.3" 9 | clap = {version = "4.5.4", features = ["cargo", "derive"] } 10 | anyhow = "1.0" 11 | fs_extra = "1.3.0" 12 | notify-debouncer-mini = "0.6.0" 13 | gitignore = "1.0.8" 14 | tauri-bundler.workspace = true 15 | tauri-utils.workspace = true 16 | serde_json.workspace = true 17 | serde = {workspace = true, features = ["derive"]} 18 | serde-value.workspace = true 19 | serde_with.workspace = true 20 | toml.workspace = true 21 | log = { workspace = true, features = ["kv", "kv_std"]} 22 | env_logger.workspace = true 23 | -------------------------------------------------------------------------------- /themes/material/icons/favorite_FILL0_wght400_GRAD0_opsz48.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /core/src/animation/progress.rs: -------------------------------------------------------------------------------- 1 | #[derive(PartialEq, Copy, Clone, Debug)] 2 | pub enum AnimateProgress { 3 | Dismissed, 4 | Between(f32), 5 | Finish, 6 | } 7 | 8 | impl AnimateProgress { 9 | pub fn value(&self) -> f32 { 10 | match self { 11 | AnimateProgress::Dismissed => 0., 12 | AnimateProgress::Between(val) => *val, 13 | AnimateProgress::Finish => 1., 14 | } 15 | } 16 | 17 | #[inline] 18 | pub fn is_dismissed(&self) -> bool { matches!(self, AnimateProgress::Dismissed) } 19 | 20 | #[inline] 21 | pub fn is_between(&self) -> bool { matches!(self, AnimateProgress::Between(_)) } 22 | 23 | #[inline] 24 | pub fn is_finish(&self) -> bool { matches!(self, AnimateProgress::Finish) } 25 | } 26 | -------------------------------------------------------------------------------- /core/src/builtin_widgets/void.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::*; 2 | 3 | /// A widget that represents an empty node in the widget tree. 4 | /// 5 | /// This widget is used when you need a placeholder widget that doesn't render 6 | /// anything and doesn't accept children. It's useful for conditional rendering 7 | /// or as a neutral widget in compositions. 8 | /// 9 | /// # Example 10 | /// 11 | /// ```rust no_run 12 | /// use ribir::prelude::*; 13 | /// 14 | /// fn_widget! { 15 | /// @Void {} 16 | /// }; 17 | /// ``` 18 | #[derive(Declare)] 19 | pub struct Void; 20 | 21 | impl Render for Void { 22 | fn perform_layout(&self, clamp: BoxClamp, _: &mut LayoutCtx) -> Size { clamp.min } 23 | 24 | fn paint(&self, _: &mut PaintingCtx) {} 25 | } 26 | -------------------------------------------------------------------------------- /gpu/src/wgpu_impl/shaders/copy_texture.wgsl: -------------------------------------------------------------------------------- 1 | struct VertexOutput { 2 | @builtin(position) pos: vec4, 3 | @location(0) tex_pos: vec2, 4 | } 5 | 6 | @vertex 7 | fn vs_main(@location(0) input_pos: vec2, @location(1) tex: vec2) -> VertexOutput { 8 | var output: VertexOutput; 9 | let pos = input_pos * vec2(2., -2.) + vec2(-1., 1.); 10 | output.pos = vec4(pos, 0.0, 1.0); 11 | output.tex_pos = tex; 12 | return output; 13 | } 14 | 15 | @group(0) @binding(0) 16 | var texture: texture_2d; 17 | @group(0) @binding(1) 18 | var tex_sampler: sampler; 19 | 20 | 21 | @fragment 22 | fn fs_main(@location(0) tex_pos: vec2) -> @location(0) vec4 { 23 | return textureSample(texture, tex_sampler, tex_pos); 24 | } -------------------------------------------------------------------------------- /themes/material/icons/info_FILL0_wght400_GRAD0_opsz48.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/todos/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod todos; 2 | use ribir::prelude::*; 3 | mod ui; 4 | pub use ui::todos; 5 | 6 | #[cfg_attr(target_arch = "wasm32", wasm_bindgen::prelude::wasm_bindgen)] 7 | pub fn run() { 8 | #[cfg(target_arch = "wasm32")] 9 | std::panic::set_hook(Box::new(console_error_panic_hook::hook)); 10 | 11 | App::run(todos) 12 | .with_app_theme(material::purple::light) 13 | .with_title("Todos"); 14 | } 15 | 16 | #[cfg(test)] 17 | mod tests { 18 | use ribir::{core::test_helper::*, material as ribir_material}; 19 | use ribir_dev_helper::*; 20 | 21 | use super::*; 22 | 23 | widget_image_tests!( 24 | todos, 25 | WidgetTester::new(todos) 26 | .with_wnd_size(Size::new(400., 640.)) 27 | .with_comparison(0.0002) 28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /core/src/events/device_id.rs: -------------------------------------------------------------------------------- 1 | pub trait DeviceId: Send { 2 | fn as_any(&self) -> &dyn std::any::Any; 3 | fn is_same_device(&self, other: &dyn DeviceId) -> bool; 4 | fn clone_boxed(&self) -> Box; 5 | } 6 | 7 | #[derive(Copy, Clone, PartialEq, Eq, Hash)] 8 | pub struct DummyDeviceId; 9 | 10 | impl DeviceId for DummyDeviceId { 11 | fn as_any(&self) -> &dyn std::any::Any { self } 12 | fn is_same_device(&self, other: &dyn DeviceId) -> bool { 13 | other 14 | .as_any() 15 | .downcast_ref::() 16 | .is_some_and(|this| this == self) 17 | } 18 | 19 | fn clone_boxed(&self) -> Box { Box::new(*self) } 20 | } 21 | 22 | impl Clone for Box { 23 | fn clone(&self) -> Box { self.clone_boxed() } 24 | } 25 | -------------------------------------------------------------------------------- /core/src/animation.rs: -------------------------------------------------------------------------------- 1 | pub mod easing; 2 | mod progress; 3 | mod transition; 4 | pub use easing::Easing; 5 | pub use progress::AnimateProgress; 6 | pub use transition::*; 7 | mod animate; 8 | pub use animate::*; 9 | mod lerp; 10 | pub use lerp::Lerp; 11 | mod animate_state; 12 | pub use animate_state::*; 13 | mod stagger; 14 | pub use stagger::Stagger; 15 | mod keyframes; 16 | pub use keyframes::*; 17 | 18 | /// Trait to describe how to control the animation. 19 | pub trait Animation { 20 | /// Start the animation. 21 | fn run(&self); 22 | /// Stop the animation if it is running, otherwise do nothing. 23 | fn stop(&self); 24 | /// Check if the animation is running. 25 | fn is_running(&self) -> bool; 26 | /// clone the animation. 27 | fn box_clone(&self) -> Box; 28 | } 29 | -------------------------------------------------------------------------------- /examples/wordle_game/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod ui; 2 | pub mod wordle; 3 | use ribir::prelude::*; 4 | pub use ui::wordle_game; 5 | 6 | #[cfg_attr(target_arch = "wasm32", wasm_bindgen::prelude::wasm_bindgen)] 7 | pub fn run() { 8 | #[cfg(target_arch = "wasm32")] 9 | std::panic::set_hook(Box::new(console_error_panic_hook::hook)); 10 | 11 | App::run(wordle_game) 12 | .with_app_theme(material::purple::light) 13 | .with_size(Size::new(700., 620.)); 14 | } 15 | 16 | #[cfg(test)] 17 | mod tests { 18 | use ribir::{core::test_helper::*, material as ribir_material}; 19 | use ribir_dev_helper::*; 20 | 21 | use super::*; 22 | 23 | widget_image_tests!( 24 | wordle_game, 25 | WidgetTester::new(wordle_game) 26 | .with_wnd_size(Size::new(700., 620.)) 27 | .with_comparison(0.008) 28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /examples/storybook/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod storybook; 2 | use ribir::prelude::*; 3 | pub use storybook::storybook; 4 | 5 | #[cfg_attr(target_arch = "wasm32", wasm_bindgen::prelude::wasm_bindgen)] 6 | pub fn run() { 7 | #[cfg(target_arch = "wasm32")] 8 | std::panic::set_hook(Box::new(console_error_panic_hook::hook)); 9 | 10 | App::run(storybook) 11 | .with_app_theme(material::purple::light) 12 | .with_title("Storybook") 13 | .with_size(Size::new(1024., 768.)); 14 | } 15 | 16 | #[cfg(test)] 17 | mod tests { 18 | use ribir::{core::test_helper::*, material as ribir_material}; 19 | use ribir_dev_helper::*; 20 | 21 | use super::*; 22 | 23 | widget_image_tests!( 24 | storybook, 25 | WidgetTester::new(storybook) 26 | .with_wnd_size(Size::new(1024., 768.)) 27 | .with_comparison(0.001) 28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors.workspace = true 3 | categories.workspace = true 4 | description.workspace = true 5 | documentation.workspace = true 6 | edition.workspace = true 7 | homepage.workspace = true 8 | keywords.workspace = true 9 | license.workspace = true 10 | name = "ribir_macros" 11 | readme.workspace = true 12 | repository = "https://github.com/RibirX/Ribir/macros" 13 | version.workspace = true 14 | 15 | [lib] 16 | proc-macro = true 17 | 18 | [dependencies] 19 | ahash.workspace = true 20 | bitflags.workspace = true 21 | proc-macro2.workspace = true 22 | quote.workspace = true 23 | ribir_painter = {path = "../painter", version = "0.4.0-alpha.53" } 24 | smallvec = { workspace = true, features= ["drain_filter"] } 25 | syn = { workspace = true, features = ["fold", "full", "extra-traits"]} 26 | heck.workspace = true 27 | -------------------------------------------------------------------------------- /tests/assets/fill_with_gradient.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /widgets/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod avatar; 2 | pub mod badge; 3 | pub mod buttons; 4 | pub mod checkbox; 5 | pub mod common_widget; 6 | pub mod divider; 7 | pub mod grid_view; 8 | pub mod icon; 9 | pub mod input; 10 | pub mod label; 11 | pub mod layout; 12 | pub mod list; 13 | pub mod menu; 14 | pub mod path; 15 | pub mod progress; 16 | pub mod radio; 17 | pub mod router; 18 | pub mod scrollbar; 19 | pub mod select_region; 20 | pub mod slider; 21 | pub mod switch; 22 | pub mod tabs; 23 | 24 | pub mod transform_box; 25 | pub mod prelude { 26 | pub use super::{ 27 | avatar::*, badge::*, buttons::*, checkbox::*, common_widget::*, divider::*, grid_view::*, 28 | icon::*, input::*, label::*, layout::*, list::*, menu::*, path::*, progress::*, radio::*, 29 | router::*, scrollbar::*, select_region::*, slider::*, switch::*, tabs::*, transform_box::*, 30 | }; 31 | } 32 | -------------------------------------------------------------------------------- /dev-helper/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors.workspace = true 3 | categories.workspace = true 4 | description.workspace = true 5 | documentation.workspace = true 6 | edition.workspace = true 7 | homepage.workspace = true 8 | keywords.workspace = true 9 | license.workspace = true 10 | name = "ribir_dev_helper" 11 | repository = "https://github.com/RibirX/Ribir/test-helper" 12 | version.workspace = true 13 | 14 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 15 | 16 | [dependencies] 17 | futures.workspace = true 18 | ribir_geom = {path = "../geom", version = "0.4.0-alpha.53" } 19 | ribir_gpu = {path = "../gpu", version = "0.4.0-alpha.53" } 20 | ribir_painter = {path = "../painter", features = ["png"], version = "0.4.0-alpha.53" } 21 | image.workspace = true 22 | dssim-core.workspace = true 23 | 24 | [dev-dependencies] 25 | colored.workspace = true 26 | -------------------------------------------------------------------------------- /themes/material/icons/account_circle_FILL0_wght400_GRAD0_opsz48.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/wordle_game/README.md: -------------------------------------------------------------------------------- 1 | # Wordle Game 2 | 3 | Example of a wordle game. 4 | 5 | You can run with: 6 | 7 | ``` sh 8 | cargo run -p wordle_game 9 | ``` 10 | 11 | or run in web: 12 | ``` sh 13 | cargo run-wasm -p wordle_game 14 | ``` 15 | 16 | ## How to play 17 | 18 | You have to guess the hidden word in 5 tries and the color of the letters hints how close you are. 19 | To start the game, just enter the word and press 'Enter' to submit, for example: 20 | 21 | 22 | 23 | ``` text 24 | Pink color like O and T, hint that not the letter in the target word at all. 25 | Light orange color like R, hint that the letter in the word but in the wrong spot. 26 | Green color like B and I, hint that the letter in the word and in the correct spot. 27 | ``` 28 | 29 | Another try to find matching letters in the target word. 30 | 31 | 32 | -------------------------------------------------------------------------------- /static/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Layer 1 5 | 7 | 10 | 12 | 14 | 15 | -------------------------------------------------------------------------------- /tests/benches/text_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{Criterion, criterion_group, criterion_main}; 2 | use font_db::GlyphBaseline; 3 | use ribir_painter::{shaper::*, *}; 4 | 5 | fn shape_1k(c: &mut Criterion) { 6 | let mut shaper = TextShaper::new(<_>::default()); 7 | shaper.font_db().borrow_mut().load_system_fonts(); 8 | 9 | let ids = shaper 10 | .font_db() 11 | .borrow_mut() 12 | .select_all_match(&FontFace { 13 | families: Box::new([FontFamily::Serif, FontFamily::Cursive]), 14 | ..<_>::default() 15 | }); 16 | 17 | c.bench_function("shape_1k", |b| { 18 | b.iter(|| { 19 | // clean cache 20 | shaper.end_frame(); 21 | shaper.end_frame(); 22 | 23 | let str = include_str!("../../LICENSE").into(); 24 | shaper.shape_text(&str, &ids, TextDirection::LeftToRight, GlyphBaseline::Alphabetic) 25 | }) 26 | }); 27 | } 28 | 29 | criterion_group!(text_benches, shape_1k); 30 | criterion_main!(text_benches); 31 | -------------------------------------------------------------------------------- /themes/material/src/classes/avatar_cls.rs: -------------------------------------------------------------------------------- 1 | use ribir_core::prelude::*; 2 | use ribir_widgets::avatar::*; 3 | 4 | use crate::md; 5 | 6 | pub(super) fn init(classes: &mut Classes) { 7 | named_styles_impl!( base_container => { 8 | clamp: BoxClamp::fixed_size(md::SIZE_40), 9 | radius: md::RADIUS_20, 10 | }); 11 | 12 | classes.insert(AVATAR_WIDGET_CONTAINER, base_container); 13 | classes.insert(AVATAR_WIDGET, empty_cls); 14 | classes.insert( 15 | AVATAR_LABEL_CONTAINER, 16 | class_multi_impl![ 17 | style_class! { background: BuildCtx::color().into_container_color(BuildCtx::get()) }, 18 | base_container 19 | ], 20 | ); 21 | classes.insert( 22 | AVATAR_LABEL, 23 | style_class! { 24 | foreground: BuildCtx::color().on_this_container_color(BuildCtx::get()), 25 | text_style: TypographyTheme::of(BuildCtx::get()).title_medium.text.clone(), 26 | h_align: HAlign::Center, 27 | v_align: VAlign::Center, 28 | }, 29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /dev-helper/readme.md: -------------------------------------------------------------------------------- 1 | # Development Helper 2 | 3 | This library offers macros to facilitate testing for `Ribir`. 4 | 5 | ## Dependencies 6 | 7 | To utilize these macros, include `paste` and `ribir_dev_helper` in the `[dev-dependencies]` section of your `Cargo.toml`. For more details, refer to the macro documentation. 8 | 9 | ## Test Case Files 10 | 11 | These macros may require reading files for testing. All such files are sourced from the `test_cases` directory located at the root of your workspace. 12 | 13 | Use the `RIBIR_IMG_TEST=overwrite` environment variable to overwrite or generate the files. For instance, `RIBIR_IMG_TEST=overwrite cargo test` can be used to overwrite all test case files. For a specific test, use `RIBIR_IMG_TEST=overwrite cargo test -- test_name`. 14 | 15 | For image tests, if the actual image differs from the expected one, both the actual image and the difference image are saved alongside the expected image. The difference image represents the discrepancies between the actual and expected images. -------------------------------------------------------------------------------- /examples/wordle_game/Cargo.toml: -------------------------------------------------------------------------------- 1 | 2 | [package] 3 | authors.workspace = true 4 | categories.workspace = true 5 | description.workspace = true 6 | documentation.workspace = true 7 | edition.workspace = true 8 | homepage.workspace = true 9 | keywords.workspace = true 10 | license.workspace = true 11 | name = "wordle_game" 12 | publish = false 13 | version.workspace = true 14 | 15 | [dependencies] 16 | paste.workspace = true 17 | ribir = {path = "../../ribir", features = ["material", "widgets"]} 18 | csv = "1.3.0" 19 | rand = "0.9.1" 20 | getrandom-v3 = { workspace = true } 21 | 22 | [target.'cfg(target_arch = "wasm32")'.dependencies] 23 | console_error_panic_hook = "0.1.6" 24 | wasm-bindgen = "0.2.92" 25 | 26 | [dev-dependencies] 27 | ribir_dev_helper = {path = "../../dev-helper"} 28 | ribir_slim = { path = "../../themes/ribir_slim" } 29 | ribir_core = { path = "../../core", features = ["test-utils"]} 30 | 31 | [features] 32 | wgpu = ["ribir/wgpu"] 33 | 34 | 35 | [lib] 36 | crate-type = ["cdylib", "rlib"] 37 | path = "src/lib.rs" 38 | 39 | -------------------------------------------------------------------------------- /examples/storybook/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors.workspace = true 3 | categories.workspace = true 4 | description.workspace = true 5 | documentation.workspace = true 6 | edition.workspace = true 7 | homepage.workspace = true 8 | keywords.workspace = true 9 | license.workspace = true 10 | name = "storybook" 11 | publish = false 12 | version.workspace = true 13 | 14 | [dependencies] 15 | paste.workspace = true 16 | # we disable `default-features`, because we want more control over testing. 17 | ribir = {path = "../../ribir", features = ["material", "widgets"]} 18 | webbrowser = "1.0.5" 19 | 20 | [dev-dependencies] 21 | ribir_dev_helper = {path = "../../dev-helper"} 22 | ribir_slim = { path = "../../themes/ribir_slim" } 23 | ribir_core = { path = "../../core", features = ["test-utils"]} 24 | 25 | [target.'cfg(target_arch = "wasm32")'.dependencies] 26 | console_error_panic_hook = "0.1.6" 27 | wasm-bindgen = "0.2.92" 28 | 29 | 30 | [features] 31 | wgpu = ["ribir/wgpu"] 32 | 33 | [lib] 34 | crate-type = ["cdylib", "rlib"] 35 | path = "src/lib.rs" 36 | -------------------------------------------------------------------------------- /macros/src/util.rs: -------------------------------------------------------------------------------- 1 | use syn::{Data, spanned::Spanned}; 2 | 3 | pub fn data_struct_unwrap<'a>( 4 | data: &'a mut syn::Data, derive_trait: &'static str, 5 | ) -> syn::Result<&'a mut syn::DataStruct> { 6 | match data { 7 | Data::Struct(stt) => Ok(stt), 8 | Data::Enum(e) => { 9 | let err_str = format!("`{derive_trait}` not support for Enum"); 10 | Err(syn::Error::new(e.enum_token.span(), err_str)) 11 | } 12 | Data::Union(u) => { 13 | let err_str = format!("`{derive_trait}` not support for Union"); 14 | Err(syn::Error::new(u.union_token.span(), err_str)) 15 | } 16 | } 17 | } 18 | 19 | pub fn doc_attr(field: &syn::Field) -> Option<&syn::Attribute> { 20 | field 21 | .attrs 22 | .iter() 23 | .find(|attr| matches!(&attr.meta, syn::Meta::NameValue(nv) if nv.path.is_ident("doc"))) 24 | } 25 | 26 | pub fn declare_init_method(member: &syn::Ident) -> syn::Ident { 27 | if member.to_string().starts_with("on_") { 28 | member.clone() 29 | } else { 30 | syn::Ident::new(&format!("with_{}", member), member.span()) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/messages/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors.workspace = true 3 | categories.workspace = true 4 | description.workspace = true 5 | documentation.workspace = true 6 | edition.workspace = true 7 | homepage.workspace = true 8 | keywords.workspace = true 9 | license.workspace = true 10 | name = "messages" 11 | publish = false 12 | version.workspace = true 13 | 14 | [dependencies] 15 | paste.workspace = true 16 | # we disable `default-features`, because we want more control over testing. 17 | ribir = {path = "../../ribir", features = ["material", "widgets"]} 18 | ribir_slim = { path = "../../themes/ribir_slim" } 19 | 20 | [target.'cfg(target_arch = "wasm32")'.dependencies] 21 | console_error_panic_hook = "0.1.6" 22 | wasm-bindgen = "0.2.92" 23 | 24 | [dev-dependencies] 25 | ribir_dev_helper = {path = "../../dev-helper"} 26 | ribir_core = { path = "../../core", features = ["test-utils"]} 27 | 28 | [features] 29 | wgpu = ["ribir/wgpu"] 30 | 31 | 32 | [lib] 33 | crate-type = ["cdylib", "rlib"] 34 | path = "src/lib.rs" 35 | 36 | [package.metadata.wasm-pack.profile.release] 37 | wasm-opt = false 38 | -------------------------------------------------------------------------------- /examples/counter/src/lib.rs: -------------------------------------------------------------------------------- 1 | use ribir::prelude::*; 2 | 3 | pub fn counter(cnt: &'static Stateful) -> Widget<'static> { 4 | button! { 5 | h_align: HAlign::Center, 6 | v_align: VAlign::Center, 7 | on_tap: move |_| *$write(cnt) += 1, 8 | @pipe!($read(cnt).to_string()) 9 | } 10 | .into_widget() 11 | } 12 | 13 | #[cfg_attr(target_arch = "wasm32", wasm_bindgen::prelude::wasm_bindgen)] 14 | pub fn run() { 15 | #[cfg(target_arch = "wasm32")] 16 | std::panic::set_hook(Box::new(console_error_panic_hook::hook)); 17 | 18 | App::run_with_data(|| Stateful::new(0), counter) 19 | .with_app_theme(material::purple::light) 20 | .with_size(Size::new(320., 240.)) 21 | .with_title("Counter"); 22 | } 23 | 24 | #[cfg(test)] 25 | mod tests { 26 | use ribir::{core::test_helper::*, material as ribir_material}; 27 | use ribir_dev_helper::*; 28 | 29 | use super::*; 30 | 31 | widget_image_tests!( 32 | counter, 33 | WidgetTester::new_with_data(Stateful::new(0), counter) 34 | .with_wnd_size(Size::new(320., 240.)) 35 | .with_comparison(0.0001) 36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /.github/workflows/dispatch-other-repo.yml: -------------------------------------------------------------------------------- 1 | name: "Call Website To Sync Docs" 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - master 8 | - release-* 9 | jobs: 10 | dispatch: 11 | # Prevent this job from running on forked repositories 12 | if: github.repository == 'RibirX/Ribir' 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/github-script@v7 16 | with: 17 | github-token: ${{ secrets.RIBIR_RELEASE }} 18 | script: |- 19 | await github.rest.actions.createWorkflowDispatch({ 20 | owner: 'RibirX', 21 | repo: 'ribir-website', 22 | workflow_id: 'sync-docs.yml', 23 | ref: 'main', 24 | inputs: { 25 | branch_ref: "${{ github.ref }}" 26 | }, 27 | }).catch(error => error).then(response => { 28 | core.debug(response); 29 | if (response.status !== 204) { 30 | core.setFailed(`create workflow_dispatch received status code ${response.status}`); 31 | } 32 | }); 33 | -------------------------------------------------------------------------------- /examples/todos/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors.workspace = true 3 | categories.workspace = true 4 | description.workspace = true 5 | documentation.workspace = true 6 | edition.workspace = true 7 | homepage.workspace = true 8 | keywords.workspace = true 9 | license.workspace = true 10 | name = "todos" 11 | publish = false 12 | version.workspace = true 13 | 14 | [dependencies] 15 | paste.workspace = true 16 | # we disable `default-features`, because we want more control over testing. 17 | ribir = {path = "../../ribir", features = ["material", "widgets"]} 18 | serde = {workspace = true, features = ["derive"]} 19 | serde_json = {workspace = true} 20 | 21 | [target.'cfg(target_arch = "wasm32")'.dependencies] 22 | console_error_panic_hook = "0.1.6" 23 | wasm-bindgen = "0.2.92" 24 | 25 | 26 | [dev-dependencies] 27 | ribir_slim = { path = "../../themes/ribir_slim" } 28 | ribir_dev_helper = {path = "../../dev-helper"} 29 | ribir_core = { path = "../../core", features = ["test-utils"]} 30 | 31 | [features] 32 | wgpu = ["ribir/wgpu"] 33 | 34 | 35 | [lib] 36 | crate-type = ["cdylib", "rlib"] 37 | path = "src/lib.rs" 38 | 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) RibirX 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /themes/material/src/classes.rs: -------------------------------------------------------------------------------- 1 | use ribir_core::prelude::Classes; 2 | 3 | mod avatar_cls; 4 | mod badge_cls; 5 | mod buttons_cls; 6 | mod checkbox_cls; 7 | mod disabled_cls; 8 | mod divider_cls; 9 | mod input_cls; 10 | mod list_cls; 11 | mod menu_cls; 12 | mod progress_cls; 13 | mod radio_cls; 14 | mod scrollbar_cls; 15 | mod slider_cls; 16 | mod switch_cls; 17 | mod tabs_cls; 18 | mod tooltips_cls; 19 | 20 | pub fn initd_classes() -> Classes { 21 | let mut classes = Classes::default(); 22 | 23 | buttons_cls::init(&mut classes); 24 | scrollbar_cls::init(&mut classes); 25 | radio_cls::init(&mut classes); 26 | progress_cls::init(&mut classes); 27 | checkbox_cls::init(&mut classes); 28 | tooltips_cls::init(&mut classes); 29 | slider_cls::init(&mut classes); 30 | input_cls::init(&mut classes); 31 | divider_cls::init(&mut classes); 32 | menu_cls::init(&mut classes); 33 | tabs_cls::init(&mut classes); 34 | disabled_cls::init(&mut classes); 35 | avatar_cls::init(&mut classes); 36 | list_cls::init(&mut classes); 37 | switch_cls::init(&mut classes); 38 | badge_cls::init(&mut classes); 39 | 40 | classes 41 | } 42 | -------------------------------------------------------------------------------- /examples/pomodoro/static/pin.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /themes/material/src/classes/tooltips_cls.rs: -------------------------------------------------------------------------------- 1 | use ribir_core::prelude::*; 2 | 3 | use crate::md; 4 | 5 | pub(super) fn init(classes: &mut Classes) { 6 | classes.insert(TOOLTIPS, |w| { 7 | fn_widget! { 8 | let mut w = FatObj::new(w); 9 | let mut w = @FatObj { 10 | background: Palette::of(BuildCtx::get()).inverse_surface(), 11 | margin: EdgeInsets::only_bottom(4.), 12 | radius: Radius::all(4.), 13 | @(w) { 14 | margin: EdgeInsets::new(4., 8., 4., 8.), 15 | foreground: Palette::of(BuildCtx::get()).inverse_on_surface(), 16 | v_align: VAlign::Center, 17 | h_align: HAlign::Center, 18 | } 19 | }; 20 | let animate = w.opacity() 21 | .transition(EasingTransition{ 22 | easing: md::easing::STANDARD_ACCELERATE, 23 | duration: md::easing::duration::SHORT2 24 | }.box_it()); 25 | @(w) { 26 | keep_alive: pipe!($read(animate).is_running() || *$read(w.opacity()) != 0.), 27 | on_disposed: move |_| { 28 | *$write(w.opacity()) = 0.; 29 | } 30 | } 31 | } 32 | .into_widget() 33 | }); 34 | } 35 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Purpose of this Pull Request 2 | 3 | *Please briefly describe what this Pull Request is aiming to achieve.* 4 | 5 | ## Checklist Before Merging 6 | 7 | Please ensure the following are completed before merging: 8 | - [ ] If this is linked to an issue, include the link in your description. 9 | - [ ] If you've made changes to the code or documentation, make sure these are updated in the `CHANGELOG.md` file. 10 | - [ ] If you've introduced any break changes, briefly describe them in the `Breaking` section of the `CHANGELOG.md` file. 11 | 12 | ## Additional Information 13 | 14 | **The bot will replace `#pr` in `CHANGELOG.md` with your pull request number. If your branch is out of sync, use `git pull --rebase` to update it.** 15 | 16 | If you're unsure about which branch to submit your Pull Request to, or when it will be released after being merged, please refer to our [Release Guide](https://github.com/RibirX/Ribir/blob/master/RELEASE.md). 17 | 18 | If you're working on a widget and need help writing test cases, we have some macros that can assist you. Please refer to the [Ribir Dev Helper](https://docs.rs/ribir_dev_helper) documentation. -------------------------------------------------------------------------------- /core/src/builtin_widgets/painting_style.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::*; 2 | 3 | /// A widget that sets the strategies for painting shapes and paths . It's can 4 | /// be inherited by its descendants. 5 | #[derive(Default)] 6 | pub struct PaintingStyleWidget { 7 | pub painting_style: PaintingStyle, 8 | } 9 | 10 | impl Declare for PaintingStyleWidget { 11 | type Builder = FatObj<()>; 12 | #[inline] 13 | fn declarer() -> Self::Builder { FatObj::new(()) } 14 | } 15 | 16 | impl<'c> ComposeChild<'c> for PaintingStyleWidget { 17 | type Child = Widget<'c>; 18 | 19 | fn compose_child(this: impl StateWriter, child: Self::Child) -> Widget<'c> { 20 | Providers::new([Self::into_provider(this)]).with_child(child) 21 | } 22 | } 23 | 24 | impl PaintingStyleWidget { 25 | pub fn into_provider(this: impl StateWriter) -> Provider { 26 | match this.try_into_value() { 27 | Ok(this) => Provider::new(this.painting_style), 28 | Err(this) => Provider::writer( 29 | this.part_writer(PartialId::any(), |w| PartMut::new(&mut w.painting_style)), 30 | Some(DirtyPhase::LayoutSubtree), 31 | ), 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /core/src/events/listener_impl_helper.rs: -------------------------------------------------------------------------------- 1 | #[macro_export] 2 | macro_rules! impl_common_event_deref { 3 | ($event_name:ident) => { 4 | impl std::ops::Deref for $event_name { 5 | type Target = CommonEvent; 6 | 7 | #[inline] 8 | fn deref(&self) -> &Self::Target { &self.common } 9 | } 10 | 11 | impl std::ops::DerefMut for $event_name { 12 | #[inline] 13 | fn deref_mut(&mut self) -> &mut Self::Target { &mut self.common } 14 | } 15 | 16 | impl std::borrow::Borrow for $event_name { 17 | #[inline] 18 | fn borrow(&self) -> &CommonEvent { &self.common } 19 | } 20 | 21 | impl std::borrow::BorrowMut for $event_name { 22 | #[inline] 23 | fn borrow_mut(&mut self) -> &mut CommonEvent { &mut self.common } 24 | } 25 | 26 | impl AsRef<$crate::prelude::ProviderCtx> for $event_name { 27 | fn as_ref(&self) -> &$crate::prelude::ProviderCtx { self.common.as_ref() } 28 | } 29 | 30 | impl AsMut<$crate::prelude::ProviderCtx> for $event_name { 31 | fn as_mut(&mut self) -> &mut $crate::prelude::ProviderCtx { self.common.as_mut() } 32 | } 33 | }; 34 | } 35 | -------------------------------------------------------------------------------- /gpu/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors.workspace = true 3 | categories.workspace = true 4 | description.workspace = true 5 | documentation.workspace = true 6 | edition.workspace = true 7 | homepage.workspace = true 8 | keywords.workspace = true 9 | license.workspace = true 10 | name = "ribir_gpu" 11 | readme.workspace = true 12 | repository = "https://github.com/RibirX/Ribir/gpu" 13 | version.workspace = true 14 | 15 | [dependencies] 16 | ahash.workspace = true 17 | tokio = {workspace = true, optional = true, features=["sync"]} 18 | guillotiere.workspace = true 19 | log.workspace = true 20 | rayon.workspace = true 21 | ribir_algo = {path = "../algo", version = "0.4.0-alpha.53" } 22 | ribir_geom = {path = "../geom", version = "0.4.0-alpha.53" } 23 | ribir_painter = {path = "../painter", features = ["tessellation"], version = "0.4.0-alpha.53" } 24 | slab = "0.4.9" 25 | wgpu = {workspace = true, optional = true} 26 | zerocopy = {workspace=true, features = ["derive"]} 27 | 28 | [dev-dependencies] 29 | paste.workspace = true 30 | ribir_dev_helper = {path = "../dev-helper"} 31 | futures.workspace = true 32 | 33 | [features] 34 | default = ["wgpu"] 35 | wgpu = ["dep:wgpu", "tokio"] 36 | -------------------------------------------------------------------------------- /themes/material/icons/settings_FILL0_wght400_GRAD0_opsz48.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /widgets/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors.workspace = true 3 | categories.workspace = true 4 | description.workspace = true 5 | documentation.workspace = true 6 | edition.workspace = true 7 | homepage.workspace = true 8 | keywords.workspace = true 9 | license.workspace = true 10 | name = "ribir_widgets" 11 | readme.workspace = true 12 | repository = "https://github.com/RibirX/Ribir/widgets" 13 | version.workspace = true 14 | 15 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 16 | 17 | [dependencies] 18 | lyon_algorithms.workspace = true 19 | lyon_path.workspace = true 20 | ribir_core = {path = "../core", version = "0.4.0-alpha.53" } 21 | ribir_geom = {path = "../geom", version = "0.4.0-alpha.53" } 22 | unicode-segmentation.workspace = true 23 | thiserror.workspace = true 24 | ahash.workspace = true 25 | 26 | [dev-dependencies] 27 | paste.workspace = true 28 | winit.workspace = true 29 | ribir_core = { path = "../core", features=["test-utils"] } 30 | ribir_dev_helper = {path = "../dev-helper"} 31 | ribir_material = {path = "../themes/material"} 32 | ribir_slim = {path = "../themes/ribir_slim"} 33 | ribir = { path = "../ribir", features = ["material"] } 34 | -------------------------------------------------------------------------------- /macros/src/fn_widget_macro.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::quote; 3 | 4 | use crate::{ 5 | error::result_to_token_stream, 6 | symbol_process::{DollarRefsCtx, symbol_to_macro}, 7 | watch_macro::BodyExpr, 8 | }; 9 | 10 | pub(crate) fn gen_code(input: TokenStream, ctx: Option<&mut DollarRefsCtx>) -> TokenStream { 11 | let res = symbol_to_macro(input).and_then(|input| { 12 | let body = syn::parse2::(input)?; 13 | let (stmts, refs) = if let Some(ctx) = ctx { 14 | ctx.new_dollar_scope(None); 15 | let stmts = body.fold(ctx).0; 16 | let refs = ctx.pop_dollar_scope(false); 17 | (stmts, refs) 18 | } else { 19 | let mut ctx = DollarRefsCtx::top_level(); 20 | let stmts = body.fold(&mut ctx).0; 21 | let refs = ctx.pop_dollar_scope(false); 22 | 23 | (stmts, refs) 24 | }; 25 | if !refs.is_empty() { 26 | Ok(quote! {{ 27 | #refs 28 | let f = move || { #(#stmts)* }; 29 | FnWidget::new(f) 30 | }}) 31 | } else { 32 | Ok(quote! {{ 33 | let f = move || { #(#stmts)* }; 34 | FnWidget::new(f) 35 | }}) 36 | } 37 | }); 38 | 39 | result_to_token_stream(res) 40 | } 41 | -------------------------------------------------------------------------------- /core/src/builtin_widgets/container.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::*; 2 | 3 | /// A simple container widget with a fixed size for its child. 4 | /// 5 | /// # Example 6 | /// 7 | /// Place text inside a 100x100 container. 8 | /// 9 | /// ```rust 10 | /// use ribir::prelude::*; 11 | /// 12 | /// container! { 13 | /// size: Size::new(100., 100.), 14 | /// background: Color::BLUE, 15 | /// @Text { text: "Hello" } 16 | /// }; 17 | /// ``` 18 | #[derive(Declare, SingleChild)] 19 | pub struct Container { 20 | pub size: Size, 21 | } 22 | 23 | impl Render for Container { 24 | fn perform_layout(&self, clamp: BoxClamp, ctx: &mut LayoutCtx) -> Size { 25 | let size = clamp.clamp(self.size); 26 | ctx.perform_single_child_layout(BoxClamp::max_size(size)); 27 | size 28 | } 29 | 30 | #[inline] 31 | fn size_affected_by_child(&self) -> bool { false } 32 | } 33 | 34 | #[cfg(test)] 35 | mod tests { 36 | use ribir_dev_helper::*; 37 | 38 | use super::*; 39 | use crate::test_helper::*; 40 | 41 | const SIZE: Size = Size::new(100., 100.); 42 | 43 | widget_layout_test!( 44 | smoke, 45 | WidgetTester::new(fn_widget! { @Container { size: SIZE }}), 46 | LayoutCase::default().with_size(SIZE) 47 | ); 48 | } 49 | -------------------------------------------------------------------------------- /themes/material/src/classes/badge_cls.rs: -------------------------------------------------------------------------------- 1 | use ribir_core::prelude::*; 2 | use ribir_widgets::prelude::{BADGE_LARGE, BADGE_SMALL, BadgeColor}; 3 | 4 | fn get_badge_color() -> VariantMap Color + Clone> { 5 | Variant::new_or_else(BuildCtx::get(), || BadgeColor(Palette::of(BuildCtx::get()).error())) 6 | .map(|c| c.0) 7 | } 8 | 9 | pub(super) fn init(classes: &mut Classes) { 10 | classes.insert( 11 | BADGE_SMALL, 12 | style_class! { 13 | clamp: BoxClamp::fixed_size(Size::new(6., 6.)), 14 | radius: Radius::all(3.), 15 | padding: EdgeInsets::all(0.), 16 | background: get_badge_color(), 17 | }, 18 | ); 19 | 20 | classes.insert(BADGE_LARGE, move |w| { 21 | fn_widget! { 22 | let color = get_badge_color(); 23 | @FatObj { 24 | clamp: BoxClamp::min_width(16.).with_min_height(16.), 25 | radius: Radius::all(8.), 26 | padding: EdgeInsets::horizontal(4.), 27 | background: color.clone(), 28 | foreground: color.on_this_color(BuildCtx::get()), 29 | text_style: TypographyTheme::of(BuildCtx::get()).label_small.text.clone(), 30 | @ { w } 31 | } 32 | } 33 | .into_widget() 34 | }); 35 | } 36 | -------------------------------------------------------------------------------- /cli/src/program_check.rs: -------------------------------------------------------------------------------- 1 | /// ** This implementation is base on [https://github.com/gfx-rs/wgpu/blob/trunk/xtask/src/util.rs]!** 2 | use std::{io, process::Command}; 3 | 4 | pub(crate) struct Program { 5 | pub binary_name: &'static str, 6 | pub crate_name: &'static str, 7 | } 8 | 9 | pub(crate) fn check_all_programs(programs: &[Program]) -> anyhow::Result<()> { 10 | let mut failed = Vec::new(); 11 | for Program { binary_name, crate_name } in programs { 12 | let mut cmd = Command::new(binary_name); 13 | cmd.arg("--help"); 14 | let output = cmd.output(); 15 | match output { 16 | Ok(_output) => { 17 | println!("Checking for {binary_name} in PATH: ✅"); 18 | } 19 | Err(e) if matches!(e.kind(), io::ErrorKind::NotFound) => { 20 | eprintln!("Checking for {binary_name} in PATH: ❌"); 21 | failed.push(*crate_name); 22 | } 23 | Err(e) => { 24 | eprintln!("Checking for {binary_name} in PATH: ❌"); 25 | panic!("Unknown IO error: {:?}", e); 26 | } 27 | } 28 | } 29 | 30 | if !failed.is_empty() { 31 | eprintln!("Please install them with: cargo install {}", failed.join(" ")); 32 | anyhow::bail!("Missing programs in PATH"); 33 | } 34 | 35 | Ok(()) 36 | } 37 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | name: Bug report 4 | about: Create a report to help us improve Ribir 5 | title: '' 6 | labels: bug 7 | assignees: '' 8 | 9 | --- 10 | 11 | **Problem** 12 | 13 | 14 | **Steps To Reproduce** 15 | Steps to reproduce the behavior: 16 | 1. Go to '...' 17 | 2. Click on '....' 18 | 3. Scroll down to '....' 19 | 4. See error 20 | 21 | Or paste reproducible error codes: 22 | 23 | **Expected behavior** 24 | A clear and concise description of what you expected to happen. 25 | 26 | **Screenshots** 27 | If applicable, add screenshots to help explain your problem. 28 | 29 | **Environment:** 30 | - Ribir version: [e.g. `master`] 31 | - Rust version: [e.g. 1.43.0, `nightly`] 32 | - OS, if relevant: [e.g. MacOS] 33 | - Browser and version, if relevant: [e.g. Chrome v83] 34 | 35 | **Questionnaire** 36 | 37 | 38 | - [ ] I'm interested in fixing this myself but don't know where to start 39 | - [ ] I would like to fix and I have a solution 40 | - [ ] I don't have time to fix this right now, but maybe later -------------------------------------------------------------------------------- /tests/path_child_test.rs: -------------------------------------------------------------------------------- 1 | use ribir::{core::test_helper::*, prelude::*}; 2 | use ribir_dev_helper::*; 3 | enum AB { 4 | A, 5 | B, 6 | } 7 | 8 | const SIZE_ONE: Size = Size::new(1., 1.); 9 | impl Compose for AB { 10 | fn compose(this: impl StateWriter) -> Widget<'static> { 11 | fn_widget! { 12 | @SizedBox { 13 | size: match *$read(this) { 14 | AB::A => ZERO_SIZE, 15 | AB::B => SIZE_ONE 16 | } 17 | } 18 | } 19 | .into_widget() 20 | } 21 | } 22 | 23 | impl AB { 24 | fn a() -> Self { AB::A } 25 | 26 | fn b() -> Self { AB::B } 27 | } 28 | 29 | #[test] 30 | fn path_widget() { 31 | let _ = fn_widget! { AB::A }; 32 | let _ = fn_widget! { AB::B }; 33 | let _ = fn_widget! { AB::a() }; 34 | let _ = fn_widget! { AB::b() }; 35 | } 36 | 37 | struct TupleBox(Size); 38 | impl Compose for TupleBox { 39 | fn compose(this: impl StateWriter) -> Widget<'static> { 40 | fn_widget! { 41 | @SizedBox { 42 | size: pipe!($read(this).0), 43 | } 44 | } 45 | .into_widget() 46 | } 47 | } 48 | 49 | widget_layout_test!( 50 | tuple_widget, 51 | WidgetTester::new(fn_widget! { 52 | TupleBox(Size::new(1., 1.)) 53 | }), 54 | LayoutCase::default().with_size(Size::new(1., 1.)) 55 | ); 56 | -------------------------------------------------------------------------------- /core/src/builtin_widgets/clip.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::*; 2 | 3 | /// A widget that clips its child using a specified path. 4 | /// 5 | /// # Example 6 | /// 7 | /// Clip a container by a rounded rectangle path. 8 | /// 9 | /// ```rust 10 | /// use ribir::prelude::*; 11 | /// 12 | /// fn_widget! { 13 | /// @Clip { 14 | /// clip_path: Path::rect_round(&Rect::new(Point::zero(), Size::new(50., 50.)), &Radius::all(25.)), 15 | /// @Container { 16 | /// size: Size::new(100., 100.), 17 | /// background: Color::RED 18 | /// } 19 | /// } 20 | /// }; 21 | /// ``` 22 | #[derive(SingleChild, Declare)] 23 | pub struct Clip { 24 | pub clip_path: Path, 25 | } 26 | 27 | impl Render for Clip { 28 | fn size_affected_by_child(&self) -> bool { false } 29 | 30 | fn perform_layout(&self, clamp: BoxClamp, ctx: &mut LayoutCtx) -> Size { 31 | ctx.assert_perform_single_child_layout(clamp); 32 | self 33 | .clip_path 34 | .bounds(None) 35 | .max() 36 | .to_tuple() 37 | .into() 38 | } 39 | 40 | fn paint(&self, ctx: &mut PaintingCtx) { ctx.painter().clip(self.clip_path.clone().into()); } 41 | 42 | fn visual_box(&self, ctx: &mut VisualCtx) -> Option { 43 | let clip_rect = self.clip_path.bounds(None); 44 | ctx.clip(clip_rect); 45 | Some(clip_rect) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /examples/counter/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors.workspace = true 3 | categories.workspace = true 4 | description.workspace = true 5 | documentation.workspace = true 6 | edition.workspace = true 7 | homepage.workspace = true 8 | keywords.workspace = true 9 | license.workspace = true 10 | name = "counter" 11 | publish = false 12 | version.workspace = true 13 | 14 | [dependencies] 15 | paste.workspace = true 16 | # we disable `default-features`, because we want more control over testing. 17 | ribir = {path = "../../ribir", features = ["material", "widgets"]} 18 | 19 | [target.'cfg(target_arch = "wasm32")'.dependencies] 20 | console_error_panic_hook = "0.1.6" 21 | wasm-bindgen = "0.2.92" 22 | 23 | [dev-dependencies] 24 | ribir_slim = { path = "../../themes/ribir_slim" } 25 | ribir_dev_helper = {path = "../../dev-helper"} 26 | ribir_core = { path = "../../core", features = ["test-utils"]} 27 | 28 | [features] 29 | wgpu = ["ribir/wgpu"] 30 | 31 | 32 | [lib] 33 | crate-type = ["cdylib", "rlib"] 34 | path = "src/lib.rs" 35 | 36 | [package.metadata.bundle] 37 | "productName" = "Counter" 38 | "version" = "1.0.0" 39 | "identifier" = "com.ribir.counter" 40 | "shortDescription" = "" 41 | "longDescription" = "" 42 | "copyright" = "Copyright (c) You 2021. All rights reserved." 43 | "icon" = ["../Logo.ico"] 44 | "resources" = [] 45 | "externalBin" = [] 46 | -------------------------------------------------------------------------------- /.github/workflows/alpha-version.yml: -------------------------------------------------------------------------------- 1 | on: 2 | workflow_dispatch: # allows manual triggering 3 | schedule: 4 | - cron: "0 0 * * 3" # runs every wednesday at 00:00 UTC 5 | permissions: 6 | contents: write 7 | name: "Alpha Version" 8 | jobs: 9 | master_check: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Master check 13 | if: github.ref != 'refs/heads/master' 14 | run: | 15 | echo "Alpha version can be released only from master branch" 16 | exit 1 17 | commit_check: 18 | needs: master_check 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: actions/checkout@v3 22 | with: 23 | fetch-depth: 0 24 | fetch-tags: true 25 | - run: | 26 | new_commit=$(git log $(git describe --tags --abbrev=0)..HEAD --oneline | wc -l) 27 | if [ $new_commit -eq 0 ]; then 28 | echo "No new commits since last version" 29 | exit 1 30 | fi 31 | call-workflow-passing-data: 32 | needs: commit_check 33 | uses: RibirX/rclog/.github/workflows/release-version.yml@main 34 | with: 35 | level: 'alpha' 36 | ref: ${{ github.ref }} 37 | merge_changelog: false 38 | toolchain: stable 39 | secrets: 40 | CRATE_RELEASE_TOKEN: ${{ secrets.CRATE_RELEASE_TOKEN }} 41 | GITHUB_RELEASE_TOKEN: ${{ secrets.RIBIR_RELEASE }} -------------------------------------------------------------------------------- /core/src/events/pointers/from_mouse.rs: -------------------------------------------------------------------------------- 1 | use winit::event::MouseButton; 2 | 3 | use super::PointerId; 4 | use crate::prelude::*; 5 | 6 | impl PointerEvent { 7 | pub(crate) fn from_mouse(target: WidgetId, wnd: &Window) -> Self { 8 | let no_button = wnd 9 | .dispatcher 10 | .borrow() 11 | .info 12 | .mouse_buttons() 13 | .is_empty(); 14 | PointerEvent { 15 | // todo: we need to trace the pressed pointer, how to generate pointer id, by device + button? 16 | id: PointerId(0), 17 | width: 1.0, 18 | height: 1.0, 19 | pressure: if no_button { 0. } else { 0.5 }, 20 | tilt_x: 90., 21 | tilt_y: 90., 22 | twist: 0., 23 | point_type: PointerType::Mouse, 24 | is_primary: true, 25 | common: CommonEvent::new(target, wnd.tree), 26 | } 27 | } 28 | } 29 | 30 | impl From for MouseButtons { 31 | fn from(btns: MouseButton) -> Self { 32 | match btns { 33 | MouseButton::Left => MouseButtons::PRIMARY, 34 | MouseButton::Right => MouseButtons::SECONDARY, 35 | MouseButton::Middle => MouseButtons::AUXILIARY, 36 | MouseButton::Back => MouseButtons::FOURTH, 37 | MouseButton::Forward => MouseButtons::FIFTH, 38 | MouseButton::Other(v) => { 39 | log::warn!("Not support the mouse button {} now", v); 40 | MouseButtons::default() 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /examples/animation_demo.rs: -------------------------------------------------------------------------------- 1 | /* 2 | use ribir::prelude::*; 3 | use std::time::Duration; 4 | 5 | fn main() { 6 | let style = PathPaintStyle::Stroke(StrokeOptions::default()); 7 | let lyon_path = include_svg!("./Logo.svg"); 8 | let mut paths = vec![]; 9 | lyon_path.paths.into_iter().for_each(|render_path| { 10 | paths.push(PathPaintKit { 11 | path: render_path.path, 12 | brush: render_path.brush.map_or(Brush::Color(Color::BLACK), |b| b), 13 | style, 14 | }); 15 | }); 16 | let w = widget! { 17 | PathsPaintKit { 18 | anchor: Anchor::left_top(100., 100.), 19 | transform: Transform::scale(0.5, 0.5), 20 | id: path_widget, 21 | paths, 22 | on_mounted: move |_| circle_animate.run(), 23 | } 24 | Animate { 25 | id: circle_animate, 26 | transition: Transition { 27 | delay: None, 28 | duration: Duration::from_millis(5000), 29 | easing: easing::LINEAR, 30 | repeat: Some(f32::MAX), 31 | }, 32 | prop: prop!(path_widget.paths, PathPaintKit::paths_lerp_fn(prop!(path_widget.paths))), 33 | from: vec![ 34 | PathPaintKit { 35 | path: Path::rect(&Rect::zero()), 36 | brush: Brush::Color(Color::WHITE), 37 | style 38 | } 39 | ] 40 | } 41 | }; 42 | App::run(w); 43 | } 44 | */ 45 | 46 | fn main() { panic!("Blocked by path sampler finished.") } 47 | -------------------------------------------------------------------------------- /core/src/events/ime_pre_edit.rs: -------------------------------------------------------------------------------- 1 | use crate::{impl_common_event_deref, prelude::*}; 2 | 3 | #[derive(Debug, Clone)] 4 | pub enum ImePreEdit { 5 | /// Notifies when the IME PreEdit begin a new round. 6 | /// 7 | /// After getting this event you could receive [`PreEdit`](Self::PreEdit). 8 | Begin, 9 | 10 | /// Notifies when a new composing text should be set at the cursor position. 11 | /// 12 | /// The value represents a pair of the preedit string and the cursor begin 13 | /// position and end position. When it's `None`, the cursor should be 14 | /// hidden. When `String` is an empty string this indicates that preedit was 15 | /// cleared. 16 | /// 17 | /// The cursor position is byte-wise indexed. 18 | PreEdit { value: String, cursor: Option<(usize, usize)> }, 19 | 20 | /// Notifies when the IME PreEdit was finished this round. 21 | /// 22 | /// After receiving this event you won't get any more PreEdit event in this 23 | /// round.You should clear pending pre_edit text. 24 | End, 25 | } 26 | 27 | #[derive(Debug)] 28 | pub struct ImePreEditEvent { 29 | pub pre_edit: ImePreEdit, 30 | pub common: CommonEvent, 31 | } 32 | 33 | impl ImePreEditEvent { 34 | pub(crate) fn new(pre_edit: ImePreEdit, target: WidgetId, wnd: &Window) -> Self { 35 | ImePreEditEvent { pre_edit, common: CommonEvent::new(target, wnd.tree) } 36 | } 37 | } 38 | 39 | impl_common_event_deref!(ImePreEditEvent); 40 | -------------------------------------------------------------------------------- /examples/pomodoro/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors.workspace = true 3 | categories.workspace = true 4 | description.workspace = true 5 | documentation.workspace = true 6 | edition.workspace = true 7 | homepage.workspace = true 8 | keywords.workspace = true 9 | license.workspace = true 10 | name = "pomodoro" 11 | publish = false 12 | version.workspace = true 13 | 14 | [dependencies] 15 | paste.workspace = true 16 | # we disable `default-features`, because we want more control over testing. 17 | ribir = {path = "../../ribir", features = ["material", "widgets"]} 18 | tokio.workspace = true 19 | serde = { version = "1.0", features = ["derive"] } 20 | serde_json = "1.0" 21 | rodio = "0.21" 22 | dirs = "5.0" 23 | 24 | [dev-dependencies] 25 | ribir_dev_helper = {path = "../../dev-helper"} 26 | ribir_slim = { path = "../../themes/ribir_slim" } 27 | ribir_core = { path = "../../core", features = ["test-utils"]} 28 | 29 | [features] 30 | wgpu = ["ribir/wgpu"] 31 | 32 | [package.metadata.release] 33 | opt-level = "z" 34 | lto = true 35 | codegen-units = 1 36 | strip = true 37 | panic = "abort" 38 | 39 | [package.metadata.bundle] 40 | "productName" = "Pomodoro" 41 | "version" = "1.0.0" 42 | "identifier" = "com.ribir.pomodoro" 43 | "shortDescription" = "" 44 | "longDescription" = "" 45 | "copyright" = "Copyright (c) You 2021. All rights reserved." 46 | "icon" = ["./icon.ico"] 47 | "resources" = ["./static"] 48 | "externalBin" = [] 49 | -------------------------------------------------------------------------------- /gpu/src/wgpu_impl/shaders/alpha_triangles.wgsl: -------------------------------------------------------------------------------- 1 | @group(0) @binding(0) 2 | var view_size: vec4; 3 | 4 | // An 8x sample provides better quality than a 4x sample in text rendering. 5 | // https://learn.microsoft.com/en-us/windows/win32/api/d3d11/ne-d3d11-d3d11_standard_multisample_quality_levels 6 | const sample_pattern: array = array( 7 | // 4x sample pattern 8 | // vec2(-6.0, 2.0) / 16.0, 9 | // vec2(-2.0, -6.0) / 16.0, 10 | // vec2(2.0, 6.0) / 16.0 11 | // vec2(6.0, -2.0) / 16.0, 12 | 13 | // 8x sample pattern 14 | vec2( -7. / 16., -1. / 16.), 15 | vec2( -5. / 16., 5. / 16.), 16 | vec2( -3. / 16., -5. / 16.), 17 | vec2( -1. / 16., 3. / 16.), 18 | vec2(1. / 16., -3. / 16.), 19 | vec2( 3. / 16., 7. / 16.), 20 | vec2( 5. / 16., 1. / 16.), 21 | vec2( 7. / 16., -7. / 16.) 22 | ); 23 | const sample_size: u32 = 8; 24 | 25 | 26 | @vertex 27 | fn vs_main(@location(0) pos: vec2, @builtin(instance_index) instance: u32) -> @builtin(position) vec4 { 28 | 29 | let size = vec2(f32(view_size.x), f32(view_size.y)); 30 | var sample_pos = pos + sample_pattern[instance % sample_size]; 31 | sample_pos = sample_pos * vec2(2., -2.) / size + vec2(-1., 1.); 32 | return vec4(sample_pos, 0.0, 1.0); 33 | } 34 | 35 | @fragment 36 | fn fs_main() -> @location(0) vec4 { 37 | let value: f32 = 1.0 / f32(sample_size); 38 | return vec4(value, value, value, value); 39 | } 40 | -------------------------------------------------------------------------------- /docs/zh/get_started/try_it.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | # 体验 Ribir 6 | 7 | 这篇文档将向你介绍如何配置和创建一个 Ribir 应用。 8 | 9 | > 你将了解 10 | > 11 | > - 如何编写和启动一个简单的 `Hello world!` 应用 12 | 13 | 14 | ## 安装 Rust 15 | 16 | 首先, 你需要安装 Rust,你可以参考 [Rust 官方文档](https://www.rust-lang.org/tools/install). 17 | 18 | ## 新建 Ribir 项目 19 | 20 | 然后,打开你的终端,创建一个新的 Rust 项目: 21 | 22 | ```sh 23 | cargo new ribir-hello-world 24 | cd ribir-hello-world 25 | ``` 26 | 27 | 接下来, 编辑 `Cargo.toml` 文件, 添加 Ribir 作为依赖: 28 | 29 | ```toml 30 | [dependencies] 31 | ribir = "@RIBIR_VERSION" 32 | ``` 33 | 34 | 或者直接运行 `cargo add --git "https://github.com/RibirX/Ribir" ribir` 让 Cargo 为你添加正在开发中的最新 Ribir 版本. 35 | 36 | ## 编写 `main.rs` 37 | 38 | 打开编辑器, 将 `src/main.rs` 文件修改为: 39 | 40 | ```rust no_run 41 | // main.rs 42 | use ribir::prelude::*; 43 | 44 | fn main() { 45 | App::run(text! { text: "Hello World!" }); 46 | } 47 | ``` 48 | 49 | ## 运行应用 50 | 51 | ```sh 52 | cargo run 53 | ``` 54 | 55 | 恭喜! 你完成了第一个 Ribir 项目。 56 | 57 | ## 运行 Ribir 自带示例 58 | 59 | 最后,Ribir 仓库中还有一些其他示例,你可以克隆 Git 仓库: 60 | 61 | ```sh 62 | git clone git@github.com:RibirX/Ribir.git 63 | cd Ribir/Ribir 64 | ``` 65 | 66 | 并使用以下命令之一运行示例: 67 | 68 | ```sh 69 | cargo run -p counter 70 | cargo run -p storybook 71 | cargo run -p messages 72 | cargo run -p todos 73 | ``` 74 | 75 | 76 | ## 下一步 77 | 78 | 如果你更喜欢直接使用函数调用来构建 UI,而不是通过 "DSL",在进入下一步之前,你可能会想先看看 [如何在不使用 "DSL" 的情况下使用 Ribir](../understanding_ribir/without_dsl.md)。 79 | -------------------------------------------------------------------------------- /tests/benches/example_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{Bencher, Criterion, criterion_group, criterion_main}; 2 | use ribir::{ 3 | core::{reset_test_env, test_helper::*}, 4 | prelude::*, 5 | }; 6 | 7 | fn bench_example(b: &mut Bencher, f: impl RInto) { 8 | let _ = AppCtx::shared(); 9 | let f: GenWidget = f.r_into(); 10 | b.iter(|| { 11 | let wnd = TestWindow::from_widget(f.clone()); 12 | wnd.draw_frame(); 13 | AppCtx::remove_wnd(wnd.id()) 14 | }) 15 | } 16 | 17 | fn bench_example_with_data>( 18 | b: &mut Bencher, data: D, f: impl Fn(&'static D) -> W + 'static, 19 | ) { 20 | let _ = AppCtx::shared(); 21 | let widget_builder = move || f(unsafe { &*(&data as *const _) }).into_widget(); 22 | bench_example(b, widget_builder); 23 | } 24 | 25 | fn examples(c: &mut Criterion) { 26 | reset_test_env!(); 27 | 28 | let mut g = c.benchmark_group("Examples"); 29 | 30 | g.bench_function("todos", |b| bench_example(b, todos::todos)); 31 | g.bench_function("counter", |b| bench_example_with_data(b, Stateful::new(0), counter::counter)); 32 | 33 | g.bench_function("messages", |b| bench_example(b, messages::messages)); 34 | g.bench_function("storybook", |b| bench_example(b, storybook::storybook)); 35 | g.bench_function("wordle_game", |b| bench_example(b, wordle_game::wordle_game)); 36 | } 37 | 38 | criterion_group!(example_benches, examples); 39 | criterion_main!(example_benches); 40 | -------------------------------------------------------------------------------- /core/src/builtin_widgets/foreground.rs: -------------------------------------------------------------------------------- 1 | use crate::{prelude::*, wrap_render::*}; 2 | 3 | /// A widget that provides a foreground brush for painting elements in its 4 | /// subtree. The foreground brush is inherited by descendant widgets; children 5 | /// can access it via the `Provider`. The built-in `Text` widget uses this brush 6 | /// when painting text. 7 | /// 8 | /// # Example 9 | /// Apply a foreground brush to render text in red. 10 | /// 11 | /// ```rust 12 | /// use ribir::prelude::*; 13 | /// 14 | /// fn_widget! { 15 | /// @Text { 16 | /// text: "I am red!", 17 | /// foreground: Color::RED, 18 | /// } 19 | /// }; 20 | /// ``` 21 | #[derive(Default)] 22 | pub struct Foreground { 23 | pub foreground: Brush, 24 | } 25 | 26 | impl Declare for Foreground { 27 | type Builder = FatObj<()>; 28 | #[inline] 29 | fn declarer() -> Self::Builder { FatObj::new(()) } 30 | } 31 | 32 | impl_compose_child_for_wrap_render!(Foreground); 33 | 34 | impl WrapRender for Foreground { 35 | fn perform_layout(&self, clamp: BoxClamp, host: &dyn Render, ctx: &mut LayoutCtx) -> Size { 36 | host.perform_layout(clamp, ctx) 37 | } 38 | 39 | fn paint(&self, host: &dyn Render, ctx: &mut PaintingCtx) { 40 | ctx 41 | .painter() 42 | .set_fill_brush(self.foreground.clone()) 43 | .set_stroke_brush(self.foreground.clone()); 44 | host.paint(ctx) 45 | } 46 | 47 | #[inline] 48 | fn wrapper_dirty_phase(&self) -> DirtyPhase { DirtyPhase::Paint } 49 | } 50 | -------------------------------------------------------------------------------- /core/src/builtin_widgets/image_widget.rs: -------------------------------------------------------------------------------- 1 | //! Implement `Render` for `Resource` so images can be used as 2 | //! widgets directly. 3 | //! 4 | //! # Example 5 | //! 6 | //! Display an image loaded from bytes. 7 | //! 8 | //! ```rust,no_run 9 | //! use ribir::prelude::*; 10 | //! 11 | //! fn_widget! { 12 | //! // Load an image from bytes (e.g., included from a file) 13 | //! let img = Resource::new( 14 | //! PixelImage::from_png(include_bytes!("../../../static/hero-banner.png")) 15 | //! ); 16 | //! @ { img } 17 | //! }; 18 | //! ``` 19 | use crate::prelude::*; 20 | 21 | impl Render for Resource { 22 | fn perform_layout(&self, clamp: BoxClamp, _: &mut LayoutCtx) -> Size { 23 | let size = Size::new(self.width() as f32, self.height() as f32); 24 | clamp.clamp(size) 25 | } 26 | 27 | fn paint(&self, ctx: &mut PaintingCtx) { 28 | let size = ctx.box_size().unwrap(); 29 | let box_rect = Rect::from_size(size); 30 | let img_rect = Rect::from_size(Size::new(self.width() as f32, self.height() as f32)); 31 | let painter = ctx.painter(); 32 | if let Some(rc) = img_rect.intersection(&box_rect) { 33 | painter.draw_img(self.clone(), &rc, &Some(rc)); 34 | } 35 | } 36 | 37 | fn visual_box(&self, ctx: &mut VisualCtx) -> Option { 38 | let box_rect = Rect::from_size(ctx.box_size()?); 39 | let img_rect = Rect::from_size(Size::new(self.width() as f32, self.height() as f32)); 40 | img_rect.intersection(&box_rect) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /examples/pomodoro/static/pin_off.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /ribir/src/backends/wgpu_backend.rs: -------------------------------------------------------------------------------- 1 | use ribir_core::prelude::{Color, DeviceRect, DeviceSize, PaintCommand, PainterBackend, Transform}; 2 | use ribir_gpu::Surface; 3 | 4 | use crate::winit_shell_wnd::WinitBackend; 5 | 6 | pub struct WgpuBackend<'a> { 7 | surface: Surface<'a>, 8 | backend: ribir_gpu::GPUBackend, 9 | } 10 | 11 | impl<'a> WinitBackend<'a> for WgpuBackend<'a> { 12 | async fn new(window: &'a winit::window::Window) -> WgpuBackend<'a> { 13 | let (wgpu, surface) = ribir_gpu::WgpuImpl::new(window).await; 14 | let size = window.inner_size(); 15 | let size = DeviceSize::new(size.width as i32, size.height as i32); 16 | 17 | let mut wgpu = WgpuBackend { surface, backend: ribir_gpu::GPUBackend::new(wgpu) }; 18 | wgpu.on_resize(size); 19 | 20 | wgpu 21 | } 22 | 23 | fn on_resize(&mut self, size: DeviceSize) { 24 | if size != self.surface.size() { 25 | self.surface.resize(size, self.backend.get_impl()); 26 | } 27 | } 28 | 29 | fn begin_frame(&mut self, surface_color: Color) { self.backend.begin_frame(surface_color); } 30 | 31 | fn draw_commands( 32 | &mut self, viewport: DeviceRect, global_matrix: &Transform, commands: &[PaintCommand], 33 | ) { 34 | self.backend.draw_commands( 35 | viewport, 36 | commands, 37 | global_matrix, 38 | self.surface.get_current_texture(), 39 | ); 40 | } 41 | 42 | fn end_frame(&mut self) { 43 | self.backend.end_frame(); 44 | self.surface.present(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /core/src/ticker.rs: -------------------------------------------------------------------------------- 1 | use std::convert::Infallible; 2 | 3 | use rxrust::subject::LocalSubject; 4 | pub use rxrust::{Duration, Instant}; 5 | 6 | /// Frame ticker emit message when new frame need to draw. 7 | pub type FrameTicker = LocalSubject<'static, FrameMsg, Infallible>; 8 | 9 | /// Message emitted at different status of a frame. 10 | 11 | #[derive(Clone, Debug)] 12 | pub enum FrameMsg { 13 | /// This message is emitted when all events have been processed and the 14 | /// framework begins the layout and painting of the frame. 15 | /// 16 | /// Only the first frame of continuous frames that do not require drawing will 17 | /// receive this message. 18 | NewFrame(Instant), 19 | /// This message is emitted before the framework starts the layout of the 20 | /// frame. 21 | BeforeLayout(Instant), 22 | /// This message is emitted when the layout process is completed, and the 23 | /// widget tree is ready to be rendered. # Notice 24 | /// - This message may be emitted more than once if there are listeners 25 | /// performing actions that trigger a widget relayout. Exercise caution when 26 | /// modifying widgets in the listener of this message. 27 | LayoutReady(Instant), 28 | /// This message is emitted after the render data has been submitted, 29 | /// indicating that all tasks for the current frame have been completed by the 30 | /// framework. 31 | /// 32 | /// Only the first frame of continuous frames that do not require drawing will 33 | /// receive this message. 34 | Finish(Instant), 35 | } 36 | -------------------------------------------------------------------------------- /macros/src/asset/basic.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::quote; 3 | 4 | use super::{Asset, AssetContext}; 5 | 6 | pub(crate) struct BinaryAsset; 7 | 8 | impl Asset for BinaryAsset { 9 | fn generate(&self, ctx: &AssetContext) -> syn::Result { 10 | if ctx.embed { 11 | let path = &ctx.input_path; 12 | Ok(quote! { include_bytes!(#path).to_vec() }) 13 | } else { 14 | ctx.copy_input_to_output()?; 15 | let path_tokens = ctx.runtime_path_tokens(); 16 | let panic_msg = ctx.panic_msg("read binary"); 17 | Ok(quote! { 18 | { 19 | let asset_path = #path_tokens; 20 | std::fs::read(&asset_path) 21 | .unwrap_or_else(|e| panic!("{}: {}. Asset path: {:?}", #panic_msg, e, asset_path)) 22 | } 23 | }) 24 | } 25 | } 26 | } 27 | 28 | pub(crate) struct TextAsset; 29 | 30 | impl Asset for TextAsset { 31 | fn generate(&self, ctx: &AssetContext) -> syn::Result { 32 | if ctx.embed { 33 | let path = &ctx.input_path; 34 | Ok(quote! { include_str!(#path).to_string() }) 35 | } else { 36 | ctx.copy_input_to_output()?; 37 | let path_tokens = ctx.runtime_path_tokens(); 38 | let panic_msg = ctx.panic_msg("read text"); 39 | Ok(quote! { 40 | { 41 | let asset_path = #path_tokens; 42 | std::fs::read_to_string(&asset_path) 43 | .unwrap_or_else(|e| panic!("{}: {}. Asset path: {:?}", #panic_msg, e, asset_path)) 44 | } 45 | }) 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /cli/README.md: -------------------------------------------------------------------------------- 1 | ## Cli for ribir 2 | ### SubCommand 3 | #### run-wasm 4 | build the example to wasm 5 | 1. Compile to target wasm32-unknown-unknown 6 | 2. Use wasm-bindgen to export relative function to js 7 | 3. Serve the wasm in 127.0.0.1:8000 by simpl-http-server 8 | 9 | #### bundle 10 | Bundle the native app 11 | 1. Change the directory to the package of the app 12 | 2. Add bundle config to Cargo.toml or Create a new bundle.toml. 13 | 3. Build the app by cargo. 14 | 4. Run the bundle command in the directory of the app. 15 | For example(used the config in the Cargo.toml): 16 | ``` bash 17 | cli bundle --verbose 18 | ``` 19 | By default, this will bundle the release binary. To bundle the debug binary instead, use the --debug flag. 20 | You can also specify the path to the bundle config file by --config follow by the file path. If no path is specified, it will read config from the current package Cargo.toml file. 21 | 22 | ##### Bundle Config File Example 23 | The bundle config file is in toml format. Here is an simple example: 24 | ``` toml 25 | [bundle] 26 | "productName" = "Counter" 27 | "version" = "1.0.0" 28 | "identifier" = "com.ribir.counter" 29 | "shortDescription" = "" 30 | "longDescription" = "" 31 | "copyright" = "Copyright (c) You 2021. All rights reserved." 32 | "icon" = ["../Logo.ico"] 33 | "resources" = [] 34 | "externalBin" = [] 35 | ``` 36 | Note that this is just an example, and the actual configuration will depend on the specific requirements of your application. and for more details, you can refer to the [`BundleConfig`] struct in the cli crate. 37 | -------------------------------------------------------------------------------- /core/src/builtin_widgets/opacity.rs: -------------------------------------------------------------------------------- 1 | use crate::{prelude::*, wrap_render::*}; 2 | 3 | /// A wrapper that controls the opacity of its child. 4 | /// 5 | /// This is a built-in `FatObj` field. Setting the `opacity` field attaches an 6 | /// `Opacity` wrapper which applies an alpha multiplier when painting. 7 | /// 8 | /// # Example 9 | /// 10 | /// Display a red container with 50% opacity. 11 | /// 12 | /// ```rust 13 | /// use ribir::prelude::*; 14 | /// 15 | /// container! { 16 | /// size: Size::new(100., 100.), 17 | /// background: Color::RED, 18 | /// opacity: 0.5, 19 | /// }; 20 | /// ``` 21 | #[derive(Clone)] 22 | pub struct Opacity { 23 | pub opacity: f32, 24 | } 25 | 26 | impl Declare for Opacity { 27 | type Builder = FatObj<()>; 28 | #[inline] 29 | fn declarer() -> Self::Builder { FatObj::new(()) } 30 | } 31 | 32 | impl Default for Opacity { 33 | #[inline] 34 | fn default() -> Self { Self { opacity: 1.0 } } 35 | } 36 | 37 | impl_compose_child_for_wrap_render!(Opacity); 38 | 39 | impl WrapRender for Opacity { 40 | fn paint(&self, host: &dyn Render, ctx: &mut PaintingCtx) { 41 | ctx.painter().apply_alpha(self.opacity); 42 | if self.opacity > 0. { 43 | host.paint(ctx) 44 | } 45 | } 46 | 47 | fn visual_box(&self, host: &dyn Render, ctx: &mut VisualCtx) -> Option { 48 | if self.opacity > 0. { 49 | host.visual_box(ctx) 50 | } else { 51 | ctx.clip(Rect::from_size(Size::zero())); 52 | None 53 | } 54 | } 55 | 56 | #[inline] 57 | fn wrapper_dirty_phase(&self) -> DirtyPhase { DirtyPhase::Paint } 58 | } 59 | -------------------------------------------------------------------------------- /macros/src/lerp_derive.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::quote; 3 | use syn::spanned::Spanned; 4 | 5 | use crate::util::data_struct_unwrap; 6 | 7 | pub(crate) fn lerp_derive(input: &mut syn::DeriveInput) -> syn::Result { 8 | let syn::DeriveInput { ident: name, generics, data, .. } = input; 9 | let (g_impl, g_ty, g_where) = generics.split_for_impl(); 10 | let stt = data_struct_unwrap(data, "Lerp")?; 11 | 12 | let tokens = match &stt.fields { 13 | syn::Fields::Named(n) => { 14 | let fields = n.named.iter().map(|f| &f.ident); 15 | quote! { 16 | impl #g_impl Lerp for #name #g_ty #g_where { 17 | fn lerp(&self, to: &Self, factor: f32) -> Self { 18 | #name { 19 | #(#fields: Lerp::lerp(&self.#fields, &to.#fields, factor)),* 20 | } 21 | } 22 | } 23 | } 24 | } 25 | syn::Fields::Unnamed(u) => { 26 | let indexes = u 27 | .unnamed 28 | .iter() 29 | .enumerate() 30 | .map(|(i, f)| syn::Index { index: i as u32, span: f.span() }); 31 | quote! { 32 | impl #g_impl Lerp for #name #g_ty #g_where { 33 | fn lerp(&self, to: &Self, factor: f32) -> Self { 34 | #name(#(Lerp::lerp(&self.#indexes, &to.#indexes, factor)) ,*) 35 | } 36 | } 37 | } 38 | } 39 | syn::Fields::Unit => quote! { 40 | impl #g_impl Lerp for #name #g_ty #g_impl #g_where { 41 | fn lerp(&self, to: &Self, factor: f32) -> Self { 42 | #name 43 | } 44 | } 45 | }, 46 | }; 47 | 48 | Ok(tokens) 49 | } 50 | -------------------------------------------------------------------------------- /core/src/builtin_widgets/text_align.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::*; 2 | 3 | /// Controls alignment of multi-line text within its bounds. Descendant 4 | /// `Text` widgets will use the `TextAlign` value to determine line alignment. 5 | /// 6 | /// This is a built-in `FatObj` field. Setting `text_align` attaches a 7 | /// `TextAlignWidget` that provides alignment to descendant text renderers. 8 | /// 9 | /// # Example 10 | /// 11 | /// Center-align all lines of a multi-line `Text` widget. 12 | /// 13 | /// ```rust 14 | /// use ribir::prelude::*; 15 | /// 16 | /// container! { 17 | /// size: Size::new(120., 40.), 18 | /// text_align: TextAlign::Center, 19 | /// @Text { text: "Line 1\nlong line 2" } 20 | /// }; 21 | /// ``` 22 | #[derive(Default)] 23 | pub struct TextAlignWidget { 24 | pub text_align: TextAlign, 25 | } 26 | 27 | impl Declare for TextAlignWidget { 28 | type Builder = FatObj<()>; 29 | 30 | #[inline] 31 | fn declarer() -> Self::Builder { FatObj::new(()) } 32 | } 33 | 34 | impl<'c> ComposeChild<'c> for TextAlignWidget { 35 | type Child = Widget<'c>; 36 | 37 | fn compose_child(this: impl StateWriter, child: Self::Child) -> Widget<'c> { 38 | Providers::new([Self::into_provider(this)]).with_child(child) 39 | } 40 | } 41 | 42 | impl TextAlignWidget { 43 | pub fn into_provider(this: impl StateWriter) -> Provider { 44 | match this.try_into_value() { 45 | Ok(this) => Provider::new(this.text_align), 46 | Err(this) => Provider::writer( 47 | this.part_writer(PartialId::any(), |w| PartMut::new(&mut w.text_align)), 48 | Some(DirtyPhase::LayoutSubtree), 49 | ), 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /core/src/local_sender.rs: -------------------------------------------------------------------------------- 1 | use std::{cell::Cell, thread::ThreadId}; 2 | 3 | /// This wrapper ensures that the data can only be accessed in the thread that 4 | /// initially utilizes it. 5 | /// 6 | /// Any attempt to access it from another thread will result in a panic. 7 | pub struct LocalSender { 8 | data: *mut T, 9 | id: Cell, 10 | } 11 | 12 | impl LocalSender { 13 | pub fn new(data: T) -> Self { 14 | let id = Cell::new(std::thread::current().id()); 15 | Self { data: Box::into_raw(Box::new(data)), id } 16 | } 17 | 18 | pub fn reset(&self) { self.id.set(std::thread::current().id()); } 19 | 20 | pub fn take(&self) 21 | where 22 | T: Default, 23 | { 24 | unsafe { self.data.replace(<_>::default()) }; 25 | } 26 | 27 | #[track_caller] 28 | fn assert_same_thread(&self) { 29 | assert_eq!( 30 | self.id.get(), 31 | std::thread::current().id(), 32 | "Access is only supported within the same thread." 33 | ); 34 | } 35 | } 36 | 37 | impl std::ops::Deref for LocalSender { 38 | type Target = T; 39 | 40 | fn deref(&self) -> &Self::Target { 41 | self.assert_same_thread(); 42 | unsafe { &*self.data } 43 | } 44 | } 45 | 46 | impl std::ops::DerefMut for LocalSender { 47 | fn deref_mut(&mut self) -> &mut Self::Target { 48 | self.assert_same_thread(); 49 | unsafe { &mut *self.data } 50 | } 51 | } 52 | 53 | impl Drop for LocalSender { 54 | fn drop(&mut self) { 55 | self.assert_same_thread(); 56 | let _drop = unsafe { Box::from_raw(self.data) }; 57 | } 58 | } 59 | 60 | unsafe impl Send for LocalSender {} 61 | unsafe impl Sync for LocalSender {} 62 | -------------------------------------------------------------------------------- /themes/material/src/classes/input_cls.rs: -------------------------------------------------------------------------------- 1 | use ribir_core::prelude::*; 2 | use ribir_widgets::input::{INPUT, TEXT_CARET, TEXT_SELECTION, TEXTAREA}; 3 | 4 | use crate::md; 5 | 6 | pub(super) fn init(classes: &mut Classes) { 7 | classes.insert(TEXT_CARET, |w| { 8 | rdl! { 9 | let mut w = FatObj::new(w); 10 | let blink_interval = Duration::from_millis(500); 11 | let u = Local::interval(blink_interval) 12 | .subscribe(move |idx| *$write(w.opacity()) = (idx % 2) as f32); 13 | let border = BuildCtx::color() 14 | .map(|color| Border::only_left(BorderSide::new(2., (*color).into()))); 15 | @(w) { 16 | clamp: BoxClamp::fixed_width(2.), 17 | border, 18 | on_disposed: move |_| u.unsubscribe() 19 | } 20 | } 21 | .into_widget() 22 | }); 23 | 24 | classes.insert( 25 | TEXT_SELECTION, 26 | style_class! { 27 | background: { 28 | let color = BuildCtx::color(); 29 | color.into_container_color(BuildCtx::get()).map(|c| c.with_alpha(0.8)) 30 | } 31 | }, 32 | ); 33 | 34 | fn input_border(w: Widget) -> Widget { 35 | let mut w = FatObj::new(w); 36 | let blur = Palette::of(BuildCtx::get()).on_surface_variant(); 37 | 38 | let focus_watcher = w.is_focused(); 39 | let border = BuildCtx::color().map_with_watcher(focus_watcher, move |c, focus| { 40 | let color = if *focus { *c } else { blur }; 41 | Border::all(BorderSide::new(1., color.into())) 42 | }); 43 | 44 | w.with_border(border).with_radius(md::RADIUS_2); 45 | w.into_widget() 46 | } 47 | classes.insert(INPUT, input_border); 48 | classes.insert(TEXTAREA, input_border); 49 | } 50 | -------------------------------------------------------------------------------- /painter/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors.workspace = true 3 | categories.workspace = true 4 | description.workspace = true 5 | documentation.workspace = true 6 | edition.workspace = true 7 | homepage.workspace = true 8 | keywords.workspace = true 9 | license.workspace = true 10 | name = "ribir_painter" 11 | readme.workspace = true 12 | repository = "https://github.com/RibirX/Ribir/painter" 13 | version.workspace = true 14 | 15 | [dependencies] 16 | bitflags = "2.3.0" 17 | image = {workspace = true, optional = true} 18 | log.workspace = true 19 | lyon_algorithms = {version = "1.0.3", features = ["serialization"]} 20 | lyon_tessellation = {version = "1.0.3", features = ["serialization"], optional = true} 21 | material-colors = {workspace = true} 22 | ribir_algo = {path = "../algo", version = "0.4.0-alpha.53" } 23 | ribir_geom = {path = "../geom", version = "0.4.0-alpha.53" } 24 | serde = {version = "1.0", features = ["derive"]} 25 | serde_json.workspace = true 26 | usvg.workspace = true 27 | zerocopy = {workspace = true, optional = true, features = ["derive"]} 28 | derive_more= {workspace = true, features = ["add", "add_assign", "not", "mul"]} 29 | smallvec = { workspace = true, features = ["serde"] } 30 | fontdb.workspace = true 31 | rustybuzz.workspace = true 32 | unicode-bidi.workspace = true 33 | unicode-script.workspace = true 34 | unicode-segmentation.workspace = true 35 | quick-xml.workspace = true 36 | ahash.workspace = true 37 | triomphe.workspace = true 38 | 39 | 40 | [target.'cfg(target_arch = "wasm32")'.dependencies] 41 | getrandom-v3.workspace = true 42 | 43 | [features] 44 | png = ["image/png"] 45 | jpeg = ["image/jpeg"] 46 | tessellation = ["lyon_tessellation", "zerocopy"] 47 | -------------------------------------------------------------------------------- /macros/src/asset/svg.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::quote; 3 | 4 | use super::{Asset, AssetContext}; 5 | 6 | pub(crate) struct SvgAsset { 7 | pub(crate) inherit_fill: bool, 8 | pub(crate) inherit_stroke: bool, 9 | } 10 | 11 | impl Asset for SvgAsset { 12 | fn generate(&self, ctx: &AssetContext) -> syn::Result { 13 | // SVG always requires processing (compression/serialization), so we always 14 | // write to output, even if embedding. 15 | let compressed_data = 16 | ribir_painter::Svg::open(&ctx.abs_input, self.inherit_fill, self.inherit_stroke) 17 | .and_then(|svg| svg.serialize()) 18 | .map_err(|e| { 19 | syn::Error::new( 20 | ctx.input_span, 21 | format!("Failed to compress SVG file '{}': {}", ctx.input_path, e), 22 | ) 23 | })?; 24 | 25 | // The data source is always the processed output file 26 | let load_expr = if ctx.embed { 27 | quote! { #compressed_data } 28 | } else { 29 | ctx 30 | .write_output(compressed_data.as_bytes()) 31 | .expect("Failed to write SVG data"); 32 | let path_tokens = ctx.runtime_path_tokens(); 33 | let panic_msg = ctx.panic_msg("read SVG"); 34 | quote! { 35 | &std::fs::read_to_string(&#path_tokens) 36 | .unwrap_or_else(|e| panic!("{}: {}", #panic_msg, e)) 37 | } 38 | }; 39 | 40 | let deserialize_msg = format!("Failed to deserialize SVG asset '{}'", ctx.relative_output); 41 | 42 | Ok(quote! { 43 | { 44 | Svg::deserialize(#load_expr) 45 | .unwrap_or_else(|e| panic!("{}: {}", #deserialize_msg, e)) 46 | } 47 | }) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /tests/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors.workspace = true 3 | categories.workspace = true 4 | description.workspace = true 5 | documentation.workspace = true 6 | edition.workspace = true 7 | homepage.workspace = true 8 | keywords.workspace = true 9 | license.workspace = true 10 | name = "tests" 11 | publish = false 12 | readme.workspace = true 13 | repository = "https://github.com/RibirX/Ribir/tests" 14 | version.workspace = true 15 | 16 | [dev-dependencies] 17 | paste.workspace = true 18 | ribir = {path = "../ribir", features = ["material", "widgets"]} 19 | ribir_dev_helper = {path = "../dev-helper"} 20 | ribir_geom = {path = "../geom"} 21 | ribir_painter = {path = "../painter"} 22 | ribir_core = {path = "../core", features=["test-utils"]} 23 | winit.workspace = true 24 | criterion = "0.7.0" 25 | todos = {path = "../examples/todos"} 26 | counter = {path = "../examples/counter"} 27 | messages = {path = "../examples/messages"} 28 | storybook = {path = "../examples/storybook"} 29 | wordle_game = {path = "../examples/wordle_game"} 30 | 31 | [[test]] 32 | name = "assets_test" 33 | path = "assets_test.rs" 34 | 35 | [[test]] 36 | name = "rdl_macro_test" 37 | path = "rdl_macro_test.rs" 38 | 39 | [[test]] 40 | name = "child_template_derive" 41 | path = "child_template_derive_test.rs" 42 | 43 | [[test]] 44 | name = "declare_builder" 45 | path = "declare_builder_test.rs" 46 | 47 | [[test]] 48 | name = "path_child" 49 | path = "path_child_test.rs" 50 | 51 | 52 | [[bench]] 53 | name = "text_bench" 54 | harness = false 55 | 56 | [[bench]] 57 | name = "example_bench" 58 | harness = false 59 | 60 | [[bench]] 61 | name = "core_bench" 62 | harness = false 63 | 64 | [[bench]] 65 | name = "widgets_bench" 66 | harness = false 67 | -------------------------------------------------------------------------------- /tests/declare_builder_test.rs: -------------------------------------------------------------------------------- 1 | use ribir::{core::reset_test_env, prelude::*}; 2 | 3 | #[test] 4 | fn declarer_smoke() { 5 | reset_test_env!(); 6 | // empty struct 7 | #[derive(Declare)] 8 | struct A; 9 | 10 | let _: FatObj = A::declarer().finish(); 11 | 12 | #[derive(Declare)] 13 | struct B { 14 | a: f32, 15 | b: i32, 16 | } 17 | 18 | let mut b = ::declarer(); 19 | b.with_a(1.).with_b(1); 20 | let b = b.finish(); 21 | assert_eq!(b.read().a, 1.); 22 | assert_eq!(b.read().b, 1); 23 | } 24 | 25 | #[test] 26 | #[should_panic = "Required field `T::a` not set"] 27 | fn panic_if_miss_require_field() { 28 | reset_test_env!(); 29 | #[derive(Declare)] 30 | struct T { 31 | a: f32, 32 | } 33 | 34 | let _ = ::declarer().finish(); 35 | } 36 | 37 | #[test] 38 | 39 | fn default_field() { 40 | reset_test_env!(); 41 | #[derive(Declare)] 42 | struct DefaultDeclare { 43 | #[declare(default)] 44 | a: f32, 45 | } 46 | 47 | let t = ::declarer().finish(); 48 | assert_eq!(t.read().a, 0.); 49 | } 50 | 51 | #[test] 52 | 53 | fn default_field_with_value() { 54 | reset_test_env!(); 55 | #[derive(Declare)] 56 | struct DefaultWithValue { 57 | #[declare(default = "hi!")] 58 | text: &'static str, 59 | } 60 | 61 | let t = ::declarer().finish(); 62 | assert_eq!(t.read().text, "hi!"); 63 | } 64 | 65 | #[test] 66 | fn declarer_simple_attr() { 67 | reset_test_env!(); 68 | #[simple_declare] 69 | struct Simple { 70 | a: f32, 71 | b: i32, 72 | } 73 | 74 | let mut s = Simple::declarer(); 75 | s.with_a(1.).with_b(1); 76 | let s = s.finish(); 77 | assert_eq!(s.read().a, 1.); 78 | assert_eq!(s.read().b, 1); 79 | } 80 | -------------------------------------------------------------------------------- /core/src/builtin_widgets/text_style.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::*; 2 | 3 | /// Provides a `TextStyle` to descendant text widgets for painting text. 4 | /// 5 | /// This is a built-in `FatObj` field. Setting `text_style` attaches a 6 | /// `TextStyleWidget` which supplies text style information to descendants. 7 | /// 8 | /// # Example 9 | /// 10 | /// Paint text using the title-large style from the typography theme. 11 | /// 12 | /// ```rust 13 | /// use ribir::prelude::*; 14 | /// 15 | /// text! { 16 | /// text: "Big Text", 17 | /// text_style: TypographyTheme::of(BuildCtx::get()).title_large.text.clone(), 18 | /// }; 19 | /// ``` 20 | pub struct TextStyleWidget { 21 | pub text_style: TextStyle, 22 | } 23 | 24 | impl Declare for TextStyleWidget { 25 | type Builder = FatObj<()>; 26 | #[inline] 27 | fn declarer() -> Self::Builder { FatObj::new(()) } 28 | } 29 | 30 | impl<'c> ComposeChild<'c> for TextStyleWidget { 31 | type Child = Widget<'c>; 32 | 33 | fn compose_child(this: impl StateWriter, child: Self::Child) -> Widget<'c> { 34 | Providers::new([Self::into_provider(this)]).with_child(child) 35 | } 36 | } 37 | 38 | impl TextStyleWidget { 39 | pub fn into_provider(this: impl StateWriter) -> Provider { 40 | match this.try_into_value() { 41 | Ok(this) => Provider::new(this.text_style), 42 | Err(this) => Provider::writer( 43 | this.part_writer(PartialId::any(), |w| PartMut::new(&mut w.text_style)), 44 | Some(DirtyPhase::LayoutSubtree), 45 | ), 46 | } 47 | } 48 | } 49 | 50 | impl TextStyleWidget { 51 | pub fn inherit_widget() -> Self { 52 | TextStyleWidget { 53 | text_style: Provider::of::(BuildCtx::get()) 54 | .unwrap() 55 | .clone(), 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /widgets/src/transform_box.rs: -------------------------------------------------------------------------------- 1 | use ribir_core::prelude::*; 2 | 3 | #[derive(SingleChild, Declare, Clone)] 4 | pub struct TransformBox { 5 | pub matrix: Transform, 6 | } 7 | 8 | impl Render for TransformBox { 9 | fn perform_layout(&self, clamp: BoxClamp, ctx: &mut LayoutCtx) -> Size { 10 | self 11 | .matrix 12 | .inverse() 13 | .map_or_else(Size::zero, |t| { 14 | let min_box = t.outer_transformed_box(&Box2D::from_size(clamp.min)); 15 | let min = min_box.size(); 16 | 17 | let max_box = t.outer_transformed_box(&Box2D::from_size(clamp.max)); 18 | let max = max_box.size(); 19 | 20 | let child_clamp = BoxClamp { min, max }; 21 | 22 | let size = ctx.assert_perform_single_child_layout(child_clamp); 23 | let rect = self 24 | .matrix 25 | .outer_transformed_rect(&Rect::from_size(size)); 26 | rect.size 27 | }) 28 | } 29 | 30 | #[inline] 31 | fn visual_box(&self, ctx: &mut VisualCtx) -> Option { 32 | Some(Rect::from_size(ctx.box_size()?)) 33 | } 34 | 35 | #[inline] 36 | fn paint(&self, ctx: &mut PaintingCtx) { ctx.painter().apply_transform(&self.matrix); } 37 | } 38 | 39 | impl TransformBox { 40 | #[inline] 41 | pub fn new(matrix: Transform) -> Self { Self { matrix } } 42 | } 43 | 44 | #[cfg(test)] 45 | mod tests { 46 | use ribir_core::test_helper::*; 47 | use ribir_dev_helper::*; 48 | 49 | use super::*; 50 | use crate::prelude::*; 51 | 52 | widget_layout_test!( 53 | smoke, 54 | WidgetTester::new(fn_widget! { 55 | @TransformBox { 56 | matrix: Transform::new(2., 0., 0., 2., 0., 0.), 57 | @SizedBox { size: Size::new(100., 100.) } 58 | } 59 | }), 60 | LayoutCase::default().with_size(Size::new(200., 200.)) 61 | ); 62 | } 63 | -------------------------------------------------------------------------------- /painter/src/style.rs: -------------------------------------------------------------------------------- 1 | use ribir_algo::Resource; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | use crate::{ 5 | Color, PixelImage, 6 | color::{LinearGradient, RadialGradient}, 7 | }; 8 | 9 | /// The brush is used to fill or stroke shapes with color, image, or gradient. 10 | #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] 11 | pub enum Brush { 12 | Color(Color), 13 | /// Image brush always use a repeat mode to brush the path. 14 | Image(Resource), 15 | RadialGradient(RadialGradient), 16 | LinearGradient(LinearGradient), 17 | } 18 | 19 | impl Brush { 20 | /// Returns the color of the brush, or `None` if the brush is not a color. 21 | pub fn get_color(&self) -> Option { 22 | match self { 23 | Brush::Color(c) => Some(*c), 24 | _ => None, 25 | } 26 | } 27 | 28 | pub fn is_visible(&self) -> bool { 29 | match self { 30 | Brush::Color(c) => c.alpha > 0, 31 | Brush::Image(_) => true, 32 | Brush::RadialGradient(RadialGradient { stops, .. }) 33 | | Brush::LinearGradient(LinearGradient { stops, .. }) => { 34 | stops.iter().any(|s| s.color.alpha > 0) 35 | } 36 | } 37 | } 38 | } 39 | 40 | impl From for Brush { 41 | #[inline] 42 | fn from(c: Color) -> Self { Brush::Color(c) } 43 | } 44 | 45 | impl From for Option { 46 | #[inline] 47 | fn from(c: Color) -> Self { Some(c.into()) } 48 | } 49 | 50 | impl From> for Brush { 51 | #[inline] 52 | fn from(img: Resource) -> Self { Brush::Image(img) } 53 | } 54 | 55 | impl From for Brush { 56 | #[inline] 57 | fn from(img: PixelImage) -> Self { Resource::new(img).into() } 58 | } 59 | 60 | impl Default for Brush { 61 | #[inline] 62 | fn default() -> Self { Color::BLACK.into() } 63 | } 64 | -------------------------------------------------------------------------------- /themes/material/src/classes/menu_cls.rs: -------------------------------------------------------------------------------- 1 | use ribir_core::prelude::*; 2 | use ribir_widgets::prelude::*; 3 | 4 | pub(super) fn init(classes: &mut Classes) { 5 | classes.insert( 6 | MENU, 7 | style_class! { 8 | background: Palette::of(BuildCtx::get()).surface_container(), 9 | clamp: BoxClamp::min_width(112.).with_max_width(280.), 10 | radius: Radius::all(4.), 11 | }, 12 | ); 13 | classes.insert( 14 | MENU_ITEM, 15 | style_class! { 16 | clamp: BoxClamp::fixed_height(48.), 17 | }, 18 | ); 19 | classes.insert( 20 | MENU_ITEM_SELECTED, 21 | style_class! { 22 | clamp: BoxClamp::fixed_height(48.), 23 | background: Palette::of(BuildCtx::get()).secondary_container(), 24 | }, 25 | ); 26 | classes.insert( 27 | MENU_ITEM_LABEL, 28 | style_class! { 29 | foreground: Palette::of(BuildCtx::get()).on_surface_variant(), 30 | padding: EdgeInsets::horizontal(12.), 31 | text_line_height: 20., 32 | font_size: 14., 33 | }, 34 | ); 35 | classes.insert( 36 | MENU_ITEM_LEADING, 37 | style_class! { 38 | clamp: BoxClamp::fixed_size(Size::new(24., 24.)), 39 | margin: EdgeInsets::only_left(12.), 40 | }, 41 | ); 42 | classes.insert( 43 | MENU_ITEM_HINT_TEXT, 44 | style_class! { 45 | padding: EdgeInsets::only_right(12.), 46 | }, 47 | ); 48 | classes.insert( 49 | MENU_ITEM_TRAILING, 50 | style_class! { 51 | clamp: BoxClamp::fixed_size(Size::new(24., 24.)), 52 | margin: EdgeInsets::only_right(12.), 53 | }, 54 | ); 55 | classes.insert( 56 | MENU_DIVIDER, 57 | style_class! { 58 | clamp: BoxClamp::fixed_height(1.).with_fixed_width(f32::INFINITY), 59 | background: Palette::of(BuildCtx::get()).surface_variant(), 60 | margin: EdgeInsets::vertical(8.), 61 | }, 62 | ); 63 | } 64 | -------------------------------------------------------------------------------- /changelog.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "disableEmoji": false, 3 | "list": [ 4 | "test", 5 | "feat", 6 | "fix", 7 | "build", 8 | "docs", 9 | "refactor", 10 | "perf", 11 | "ce" 12 | ], 13 | "maxMessageLength": 96, 14 | "minMessageLength": 3, 15 | "questions": [ 16 | "type", 17 | "scope", 18 | "subject", 19 | "body", 20 | "breaking", 21 | "issues", 22 | "lerna" 23 | ], 24 | "scopes": [ 25 | "core", 26 | "painter", 27 | "macros", 28 | "gpu", 29 | "algo", 30 | "widgets", 31 | "ribir", 32 | "theme", 33 | "geom", 34 | "examples", 35 | "deps", 36 | "dev-helper" 37 | ], 38 | "types": { 39 | "build": { 40 | "description": "Build process or auxiliary tool changes", 41 | "emoji": "🤖", 42 | "value": "build" 43 | }, 44 | "docs": { 45 | "description": "Documentation only changes", 46 | "emoji": "✏️", 47 | "value": "docs" 48 | }, 49 | "feat": { 50 | "description": "A new feature", 51 | "emoji": "🎸", 52 | "value": "feat" 53 | }, 54 | "fix": { 55 | "description": "A bug fix", 56 | "emoji": "🐛", 57 | "value": "fix" 58 | }, 59 | "perf": { 60 | "description": "A code change that improves performance", 61 | "emoji": "⚡️", 62 | "value": "perf" 63 | }, 64 | "refactor": { 65 | "description": "A code change that neither fixes a bug or adds a feature", 66 | "emoji": "💡", 67 | "value": "refactor" 68 | }, 69 | "test": { 70 | "description": "Adding missing tests or correcting existing tests", 71 | "emoji": "💍", 72 | "value": "test" 73 | }, 74 | "ce": { 75 | "description": "improve the compile error of macros", 76 | "emoji": "🔧", 77 | "value": "ce" 78 | } 79 | } 80 | }; -------------------------------------------------------------------------------- /.github/workflows/patch-version.yml: -------------------------------------------------------------------------------- 1 | on: 2 | workflow_dispatch: # allows manual triggering 3 | inputs: 4 | level: 5 | description: 'Release a patch version on the release branch, `auto` follow the last version level.' 6 | required: true 7 | default: 'auto' 8 | type: choice 9 | options: 10 | - 'auto' 11 | - 'beta' 12 | - 'rc' 13 | - 'patch' 14 | permissions: 15 | contents: write 16 | name: "Patch Version" 17 | jobs: 18 | release_level: 19 | runs-on: ubuntu-latest 20 | outputs: 21 | level: ${{ steps.concrete_level.outputs.level }} 22 | steps: 23 | - name: Release branch check 24 | if: startsWith(github.ref, 'refs/heads/release-') != true 25 | run: | 26 | echo "You should only run this workflow on the release branch." 27 | exit 1 28 | - uses: actions/checkout@v3 29 | with: 30 | fetch-depth: 0 31 | fetch-tags: true 32 | - id: auto_detect 33 | if: ${{ github.event.inputs.level == 'auto' || github.event.inputs.level == '' }} 34 | run: echo "level=$(git describe --tags --abbrev=0 | grep -o "rc\|beta")" >> $GITHUB_OUTPUT 35 | - id: concrete_level 36 | run: | 37 | level=${{ steps.auto_detect.outputs.level }} 38 | echo "level=${level:=${{inputs.level}}}" >> $GITHUB_OUTPUT 39 | call-workflow-passing-data: 40 | needs: release_level 41 | uses: RibirX/rclog/.github/workflows/release-version.yml@main 42 | with: 43 | level: ${{ needs.release_level.outputs.level }} 44 | ref: ${{ github.ref }} 45 | merge_changelog: ${{ needs.release_level.outputs.level == 'patch' }} 46 | toolchain: stable 47 | secrets: 48 | CRATE_RELEASE_TOKEN: ${{ secrets.CRATE_RELEASE_TOKEN }} 49 | GITHUB_RELEASE_TOKEN: ${{ secrets.RIBIR_RELEASE }} -------------------------------------------------------------------------------- /core/src/events/wheel.rs: -------------------------------------------------------------------------------- 1 | use crate::{impl_common_event_deref, prelude::*}; 2 | 3 | #[derive(Debug)] 4 | pub struct WheelEvent { 5 | pub delta_x: f32, 6 | pub delta_y: f32, 7 | pub common: CommonEvent, 8 | } 9 | 10 | impl_common_event_deref!(WheelEvent); 11 | 12 | impl WheelEvent { 13 | #[inline] 14 | pub fn new(delta_x: f32, delta_y: f32, id: WidgetId, wnd: &Window) -> Self { 15 | Self { delta_x, delta_y, common: CommonEvent::new(id, wnd.tree) } 16 | } 17 | } 18 | 19 | #[cfg(test)] 20 | mod tests { 21 | use super::*; 22 | use crate::{reset_test_env, test_helper::*}; 23 | 24 | #[test] 25 | fn smoke() { 26 | reset_test_env!(); 27 | 28 | let (bubble_receive_reader, bubble_receive) = split_value((0., 0.)); 29 | let (capture_receive_reader, capture_receive) = split_value((0., 0.)); 30 | let (event_order_reader, event_order) = split_value(vec![]); 31 | 32 | let widget = fn_widget! { 33 | @MockBox { 34 | size: Size::new(200., 200.), 35 | on_wheel_capture: move |wheel| { 36 | *$write(capture_receive) = (wheel.delta_x, wheel.delta_y); 37 | $write(event_order).push("capture"); 38 | }, 39 | @MockBox { 40 | size: Size::new(100., 100.), 41 | auto_focus: true, 42 | on_wheel: move |wheel| { 43 | *$write(bubble_receive) = (wheel.delta_x, wheel.delta_y); 44 | $write(event_order).push("bubble"); 45 | } 46 | } 47 | } 48 | }; 49 | 50 | let wnd = TestWindow::new_with_size(widget, Size::new(100., 100.)); 51 | 52 | wnd.draw_frame(); 53 | 54 | wnd.process_wheel(1.0, 1.0); 55 | wnd.run_frame_tasks(); 56 | 57 | assert_eq!(*bubble_receive_reader.read(), (1., 1.)); 58 | assert_eq!(*capture_receive_reader.read(), (1., 1.)); 59 | assert_eq!(*event_order_reader.read(), ["capture", "bubble"]); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /docs/zh/guide/README.md: -------------------------------------------------------------------------------- 1 | # 指南 2 | 3 | 本指南介绍 Ribir 的基本概念和使用模式,Ribir 是一个非侵入式的 Rust GUI 框架,让您能够从单一代码库构建跨平台应用程序。 4 | 5 | ## 核心概念 6 | 7 | ### 声明式 UI 8 | Ribir 使用声明式方法进行 UI 开发,您可以根据当前状态描述 UI 应该是什么样子。当状态改变时,框架会自动处理渲染和更新。 9 | 10 | ### Widget 和组合 11 | Ribir 中的一切都是由 Widget 构建的。您可以使用 `fn_widget!` 宏和 `@` 语法将简单的 Widget 组合成复杂的 UI: 12 | 13 | ```rust no_run 14 | use ribir::prelude::*; 15 | fn_widget! { 16 | @Container { 17 | size: Size::new(100., 100.), 18 | background: Color::RED, 19 | @Text { text: "Hello, Ribir!" } 20 | } 21 | }; 22 | ``` 23 | 24 | ### 状态管理 25 | Ribir 通过 `Stateful` 对象提供响应式状态管理。您可以使用 `$read` 读取状态,使用 `$write` 写入状态,并使用 `pipe!` 和 `watch!` 创建响应式绑定: 26 | 27 | ```rust no_run 28 | use ribir::prelude::*; 29 | let counter = Stateful::new(0); 30 | fn_widget! { 31 | @Column { 32 | @Text { text: pipe!($read(counter).to_string()) } 33 | @Button { 34 | on_tap: move |_| *$write(counter) += 1, 35 | @{ "Increment" } 36 | } 37 | } 38 | }; 39 | ``` 40 | 41 | ### 布局系统 42 | Ribir 的布局系统使用约束向下流动到 Widget 树,尺寸信息向上回流。`clamp` 属性允许您控制 Widget 在可用空间内如何调整尺寸。 43 | 44 | ### 数据共享和事件 45 | 使用 `Provider` 通过 Widget 树自上而下地共享数据,使用 `自定义事件` 从子 Widget 向上冒泡事件到父 Widget。 46 | 47 | ## 入门 48 | 49 | 要开始使用 Ribir 构建应用,请详细了解这些核心概念: 50 | - **[声明式 UI](./core_concepts/declarative_ui.md)**: 了解 DSL 语法和 Widget 组合 51 | - **[内置属性和 FatObj](./core_concepts/built_in_attributes_and_fat_obj.md)**: 使用内置功能 52 | - **[状态管理](./core_concepts/state_management.md)**: 管理应用程序状态 53 | - **[数据共享和事件](./core_concepts/data_sharing_and_events.md)**: 组件间的通信 54 | - **[Widget 系统](./core_concepts/widgets_composition.md)**: 了解架构 55 | - **[布局系统](./core_concepts/layout.md)**: 控制 UI 的排列方式 56 | 57 | 一旦您熟悉了这些基础知识,可以继续学习高级主题以扩展您的知识: 58 | - **[自定义 Widget](./advanced/custom_widgets.md)**: 构建可重用组件 59 | - **[动画](./advanced/animations.md)**: 为 UI 添加动态效果 60 | - **[主题](./advanced/theming.md)**: 自定义外观和风格 61 | - **[Widget 组件](./advanced/widgets.md)**: 详细了解 Widget 组件 -------------------------------------------------------------------------------- /themes/material/src/classes/divider_cls.rs: -------------------------------------------------------------------------------- 1 | use ribir_core::prelude::*; 2 | use ribir_widgets::prelude::*; 3 | 4 | use crate::md; 5 | 6 | const THICKNESS: f32 = 1.; 7 | 8 | named_style_impl!(horizontal_base => { 9 | clamp: BoxClamp::fixed_height(THICKNESS), 10 | h_align: HAlign::Stretch, 11 | background: Palette::of(BuildCtx::get()).outline_variant(), 12 | }); 13 | 14 | named_style_impl!(vertical_base => { 15 | clamp: BoxClamp::fixed_width(THICKNESS), 16 | v_align: VAlign::Stretch, 17 | background: Palette::of(BuildCtx::get()).outline_variant(), 18 | }); 19 | pub(super) fn init(classes: &mut Classes) { 20 | classes.insert(HORIZONTAL_DIVIDER, horizontal_base); 21 | 22 | classes.insert( 23 | HORIZONTAL_DIVIDER_INDENT_START, 24 | class_multi_impl! { 25 | horizontal_base, 26 | style_class!{ margin: md::EDGES_LEFT_16 } 27 | }, 28 | ); 29 | 30 | classes.insert( 31 | HORIZONTAL_DIVIDER_INDENT_END, 32 | class_multi_impl! { 33 | horizontal_base, 34 | style_class!{ margin: md::EDGES_RIGHT_16 } 35 | }, 36 | ); 37 | 38 | classes.insert( 39 | HORIZONTAL_DIVIDER_INDENT_BOTH, 40 | class_multi_impl! { 41 | horizontal_base, 42 | style_class!{ margin: md::EDGES_HOR_16 } 43 | }, 44 | ); 45 | 46 | classes.insert(VERTICAL_DIVIDER, vertical_base); 47 | 48 | classes.insert( 49 | VERTICAL_DIVIDER_INDENT_START, 50 | class_multi_impl! { 51 | vertical_base, 52 | style_class! { margin: md::EDGES_TOP_8 } 53 | }, 54 | ); 55 | 56 | classes.insert( 57 | VERTICAL_DIVIDER_INDENT_END, 58 | class_multi_impl! { 59 | vertical_base, 60 | style_class! { margin: md::EDGES_BOTTOM_8} 61 | }, 62 | ); 63 | 64 | classes.insert( 65 | VERTICAL_DIVIDER_INDENT_BOTH, 66 | class_multi_impl! { 67 | vertical_base, 68 | style_class! { margin: md::EDGES_VER_8 } 69 | }, 70 | ); 71 | } 72 | -------------------------------------------------------------------------------- /core/src/builtin_widgets/disabled.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::*; 2 | 3 | class_names! { 4 | #[doc = "Class name for the Disabled"] 5 | DISABLED, 6 | } 7 | 8 | /// A widget wrapper that marks its subtree as disabled. 9 | /// 10 | /// When disabled, a widget and its descendants do not receive keyboard or 11 | /// pointer events. Toggle the built-in `disabled` field to apply this 12 | /// behavior. To change the visual appearance, provide a custom `Disabled` 13 | /// style class. 14 | /// 15 | /// This is a built-in `FatObj` field. Setting `disabled` attaches a 16 | /// `Disabled` wrapper to the host. 17 | /// 18 | /// # Example 19 | /// 20 | /// Disable a text widget so it cannot be clicked. 21 | /// 22 | /// ```rust 23 | /// use ribir::prelude::*; 24 | /// 25 | /// text! { 26 | /// text: "You can't click me", 27 | /// disabled: true, 28 | /// on_tap: |_: &mut PointerEvent| println!("Click!"), 29 | /// }; 30 | /// ``` 31 | #[derive(Clone, Default)] 32 | pub struct Disabled { 33 | pub disabled: bool, 34 | } 35 | 36 | impl Declare for Disabled { 37 | type Builder = FatObj<()>; 38 | #[inline] 39 | fn declarer() -> Self::Builder { FatObj::new(()) } 40 | } 41 | 42 | impl<'c> ComposeChild<'c> for Disabled { 43 | type Child = Widget<'c>; 44 | fn compose_child(this: impl StateWriter, child: Self::Child) -> Widget<'c> { 45 | fn_widget! { 46 | let mut child = FatObj::new(child); 47 | @FocusScope { 48 | skip_descendants: pipe!($read(this).disabled()), 49 | skip_host: pipe!($read(this).disabled()), 50 | @IgnorePointer { 51 | ignore: pipe! { 52 | if $read(this).disabled() { IgnoreScope::Subtree } else { IgnoreScope::None } 53 | }, 54 | @(child) { class: pipe!($read(this).disabled().then_some(DISABLED)) } 55 | } 56 | } 57 | } 58 | .into_widget() 59 | } 60 | } 61 | 62 | impl Disabled { 63 | fn disabled(&self) -> bool { self.disabled } 64 | } 65 | -------------------------------------------------------------------------------- /examples/pomodoro/src/main.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] 2 | 3 | mod audio; 4 | mod config; 5 | mod pomodoro; 6 | mod ui; 7 | 8 | use pomodoro::Pomodoro; 9 | use ribir::{material, prelude::*}; 10 | 11 | use crate::ui::{ 12 | APP_ICON, load_icons, 13 | styles::{MINI_HEIGHT, MINI_WIDTH}, 14 | }; 15 | 16 | pub fn main() { 17 | load_icons(); 18 | App::run(fn_widget! { @Pomodoro {}}) 19 | .with_app_theme(material::purple::light) 20 | .with_size(Size::new(MINI_WIDTH, MINI_HEIGHT)) 21 | .with_resizable(false) 22 | .with_icon(&APP_ICON) 23 | .with_decorations(false) 24 | .with_title("Pomodoro Timer"); 25 | } 26 | 27 | #[cfg(not(target_arch = "wasm32"))] 28 | #[cfg(test)] 29 | mod tests { 30 | use ribir::{core::test_helper::*, material as ribir_material}; 31 | use ribir_dev_helper::*; 32 | 33 | use super::*; 34 | use crate::config::PomodoroConfig; 35 | 36 | fn mini_pomodoro() -> Widget<'static> { 37 | load_icons(); 38 | let mut config = PomodoroConfig::default_config(); 39 | config.auto_run = false; 40 | config.start_mini_mode = true; 41 | fn_widget! { 42 | @Pomodoro { 43 | config: config, 44 | } 45 | } 46 | .into_widget() 47 | } 48 | 49 | fn full_pomodoro() -> Widget<'static> { 50 | load_icons(); 51 | let mut config = PomodoroConfig::default_config(); 52 | config.auto_run = false; 53 | config.start_mini_mode = false; 54 | fn_widget! { 55 | @Pomodoro { 56 | config: config, 57 | } 58 | } 59 | .into_widget() 60 | } 61 | 62 | widget_image_tests!( 63 | mini_pomodoro, 64 | WidgetTester::new(mini_pomodoro) 65 | .with_wnd_size(Size::new(320., 240.)) 66 | .with_comparison(0.0005) 67 | ); 68 | widget_image_tests!( 69 | full_pomodoro, 70 | WidgetTester::new(full_pomodoro) 71 | .with_wnd_size(Size::new(320., 240.)) 72 | .with_comparison(0.0005) 73 | ); 74 | } 75 | -------------------------------------------------------------------------------- /core/src/builtin_widgets/ignore_pointer.rs: -------------------------------------------------------------------------------- 1 | use crate::{prelude::*, wrap_render::*}; 2 | 3 | /// A wrapper that ignores pointer events according to a specified scope. 4 | /// 5 | /// `IgnorePointer` can suppress pointer events for the host, only the host, 6 | /// or the whole subtree depending on `IgnoreScope`. 7 | /// 8 | /// # Example 9 | /// 10 | /// The red container below will not respond to pointer events when the scope 11 | /// is set to `IgnoreScope::Subtree`. 12 | /// 13 | /// ```rust 14 | /// use ribir::prelude::*; 15 | /// 16 | /// fn_widget! { 17 | /// @IgnorePointer { 18 | /// ignore: IgnoreScope::Subtree, 19 | /// @Container { 20 | /// size: Size::new(100., 100.), 21 | /// background: Color::RED, 22 | /// on_tap: |_: &mut PointerEvent| println!("This will never be printed"), 23 | /// } 24 | /// } 25 | /// }; 26 | /// ``` 27 | #[derive(Declare, Clone)] 28 | pub struct IgnorePointer { 29 | #[declare(default)] 30 | pub ignore: IgnoreScope, 31 | } 32 | 33 | /// Specify the scope for ignoring events. 34 | #[derive(Debug, Clone, Copy, Default)] 35 | pub enum IgnoreScope { 36 | /// Not ignore 37 | None, 38 | /// Ignore only the event of the current widget. 39 | OnlySelf, 40 | /// Ignored the event within the subtree, including the current widget. 41 | #[default] 42 | Subtree, 43 | } 44 | 45 | impl_compose_child_for_wrap_render!(IgnorePointer); 46 | 47 | impl WrapRender for IgnorePointer { 48 | fn hit_test(&self, host: &dyn Render, ctx: &mut HitTestCtx, pos: Point) -> HitTest { 49 | match self.ignore { 50 | IgnoreScope::Subtree => HitTest { hit: false, can_hit_child: false }, 51 | IgnoreScope::OnlySelf => { 52 | let hit = host.hit_test(ctx, pos); 53 | HitTest { hit: false, can_hit_child: hit.can_hit_child } 54 | } 55 | IgnoreScope::None => host.hit_test(ctx, pos), 56 | } 57 | } 58 | 59 | #[inline] 60 | fn wrapper_dirty_phase(&self) -> DirtyPhase { DirtyPhase::Paint } 61 | } 62 | -------------------------------------------------------------------------------- /docs/en/get_started/try_it.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | # Try Ribir 6 | 7 | > You will learn 8 | > 9 | > - How to write and start a simple `Hello world!` application 10 | 11 | 12 | ## Install Rust 13 | 14 | At first, you need to install Rust, you can reference the [official documentation](https://www.rust-lang.org/tools/install). 15 | 16 | 17 | ## Create a new Ribir project 18 | 19 | Then, open your terminal and create a new Rust project: 20 | 21 | ```sh 22 | cargo new ribir-hello-world 23 | cd ribir-hello-world 24 | ``` 25 | 26 | Next, edit the `Cargo.toml` file and add Ribir as a dependency: 27 | 28 | ```toml 29 | [dependencies] 30 | ribir = "@RIBIR_VERSION" 31 | ``` 32 | 33 | Or you can directly run `cargo add --git "https://github.com/RibirX/Ribir" ribir` to let Cargo add the latest Ribir version that is under development for you. 34 | 35 | ## Start writing `main.rs` 36 | 37 | Open your editor and modify the `src/main.rs` file to: 38 | 39 | ```rust no_run 40 | // main.rs 41 | use ribir::prelude::*; 42 | 43 | fn main() { 44 | App::run(text! { text: "Hello World!" }); 45 | } 46 | ``` 47 | 48 | ## Run the application 49 | 50 | ```sh 51 | cargo run 52 | ``` 53 | 54 | Congratulations! You have completed your first Ribir project. 55 | 56 | ## Run Ribir's built-in examples 57 | 58 | Finally, there are some other examples in the Ribir repository, you can clone the Git repository: 59 | 60 | ```sh 61 | git clone git@github.com:RibirX/Ribir.git 62 | cd Ribir/Ribir 63 | ``` 64 | 65 | and run the examples with one of the following commands: 66 | 67 | ```sh 68 | cargo run -p counter 69 | cargo run -p storybook 70 | cargo run -p messages 71 | cargo run -p todos 72 | cargo run -p wordle_game 73 | ``` 74 | 75 | ## Next Steps 76 | 77 | If you're hesitant about creating UI with "DSL" and prefer to build your UI through direct function calls, before moving on to the next step, you might want to read [Using Ribir without "DSL"](../understanding_ribir/without_dsl.md) first. -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/documentation.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Documentation 3 | about: Report an issue relating to Ribir's documetation 4 | title: '' 5 | labels: documentation 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | 12 | This is about: 13 | - [ ] A typo 14 | - [ ] Innaccurate/misleading documentation (e.g. technically incorrect advice) 15 | - [ ] Undocumented code 16 | - [ ] Outdated documentation 17 | - [ ] Other 18 | 19 | **Problem** 20 | 25 | 26 | 27 | 28 | 30 | 31 | 32 | 33 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | **Details about the solution you'd like** _(Optional)_ 44 | 45 | **Additional context** _(Optional)_ 46 | 47 | 48 | **Questionaire** _(Optional)_ 49 | - [ ] I'd like to write this documentation 50 | - [ ] I'd like to write this documentation but I'm not sure what's needed 51 | - [ ] I don't have time to add this right now, but maybe later 52 | -------------------------------------------------------------------------------- /.github/workflows/ISSUE_TEMPLATE/documentation.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Documentation 3 | about: Report an issue relating to this project's documetation. 4 | title: '' 5 | labels: documentation 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | 12 | This is about: 13 | - [ ] A typo 14 | - [ ] Innaccurate/misleading documentation (e.g. technically incorrect advice) 15 | - [ ] Undocumented code 16 | - [ ] Outdated documentation 17 | - [ ] Other 18 | 19 | **Problem** 20 | 25 | 26 | 27 | 28 | 30 | 31 | 32 | 33 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | **Details about the solution you'd like** _(Optional)_ 44 | 45 | **Additional context** _(Optional)_ 46 | 47 | 48 | **Questionaire** _(Optional)_ 49 | - [ ] I'd like to write this documentation 50 | - [ ] I'd like to write this documentation but I'm not sure what's needed 51 | - [ ] I don't have time to add this right now, but maybe later -------------------------------------------------------------------------------- /core/src/builtin_widgets/track_widget_id.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::*; 2 | 3 | #[derive(Default)] 4 | pub struct TrackWidgetId { 5 | pub(crate) wid: TrackId, 6 | } 7 | 8 | impl<'c> ComposeChild<'c> for TrackWidgetId { 9 | type Child = Widget<'c>; 10 | fn compose_child(this: impl StateWriter, child: Self::Child) -> Widget<'c> { 11 | let track_id = this.read().wid.clone(); 12 | child 13 | .on_build(move |id| track_id.set(Some(id))) 14 | .attach_data(Box::new(Queryable(this.read().wid.clone()))) 15 | } 16 | } 17 | 18 | impl TrackWidgetId { 19 | /// The WidgetId of the Widget may be changed durning running. 20 | /// Don't rely on the id to do things unless necessary. If you must get the 21 | /// id, you can use TrackId to capture the value. 22 | pub fn track_id(&self) -> TrackId { self.wid.clone() } 23 | } 24 | 25 | impl Declare for TrackWidgetId { 26 | type Builder = FatObj<()>; 27 | #[inline] 28 | fn declarer() -> Self::Builder { FatObj::new(()) } 29 | } 30 | 31 | #[cfg(test)] 32 | mod tests { 33 | use super::*; 34 | use crate::{ 35 | reset_test_env, 36 | test_helper::{split_value, *}, 37 | }; 38 | 39 | #[test] 40 | fn track_id_of_pipe() { 41 | reset_test_env!(); 42 | let (trigger, w_trigger) = split_value(false); 43 | let (id, w_id) = split_value(>::None); 44 | let w = fn_widget! { 45 | let w = @ pipe!(*$read(trigger)).map(move |_| fn_widget!{ @ Void {} }); 46 | let mut w = FatObj::new(w); 47 | @(w) { 48 | on_mounted: move |_| { 49 | *$write(w_id) = Some($clone(w.track_id())); 50 | } 51 | } 52 | }; 53 | 54 | let wnd = TestWindow::from_widget(w); 55 | wnd.draw_frame(); 56 | let first_id = id.read().as_ref().map(|w| w.get()); 57 | assert!(first_id.is_some()); 58 | 59 | *w_trigger.write() = true; 60 | wnd.draw_frame(); 61 | let second_id = id.read().as_ref().map(|w| w.get()); 62 | assert!(second_id.is_some()); 63 | assert_ne!(first_id, second_id); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /examples/todos/src/todos.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::BTreeMap, 3 | fs::File, 4 | io::{self, BufWriter, Write}, 5 | }; 6 | 7 | use serde::{Deserialize, Serialize}; 8 | 9 | #[derive(Debug, Serialize, Deserialize, Default)] 10 | pub struct Todos { 11 | tasks: BTreeMap, 12 | next_id: TaskId, 13 | } 14 | 15 | #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] 16 | pub struct Task { 17 | id: TaskId, 18 | pub complete: bool, 19 | pub label: String, 20 | } 21 | 22 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Default)] 23 | pub struct TaskId(usize); 24 | 25 | impl Todos { 26 | pub fn new_task(&mut self, label: String) -> TaskId { 27 | let id = self.next_id; 28 | self.next_id = self.next_id.next(); 29 | 30 | self 31 | .tasks 32 | .insert(id, Task { id, label, complete: false }); 33 | id 34 | } 35 | 36 | pub fn remove(&mut self, id: TaskId) { self.tasks.remove(&id); } 37 | 38 | pub fn get_task(&self, id: TaskId) -> Option<&Task> { self.tasks.get(&id) } 39 | 40 | pub fn get_task_mut(&mut self, id: TaskId) -> Option<&mut Task> { self.tasks.get_mut(&id) } 41 | 42 | pub fn all_tasks(&self) -> impl Iterator + '_ { self.tasks.keys().copied() } 43 | } 44 | 45 | impl Task { 46 | pub fn id(&self) -> TaskId { self.id } 47 | } 48 | 49 | impl Todos { 50 | pub fn load() -> Self { 51 | std::fs::read(Self::store_path()) 52 | .map(|v| serde_json::from_slice(v.as_slice()).unwrap()) 53 | .unwrap_or_else(|_| Todos { tasks: BTreeMap::new(), next_id: TaskId(0) }) 54 | } 55 | 56 | pub fn save(&self) -> Result<(), io::Error> { 57 | let file = File::create(Self::store_path())?; 58 | let mut writer = BufWriter::new(file); 59 | serde_json::to_writer(&mut writer, self)?; 60 | writer.flush()?; 61 | Ok(()) 62 | } 63 | 64 | fn store_path() -> std::path::PathBuf { std::env::temp_dir().join("ribir_todos.json") } 65 | } 66 | 67 | impl TaskId { 68 | pub fn next(&self) -> Self { Self(self.0 + 1) } 69 | } 70 | -------------------------------------------------------------------------------- /core/src/state/watcher.rs: -------------------------------------------------------------------------------- 1 | use std::convert::Infallible; 2 | 3 | use rxrust::observable::boxed::LocalBoxedObservableClone; 4 | 5 | use crate::prelude::*; 6 | 7 | pub struct Watcher { 8 | reader: R, 9 | modifies_observable: LocalBoxedObservableClone<'static, ModifyInfo, Infallible>, 10 | } 11 | 12 | impl Watcher { 13 | pub fn new( 14 | reader: R, modifies_observable: LocalBoxedObservableClone<'static, ModifyInfo, Infallible>, 15 | ) -> Self { 16 | Self { reader, modifies_observable } 17 | } 18 | } 19 | 20 | impl From>> for Reader { 21 | fn from(w: Watcher>) -> Self { w.reader } 22 | } 23 | 24 | impl StateReader for Watcher { 25 | type Value = R::Value; 26 | type Reader = R::Reader; 27 | 28 | #[inline] 29 | fn read(&self) -> ReadRef<'_, Self::Value> { self.reader.read() } 30 | 31 | #[inline] 32 | fn clone_boxed_reader(&self) -> Box> { 33 | Box::new(self.clone_reader()) 34 | } 35 | 36 | #[inline] 37 | fn clone_reader(&self) -> Self::Reader { self.reader.clone_reader() } 38 | 39 | #[inline] 40 | fn try_into_value(self) -> Result 41 | where 42 | Self::Value: Sized, 43 | { 44 | let Self { reader, modifies_observable } = self; 45 | reader 46 | .try_into_value() 47 | .map_err(|reader| Self { reader, modifies_observable }) 48 | } 49 | } 50 | 51 | impl StateWatcher for Watcher { 52 | type Watcher = Watcher; 53 | 54 | fn into_reader(self) -> Result { Err(self) } 55 | 56 | #[inline] 57 | fn clone_boxed_watcher(&self) -> Box> { 58 | Box::new(self.clone_watcher()) 59 | } 60 | 61 | #[inline] 62 | fn raw_modifies(&self) -> LocalBoxedObservableClone<'static, ModifyInfo, Infallible> { 63 | self.modifies_observable.clone() 64 | } 65 | 66 | fn clone_watcher(&self) -> Watcher { 67 | Watcher::new(self.clone_reader(), self.raw_modifies()) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /docs/zh/understanding_ribir/widget_in_depth.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | # 深入了解 Widget 6 | 7 | 8 | 在 Ribir 中: 9 | 10 | - 视图由 widget 作为基本单位构建。 11 | - widget 之间通过**纯组合**的方式组成新的 widget。 12 | 13 | Ribir 的独特之处在于它是通过**纯组合**的方式来组成新的 widget。 14 | 15 | ## 纯组合 16 | 17 | 当我们说**纯组合**时,意味着: widget 之间的父子关系并不涉及所有权,父 widget 通过 trait 来约定可以有子 widget,但并不拥有子 widget。 18 | 19 | 通常,其它框架的数据结构是这样的,父亲通过一个类似 `children` 的属性持有孩子: 20 | 21 | ```rust 22 | struct Parent { 23 | property: &'static str, 24 | children: Vec, 25 | } 26 | 27 | struct Child { 28 | property: &'static str, 29 | } 30 | 31 | let widget = Parent { 32 | property: "parent", 33 | children: vec![ 34 | Child { property: "child1" }, 35 | Child { property: "child2" }, 36 | ], 37 | }; 38 | ``` 39 | 40 | 而在 Ribir 中,数据结构却是类似这样的: 41 | 42 | ```rust ignore 43 | struct Parent { 44 | property: &'static str, 45 | } 46 | 47 | struct Child { 48 | property: &'static str, 49 | } 50 | 51 | let parent = Parent { property: "parent" }; 52 | let child1 = Child { property: "child1" }; 53 | let child2 = Child { property: "child2" }; 54 | 55 | let widget = MultiPair { 56 | parent, 57 | children: vec![child1, child2], 58 | }; 59 | ``` 60 | 61 | 父 widget 和子 widget 之间是完全透明和独立的,我们并不将子 widget 添加到父 widget 中。 62 | 63 | 当然,这只是一个简化的例子。实际上,Ribir 的组合方式更加灵活,而且在实际使用中,你不会接触到 `MultiPair` 这样的中间数据结构。 64 | 65 | 这种组合方式的优点是,它产生的 widget 更小,更纯粹,更容易复用,可以根据需要进行组合。让我们以 Ribir 的内建 widget 为例来说明这一点。 66 | 67 | 在传统的 GUI 框架中,我们通常通过继承一个基础对象(或类似的方式)来获得一组常用的基础功能。这个基础对象通常包含许多属性,因此并不小。然而,在 Ribir 中,我们通过按需组合一组迷你的内建 widget 来实现这些功能。以 `Opacity` 为例,在 Ribir 中,它只有一个 `f32` 类型的属性。当你需要改变 widget 的透明度时,你可以直接使用 `Opacity` 来组合你的 widget: 68 | 69 | ```rust ignore 70 | use ribir::prelude::*; 71 | 72 | // Opacity 的定义是这样的: 73 | // struct Opacity { opacity: f64 } 74 | 75 | let w = Opacity { opacity: 0.5 }.with_child(Void, ctx); 76 | ``` 77 | 78 | 当然,实际代码中,你可以直接写成 `@Void { opacity: 0.5 }`。 79 | 80 | 81 | ## 四种基础 widget 82 | 83 | > 即将推出 84 | 85 | - [ ] render widget 86 | - [ ] compose widget 87 | - [ ] compose child widget 88 | - [ ] function widget 89 | -------------------------------------------------------------------------------- /widgets/src/layout/sized_box.rs: -------------------------------------------------------------------------------- 1 | use ribir_core::prelude::*; 2 | 3 | /// A box with a specified size. 4 | /// 5 | /// This widget forces its child to have a specific width and/or height 6 | /// (assuming values are permitted by the parent of this widget). 7 | #[derive(SingleChild, Declare, Clone)] 8 | pub struct SizedBox { 9 | /// The specified size of the box. 10 | pub size: Size, 11 | } 12 | 13 | impl Render for SizedBox { 14 | #[inline] 15 | fn perform_layout(&self, clamp: BoxClamp, ctx: &mut LayoutCtx) -> Size { 16 | let size = clamp.clamp(self.size); 17 | ctx.perform_single_child_layout(BoxClamp { min: size, max: size }); 18 | size 19 | } 20 | 21 | #[inline] 22 | fn size_affected_by_child(&self) -> bool { false } 23 | } 24 | 25 | #[cfg(test)] 26 | mod tests { 27 | use ribir_core::test_helper::*; 28 | use ribir_dev_helper::*; 29 | 30 | use super::*; 31 | 32 | widget_layout_test!( 33 | fix_size, 34 | WidgetTester::new(fn_widget! { 35 | let size: Size = Size::new(100., 100.); 36 | @SizedBox { 37 | size, 38 | @Text { text: "" } 39 | } 40 | }), 41 | LayoutCase::default().with_size(Size::new(100., 100.)) 42 | ); 43 | 44 | widget_layout_test!( 45 | shrink_size, 46 | WidgetTester::new(fn_widget! { 47 | @SizedBox { 48 | size: ZERO_SIZE, 49 | @Text { text: "" } 50 | } 51 | }), 52 | LayoutCase::default().with_size(ZERO_SIZE), 53 | LayoutCase::new(&[0, 0]).with_size(ZERO_SIZE) 54 | ); 55 | 56 | widget_layout_test!( 57 | expanded_size, 58 | WidgetTester::new(fn_widget! { 59 | @SizedBox { 60 | size: INFINITY_SIZE, 61 | @Text { text: "" } 62 | } 63 | }) 64 | .with_wnd_size(Size::new(500., 500.)), 65 | LayoutCase::default().with_size(Size::new(500., 500.)), 66 | LayoutCase::new(&[0, 0]).with_size(Size::new(500., 500.)) 67 | ); 68 | 69 | widget_layout_test!( 70 | empty_box, 71 | WidgetTester::new(fn_widget!(SizedBox { size: Size::new(10., 10.) })), 72 | LayoutCase::default().with_size(Size::new(10., 10.)) 73 | ); 74 | } 75 | -------------------------------------------------------------------------------- /core/src/builtin_widgets/constrained_box.rs: -------------------------------------------------------------------------------- 1 | use crate::{prelude::*, wrap_render::*}; 2 | 3 | /// A wrapper that applies additional constraint clamping to its child. 4 | /// 5 | /// This is a built-in `FatObj` field. Setting the `clamp` field attaches a 6 | /// `ConstrainedBox` which constrains the child's min/max sizes. 7 | /// 8 | /// # Example 9 | /// 10 | /// Constrain a container to a maximum width of 100. 11 | /// 12 | /// ```rust 13 | /// use ribir::prelude::*; 14 | /// 15 | /// container! { 16 | /// size: Size::new(200., 50.), // This will be constrained to width 100. 17 | /// background: Color::RED, 18 | /// clamp: BoxClamp::max_width(100.), 19 | /// }; 20 | /// ``` 21 | #[derive(Clone, Default)] 22 | pub struct ConstrainedBox { 23 | pub clamp: BoxClamp, 24 | } 25 | 26 | impl Declare for ConstrainedBox { 27 | type Builder = FatObj<()>; 28 | #[inline] 29 | fn declarer() -> Self::Builder { FatObj::new(()) } 30 | } 31 | 32 | impl_compose_child_for_wrap_render!(ConstrainedBox); 33 | 34 | impl WrapRender for ConstrainedBox { 35 | fn perform_layout(&self, clamp: BoxClamp, host: &dyn Render, ctx: &mut LayoutCtx) -> Size { 36 | let max = clamp.clamp(self.clamp.max); 37 | let min = clamp.clamp(self.clamp.min); 38 | host.perform_layout(BoxClamp { min, max }, ctx) 39 | } 40 | 41 | fn size_affected_by_child(&self, host: &dyn Render) -> bool { 42 | let is_fixed = self.clamp.min == self.clamp.max; 43 | if is_fixed { false } else { host.size_affected_by_child() } 44 | } 45 | 46 | #[inline] 47 | fn wrapper_dirty_phase(&self) -> DirtyPhase { DirtyPhase::Layout } 48 | } 49 | 50 | #[cfg(test)] 51 | mod tests { 52 | use ribir_dev_helper::*; 53 | 54 | use super::*; 55 | use crate::test_helper::*; 56 | 57 | widget_layout_test!( 58 | outside_fixed_clamp, 59 | WidgetTester::new(fn_widget! { 60 | @ConstrainedBox { 61 | clamp: BoxClamp::fixed_size(Size::new(50., 50.)), 62 | @Void { 63 | clamp: BoxClamp::fixed_size(Size::new(40., 40.)) 64 | } 65 | } 66 | }), 67 | LayoutCase::new(&[0]).with_size(Size::new(50., 50.)) 68 | ); 69 | } 70 | -------------------------------------------------------------------------------- /geom/src/lib.rs: -------------------------------------------------------------------------------- 1 | /// The tag for device unit system to prevent mixing values from different 2 | /// system. 3 | #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] 4 | pub struct PhysicUnit; 5 | 6 | /// The tag for logic unit system to prevent mixing values from different 7 | /// system. 8 | #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] 9 | pub struct LogicUnit; 10 | 11 | pub type Rect = euclid::Rect; 12 | pub type Point = euclid::Point2D; 13 | pub type Size = euclid::Size2D; 14 | pub type Transform = euclid::Transform2D; 15 | pub type Vector = euclid::Vector2D; 16 | pub type Angle = euclid::Angle; 17 | pub type Box2D = euclid::Box2D; 18 | pub type SideOffsets2D = euclid::SideOffsets2D; 19 | 20 | pub type DeviceRect = euclid::Rect; 21 | pub type DevicePoint = euclid::Point2D; 22 | pub type DeviceSize = euclid::Size2D; 23 | pub type DeviceVector = euclid::Vector2D; 24 | 25 | pub const INFINITY_SIZE: Size = Size::new(f32::INFINITY, f32::INFINITY); 26 | pub const ZERO_SIZE: Size = Size::new(0., 0.); 27 | use std::ops::Add; 28 | 29 | pub use euclid::{num::Zero, rect}; 30 | 31 | /// Return the four corners of a rectangle: [left-top, right-top, 32 | /// right-bottom, left-bottom] 33 | pub fn rect_corners(rect: &euclid::Rect) -> [euclid::Point2D; 4] 34 | where 35 | T: Copy + Add, 36 | { 37 | use euclid::Point2D; 38 | 39 | [ 40 | rect.min(), 41 | Point2D::new(rect.max_x(), rect.min_y()), 42 | rect.max(), 43 | Point2D::new(rect.min_x(), rect.max_y()), 44 | ] 45 | } 46 | 47 | /// Apply the transform to the rect and return the new rect as device unit. 48 | pub fn transform_to_device_rect(rect: &Rect, matrix: &Transform) -> DeviceRect { 49 | matrix 50 | .outer_transformed_rect(rect) 51 | .round_out() 52 | .to_i32() 53 | .cast_unit() 54 | } 55 | --------------------------------------------------------------------------------