Repository: iced-rs/iced Branch: master Commit: 12a0126568ba Files: 542 Total size: 2.9 MB Directory structure: gitextract_lpzyxa4s/ ├── .cargo/ │ └── config.toml ├── .github/ │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ ├── BUG-REPORT.yml │ │ └── config.yml │ ├── PULL_REQUEST_TEMPLATE.md │ └── workflows/ │ ├── audit.yml │ ├── build.yml │ ├── check.yml │ ├── document.yml │ ├── format.yml │ ├── lint.yml │ └── test.yml ├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Cargo.toml ├── Cross.toml ├── DEPENDENCIES.md ├── LICENSE ├── README.md ├── ROADMAP.md ├── beacon/ │ ├── Cargo.toml │ └── src/ │ ├── client.rs │ ├── error.rs │ ├── lib.rs │ ├── span.rs │ └── stream.rs ├── benches/ │ ├── ipsum.txt │ └── wgpu.rs ├── clippy.toml ├── core/ │ ├── Cargo.toml │ ├── README.md │ └── src/ │ ├── alignment.rs │ ├── angle.rs │ ├── animation.rs │ ├── background.rs │ ├── border.rs │ ├── clipboard.rs │ ├── color.rs │ ├── content_fit.rs │ ├── element.rs │ ├── event.rs │ ├── font.rs │ ├── gradient.rs │ ├── image.rs │ ├── input_method.rs │ ├── keyboard/ │ │ ├── event.rs │ │ ├── key.rs │ │ ├── location.rs │ │ └── modifiers.rs │ ├── keyboard.rs │ ├── layout/ │ │ ├── DRUID_LICENSE │ │ ├── flex.rs │ │ ├── limits.rs │ │ └── node.rs │ ├── layout.rs │ ├── length.rs │ ├── lib.rs │ ├── mouse/ │ │ ├── button.rs │ │ ├── click.rs │ │ ├── cursor.rs │ │ ├── event.rs │ │ └── interaction.rs │ ├── mouse.rs │ ├── overlay/ │ │ ├── element.rs │ │ ├── group.rs │ │ └── nested.rs │ ├── overlay.rs │ ├── padding.rs │ ├── pixels.rs │ ├── point.rs │ ├── rectangle.rs │ ├── renderer/ │ │ └── null.rs │ ├── renderer.rs │ ├── rotation.rs │ ├── settings.rs │ ├── shadow.rs │ ├── shell.rs │ ├── size.rs │ ├── svg.rs │ ├── text/ │ │ ├── editor.rs │ │ ├── highlighter.rs │ │ └── paragraph.rs │ ├── text.rs │ ├── theme/ │ │ └── palette.rs │ ├── theme.rs │ ├── time.rs │ ├── touch.rs │ ├── transformation.rs │ ├── vector.rs │ ├── widget/ │ │ ├── id.rs │ │ ├── operation/ │ │ │ ├── focusable.rs │ │ │ ├── scrollable.rs │ │ │ └── text_input.rs │ │ ├── operation.rs │ │ ├── text.rs │ │ └── tree.rs │ ├── widget.rs │ ├── window/ │ │ ├── direction.rs │ │ ├── event.rs │ │ ├── icon.rs │ │ ├── id.rs │ │ ├── level.rs │ │ ├── mode.rs │ │ ├── position.rs │ │ ├── redraw_request.rs │ │ ├── screenshot.rs │ │ ├── settings/ │ │ │ ├── linux.rs │ │ │ ├── macos.rs │ │ │ ├── other.rs │ │ │ ├── wasm.rs │ │ │ └── windows.rs │ │ ├── settings.rs │ │ └── user_attention.rs │ └── window.rs ├── debug/ │ ├── Cargo.toml │ └── src/ │ └── lib.rs ├── devtools/ │ ├── Cargo.toml │ └── src/ │ ├── comet.rs │ ├── lib.rs │ └── time_machine.rs ├── docs/ │ ├── redirect.html │ └── release_summary.py ├── examples/ │ ├── README.md │ ├── arc/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src/ │ │ └── main.rs │ ├── bezier_tool/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src/ │ │ └── main.rs │ ├── changelog/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── changelog.rs │ │ ├── icon.rs │ │ └── main.rs │ ├── checkbox/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src/ │ │ └── main.rs │ ├── clock/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src/ │ │ └── main.rs │ ├── color_palette/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src/ │ │ └── main.rs │ ├── combo_box/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src/ │ │ └── main.rs │ ├── counter/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── index.html │ │ └── src/ │ │ └── main.rs │ ├── custom_quad/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── main.rs │ ├── custom_shader/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── main.rs │ │ ├── scene/ │ │ │ ├── camera.rs │ │ │ ├── pipeline/ │ │ │ │ ├── buffer.rs │ │ │ │ ├── cube.rs │ │ │ │ ├── uniforms.rs │ │ │ │ └── vertex.rs │ │ │ └── pipeline.rs │ │ ├── scene.rs │ │ └── shaders/ │ │ ├── cubes.wgsl │ │ └── depth.wgsl │ ├── custom_widget/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src/ │ │ └── main.rs │ ├── delineate/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── main.rs │ ├── download_progress/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── index.html │ │ └── src/ │ │ ├── download.rs │ │ └── main.rs │ ├── editor/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── main.rs │ ├── events/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src/ │ │ └── main.rs │ ├── exit/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src/ │ │ └── main.rs │ ├── ferris/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── main.rs │ ├── gallery/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── civitai.rs │ │ └── main.rs │ ├── game_of_life/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src/ │ │ ├── main.rs │ │ └── preset.rs │ ├── geometry/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src/ │ │ └── main.rs │ ├── gradient/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── main.rs │ ├── integration/ │ │ ├── .gitignore │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src/ │ │ ├── controls.rs │ │ ├── main.rs │ │ ├── scene.rs │ │ └── shader/ │ │ ├── frag.wgsl │ │ └── vert.wgsl │ ├── layout/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── main.rs │ ├── lazy/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── main.rs │ ├── loading_spinners/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src/ │ │ ├── circular.rs │ │ ├── easing.rs │ │ ├── linear.rs │ │ └── main.rs │ ├── loupe/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── main.rs │ ├── markdown/ │ │ ├── Cargo.toml │ │ ├── build.rs │ │ ├── fonts/ │ │ │ └── markdown-icons.toml │ │ ├── overview.md │ │ └── src/ │ │ ├── icon.rs │ │ └── main.rs │ ├── modal/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── main.rs │ ├── multi_window/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── main.rs │ ├── multitouch/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── main.rs │ ├── pane_grid/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src/ │ │ └── main.rs │ ├── pick_list/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src/ │ │ └── main.rs │ ├── pokedex/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src/ │ │ └── main.rs │ ├── progress_bar/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src/ │ │ └── main.rs │ ├── qr_code/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src/ │ │ └── main.rs │ ├── sandpiles/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── main.rs │ ├── screenshot/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── main.rs │ ├── scrollable/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src/ │ │ └── main.rs │ ├── sierpinski_triangle/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src/ │ │ └── main.rs │ ├── slider/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src/ │ │ └── main.rs │ ├── solar_system/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src/ │ │ └── main.rs │ ├── stopwatch/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src/ │ │ └── main.rs │ ├── styling/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── snapshots/ │ │ │ ├── catppuccin_frappé-tiny-skia.sha256 │ │ │ ├── catppuccin_latte-tiny-skia.sha256 │ │ │ ├── catppuccin_macchiato-tiny-skia.sha256 │ │ │ ├── catppuccin_mocha-tiny-skia.sha256 │ │ │ ├── dark-tiny-skia.sha256 │ │ │ ├── dracula-tiny-skia.sha256 │ │ │ ├── ferra-tiny-skia.sha256 │ │ │ ├── gruvbox_dark-tiny-skia.sha256 │ │ │ ├── gruvbox_light-tiny-skia.sha256 │ │ │ ├── kanagawa_dragon-tiny-skia.sha256 │ │ │ ├── kanagawa_lotus-tiny-skia.sha256 │ │ │ ├── kanagawa_wave-tiny-skia.sha256 │ │ │ ├── light-tiny-skia.sha256 │ │ │ ├── moonfly-tiny-skia.sha256 │ │ │ ├── nightfly-tiny-skia.sha256 │ │ │ ├── nord-tiny-skia.sha256 │ │ │ ├── oxocarbon-tiny-skia.sha256 │ │ │ ├── solarized_dark-tiny-skia.sha256 │ │ │ ├── solarized_light-tiny-skia.sha256 │ │ │ ├── tokyo_night-tiny-skia.sha256 │ │ │ ├── tokyo_night_light-tiny-skia.sha256 │ │ │ └── tokyo_night_storm-tiny-skia.sha256 │ │ └── src/ │ │ └── main.rs │ ├── svg/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src/ │ │ └── main.rs │ ├── system_information/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── main.rs │ ├── table/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── main.rs │ ├── text/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── main.rs │ ├── the_matrix/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── main.rs │ ├── toast/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── main.rs │ ├── todos/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── iced-todos.desktop │ │ ├── index.html │ │ ├── snapshots/ │ │ │ └── creates_a_new_task-tiny-skia.sha256 │ │ ├── src/ │ │ │ └── main.rs │ │ └── tests/ │ │ └── carl_sagan.ice │ ├── tooltip/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src/ │ │ └── main.rs │ ├── tour/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── index.html │ │ └── src/ │ │ └── main.rs │ ├── url_handler/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── main.rs │ ├── vectorial_text/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── main.rs │ └── websocket/ │ ├── Cargo.toml │ ├── README.md │ └── src/ │ ├── echo/ │ │ └── server.rs │ ├── echo.rs │ └── main.rs ├── futures/ │ ├── Cargo.toml │ └── src/ │ ├── backend/ │ │ ├── default.rs │ │ ├── native/ │ │ │ ├── smol.rs │ │ │ ├── thread_pool.rs │ │ │ └── tokio.rs │ │ ├── native.rs │ │ ├── null.rs │ │ ├── wasm/ │ │ │ └── wasm_bindgen.rs │ │ └── wasm.rs │ ├── backend.rs │ ├── event.rs │ ├── executor.rs │ ├── keyboard.rs │ ├── lib.rs │ ├── maybe.rs │ ├── runtime.rs │ ├── stream.rs │ ├── subscription/ │ │ └── tracker.rs │ └── subscription.rs ├── graphics/ │ ├── Cargo.toml │ └── src/ │ ├── antialiasing.rs │ ├── cache.rs │ ├── color.rs │ ├── compositor.rs │ ├── damage.rs │ ├── error.rs │ ├── geometry/ │ │ ├── cache.rs │ │ ├── fill.rs │ │ ├── frame.rs │ │ ├── path/ │ │ │ ├── arc.rs │ │ │ └── builder.rs │ │ ├── path.rs │ │ ├── stroke.rs │ │ ├── style.rs │ │ └── text.rs │ ├── geometry.rs │ ├── gradient.rs │ ├── image/ │ │ └── storage.rs │ ├── image.rs │ ├── layer.rs │ ├── lib.rs │ ├── mesh.rs │ ├── shell.rs │ ├── text/ │ │ ├── cache.rs │ │ ├── editor.rs │ │ └── paragraph.rs │ ├── text.rs │ └── viewport.rs ├── highlighter/ │ ├── Cargo.toml │ └── src/ │ └── lib.rs ├── program/ │ ├── Cargo.toml │ └── src/ │ ├── lib.rs │ ├── message.rs │ └── preset.rs ├── renderer/ │ ├── Cargo.toml │ └── src/ │ ├── fallback.rs │ └── lib.rs ├── runtime/ │ ├── Cargo.toml │ ├── README.md │ └── src/ │ ├── clipboard.rs │ ├── font.rs │ ├── image.rs │ ├── keyboard.rs │ ├── lib.rs │ ├── system.rs │ ├── task.rs │ ├── user_interface.rs │ ├── widget/ │ │ ├── operation.rs │ │ └── selector.rs │ ├── widget.rs │ └── window.rs ├── selector/ │ ├── Cargo.toml │ └── src/ │ ├── find.rs │ ├── lib.rs │ └── target.rs ├── src/ │ ├── advanced.rs │ ├── application/ │ │ └── timed.rs │ ├── application.rs │ ├── daemon.rs │ ├── error.rs │ ├── lib.rs │ ├── time.rs │ ├── touch.rs │ ├── window/ │ │ └── icon.rs │ └── window.rs ├── test/ │ ├── Cargo.toml │ └── src/ │ ├── emulator.rs │ ├── error.rs │ ├── ice.rs │ ├── instruction.rs │ ├── lib.rs │ └── simulator.rs ├── tester/ │ ├── Cargo.toml │ ├── fonts/ │ │ └── iced_tester-icons.toml │ └── src/ │ ├── icon.rs │ ├── lib.rs │ └── recorder.rs ├── tiny_skia/ │ ├── Cargo.toml │ └── src/ │ ├── engine.rs │ ├── geometry.rs │ ├── layer.rs │ ├── lib.rs │ ├── primitive.rs │ ├── raster.rs │ ├── text.rs │ ├── vector.rs │ ├── window/ │ │ └── compositor.rs │ └── window.rs ├── wgpu/ │ ├── Cargo.toml │ ├── README.md │ └── src/ │ ├── buffer.rs │ ├── color.rs │ ├── engine.rs │ ├── geometry.rs │ ├── image/ │ │ ├── atlas/ │ │ │ ├── allocation.rs │ │ │ ├── allocator.rs │ │ │ ├── entry.rs │ │ │ └── layer.rs │ │ ├── atlas.rs │ │ ├── cache.rs │ │ ├── mod.rs │ │ ├── null.rs │ │ ├── raster.rs │ │ └── vector.rs │ ├── layer.rs │ ├── lib.rs │ ├── primitive.rs │ ├── quad/ │ │ ├── gradient.rs │ │ └── solid.rs │ ├── quad.rs │ ├── shader/ │ │ ├── blit.wgsl │ │ ├── color/ │ │ │ └── linear_rgb.wgsl │ │ ├── color.wgsl │ │ ├── image.wgsl │ │ ├── quad/ │ │ │ ├── gradient.wgsl │ │ │ └── solid.wgsl │ │ ├── quad.wgsl │ │ ├── triangle/ │ │ │ ├── gradient.wgsl │ │ │ └── solid.wgsl │ │ ├── triangle.wgsl │ │ └── vertex.wgsl │ ├── text.rs │ ├── triangle/ │ │ └── msaa.rs │ ├── triangle.rs │ ├── window/ │ │ └── compositor.rs │ └── window.rs ├── widget/ │ ├── Cargo.toml │ └── src/ │ ├── action.rs │ ├── button.rs │ ├── canvas/ │ │ └── program.rs │ ├── canvas.rs │ ├── checkbox.rs │ ├── column.rs │ ├── combo_box.rs │ ├── container.rs │ ├── float.rs │ ├── grid.rs │ ├── helpers.rs │ ├── image/ │ │ └── viewer.rs │ ├── image.rs │ ├── keyed/ │ │ └── column.rs │ ├── keyed.rs │ ├── lazy/ │ │ ├── cache.rs │ │ ├── component.rs │ │ └── helpers.rs │ ├── lazy.rs │ ├── lib.rs │ ├── markdown.rs │ ├── mouse_area.rs │ ├── overlay/ │ │ └── menu.rs │ ├── overlay.rs │ ├── pane_grid/ │ │ ├── axis.rs │ │ ├── configuration.rs │ │ ├── content.rs │ │ ├── controls.rs │ │ ├── direction.rs │ │ ├── draggable.rs │ │ ├── node.rs │ │ ├── pane.rs │ │ ├── split.rs │ │ ├── state.rs │ │ └── title_bar.rs │ ├── pane_grid.rs │ ├── pick_list.rs │ ├── pin.rs │ ├── progress_bar.rs │ ├── qr_code.rs │ ├── radio.rs │ ├── responsive.rs │ ├── row.rs │ ├── rule.rs │ ├── scrollable.rs │ ├── sensor.rs │ ├── shader/ │ │ └── program.rs │ ├── shader.rs │ ├── slider.rs │ ├── space.rs │ ├── stack.rs │ ├── svg.rs │ ├── table.rs │ ├── text/ │ │ └── rich.rs │ ├── text.rs │ ├── text_editor.rs │ ├── text_input/ │ │ ├── cursor.rs │ │ ├── editor.rs │ │ └── value.rs │ ├── text_input.rs │ ├── themer.rs │ ├── toggler.rs │ ├── tooltip.rs │ └── vertical_slider.rs └── winit/ ├── Cargo.toml ├── README.md └── src/ ├── clipboard.rs ├── conversion.rs ├── error.rs ├── lib.rs ├── proxy.rs ├── window/ │ └── state.rs └── window.rs ================================================ FILE CONTENTS ================================================ ================================================ FILE: .cargo/config.toml ================================================ [alias] lint = "clippy --workspace --benches --all-features --no-deps -- -D warnings" lint-fix = "clippy --fix --allow-dirty --workspace --benches --all-features --no-deps -- -D warnings" ================================================ FILE: .github/FUNDING.yml ================================================ github: hecrj ko_fi: hecrj_ ================================================ FILE: .github/ISSUE_TEMPLATE/BUG-REPORT.yml ================================================ name: I have a problem with the library description: File a bug report. labels: ["bug"] body: - type: markdown attributes: value: | Thanks for taking the time to fill out this bug report! - type: checkboxes attributes: label: Is your issue REALLY a bug? description: | This issue tracker is for __BUG REPORTS ONLY__. It's obvious, right? This is a bug report form, after all! Still, some crazy users seem to forcefully fill out this form just to ask questions and request features. The core team does not appreciate that. Don't do it. If you want to ask a question or request a feature, please [go back here](https://github.com/iced-rs/iced/issues/new/choose) and read carefully. options: - label: My issue is indeed a bug! required: true - label: I am not crazy! I will not fill out this form just to ask a question or request a feature. Pinky promise. required: true - type: checkboxes attributes: label: Is there an existing issue for this? description: | Please, search [the existing issues] and see if an issue already exists for the bug you encountered. [the existing issues]: https://github.com/iced-rs/iced/issues options: - label: I have searched the existing issues. required: true - type: checkboxes attributes: label: Is this issue related to iced? description: | If your application is crashing during startup or you are observing graphical glitches, there is a chance it may be caused by incompatible hardware or outdated graphics drivers. Before filing an issue... - If you are using `wgpu`, you need an environment that supports Vulkan, Metal, or DirectX 12. Please, make sure you can run [the `wgpu` examples]. If you have any issues running any of the examples, make sure your graphics drivers are up-to-date. If the issues persist, please report them to the authors of the libraries directly! [the `wgpu` examples]: https://github.com/gfx-rs/wgpu/tree/trunk/examples [the `glow` examples]: https://github.com/grovesNL/glow/tree/main/examples options: - label: My hardware is compatible and my graphics drivers are up-to-date. required: true - type: textarea attributes: label: What happened? id: what-happened description: | What problem are you having? Please, also provide the steps to reproduce it. If the issue happens with a particular program, please share an [SSCCE]. [SSCCE]: http://sscce.org/ validations: required: true - type: textarea attributes: label: What is the expected behavior? id: what-expected description: What were you expecting to happen? validations: required: true - type: dropdown id: version attributes: label: Version description: | We only offer support for the latest release on crates.io and the `master` branch on this repository. Which version are you using? Please make sure you are using the latest patch available (e.g. run `cargo update`). If you are using an older release, please upgrade to the latest one before filing an issue. options: - crates.io release - master validations: required: true - type: dropdown id: os attributes: label: Operating System description: Which operating system are you using? options: - Windows - macOS - Linux validations: required: true - type: textarea id: logs attributes: label: Do you have any log output? description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. render: shell ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: false contact_links: - name: I have a question url: https://iced.zulipchat.com/#narrow/channel/213445-questions about: Ask and learn from others in the Zulip forum. - name: I want to request a feature or start a discussion url: https://iced.zulipchat.com/#narrow/channel/213316-discussions about: Share your idea and gather feedback in the Zulip forum. - name: I want to chat with other users of the library url: https://discord.com/invite/3xZJ65GAhd about: Join the Discord server and get involved with the community! ================================================ FILE: .github/PULL_REQUEST_TEMPLATE.md ================================================ The core team is busy and does not have time to mentor nor babysit new contributors. If a member of the core team thinks that reviewing and understanding your work will take more time and effort than writing it from scratch by themselves, your contribution will be dismissed. It is your responsibility to communicate and figure out how to reduce the likelihood of this! Read the contributing guidelines for more details: https://github.com/iced-rs/iced/blob/master/CONTRIBUTING.md ================================================ FILE: .github/workflows/audit.yml ================================================ name: Audit on: push: {} pull_request: {} schedule: - cron: '0 0 * * *' jobs: vulnerabilities: runs-on: ubuntu-latest steps: - uses: hecrj/setup-rust-action@v2 - name: Install cargo-audit run: cargo install cargo-audit - uses: actions/checkout@master - name: Resolve dependencies run: cargo update - name: Audit vulnerabilities run: cargo audit # artifacts: # runs-on: ubuntu-latest # steps: # - uses: hecrj/setup-rust-action@v2 # - name: Install cargo-outdated # run: cargo install cargo-outdated # - uses: actions/checkout@master # - name: Delete `web-sys` dependency from `integration` example # run: sed -i '$d' examples/integration/Cargo.toml # - name: Find outdated dependencies # run: cargo outdated --workspace --exit-code 1 --ignore raw-window-handle ================================================ FILE: .github/workflows/build.yml ================================================ name: Build on: push: branches: - master jobs: todos_linux: runs-on: ubuntu-latest steps: - uses: hecrj/setup-rust-action@v2 - name: Install cargo-deb run: cargo install cargo-deb - uses: actions/checkout@master - name: Install dependencies run: | export DEBIAN_FRONTED=noninteractive sudo apt-get -qq update sudo apt-get install -y libxkbcommon-dev - name: Build todos binary run: cargo build --verbose --profile release-opt --package todos - name: Archive todos binary uses: actions/upload-artifact@v4 with: name: todos-x86_64-unknown-linux-gnu path: target/release-opt/todos - name: Pack todos .deb package run: cargo deb --no-build --profile release-opt --package todos - name: Rename todos .deb package run: mv target/debian/*.deb target/debian/iced_todos-x86_64-debian-linux-gnu.deb - name: Archive todos .deb package uses: actions/upload-artifact@v4 with: name: todos-x86_64-debian-linux-gnu path: target/debian/iced_todos-x86_64-debian-linux-gnu.deb todos_windows: runs-on: windows-latest steps: - uses: hecrj/setup-rust-action@v2 - uses: actions/checkout@master - name: Enable static CRT linkage run: | echo '[target.x86_64-pc-windows-msvc]' >> .cargo/config echo 'rustflags = ["-Ctarget-feature=+crt-static"]' >> .cargo/config - name: Run the application without starting the shell run: | sed -i '1 i\#![windows_subsystem = \"windows\"]' examples/todos/src/main.rs - name: Build todos binary run: cargo build --verbose --profile release-opt --package todos - name: Archive todos binary uses: actions/upload-artifact@v4 with: name: todos-x86_64-pc-windows-msvc path: target/release-opt/todos.exe todos_macos: runs-on: macOS-latest steps: - uses: hecrj/setup-rust-action@v2 - uses: actions/checkout@master - name: Build todos binary env: MACOSX_DEPLOYMENT_TARGET: 10.14 run: cargo build --verbose --profile release-opt --package todos - name: Open binary via double-click run: chmod +x target/release-opt/todos - name: Archive todos binary uses: actions/upload-artifact@v4 with: name: todos-x86_64-apple-darwin path: target/release-opt/todos todos_raspberry: runs-on: ubuntu-latest steps: - uses: hecrj/setup-rust-action@v2 - uses: actions/checkout@master - name: Install cross run: cargo install cross - name: Build todos binary for Raspberry Pi 3/4 (64 bits) run: cross build --verbose --profile release-opt --package todos --target aarch64-unknown-linux-gnu - name: Archive todos binary uses: actions/upload-artifact@v4 with: name: todos-aarch64-unknown-linux-gnu path: target/aarch64-unknown-linux-gnu/release-opt/todos - name: Build todos binary for Raspberry Pi 2/3/4 (32 bits) run: cross build --verbose --profile release-opt --package todos --target armv7-unknown-linux-gnueabihf - name: Archive todos binary uses: actions/upload-artifact@v4 with: name: todos-armv7-unknown-linux-gnueabihf path: target/armv7-unknown-linux-gnueabihf/release-opt/todos ================================================ FILE: .github/workflows/check.yml ================================================ name: Check on: [push, pull_request] jobs: wasm: runs-on: ubuntu-latest env: RUSTFLAGS: --cfg=web_sys_unstable_apis steps: - uses: hecrj/setup-rust-action@v2 with: rust-version: stable targets: wasm32-unknown-unknown - uses: actions/checkout@master - name: Run checks run: cargo check --package iced --target wasm32-unknown-unknown - name: Check compilation of `tour` example run: cargo build --package tour --target wasm32-unknown-unknown - name: Check compilation of `todos` example run: cargo build --package todos --target wasm32-unknown-unknown widget: runs-on: ubuntu-latest steps: - uses: hecrj/setup-rust-action@v2 - uses: actions/checkout@master - name: Check standalone `iced_widget` crate run: cargo check --package iced_widget --features image,svg,canvas ================================================ FILE: .github/workflows/document.yml ================================================ name: Document on: [push, pull_request] jobs: all: runs-on: ubuntu-latest concurrency: group: ${{ github.workflow }}-${{ github.ref }} steps: - uses: hecrj/setup-rust-action@v2 with: rust-version: nightly-2026-02-20 - uses: actions/checkout@v2 - name: Generate documentation run: | RUSTDOCFLAGS="--cfg docsrs" \ cargo doc --no-deps --all-features \ -p futures-core \ -p iced_beacon \ -p iced_core \ -p iced_debug \ -p iced_devtools \ -p iced_futures \ -p iced_graphics \ -p iced_highlighter \ -p iced_renderer \ -p iced_runtime \ -p iced_tiny_skia \ -p iced_wgpu \ -p iced_widget \ -p iced_winit \ -p iced_test \ -p iced - name: Write CNAME file run: echo 'docs.iced.rs' > ./target/doc/CNAME - name: Copy redirect file as index.html run: cp docs/redirect.html target/doc/index.html - name: Publish documentation if: github.ref == 'refs/heads/master' uses: peaceiris/actions-gh-pages@v3 with: deploy_key: ${{ secrets.DOCS_DEPLOY_KEY }} external_repository: iced-rs/docs publish_dir: ./target/doc ================================================ FILE: .github/workflows/format.yml ================================================ name: Format on: [push, pull_request] jobs: all: runs-on: ubuntu-latest steps: - uses: hecrj/setup-rust-action@v2 with: components: rustfmt - uses: actions/checkout@master - name: Check format run: cargo fmt --all -- --check --verbose ================================================ FILE: .github/workflows/lint.yml ================================================ name: Lint on: [push, pull_request] jobs: all: runs-on: ubuntu-latest steps: - uses: hecrj/setup-rust-action@v2 with: components: clippy - uses: actions/checkout@master - name: Install dependencies run: | export DEBIAN_FRONTED=noninteractive sudo apt-get -qq update sudo apt-get install -y libxkbcommon-dev libgtk-3-dev - name: Check lints run: cargo lint ================================================ FILE: .github/workflows/test.yml ================================================ name: Test on: [push, pull_request] jobs: all: runs-on: ${{ matrix.os }} env: RUSTFLAGS: --deny warnings ICED_TEST_BACKEND: tiny-skia strategy: matrix: os: [ubuntu-latest, windows-latest, macOS-latest] rust: [stable, beta, "1.92"] steps: - uses: hecrj/setup-rust-action@v2 with: rust-version: ${{ matrix.rust }} - uses: actions/checkout@master - name: Install dependencies if: matrix.os == 'ubuntu-latest' run: | export DEBIAN_FRONTED=noninteractive sudo apt-get -qq update sudo apt-get install -y libxkbcommon-dev libgtk-3-dev - name: Run tests run: | cargo test --verbose --workspace cargo test --verbose --workspace -- --ignored cargo test --verbose --workspace --all-features ================================================ FILE: .gitignore ================================================ target/ pkg/ **/*.rs.bk dist/ traces/ ================================================ FILE: CHANGELOG.md ================================================ # Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] ## [0.14.0] - 2025-12-07 ### Added - Reactive rendering. [#2662](https://github.com/iced-rs/iced/pull/2662) - Time travel debugging. [#2910](https://github.com/iced-rs/iced/pull/2910) - `Animation` API for application code. [#2757](https://github.com/iced-rs/iced/pull/2757) - Headless mode testing. [#2698](https://github.com/iced-rs/iced/pull/2698) - First-class end-to-end testing. [#3059](https://github.com/iced-rs/iced/pull/3059) - Input method support. [#2777](https://github.com/iced-rs/iced/pull/2777) - Hot reloading. [#3000](https://github.com/iced-rs/iced/pull/3000) - Concurrent image decoding and uploading (and more cool stuff). [#3092](https://github.com/iced-rs/iced/pull/3092) - `comet` debugger and `devtools` foundations. [#2879](https://github.com/iced-rs/iced/pull/2879) - Presentation metrics for `comet`. [#2881](https://github.com/iced-rs/iced/pull/2881) - Custom performance metrics for `comet`. [#2891](https://github.com/iced-rs/iced/pull/2891) - Smart scrollbars. [#2922](https://github.com/iced-rs/iced/pull/2922) - System theme reactions. [#3051](https://github.com/iced-rs/iced/pull/3051) - `table` widget. [#3018](https://github.com/iced-rs/iced/pull/3018) - `grid` widget. [#2885](https://github.com/iced-rs/iced/pull/2885) - `sensor` widget. [#2751](https://github.com/iced-rs/iced/pull/2751) - `float` widget and other cool stuff. [#2916](https://github.com/iced-rs/iced/pull/2916) - `pin` widget. [#2673](https://github.com/iced-rs/iced/pull/2673) - `wrap` method for `column` widget. [#2884](https://github.com/iced-rs/iced/pull/2884) - `auto_scroll` support for `scrollable` widget. [#2973](https://github.com/iced-rs/iced/pull/2973) - `delay` support for `tooltip` widget. [#2960](https://github.com/iced-rs/iced/pull/2960) - `Auto` strategy to `text::Shaping`. [#3048](https://github.com/iced-rs/iced/pull/3048) - Incremental `markdown` parsing. [#2776](https://github.com/iced-rs/iced/pull/2776) - Customizable markdown rendering and image support. [#2786](https://github.com/iced-rs/iced/pull/2786) - Quote support for `markdown` widget. [#3005](https://github.com/iced-rs/iced/pull/3005) - Tasklist support for `markdown` widget. [#3022](https://github.com/iced-rs/iced/pull/3022) - `crisp` feature for default quad snapping. [#2969](https://github.com/iced-rs/iced/pull/2969) - Basic layer merging for `graphics::layer::Stack`. [#3033](https://github.com/iced-rs/iced/pull/3033) - Headless mode for `iced_wgpu` and concurrency foundations. [#2857](https://github.com/iced-rs/iced/pull/2857) - Primitive culling in `column` and `row` widgets. [#2611](https://github.com/iced-rs/iced/pull/2611) - Lazy `Compositor` initialization in `winit` shell. [#2722](https://github.com/iced-rs/iced/pull/2722) - Support for `Justified` text alignment. [#2836](https://github.com/iced-rs/iced/pull/2836) - Support for double click event to `mouse_area`. [#2602](https://github.com/iced-rs/iced/pull/2602) - `Default` implementation for `iced_wgpu::geometry::Cache`. [#2619](https://github.com/iced-rs/iced/pull/2619) - `physical_key` field to `KeyReleased` event. [#2608](https://github.com/iced-rs/iced/pull/2608) - `total_size` method for `qr_code` widget. [#2606](https://github.com/iced-rs/iced/pull/2606) - `PartialEq` implementations for widget styles. [#2637](https://github.com/iced-rs/iced/pull/2637) - `Send` marker to `iced_wgpu::Renderer` by using `Arc` in caches. [#2692](https://github.com/iced-rs/iced/pull/2692) - Disabled `Status` for `scrollbar` widget. [#2585](https://github.com/iced-rs/iced/pull/2585) - `warning` color to `theme::Palette`. [#2607](https://github.com/iced-rs/iced/pull/2607) - `maximized` and `fullscreen` fields to `window::Settings`. [#2627](https://github.com/iced-rs/iced/pull/2627) - `window` tasks for controlling sizes and resize increments. [#2633](https://github.com/iced-rs/iced/pull/2633) - `window` task for drag resizing. [#2642](https://github.com/iced-rs/iced/pull/2642) - Helper functions for alignment to `widget` module. [#2746](https://github.com/iced-rs/iced/pull/2746) - `time::repeat` subscription. [#2747](https://github.com/iced-rs/iced/pull/2747) - Vertical support for `progress_bar`. [#2748](https://github.com/iced-rs/iced/pull/2748) - `scale` support for `image` widget. [#2755](https://github.com/iced-rs/iced/pull/2755) - `LineEnding` support for `text_editor`. [#2759](https://github.com/iced-rs/iced/pull/2759) - `Mul` implementation for `mouse::Cursor` and `mouse::Click`. [#2758](https://github.com/iced-rs/iced/pull/2758) - `animation` module support for Wasm target. [#2764](https://github.com/iced-rs/iced/pull/2764) - Flake for a dev shell in `DEPENDENCIES`. [#2769](https://github.com/iced-rs/iced/pull/2769) - `unfocus` widget operation. [#2804](https://github.com/iced-rs/iced/pull/2804) - `sipper` support and some QoL. [#2805](https://github.com/iced-rs/iced/pull/2805) - Variable text size for preedit IME window. [#2790](https://github.com/iced-rs/iced/pull/2790) - `is_focused` widget operation. [#2812](https://github.com/iced-rs/iced/pull/2812) - Notification of `window` pre-presentation to windowing system. [#2849](https://github.com/iced-rs/iced/pull/2849) - Customizable vertical `spacing` for wrapped rows. [#2852](https://github.com/iced-rs/iced/pull/2852) - Indent and unindent actions for `text_editor`. [#2901](https://github.com/iced-rs/iced/pull/2901) - Floating Images. [#2903](https://github.com/iced-rs/iced/pull/2903) - `min_size` method to `PaneGrid`. [#2911](https://github.com/iced-rs/iced/pull/2911) - Generic key for `sensor` widget. [#2944](https://github.com/iced-rs/iced/pull/2944) - `Debug` implementation for `Task`. [#2955](https://github.com/iced-rs/iced/pull/2955) - `draw_with_bounds` method to `canvas::Cache`. [#3035](https://github.com/iced-rs/iced/pull/3035) - Synchronous `Task` Execution and `RedrawRequested` Consistency. [#3084](https://github.com/iced-rs/iced/pull/3084) - `id` method to `text_editor`. [#2653](https://github.com/iced-rs/iced/pull/2653) - `horizontal` and `vertical` methods to `Padding`. [#2655](https://github.com/iced-rs/iced/pull/2655) - `is_focused` selector and `find` / `find_all` operations. [#2664](https://github.com/iced-rs/iced/pull/2664) - `push` and `into_options` methods to `combo_box::State`. [#2684](https://github.com/iced-rs/iced/pull/2684) - `Hidden` variant to `mouse::Interaction`. [#2685](https://github.com/iced-rs/iced/pull/2685) - `menu_height` method to `pick_list` and `combo_box` widgets. [#2699](https://github.com/iced-rs/iced/pull/2699) - `text_color` to `toggler::Style`. [#2707](https://github.com/iced-rs/iced/pull/2707) - `text_shaping` method to `combo_box` widget. [#2714](https://github.com/iced-rs/iced/pull/2714) - `transparent` field for `window::Settings`. [#2728](https://github.com/iced-rs/iced/pull/2728) - `closeable` and `minimizable` fields to `window::Settings`. [#2735](https://github.com/iced-rs/iced/pull/2735) - `window::monitor_size` task. [#2754](https://github.com/iced-rs/iced/pull/2754) - Division operation for `Size` and `Vector`. [#2767](https://github.com/iced-rs/iced/pull/2767) - `hidden` method to `scrollable` widget. [#2775](https://github.com/iced-rs/iced/pull/2775) - Support for macOS-specific key shortcuts with `Control` modifier. [#2801](https://github.com/iced-rs/iced/pull/2801) - Additional variants to `mouse::Interaction`. [#2815](https://github.com/iced-rs/iced/pull/2815) - `vsync` field to `window::Settings`. [#2837](https://github.com/iced-rs/iced/pull/2837) - `wgpu-bare` feature flag to disable default `wgpu` features. [#2828](https://github.com/iced-rs/iced/pull/2828) - `ratio` method for `Size`. [#2861](https://github.com/iced-rs/iced/pull/2861) - Support for `⌘ + Backspace` and `⌘ + Delete` macOS shortcuts. [#2862](https://github.com/iced-rs/iced/pull/2862) - Expandable selection-by-word after double click in text editors. [#2865](https://github.com/iced-rs/iced/pull/2865) - `x11` and `wayland` feature flags. [#2869](https://github.com/iced-rs/iced/pull/2869) - `label` method for `checkbox` widget. [#2873](https://github.com/iced-rs/iced/pull/2873) - `shader::Pipeline` trait for easier `wgpu` resource management. [#2876](https://github.com/iced-rs/iced/pull/2876) - `select_range` widget operation. [#2890](https://github.com/iced-rs/iced/pull/2890) - `grid!` macro helper. [#2904](https://github.com/iced-rs/iced/pull/2904) - `warning` style for `container` widget. [#2912](https://github.com/iced-rs/iced/pull/2912) - Current toggle state to `toggler::Status::Disabled`. [#2908](https://github.com/iced-rs/iced/pull/2908) - Cursor size awareness for input methods. [#2918](https://github.com/iced-rs/iced/pull/2918) - `allow_automatic_tabbing` task to `runtime::window`. [#2933](https://github.com/iced-rs/iced/pull/2933) - `FromStr` and `Display` implementations for `Color`. [#2937](https://github.com/iced-rs/iced/pull/2937) - `text::Renderer` trait in `iced_graphics` with `fill_raw` method. [#2958](https://github.com/iced-rs/iced/pull/2958) - `font_maybe` helper for `text` widget. [#2988](https://github.com/iced-rs/iced/pull/2988) - `filter_map` method to `Subscription`. [#2981](https://github.com/iced-rs/iced/pull/2981) - `repeat` field to `keyboard::Event::KeyPressed`. [#2991](https://github.com/iced-rs/iced/pull/2991) - Additional settings to control the fonts used for `markdown` rendering. [#2999](https://github.com/iced-rs/iced/pull/2999) - `Rescaled` variant to `window::Event`. [#3001](https://github.com/iced-rs/iced/pull/3001) - Environment variable to define `beacon` server listen address. [#3003](https://github.com/iced-rs/iced/pull/3003) - `push_under` method to `stack` widget. [#3010](https://github.com/iced-rs/iced/pull/3010) - `NONE` constant to `keyboard::Modifiers`. [#3037](https://github.com/iced-rs/iced/pull/3037) - `shadow` field to `overlay::menu::Style`. [#3049](https://github.com/iced-rs/iced/pull/3049) - `draw_mesh_cache` method in `mesh::Renderer` trait. [#3070](https://github.com/iced-rs/iced/pull/3070) - Efficient `is_empty` method for `text_editor::Content`. [#3117](https://github.com/iced-rs/iced/pull/3117) - `*Assign` implementations for `Point` and `Vector`. [#3131](https://github.com/iced-rs/iced/pull/3131) - Support `Background` instead of `Color` styling for `scrollable`. [#3127](https://github.com/iced-rs/iced/pull/3127) - `CornerPreference` window setting for Windows. [#3128](https://github.com/iced-rs/iced/pull/3128) - `move_to` method for `Editor` API. [#3125](https://github.com/iced-rs/iced/pull/3125) - `Background` and `padding_ratio` support for `toggler` styling. [#3129](https://github.com/iced-rs/iced/pull/3129) - More syntaxes for `iced_highlighter`. [#2822](https://github.com/iced-rs/iced/pull/2822) - Implement `Sub` for `Cursor`. [#3137](https://github.com/iced-rs/iced/pull/3137) ### Changed - Replace `Rc` with `Arc` for `markdown` caching. [#2599](https://github.com/iced-rs/iced/pull/2599) - Improved `button::Catalog` and `Style` documentation. [#2590](https://github.com/iced-rs/iced/pull/2590) - Improved `clock` example to display ticks and numbers. [#2644](https://github.com/iced-rs/iced/pull/2644) - Derived `PartialEq` and `Eq` for `mouse::click::Kind`. [#2741](https://github.com/iced-rs/iced/pull/2741) - Marked `Color::from_rgb8` and `Color::from_rgba8` as const. [#2749](https://github.com/iced-rs/iced/pull/2749) - Replaced unmaintained `directories-next` crate with `directories`. [#2761](https://github.com/iced-rs/iced/pull/2761) - Changed `Widget::update` to take `Event` by reference. [#2781](https://github.com/iced-rs/iced/pull/2781) - Improved `gallery` example with blurhash previews. [#2796](https://github.com/iced-rs/iced/pull/2796) - Replaced `wasm-timer` with `wasmtimer`. [#2780](https://github.com/iced-rs/iced/pull/2780) - Tweaked `Palette` Generation. [#2811](https://github.com/iced-rs/iced/pull/2811) - Relaxed `Task::perform` bound from `Fn` to `FnOnce`. [#2827](https://github.com/iced-rs/iced/pull/2827) - Improved `quad` shader to use a single SDF in `iced_wgpu`. [#2967](https://github.com/iced-rs/iced/pull/2967) - Leveraged `Limits::min` directly in `scrollable::layout`. [#3004](https://github.com/iced-rs/iced/pull/3004) - Overhauled `theme::Palette` generation by leveraging `Oklch`. [#3028](https://github.com/iced-rs/iced/pull/3028) - Mutable `Widget` Methods. [#3038](https://github.com/iced-rs/iced/pull/3038) - Prioritized `Shrink` over `Fill` in `layout` logic. [#3045](https://github.com/iced-rs/iced/pull/3045) - Replaced `format!` with `concat!` for string literals. [#2695](https://github.com/iced-rs/iced/pull/2695) - Replaced `window::run_with_handle` with a more powerful `window::run`. [#2718](https://github.com/iced-rs/iced/pull/2718) - Made color helpers in `palette` module public. [#2771](https://github.com/iced-rs/iced/pull/2771) - Changed default `PowerPreference` to `HighPerformance` in `iced_wgpu`. [#2813](https://github.com/iced-rs/iced/pull/2813) - Made `button::DEFAULT_PADDING` public. [#2858](https://github.com/iced-rs/iced/pull/2858) - Replaced `Url` parsing in `markdown` widget with `String` URIs. [#2992](https://github.com/iced-rs/iced/pull/2992) - Improved alignment docs of `container`. [#2871](https://github.com/iced-rs/iced/pull/2871) - Made `input_method` module public. [#2897](https://github.com/iced-rs/iced/pull/2897) - `iced` logo to built-in icons font. [#2902](https://github.com/iced-rs/iced/pull/2902) - Made `Layout::children` return an `ExactSizeIterator`. [#2915](https://github.com/iced-rs/iced/pull/2915) - Enabled `fancy-regex` instead of `onig` for `syntect`. [#2932](https://github.com/iced-rs/iced/pull/2932) - Added `warning` status to `toast` example. [#2936](https://github.com/iced-rs/iced/pull/2936) - Improved `scroll_to` and `snap_to` to allow operating on a single axis. [#2994](https://github.com/iced-rs/iced/pull/2994) - Disabled `png-format` feature from `iced_tiny_skia`. [#3043](https://github.com/iced-rs/iced/pull/3043) - Unified `keyboard` subscriptions into a single `listen` subscription. [#3135](https://github.com/iced-rs/iced/pull/3135) - Updated to Rust 2024. [#2809](https://github.com/iced-rs/iced/pull/2809) - Updated `wgpu` to `22.0`. [#2510](https://github.com/iced-rs/iced/pull/2510) - Updated `wgpu` to `23.0`. [#2663](https://github.com/iced-rs/iced/pull/2663) - Updated `wgpu` to `24.0`. [#2832](https://github.com/iced-rs/iced/pull/2832) - Updated `wgpu` to `26.0`. [#3019](https://github.com/iced-rs/iced/pull/3019) - Updated `wgpu` to `27.0`. [#3097](https://github.com/iced-rs/iced/pull/3097) - Updated `image` to `0.25`. [#2716](https://github.com/iced-rs/iced/pull/2716) - Updated `cosmic-text` to `0.13`. [#2834](https://github.com/iced-rs/iced/pull/2834) - Updated `cosmic-text` to `0.14`. [#2880](https://github.com/iced-rs/iced/pull/2880) - Updated `cosmic-text` to `0.15`. [#3098](https://github.com/iced-rs/iced/pull/3098) - Updated `resvg` to `0.45`. [#2846](https://github.com/iced-rs/iced/pull/2846) - Updated `wasmtimer` to `0.4.2`. [#3012](https://github.com/iced-rs/iced/pull/3012) - Updated `dark-light` to `2.0`. [#2724](https://github.com/iced-rs/iced/pull/2724) - Updated `openssl` to `0.10.70`. [#2783](https://github.com/iced-rs/iced/pull/2783) - Updated our `winit` fork with `0.30.8` fixes. [#2737](https://github.com/iced-rs/iced/pull/2737) ### Fixed - Slow `wgpu` documentation. [#2593](https://github.com/iced-rs/iced/pull/2593) - Documentation for `open_events`. [#2594](https://github.com/iced-rs/iced/pull/2594) - Layout for wrapped `row` with `spacing`. [#2596](https://github.com/iced-rs/iced/pull/2596) - Flex layout of `Fill` elements in a `Shrink` cross axis. [#2598](https://github.com/iced-rs/iced/pull/2598) - Incorrect triangle mesh counting in `wgpu`. [#2601](https://github.com/iced-rs/iced/pull/2601) - Dropped images and meshes when pasting `Frame`. [#2605](https://github.com/iced-rs/iced/pull/2605) - `loading_spinners` example skipping part of the animation cycle. [#2617](https://github.com/iced-rs/iced/pull/2617) - Window `File*` events not marked as unsupported for Wayland. [#2615](https://github.com/iced-rs/iced/pull/2615) - Coupling of `markdown::view` iterator lifetime with resulting `Element`. [#2623](https://github.com/iced-rs/iced/pull/2623) - Delete key not working in `text_editor` widget. [#2632](https://github.com/iced-rs/iced/pull/2632) - Consecutive clicks triggering independently of distance. [#2639](https://github.com/iced-rs/iced/pull/2639) - `pane_grid` losing continuity when adding or removing panes. [#2628](https://github.com/iced-rs/iced/pull/2628) - Synthetic keyboard events not being discarded. [#2649](https://github.com/iced-rs/iced/pull/2649) - `sort_by` without total ordering in `tiny-skia` damage tracking. [#2651](https://github.com/iced-rs/iced/pull/2651) - Outdated docs of `Scrollable::with_direction` and `direction`. [#2668](https://github.com/iced-rs/iced/pull/2668) - `button` calling its `on_press` handler unnecessarily. [#2683](https://github.com/iced-rs/iced/pull/2683) - `system_information` example getting stuck at boot. [#2681](https://github.com/iced-rs/iced/pull/2681) - `tooltip` widget not redrawing when hovered. [#2675](https://github.com/iced-rs/iced/pull/2675) - `pane_grid::DragEvent::Canceled` not emitted within deadband. [#2691](https://github.com/iced-rs/iced/pull/2691) - Inconsistent positions in window-related operations. [#2688](https://github.com/iced-rs/iced/pull/2688) - `text::Wrapping` not being applied to `Paragraph`. [#2723](https://github.com/iced-rs/iced/pull/2723) - Broken nested `markdown` lists without empty line. [#2641](https://github.com/iced-rs/iced/pull/2641) - Unnecessary cast in `the_matrix` example. [#2731](https://github.com/iced-rs/iced/pull/2731) - Incorrect layer counting in `iced_wgpu`. [#2701](https://github.com/iced-rs/iced/pull/2701) - `Image` not respecting `viewport` bounds. [#2752](https://github.com/iced-rs/iced/pull/2752) - Attempting to draw empty meshes in `iced_wgpu`. [#2782](https://github.com/iced-rs/iced/pull/2782) - Input placeholder text not clearing when IME is activated. [#2785](https://github.com/iced-rs/iced/pull/2785) - Missing redraw request in `image::Viewer`. [#2795](https://github.com/iced-rs/iced/pull/2795) - Wrong position of preedit text on scrolled content. [#2798](https://github.com/iced-rs/iced/pull/2798) - Wrong initial candidate position for IME. [#2793](https://github.com/iced-rs/iced/pull/2793) - Text spans in IME preedit not being properly cached. [#2806](https://github.com/iced-rs/iced/pull/2806) - `cpu_brand` in `system_information` always being empty. [#2797](https://github.com/iced-rs/iced/pull/2797) - Horizontal text alignment being ignored on multi-line text. [#2835](https://github.com/iced-rs/iced/pull/2835) - Missing redraw request in `mouse_area` when hovered. [#2845](https://github.com/iced-rs/iced/pull/2845) - `futures-executor` being pulled even when it's not the default executor. [#2841](https://github.com/iced-rs/iced/pull/2841) - WebGPU failing to boot in Chromium. [#2686](https://github.com/iced-rs/iced/pull/2686) - Crash when using WebGL due to wrong binding alignment. [#2883](https://github.com/iced-rs/iced/pull/2883) - Wrong calculation of rows in `grid` widget when evenly distributed. [#2896](https://github.com/iced-rs/iced/pull/2896) - Panic in `combo_box` due to cleared children during `diff`. [#2905](https://github.com/iced-rs/iced/pull/2905) - OpenGL backend in `wgpu` interpreting atlas texture as cube map instead of texture array. [#2919](https://github.com/iced-rs/iced/pull/2919) - `quad` shader blending without pre-multiplication. [#2925](https://github.com/iced-rs/iced/pull/2925) - Inconsistent primitive pixel snapping in `iced_wgpu`. [#2962](https://github.com/iced-rs/iced/pull/2962) - Inconsistent `Rectangle::is_within` implementation. [#2966](https://github.com/iced-rs/iced/pull/2966) - Text damage calculation in `iced_tiny_skia`. [#2964](https://github.com/iced-rs/iced/pull/2964) - Leftover `title` mention in documentation. [#2972](https://github.com/iced-rs/iced/pull/2972) - Text bounds cutoff in `iced_wgpu`. [#2975](https://github.com/iced-rs/iced/pull/2975) - Rectangle vertices not being snapped to the pixel grid independently. [#2768](https://github.com/iced-rs/iced/pull/2768) - Lints for Rust 1.89. [#3030](https://github.com/iced-rs/iced/pull/3030) - `debug` builds on macOS Tahoe. [#3056](https://github.com/iced-rs/iced/pull/3056) - Typo in documentation comment for `filter_map`. [#3052](https://github.com/iced-rs/iced/pull/3052) - `container::Style` not respecting `crisp` feature. [#3112](https://github.com/iced-rs/iced/pull/3112) - Incorrect padding in `text_editor`. [#3115](https://github.com/iced-rs/iced/pull/3115) - Outdated documentation of `Widget::mouse_interaction`. [#2696](https://github.com/iced-rs/iced/pull/2696) - Incorrect render pass viewport in `custom_shader` example. [#2738](https://github.com/iced-rs/iced/pull/2738) - Capturing `ButtonReleased` event inside `image::Viewer`. [#2744](https://github.com/iced-rs/iced/pull/2744) - Incomplete docs for `on_link_click` in `rich_text`. [#2803](https://github.com/iced-rs/iced/pull/2803) - Stale syntax highlighting on `text_editor` after theme changes. [#2818](https://github.com/iced-rs/iced/pull/2818) - Wrong background color for `window::Preedit` on translucent themes. [#2819](https://github.com/iced-rs/iced/pull/2819) - Panic on Chromium-like browsers when canvas initial size is `(0, 0)`. [#2829](https://github.com/iced-rs/iced/pull/2829) - Outdated dev shell templates. [#2840](https://github.com/iced-rs/iced/pull/2840) - Missing `derive` feature for `serde` dependency. [#2854](https://github.com/iced-rs/iced/pull/2854) - `bezier_tool` listed as an example in the `Widget` trait docs. [#2867](https://github.com/iced-rs/iced/pull/2867) - Incomplete doc comment of `Length::is_fill`. [#2892](https://github.com/iced-rs/iced/pull/2892) - `scrollable` touch scrolling when out of bounds. [#2906](https://github.com/iced-rs/iced/pull/2906) - `Element::explain` being hidden by multi-layer widgets. [#2913](https://github.com/iced-rs/iced/pull/2913) - Missing `Shell::request_redraw` on `component`. [#2930](https://github.com/iced-rs/iced/pull/2930) - Text clipping in `iced_tiny_skia`. [#2929](https://github.com/iced-rs/iced/pull/2929) - Inconsistent naming of `tree` parameter in `Widget` trait. [#2950](https://github.com/iced-rs/iced/pull/2950) - `text_editor` syntax highlighting not updating on paste. [#2947](https://github.com/iced-rs/iced/pull/2947) - `svg` scaling in `iced_tiny_skia`. [#2954](https://github.com/iced-rs/iced/pull/2954) - Stroke bounds calculation and clip transformations in `iced_tiny_skia`. [#2882](https://github.com/iced-rs/iced/pull/2882) - Artifacts when drawing small arcs in `canvas` widget. [#2959](https://github.com/iced-rs/iced/pull/2959) - Path not being closed in `Path::circle`. [#2979](https://github.com/iced-rs/iced/pull/2979) - Incorrect transformation of cached primitives in `iced_tiny_skia`. [#2977](https://github.com/iced-rs/iced/pull/2977) - Panic when drawing empty image in `iced_tiny_skia`. [#2986](https://github.com/iced-rs/iced/pull/2986) - Incorrect mapping of navigation keys on higher keyboard layers. [#3007](https://github.com/iced-rs/iced/pull/3007) - `Status` of `svg` widget not being updated on cursor movement. [#3009](https://github.com/iced-rs/iced/pull/3009) - `hover` widget ignoring events in certain conditions. [#3015](https://github.com/iced-rs/iced/pull/3015) - OpenGL backend in `iced_wgpu` choosing wrong texture format in `wgpu::image::atlas`. [#3016](https://github.com/iced-rs/iced/pull/3016) - Missing redraw request in `geometry` example. [#3020](https://github.com/iced-rs/iced/pull/3020) - Buffer presentation logic in `iced_tiny_skia`. [#3032](https://github.com/iced-rs/iced/pull/3032) - `combo_box` text not getting cleared on selection. [#3063](https://github.com/iced-rs/iced/pull/3063) - `wgpu` surface not being reconfigured on `SurfaceError::Lost` or `Outdated`. [#3067](https://github.com/iced-rs/iced/pull/3067) - Incorrect cursor for `slider` widget on Windows . [#3068](https://github.com/iced-rs/iced/pull/3068) - `Paragraph::hit_span` returning false positives at end of content. [#3072](https://github.com/iced-rs/iced/pull/3072) - Incorrect `Limits::loose` documentation. [#3116](https://github.com/iced-rs/iced/pull/3116) - Missing semicolon triggering a `clippy` lint. [#3118](https://github.com/iced-rs/iced/pull/3118) - `iced_tiny_skia` using a `Window` instead of a `Display` handle for `softbuffer::Context` creation. [#3090](https://github.com/iced-rs/iced/pull/3090) - Missing `fn operate` in `tooltip` widget. [#3132](https://github.com/iced-rs/iced/pull/3132) - Panic when rendering problematic `svg`. [#3123](https://github.com/iced-rs/iced/pull/3123) - Hotkey combinations not working on non-latin keyboard layouts. [#3134](https://github.com/iced-rs/iced/pull/3134) - `keyboard::listen` reporting captured key events. [#3136](https://github.com/iced-rs/iced/pull/3136) ### Removed - `is_over` method in `Overlay` trait. [#2921](https://github.com/iced-rs/iced/pull/2921) - Short-hand notation support for `color!` macro. [#2592](https://github.com/iced-rs/iced/pull/2592) - `surface` argument of `Compositor::screenshot`. [#2672](https://github.com/iced-rs/iced/pull/2672) - `once_cell` dependency. [#2626](https://github.com/iced-rs/iced/pull/2626) - `winapi` dependency. [#2760](https://github.com/iced-rs/iced/pull/2760) - `palette` dependency. [#2839](https://github.com/iced-rs/iced/pull/2839) Many thanks to... - @edwloef - @rhysd - @DKolter - @pml68 - @andymandias - @dtzxporter - @tarkah - @tvolk131 - @alex-ds13 - @B0ney - @bbb651 - @JL710 - @kenz-gelsoft - @mfreeborn - @mtkennerly - @watsaig - @13r0ck - @airstrike - @bungoboingo - @EmmanuelDodoo - @karolisr - @Remmirad - @semiversus - @Ultrasquid9 - @xosxos - @Zarthus - @7h0ma5 - @7sDream - @Adam-Ladd - @AMS21 - @Atreyagaurav - @AustinEvansWX - @Azorlogh - @berserkware - @biglizards - @boondocklabs - @bradysimon - @camspiers - @chrismanning - @codewing - @csmoe - @davehorner - @DavidAguilo - @dcz-self - @dejang - @dependabot[bot] - @EleDiaz - @ellieplayswow - @Exidex - @Fili-pk - @flakes - @Gobbel2000 - @GyulyVGC - @hammerlink - @hydra - @ibaryshnikov - @ids1024 - @iMohmmedSA - @Integral-Tech - @inthehack - @jakobhellermann - @janTatesa - @jbirnick - @jcdickinson - @Jinderamarak - @jsatka - @kbjr - @kgday - @kiedtl - @Konsl - @Koranir - @kosayoda - @Krahos - @l-const - @l4l - @laycookie - @leo030303 - @Leonie-Theobald - @libkurisu - @lmaxyz - @mariinkys - @max-privatevoid - @MichelleGranat - @misaka10987 - @mytdragon - @njust - @nrjais - @nz366 - @OpenSauce - @Ottatop - @Redhawk18 - @rhogenson - @rizzen-yazston - @rotmh - @Rudxain - @ryco117 - @Seppel3210 - @sgued - @sopvop - @T-256 - @tafia - @thorn132 - @tigerros - @tsuza - @vincenthz - @will-lynas ## [0.13.1] - 2024-09-19 ### Added - Some `From` trait implementations for `text_input::Id`. [#2582](https://github.com/iced-rs/iced/pull/2582) - Custom `Executor` support for `Application` and `Daemon`. [#2580](https://github.com/iced-rs/iced/pull/2580) - `rust-version` metadata to `Cargo.toml`. [#2579](https://github.com/iced-rs/iced/pull/2579) - Widget examples to API reference. [#2587](https://github.com/iced-rs/iced/pull/2587) ### Fixed - Inverted scrolling direction with trackpad in `scrollable`. [#2583](https://github.com/iced-rs/iced/pull/2583) - `scrollable` transactions when `on_scroll` is not set. [#2584](https://github.com/iced-rs/iced/pull/2584) - Incorrect text color styling in `text_editor` widget. [#2586](https://github.com/iced-rs/iced/pull/2586) Many thanks to... - @dcampbell24 - @lufte - @mtkennerly ## [0.13.0] - 2024-09-18 ### Added - Introductory chapters to the [official guide book](https://book.iced.rs/). - [Pocket guide](https://docs.rs/iced/0.13.0/iced/#the-pocket-guide) in API reference. - `Program` API. [#2331](https://github.com/iced-rs/iced/pull/2331) - `Task` API. [#2463](https://github.com/iced-rs/iced/pull/2463) - `Daemon` API and Shell Runtime Unification. [#2469](https://github.com/iced-rs/iced/pull/2469) - `rich_text` and `markdown` widgets. [#2508](https://github.com/iced-rs/iced/pull/2508) - `stack` widget. [#2405](https://github.com/iced-rs/iced/pull/2405) - `hover` widget. [#2408](https://github.com/iced-rs/iced/pull/2408) - `row::Wrapping` widget. [#2539](https://github.com/iced-rs/iced/pull/2539) - `text` macro helper. [#2338](https://github.com/iced-rs/iced/pull/2338) - `text::Wrapping` support. [#2279](https://github.com/iced-rs/iced/pull/2279) - Functional widget styling. [#2312](https://github.com/iced-rs/iced/pull/2312) - Closure-based widget styling. [#2326](https://github.com/iced-rs/iced/pull/2326) - Class-based Theming. [#2350](https://github.com/iced-rs/iced/pull/2350) - Type-Driven Renderer Fallback. [#2351](https://github.com/iced-rs/iced/pull/2351) - Background styling to `rich_text` widget. [#2516](https://github.com/iced-rs/iced/pull/2516) - Underline support for `rich_text`. [#2526](https://github.com/iced-rs/iced/pull/2526) - Strikethrough support for `rich_text`. [#2528](https://github.com/iced-rs/iced/pull/2528) - Abortable `Task`. [#2496](https://github.com/iced-rs/iced/pull/2496) - `abort_on_drop` to `task::Handle`. [#2503](https://github.com/iced-rs/iced/pull/2503) - `Ferra` theme. [#2329](https://github.com/iced-rs/iced/pull/2329) - `auto-detect-theme` feature. [#2343](https://github.com/iced-rs/iced/pull/2343) - Custom key binding support for `text_editor`. [#2522](https://github.com/iced-rs/iced/pull/2522) - `align_x` for `text_input` widget. [#2535](https://github.com/iced-rs/iced/pull/2535) - `center` widget helper. [#2423](https://github.com/iced-rs/iced/pull/2423) - Rotation support for `image` and `svg` widgets. [#2334](https://github.com/iced-rs/iced/pull/2334) - Dynamic `opacity` support for `image` and `svg`. [#2424](https://github.com/iced-rs/iced/pull/2424) - Scroll transactions for `scrollable` widget. [#2401](https://github.com/iced-rs/iced/pull/2401) - `physical_key` and `modified_key` to `keyboard::Event`. [#2576](https://github.com/iced-rs/iced/pull/2576) - `fetch_position` command in `window` module. [#2280](https://github.com/iced-rs/iced/pull/2280) - `filter_method` property for `image::Viewer` widget. [#2324](https://github.com/iced-rs/iced/pull/2324) - Support for pre-multiplied alpha `wgpu` composite mode. [#2341](https://github.com/iced-rs/iced/pull/2341) - `text_size` and `line_height` properties for `text_editor` widget. [#2358](https://github.com/iced-rs/iced/pull/2358) - `is_focused` method for `text_editor::State`. [#2386](https://github.com/iced-rs/iced/pull/2386) - `canvas::Cache` Grouping. [#2415](https://github.com/iced-rs/iced/pull/2415) - `ICED_PRESENT_MODE` env var to pick a `wgpu::PresentMode`. [#2428](https://github.com/iced-rs/iced/pull/2428) - `SpecificWith` variant to `window::Position`. [#2435](https://github.com/iced-rs/iced/pull/2435) - `scale_factor` field to `window::Screenshot`. [#2449](https://github.com/iced-rs/iced/pull/2449) - Styling support for `overlay::Menu` of `pick_list` widget. [#2457](https://github.com/iced-rs/iced/pull/2457) - `window::Id` in `Event` subscriptions. [#2456](https://github.com/iced-rs/iced/pull/2456) - `FromIterator` implementation for `row` and `column`. [#2460](https://github.com/iced-rs/iced/pull/2460) - `content_fit` for `image::viewer` widget. [#2330](https://github.com/iced-rs/iced/pull/2330) - `Display` implementation for `Radians`. [#2446](https://github.com/iced-rs/iced/pull/2446) - Helper methods for `window::Settings` in `Application`. [#2470](https://github.com/iced-rs/iced/pull/2470) - `Copy` implementation for `canvas::Fill` and `canvas::Stroke`. [#2475](https://github.com/iced-rs/iced/pull/2475) - Clarification of `Border` alignment for `Quad`. [#2485](https://github.com/iced-rs/iced/pull/2485) - "Select All" functionality on `Ctrl+A` to `text_editor`. [#2321](https://github.com/iced-rs/iced/pull/2321) - `stream::try_channel` helper. [#2497](https://github.com/iced-rs/iced/pull/2497) - `iced` widget helper to display the iced logo :comet:. [#2498](https://github.com/iced-rs/iced/pull/2498) - `align_x` and `align_y` helpers to `scrollable`. [#2499](https://github.com/iced-rs/iced/pull/2499) - Built-in text styles for each `Palette` color. [#2500](https://github.com/iced-rs/iced/pull/2500) - Embedded `Scrollbar` support for `scrollable`. [#2269](https://github.com/iced-rs/iced/pull/2269) - `on_press_with` method for `button`. [#2502](https://github.com/iced-rs/iced/pull/2502) - `resize_events` subscription to `window` module. [#2505](https://github.com/iced-rs/iced/pull/2505) - `Link` support to `rich_text` widget. [#2512](https://github.com/iced-rs/iced/pull/2512) - `image` and `svg` support for `canvas` widget. [#2537](https://github.com/iced-rs/iced/pull/2537) - `Compact` variant for `pane_grid::Controls`. [#2555](https://github.com/iced-rs/iced/pull/2555) - `image-without-codecs` feature flag. [#2244](https://github.com/iced-rs/iced/pull/2244) - `container::background` styling helper. [#2261](https://github.com/iced-rs/iced/pull/2261) - `undecorated_shadow` window setting for Windows. [#2285](https://github.com/iced-rs/iced/pull/2285) - Tasks for setting mouse passthrough. [#2284](https://github.com/iced-rs/iced/pull/2284) - `*_maybe` helpers for `text_input` widget. [#2390](https://github.com/iced-rs/iced/pull/2390) - Wasm support for `download_progress` example. [#2419](https://github.com/iced-rs/iced/pull/2419) - `scrollable::scroll_by` widget operation. [#2436](https://github.com/iced-rs/iced/pull/2436) - Enhancements to `slider` widget styling. [#2444](https://github.com/iced-rs/iced/pull/2444) - `on_scroll` handler to `mouse_area` widget. [#2450](https://github.com/iced-rs/iced/pull/2450) - `stroke_rectangle` method to `canvas::Frame`. [#2473](https://github.com/iced-rs/iced/pull/2473) - `override_redirect` setting for X11 windows. [#2476](https://github.com/iced-rs/iced/pull/2476) - Disabled state support for `toggler` widget. [#2478](https://github.com/iced-rs/iced/pull/2478) - `Color::parse` helper for parsing color strings. [#2486](https://github.com/iced-rs/iced/pull/2486) - `rounded_rectangle` method to `canvas::Path`. [#2491](https://github.com/iced-rs/iced/pull/2491) - `width` method to `text_editor` widget. [#2513](https://github.com/iced-rs/iced/pull/2513) - `on_open` handler to `combo_box` widget. [#2534](https://github.com/iced-rs/iced/pull/2534) - Additional `mouse::Interaction` cursors. [#2551](https://github.com/iced-rs/iced/pull/2551) - Scroll wheel handling in `slider` widget. [#2565](https://github.com/iced-rs/iced/pull/2565) ### Changed - Use a `StagingBelt` in `iced_wgpu` for regular buffer uploads. [#2357](https://github.com/iced-rs/iced/pull/2357) - Use generic `Content` in `Text` to avoid reallocation in `fill_text`. [#2360](https://github.com/iced-rs/iced/pull/2360) - Use `Iterator::size_hint` to initialize `Column` and `Row` capacity. [#2362](https://github.com/iced-rs/iced/pull/2362) - Specialize `widget::text` helper. [#2363](https://github.com/iced-rs/iced/pull/2363) - Use built-in `[lints]` table in `Cargo.toml`. [#2377](https://github.com/iced-rs/iced/pull/2377) - Target `#iced` container by default on Wasm. [#2342](https://github.com/iced-rs/iced/pull/2342) - Improved architecture for `iced_wgpu` and `iced_tiny_skia`. [#2382](https://github.com/iced-rs/iced/pull/2382) - Make image `Cache` eviction strategy less aggressive in `iced_wgpu`. [#2403](https://github.com/iced-rs/iced/pull/2403) - Retain caches in `iced_wgpu` as long as `Rc` values are alive. [#2409](https://github.com/iced-rs/iced/pull/2409) - Use `bytes` crate for `image` widget. [#2356](https://github.com/iced-rs/iced/pull/2356) - Update `winit` to `0.30`. [#2427](https://github.com/iced-rs/iced/pull/2427) - Reuse `glyphon::Pipeline` state in `iced_wgpu`. [#2430](https://github.com/iced-rs/iced/pull/2430) - Ask for explicit `Length` in `center_*` methods. [#2441](https://github.com/iced-rs/iced/pull/2441) - Hide internal `Task` constructors. [#2492](https://github.com/iced-rs/iced/pull/2492) - Hide `Subscription` internals. [#2493](https://github.com/iced-rs/iced/pull/2493) - Improved `view` ergonomics. [#2504](https://github.com/iced-rs/iced/pull/2504) - Update `cosmic-text` and `resvg`. [#2416](https://github.com/iced-rs/iced/pull/2416) - Snap `Quad` lines to the pixel grid in `iced_wgpu`. [#2531](https://github.com/iced-rs/iced/pull/2531) - Update `web-sys` to `0.3.69`. [#2507](https://github.com/iced-rs/iced/pull/2507) - Allow disabled `TextInput` to still be interacted with. [#2262](https://github.com/iced-rs/iced/pull/2262) - Enable horizontal scrolling without shift modifier for `srollable` widget. [#2392](https://github.com/iced-rs/iced/pull/2392) - Add `mouse::Button` to `mouse::Click`. [#2414](https://github.com/iced-rs/iced/pull/2414) - Notify `scrollable::Viewport` changes. [#2438](https://github.com/iced-rs/iced/pull/2438) - Improved documentation of `Component` state management. [#2556](https://github.com/iced-rs/iced/pull/2556) ### Fixed - Fix `block_on` in `iced_wgpu` hanging Wasm builds. [#2313](https://github.com/iced-rs/iced/pull/2313) - Private `PaneGrid` style fields. [#2316](https://github.com/iced-rs/iced/pull/2316) - Some documentation typos. [#2317](https://github.com/iced-rs/iced/pull/2317) - Blurry input caret with non-integral scaling. [#2320](https://github.com/iced-rs/iced/pull/2320) - Scrollbar stuck in a `scrollable` under some circumstances. [#2322](https://github.com/iced-rs/iced/pull/2322) - Broken `wgpu` examples link in issue template. [#2327](https://github.com/iced-rs/iced/pull/2327) - Empty `wgpu` draw calls in `image` pipeline. [#2344](https://github.com/iced-rs/iced/pull/2344) - Layout invalidation for `Responsive` widget. [#2345](https://github.com/iced-rs/iced/pull/2345) - Incorrect shadows on quads with rounded corners. [#2354](https://github.com/iced-rs/iced/pull/2354) - Empty menu overlay on `combo_box`. [#2364](https://github.com/iced-rs/iced/pull/2364) - Copy / cut vulnerability in a secure `TextInput`. [#2366](https://github.com/iced-rs/iced/pull/2366) - Inadequate readability / contrast for built-in themes. [#2376](https://github.com/iced-rs/iced/pull/2376) - Fix `pkg-config` typo in `DEPENDENCIES.md`. [#2379](https://github.com/iced-rs/iced/pull/2379) - Unbounded memory consumption by `iced_winit::Proxy`. [#2389](https://github.com/iced-rs/iced/pull/2389) - Typo in `icon::Error` message. [#2393](https://github.com/iced-rs/iced/pull/2393) - Nested scrollables capturing all scroll events. [#2397](https://github.com/iced-rs/iced/pull/2397) - Content capturing scrollbar events in a `scrollable`. [#2406](https://github.com/iced-rs/iced/pull/2406) - Out of bounds caret and overflow when scrolling in `text_editor`. [#2407](https://github.com/iced-rs/iced/pull/2407) - Missing `derive(Default)` in overview code snippet. [#2412](https://github.com/iced-rs/iced/pull/2412) - `image::Viewer` triggering grab from outside the widget. [#2420](https://github.com/iced-rs/iced/pull/2420) - Different windows fighting over shared `image::Cache`. [#2425](https://github.com/iced-rs/iced/pull/2425) - Images not aligned to the (logical) pixel grid in `iced_wgpu`. [#2440](https://github.com/iced-rs/iced/pull/2440) - Incorrect local time in `clock` example under Unix systems. [#2421](https://github.com/iced-rs/iced/pull/2421) - `⌘ + ←` and `⌘ + →` behavior for `text_input` on macOS. [#2315](https://github.com/iced-rs/iced/pull/2315) - Wayland packages in `DEPENDENCIES.md`. [#2465](https://github.com/iced-rs/iced/pull/2465) - Typo in documentation. [#2487](https://github.com/iced-rs/iced/pull/2487) - Extraneous comment in `scrollable` module. [#2488](https://github.com/iced-rs/iced/pull/2488) - Top layer in `hover` widget hiding when focused. [#2544](https://github.com/iced-rs/iced/pull/2544) - Out of bounds text in `text_editor` widget. [#2536](https://github.com/iced-rs/iced/pull/2536) - Segfault on Wayland when closing the app. [#2547](https://github.com/iced-rs/iced/pull/2547) - `lazy` feature flag sometimes not present in documentation. [#2289](https://github.com/iced-rs/iced/pull/2289) - Border of `progress_bar` widget being rendered below the active bar. [#2443](https://github.com/iced-rs/iced/pull/2443) - `radii` typo in `iced_wgpu` shader. [#2484](https://github.com/iced-rs/iced/pull/2484) - Incorrect priority of `Binding::Delete` in `text_editor`. [#2514](https://github.com/iced-rs/iced/pull/2514) - Division by zero in `multitouch` example. [#2517](https://github.com/iced-rs/iced/pull/2517) - Invisible text in `svg` widget. [#2560](https://github.com/iced-rs/iced/pull/2560) - `wasm32` deployments not displaying anything. [#2574](https://github.com/iced-rs/iced/pull/2574) - Unnecessary COM initialization on Windows. [#2578](https://github.com/iced-rs/iced/pull/2578) ### Removed - Unnecessary struct from `download_progress` example. [#2380](https://github.com/iced-rs/iced/pull/2380) - Out of date comment from `custom_widget` example. [#2549](https://github.com/iced-rs/iced/pull/2549) - `Clone` bound for `graphics::Cache::clear`. [#2575](https://github.com/iced-rs/iced/pull/2575) Many thanks to... - @Aaron-McGuire - @airstrike - @alex-ds13 - @alliby - @Andrew-Schwartz - @ayeniswe - @B0ney - @Bajix - @blazra - @Brady-Simon - @breynard0 - @bungoboingo - @casperstorm - @Davidster - @derezzedex - @DKolter - @dtoniolo - @dtzxporter - @fenhl - @Gigas002 - @gintsgints - @henrispriet - @IsaacMarovitz - @ivanceras - @Jinderamarak - @JL710 - @jquesada2016 - @JustSoup312 - @kiedtl - @kmoon2437 - @Koranir - @lufte - @LuisLiraC - @m4rch3n1ng - @meithecatte - @mtkennerly - @myuujiku - @n1ght-hunter - @nrjais - @PgBiel - @PolyMeilex - @rustrover - @ryankopf - @saihaze - @shartrec - @skygrango - @SolidStateDj - @sundaram123krishnan - @tarkah - @vladh - @WailAbou - @wiiznokes - @woelfman - @Zaubentrucker ## [0.12.1] - 2024-02-22 ### Added - `extend` and `from_vec` methods for `Column` and `Row`. [#2264](https://github.com/iced-rs/iced/pull/2264) - `PartialOrd`, `Ord`, and `Hash` implementations for `keyboard::Modifiers`. [#2270](https://github.com/iced-rs/iced/pull/2270) - `clipboard` module in `advanced` module. [#2272](https://github.com/iced-rs/iced/pull/2272) - Default `disabled` style for `checkbox` and `hovered` style for `Svg`. [#2273](https://github.com/iced-rs/iced/pull/2273) - `From` and `From` implementations for `border::Radius`. [#2274](https://github.com/iced-rs/iced/pull/2274) - `size_hint` method for `Component` trait. [#2275](https://github.com/iced-rs/iced/pull/2275) ### Fixed - Black images when using OpenGL backend in `iced_wgpu`. [#2259](https://github.com/iced-rs/iced/pull/2259) - Documentation for `horizontal_space` and `vertical_space` helpers. [#2265](https://github.com/iced-rs/iced/pull/2265) - WebAssembly platform. [#2271](https://github.com/iced-rs/iced/pull/2271) - Decouple `Key` from `keyboard::Modifiers` and apply them to `text` in `KeyboardInput`. [#2238](https://github.com/iced-rs/iced/pull/2238) - Text insertion not being prioritized in `TextInput` and `TextEditor`. [#2278](https://github.com/iced-rs/iced/pull/2278) - `iced_tiny_skia` clipping line strokes. [#2282](https://github.com/iced-rs/iced/pull/2282) Many thanks to... - @PolyMeilex - @rizzen-yazston - @wash2 ## [0.12.0] - 2024-02-15 ### Added - Multi-window support. [#1964](https://github.com/iced-rs/iced/pull/1964) - `TextEditor` widget (or multi-line text input). [#2123](https://github.com/iced-rs/iced/pull/2123) - `Shader` widget. [#2085](https://github.com/iced-rs/iced/pull/2085) - Shadows. [#1882](https://github.com/iced-rs/iced/pull/1882) - Vectorial text for `Canvas`. [#2204](https://github.com/iced-rs/iced/pull/2204) - Layout consistency. [#2192](https://github.com/iced-rs/iced/pull/2192) - Explicit text caching. [#2058](https://github.com/iced-rs/iced/pull/2058) - Gradients in Oklab color space. [#2055](https://github.com/iced-rs/iced/pull/2055) - `Themer` widget. [#2209](https://github.com/iced-rs/iced/pull/2209) - `Transform` primitive. [#2120](https://github.com/iced-rs/iced/pull/2120) - Cut functionality for `TextEditor`. [#2215](https://github.com/iced-rs/iced/pull/2215) - Primary clipboard support. [#2240](https://github.com/iced-rs/iced/pull/2240) - Disabled state for `Checkbox`. [#2109](https://github.com/iced-rs/iced/pull/2109) - `skip_taskbar` window setting for Windows. [#2211](https://github.com/iced-rs/iced/pull/2211) - `fetch_maximized` and `fetch_minimized` commands in `window`. [#2189](https://github.com/iced-rs/iced/pull/2189) - `run_with_handle` command in `window`. [#2200](https://github.com/iced-rs/iced/pull/2200) - `show_system_menu` command in `window`. [#2243](https://github.com/iced-rs/iced/pull/2243) - `text_shaping` method for `Tooltip`. [#2172](https://github.com/iced-rs/iced/pull/2172) - `interaction` method for `MouseArea`. [#2207](https://github.com/iced-rs/iced/pull/2207) - `hovered` styling for `Svg` widget. [#2163](https://github.com/iced-rs/iced/pull/2163) - `height` method for `TextEditor`. [#2221](https://github.com/iced-rs/iced/pull/2221) - Customizable style for `TextEditor`. [#2159](https://github.com/iced-rs/iced/pull/2159) - Customizable style for `QRCode`. [#2229](https://github.com/iced-rs/iced/pull/2229) - Border width styling for `Toggler`. [#2219](https://github.com/iced-rs/iced/pull/2219) - `RawText` variant for `Primitive` in `iced_graphics`. [#2158](https://github.com/iced-rs/iced/pull/2158) - `Stream` support for `Command`. [#2150](https://github.com/iced-rs/iced/pull/2150) - Access to bounds/content bounds from a `Scrollable` viewport. [#2072](https://github.com/iced-rs/iced/pull/2072) - `Frame::scale_nonuniform` method. [#2070](https://github.com/iced-rs/iced/pull/2070) - `theme::Custom::with_fn` to generate completely custom themes. [#2067](https://github.com/iced-rs/iced/pull/2067) - `style` attribute for `Font`. [#2041](https://github.com/iced-rs/iced/pull/2041) - Texture filtering options for `Image`. [#1894](https://github.com/iced-rs/iced/pull/1894) - `default` and `shift_step` methods for `slider` widgets. [#2100](https://github.com/iced-rs/iced/pull/2100) - `Custom` variant to `command::Action`. [#2146](https://github.com/iced-rs/iced/pull/2146) - Mouse movement events for `MouseArea`. [#2147](https://github.com/iced-rs/iced/pull/2147) - Dracula, Nord, Solarized, and Gruvbox variants for `Theme`. [#2170](https://github.com/iced-rs/iced/pull/2170) - Catppuccin, Tokyo Night, Kanagawa, Moonfly, Nightfly and Oxocarbon variants for `Theme`. [#2233](https://github.com/iced-rs/iced/pull/2233) - `From where T: Into` for `svg::Handle`. [#2235](https://github.com/iced-rs/iced/pull/2235) - `on_open` and `on_close` handlers for `PickList`. [#2174](https://github.com/iced-rs/iced/pull/2174) - Support for generic `Element` in `Tooltip`. [#2228](https://github.com/iced-rs/iced/pull/2228) - Container and `gap` styling for `Scrollable`. [#2239](https://github.com/iced-rs/iced/pull/2239) - Use `Borrow` for both `options` and `selected` in PickList. [#2251](https://github.com/iced-rs/iced/pull/2251) - `clip` property for `Container`, `Column`, `Row`, and `Button`. #[2252](https://github.com/iced-rs/iced/pull/2252) ### Changed - Enable WebGPU backend in `wgpu` by default instead of WebGL. [#2068](https://github.com/iced-rs/iced/pull/2068) - Update `glyphon` to `0.4`. [#2203](https://github.com/iced-rs/iced/pull/2203) - Require `Send` on stored pipelines. [#2197](https://github.com/iced-rs/iced/pull/2197) - Update `wgpu` to `0.19`, `glyphon` to `0.5`, `softbuffer` to `0.4`, `window-clipboard` to `0.4`, and `raw-window-handle` to `0.6`. [#2191](https://github.com/iced-rs/iced/pull/2191) - Update `winit` to `0.29`. [#2169](https://github.com/iced-rs/iced/pull/2169) - Provide actual bounds to `Shader` primitives. [#2149](https://github.com/iced-rs/iced/pull/2149) - Deny warnings in `test` workflow. [#2135](https://github.com/iced-rs/iced/pull/2135) - Update `wgpu` to `0.18` and `cosmic-text` to `0.10`. [#2122](https://github.com/iced-rs/iced/pull/2122) - Compute vertex positions in the shader. [#2099](https://github.com/iced-rs/iced/pull/2099) - Migrate twox-hash -> xxhash-rust and switch to Xxh3 for better performance. [#2080](https://github.com/iced-rs/iced/pull/2080) - Add `keyboard` subscriptions and rename `subscription::events` to `event::listen`. [#2073](https://github.com/iced-rs/iced/pull/2073) - Use workspace dependencies and package inheritance. [#2069](https://github.com/iced-rs/iced/pull/2069) - Update `wgpu` to `0.17`. [#2065](https://github.com/iced-rs/iced/pull/2065) - Support automatic style type casting for `Button`. [#2046](https://github.com/iced-rs/iced/pull/2046) - Make `with_clip` and `with_save` in `Frame` able to return the data of the provided closure. [#1994](https://github.com/iced-rs/iced/pull/1994) - Use `Radians` for angle fields in `Arc` and `arc::Elliptical`. [#2027](https://github.com/iced-rs/iced/pull/2027) - Assert dimensions of quads are normal in `iced_tiny_skia`. [#2082](https://github.com/iced-rs/iced/pull/2082) - Remove `position` from `overlay::Element`. [#2226](https://github.com/iced-rs/iced/pull/2226) - Add a capacity limit to the `GlyphCache` in `iced_tiny_skia`. [#2210](https://github.com/iced-rs/iced/pull/2210) - Use pointer equality to speed up `PartialEq` implementation of `image::Bytes`. [#2220](https://github.com/iced-rs/iced/pull/2220) - Update `bitflags`, `glam`, `kurbo`, `ouroboros`, `qrcode`, and `sysinfo` dependencies. [#2227](https://github.com/iced-rs/iced/pull/2227) - Improve some widget ergonomics. [#2253](https://github.com/iced-rs/iced/pull/2253) ### Fixed - Clipping of `TextInput` selection. [#2199](https://github.com/iced-rs/iced/pull/2199) - `Paragraph::grapheme_position` when ligatures are present. [#2196](https://github.com/iced-rs/iced/pull/2196) - Docs to include missing feature tags. [#2184](https://github.com/iced-rs/iced/pull/2184) - `PaneGrid` click interaction on the top edge. [#2168](https://github.com/iced-rs/iced/pull/2168) - `iced_wgpu` not rendering text in SVGs. [#2161](https://github.com/iced-rs/iced/pull/2161) - Text clipping. [#2154](https://github.com/iced-rs/iced/pull/2154) - Text transparency in `iced_tiny_skia`. [#2250](https://github.com/iced-rs/iced/pull/2250) - Layout invalidation when `Tooltip` changes `overlay`. [#2143](https://github.com/iced-rs/iced/pull/2143) - `Overlay` composition. [#2142](https://github.com/iced-rs/iced/pull/2142) - Incorrect GIF for the `progress_bar` example. [#2141](https://github.com/iced-rs/iced/pull/2141) - Standalone compilation of `iced_renderer` crate. [#2134](https://github.com/iced-rs/iced/pull/2134) - Maximize window button enabled when `Settings::resizable` is `false`. [#2124](https://github.com/iced-rs/iced/pull/2124) - Width of horizontal scrollbar in `Scrollable`. [#2084](https://github.com/iced-rs/iced/pull/2084) - `ComboBox` widget panic on wasm. [#2078](https://github.com/iced-rs/iced/pull/2078) - Majority of unresolved documentation links. [#2077](https://github.com/iced-rs/iced/pull/2077) - Web examples not running. [#2076](https://github.com/iced-rs/iced/pull/2076) - GIFs and video examples broken. [#2074](https://github.com/iced-rs/iced/pull/2074) - `@interpolate(flat)` not used as attribute. [#2071](https://github.com/iced-rs/iced/pull/2071) - `Checkbox` and `Toggler` hidden behind scrollbar in `styling` example. [#2062](https://github.com/iced-rs/iced/pull/2062) - Absolute `LineHeight` sometimes being `0`. [#2059](https://github.com/iced-rs/iced/pull/2059) - Paste while holding ALT. [#2006](https://github.com/iced-rs/iced/pull/2006) - `Command::perform` to return a `Command`. [#2000](https://github.com/iced-rs/iced/pull/2000) - `convert_text` not called on `Svg` trees. [#1908](https://github.com/iced-rs/iced/pull/1908) - Unused `backend.rs` file in renderer crate. [#2182](https://github.com/iced-rs/iced/pull/2182) - Some `clippy::pedantic` lints. [#2096](https://github.com/iced-rs/iced/pull/2096) - Some minor clippy fixes. [#2092](https://github.com/iced-rs/iced/pull/2092) - Clippy docs keyword quoting. [#2091](https://github.com/iced-rs/iced/pull/2091) - Clippy map transformations. [#2090](https://github.com/iced-rs/iced/pull/2090) - Inline format args for ease of reading. [#2089](https://github.com/iced-rs/iced/pull/2089) - Stuck scrolling in `Scrollable` with touch events. [#1940](https://github.com/iced-rs/iced/pull/1940) - Incorrect unit in `system::Information`. [#2223](https://github.com/iced-rs/iced/pull/2223) - `size_hint` not being called from `element::Map`. [#2224](https://github.com/iced-rs/iced/pull/2224) - `size_hint` not being called from `element::Explain`. [#2225](https://github.com/iced-rs/iced/pull/2225) - Slow touch scrolling for `TextEditor` widget. [#2140](https://github.com/iced-rs/iced/pull/2140) - `Subscription::map` using unreliable function pointer hash to identify mappers. [#2237](https://github.com/iced-rs/iced/pull/2237) - Missing feature flag docs for `time::every`. [#2188](https://github.com/iced-rs/iced/pull/2188) - Event loop not being resumed on Windows while resizing. [#2214](https://github.com/iced-rs/iced/pull/2214) - Alpha mode misconfiguration in `iced_wgpu`. [#2231](https://github.com/iced-rs/iced/pull/2231) - Outdated documentation leading to a dead link. [#2232](https://github.com/iced-rs/iced/pull/2232) Many thanks to... - @akshayr-mecha - @alec-deason - @arslee07 - @AustinMReppert - @avsaase - @blazra - @brianch - @bungoboingo - @Calastrophe - @casperstorm - @cfrenette - @clarkmoody - @Davidster - @Decodetalkers - @derezzedex - @DoomDuck - @dtzxporter - @Dworv - @fogarecious - @GyulyVGC - @hicaru - @ids1024 - @Imberflur - @jhannyj - @jhff - @jim-ec - @joshuamegnauth54 - @jpttrssn - @julianbraha - @Koranir - @lufte - @matze - @MichalLebeda - @MoSal - @MrAntix - @nicksenger - @Nisatru - @nyurik - @Remmirad - @ripytide - @snaggen - @Tahinli - @tarkah - @tzemanovic - @varbhat - @VAWVAW - @william-shere - @wyatt-herkamp ## [0.10.0] - 2023-07-28 ### Added - Text shaping, font fallback, and `iced_wgpu` overhaul. [#1697](https://github.com/iced-rs/iced/pull/1697) - Software renderer, runtime renderer fallback, and core consolidation. [#1748](https://github.com/iced-rs/iced/pull/1748) - Incremental rendering for `iced_tiny_skia`. [#1811](https://github.com/iced-rs/iced/pull/1811) - Configurable `LineHeight` support for text widgets. [#1828](https://github.com/iced-rs/iced/pull/1828) - `text::Shaping` strategy selection. [#1822](https://github.com/iced-rs/iced/pull/1822) - Subpixel glyph positioning and layout linearity. [#1921](https://github.com/iced-rs/iced/pull/1921) - Background gradients. [#1846](https://github.com/iced-rs/iced/pull/1846) - Offscreen rendering and screenshots. [#1845](https://github.com/iced-rs/iced/pull/1845) - Nested overlays. [#1719](https://github.com/iced-rs/iced/pull/1719) - Cursor availability. [#1904](https://github.com/iced-rs/iced/pull/1904) - Backend-specific primitives. [#1932](https://github.com/iced-rs/iced/pull/1932) - `ComboBox` widget. [#1954](https://github.com/iced-rs/iced/pull/1954) - `web-colors` feature flag to enable "sRGB linear" blending. [#1888](https://github.com/iced-rs/iced/pull/1888) - `PaneGrid` logic to split panes by drag & drop. [#1856](https://github.com/iced-rs/iced/pull/1856) - `PaneGrid` logic to drag & drop panes to the edges. [#1865](https://github.com/iced-rs/iced/pull/1865) - Type-safe `Scrollable` direction. [#1878](https://github.com/iced-rs/iced/pull/1878) - `Scrollable` alignment. [#1912](https://github.com/iced-rs/iced/pull/1912) - Helpers to change viewport alignment of a `Scrollable`. [#1953](https://github.com/iced-rs/iced/pull/1953) - `scroll_to` widget operation. [#1796](https://github.com/iced-rs/iced/pull/1796) - `scroll_to` helper. [#1804](https://github.com/iced-rs/iced/pull/1804) - `visible_bounds` widget operation for `Container`. [#1971](https://github.com/iced-rs/iced/pull/1971) - Command to fetch window size. [#1927](https://github.com/iced-rs/iced/pull/1927) - Conversion support from `Fn` trait to custom theme. [#1861](https://github.com/iced-rs/iced/pull/1861) - Configurable border radii on relevant widgets. [#1869](https://github.com/iced-rs/iced/pull/1869) - `border_radius` styling to `Slider` rail. [#1892](https://github.com/iced-rs/iced/pull/1892) - `application_id` in `PlatformSpecific` settings for Linux. [#1963](https://github.com/iced-rs/iced/pull/1963) - Aliased entries in `text::Cache`. [#1934](https://github.com/iced-rs/iced/pull/1934) - Text cache modes. [#1938](https://github.com/iced-rs/iced/pull/1938) - `operate` method for `program::State`. [#1913](https://github.com/iced-rs/iced/pull/1913) - `Viewport` argument to `Widget::on_event`. [#1956](https://github.com/iced-rs/iced/pull/1956) - Nix instructions to `DEPENDENCIES.md`. [#1859](https://github.com/iced-rs/iced/pull/1859) - Loading spinners example. [#1902](https://github.com/iced-rs/iced/pull/1902) - Workflow that verifies `CHANGELOG` is always up-to-date. [#1970](https://github.com/iced-rs/iced/pull/1970) - Outdated mentions of `iced_native` in `README`. [#1979](https://github.com/iced-rs/iced/pull/1979) ### Changed - Updated `wgpu` to `0.16`. [#1807](https://github.com/iced-rs/iced/pull/1807) - Updated `glam` to `0.24`. [#1840](https://github.com/iced-rs/iced/pull/1840) - Updated `winit` to `0.28`. [#1738](https://github.com/iced-rs/iced/pull/1738) - Updated `palette` to `0.7`. [#1875](https://github.com/iced-rs/iced/pull/1875) - Updated `ouroboros` to `0.17`. [#1925](https://github.com/iced-rs/iced/pull/1925) - Updated `resvg` to `0.35` and `tiny-skia` to `0.10`. [#1907](https://github.com/iced-rs/iced/pull/1907) - Changed `mouse::Button::Other` to take `u16` instead of `u8`. [#1797](https://github.com/iced-rs/iced/pull/1797) - Changed `subscription::channel` to take a `FnOnce` non-`Sync` closure. [#1917](https://github.com/iced-rs/iced/pull/1917) - Removed `Copy` requirement for text `StyleSheet::Style`. [#1814](https://github.com/iced-rs/iced/pull/1814) - Removed `min_width` of 1 from scrollbar & scroller for `Scrollable`. [#1844](https://github.com/iced-rs/iced/pull/1844) - Used `Widget::overlay` for `Tooltip`. [#1692](https://github.com/iced-rs/iced/pull/1692) ### Fixed - `Responsive` layout not invalidated when shell layout is invalidated. [#1799](https://github.com/iced-rs/iced/pull/1799) - `Responsive` layout not invalidated when size changes without a `view` call. [#1890](https://github.com/iced-rs/iced/pull/1890) - Broken link in `ROADMAP.md`. [#1815](https://github.com/iced-rs/iced/pull/1815) - `bounds` of selected option background in `Menu`. [#1831](https://github.com/iced-rs/iced/pull/1831) - Border radius logic in `iced_tiny_skia`. [#1842](https://github.com/iced-rs/iced/pull/1842) - `Svg` filtered color not premultiplied. [#1841](https://github.com/iced-rs/iced/pull/1841) - Race condition when growing an `image::Atlas`. [#1847](https://github.com/iced-rs/iced/pull/1847) - Clearing damaged surface with background color in `iced_tiny_skia`. [#1854](https://github.com/iced-rs/iced/pull/1854) - Private gradient pack logic for `iced_graphics::Gradient`. [#1871](https://github.com/iced-rs/iced/pull/1871) - Unordered quads of different background types. [#1873](https://github.com/iced-rs/iced/pull/1873) - Panic in `glyphon` when glyphs are missing. [#1883](https://github.com/iced-rs/iced/pull/1883) - Empty scissor rectangle in `iced_wgpu::triangle` pipeline. [#1893](https://github.com/iced-rs/iced/pull/1893) - `Scrollable` scrolling when mouse not over it. [#1910](https://github.com/iced-rs/iced/pull/1910) - `translation` in `layout` of `Nested` overlay. [#1924](https://github.com/iced-rs/iced/pull/1924) - Build when using vendored dependencies. [#1928](https://github.com/iced-rs/iced/pull/1928) - Minor grammar mistake. [#1931](https://github.com/iced-rs/iced/pull/1931) - Quad rendering including border only inside of the bounds. [#1843](https://github.com/iced-rs/iced/pull/1843) - Redraw requests not being forwarded for `Component` overlays. [#1949](https://github.com/iced-rs/iced/pull/1949) - Blinking input cursor when window loses focus. [#1955](https://github.com/iced-rs/iced/pull/1955) - `BorderRadius` not exposed in root crate. [#1972](https://github.com/iced-rs/iced/pull/1972) - Outdated `ROADMAP`. [#1958](https://github.com/iced-rs/iced/pull/1958) ### Patched - Keybinds to cycle `ComboBox` options. [#1991](https://github.com/iced-rs/iced/pull/1991) - `Tooltip` overlay position inside `Scrollable`. [#1978](https://github.com/iced-rs/iced/pull/1978) - `iced_wgpu` freezing on empty layers. [#1996](https://github.com/iced-rs/iced/pull/1996) - `image::Viewer` reacting to any scroll event. [#1998](https://github.com/iced-rs/iced/pull/1998) - `TextInput` pasting text when `Alt` key is pressed. [#2006](https://github.com/iced-rs/iced/pull/2006) - Broken link to old `iced_native` crate in `README`. [#2024](https://github.com/iced-rs/iced/pull/2024) - `Rectangle::contains` being non-exclusive. [#2017](https://github.com/iced-rs/iced/pull/2017) - Documentation for `Arc` and `arc::Elliptical`. [#2008](https://github.com/iced-rs/iced/pull/2008) Many thanks to... - @a1phyr - @alec-deason - @AustinMReppert - @bbb651 - @bungoboingo - @casperstorm - @clarkmoody - @Davidster - @Drakulix - @genusistimelord - @GyulyVGC - @ids1024 - @jhff - @JonathanLindsey - @kr105 - @marienz - @malramsay64 - @nicksenger - @nicoburns - @NyxAlexandra - @Redhawk18 - @RGBCube - @rs017991 - @tarkah - @thunderstorm010 - @ua-kxie - @wash2 - @wiiznokes ## [0.9.0] - 2023-04-13 ### Added - `MouseArea` widget. [#1594](https://github.com/iced-rs/iced/pull/1594) - `channel` helper in `subscription`. [#1786](https://github.com/iced-rs/iced/pull/1786) - Configurable `width` for `Scrollable`. [#1749](https://github.com/iced-rs/iced/pull/1749) - Support for disabled `TextInput`. [#1744](https://github.com/iced-rs/iced/pull/1744) - Platform-specific window settings. [#1730](https://github.com/iced-rs/iced/pull/1730) - Left and right colors for sliders. [#1643](https://github.com/iced-rs/iced/pull/1643) - Icon for `TextInput`. [#1702](https://github.com/iced-rs/iced/pull/1702) - Mouse over scrollbar flag for `scrollable::StyleSheet`. [#1669](https://github.com/iced-rs/iced/pull/1669) - Better example for `Radio`. [#1762](https://github.com/iced-rs/iced/pull/1762) ### Changed - `wgpu` has been updated to `0.15` in `iced_wgpu`. [#1789](https://github.com/iced-rs/iced/pull/1789) - `resvg` has been updated to `0.29` in `iced_graphics`. [#1733](https://github.com/iced-rs/iced/pull/1733) - `subscription::run` now takes a function pointer. [#1723](https://github.com/iced-rs/iced/pull/1723) ### Fixed - Redundant `on_scroll` messages for `Scrollable`. [#1788](https://github.com/iced-rs/iced/pull/1788) - Outdated items in `ROADMAP.md` [#1782](https://github.com/iced-rs/iced/pull/1782) - Colons in shader labels causing compilation issues in `iced_wgpu`. [#1779](https://github.com/iced-rs/iced/pull/1779) - Re-expose winit features for window servers in Linux. [#1777](https://github.com/iced-rs/iced/pull/1777) - Replacement of application node in Wasm. [#1765](https://github.com/iced-rs/iced/pull/1765) - `clippy` lints for Rust 1.68. [#1755](https://github.com/iced-rs/iced/pull/1755) - Unnecessary `Component` rebuilds. [#1754](https://github.com/iced-rs/iced/pull/1754) - Incorrect package name in checkbox example docs. [#1750](https://github.com/iced-rs/iced/pull/1750) - Fullscreen only working on primary monitor. [#1742](https://github.com/iced-rs/iced/pull/1742) - `Padding::fit` on irregular values for an axis. [#1734](https://github.com/iced-rs/iced/pull/1734) - `Debug` implementation of `Font` displaying its bytes. [#1731](https://github.com/iced-rs/iced/pull/1731) - Sliders bleeding over their rail. [#1721](https://github.com/iced-rs/iced/pull/1721) ### Removed - `Fill` variant for `Alignment`. [#1735](https://github.com/iced-rs/iced/pull/1735) Many thanks to... - @ahoneybun - @bq-wrongway - @bungoboingo - @casperstorm - @Davidster - @ElhamAryanpur - @FinnPerry - @GyulyVGC - @JungleTryne - @lupd - @mmstick - @nicksenger - @Night-Hunter-NF - @tarkah - @traxys - @Xaeroxe ## [0.8.0] - 2023-02-18 ### Added - Generic pixel units. [#1711](https://github.com/iced-rs/iced/pull/1711) - `custom` method to `widget::Operation` trait. [#1649](https://github.com/iced-rs/iced/pull/1649) - `Group` overlay. [#1655](https://github.com/iced-rs/iced/pull/1655) - Standalone `draw` helper for `image`. [#1682](https://github.com/iced-rs/iced/pull/1682) - Dynamic `pick_list::Handle`. [#1675](https://github.com/iced-rs/iced/pull/1675) - `Id` support for `Container`. [#1695](https://github.com/iced-rs/iced/pull/1695) - Custom `Checkbox` icon support. [#1707](https://github.com/iced-rs/iced/pull/1707) - `window` action to change always on top setting. [#1587](https://github.com/iced-rs/iced/pull/1587) - `window` action to fetch its unique identifier. [#1589](https://github.com/iced-rs/iced/pull/1589) ### Changed - Annotated `Command` and `Subscription` with `#[must_use]`. [#1676](https://github.com/iced-rs/iced/pull/1676) - Replaced `Fn` with `FnOnce` in `canvas::Cache::draw`. [#1694](https://github.com/iced-rs/iced/pull/1694) - Used `[default]` on enum in `game_of_life` example. [#1660](https://github.com/iced-rs/iced/pull/1660) - Made `QRCode` hide when data is empty in `qr_code` example. [#1665](https://github.com/iced-rs/iced/pull/1665) - Replaced `Cow` with `Bytes` in `image` to accept any kind of data that implements `AsRef<[u8]>`. [#1551](https://github.com/iced-rs/iced/pull/1551) ### Fixed - Blank window on application startup. [#1698](https://github.com/iced-rs/iced/pull/1698) - Off-by-one pixel error on `pick_list` width. [#1679](https://github.com/iced-rs/iced/pull/1679) - Missing `text_input` implementation in `operation::Map`. [#1678](https://github.com/iced-rs/iced/pull/1678) - Widget-driven animations for `Component`. [#1685](https://github.com/iced-rs/iced/pull/1685) - Layout translation in `overlay::Group`. [#1686](https://github.com/iced-rs/iced/pull/1686) - Missing `is_over` implementation for overlays of `iced_lazy` widgets. [#1699](https://github.com/iced-rs/iced/pull/1699) - Panic when overlay event processing removes overlay. [#1700](https://github.com/iced-rs/iced/pull/1700) - Panic when using operations with components in certain cases. [#1701](https://github.com/iced-rs/iced/pull/1701) - `TextInput` width when using padding. [#1706](https://github.com/iced-rs/iced/pull/1706) - `iced_glow` crash on some hardware. [#1703](https://github.com/iced-rs/iced/pull/1703) - Height of `overlay::Menu`. [#1714](https://github.com/iced-rs/iced/pull/1714) - Size of images in `README`. [#1659](https://github.com/iced-rs/iced/pull/1659) - New `clippy` lints. [#1681](https://github.com/iced-rs/iced/pull/1681) Many thanks to... - @13r0ck - @bungoboingo - @casperstorm - @frey - @greatest-ape - @ids1024 - @Jedsek - @nicksenger - @Night-Hunter-NF - @sdroege - @Sn-Kinos - @sushigiri - @tarkah ## [0.7.0] - 2023-01-14 ### Added - Widget-driven animations. [#1647](https://github.com/iced-rs/iced/pull/1647) - Multidirectional scrolling support for `Scrollable`. [#1550](https://github.com/iced-rs/iced/pull/1550) - `VerticalSlider` widget. [#1596](https://github.com/iced-rs/iced/pull/1596) - `Shift+Click` text selection support in `TextInput`. [#1622](https://github.com/iced-rs/iced/pull/1622) - Profiling support with the `chrome-trace` feature. [#1565](https://github.com/iced-rs/iced/pull/1565) - Customization of the handle of a `PickList`. [#1562](https://github.com/iced-rs/iced/pull/1562) - `window` action to request user attention. [#1584](https://github.com/iced-rs/iced/pull/1584) - `window` action to gain focus. [#1585](https://github.com/iced-rs/iced/pull/1585) - `window` action to toggle decorations. [#1588](https://github.com/iced-rs/iced/pull/1588) - `Copy` implementation for `gradient::Location`. [#1636](https://github.com/iced-rs/iced/pull/1636) ### Changed - Replaced `Application::should_exit` with a `window::close` action. [#1606](https://github.com/iced-rs/iced/pull/1606) - Made `focusable::Count` fields public. [#1635](https://github.com/iced-rs/iced/pull/1635) - Added `Dependency` argument to the closure of `Lazy`. [#1646](https://github.com/iced-rs/iced/pull/1646) - Switched arguments order of `Toggler::new` for consistency. [#1616](https://github.com/iced-rs/iced/pull/1616) - Switched arguments order of `Checkbox::new` for consistency. [#1633](https://github.com/iced-rs/iced/pull/1633) ### Fixed - Compilation error in `iced_glow` when the `image` feature is enabled but `svg` isn't. [#1593](https://github.com/iced-rs/iced/pull/1593) - Widget operations for `Responsive` widget. [#1615](https://github.com/iced-rs/iced/pull/1615) - Overlay placement for `Responsive`. [#1638](https://github.com/iced-rs/iced/pull/1638) - `overlay` implementation for `Lazy`. [#1644](https://github.com/iced-rs/iced/pull/1644) - Minor typo in documentation. [#1624](https://github.com/iced-rs/iced/pull/1624) - Links in documentation. [#1634](https://github.com/iced-rs/iced/pull/1634) - Missing comment in documentation. [#1648](https://github.com/iced-rs/iced/pull/1648) Many thanks to... - @13r0ck - @Araxeus - @ben-wallis - @bungoboingo - @casperstorm - @nicksenger - @Night-Hunter-NF - @rpitasky - @rs017991 - @tarkah - @wiktor-k ## [0.6.0] - 2022-12-07 ### Added - Support for non-uniform border radius for `Primitive::Quad`. [#1506](https://github.com/iced-rs/iced/pull/1506) - Operation to query the current focused widget. [#1526](https://github.com/iced-rs/iced/pull/1526) - Additional operations for `TextInput`. [#1529](https://github.com/iced-rs/iced/pull/1529) - Styling support for `Svg`. [#1578](https://github.com/iced-rs/iced/pull/1578) ### Changed - Triangle geometry using a solid color is now drawn in a single draw call. [#1538](https://github.com/iced-rs/iced/pull/1538) ### Fixed - Gradients for WebAssembly target. [#1524](https://github.com/iced-rs/iced/pull/1524) - `Overlay` layout cache not being invalidated. [#1528](https://github.com/iced-rs/iced/pull/1528) - Operations not working for `PaneGrid`. [#1533](https://github.com/iced-rs/iced/pull/1533) - Mapped `widget::Operation` always returning `Outcome::None`. [#1536](https://github.com/iced-rs/iced/pull/1536) - Padding of `TextInput` with `Length::Units` width. [#1539](https://github.com/iced-rs/iced/pull/1539) - Clipping of `Image` and `Svg` widgets in `iced_glow`. [#1557](https://github.com/iced-rs/iced/pull/1557) - Invalid links in documentation. [#1560](https://github.com/iced-rs/iced/pull/1560) - `Custom` style of `PickList` widget. [#1570](https://github.com/iced-rs/iced/pull/1570) - Scroller in `Scrollable` always being drawn. [#1574](https://github.com/iced-rs/iced/pull/1574) Many thanks to... - @bungoboingo - @l1Dan - @mmstick - @mtkennerly - @PolyMeilex - @rksm - @rs017991 - @tarkah - @wash2 ## [0.5.0] - 2022-11-10 ### Added - __[Stabilization of stateless widgets][stateless]__ (#1393) The old widget API has been completely replaced by stateless widgets (introduced in #1284). Alongside the new API, there are a bunch of new helper functions and macros for easily describing view logic (like `row!` and `column!`). - __[First-class theming][theming]__ (#1362) A complete overhaul of our styling primitives, introducing a `Theme` as a first-class concept of the library. - __[Widget operations][operations]__ (#1399) An abstraction that can be used to traverse (and operate on) the widget tree of an application in order to query or update some widget state. - __[`Lazy` widget][lazy]__ (#1400) A widget that can call some view logic lazily only when some data has changed. Thanks to @nicksenger! - __[Linear gradient support for `Canvas`][gradient]__ (#1448) The `Canvas` widget can draw linear gradients now. Thanks to @bungoboingo! - __[Touch support for `Canvas`][touch]__ (#1305) The `Canvas` widget now supports touch events. Thanks to @artursapek! - __[`Image` and `Svg` support for `iced_glow`][image]__ (#1485) Our OpenGL renderer now is capable of rendering both the `Image` and `Svg` widgets. Thanks to @ids1024! [stateless]: https://github.com/iced-rs/iced/pull/1393 [theming]: https://github.com/iced-rs/iced/pull/1362 [operations]: https://github.com/iced-rs/iced/pull/1399 [lazy]: https://github.com/iced-rs/iced/pull/1400 [gradient]: https://github.com/iced-rs/iced/pull/1448 [touch]: https://github.com/iced-rs/iced/pull/1305 [image]: https://github.com/iced-rs/iced/pull/1485 ## [0.4.2] - 2022-05-03 ### Fixed - `Padding` type not exposed in `iced`. ## [0.4.1] - 2022-05-02 ### Fixed - Version number in `README`. ## [0.4.0] - 2022-05-02 ### Added - __[Stateless widgets][stateless]__ (#1284) A brand new widget API that removes the need to keep track of internal widget state. No more `button::State` in your application! - __[`Component` trait][component]__ (#1131) A new trait to implement custom widgets with internal mutable state while using composition and [The Elm Architecture]. - __[`Responsive` widget][responsive]__ (#1193) A widget that is aware of its dimensions and can be used to easily build responsive user interfaces. - __[Experimental WebGL support][webgl]__ (#1096) Applications can now be rendered into an HTML `canvas` when targeting Wasm by leveraging the WebGL support in [`wgpu`]. Thanks to @pacmancoder and @kaimast! - __[Support for Raspberry Pis and older devices][raspberry]__ (#1160) The compatibility of our OpenGL renderer has been improved and should run on any hardware that supports OpenGL 3.0+ or OpenGL ES 2.0+. Additionally, we started maintaining [Docker images for `aarch64` and `armv7`](https://github.com/orgs/iced-rs/packages) to easily cross-compile `iced` applications and target Raspberry Pis. Thanks to @derezzedex! - __[Simpler `Renderer` APIs][renderer_apis]__ (#1110) The surface of the `Renderer` APIs of the library has been considerably reduced. Instead of a `Renderer` trait per widget, now there are only 3 traits that are reused by all the widgets. [webgl]: https://github.com/iced-rs/iced/pull/1096 [renderer_apis]: https://github.com/iced-rs/iced/pull/1110 [component]: https://github.com/iced-rs/iced/pull/1131 [raspberry]: https://github.com/iced-rs/iced/pull/1160 [responsive]: https://github.com/iced-rs/iced/pull/1193 [stateless]: https://github.com/iced-rs/iced/pull/1284 [The Elm Architecture]: https://guide.elm-lang.org/architecture/ [`wgpu`]: https://github.com/gfx-rs/wgpu ## [0.3.0] - 2021-03-31 ### Added - Touch support. [#57] [#650] (thanks to @simlay and @discordance!) - Clipboard write access for - `TextInput` widget. [#770] - `Application::update`. [#773] - `image::Viewer` widget. It allows panning and scaling of an image. [#319] (thanks to @tarkah!) - `Tooltip` widget. It annotates content with some text on mouse hover. [#465] (thanks to @yusdacra!) - Support for the [`smol`] async runtime. [#699] (thanks to @JayceFayne!) - Support for graceful exiting when using the `Application` trait. [#804] - Image format features in [`iced_wgpu`] to reduce code bloat. [#392] (thanks to @unrelentingtech!) - `Focused` and `Unfocused` variant to `window::Event`. [#701] (thanks to @cossonleo!) - `WGPU_BACKEND` environment variable to configure the internal graphics backend of `iced_wgpu`. [#789] (thanks to @Cupnfish!) ### Changed - The `TitleBar` of a `PaneGrid` now supports generic elements. [#657] (thanks to @clarkmoody!) - The `Error` type now implements `Send` and `Sync`. [#719] (thanks to @taiki-e!) - The `Style` types in `iced_style` now implement `Clone` and `Copy`. [#720] (thanks to @taiki-e!) - The following dependencies have been updated: - [`font-kit`] → `0.10` [#669] - [`glutin`] → `0.26` [#658] - [`resvg`] → `0.12` [#669] - [`tokio`] → `1.0` [#672] (thanks to @yusdacra!) - [`winit`] → `0.24` [#658] - [`wgpu`] → `0.7` [#725] (thanks to @PolyMeilex) - The following examples were improved: - `download_progress` now showcases multiple file downloads at once. [#283] (thanks to @Folyd!) - `solar_system` uses the new `rand` API. [#760] (thanks to @TriedAngle!) ### Fixed - Button events not being propagated to contents. [#668] - Incorrect overlay implementation for the `Button` widget. [#764] - `Viewport::physical_width` returning the wrong value. [#700] - Outdated documentation for the `Sandbox` trait. [#710] [#57]: https://github.com/iced-rs/iced/pull/57 [#283]: https://github.com/iced-rs/iced/pull/283 [#319]: https://github.com/iced-rs/iced/pull/319 [#392]: https://github.com/iced-rs/iced/pull/392 [#465]: https://github.com/iced-rs/iced/pull/465 [#650]: https://github.com/iced-rs/iced/pull/650 [#657]: https://github.com/iced-rs/iced/pull/657 [#658]: https://github.com/iced-rs/iced/pull/658 [#668]: https://github.com/iced-rs/iced/pull/668 [#669]: https://github.com/iced-rs/iced/pull/669 [#672]: https://github.com/iced-rs/iced/pull/672 [#699]: https://github.com/iced-rs/iced/pull/699 [#700]: https://github.com/iced-rs/iced/pull/700 [#701]: https://github.com/iced-rs/iced/pull/701 [#710]: https://github.com/iced-rs/iced/pull/710 [#719]: https://github.com/iced-rs/iced/pull/719 [#720]: https://github.com/iced-rs/iced/pull/720 [#725]: https://github.com/iced-rs/iced/pull/725 [#760]: https://github.com/iced-rs/iced/pull/760 [#764]: https://github.com/iced-rs/iced/pull/764 [#770]: https://github.com/iced-rs/iced/pull/770 [#773]: https://github.com/iced-rs/iced/pull/773 [#789]: https://github.com/iced-rs/iced/pull/789 [#804]: https://github.com/iced-rs/iced/pull/804 [`smol`]: https://github.com/smol-rs/smol [`winit`]: https://github.com/rust-windowing/winit [`glutin`]: https://github.com/rust-windowing/glutin [`font-kit`]: https://github.com/servo/font-kit ## [0.2.0] - 2020-11-26 ### Added - __[`Canvas` interactivity][canvas]__ (#325) A trait-based approach to react to mouse and keyboard interactions in [the `Canvas` widget][#193]. - __[`iced_graphics` subcrate][opengl]__ (#354) A backend-agnostic graphics subcrate that can be leveraged to build new renderers. - __[OpenGL renderer][opengl]__ (#354) An OpenGL renderer powered by [`iced_graphics`], [`glow`], and [`glutin`]. It is an alternative to the default [`wgpu`] renderer. - __[Overlay support][pick_list]__ (#444) Basic support for superpositioning interactive widgets on top of other widgets. - __[Faster event loop][view]__ (#597) The event loop now takes advantage of the data dependencies in [The Elm Architecture] and leverages the borrow checker to keep the widget tree alive between iterations, avoiding unnecessary rebuilds. - __[Event capturing][event]__ (#614) The runtime now can tell whether a widget has handled an event or not, easing [integration with existing applications]. - __[`PickList` widget][pick_list]__ (#444) A drop-down selector widget built on top of the new overlay support. - __[`QRCode` widget][qr_code]__ (#622) A widget that displays a QR code, powered by [the `qrcode` crate]. [canvas]: https://github.com/iced-rs/iced/pull/325 [opengl]: https://github.com/iced-rs/iced/pull/354 [`iced_graphics`]: https://github.com/iced-rs/iced/pull/354 [pane_grid]: https://github.com/iced-rs/iced/pull/397 [pick_list]: https://github.com/iced-rs/iced/pull/444 [error]: https://github.com/iced-rs/iced/pull/514 [view]: https://github.com/iced-rs/iced/pull/597 [event]: https://github.com/iced-rs/iced/pull/614 [color]: https://github.com/iced-rs/iced/pull/200 [qr_code]: https://github.com/iced-rs/iced/pull/622 [#193]: https://github.com/iced-rs/iced/pull/193 [`glutin`]: https://github.com/rust-windowing/glutin [`wgpu`]: https://github.com/gfx-rs/wgpu [`glow`]: https://github.com/grovesNL/glow [the `qrcode` crate]: https://docs.rs/qrcode/0.12.0/qrcode/ [integration with existing applications]: https://github.com/iced-rs/iced/pull/183 [The Elm Architecture]: https://guide.elm-lang.org/architecture/ ## [0.1.1] - 2020-04-15 ### Added - `Settings::with_flags` to easily initialize some default settings with flags. [#266] - `Default` implementation for `canvas::layer::Cache`. [#267] - `Ctrl + Del` support for `TextInput`. [#268] - Helper methods in `canvas::Path` to easily draw lines, rectangles, and circles. [#293] - `From` implementation for `canvas::Fill`. [#293] - `From` implementation for `canvas::Text`. [#293] - `From<&str>` implementation for `canvas::Text`. [#293] ### Changed - `new` method of `Radio` and `Checkbox` now take a generic `Into` for the label. [#260] - `Frame::fill` now takes a generic `Into`. [#293] - `Frame::stroke` now takes a generic `Into`. [#293] - `Frame::fill_text` now takes a generic `Into`. [#293] ### Fixed - Feature flags not being referenced in documentation. [#259] - Crash in some graphics drivers when displaying an empty `Canvas`. [#278] - Text measuring when spaces where present at the beginning of a `TextInput` value. [#279] - `TextInput` producing a `Clip` primitive when unnecessary. [#279] - Alignment of `Text` primitive in `iced_wgpu`. [#281] - `CursorEntered` and `CursorLeft` not being generated. [#289] ### Removed - Unnecessary `'static` lifetimes in `Renderer` bounds. [#290] [#259]: https://github.com/iced-rs/iced/pull/259 [#260]: https://github.com/iced-rs/iced/pull/260 [#266]: https://github.com/iced-rs/iced/pull/266 [#267]: https://github.com/iced-rs/iced/pull/267 [#268]: https://github.com/iced-rs/iced/pull/268 [#278]: https://github.com/iced-rs/iced/pull/278 [#279]: https://github.com/iced-rs/iced/pull/279 [#281]: https://github.com/iced-rs/iced/pull/281 [#289]: https://github.com/iced-rs/iced/pull/289 [#290]: https://github.com/iced-rs/iced/pull/290 [#293]: https://github.com/iced-rs/iced/pull/293 ## [0.1.0] - 2020-04-02 ### Added - __[Event subscriptions]__ (#122) A declarative way to listen to external events asynchronously by leveraging [streams]. - __[Custom styling]__ (#146) A simple, trait-based approach for customizing the appearance of different widgets. - __[`Canvas` widget]__ (#193) A widget for drawing 2D graphics with an interface inspired by the [Web Canvas API] and powered by [`lyon`]. - __[`PaneGrid` widget]__ (#224) A widget that dynamically organizes layout by splitting panes that can be resized and drag and dropped. - __[`Svg` widget]__ (#111) A widget that renders vector graphics on top of [`resvg`] and [`raqote`]. Thanks to @Maldela! - __[`ProgressBar` widget]__ (#141) A widget to notify progress of asynchronous tasks to your users. Thanks to @Songtronix! - __[Configurable futures executor]__ (#164) Support for plugging [`tokio`], [`async-std`], [`wasm-bindgen-futures`], or your own custom futures executor to an application. - __[Compatibility with existing `wgpu` projects]__ (#183) A bunch of improvements to the flexibility of [`iced_wgpu`] to allow integration in existing codebases. - __[Text selection for `TextInput`]__ (#202) Thanks to @FabianLars and @Finnerale! - __[Texture atlas for `iced_wgpu`]__ (#154) An atlas on top of [`guillotiere`] for batching draw calls. Thanks to @Maldela! [Event subscriptions]: https://github.com/iced-rs/iced/pull/122 [Custom styling]: https://github.com/iced-rs/iced/pull/146 [`Canvas` widget]: https://github.com/iced-rs/iced/pull/193 [`PaneGrid` widget]: https://github.com/iced-rs/iced/pull/224 [`Svg` widget]: https://github.com/iced-rs/iced/pull/111 [`ProgressBar` widget]: https://github.com/iced-rs/iced/pull/141 [Configurable futures executor]: https://github.com/iced-rs/iced/pull/164 [Compatibility with existing `wgpu` projects]: https://github.com/iced-rs/iced/pull/183 [Clipboard access]: https://github.com/iced-rs/iced/pull/132 [Texture atlas for `iced_wgpu`]: https://github.com/iced-rs/iced/pull/154 [Text selection for `TextInput`]: https://github.com/iced-rs/iced/pull/202 [`lyon`]: https://github.com/nical/lyon [`guillotiere`]: https://github.com/nical/guillotiere [Web Canvas API]: https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API [streams]: https://docs.rs/futures/0.3.4/futures/stream/index.html [`tokio`]: https://github.com/tokio-rs/tokio [`async-std`]: https://github.com/async-rs/async-std [`wasm-bindgen-futures`]: https://github.com/rustwasm/wasm-bindgen/tree/master/crates/futures [`resvg`]: https://github.com/RazrFalcon/resvg [`raqote`]: https://github.com/jrmuizel/raqote [`iced_wgpu`]: wgpu/ ## [0.1.0-beta] - 2019-11-25 ### Changed - The old `iced` becomes `iced_native`. The current `iced` crate turns into a batteries-included, cross-platform GUI library. ## [0.1.0-alpha] - 2019-09-05 ### Added - First release! :tada: [Unreleased]: https://github.com/iced-rs/iced/compare/0.14.0...HEAD [0.14.0]: https://github.com/iced-rs/iced/compare/0.13.1...0.14.0 [0.13.1]: https://github.com/iced-rs/iced/compare/0.13.0...0.13.1 [0.13.0]: https://github.com/iced-rs/iced/compare/0.12.1...0.13.0 [0.12.1]: https://github.com/iced-rs/iced/compare/0.12.0...0.12.1 [0.12.0]: https://github.com/iced-rs/iced/compare/0.10.0...0.12.0 [0.10.0]: https://github.com/iced-rs/iced/compare/0.9.0...0.10.0 [0.9.0]: https://github.com/iced-rs/iced/compare/0.8.0...0.9.0 [0.8.0]: https://github.com/iced-rs/iced/compare/0.7.0...0.8.0 [0.7.0]: https://github.com/iced-rs/iced/compare/0.6.0...0.7.0 [0.6.0]: https://github.com/iced-rs/iced/compare/0.5.0...0.6.0 [0.5.0]: https://github.com/iced-rs/iced/compare/0.4.2...0.5.0 [0.4.2]: https://github.com/iced-rs/iced/compare/0.4.1...0.4.2 [0.4.1]: https://github.com/iced-rs/iced/compare/0.4.0...0.4.1 [0.4.0]: https://github.com/iced-rs/iced/compare/0.3.0...0.4.0 [0.3.0]: https://github.com/iced-rs/iced/compare/0.2.0...0.3.0 [0.2.0]: https://github.com/iced-rs/iced/compare/0.1.1...0.2.0 [0.1.1]: https://github.com/iced-rs/iced/compare/0.1.0...0.1.1 [0.1.0]: https://github.com/iced-rs/iced/compare/0.1.0-beta...0.1.0 [0.1.0-beta]: https://github.com/iced-rs/iced/compare/0.1.0-alpha...0.1.0-beta [0.1.0-alpha]: https://github.com/iced-rs/iced/releases/tag/0.1.0-alpha ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing Thank you for considering contributing to Iced! Take a look at [the roadmap] to get an idea of the current state of the library. The core team is busy and does not have time to mentor nor babysit new contributors. If a member of the core team thinks that reviewing and understanding your work will take more time and effort than writing it from scratch by themselves, your contribution will be dismissed. It is your responsibility to communicate and figure out how to reduce the likelihood of this! The general advice for new contributors is to share your ideas with the community. You can share your ideas and gather feedback in [our Zulip forum]. This is a very important step. It helps to coordinate work, get on the same page, and start building trust. Remember that [Code is the Easy Part] and also [The Hard Parts of Open Source]! Once you have started a channel of communication, you must wait until someone from the core team chimes in. If the core team is busy, this can take a long time (maybe months!). Your idea may need a bunch of iteration, or it may turn into something completely different, or it may be completely discarded! You will have to be patient and humble. Remember that open-source is a gift. Besides directly writing code, there are many other different ways you can contribute. To name a few: - Writing tutorials or blog posts - Improving the documentation - Submitting bug reports and use cases - Sharing, discussing, researching and exploring new ideas or crates [the roadmap]: ROADMAP.md [our Zulip forum]: https://iced.zulipchat.com/ [Code is the Easy Part]: https://youtu.be/DSjbTC-hvqQ?t=1138 [The Hard Parts of Open Source]: https://www.youtube.com/watch?v=o_4EX4dPppA ================================================ FILE: Cargo.toml ================================================ [package] name = "iced" description = "A cross-platform GUI library inspired by Elm" version.workspace = true edition.workspace = true authors.workspace = true license.workspace = true repository.workspace = true homepage.workspace = true categories.workspace = true keywords.workspace = true rust-version.workspace = true [lints] workspace = true [package.metadata.docs.rs] rustdoc-args = ["--cfg", "docsrs"] all-features = true [badges] maintenance = { status = "actively-developed" } [features] default = ["wgpu", "tiny-skia", "crisp", "hinting", "web-colors", "thread-pool", "linux-theme-detection", "x11", "wayland"] # Enables the `wgpu` GPU-accelerated renderer with all its default features (Vulkan, Metal, DX12, OpenGL, and WebGPU) wgpu = ["wgpu-bare", "iced_renderer/wgpu"] # Enables the `wgpu` GPU-accelerated renderer with the minimum required features (no backends!) wgpu-bare = ["iced_renderer/wgpu-bare", "iced_widget/wgpu"] # Enables the `tiny-skia` software renderer tiny-skia = ["iced_renderer/tiny-skia"] # Enables the `image` widget and image clipboard support image = ["image-without-codecs", "image/default", "iced_winit/image"] # Enables the `image` widget, without any built-in codecs of the `image` crate image-without-codecs = ["iced_widget/image", "dep:image"] # Enables the `svg` widget svg = ["iced_widget/svg"] # Enables the `canvas` widget canvas = ["iced_widget/canvas"] # Enables the `qr_code` widget qr_code = ["iced_widget/qr_code"] # Enables the `markdown` widget markdown = ["iced_widget/markdown"] # Enables lazy widgets lazy = ["iced_widget/lazy"] # Enables debug metrics in native platforms (press F12) debug = ["iced_winit/debug", "dep:iced_devtools"] # Enables time-travel debugging (very experimental!) time-travel = ["debug", "iced_devtools/time-travel"] # Enables hot reloading (very experimental!) hot = ["debug", "iced_debug/hot"] # Enables the tester developer tool for recording and playing tests (press F12) tester = ["dep:iced_tester"] # Enables the `thread-pool` futures executor as the `executor::Default` on native platforms thread-pool = ["iced_futures/thread-pool"] # Enables `tokio` as the `executor::Default` on native platforms tokio = ["iced_futures/tokio"] # Enables `smol` as the `executor::Default` on native platforms smol = ["iced_futures/smol"] # Enables querying system information sysinfo = ["iced_winit/sysinfo"] # Enables broken "sRGB linear" blending to reproduce color management of the Web web-colors = ["iced_renderer/web-colors"] # Enables pixel snapping for crisp edges by default (can cause jitter!) crisp = ["iced_core/crisp"] # Enables the WebGL backend webgl = ["iced_renderer/webgl"] # Enables syntax highlighting highlighter = ["iced_highlighter", "iced_widget/highlighter"] # Enables the `widget::selector` module selector = ["iced_runtime/selector"] # Enables the advanced module advanced = ["iced_core/advanced", "iced_widget/advanced"] # Embeds Fira Sans into the final application; useful for testing and Wasm builds fira-sans = ["iced_renderer/fira-sans"] # Enables basic text shaping by default basic-shaping = ["iced_core/basic-shaping"] # Enables advanced text shaping by default advanced-shaping = ["iced_core/advanced-shaping"] # Enables rendering hints (e.g. metrics hinting) hinting = ["iced_winit/hinting"] # Enables strict assertions for debugging purposes at the expense of performance strict-assertions = ["iced_renderer/strict-assertions"] # Redraws on every runtime event, and not only when a widget requests it unconditional-rendering = ["iced_winit/unconditional-rendering"] # Enables support for the `sipper` library sipper = ["iced_runtime/sipper"] # Enables Linux system theme detection linux-theme-detection = ["iced_winit/linux-theme-detection"] # Enables the Unix X11 backend x11 = ["iced_renderer/x11", "iced_winit/x11"] # Enables the Unix Wayland backend wayland = ["iced_renderer/wayland", "iced_winit/wayland"] [dependencies] iced_debug.workspace = true iced_core.workspace = true iced_futures.workspace = true iced_renderer.workspace = true iced_runtime.workspace = true iced_widget.workspace = true iced_winit.workspace = true iced_devtools.workspace = true iced_devtools.optional = true iced_tester.workspace = true iced_tester.optional = true iced_highlighter.workspace = true iced_highlighter.optional = true thiserror.workspace = true image.workspace = true image.optional = true [dev-dependencies] criterion = "0.5" iced_wgpu.workspace = true [[bench]] name = "wgpu" harness = false required-features = ["canvas"] [profile.release-opt] inherits = "release" codegen-units = 1 debug = false lto = true incremental = false opt-level = 3 overflow-checks = false strip = "debuginfo" [workspace] members = [ "beacon", "core", "debug", "devtools", "futures", "graphics", "highlighter", "program", "renderer", "runtime", "selector", "test", "tester", "tiny_skia", "wgpu", "widget", "winit", "examples/*", ] [workspace.package] version = "0.15.0-dev" authors = ["Héctor Ramón Jiménez "] edition = "2024" license = "MIT" repository = "https://github.com/iced-rs/iced" homepage = "https://iced.rs" categories = ["gui"] keywords = ["gui", "ui", "graphics", "interface", "widgets"] rust-version = "1.92" [workspace.dependencies] iced = { version = "0.15.0-dev", path = "." } iced_beacon = { version = "0.15.0-dev", path = "beacon" } iced_core = { version = "0.15.0-dev", path = "core" } iced_debug = { version = "0.15.0-dev", path = "debug" } iced_devtools = { version = "0.15.0-dev", path = "devtools" } iced_futures = { version = "0.15.0-dev", path = "futures" } iced_graphics = { version = "0.15.0-dev", path = "graphics" } iced_highlighter = { version = "0.15.0-dev", path = "highlighter" } iced_program = { version = "0.15.0-dev", path = "program" } iced_renderer = { version = "0.15.0-dev", path = "renderer" } iced_runtime = { version = "0.15.0-dev", path = "runtime" } iced_selector = { version = "0.15.0-dev", path = "selector" } iced_test = { version = "0.15.0-dev", path = "test" } iced_tester = { version = "0.15.0-dev", path = "tester" } iced_tiny_skia = { version = "0.15.0-dev", path = "tiny_skia", default-features = false } iced_wgpu = { version = "0.15.0-dev", path = "wgpu", default-features = false } iced_widget = { version = "0.15.0-dev", path = "widget" } iced_winit = { version = "0.15.0-dev", path = "winit", default-features = false } arboard = { version = "3.6", default-features = false } bincode = "1.3" bitflags = "2.0" bytemuck = { version = "1.0", features = ["derive"] } bytes = "1.6" cargo-hot = { version = "0.1", package = "cargo-hot-protocol" } cosmic-text = "0.18" cryoglyph = { git = "https://github.com/iced-rs/cryoglyph.git", rev = "1d68895e9c4c9b73739f826e81c2e3012c155cce" } futures = { version = "0.3", default-features = false, features = ["std", "async-await"] } glam = "0.25" guillotiere = "0.6" half = "2.2" image = { version = "0.25", default-features = false } kamadak-exif = "0.6" kurbo = "0.10" lilt = "0.8" log = "0.4" lyon = "1.0" lyon_path = "1.0" mundy = { version = "0.2", default-features = false } nom = "8" num-traits = "0.2" ouroboros = "0.18" png = "0.18" pulldown-cmark = "0.12" qrcode = { version = "0.13", default-features = false } raw-window-handle = "0.6" resvg = "0.45" rfd = "0.16" rustc-hash = "2.0" semver = "1.0" serde = "1.0" sha2 = "0.10" sipper = "0.1" smol = "2" smol_str = "0.2" softbuffer = { version = "0.4", default-features = false } sysinfo = "0.33" thiserror = "2" tiny-skia = { version = "0.11", default-features = false, features = ["std", "simd"] } tokio = "1.0" tracing = "0.1" two-face = { version = "0.4", default-features = false, features = ["syntect-default-fancy"] } unicode-segmentation = "1.0" url = "2.5" wasm-bindgen-futures = "0.4" wasmtimer = "0.4.2" web-sys = "=0.3.85" web-time = "1.1" wgpu = { version = "28.0", default-features = false, features = ["std", "wgsl"] } winit = { git = "https://github.com/iced-rs/winit.git", rev = "05b8ff17a06562f0a10bb46e6eaacbe2a95cb5ed", default-features = false, features = ["rwh_06"] } [workspace.lints.rust] rust_2018_idioms = { level = "deny", priority = -1 } missing_docs = "deny" unsafe_code = "deny" unused_results = "deny" [workspace.lints.clippy] type-complexity = "allow" map-entry = "allow" large-enum-variant = "allow" result_large_err = "allow" semicolon_if_nothing_returned = "deny" trivially-copy-pass-by-ref = "deny" default_trait_access = "deny" match-wildcard-for-single-variants = "deny" redundant-closure-for-method-calls = "deny" filter_map_next = "deny" manual_let_else = "deny" unused_async = "deny" from_over_into = "deny" needless_borrow = "deny" new_without_default = "deny" useless_conversion = "deny" [workspace.lints.rustdoc] broken_intra_doc_links = "forbid" ================================================ FILE: Cross.toml ================================================ [target.aarch64-unknown-linux-gnu] image = "ghcr.io/iced-rs/aarch64:latest" xargo = false [target.armv7-unknown-linux-gnueabihf] image = "ghcr.io/iced-rs/armv7:latest" xargo = false ================================================ FILE: DEPENDENCIES.md ================================================ # Dependencies Iced requires some system dependencies to work, and not all operating systems come with them installed. You can follow the provided instructions for your system to get them, if your system isn't here, add it! ## NixOS You can add this `shell.nix` to your project and use it by running `nix-shell`: ```nix { pkgs ? import { } }: let dlopenLibraries = with pkgs; [ libxkbcommon # GPU backend vulkan-loader # libGL # Window system wayland # xorg.libX11 # xorg.libXcursor # xorg.libXi ]; in pkgs.mkShell { nativeBuildInputs = with pkgs; [ cargo rustc ]; # additional libraries that your project # links to at build time, e.g. OpenSSL buildInputs = []; env.RUSTFLAGS = "-C link-arg=-Wl,-rpath,${pkgs.lib.makeLibraryPath dlopenLibraries}"; } ``` Alternatively, you can use this `flake.nix` to create a dev shell, activated by `nix develop`: ```nix { inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; systems.url = "github:nix-systems/default"; }; outputs = { nixpkgs, systems, ... }: let eachSystem = nixpkgs.lib.genAttrs (import systems); pkgsFor = nixpkgs.legacyPackages; in { devShells = eachSystem (system: let pkgs = pkgsFor.${system}; dlopenLibraries = with pkgs; [ libxkbcommon # GPU backend vulkan-loader # libGL # Window system wayland # xorg.libX11 # xorg.libXcursor # xorg.libXi ]; in { default = pkgs.mkShell { nativeBuildInputs = with pkgs; [ cargo rustc ]; # additional libraries that your project # links to at build time, e.g. OpenSSL buildInputs = []; env.RUSTFLAGS = "-C link-arg=-Wl,-rpath,${nixpkgs.lib.makeLibraryPath dlopenLibraries}"; }; }); }; } ``` ================================================ FILE: LICENSE ================================================ Copyright 2019 Héctor Ramón, Iced contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================
# Iced [![Documentation](https://docs.rs/iced/badge.svg)][documentation] [![Crates.io](https://img.shields.io/crates/v/iced.svg)](https://crates.io/crates/iced) [![License](https://img.shields.io/crates/l/iced.svg)](https://github.com/iced-rs/iced/blob/master/LICENSE) [![Downloads](https://img.shields.io/crates/d/iced.svg)](https://crates.io/crates/iced) [![Test Status](https://img.shields.io/github/actions/workflow/status/iced-rs/iced/test.yml?branch=master&event=push&label=test)](https://github.com/iced-rs/iced/actions) [![Zulip Chat](https://img.shields.io/badge/chat-on%20Zulip-5e7ce2?logo=zulip&logoColor=white)](https://iced.zulipchat.com/) [![Discord Server](https://img.shields.io/discord/628993209984614400?label=&labelColor=6A7EC2&logo=discord&logoColor=ffffff&color=7389D8)](https://discord.gg/3xZJ65GAhd) A cross-platform GUI library for Rust focused on simplicity and type-safety. Inspired by [Elm].
## Features * Simple, easy-to-use, batteries-included API * Type-safe, reactive programming model * [Cross-platform support] (Windows, macOS, Linux, and the Web) * Responsive layout * Built-in widgets (including [text inputs], [scrollables], and more!) * Custom widget support (create your own!) * [Debug tooling with performance metrics and time traveling] * First-class support for async actions (use futures!) * Modular ecosystem split into reusable parts: * A [renderer-agnostic native runtime] enabling integration with existing systems * Two built-in renderers leveraging [`wgpu`] and [`tiny-skia`] * [`iced_wgpu`] supporting Vulkan, Metal and DX12 * [`iced_tiny_skia`] offering a software alternative as a fallback * A [windowing shell] __Iced is currently experimental software.__ [Take a look at the roadmap] and [check out the issues]. [Cross-platform support]: https://raw.githubusercontent.com/iced-rs/iced/master/docs/images/todos_desktop.jpg [text inputs]: https://iced.rs/examples/text_input.mp4 [scrollables]: https://iced.rs/examples/scrollable.mp4 [Debug tooling with performance metrics and time traveling]: https://github.com/user-attachments/assets/2e49695c-0261-4b43-ac2e-8d7da5454c4b [renderer-agnostic native runtime]: runtime/ [`wgpu`]: https://github.com/gfx-rs/wgpu [`tiny-skia`]: https://github.com/RazrFalcon/tiny-skia [`iced_wgpu`]: wgpu/ [`iced_tiny_skia`]: tiny_skia/ [windowing shell]: winit/ [Take a look at the roadmap]: ROADMAP.md [check out the issues]: https://github.com/iced-rs/iced/issues ## Overview Inspired by [The Elm Architecture], Iced expects you to split user interfaces into four different concepts: * __State__ — the state of your application * __Messages__ — user interactions or meaningful events that you care about * __View logic__ — a way to display your __state__ as widgets that may produce __messages__ on user interaction * __Update logic__ — a way to react to __messages__ and update your __state__ We can build something to see how this works! Let's say we want a simple counter that can be incremented and decremented using two buttons. We start by modelling the __state__ of our application: ```rust #[derive(Default)] struct Counter { value: i32, } ``` Next, we need to define the possible user interactions of our counter: the button presses. These interactions are our __messages__: ```rust #[derive(Debug, Clone, Copy)] pub enum Message { Increment, Decrement, } ``` Now, let's show the actual counter by putting it all together in our __view logic__: ```rust use iced::widget::{button, column, text, Column}; impl Counter { pub fn view(&self) -> Column<'_, Message> { // We use a column: a simple vertical layout column![ // The increment button. We tell it to produce an // `Increment` message when pressed button("+").on_press(Message::Increment), // We show the value of the counter here text(self.value).size(50), // The decrement button. We tell it to produce a // `Decrement` message when pressed button("-").on_press(Message::Decrement), ] } } ``` Finally, we need to be able to react to any produced __messages__ and change our __state__ accordingly in our __update logic__: ```rust impl Counter { // ... pub fn update(&mut self, message: Message) { match message { Message::Increment => { self.value += 1; } Message::Decrement => { self.value -= 1; } } } } ``` And that's everything! We just wrote a whole user interface. Let's run it: ```rust fn main() -> iced::Result { iced::run(Counter::update, Counter::view) } ``` Iced will automatically: 1. Take the result of our __view logic__ and layout its widgets. 1. Process events from our system and produce __messages__ for our __update logic__. 1. Draw the resulting user interface. Read the [book], the [documentation], and the [examples] to learn more! ## Implementation details Iced was originally born as an attempt at bringing the simplicity of [Elm] and [The Elm Architecture] into [Coffee], a 2D game library I am working on. The core of the library was implemented during May 2019 in [this pull request]. [The first alpha version] was eventually released as [a renderer-agnostic GUI library]. The library did not provide a renderer and implemented the current [tour example] on top of [`ggez`], a game library. Since then, the focus has shifted towards providing a batteries-included, end-user-oriented GUI library, while keeping the ecosystem modular. [this pull request]: https://github.com/hecrj/coffee/pull/35 [The first alpha version]: https://github.com/iced-rs/iced/tree/0.1.0-alpha [a renderer-agnostic GUI library]: https://www.reddit.com/r/rust/comments/czzjnv/iced_a_rendereragnostic_gui_library_focused_on/ [tour example]: examples/README.md#tour [`ggez`]: https://github.com/ggez/ggez ## Contributing / Feedback If you want to contribute, please read our [contributing guidelines] for more details. Feedback is also welcome! You can create a new topic in [our Zulip forum] or come chat to [our Discord server]. ## Sponsors The development of Iced is sponsored by the [Cryptowatch] team at [Kraken.com] [book]: https://book.iced.rs/ [documentation]: https://docs.rs/iced/ [examples]: https://github.com/iced-rs/iced/tree/master/examples#examples [Coffee]: https://github.com/hecrj/coffee [Elm]: https://elm-lang.org/ [The Elm Architecture]: https://guide.elm-lang.org/architecture/ [the current issues]: https://github.com/iced-rs/iced/issues [contributing guidelines]: https://github.com/iced-rs/iced/blob/master/CONTRIBUTING.md [our Zulip forum]: https://iced.zulipchat.com/ [our Discord server]: https://discord.gg/3xZJ65GAhd [Cryptowatch]: https://cryptowat.ch/charts [Kraken.com]: https://kraken.com/ ================================================ FILE: ROADMAP.md ================================================ # Roadmap We have [a detailed graphical roadmap now](https://whimsical.com/roadmap-iced-7vhq6R35Lp3TmYH4WeYwLM)! ================================================ FILE: beacon/Cargo.toml ================================================ [package] name = "iced_beacon" description = "A client/server protocol to monitor and supervise iced applications" version.workspace = true edition.workspace = true authors.workspace = true license.workspace = true repository.workspace = true homepage.workspace = true categories.workspace = true keywords.workspace = true [dependencies] iced_core.workspace = true iced_core.features = ["serde"] bincode.workspace = true futures.workspace = true log.workspace = true thiserror.workspace = true tokio.workspace = true tokio.features = ["rt", "rt-multi-thread", "net", "sync", "time", "io-util", "macros"] serde.workspace = true serde.features = ["derive"] semver.workspace = true semver.features = ["serde"] ================================================ FILE: beacon/src/client.rs ================================================ use crate::Error; use crate::core::theme::palette; use crate::core::time::{Duration, SystemTime}; use crate::span; use semver::Version; use serde::{Deserialize, Serialize}; use tokio::io::{self, AsyncReadExt, AsyncWriteExt}; use tokio::net; use tokio::sync::{Mutex, mpsc}; use tokio::task; use tokio::time; use std::sync::Arc; use std::sync::atomic::{self, AtomicBool}; use std::thread; #[derive(Debug, Clone)] pub struct Client { sender: mpsc::Sender, is_connected: Arc, _handle: Arc>, } #[derive(Debug, Clone, Serialize, Deserialize)] pub enum Message { Connected { at: SystemTime, name: String, version: Version, theme: Option, can_time_travel: bool, }, EventLogged { at: SystemTime, event: Event, }, Quit { at: SystemTime, }, } #[derive(Debug, Clone, Serialize, Deserialize)] pub enum Event { ThemeChanged(palette::Seed), SpanStarted(span::Stage), SpanFinished(span::Stage, Duration), MessageLogged { number: usize, message: String }, CommandsSpawned(usize), SubscriptionsTracked(usize), LayersRendered(usize), } impl Client { pub fn log(&self, event: Event) { let _ = self.sender.try_send(Action::Send(Message::EventLogged { at: SystemTime::now(), event, })); } pub fn is_connected(&self) -> bool { self.is_connected.load(atomic::Ordering::Relaxed) } pub fn quit(&self) { let _ = self.sender.try_send(Action::Send(Message::Quit { at: SystemTime::now(), })); } pub fn subscribe(&self) -> mpsc::Receiver { let (sender, receiver) = mpsc::channel(100); let _ = self.sender.try_send(Action::Forward(sender)); receiver } } #[derive(Debug, Clone, Default)] pub struct Metadata { pub name: &'static str, pub theme: Option, pub can_time_travel: bool, } #[must_use] pub fn connect(metadata: Metadata) -> Client { let (sender, receiver) = mpsc::channel(10_000); let is_connected = Arc::new(AtomicBool::new(false)); let handle = { let is_connected = is_connected.clone(); std::thread::spawn(move || run(metadata, is_connected, receiver)) }; Client { sender, is_connected, _handle: Arc::new(handle), } } enum Action { Send(Message), Forward(mpsc::Sender), } #[derive(Debug, Clone, Copy, Serialize, Deserialize)] pub enum Command { RewindTo { message: usize }, GoLive, } #[tokio::main] async fn run( mut metadata: Metadata, is_connected: Arc, mut receiver: mpsc::Receiver, ) { let version = semver::Version::parse(env!("CARGO_PKG_VERSION")).expect("Parse package version"); let command_sender = { // Discard by default let (sender, _receiver) = mpsc::channel(1); Arc::new(Mutex::new(sender)) }; loop { match _connect().await { Ok(stream) => { is_connected.store(true, atomic::Ordering::Relaxed); let (mut reader, mut writer) = stream.into_split(); let _ = send( &mut writer, Message::Connected { at: SystemTime::now(), name: metadata.name.to_owned(), version: version.clone(), can_time_travel: metadata.can_time_travel, theme: metadata.theme, }, ) .await; { let command_sender = command_sender.clone(); drop(task::spawn(async move { let mut buffer = Vec::new(); loop { match receive(&mut reader, &mut buffer).await { Ok(command) => { match command { Command::RewindTo { .. } | Command::GoLive if !metadata.can_time_travel => { continue; } _ => {} } let sender = command_sender.lock().await; let _ = sender.send(command).await; } Err(Error::DecodingFailed(_)) => {} Err(Error::IOFailed(_)) => break, } } })) }; while let Some(action) = receiver.recv().await { match action { Action::Send(message) => { if let Message::EventLogged { event: Event::ThemeChanged(palette), .. } = message { metadata.theme = Some(palette); } match send(&mut writer, message).await { Ok(()) => {} Err(error) => { if error.kind() != io::ErrorKind::BrokenPipe { log::warn!("Error sending message to server: {error}"); } is_connected.store(false, atomic::Ordering::Relaxed); break; } } } Action::Forward(sender) => { *command_sender.lock().await = sender; } } } } Err(_) => { is_connected.store(false, atomic::Ordering::Relaxed); time::sleep(time::Duration::from_secs(2)).await; } } } } /// Returns the address of the beacon server in this environment. /// /// The value of the `ICED_BEACON_SERVER_ADDRESS` env variable will /// be returned, if defined. /// /// Otherwise, a default local server address will be returned. pub fn server_address_from_env() -> String { const DEFAULT_ADDRESS: &str = "127.0.0.1:9167"; std::env::var("ICED_BEACON_SERVER_ADDRESS").unwrap_or_else(|_| String::from(DEFAULT_ADDRESS)) } async fn _connect() -> Result { log::debug!("Attempting to connect to server..."); let stream = net::TcpStream::connect(server_address_from_env()).await?; stream.set_nodelay(true)?; stream.writable().await?; Ok(stream) } async fn send(stream: &mut net::tcp::OwnedWriteHalf, message: Message) -> Result<(), io::Error> { let bytes = bincode::serialize(&message).expect("Encode input message"); let size = bytes.len() as u64; stream.write_all(&size.to_be_bytes()).await?; stream.write_all(&bytes).await?; stream.flush().await?; Ok(()) } async fn receive( stream: &mut net::tcp::OwnedReadHalf, buffer: &mut Vec, ) -> Result { let size = stream.read_u64().await? as usize; if buffer.len() < size { buffer.resize(size, 0); } let _n = stream.read_exact(&mut buffer[..size]).await?; Ok(bincode::deserialize(buffer)?) } ================================================ FILE: beacon/src/error.rs ================================================ use std::io; #[derive(Debug, thiserror::Error)] pub enum Error { #[error("input/output operation failed: {0}")] IOFailed(#[from] io::Error), #[error("decoding failed: {0}")] DecodingFailed(#[from] Box), } ================================================ FILE: beacon/src/lib.rs ================================================ pub use iced_core as core; pub use semver::Version; pub mod client; pub mod span; mod error; mod stream; pub use client::Client; pub use span::Span; use crate::core::theme::palette; use crate::core::time::{Duration, SystemTime}; use crate::error::Error; use crate::span::present; use futures::{SinkExt, Stream}; use tokio::io::{self, AsyncReadExt, AsyncWriteExt}; use tokio::net; use tokio::sync::mpsc; use tokio::task; #[derive(Debug, Clone)] pub struct Connection { commands: mpsc::Sender, } impl Connection { pub fn rewind_to<'a>(&self, message: usize) -> impl Future + 'a { let commands = self.commands.clone(); async move { let _ = commands.send(client::Command::RewindTo { message }).await; } } pub fn go_live<'a>(&self) -> impl Future + 'a { let commands = self.commands.clone(); async move { let _ = commands.send(client::Command::GoLive).await; } } } #[derive(Debug, Clone)] pub enum Event { Connected { connection: Connection, at: SystemTime, name: String, version: Version, theme: Option, can_time_travel: bool, }, Disconnected { at: SystemTime, }, ThemeChanged { at: SystemTime, seed: palette::Seed, }, SpanFinished { at: SystemTime, duration: Duration, span: Span, }, QuitRequested { at: SystemTime, }, AlreadyRunning { at: SystemTime, }, } impl Event { pub fn at(&self) -> SystemTime { match self { Self::Connected { at, .. } | Self::Disconnected { at, .. } | Self::ThemeChanged { at, .. } | Self::SpanFinished { at, .. } | Self::QuitRequested { at } | Self::AlreadyRunning { at } => *at, } } } pub fn is_running() -> bool { std::net::TcpListener::bind(client::server_address_from_env()).is_err() } pub fn run() -> impl Stream { stream::channel(|mut output| async move { let mut buffer = Vec::new(); let server = loop { match net::TcpListener::bind(client::server_address_from_env()).await { Ok(server) => break server, Err(error) => { if error.kind() == io::ErrorKind::AddrInUse { let _ = output .send(Event::AlreadyRunning { at: SystemTime::now(), }) .await; } delay().await; } }; }; loop { let Ok((stream, _)) = server.accept().await else { continue; }; let (mut reader, mut writer) = { let _ = stream.set_nodelay(true); stream.into_split() }; let (command_sender, mut command_receiver) = mpsc::channel(1); let mut last_message = String::new(); let mut last_update_number = 0; let mut last_tasks = 0; let mut last_subscriptions = 0; let mut last_present_layers = 0; let mut last_prepare = present::Stage::default(); let mut last_render = present::Stage::default(); drop(task::spawn(async move { let mut last_message_number = None; while let Some(command) = command_receiver.recv().await { match command { client::Command::RewindTo { message } => { if Some(message) == last_message_number { continue; } last_message_number = Some(message); } client::Command::GoLive => { last_message_number = None; } } let _ = send(&mut writer, command) .await .inspect_err(|error| log::error!("Error when sending command: {error}")); } })); loop { match receive(&mut reader, &mut buffer).await { Ok(message) => { match message { client::Message::Connected { at, name, version, theme, can_time_travel, } => { let _ = output .send(Event::Connected { connection: Connection { commands: command_sender.clone(), }, at, name, version, theme, can_time_travel, }) .await; } client::Message::EventLogged { at, event } => match event { client::Event::ThemeChanged(seed) => { let _ = output.send(Event::ThemeChanged { at, seed }).await; } client::Event::SubscriptionsTracked(amount_alive) => { last_subscriptions = amount_alive; } client::Event::MessageLogged { number, message } => { last_update_number = number; last_message = message; } client::Event::CommandsSpawned(commands) => { last_tasks = commands; } client::Event::LayersRendered(layers) => { last_present_layers = layers; } client::Event::SpanStarted(span::Stage::Update) => { last_message.clear(); last_tasks = 0; } client::Event::SpanStarted(_) => {} client::Event::SpanFinished(stage, duration) => { let span = match stage { span::Stage::Boot => Span::Boot, span::Stage::Update => Span::Update { number: last_update_number, message: last_message.clone(), tasks: last_tasks, subscriptions: last_subscriptions, }, span::Stage::View(window) => Span::View { window }, span::Stage::Layout(window) => Span::Layout { window }, span::Stage::Interact(window) => Span::Interact { window }, span::Stage::Draw(window) => Span::Draw { window }, span::Stage::Prepare(primitive) | span::Stage::Render(primitive) => { let stage = if matches!(stage, span::Stage::Prepare(_),) { &mut last_prepare } else { &mut last_render }; let primitive = match primitive { present::Primitive::Quad => &mut stage.quads, present::Primitive::Triangle => { &mut stage.triangles } present::Primitive::Shader => &mut stage.shaders, present::Primitive::Text => &mut stage.text, present::Primitive::Image => &mut stage.images, }; *primitive += duration; continue; } span::Stage::Present(window) => { let span = Span::Present { window, prepare: last_prepare, render: last_render, layers: last_present_layers, }; last_prepare = present::Stage::default(); last_render = present::Stage::default(); last_present_layers = 0; span } span::Stage::Custom(name) => Span::Custom { name }, }; let _ = output .send(Event::SpanFinished { at, duration, span }) .await; } }, client::Message::Quit { at } => { let _ = output.send(Event::QuitRequested { at }).await; } }; } Err(Error::IOFailed(_)) => { let _ = output .send(Event::Disconnected { at: SystemTime::now(), }) .await; break; } Err(Error::DecodingFailed(error)) => { log::warn!("Error decoding beacon output: {error}") } } } } }) } async fn receive( stream: &mut net::tcp::OwnedReadHalf, buffer: &mut Vec, ) -> Result { let size = stream.read_u64().await? as usize; if buffer.len() < size { buffer.resize(size, 0); } let _n = stream.read_exact(&mut buffer[..size]).await?; Ok(bincode::deserialize(buffer)?) } async fn send( stream: &mut net::tcp::OwnedWriteHalf, command: client::Command, ) -> Result<(), io::Error> { let bytes = bincode::serialize(&command).expect("Encode input message"); let size = bytes.len() as u64; stream.write_all(&size.to_be_bytes()).await?; stream.write_all(&bytes).await?; stream.flush().await?; Ok(()) } async fn delay() { tokio::time::sleep(Duration::from_secs(2)).await; } ================================================ FILE: beacon/src/span.rs ================================================ use crate::core::window; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum Span { Boot, Update { number: usize, message: String, tasks: usize, subscriptions: usize, }, View { window: window::Id, }, Layout { window: window::Id, }, Interact { window: window::Id, }, Draw { window: window::Id, }, Present { window: window::Id, prepare: present::Stage, render: present::Stage, layers: usize, }, Custom { name: String, }, } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] pub enum Stage { Boot, Update, View(window::Id), Layout(window::Id), Interact(window::Id), Draw(window::Id), Present(window::Id), Prepare(present::Primitive), Render(present::Primitive), Custom(String), } impl std::fmt::Display for Stage { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(match self { Stage::Boot => "Boot", Stage::Update => "Update", Stage::View(_) => "View", Stage::Layout(_) => "Layout", Stage::Interact(_) => "Interact", Stage::Draw(_) => "Draw", Stage::Prepare(_) => "Prepare", Stage::Render(_) => "Render", Stage::Present(_) => "Present", Stage::Custom(name) => name, }) } } pub mod present { use crate::core::time::Duration; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] pub struct Stage { pub quads: Duration, pub triangles: Duration, pub shaders: Duration, pub text: Duration, pub images: Duration, } #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] pub enum Primitive { Quad, Triangle, Shader, Text, Image, } } ================================================ FILE: beacon/src/stream.rs ================================================ use futures::Future; use futures::channel::mpsc; use futures::stream::{self, Stream, StreamExt}; pub fn channel(f: impl Fn(mpsc::Sender) -> F) -> impl Stream where F: Future, { let (sender, receiver) = mpsc::channel(1); stream::select( receiver, stream::once(f(sender)).filter_map(|_| async { None }), ) } ================================================ FILE: benches/ipsum.txt ================================================ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur at elit mollis, dictum nunc non, tempus metus. Sed iaculis ac mauris eu lobortis. Integer elementum venenatis eros, id placerat odio feugiat vel. Maecenas consequat convallis tincidunt. Nunc eu lorem justo. Praesent quis ornare sapien. Aliquam interdum tortor ut rhoncus faucibus. Suspendisse molestie scelerisque nulla, eget sodales lacus sodales vel. Nunc placerat id arcu sodales venenatis. Praesent ullamcorper viverra nibh eget efficitur. Aliquam molestie felis vehicula, finibus sapien eget, accumsan purus. Praesent vestibulum eleifend consectetur. Sed tincidunt lectus a libero efficitur, non scelerisque lectus tincidunt. Cras ullamcorper tincidunt tellus non tempor. Integer pulvinar turpis quam, nec pharetra purus egestas non. Vivamus sed ipsum consequat, dignissim ante et, suscipit nibh. Quisque et mauris eu erat rutrum cursus. Pellentesque ut neque eu neque eleifend auctor ac hendrerit dolor. Morbi eget egestas ex. Integer hendrerit ipsum in enim bibendum, at vehicula ipsum dapibus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce tempus consectetur tortor, vel fermentum sem pulvinar eget. Maecenas rutrum fringilla eros a pellentesque. Cras quis magna consectetur, tristique massa vel, aliquet nunc. Aliquam erat volutpat. Suspendisse porttitor risus id auctor fermentum. Vivamus efficitur tellus sed tortor cursus tincidunt. Sed auctor varius arcu, non congue tellus vehicula finibus. Fusce a tincidunt urna. Nunc at quam ac enim tempor vehicula imperdiet in sapien. Donec lobortis tristique felis vel semper. Quisque vulputate felis eu enim vestibulum malesuada. Fusce a lobortis mauris, iaculis eleifend ligula. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Vivamus sodales vel elit dignissim mattis. Aliquam placerat vulputate dignissim. Proin pellentesque vitae arcu ut feugiat. Nunc mi felis, ornare at gravida sed, vestibulum sed urna. Duis fermentum maximus viverra. Donec imperdiet pellentesque sollicitudin. Cras non sem quis metus bibendum molestie. Duis imperdiet nec lectus eu rutrum. Mauris congue enim purus, in iaculis arcu dapibus ut. Nullam id erat tincidunt, iaculis dolor non, lobortis magna. Proin convallis scelerisque maximus. Morbi at lorem fringilla libero blandit fringilla. Ut aliquet tellus non sem dictum viverra. Aenean venenatis purus eget lacus placerat, non mollis mauris pellentesque. Etiam elit diam, aliquet quis suscipit non, condimentum viverra odio. Praesent mi enim, suscipit id mi in, rhoncus ultricies lorem. Nulla facilisi. Integer convallis sagittis euismod. Vestibulum porttitor sodales turpis ac accumsan. Nullam molestie turpis vel lacus tincidunt, sed finibus erat pharetra. Nullam vestibulum turpis id sollicitudin accumsan. Praesent eget posuere lacus. Donec vehicula, nisl nec suscipit porta, felis lorem gravida orci, a hendrerit tellus nibh sit amet elit. ================================================ FILE: benches/wgpu.rs ================================================ #![allow(missing_docs)] use criterion::{Bencher, Criterion, criterion_group, criterion_main}; use iced::alignment; use iced::mouse; use iced::widget::{canvas, scrollable, stack, text}; use iced::{Color, Element, Font, Length, Pixels, Point, Rectangle, Size, Theme}; use iced_wgpu::Renderer; use iced_wgpu::wgpu; criterion_main!(benches); criterion_group!(benches, wgpu_benchmark); #[allow(unused_results)] pub fn wgpu_benchmark(c: &mut Criterion) { use iced_futures::futures::executor; use iced_wgpu::wgpu; let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor { backends: wgpu::Backends::all(), ..Default::default() }); let adapter = executor::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions { power_preference: wgpu::PowerPreference::HighPerformance, compatible_surface: None, force_fallback_adapter: false, })) .expect("request adapter"); let (device, queue) = executor::block_on(adapter.request_device(&wgpu::DeviceDescriptor { label: None, required_features: wgpu::Features::empty(), required_limits: wgpu::Limits::default(), memory_hints: wgpu::MemoryHints::MemoryUsage, trace: wgpu::Trace::Off, experimental_features: wgpu::ExperimentalFeatures::disabled(), })) .expect("request device"); c.bench_function("wgpu — canvas (light)", |b| { benchmark(b, &adapter, &device, &queue, |_| scene(10)); }); c.bench_function("wgpu — canvas (heavy)", |b| { benchmark(b, &adapter, &device, &queue, |_| scene(1_000)); }); c.bench_function("wgpu - layered text (light)", |b| { benchmark(b, &adapter, &device, &queue, |_| layered_text(10)); }); c.bench_function("wgpu - layered text (heavy)", |b| { benchmark(b, &adapter, &device, &queue, |_| layered_text(1_000)); }); c.bench_function("wgpu - dynamic text (light)", |b| { benchmark(b, &adapter, &device, &queue, |i| dynamic_text(1_000, i)); }); c.bench_function("wgpu - dynamic text (heavy)", |b| { benchmark(b, &adapter, &device, &queue, |i| dynamic_text(100_000, i)); }); c.bench_function("wgpu - advanced shaping (light)", |b| { benchmark(b, &adapter, &device, &queue, |i| advanced_shaping(1_000, i)); }); c.bench_function("wgpu - advanced shaping (heavy)", |b| { benchmark(b, &adapter, &device, &queue, |i| { advanced_shaping(100_000, i) }); }); } fn benchmark<'a>( bencher: &mut Bencher<'_>, adapter: &wgpu::Adapter, device: &wgpu::Device, queue: &wgpu::Queue, view: impl Fn(usize) -> Element<'a, (), Theme, Renderer>, ) { use iced_wgpu::graphics; use iced_wgpu::graphics::{Antialiasing, Shell}; use iced_wgpu::wgpu; use iced_winit::core; use iced_winit::core::renderer; use iced_winit::runtime; let format = wgpu::TextureFormat::Bgra8UnormSrgb; let engine = iced_wgpu::Engine::new( adapter, device.clone(), queue.clone(), format, Some(Antialiasing::MSAAx4), Shell::headless(), ); let mut renderer = Renderer::new(engine, renderer::Settings::default()); let viewport = graphics::Viewport::with_physical_size(Size::new(3840, 2160), 2.0); let texture = device.create_texture(&wgpu::TextureDescriptor { label: None, size: wgpu::Extent3d { width: 3840, height: 2160, depth_or_array_layers: 1, }, mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, format, usage: wgpu::TextureUsages::RENDER_ATTACHMENT, view_formats: &[], }); let texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default()); let mut i = 0; let mut cache = Some(runtime::user_interface::Cache::default()); bencher.iter(|| { let mut user_interface = runtime::UserInterface::build( view(i), viewport.logical_size(), cache.take().unwrap(), &mut renderer, ); user_interface.draw( &mut renderer, &Theme::Dark, &core::renderer::Style { text_color: Color::WHITE, }, mouse::Cursor::Unavailable, ); cache = Some(user_interface.into_cache()); let submission = renderer.present(Some(Color::BLACK), format, &texture_view, &viewport); let _ = device.poll(wgpu::PollType::Wait { submission_index: Some(submission), timeout: None, }); i += 1; }); } fn scene<'a, Message: 'a>(n: usize) -> Element<'a, Message, Theme, Renderer> { struct Scene { n: usize, } impl canvas::Program for Scene { type State = canvas::Cache; fn draw( &self, cache: &Self::State, renderer: &Renderer, _theme: &Theme, bounds: Rectangle, _cursor: mouse::Cursor, ) -> Vec> { vec![cache.draw(renderer, bounds.size(), |frame| { for i in 0..self.n { frame.fill_rectangle( Point::new(0.0, i as f32), Size::new(10.0, 10.0), Color::WHITE, ); } for i in 0..self.n { frame.fill_text(canvas::Text { content: i.to_string(), position: Point::new(0.0, i as f32), color: Color::BLACK, size: Pixels::from(16), line_height: text::LineHeight::default(), font: Font::DEFAULT, align_x: text::Alignment::Left, align_y: alignment::Vertical::Top, shaping: text::Shaping::Basic, wrapping: text::Wrapping::default(), ellipsis: text::Ellipsis::default(), max_width: f32::INFINITY, }); } })] } } canvas(Scene { n }) .width(Length::Fill) .height(Length::Fill) .into() } fn layered_text<'a, Message: 'a>(n: usize) -> Element<'a, Message, Theme, Renderer> { stack((0..n).map(|i| text!("I am paragraph {i}!").into())) .width(Length::Fill) .height(Length::Fill) .into() } fn dynamic_text<'a, Message: 'a>(n: usize, i: usize) -> Element<'a, Message, Theme, Renderer> { const LOREM_IPSUM: &str = include_str!("ipsum.txt"); scrollable( text!( "{}... Iteration {i}", std::iter::repeat(LOREM_IPSUM.chars()) .flatten() .take(n) .collect::(), ) .size(10), ) .into() } fn advanced_shaping<'a, Message: 'a>(n: usize, i: usize) -> Element<'a, Message, Theme, Renderer> { const LOREM_IPSUM: &str = include_str!("ipsum.txt"); scrollable( text!( "{}... Iteration {i} 😎", std::iter::repeat(LOREM_IPSUM.chars()) .flatten() .take(n) .collect::(), ) .shaping(text::Shaping::Advanced) .size(10), ) .into() } ================================================ FILE: clippy.toml ================================================ too-many-arguments-threshold = 20 enum-variant-name-threshold = 10 ================================================ FILE: core/Cargo.toml ================================================ [package] name = "iced_core" description = "The essential ideas of iced" version.workspace = true edition.workspace = true authors.workspace = true license.workspace = true repository.workspace = true homepage.workspace = true categories.workspace = true keywords.workspace = true [lints] workspace = true [features] advanced = [] crisp = [] image = [] basic-shaping = [] advanced-shaping = [] [dependencies] bitflags.workspace = true bytes.workspace = true glam.workspace = true lilt.workspace = true log.workspace = true num-traits.workspace = true rustc-hash.workspace = true smol_str.workspace = true thiserror.workspace = true web-time.workspace = true serde.workspace = true serde.optional = true serde.features = ["derive"] ================================================ FILE: core/README.md ================================================ # `iced_core` [![Documentation](https://docs.rs/iced_core/badge.svg)][documentation] [![Crates.io](https://img.shields.io/crates/v/iced_core.svg)](https://crates.io/crates/iced_core) [![License](https://img.shields.io/crates/l/iced_core.svg)](https://github.com/iced-rs/iced/blob/master/LICENSE) [![Discord Server](https://img.shields.io/discord/628993209984614400?label=&labelColor=6A7EC2&logo=discord&logoColor=ffffff&color=7389D8)](https://discord.gg/3xZJ65GAhd) `iced_core` holds basic reusable types of the public API. For instance, basic data types like `Point`, `Rectangle`, `Length`, etc. This crate is meant to be a starting point for an Iced runtime.

The foundations

[documentation]: https://docs.rs/iced_core ================================================ FILE: core/src/alignment.rs ================================================ //! Align and position widgets. /// Alignment on the axis of a container. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum Alignment { /// Align at the start of the axis. Start, /// Align at the center of the axis. Center, /// Align at the end of the axis. End, } impl From for Alignment { fn from(horizontal: Horizontal) -> Self { match horizontal { Horizontal::Left => Self::Start, Horizontal::Center => Self::Center, Horizontal::Right => Self::End, } } } impl From for Alignment { fn from(vertical: Vertical) -> Self { match vertical { Vertical::Top => Self::Start, Vertical::Center => Self::Center, Vertical::Bottom => Self::End, } } } /// The horizontal [`Alignment`] of some resource. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum Horizontal { /// Align left Left, /// Horizontally centered Center, /// Align right Right, } impl From for Horizontal { fn from(alignment: Alignment) -> Self { match alignment { Alignment::Start => Self::Left, Alignment::Center => Self::Center, Alignment::End => Self::Right, } } } /// The vertical [`Alignment`] of some resource. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum Vertical { /// Align top Top, /// Vertically centered Center, /// Align bottom Bottom, } impl From for Vertical { fn from(alignment: Alignment) -> Self { match alignment { Alignment::Start => Self::Top, Alignment::Center => Self::Center, Alignment::End => Self::Bottom, } } } ================================================ FILE: core/src/angle.rs ================================================ use crate::{Point, Rectangle, Vector}; use std::f32::consts::{FRAC_PI_2, PI}; use std::fmt::Display; use std::ops::{Add, AddAssign, Div, Mul, RangeInclusive, Rem, Sub, SubAssign}; /// Degrees #[derive(Debug, Copy, Clone, PartialEq, PartialOrd)] pub struct Degrees(pub f32); impl Degrees { /// The range of degrees of a circle. pub const RANGE: RangeInclusive = Self(0.0)..=Self(360.0); } impl PartialEq for Degrees { fn eq(&self, other: &f32) -> bool { self.0.eq(other) } } impl PartialOrd for Degrees { fn partial_cmp(&self, other: &f32) -> Option { self.0.partial_cmp(other) } } impl From for Degrees { fn from(degrees: f32) -> Self { Self(degrees) } } impl From for Degrees { fn from(degrees: u8) -> Self { Self(f32::from(degrees)) } } impl From for f32 { fn from(degrees: Degrees) -> Self { degrees.0 } } impl From for f64 { fn from(degrees: Degrees) -> Self { Self::from(degrees.0) } } impl Mul for Degrees { type Output = Degrees; fn mul(self, rhs: f32) -> Self::Output { Self(self.0 * rhs) } } impl num_traits::FromPrimitive for Degrees { fn from_i64(n: i64) -> Option { Some(Self(n as f32)) } fn from_u64(n: u64) -> Option { Some(Self(n as f32)) } fn from_f64(n: f64) -> Option { Some(Self(n as f32)) } } /// Radians #[derive(Debug, Copy, Clone, PartialEq, PartialOrd)] pub struct Radians(pub f32); impl Radians { /// The range of radians of a circle. pub const RANGE: RangeInclusive = Self(0.0)..=Self(2.0 * PI); /// The amount of radians in half a circle. pub const PI: Self = Self(PI); /// Calculates the line in which the angle intercepts the `bounds`. pub fn to_distance(&self, bounds: &Rectangle) -> (Point, Point) { let angle = self.0 - FRAC_PI_2; let r = Vector::new(f32::cos(angle), f32::sin(angle)); let distance_to_rect = f32::max( f32::abs(r.x * bounds.width / 2.0), f32::abs(r.y * bounds.height / 2.0), ); let start = bounds.center() - r * distance_to_rect; let end = bounds.center() + r * distance_to_rect; (start, end) } } impl From for Radians { fn from(degrees: Degrees) -> Self { Self(degrees.0 * PI / 180.0) } } impl From for Radians { fn from(radians: f32) -> Self { Self(radians) } } impl From for Radians { fn from(radians: u8) -> Self { Self(f32::from(radians)) } } impl From for f32 { fn from(radians: Radians) -> Self { radians.0 } } impl From for f64 { fn from(radians: Radians) -> Self { Self::from(radians.0) } } impl num_traits::FromPrimitive for Radians { fn from_i64(n: i64) -> Option { Some(Self(n as f32)) } fn from_u64(n: u64) -> Option { Some(Self(n as f32)) } fn from_f64(n: f64) -> Option { Some(Self(n as f32)) } } impl Sub for Radians { type Output = Self; fn sub(self, rhs: Self) -> Self::Output { Self(self.0 - rhs.0) } } impl SubAssign for Radians { fn sub_assign(&mut self, rhs: Self) { self.0 = self.0 - rhs.0; } } impl Add for Radians { type Output = Self; fn add(self, rhs: Self) -> Self::Output { Self(self.0 + rhs.0) } } impl Add for Radians { type Output = Self; fn add(self, rhs: Degrees) -> Self::Output { Self(self.0 + rhs.0.to_radians()) } } impl AddAssign for Radians { fn add_assign(&mut self, rhs: Radians) { self.0 = self.0 + rhs.0; } } impl Mul for Radians { type Output = Self; fn mul(self, rhs: Radians) -> Self::Output { Radians(self.0 * rhs.0) } } impl Mul for Radians { type Output = Self; fn mul(self, rhs: f32) -> Self::Output { Self(self.0 * rhs) } } impl Mul for f32 { type Output = Radians; fn mul(self, rhs: Radians) -> Self::Output { Radians(self * rhs.0) } } impl Div for Radians { type Output = Self; fn div(self, rhs: f32) -> Self::Output { Radians(self.0 / rhs) } } impl Div for Radians { type Output = Self; fn div(self, rhs: Self) -> Self::Output { Self(self.0 / rhs.0) } } impl Rem for Radians { type Output = Self; fn rem(self, rhs: Self) -> Self::Output { Self(self.0 % rhs.0) } } impl PartialEq for Radians { fn eq(&self, other: &f32) -> bool { self.0.eq(other) } } impl PartialOrd for Radians { fn partial_cmp(&self, other: &f32) -> Option { self.0.partial_cmp(other) } } impl Display for Radians { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{} rad", self.0) } } ================================================ FILE: core/src/animation.rs ================================================ //! Animate your applications. use crate::time::{Duration, Instant}; pub use lilt::{Easing, FloatRepresentable as Float, Interpolable}; /// The animation of some particular state. /// /// It tracks state changes and allows projecting interpolated values /// through time. #[derive(Debug, Clone)] pub struct Animation where T: Clone + Copy + PartialEq + Float, { raw: lilt::Animated, duration: Duration, // TODO: Expose duration getter in `lilt` } impl Animation where T: Clone + Copy + PartialEq + Float, { /// Creates a new [`Animation`] with the given initial state. pub fn new(state: T) -> Self { Self { raw: lilt::Animated::new(state), duration: Duration::from_millis(100), } } /// Sets the [`Easing`] function of the [`Animation`]. /// /// See the [Easing Functions Cheat Sheet](https://easings.net) for /// details! pub fn easing(mut self, easing: Easing) -> Self { self.raw = self.raw.easing(easing); self } /// Sets the duration of the [`Animation`] to 100ms. pub fn very_quick(self) -> Self { self.duration(Duration::from_millis(100)) } /// Sets the duration of the [`Animation`] to 200ms. pub fn quick(self) -> Self { self.duration(Duration::from_millis(200)) } /// Sets the duration of the [`Animation`] to 400ms. pub fn slow(self) -> Self { self.duration(Duration::from_millis(400)) } /// Sets the duration of the [`Animation`] to 500ms. pub fn very_slow(self) -> Self { self.duration(Duration::from_millis(500)) } /// Sets the duration of the [`Animation`] to the given value. pub fn duration(mut self, duration: Duration) -> Self { self.raw = self.raw.duration(duration.as_secs_f32() * 1_000.0); self.duration = duration; self } /// Sets a delay for the [`Animation`]. pub fn delay(mut self, duration: Duration) -> Self { self.raw = self.raw.delay(duration.as_secs_f64() as f32 * 1000.0); self } /// Makes the [`Animation`] repeat a given amount of times. /// /// Providing 1 repetition plays the animation twice in total. pub fn repeat(mut self, repetitions: u32) -> Self { self.raw = self.raw.repeat(repetitions); self } /// Makes the [`Animation`] repeat forever. pub fn repeat_forever(mut self) -> Self { self.raw = self.raw.repeat_forever(); self } /// Makes the [`Animation`] automatically reverse when repeating. pub fn auto_reverse(mut self) -> Self { self.raw = self.raw.auto_reverse(); self } /// Transitions the [`Animation`] from its current state to the given new state /// at the given time. pub fn go(mut self, new_state: T, at: Instant) -> Self { self.go_mut(new_state, at); self } /// Transitions the [`Animation`] from its current state to the given new state /// at the given time, by reference. pub fn go_mut(&mut self, new_state: T, at: Instant) { self.raw.transition(new_state, at); } /// Returns true if the [`Animation`] is currently in progress. /// /// An [`Animation`] is in progress when it is transitioning to a different state. pub fn is_animating(&self, at: Instant) -> bool { self.raw.in_progress(at) } /// Projects the [`Animation`] into an interpolated value at the given [`Instant`]; using the /// closure provided to calculate the different keyframes of interpolated values. /// /// If the [`Animation`] state is a `bool`, you can use the simpler [`interpolate`] method. /// /// [`interpolate`]: Animation::interpolate pub fn interpolate_with(&self, f: impl Fn(T) -> I, at: Instant) -> I where I: Interpolable, { self.raw.animate(f, at) } /// Retuns the current state of the [`Animation`]. pub fn value(&self) -> T { self.raw.value } } impl Animation { /// Projects the [`Animation`] into an interpolated value at the given [`Instant`]; using the /// `start` and `end` values as the origin and destination keyframes. pub fn interpolate(&self, start: I, end: I, at: Instant) -> I where I: Interpolable + Clone, { self.raw.animate_bool(start, end, at) } /// Returns the remaining [`Duration`] of the [`Animation`]. pub fn remaining(&self, at: Instant) -> Duration { Duration::from_secs_f32(self.interpolate(self.duration.as_secs_f32(), 0.0, at)) } } ================================================ FILE: core/src/background.rs ================================================ use crate::Color; use crate::gradient::{self, Gradient}; /// The background of some element. #[derive(Debug, Clone, Copy, PartialEq)] pub enum Background { /// A solid color. Color(Color), /// Linearly interpolate between several colors. Gradient(Gradient), // TODO: Add image variant } impl Background { /// Scales the alpha channel of the [`Background`] by the given /// factor. pub fn scale_alpha(self, factor: f32) -> Self { match self { Self::Color(color) => Self::Color(color.scale_alpha(factor)), Self::Gradient(gradient) => Self::Gradient(gradient.scale_alpha(factor)), } } } impl From for Background { fn from(color: Color) -> Self { Background::Color(color) } } impl From for Background { fn from(gradient: Gradient) -> Self { Background::Gradient(gradient) } } impl From for Background { fn from(gradient: gradient::Linear) -> Self { Background::Gradient(Gradient::Linear(gradient)) } } ================================================ FILE: core/src/border.rs ================================================ //! Draw lines around containers. use crate::{Color, Pixels}; /// A border. #[derive(Debug, Clone, Copy, PartialEq, Default)] pub struct Border { /// The color of the border. pub color: Color, /// The width of the border. pub width: f32, /// The [`Radius`] of the border. pub radius: Radius, } /// Creates a new [`Border`] with the given [`Radius`]. /// /// ``` /// # use iced_core::border::{self, Border}; /// # /// assert_eq!(border::rounded(10), Border::default().rounded(10)); /// ``` pub fn rounded(radius: impl Into) -> Border { Border::default().rounded(radius) } /// Creates a new [`Border`] with the given [`Color`]. /// /// ``` /// # use iced_core::border::{self, Border}; /// # use iced_core::Color; /// # /// assert_eq!(border::color(Color::BLACK), Border::default().color(Color::BLACK)); /// ``` pub fn color(color: impl Into) -> Border { Border::default().color(color) } /// Creates a new [`Border`] with the given `width`. /// /// ``` /// # use iced_core::border::{self, Border}; /// # use iced_core::Color; /// # /// assert_eq!(border::width(10), Border::default().width(10)); /// ``` pub fn width(width: impl Into) -> Border { Border::default().width(width) } impl Border { /// Sets the [`Color`] of the [`Border`]. pub fn color(self, color: impl Into) -> Self { Self { color: color.into(), ..self } } /// Sets the [`Radius`] of the [`Border`]. pub fn rounded(self, radius: impl Into) -> Self { Self { radius: radius.into(), ..self } } /// Sets the width of the [`Border`]. pub fn width(self, width: impl Into) -> Self { Self { width: width.into().0, ..self } } } /// The border radii for the corners of a graphics primitive in the order: /// top-left, top-right, bottom-right, bottom-left. #[derive(Debug, Clone, Copy, PartialEq, Default)] pub struct Radius { /// Top left radius pub top_left: f32, /// Top right radius pub top_right: f32, /// Bottom right radius pub bottom_right: f32, /// Bottom left radius pub bottom_left: f32, } /// Creates a new [`Radius`] with the same value for each corner. pub fn radius(value: impl Into) -> Radius { Radius::new(value) } /// Creates a new [`Radius`] with the given top left value. pub fn top_left(value: impl Into) -> Radius { Radius::default().top_left(value) } /// Creates a new [`Radius`] with the given top right value. pub fn top_right(value: impl Into) -> Radius { Radius::default().top_right(value) } /// Creates a new [`Radius`] with the given bottom right value. pub fn bottom_right(value: impl Into) -> Radius { Radius::default().bottom_right(value) } /// Creates a new [`Radius`] with the given bottom left value. pub fn bottom_left(value: impl Into) -> Radius { Radius::default().bottom_left(value) } /// Creates a new [`Radius`] with the given value as top left and top right. pub fn top(value: impl Into) -> Radius { Radius::default().top(value) } /// Creates a new [`Radius`] with the given value as bottom left and bottom right. pub fn bottom(value: impl Into) -> Radius { Radius::default().bottom(value) } /// Creates a new [`Radius`] with the given value as top left and bottom left. pub fn left(value: impl Into) -> Radius { Radius::default().left(value) } /// Creates a new [`Radius`] with the given value as top right and bottom right. pub fn right(value: impl Into) -> Radius { Radius::default().right(value) } impl Radius { /// Creates a new [`Radius`] with the same value for each corner. pub fn new(value: impl Into) -> Self { let value = value.into().0; Self { top_left: value, top_right: value, bottom_right: value, bottom_left: value, } } /// Sets the top left value of the [`Radius`]. pub fn top_left(self, value: impl Into) -> Self { Self { top_left: value.into().0, ..self } } /// Sets the top right value of the [`Radius`]. pub fn top_right(self, value: impl Into) -> Self { Self { top_right: value.into().0, ..self } } /// Sets the bottom right value of the [`Radius`]. pub fn bottom_right(self, value: impl Into) -> Self { Self { bottom_right: value.into().0, ..self } } /// Sets the bottom left value of the [`Radius`]. pub fn bottom_left(self, value: impl Into) -> Self { Self { bottom_left: value.into().0, ..self } } /// Sets the top left and top right values of the [`Radius`]. pub fn top(self, value: impl Into) -> Self { let value = value.into().0; Self { top_left: value, top_right: value, ..self } } /// Sets the bottom left and bottom right values of the [`Radius`]. pub fn bottom(self, value: impl Into) -> Self { let value = value.into().0; Self { bottom_left: value, bottom_right: value, ..self } } /// Sets the top left and bottom left values of the [`Radius`]. pub fn left(self, value: impl Into) -> Self { let value = value.into().0; Self { top_left: value, bottom_left: value, ..self } } /// Sets the top right and bottom right values of the [`Radius`]. pub fn right(self, value: impl Into) -> Self { let value = value.into().0; Self { top_right: value, bottom_right: value, ..self } } } impl From for Radius { fn from(radius: f32) -> Self { Self { top_left: radius, top_right: radius, bottom_right: radius, bottom_left: radius, } } } impl From for Radius { fn from(w: u8) -> Self { Self::from(f32::from(w)) } } impl From for Radius { fn from(w: u32) -> Self { Self::from(w as f32) } } impl From for Radius { fn from(w: i32) -> Self { Self::from(w as f32) } } impl From for [f32; 4] { fn from(radi: Radius) -> Self { [ radi.top_left, radi.top_right, radi.bottom_right, radi.bottom_left, ] } } impl std::ops::Mul for Radius { type Output = Self; fn mul(self, scale: f32) -> Self::Output { Self { top_left: self.top_left * scale, top_right: self.top_right * scale, bottom_right: self.bottom_right * scale, bottom_left: self.bottom_left * scale, } } } ================================================ FILE: core/src/clipboard.rs ================================================ //! Access the clipboard. use std::path::PathBuf; use std::sync::Arc; /// A set of clipboard requests. #[derive(Debug, Clone)] pub struct Clipboard { /// The read requests the runtime must fulfill. pub reads: Vec, /// The content that must be written to the clipboard by the runtime, /// if any. pub write: Option, } impl Clipboard { /// Creates a new empty set of [`Clipboard`] requests. pub fn new() -> Self { Self { reads: Vec::new(), write: None, } } /// Merges the current [`Clipboard`] requests with others. pub fn merge(&mut self, other: &mut Self) { self.reads.append(&mut other.reads); self.write = other.write.take().or(self.write.take()); } } impl Default for Clipboard { fn default() -> Self { Self::new() } } /// A clipboard event. #[derive(Debug, Clone, PartialEq)] pub enum Event { /// The clipboard was read. Read(Result, Error>), /// The clipboard was written. Written(Result<(), Error>), } /// Some clipboard content. #[derive(Debug, Clone, PartialEq)] #[allow(missing_docs)] #[non_exhaustive] pub enum Content { Text(String), Html(String), #[cfg(feature = "image")] Image(Image), Files(Vec), } impl From for Content { fn from(text: String) -> Self { Self::Text(text) } } #[cfg(feature = "image")] impl From for Content { fn from(image: Image) -> Self { Self::Image(image) } } impl From> for Content { fn from(files: Vec) -> Self { Self::Files(files) } } /// The kind of some clipboard [`Content`]. #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[allow(missing_docs)] #[non_exhaustive] pub enum Kind { Text, Html, #[cfg(feature = "image")] Image, Files, } /// A clipboard image. #[cfg(feature = "image")] #[derive(Debug, Clone, PartialEq)] pub struct Image { /// The pixels of the image in RGBA format. pub rgba: crate::Bytes, /// The physical [`Size`](crate::Size) of the image. pub size: crate::Size, } /// A clipboard error. #[derive(Debug, Clone, PartialEq)] pub enum Error { /// The clipboard in the current environment is either not present or could not be accessed. ClipboardUnavailable, /// The native clipboard is not accessible due to being held by another party. ClipboardOccupied, /// The clipboard contents were not available in the requested format. /// This could either be due to the clipboard being empty or the clipboard contents having /// an incompatible format to the requested one ContentNotAvailable, /// The image or the text that was about the be transferred to/from the clipboard could not be /// converted to the appropriate format. ConversionFailure, /// Any error that doesn't fit the other error types. Unknown { /// A description only meant to help the developer that should not be relied on as a /// means to identify an error case during runtime. description: Arc, }, } ================================================ FILE: core/src/color.rs ================================================ use crate::animation::Interpolable; /// A color in the `sRGB` color space. /// /// # String Representation /// /// A color can be represented in either of the following valid formats: `#rrggbb`, `#rrggbbaa`, `#rgb`, and `#rgba`. /// Where `rgba` represent hexadecimal digits. Both uppercase and lowercase letters are supported. /// /// If `a` (transparency) is not specified, `1.0` (completely opaque) would be used by default. /// /// If you have a static color string, using the [`color!`] macro should be preferred /// since it leverages hexadecimal literal notation and arithmetic directly. /// /// [`color!`]: crate::color! #[derive(Debug, Clone, Copy, PartialEq, Default)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[must_use] pub struct Color { /// Red component, 0.0 - 1.0 pub r: f32, /// Green component, 0.0 - 1.0 pub g: f32, /// Blue component, 0.0 - 1.0 pub b: f32, /// Transparency, 0.0 - 1.0 pub a: f32, } impl Color { /// The black color. pub const BLACK: Color = Color { r: 0.0, g: 0.0, b: 0.0, a: 1.0, }; /// The white color. pub const WHITE: Color = Color { r: 1.0, g: 1.0, b: 1.0, a: 1.0, }; /// A color with no opacity. pub const TRANSPARENT: Color = Color { r: 0.0, g: 0.0, b: 0.0, a: 0.0, }; /// Creates a new [`Color`]. /// /// In debug mode, it will panic if the values are not in the correct /// range: 0.0 - 1.0 const fn new(r: f32, g: f32, b: f32, a: f32) -> Color { debug_assert!( r >= 0.0 && r <= 1.0, "Red component must be in [0, 1] range." ); debug_assert!( g >= 0.0 && g <= 1.0, "Green component must be in [0, 1] range." ); debug_assert!( b >= 0.0 && b <= 1.0, "Blue component must be in [0, 1] range." ); Self { r, g, b, a } } /// Creates a [`Color`] from its RGB components. pub const fn from_rgb(r: f32, g: f32, b: f32) -> Self { Self::from_rgba(r, g, b, 1.0f32) } /// Creates a [`Color`] from its RGBA components. pub const fn from_rgba(r: f32, g: f32, b: f32, a: f32) -> Self { Self::new(r, g, b, a) } /// Creates a [`Color`] from its RGB8 components. pub const fn from_rgb8(r: u8, g: u8, b: u8) -> Self { Self::from_rgba8(r, g, b, 1.0) } /// Creates a [`Color`] from its RGB8 components and an alpha value. pub const fn from_rgba8(r: u8, g: u8, b: u8, a: f32) -> Self { Self::new(r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0, a) } /// Creates a [`Color`] from its RGB8 components packed in the lower bits of a `u32`. pub const fn from_packed_rgb8(rgb: u32) -> Self { Self::from_packed_rgba8(rgb, 1.0) } /// Creates a [`Color`] from its RGB8 components packed in the lower bits of a `u32` /// and an alpha value. pub const fn from_packed_rgba8(rgb: u32, a: f32) -> Self { let r = (rgb & 0xff0000) >> 16; let g = (rgb & 0xff00) >> 8; let b = rgb & 0xff; Self::from_rgba8(r as u8, g as u8, b as u8, a) } /// Creates a [`Color`] from its linear RGBA components. pub fn from_linear_rgba(r: f32, g: f32, b: f32, a: f32) -> Self { // As described in: // https://en.wikipedia.org/wiki/SRGB fn gamma_component(u: f32) -> f32 { if u < 0.0031308 { 12.92 * u } else { 1.055 * u.powf(1.0 / 2.4) - 0.055 } } Self::new( gamma_component(r), gamma_component(g), gamma_component(b), a, ) } /// Inverts the [`Color`] in-place. pub const fn invert(&mut self) { self.r = 1.0f32 - self.r; self.b = 1.0f32 - self.g; self.g = 1.0f32 - self.b; } /// Returns the inverted [`Color`]. pub const fn inverse(self) -> Self { Self::new(1.0f32 - self.r, 1.0f32 - self.g, 1.0f32 - self.b, self.a) } /// Scales the alpha channel of the [`Color`] by the given factor. pub const fn scale_alpha(self, factor: f32) -> Self { Self { a: self.a * factor, ..self } } /// Mixes the current [`Color`] with another one by the given factor. pub fn mix(self, b: Color, factor: f32) -> Color { let b_amount = factor.clamp(0.0, 1.0); let a_amount = 1.0 - b_amount; let a_linear = self.into_linear().map(|c| c * a_amount); let b_linear = b.into_linear().map(|c| c * b_amount); Color::from_linear_rgba( a_linear[0] + b_linear[0], a_linear[1] + b_linear[1], a_linear[2] + b_linear[2], a_linear[3] + b_linear[3], ) } /// Returns the relative luminance of the [`Color`]. /// #[must_use] pub fn relative_luminance(self) -> f32 { let linear = self.into_linear(); 0.2126 * linear[0] + 0.7152 * linear[1] + 0.0722 * linear[2] } /// Returns the [relative contrast ratio] of the [`Color`] against another one. /// /// [relative contrast ratio]: https://www.w3.org/TR/WCAG21/#dfn-contrast-ratio #[must_use] pub fn relative_contrast(self, b: Self) -> f32 { let lum_a = self.relative_luminance(); let lum_b = b.relative_luminance(); (lum_a.max(lum_b) + 0.05) / (lum_a.min(lum_b) + 0.05) } /// Returns true if the current [`Color`] is readable on top /// of the given background [`Color`]. #[must_use] pub fn is_readable_on(self, background: Self) -> bool { background.relative_contrast(self) >= 6.0 } /// Converts the [`Color`] into its RGBA8 equivalent. #[must_use] pub const fn into_rgba8(self) -> [u8; 4] { [ (self.r * 255.0).round() as u8, (self.g * 255.0).round() as u8, (self.b * 255.0).round() as u8, (self.a * 255.0).round() as u8, ] } /// Converts the [`Color`] into its linear values. #[must_use] pub fn into_linear(self) -> [f32; 4] { // As described in: // https://en.wikipedia.org/wiki/SRGB#The_reverse_transformation fn linear_component(u: f32) -> f32 { if u < 0.04045 { u / 12.92 } else { ((u + 0.055) / 1.055).powf(2.4) } } [ linear_component(self.r), linear_component(self.g), linear_component(self.b), self.a, ] } } impl From<[f32; 3]> for Color { fn from([r, g, b]: [f32; 3]) -> Self { Color::new(r, g, b, 1.0) } } impl From<[f32; 4]> for Color { fn from([r, g, b, a]: [f32; 4]) -> Self { Color::new(r, g, b, a) } } /// An error which can be returned when parsing color from an RGB hexadecimal string. /// /// See [`Color`] for specifications for the string. #[derive(Debug, thiserror::Error)] pub enum ParseError { /// The string could not be parsed to valid integers. #[error(transparent)] ParseIntError(#[from] std::num::ParseIntError), /// The string is of invalid length. #[error("expected hex string of length 3, 4, 6 or 8 excluding optional prefix '#', found {0}")] InvalidLength(usize), } impl std::str::FromStr for Color { type Err = ParseError; fn from_str(s: &str) -> Result { let hex = s.strip_prefix('#').unwrap_or(s); let parse_channel = |from: usize, to: usize| -> Result { let num = usize::from_str_radix(&hex[from..=to], 16)? as f32 / 255.0; // If we only got half a byte (one letter), expand it into a full byte (two letters) Ok(if from == to { num + num * 16.0 } else { num }) }; let val = match hex.len() { 3 => Color::from_rgb( parse_channel(0, 0)?, parse_channel(1, 1)?, parse_channel(2, 2)?, ), 4 => Color::from_rgba( parse_channel(0, 0)?, parse_channel(1, 1)?, parse_channel(2, 2)?, parse_channel(3, 3)?, ), 6 => Color::from_rgb( parse_channel(0, 1)?, parse_channel(2, 3)?, parse_channel(4, 5)?, ), 8 => Color::from_rgba( parse_channel(0, 1)?, parse_channel(2, 3)?, parse_channel(4, 5)?, parse_channel(6, 7)?, ), _ => return Err(ParseError::InvalidLength(hex.len())), }; Ok(val) } } impl std::fmt::Display for Color { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let [r, g, b, a] = self.into_rgba8(); if self.a == 1.0 { return write!(f, "#{r:02x}{g:02x}{b:02x}"); } write!(f, "#{r:02x}{g:02x}{b:02x}{a:02x}") } } impl Interpolable for Color { /// Interpolates the color. Equivalent to [`Color::mix`]. fn interpolated(&self, other: Self, ratio: f32) -> Self { self.mix(other, ratio) } } /// Creates a [`Color`] with shorter and cleaner syntax. /// /// # Examples /// /// ``` /// # use iced_core::{Color, color}; /// assert_eq!(color!(0, 0, 0), Color::BLACK); /// assert_eq!(color!(0, 0, 0, 0.0), Color::TRANSPARENT); /// assert_eq!(color!(0xffffff), Color::from_rgb(1.0, 1.0, 1.0)); /// assert_eq!(color!(0xffffff, 0.), Color::from_rgba(1.0, 1.0, 1.0, 0.0)); /// assert_eq!(color!(0x0000ff), Color::from_rgba(0.0, 0.0, 1.0, 1.0)); /// ``` #[macro_export] macro_rules! color { ($r:expr, $g:expr, $b:expr) => { $crate::Color::from_rgb8($r, $g, $b) }; ($r:expr, $g:expr, $b:expr, $a:expr) => {{ $crate::Color::from_rgba8($r, $g, $b, $a) }}; ($hex:literal) => {{ $crate::color!($hex, 1.0) }}; ($hex:literal, $a:expr) => {{ let mut hex = $hex as u32; // Shorthand notation: 0x123 if stringify!($hex).len() == 5 { let r = hex & 0xF00; let g = hex & 0xF0; let b = hex & 0xF; hex = (r << 12) | (r << 8) | (g << 8) | (g << 4) | (b << 4) | b; } debug_assert!(hex <= 0xffffff, "color! value must not exceed 0xffffff"); $crate::Color::from_packed_rgba8(hex, $a) }}; } #[cfg(test)] mod tests { use super::*; #[test] fn parse() { let tests = [ ("#ff0000", [255, 0, 0, 255], "#ff0000"), ("00ff0080", [0, 255, 0, 128], "#00ff0080"), ("#F80", [255, 136, 0, 255], "#ff8800"), ("#00f1", [0, 0, 255, 17], "#0000ff11"), ("#00ff", [0, 0, 255, 255], "#0000ff"), ]; for (arg, expected_rgba8, expected_str) in tests { let color = arg.parse::().expect("color must parse"); assert_eq!(color.into_rgba8(), expected_rgba8); assert_eq!(color.to_string(), expected_str); } assert!("invalid".parse::().is_err()); } const SHORTHAND: Color = color!(0x123); #[test] fn shorthand_notation() { assert_eq!(SHORTHAND, Color::from_rgb8(0x11, 0x22, 0x33)); } } ================================================ FILE: core/src/content_fit.rs ================================================ //! Control the fit of some content (like an image) within a space. use crate::Size; use std::fmt; /// The strategy used to fit the contents of a widget to its bounding box. /// /// Each variant of this enum is a strategy that can be applied for resolving /// differences in aspect ratio and size between the image being displayed and /// the space its being displayed in. /// /// For an interactive demonstration of these properties as they are implemented /// in CSS, see [Mozilla's docs][1], or run the `tour` example /// /// [1]: https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit #[derive(Debug, Hash, Clone, Copy, PartialEq, Eq, Default)] pub enum ContentFit { /// Scale as big as it can be without needing to crop or hide parts. /// /// The image will be scaled (preserving aspect ratio) so that it just fits /// within the window. This won't distort the image or crop/hide any edges, /// but if the image doesn't fit perfectly, there may be whitespace on the /// top/bottom or left/right. /// /// This is a great fit for when you need to display an image without losing /// any part of it, particularly when the image itself is the focus of the /// screen. #[default] Contain, /// Scale the image to cover all of the bounding box, cropping if needed. /// /// This doesn't distort the image, and it ensures that the widget's area is /// completely covered, but it might crop off a bit of the edges of the /// widget, particularly when there is a big difference between the aspect /// ratio of the widget and the aspect ratio of the image. /// /// This is best for when you're using an image as a background, or to fill /// space, and any details of the image around the edge aren't too /// important. Cover, /// Distort the image so the widget is 100% covered without cropping. /// /// This stretches the image to fit the widget, without any whitespace or /// cropping. However, because of the stretch, the image may look distorted /// or elongated, particularly when there's a mismatch of aspect ratios. Fill, /// Don't resize or scale the image at all. /// /// This will not apply any transformations to the provided image, but also /// means that unless you do the math yourself, the widget's area will not /// be completely covered, or the image might be cropped. /// /// This is best for when you've sized the image yourself. None, /// Scale the image down if it's too big for the space, but never scale it /// up. /// /// This works much like [`Contain`](Self::Contain), except that if the /// image would have been scaled up, it keeps its original resolution to /// avoid the bluring that accompanies upscaling images. ScaleDown, } impl ContentFit { /// Attempt to apply the given fit for a content size within some bounds. /// /// The returned value is the recommended scaled size of the content. pub fn fit(&self, content: Size, bounds: Size) -> Size { let content_ar = content.width / content.height; let bounds_ar = bounds.width / bounds.height; match self { Self::Contain => { if bounds_ar > content_ar { Size { width: content.width * bounds.height / content.height, ..bounds } } else { Size { height: content.height * bounds.width / content.width, ..bounds } } } Self::Cover => { if bounds_ar < content_ar { Size { width: content.width * bounds.height / content.height, ..bounds } } else { Size { height: content.height * bounds.width / content.width, ..bounds } } } Self::Fill => bounds, Self::None => content, Self::ScaleDown => { if bounds_ar > content_ar && bounds.height < content.height { Size { width: content.width * bounds.height / content.height, ..bounds } } else if bounds.width < content.width { Size { height: content.height * bounds.width / content.width, ..bounds } } else { content } } } } } impl fmt::Display for ContentFit { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(match self { ContentFit::Contain => "Contain", ContentFit::Cover => "Cover", ContentFit::Fill => "Fill", ContentFit::None => "None", ContentFit::ScaleDown => "Scale Down", }) } } ================================================ FILE: core/src/element.rs ================================================ use crate::layout; use crate::mouse; use crate::overlay; use crate::renderer; use crate::widget; use crate::widget::tree::{self, Tree}; use crate::{Border, Color, Event, Layout, Length, Rectangle, Shell, Size, Vector, Widget}; use std::borrow::Borrow; /// A generic [`Widget`]. /// /// It is useful to build composable user interfaces that do not leak /// implementation details in their __view logic__. /// /// If you have a [built-in widget], you should be able to use `Into` /// to turn it into an [`Element`]. /// /// [built-in widget]: crate::widget pub struct Element<'a, Message, Theme, Renderer> { widget: Box + 'a>, } impl<'a, Message, Theme, Renderer> Element<'a, Message, Theme, Renderer> { /// Creates a new [`Element`] containing the given [`Widget`]. pub fn new(widget: impl Widget + 'a) -> Self where Renderer: crate::Renderer, { Self { widget: Box::new(widget), } } /// Returns a reference to the [`Widget`] of the [`Element`], pub fn as_widget(&self) -> &dyn Widget { self.widget.as_ref() } /// Returns a mutable reference to the [`Widget`] of the [`Element`], pub fn as_widget_mut(&mut self) -> &mut dyn Widget { self.widget.as_mut() } /// Applies a transformation to the produced message of the [`Element`]. /// /// This method is useful when you want to decouple different parts of your /// UI and make them __composable__. /// /// # Example /// Imagine we want to use [our counter](index.html#usage). But instead of /// showing a single counter, we want to display many of them. We can reuse /// the `Counter` type as it is! /// /// We use composition to model the __state__ of our new application: /// /// ``` /// # mod counter { /// # pub struct Counter; /// # } /// use counter::Counter; /// /// struct ManyCounters { /// counters: Vec, /// } /// ``` /// /// We can store the state of multiple counters now. However, the /// __messages__ we implemented before describe the user interactions /// of a __single__ counter. Right now, we need to also identify which /// counter is receiving user interactions. Can we use composition again? /// Yes. /// /// ``` /// # mod counter { /// # #[derive(Debug, Clone, Copy)] /// # pub enum Message {} /// # } /// #[derive(Debug, Clone, Copy)] /// pub enum Message { /// Counter(usize, counter::Message) /// } /// ``` /// /// We compose the previous __messages__ with the index of the counter /// producing them. Let's implement our __view logic__ now: /// /// ```no_run /// # mod iced { /// # pub use iced_core::Function; /// # pub type Element<'a, Message> = iced_core::Element<'a, Message, iced_core::Theme, ()>; /// # /// # pub mod widget { /// # pub fn row<'a, Message>(iter: impl IntoIterator>) -> super::Element<'a, Message> { /// # unimplemented!() /// # } /// # } /// # } /// # /// # mod counter { /// # #[derive(Debug, Clone, Copy)] /// # pub enum Message {} /// # pub struct Counter; /// # /// # pub type Element<'a, Message> = iced_core::Element<'a, Message, iced_core::Theme, ()>; /// # /// # impl Counter { /// # pub fn view(&self) -> Element { /// # unimplemented!() /// # } /// # } /// # } /// # /// use counter::Counter; /// /// use iced::widget::row; /// use iced::{Element, Function}; /// /// struct ManyCounters { /// counters: Vec, /// } /// /// #[derive(Debug, Clone, Copy)] /// pub enum Message { /// Counter(usize, counter::Message), /// } /// /// impl ManyCounters { /// pub fn view(&self) -> Element { /// // We can quickly populate a `row` by mapping our counters /// row( /// self.counters /// .iter() /// .map(Counter::view) /// .enumerate() /// .map(|(index, counter)| { /// // Here we turn our `Element` into /// // an `Element` by combining the `index` and the /// // message of the `element`. /// counter.map(Message::Counter.with(index)) /// }), /// ) /// .into() /// } /// } /// ``` /// /// Finally, our __update logic__ is pretty straightforward: simple /// delegation. /// /// ``` /// # mod counter { /// # #[derive(Debug, Clone, Copy)] /// # pub enum Message {} /// # pub struct Counter; /// # /// # impl Counter { /// # pub fn update(&mut self, _message: Message) {} /// # } /// # } /// # /// # use counter::Counter; /// # /// # struct ManyCounters { /// # counters: Vec, /// # } /// # /// # #[derive(Debug, Clone, Copy)] /// # pub enum Message { /// # Counter(usize, counter::Message) /// # } /// impl ManyCounters { /// pub fn update(&mut self, message: Message) { /// match message { /// Message::Counter(index, counter_msg) => { /// if let Some(counter) = self.counters.get_mut(index) { /// counter.update(counter_msg); /// } /// } /// } /// } /// } /// ``` pub fn map(self, f: impl Fn(Message) -> B + 'a) -> Element<'a, B, Theme, Renderer> where Message: 'a, Theme: 'a, Renderer: crate::Renderer + 'a, B: 'a, { Element::new(Map::new(self.widget, f)) } /// Marks the [`Element`] as _to-be-explained_. /// /// The [`Renderer`] will explain the layout of the [`Element`] graphically. /// This can be very useful for debugging your layout! /// /// [`Renderer`]: crate::Renderer pub fn explain>(self, color: C) -> Element<'a, Message, Theme, Renderer> where Message: 'a, Theme: 'a, Renderer: crate::Renderer + 'a, { Element { widget: Box::new(Explain::new(self, color.into())), } } } impl<'a, Message, Theme, Renderer> Borrow + 'a> for Element<'a, Message, Theme, Renderer> { fn borrow(&self) -> &(dyn Widget + 'a) { self.widget.borrow() } } impl<'a, Message, Theme, Renderer> Borrow + 'a> for &Element<'a, Message, Theme, Renderer> { fn borrow(&self) -> &(dyn Widget + 'a) { self.widget.borrow() } } struct Map<'a, A, B, Theme, Renderer> { widget: Box + 'a>, mapper: Box B + 'a>, } impl<'a, A, B, Theme, Renderer> Map<'a, A, B, Theme, Renderer> { pub fn new( widget: Box + 'a>, mapper: F, ) -> Map<'a, A, B, Theme, Renderer> where F: 'a + Fn(A) -> B, { Map { widget, mapper: Box::new(mapper), } } } impl<'a, A, B, Theme, Renderer> Widget for Map<'a, A, B, Theme, Renderer> where Renderer: crate::Renderer + 'a, A: 'a, B: 'a, { fn tag(&self) -> tree::Tag { self.widget.tag() } fn state(&self) -> tree::State { self.widget.state() } fn children(&self) -> Vec { self.widget.children() } fn diff(&self, tree: &mut Tree) { self.widget.diff(tree); } fn size(&self) -> Size { self.widget.size() } fn size_hint(&self) -> Size { self.widget.size_hint() } fn layout( &mut self, tree: &mut Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { self.widget.layout(tree, renderer, limits) } fn operate( &mut self, tree: &mut Tree, layout: Layout<'_>, renderer: &Renderer, operation: &mut dyn widget::Operation, ) { self.widget.operate(tree, layout, renderer, operation); } fn update( &mut self, tree: &mut Tree, event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, shell: &mut Shell<'_, B>, viewport: &Rectangle, ) { let mut local_messages = Vec::new(); let mut local_shell = Shell::new(&mut local_messages); self.widget.update( tree, event, layout, cursor, renderer, &mut local_shell, viewport, ); shell.merge(local_shell, &self.mapper); } fn draw( &self, tree: &Tree, renderer: &mut Renderer, theme: &Theme, style: &renderer::Style, layout: Layout<'_>, cursor: mouse::Cursor, viewport: &Rectangle, ) { self.widget .draw(tree, renderer, theme, style, layout, cursor, viewport); } fn mouse_interaction( &self, tree: &Tree, layout: Layout<'_>, cursor: mouse::Cursor, viewport: &Rectangle, renderer: &Renderer, ) -> mouse::Interaction { self.widget .mouse_interaction(tree, layout, cursor, viewport, renderer) } fn overlay<'b>( &'b mut self, tree: &'b mut Tree, layout: Layout<'b>, renderer: &Renderer, viewport: &Rectangle, translation: Vector, ) -> Option> { let mapper = &self.mapper; self.widget .overlay(tree, layout, renderer, viewport, translation) .map(move |overlay| overlay.map(mapper)) } } struct Explain<'a, Message, Theme, Renderer: crate::Renderer> { element: Element<'a, Message, Theme, Renderer>, color: Color, } impl<'a, Message, Theme, Renderer> Explain<'a, Message, Theme, Renderer> where Renderer: crate::Renderer, { fn new(element: Element<'a, Message, Theme, Renderer>, color: Color) -> Self { Explain { element, color } } } impl Widget for Explain<'_, Message, Theme, Renderer> where Renderer: crate::Renderer, { fn size(&self) -> Size { self.element.widget.size() } fn size_hint(&self) -> Size { self.element.widget.size_hint() } fn tag(&self) -> tree::Tag { self.element.widget.tag() } fn state(&self) -> tree::State { self.element.widget.state() } fn children(&self) -> Vec { self.element.widget.children() } fn diff(&self, tree: &mut Tree) { self.element.widget.diff(tree); } fn layout( &mut self, tree: &mut Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { self.element.widget.layout(tree, renderer, limits) } fn operate( &mut self, tree: &mut Tree, layout: Layout<'_>, renderer: &Renderer, operation: &mut dyn widget::Operation, ) { self.element .widget .operate(tree, layout, renderer, operation); } fn update( &mut self, tree: &mut Tree, event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, shell: &mut Shell<'_, Message>, viewport: &Rectangle, ) { self.element .widget .update(tree, event, layout, cursor, renderer, shell, viewport); } fn draw( &self, tree: &Tree, renderer: &mut Renderer, theme: &Theme, style: &renderer::Style, layout: Layout<'_>, cursor: mouse::Cursor, viewport: &Rectangle, ) { fn explain_layout( renderer: &mut Renderer, color: Color, layout: Layout<'_>, ) { renderer.fill_quad( renderer::Quad { bounds: layout.bounds(), border: Border { color, width: 1.0, ..Border::default() }, ..renderer::Quad::default() }, Color::TRANSPARENT, ); for child in layout.children() { explain_layout(renderer, color, child); } } self.element .widget .draw(tree, renderer, theme, style, layout, cursor, viewport); renderer.with_layer(Rectangle::INFINITE, |renderer| { explain_layout(renderer, self.color, layout); }); } fn mouse_interaction( &self, tree: &Tree, layout: Layout<'_>, cursor: mouse::Cursor, viewport: &Rectangle, renderer: &Renderer, ) -> mouse::Interaction { self.element .widget .mouse_interaction(tree, layout, cursor, viewport, renderer) } fn overlay<'b>( &'b mut self, tree: &'b mut Tree, layout: Layout<'b>, renderer: &Renderer, viewport: &Rectangle, translation: Vector, ) -> Option> { self.element .widget .overlay(tree, layout, renderer, viewport, translation) } } impl<'a, T, Message, Theme, Renderer> From> for Element<'a, Message, Theme, Renderer> where T: Into, Renderer: crate::Renderer, { fn from(element: Option) -> Self { struct Void; impl Widget for Void where Renderer: crate::Renderer, { fn size(&self) -> Size { Size { width: Length::Fixed(0.0), height: Length::Fixed(0.0), } } fn layout( &mut self, _tree: &mut Tree, _renderer: &Renderer, _limits: &layout::Limits, ) -> layout::Node { layout::Node::new(Size::ZERO) } fn draw( &self, _tree: &Tree, _renderer: &mut Renderer, _theme: &Theme, _style: &renderer::Style, _layout: Layout<'_>, _cursor: mouse::Cursor, _viewport: &Rectangle, ) { } } element.map(T::into).unwrap_or_else(|| Element::new(Void)) } } ================================================ FILE: core/src/event.rs ================================================ //! Handle events of a user interface. use crate::clipboard; use crate::input_method; use crate::keyboard; use crate::mouse; use crate::touch; use crate::window; /// A user interface event. /// /// _**Note:** This type is largely incomplete! If you need to track /// additional events, feel free to [open an issue] and share your use case!_ /// /// [open an issue]: https://github.com/iced-rs/iced/issues #[derive(Debug, Clone, PartialEq)] pub enum Event { /// A keyboard event Keyboard(keyboard::Event), /// A mouse event Mouse(mouse::Event), /// A window event Window(window::Event), /// A touch event Touch(touch::Event), /// An input method event InputMethod(input_method::Event), /// A clipboard event Clipboard(clipboard::Event), } /// The status of an [`Event`] after being processed. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Status { /// The [`Event`] was **NOT** handled by any widget. Ignored, /// The [`Event`] was handled and processed by a widget. Captured, } impl Status { /// Merges two [`Status`] into one. /// /// `Captured` takes precedence over `Ignored`: /// /// ``` /// use iced_core::event::Status; /// /// assert_eq!(Status::Ignored.merge(Status::Ignored), Status::Ignored); /// assert_eq!(Status::Ignored.merge(Status::Captured), Status::Captured); /// assert_eq!(Status::Captured.merge(Status::Ignored), Status::Captured); /// assert_eq!(Status::Captured.merge(Status::Captured), Status::Captured); /// ``` pub fn merge(self, b: Self) -> Self { match self { Status::Ignored => b, Status::Captured => Status::Captured, } } } ================================================ FILE: core/src/font.rs ================================================ //! Load and use fonts. use std::hash::Hash; /// A font. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] pub struct Font { /// The [`Family`] of the [`Font`]. pub family: Family, /// The [`Weight`] of the [`Font`]. pub weight: Weight, /// The [`Stretch`] of the [`Font`]. pub stretch: Stretch, /// The [`Style`] of the [`Font`]. pub style: Style, } impl Font { /// A non-monospaced sans-serif font with normal [`Weight`]. pub const DEFAULT: Font = Font { family: Family::SansSerif, weight: Weight::Normal, stretch: Stretch::Normal, style: Style::Normal, }; /// A monospaced font with normal [`Weight`]. pub const MONOSPACE: Font = Font { family: Family::Monospace, ..Self::DEFAULT }; /// Creates a [`Font`] with the given [`Family::Name`] and default attributes. pub const fn new(name: &'static str) -> Self { Self { family: Family::Name(name), ..Self::DEFAULT } } /// Creates a [`Font`] with the given [`Family`] and default attributes. pub fn with_family(family: impl Into) -> Self { Font { family: family.into(), ..Self::DEFAULT } } /// Sets the [`Weight`] of the [`Font`]. pub const fn weight(self, weight: Weight) -> Self { Self { weight, ..self } } /// Sets the [`Stretch`] of the [`Font`]. pub const fn stretch(self, stretch: Stretch) -> Self { Self { stretch, ..self } } /// Sets the [`Style`] of the [`Font`]. pub const fn style(self, style: Style) -> Self { Self { style, ..self } } } impl From<&'static str> for Font { fn from(name: &'static str) -> Self { Font::new(name) } } impl From for Font { fn from(family: Family) -> Self { Font::with_family(family) } } /// A font family. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] pub enum Family { /// The name of a font family of choice. Name(&'static str), /// Serif fonts represent the formal text style for a script. Serif, /// Glyphs in sans-serif fonts, as the term is used in CSS, are generally low /// contrast and have stroke endings that are plain — without any flaring, /// cross stroke, or other ornamentation. #[default] SansSerif, /// Glyphs in cursive fonts generally use a more informal script style, and /// the result looks more like handwritten pen or brush writing than printed /// letterwork. Cursive, /// Fantasy fonts are primarily decorative or expressive fonts that contain /// decorative or expressive representations of characters. Fantasy, /// The sole criterion of a monospace font is that all glyphs have the same /// fixed width. Monospace, } impl Family { /// A list of all the different standalone family variants. pub const VARIANTS: &[Self] = &[ Self::Serif, Self::SansSerif, Self::Cursive, Self::Fantasy, Self::Monospace, ]; /// Creates a [`Family::Name`] from the given string. /// /// The name is interned in a global cache and never freed. pub fn name(name: &str) -> Self { use rustc_hash::FxHashSet; use std::sync::{LazyLock, Mutex}; static NAMES: LazyLock>> = LazyLock::new(Mutex::default); let mut names = NAMES.lock().expect("lock font name cache"); let Some(name) = names.get(name) else { let name: &'static str = name.to_owned().leak(); let _ = names.insert(name); return Self::Name(name); }; Self::Name(name) } } impl From<&str> for Family { fn from(name: &str) -> Self { Family::name(name) } } impl std::fmt::Display for Family { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(match self { Family::Name(name) => name, Family::Serif => "Serif", Family::SansSerif => "Sans-serif", Family::Cursive => "Cursive", Family::Fantasy => "Fantasy", Family::Monospace => "Monospace", }) } } /// The weight of some text. #[allow(missing_docs)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] pub enum Weight { Thin, ExtraLight, Light, #[default] Normal, Medium, Semibold, Bold, ExtraBold, Black, } /// The width of some text. #[allow(missing_docs)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] pub enum Stretch { UltraCondensed, ExtraCondensed, Condensed, SemiCondensed, #[default] Normal, SemiExpanded, Expanded, ExtraExpanded, UltraExpanded, } /// The style of some text. #[allow(missing_docs)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] pub enum Style { #[default] Normal, Italic, Oblique, } /// A font error. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Error {} ================================================ FILE: core/src/gradient.rs ================================================ //! Colors that transition progressively. use crate::{Color, Radians}; use std::cmp::Ordering; #[derive(Debug, Clone, Copy, PartialEq)] /// A fill which transitions colors progressively along a direction, either linearly, radially (TBD), /// or conically (TBD). pub enum Gradient { /// A linear gradient interpolates colors along a direction at a specific angle. Linear(Linear), } impl Gradient { /// Scales the alpha channel of the [`Gradient`] by the given factor. pub fn scale_alpha(self, factor: f32) -> Self { match self { Gradient::Linear(linear) => Gradient::Linear(linear.scale_alpha(factor)), } } } impl From for Gradient { fn from(gradient: Linear) -> Self { Self::Linear(gradient) } } #[derive(Debug, Default, Clone, Copy, PartialEq)] /// A point along the gradient vector where the specified [`color`] is unmixed. /// /// [`color`]: Self::color pub struct ColorStop { /// Offset along the gradient vector. pub offset: f32, /// The color of the gradient at the specified [`offset`]. /// /// [`offset`]: Self::offset pub color: Color, } /// A linear gradient. #[derive(Debug, Clone, Copy, PartialEq)] pub struct Linear { /// How the [`Gradient`] is angled within its bounds. pub angle: Radians, /// [`ColorStop`]s along the linear gradient path. pub stops: [Option; 8], } impl Linear { /// Creates a new [`Linear`] gradient with the given angle in [`Radians`]. pub fn new(angle: impl Into) -> Self { Self { angle: angle.into(), stops: [None; 8], } } /// Adds a new [`ColorStop`], defined by an offset and a color, to the gradient. /// /// Any `offset` that is not within `0.0..=1.0` will be silently ignored. /// /// Any stop added after the 8th will be silently ignored. pub fn add_stop(mut self, offset: f32, color: Color) -> Self { if offset.is_finite() && (0.0..=1.0).contains(&offset) { let (Ok(index) | Err(index)) = self.stops.binary_search_by(|stop| match stop { None => Ordering::Greater, Some(stop) => stop.offset.partial_cmp(&offset).unwrap(), }); if index < 8 { self.stops[index] = Some(ColorStop { offset, color }); } } else { log::warn!("Gradient color stop must be within 0.0..=1.0 range."); }; self } /// Adds multiple [`ColorStop`]s to the gradient. /// /// Any stop added after the 8th will be silently ignored. pub fn add_stops(mut self, stops: impl IntoIterator) -> Self { for stop in stops { self = self.add_stop(stop.offset, stop.color); } self } /// Scales the alpha channel of the [`Linear`] gradient by the given /// factor. pub fn scale_alpha(mut self, factor: f32) -> Self { for stop in self.stops.iter_mut().flatten() { stop.color.a *= factor; } self } } ================================================ FILE: core/src/image.rs ================================================ //! Load and draw raster graphics. use crate::border; use crate::{Bytes, Radians, Rectangle, Size}; use rustc_hash::FxHasher; use std::hash::{Hash, Hasher}; use std::io; use std::path::{Path, PathBuf}; use std::sync::{Arc, Weak}; /// A raster image that can be drawn. #[derive(Debug, Clone, PartialEq)] pub struct Image { /// The handle of the image. pub handle: H, /// The filter method of the image. pub filter_method: FilterMethod, /// The rotation to be applied to the image; on its center. pub rotation: Radians, /// The border radius of the [`Image`]. /// /// Currently, this will only be applied to the `clip_bounds`. pub border_radius: border::Radius, /// The opacity of the image. /// /// 0 means transparent. 1 means opaque. pub opacity: f32, } impl Image { /// Creates a new [`Image`] with the given handle. pub fn new(handle: impl Into) -> Self { Self { handle: handle.into(), filter_method: FilterMethod::default(), rotation: Radians(0.0), border_radius: border::Radius::default(), opacity: 1.0, } } /// Sets the filter method of the [`Image`]. pub fn filter_method(mut self, filter_method: FilterMethod) -> Self { self.filter_method = filter_method; self } /// Sets the rotation of the [`Image`]. pub fn rotation(mut self, rotation: impl Into) -> Self { self.rotation = rotation.into(); self } /// Sets the opacity of the [`Image`]. pub fn opacity(mut self, opacity: impl Into) -> Self { self.opacity = opacity.into(); self } } impl From<&Handle> for Image { fn from(handle: &Handle) -> Self { Image::new(handle.clone()) } } /// A handle of some image data. #[derive(Clone, PartialEq, Eq)] pub enum Handle { /// A file handle. The image data will be read /// from the file path. /// /// Use [`from_path`] to create this variant. /// /// [`from_path`]: Self::from_path Path(Id, PathBuf), /// A handle pointing to some encoded image bytes in-memory. /// /// Use [`from_bytes`] to create this variant. /// /// [`from_bytes`]: Self::from_bytes Bytes(Id, Bytes), /// A handle pointing to decoded image pixels in RGBA format. /// /// Use [`from_rgba`] to create this variant. /// /// [`from_rgba`]: Self::from_rgba Rgba { /// The id of this handle. id: Id, /// The width of the image. width: u32, /// The height of the image. height: u32, /// The pixels. pixels: Bytes, }, } impl Handle { /// Creates an image [`Handle`] pointing to the image of the given path. /// /// Makes an educated guess about the image format by examining the data in the file. pub fn from_path>(path: T) -> Handle { let path = path.into(); Self::Path(Id::path(&path), path) } /// Creates an image [`Handle`] containing the encoded image data directly. /// /// Makes an educated guess about the image format by examining the given data. /// /// This is useful if you already have your image loaded in-memory, maybe /// because you downloaded or generated it procedurally. pub fn from_bytes(bytes: impl Into) -> Handle { Self::Bytes(Id::unique(), bytes.into()) } /// Creates an image [`Handle`] containing the decoded image pixels directly. /// /// This function expects the pixel data to be provided as a collection of [`Bytes`] /// of RGBA pixels. Therefore, the length of the pixel data should always be /// `width * height * 4`. /// /// This is useful if you have already decoded your image. pub fn from_rgba(width: u32, height: u32, pixels: impl Into) -> Handle { Self::Rgba { id: Id::unique(), width, height, pixels: pixels.into(), } } /// Returns the unique identifier of the [`Handle`]. pub fn id(&self) -> Id { match self { Handle::Path(id, _) | Handle::Bytes(id, _) | Handle::Rgba { id, .. } => *id, } } } impl From for Handle where T: Into, { fn from(path: T) -> Handle { Handle::from_path(path.into()) } } impl From<&Handle> for Handle { fn from(value: &Handle) -> Self { value.clone() } } impl std::fmt::Debug for Handle { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Path(id, path) => write!(f, "Path({id:?}, {path:?})"), Self::Bytes(id, _) => write!(f, "Bytes({id:?}, ...)"), Self::Rgba { id, width, height, .. } => { write!(f, "Pixels({id:?}, {width} * {height})") } } } } /// The unique identifier of some [`Handle`] data. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Id(_Id); #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] enum _Id { Unique(u64), Hash(u64), } impl Id { fn unique() -> Self { use std::sync::atomic::{self, AtomicU64}; static NEXT_ID: AtomicU64 = AtomicU64::new(0); Self(_Id::Unique(NEXT_ID.fetch_add(1, atomic::Ordering::Relaxed))) } fn path(path: impl AsRef) -> Self { let hash = { let mut hasher = FxHasher::default(); path.as_ref().hash(&mut hasher); hasher.finish() }; Self(_Id::Hash(hash)) } } /// Image filtering strategy. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] pub enum FilterMethod { /// Bilinear interpolation. #[default] Linear, /// Nearest neighbor. Nearest, } /// A memory allocation of a [`Handle`], often in GPU memory. /// /// Renderers tend to decode and upload image data concurrently to /// avoid blocking the user interface. This means that when you use a /// [`Handle`] in a widget, there may be a slight frame delay until it /// is finally visible. If you are animating images, this can cause /// undesirable flicker. /// /// When you obtain an [`Allocation`] explicitly, you get the guarantee /// that using a [`Handle`] will draw the corresponding [`Image`] /// immediately in the next frame. /// /// This guarantee is valid as long as you hold an [`Allocation`]. /// Only when you drop all its clones, the renderer may choose to free /// the memory of the [`Handle`]. Be careful! #[derive(Debug, Clone, PartialEq, Eq)] pub struct Allocation(Arc); /// Some memory taken by an [`Allocation`]. #[derive(Debug, Clone, PartialEq, Eq)] pub struct Memory { handle: Handle, size: Size, } impl Allocation { /// Returns a weak reference to the [`Memory`] of the [`Allocation`]. pub fn downgrade(&self) -> Weak { Arc::downgrade(&self.0) } /// Upgrades a [`Weak`] memory reference to an [`Allocation`]. pub fn upgrade(weak: &Weak) -> Option { Weak::upgrade(weak).map(Allocation) } /// Returns the [`Handle`] of this [`Allocation`]. pub fn handle(&self) -> &Handle { &self.0.handle } /// Returns the [`Size`] of the image of this [`Allocation`]. pub fn size(&self) -> Size { self.0.size } } /// Creates a new [`Allocation`] for the given handle. /// /// This should only be used internally by renderer implementations. /// /// # Safety /// Must only be created once the [`Handle`] is allocated in memory. #[allow(unsafe_code)] pub unsafe fn allocate(handle: &Handle, size: Size) -> Allocation { Allocation(Arc::new(Memory { handle: handle.clone(), size, })) } /// A [`Renderer`] that can render raster graphics. /// /// [renderer]: crate::renderer pub trait Renderer: crate::Renderer { /// The image Handle to be displayed. Iced exposes its own default implementation of a [`Handle`] /// /// [`Handle`]: Self::Handle type Handle: Clone; /// Loads an image and returns an explicit [`Allocation`] to it. /// /// If the image is not already loaded, this method will block! You should /// generally not use it in drawing logic if you want to avoid frame drops. fn load_image(&self, handle: &Self::Handle) -> Result; /// Returns the dimensions of an image for the given [`Handle`]. /// /// If the image is not already loaded, the [`Renderer`] may choose to return /// `None`, load the image in the background, and then trigger a relayout. /// /// If you need a measurement right away, consider using [`Renderer::load_image`]. fn measure_image(&self, handle: &Self::Handle) -> Option>; /// Draws an [`Image`] inside the provided `bounds`. /// /// If the image is not already loaded, the [`Renderer`] may choose to render /// nothing, load the image in the background, and then trigger a redraw. /// /// If you need to draw an image right away, consider using [`Renderer::load_image`] /// and hold on to an [`Allocation`] first. fn draw_image(&mut self, image: Image, bounds: Rectangle, clip_bounds: Rectangle); } /// An image loading error. #[derive(Debug, Clone, thiserror::Error)] pub enum Error { /// The image data was invalid or could not be decoded. #[error("the image data was invalid or could not be decoded: {0}")] Invalid(Arc), /// The image file was not found. #[error("the image file could not be opened: {0}")] Inaccessible(Arc), /// Loading images is unsupported. #[error("loading images is unsupported")] Unsupported, /// The image is empty. #[error("the image is empty")] Empty, /// Not enough memory to allocate the image. #[error("not enough memory to allocate the image")] OutOfMemory, } ================================================ FILE: core/src/input_method.rs ================================================ //! Listen to input method events. use crate::{Pixels, Rectangle}; use std::ops::Range; /// The input method strategy of a widget. #[derive(Debug, Clone, PartialEq)] pub enum InputMethod { /// Input method is disabled. Disabled, /// Input method is enabled. Enabled { /// The area of the cursor of the input method. /// /// This area should not be covered. cursor: Rectangle, /// The [`Purpose`] of the input method. purpose: Purpose, /// The preedit to overlay on top of the input method dialog, if needed. /// /// Ideally, your widget will show pre-edits on-the-spot; but, since that can /// be tricky, you can instead provide the current pre-edit here and the /// runtime will display it as an overlay (i.e. "Over-the-spot IME"). preedit: Option>, }, } /// The pre-edit of an [`InputMethod`]. #[derive(Debug, Clone, PartialEq, Default)] pub struct Preedit { /// The current content. pub content: T, /// The selected range of the content. pub selection: Option>, /// The text size of the content. pub text_size: Option, } impl Preedit { /// Creates a new empty [`Preedit`]. pub fn new() -> Self where T: Default, { Self::default() } /// Turns a [`Preedit`] into its owned version. pub fn to_owned(&self) -> Preedit where T: AsRef, { Preedit { content: self.content.as_ref().to_owned(), selection: self.selection.clone(), text_size: self.text_size, } } } impl Preedit { /// Borrows the contents of a [`Preedit`]. pub fn as_ref(&self) -> Preedit<&str> { Preedit { content: &self.content, selection: self.selection.clone(), text_size: self.text_size, } } } /// The purpose of an [`InputMethod`]. #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum Purpose { /// No special hints for the IME (default). #[default] Normal, /// The IME is used for secure input (e.g. passwords). Secure, /// The IME is used to input into a terminal. /// /// For example, that could alter OSK on Wayland to show extra buttons. Terminal, } impl InputMethod { /// Merges two [`InputMethod`] strategies, prioritizing the first one when both open: /// ``` /// # use iced_core::input_method::{InputMethod, Purpose, Preedit}; /// # use iced_core::{Point, Rectangle, Size}; /// /// let open = InputMethod::Enabled { /// cursor: Rectangle::new(Point::ORIGIN, Size::UNIT), /// purpose: Purpose::Normal, /// preedit: Some(Preedit { content: "1".to_owned(), selection: None, text_size: None }), /// }; /// /// let open_2 = InputMethod::Enabled { /// cursor: Rectangle::new(Point::ORIGIN, Size::UNIT), /// purpose: Purpose::Secure, /// preedit: Some(Preedit { content: "2".to_owned(), selection: None, text_size: None }), /// }; /// /// let mut ime = InputMethod::Disabled; /// /// ime.merge(&open); /// assert_eq!(ime, open); /// /// ime.merge(&open_2); /// assert_eq!(ime, open); /// ``` pub fn merge>(&mut self, other: &InputMethod) { if let InputMethod::Enabled { .. } = self { return; } *self = other.to_owned(); } /// Returns true if the [`InputMethod`] is open. pub fn is_enabled(&self) -> bool { matches!(self, Self::Enabled { .. }) } } impl InputMethod { /// Turns an [`InputMethod`] into its owned version. pub fn to_owned(&self) -> InputMethod where T: AsRef, { match self { Self::Disabled => InputMethod::Disabled, Self::Enabled { cursor, purpose, preedit, } => InputMethod::Enabled { cursor: *cursor, purpose: *purpose, preedit: preedit.as_ref().map(Preedit::to_owned), }, } } } /// Describes [input method](https://en.wikipedia.org/wiki/Input_method) events. /// /// This is also called a "composition event". /// /// Most keypresses using a latin-like keyboard layout simply generate a /// [`keyboard::Event::KeyPressed`](crate::keyboard::Event::KeyPressed). /// However, one couldn't possibly have a key for every single /// unicode character that the user might want to type. The solution operating systems employ is /// to allow the user to type these using _a sequence of keypresses_ instead. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum Event { /// Notifies when the IME was opened. /// /// After getting this event you could receive [`Preedit`][Self::Preedit] and /// [`Commit`][Self::Commit] events. You should also start performing IME related requests /// like [`Shell::request_input_method`]. /// /// [`Shell::request_input_method`]: crate::Shell::request_input_method Opened, /// Notifies when a new composing text should be set at the cursor position. /// /// The value represents a pair of the preedit string and the cursor begin position and end /// position. When it's `None`, the cursor should be hidden. When `String` is an empty string /// this indicates that preedit was cleared. /// /// The cursor range is byte-wise indexed. Preedit(String, Option>), /// Notifies when text should be inserted into the editor widget. /// /// Right before this event, an empty [`Self::Preedit`] event will be issued. Commit(String), /// Notifies when the IME was disabled. /// /// After receiving this event you won't get any more [`Preedit`][Self::Preedit] or /// [`Commit`][Self::Commit] events until the next [`Opened`][Self::Opened] event. You should /// also stop issuing IME related requests like [`Shell::request_input_method`] and clear /// pending preedit text. /// /// [`Shell::request_input_method`]: crate::Shell::request_input_method Closed, } ================================================ FILE: core/src/keyboard/event.rs ================================================ use crate::SmolStr; use crate::keyboard::key; use crate::keyboard::{Key, Location, Modifiers}; /// A keyboard event. /// /// _**Note:** This type is largely incomplete! If you need to track /// additional events, feel free to [open an issue] and share your use case!_ /// /// [open an issue]: https://github.com/iced-rs/iced/issues #[derive(Debug, Clone, PartialEq, Eq)] pub enum Event { /// A keyboard key was pressed. KeyPressed { /// The key pressed. key: Key, /// The key pressed with all keyboard modifiers applied, except Ctrl. modified_key: Key, /// The physical key pressed. physical_key: key::Physical, /// The location of the key. location: Location, /// The state of the modifier keys. modifiers: Modifiers, /// The text produced by the key press, if any. text: Option, /// Whether the event was the result of key repeat. repeat: bool, }, /// A keyboard key was released. KeyReleased { /// The key released. key: Key, /// The key released with all keyboard modifiers applied, except Ctrl. modified_key: Key, /// The physical key released. physical_key: key::Physical, /// The location of the key. location: Location, /// The state of the modifier keys. modifiers: Modifiers, }, /// The keyboard modifiers have changed. ModifiersChanged(Modifiers), } ================================================ FILE: core/src/keyboard/key.rs ================================================ //! Identify keyboard keys. use crate::SmolStr; /// A key on the keyboard. /// /// This is mostly the `Key` type found in [`winit`]. /// /// [`winit`]: https://docs.rs/winit/0.30/winit/keyboard/enum.Key.html #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum Key { /// A key with an established name. Named(Named), /// A key string that corresponds to the character typed by the user, taking into account the /// user’s current locale setting, and any system-level keyboard mapping overrides that are in /// effect. Character(C), /// An unidentified key. Unidentified, } impl Key { /// Convert `Key::Character(SmolStr)` to `Key::Character(&str)` so you can more easily match on /// `Key`. All other variants remain unchanged. pub fn as_ref(&self) -> Key<&str> { match self { Self::Named(named) => Key::Named(*named), Self::Character(c) => Key::Character(c.as_ref()), Self::Unidentified => Key::Unidentified, } } /// Tries to convert this logical [`Key`] into its latin character, using the /// [`Physical`] key provided for translation if it isn't already in latin. /// /// Returns `None` if no latin variant could be found. /// /// ``` /// use iced_core::keyboard::key::{Key, Named, Physical, Code}; /// /// // Latin c /// assert_eq!( /// Key::Character("c".into()).to_latin(Physical::Code(Code::KeyC)), /// Some('c'), /// ); /// /// // Cyrillic с /// assert_eq!( /// Key::Character("с".into()).to_latin(Physical::Code(Code::KeyC)), /// Some('c'), /// ); /// /// // Arrow Left /// assert_eq!( /// Key::Named(Named::ArrowLeft).to_latin(Physical::Code(Code::ArrowLeft)), /// None, /// ); /// ``` pub fn to_latin(&self, physical_key: Physical) -> Option { let Self::Character(s) = self else { return None; }; let mut chars = s.chars(); let c = chars.next()?; if chars.next().is_none() && c < '\u{370}' { return Some(c); } let Physical::Code(code) = physical_key else { return None; }; let latin = match code { Code::KeyA => 'a', Code::KeyB => 'b', Code::KeyC => 'c', Code::KeyD => 'd', Code::KeyE => 'e', Code::KeyF => 'f', Code::KeyG => 'g', Code::KeyH => 'h', Code::KeyI => 'i', Code::KeyJ => 'j', Code::KeyK => 'k', Code::KeyL => 'l', Code::KeyM => 'm', Code::KeyN => 'n', Code::KeyO => 'o', Code::KeyP => 'p', Code::KeyQ => 'q', Code::KeyR => 'r', Code::KeyS => 's', Code::KeyT => 't', Code::KeyU => 'u', Code::KeyV => 'v', Code::KeyW => 'w', Code::KeyX => 'x', Code::KeyY => 'y', Code::KeyZ => 'z', Code::Digit0 => '0', Code::Digit1 => '1', Code::Digit2 => '2', Code::Digit3 => '3', Code::Digit4 => '4', Code::Digit5 => '5', Code::Digit6 => '6', Code::Digit7 => '7', Code::Digit8 => '8', Code::Digit9 => '9', _ => return None, }; Some(latin) } } impl From for Key { fn from(named: Named) -> Self { Self::Named(named) } } /// A named key. /// /// This is mostly the `NamedKey` type found in [`winit`]. /// /// [`winit`]: https://docs.rs/winit/0.30/winit/keyboard/enum.Key.html #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[allow(missing_docs)] pub enum Named { /// The `Alt` (Alternative) key. /// /// This key enables the alternate modifier function for interpreting concurrent or subsequent /// keyboard input. This key value is also used for the Apple Option key. Alt, /// The Alternate Graphics (AltGr or AltGraph) key. /// /// This key is used enable the ISO Level 3 shift modifier (the standard `Shift` key is the /// level 2 modifier). AltGraph, /// The `Caps Lock` (Capital) key. /// /// Toggle capital character lock function for interpreting subsequent keyboard input event. CapsLock, /// The `Control` or `Ctrl` key. /// /// Used to enable control modifier function for interpreting concurrent or subsequent keyboard /// input. Control, /// The Function switch `Fn` key. Activating this key simultaneously with another key changes /// that key’s value to an alternate character or function. This key is often handled directly /// in the keyboard hardware and does not usually generate key events. Fn, /// The Function-Lock (`FnLock` or `F-Lock`) key. Activating this key switches the mode of the /// keyboard to changes some keys' values to an alternate character or function. This key is /// often handled directly in the keyboard hardware and does not usually generate key events. FnLock, /// The `NumLock` or Number Lock key. Used to toggle numpad mode function for interpreting /// subsequent keyboard input. NumLock, /// Toggle between scrolling and cursor movement modes. ScrollLock, /// Used to enable shift modifier function for interpreting concurrent or subsequent keyboard /// input. Shift, /// The Symbol modifier key (used on some virtual keyboards). Symbol, SymbolLock, // Legacy modifier key. Also called "Super" in certain places. Meta, // Legacy modifier key. Hyper, /// Used to enable "super" modifier function for interpreting concurrent or subsequent keyboard /// input. This key value is used for the "Windows Logo" key and the Apple `Command` or `⌘` key. /// /// Note: In some contexts (e.g. the Web) this is referred to as the "Meta" key. Super, /// The `Enter` or `↵` key. Used to activate current selection or accept current input. This key /// value is also used for the `Return` (Macintosh numpad) key. This key value is also used for /// the Android `KEYCODE_DPAD_CENTER`. Enter, /// The Horizontal Tabulation `Tab` key. Tab, /// Used in text to insert a space between words. Usually located below the character keys. Space, /// Navigate or traverse downward. (`KEYCODE_DPAD_DOWN`) ArrowDown, /// Navigate or traverse leftward. (`KEYCODE_DPAD_LEFT`) ArrowLeft, /// Navigate or traverse rightward. (`KEYCODE_DPAD_RIGHT`) ArrowRight, /// Navigate or traverse upward. (`KEYCODE_DPAD_UP`) ArrowUp, /// The End key, used with keyboard entry to go to the end of content (`KEYCODE_MOVE_END`). End, /// The Home key, used with keyboard entry, to go to start of content (`KEYCODE_MOVE_HOME`). /// For the mobile phone `Home` key (which goes to the phone’s main screen), use [`GoHome`]. /// /// [`GoHome`]: Self::GoHome Home, /// Scroll down or display next page of content. PageDown, /// Scroll up or display previous page of content. PageUp, /// Used to remove the character to the left of the cursor. This key value is also used for /// the key labeled `Delete` on MacOS keyboards. Backspace, /// Remove the currently selected input. Clear, /// Copy the current selection. (`APPCOMMAND_COPY`) Copy, /// The Cursor Select key. CrSel, /// Cut the current selection. (`APPCOMMAND_CUT`) Cut, /// Used to delete the character to the right of the cursor. This key value is also used for the /// key labeled `Delete` on MacOS keyboards when `Fn` is active. Delete, /// The Erase to End of Field key. This key deletes all characters from the current cursor /// position to the end of the current field. EraseEof, /// The Extend Selection (Exsel) key. ExSel, /// Toggle between text modes for insertion or overtyping. /// (`KEYCODE_INSERT`) Insert, /// The Paste key. (`APPCOMMAND_PASTE`) Paste, /// Redo the last action. (`APPCOMMAND_REDO`) Redo, /// Undo the last action. (`APPCOMMAND_UNDO`) Undo, /// The Accept (Commit, OK) key. Accept current option or input method sequence conversion. Accept, /// Redo or repeat an action. Again, /// The Attention (Attn) key. Attn, Cancel, /// Show the application’s context menu. /// This key is commonly found between the right `Super` key and the right `Control` key. ContextMenu, /// The `Esc` key. This key was originally used to initiate an escape sequence, but is /// now more generally used to exit or "escape" the current context, such as closing a dialog /// or exiting full screen mode. Escape, Execute, /// Open the Find dialog. (`APPCOMMAND_FIND`) Find, /// Open a help dialog or toggle display of help information. (`APPCOMMAND_HELP`, /// `KEYCODE_HELP`) Help, /// Pause the current state or application (as appropriate). /// /// Note: Do not use this value for the `Pause` button on media controllers. Use `"MediaPause"` /// instead. Pause, /// Play or resume the current state or application (as appropriate). /// /// Note: Do not use this value for the `Play` button on media controllers. Use `"MediaPlay"` /// instead. Play, /// The properties (Props) key. Props, Select, /// The ZoomIn key. (`KEYCODE_ZOOM_IN`) ZoomIn, /// The ZoomOut key. (`KEYCODE_ZOOM_OUT`) ZoomOut, /// The Brightness Down key. Typically controls the display brightness. /// (`KEYCODE_BRIGHTNESS_DOWN`) BrightnessDown, /// The Brightness Up key. Typically controls the display brightness. (`KEYCODE_BRIGHTNESS_UP`) BrightnessUp, /// Toggle removable media to eject (open) and insert (close) state. (`KEYCODE_MEDIA_EJECT`) Eject, LogOff, /// Toggle power state. (`KEYCODE_POWER`) /// Note: Note: Some devices might not expose this key to the operating environment. Power, /// The `PowerOff` key. Sometime called `PowerDown`. PowerOff, /// Initiate print-screen function. PrintScreen, /// The Hibernate key. This key saves the current state of the computer to disk so that it can /// be restored. The computer will then shutdown. Hibernate, /// The Standby key. This key turns off the display and places the computer into a low-power /// mode without completely shutting down. It is sometimes labelled `Suspend` or `Sleep` key. /// (`KEYCODE_SLEEP`) Standby, /// The WakeUp key. (`KEYCODE_WAKEUP`) WakeUp, /// Initiate the multi-candidate mode. AllCandidates, Alphanumeric, /// Initiate the Code Input mode to allow characters to be entered by /// their code points. CodeInput, /// The Compose key, also known as "Multi_key" on the X Window System. This key acts in a /// manner similar to a dead key, triggering a mode where subsequent key presses are combined to /// produce a different character. Compose, /// Convert the current input method sequence. Convert, /// The Final Mode `Final` key used on some Asian keyboards, to enable the final mode for IMEs. FinalMode, /// Switch to the first character group. (ISO/IEC 9995) GroupFirst, /// Switch to the last character group. (ISO/IEC 9995) GroupLast, /// Switch to the next character group. (ISO/IEC 9995) GroupNext, /// Switch to the previous character group. (ISO/IEC 9995) GroupPrevious, /// Toggle between or cycle through input modes of IMEs. ModeChange, NextCandidate, /// Accept current input method sequence without /// conversion in IMEs. NonConvert, PreviousCandidate, Process, SingleCandidate, /// Toggle between Hangul and English modes. HangulMode, HanjaMode, JunjaMode, /// The Eisu key. This key may close the IME, but its purpose is defined by the current IME. /// (`KEYCODE_EISU`) Eisu, /// The (Half-Width) Characters key. Hankaku, /// The Hiragana (Japanese Kana characters) key. Hiragana, /// The Hiragana/Katakana toggle key. (`KEYCODE_KATAKANA_HIRAGANA`) HiraganaKatakana, /// The Kana Mode (Kana Lock) key. This key is used to enter hiragana mode (typically from /// romaji mode). KanaMode, /// The Kanji (Japanese name for ideographic characters of Chinese origin) Mode key. This key is /// typically used to switch to a hiragana keyboard for the purpose of converting input into /// kanji. (`KEYCODE_KANA`) KanjiMode, /// The Katakana (Japanese Kana characters) key. Katakana, /// The Roman characters function key. Romaji, /// The Zenkaku (Full-Width) Characters key. Zenkaku, /// The Zenkaku/Hankaku (full-width/half-width) toggle key. (`KEYCODE_ZENKAKU_HANKAKU`) ZenkakuHankaku, /// General purpose virtual function key, as index 1. Soft1, /// General purpose virtual function key, as index 2. Soft2, /// General purpose virtual function key, as index 3. Soft3, /// General purpose virtual function key, as index 4. Soft4, /// Select next (numerically or logically) lower channel. (`APPCOMMAND_MEDIA_CHANNEL_DOWN`, /// `KEYCODE_CHANNEL_DOWN`) ChannelDown, /// Select next (numerically or logically) higher channel. (`APPCOMMAND_MEDIA_CHANNEL_UP`, /// `KEYCODE_CHANNEL_UP`) ChannelUp, /// Close the current document or message (Note: This doesn’t close the application). /// (`APPCOMMAND_CLOSE`) Close, /// Open an editor to forward the current message. (`APPCOMMAND_FORWARD_MAIL`) MailForward, /// Open an editor to reply to the current message. (`APPCOMMAND_REPLY_TO_MAIL`) MailReply, /// Send the current message. (`APPCOMMAND_SEND_MAIL`) MailSend, /// Close the current media, for example to close a CD or DVD tray. (`KEYCODE_MEDIA_CLOSE`) MediaClose, /// Initiate or continue forward playback at faster than normal speed, or increase speed if /// already fast forwarding. (`APPCOMMAND_MEDIA_FAST_FORWARD`, `KEYCODE_MEDIA_FAST_FORWARD`) MediaFastForward, /// Pause the currently playing media. (`APPCOMMAND_MEDIA_PAUSE`, `KEYCODE_MEDIA_PAUSE`) /// /// Note: Media controller devices should use this value rather than `"Pause"` for their pause /// keys. MediaPause, /// Initiate or continue media playback at normal speed, if not currently playing at normal /// speed. (`APPCOMMAND_MEDIA_PLAY`, `KEYCODE_MEDIA_PLAY`) MediaPlay, /// Toggle media between play and pause states. (`APPCOMMAND_MEDIA_PLAY_PAUSE`, /// `KEYCODE_MEDIA_PLAY_PAUSE`) MediaPlayPause, /// Initiate or resume recording of currently selected media. (`APPCOMMAND_MEDIA_RECORD`, /// `KEYCODE_MEDIA_RECORD`) MediaRecord, /// Initiate or continue reverse playback at faster than normal speed, or increase speed if /// already rewinding. (`APPCOMMAND_MEDIA_REWIND`, `KEYCODE_MEDIA_REWIND`) MediaRewind, /// Stop media playing, pausing, forwarding, rewinding, or recording, if not already stopped. /// (`APPCOMMAND_MEDIA_STOP`, `KEYCODE_MEDIA_STOP`) MediaStop, /// Seek to next media or program track. (`APPCOMMAND_MEDIA_NEXTTRACK`, `KEYCODE_MEDIA_NEXT`) MediaTrackNext, /// Seek to previous media or program track. (`APPCOMMAND_MEDIA_PREVIOUSTRACK`, /// `KEYCODE_MEDIA_PREVIOUS`) MediaTrackPrevious, /// Open a new document or message. (`APPCOMMAND_NEW`) New, /// Open an existing document or message. (`APPCOMMAND_OPEN`) Open, /// Print the current document or message. (`APPCOMMAND_PRINT`) Print, /// Save the current document or message. (`APPCOMMAND_SAVE`) Save, /// Spellcheck the current document or selection. (`APPCOMMAND_SPELL_CHECK`) SpellCheck, /// The `11` key found on media numpads that /// have buttons from `1` ... `12`. Key11, /// The `12` key found on media numpads that /// have buttons from `1` ... `12`. Key12, /// Adjust audio balance leftward. (`VK_AUDIO_BALANCE_LEFT`) AudioBalanceLeft, /// Adjust audio balance rightward. (`VK_AUDIO_BALANCE_RIGHT`) AudioBalanceRight, /// Decrease audio bass boost or cycle down through bass boost states. (`APPCOMMAND_BASS_DOWN`, /// `VK_BASS_BOOST_DOWN`) AudioBassBoostDown, /// Toggle bass boost on/off. (`APPCOMMAND_BASS_BOOST`) AudioBassBoostToggle, /// Increase audio bass boost or cycle up through bass boost states. (`APPCOMMAND_BASS_UP`, /// `VK_BASS_BOOST_UP`) AudioBassBoostUp, /// Adjust audio fader towards front. (`VK_FADER_FRONT`) AudioFaderFront, /// Adjust audio fader towards rear. (`VK_FADER_REAR`) AudioFaderRear, /// Advance surround audio mode to next available mode. (`VK_SURROUND_MODE_NEXT`) AudioSurroundModeNext, /// Decrease treble. (`APPCOMMAND_TREBLE_DOWN`) AudioTrebleDown, /// Increase treble. (`APPCOMMAND_TREBLE_UP`) AudioTrebleUp, /// Decrease audio volume. (`APPCOMMAND_VOLUME_DOWN`, `KEYCODE_VOLUME_DOWN`) AudioVolumeDown, /// Increase audio volume. (`APPCOMMAND_VOLUME_UP`, `KEYCODE_VOLUME_UP`) AudioVolumeUp, /// Toggle between muted state and prior volume level. (`APPCOMMAND_VOLUME_MUTE`, /// `KEYCODE_VOLUME_MUTE`) AudioVolumeMute, /// Toggle the microphone on/off. (`APPCOMMAND_MIC_ON_OFF_TOGGLE`) MicrophoneToggle, /// Decrease microphone volume. (`APPCOMMAND_MICROPHONE_VOLUME_DOWN`) MicrophoneVolumeDown, /// Increase microphone volume. (`APPCOMMAND_MICROPHONE_VOLUME_UP`) MicrophoneVolumeUp, /// Mute the microphone. (`APPCOMMAND_MICROPHONE_VOLUME_MUTE`, `KEYCODE_MUTE`) MicrophoneVolumeMute, /// Show correction list when a word is incorrectly identified. (`APPCOMMAND_CORRECTION_LIST`) SpeechCorrectionList, /// Toggle between dictation mode and command/control mode. /// (`APPCOMMAND_DICTATE_OR_COMMAND_CONTROL_TOGGLE`) SpeechInputToggle, /// The first generic "LaunchApplication" key. This is commonly associated with launching "My /// Computer", and may have a computer symbol on the key. (`APPCOMMAND_LAUNCH_APP1`) LaunchApplication1, /// The second generic "LaunchApplication" key. This is commonly associated with launching /// "Calculator", and may have a calculator symbol on the key. (`APPCOMMAND_LAUNCH_APP2`, /// `KEYCODE_CALCULATOR`) LaunchApplication2, /// The "Calendar" key. (`KEYCODE_CALENDAR`) LaunchCalendar, /// The "Contacts" key. (`KEYCODE_CONTACTS`) LaunchContacts, /// The "Mail" key. (`APPCOMMAND_LAUNCH_MAIL`) LaunchMail, /// The "Media Player" key. (`APPCOMMAND_LAUNCH_MEDIA_SELECT`) LaunchMediaPlayer, LaunchMusicPlayer, LaunchPhone, LaunchScreenSaver, LaunchSpreadsheet, LaunchWebBrowser, LaunchWebCam, LaunchWordProcessor, /// Navigate to previous content or page in current history. (`APPCOMMAND_BROWSER_BACKWARD`) BrowserBack, /// Open the list of browser favorites. (`APPCOMMAND_BROWSER_FAVORITES`) BrowserFavorites, /// Navigate to next content or page in current history. (`APPCOMMAND_BROWSER_FORWARD`) BrowserForward, /// Go to the user’s preferred home page. (`APPCOMMAND_BROWSER_HOME`) BrowserHome, /// Refresh the current page or content. (`APPCOMMAND_BROWSER_REFRESH`) BrowserRefresh, /// Call up the user’s preferred search page. (`APPCOMMAND_BROWSER_SEARCH`) BrowserSearch, /// Stop loading the current page or content. (`APPCOMMAND_BROWSER_STOP`) BrowserStop, /// The Application switch key, which provides a list of recent apps to switch between. /// (`KEYCODE_APP_SWITCH`) AppSwitch, /// The Call key. (`KEYCODE_CALL`) Call, /// The Camera key. (`KEYCODE_CAMERA`) Camera, /// The Camera focus key. (`KEYCODE_FOCUS`) CameraFocus, /// The End Call key. (`KEYCODE_ENDCALL`) EndCall, /// The Back key. (`KEYCODE_BACK`) GoBack, /// The Home key, which goes to the phone’s main screen. (`KEYCODE_HOME`) GoHome, /// The Headset Hook key. (`KEYCODE_HEADSETHOOK`) HeadsetHook, LastNumberRedial, /// The Notification key. (`KEYCODE_NOTIFICATION`) Notification, /// Toggle between manner mode state: silent, vibrate, ring, ... (`KEYCODE_MANNER_MODE`) MannerMode, VoiceDial, /// Switch to viewing TV. (`KEYCODE_TV`) TV, /// TV 3D Mode. (`KEYCODE_3D_MODE`) TV3DMode, /// Toggle between antenna and cable input. (`KEYCODE_TV_ANTENNA_CABLE`) TVAntennaCable, /// Audio description. (`KEYCODE_TV_AUDIO_DESCRIPTION`) TVAudioDescription, /// Audio description mixing volume down. (`KEYCODE_TV_AUDIO_DESCRIPTION_MIX_DOWN`) TVAudioDescriptionMixDown, /// Audio description mixing volume up. (`KEYCODE_TV_AUDIO_DESCRIPTION_MIX_UP`) TVAudioDescriptionMixUp, /// Contents menu. (`KEYCODE_TV_CONTENTS_MENU`) TVContentsMenu, /// Contents menu. (`KEYCODE_TV_DATA_SERVICE`) TVDataService, /// Switch the input mode on an external TV. (`KEYCODE_TV_INPUT`) TVInput, /// Switch to component input #1. (`KEYCODE_TV_INPUT_COMPONENT_1`) TVInputComponent1, /// Switch to component input #2. (`KEYCODE_TV_INPUT_COMPONENT_2`) TVInputComponent2, /// Switch to composite input #1. (`KEYCODE_TV_INPUT_COMPOSITE_1`) TVInputComposite1, /// Switch to composite input #2. (`KEYCODE_TV_INPUT_COMPOSITE_2`) TVInputComposite2, /// Switch to HDMI input #1. (`KEYCODE_TV_INPUT_HDMI_1`) TVInputHDMI1, /// Switch to HDMI input #2. (`KEYCODE_TV_INPUT_HDMI_2`) TVInputHDMI2, /// Switch to HDMI input #3. (`KEYCODE_TV_INPUT_HDMI_3`) TVInputHDMI3, /// Switch to HDMI input #4. (`KEYCODE_TV_INPUT_HDMI_4`) TVInputHDMI4, /// Switch to VGA input #1. (`KEYCODE_TV_INPUT_VGA_1`) TVInputVGA1, /// Media context menu. (`KEYCODE_TV_MEDIA_CONTEXT_MENU`) TVMediaContext, /// Toggle network. (`KEYCODE_TV_NETWORK`) TVNetwork, /// Number entry. (`KEYCODE_TV_NUMBER_ENTRY`) TVNumberEntry, /// Toggle the power on an external TV. (`KEYCODE_TV_POWER`) TVPower, /// Radio. (`KEYCODE_TV_RADIO_SERVICE`) TVRadioService, /// Satellite. (`KEYCODE_TV_SATELLITE`) TVSatellite, /// Broadcast Satellite. (`KEYCODE_TV_SATELLITE_BS`) TVSatelliteBS, /// Communication Satellite. (`KEYCODE_TV_SATELLITE_CS`) TVSatelliteCS, /// Toggle between available satellites. (`KEYCODE_TV_SATELLITE_SERVICE`) TVSatelliteToggle, /// Analog Terrestrial. (`KEYCODE_TV_TERRESTRIAL_ANALOG`) TVTerrestrialAnalog, /// Digital Terrestrial. (`KEYCODE_TV_TERRESTRIAL_DIGITAL`) TVTerrestrialDigital, /// Timer programming. (`KEYCODE_TV_TIMER_PROGRAMMING`) TVTimer, /// Switch the input mode on an external AVR (audio/video receiver). (`KEYCODE_AVR_INPUT`) AVRInput, /// Toggle the power on an external AVR (audio/video receiver). (`KEYCODE_AVR_POWER`) AVRPower, /// General purpose color-coded media function key, as index 0 (red). (`VK_COLORED_KEY_0`, /// `KEYCODE_PROG_RED`) ColorF0Red, /// General purpose color-coded media function key, as index 1 (green). (`VK_COLORED_KEY_1`, /// `KEYCODE_PROG_GREEN`) ColorF1Green, /// General purpose color-coded media function key, as index 2 (yellow). (`VK_COLORED_KEY_2`, /// `KEYCODE_PROG_YELLOW`) ColorF2Yellow, /// General purpose color-coded media function key, as index 3 (blue). (`VK_COLORED_KEY_3`, /// `KEYCODE_PROG_BLUE`) ColorF3Blue, /// General purpose color-coded media function key, as index 4 (grey). (`VK_COLORED_KEY_4`) ColorF4Grey, /// General purpose color-coded media function key, as index 5 (brown). (`VK_COLORED_KEY_5`) ColorF5Brown, /// Toggle the display of Closed Captions. (`VK_CC`, `KEYCODE_CAPTIONS`) ClosedCaptionToggle, /// Adjust brightness of device, by toggling between or cycling through states. (`VK_DIMMER`) Dimmer, /// Swap video sources. (`VK_DISPLAY_SWAP`) DisplaySwap, /// Select Digital Video Rrecorder. (`KEYCODE_DVR`) DVR, /// Exit the current application. (`VK_EXIT`) Exit, /// Clear program or content stored as favorite 0. (`VK_CLEAR_FAVORITE_0`) FavoriteClear0, /// Clear program or content stored as favorite 1. (`VK_CLEAR_FAVORITE_1`) FavoriteClear1, /// Clear program or content stored as favorite 2. (`VK_CLEAR_FAVORITE_2`) FavoriteClear2, /// Clear program or content stored as favorite 3. (`VK_CLEAR_FAVORITE_3`) FavoriteClear3, /// Select (recall) program or content stored as favorite 0. (`VK_RECALL_FAVORITE_0`) FavoriteRecall0, /// Select (recall) program or content stored as favorite 1. (`VK_RECALL_FAVORITE_1`) FavoriteRecall1, /// Select (recall) program or content stored as favorite 2. (`VK_RECALL_FAVORITE_2`) FavoriteRecall2, /// Select (recall) program or content stored as favorite 3. (`VK_RECALL_FAVORITE_3`) FavoriteRecall3, /// Store current program or content as favorite 0. (`VK_STORE_FAVORITE_0`) FavoriteStore0, /// Store current program or content as favorite 1. (`VK_STORE_FAVORITE_1`) FavoriteStore1, /// Store current program or content as favorite 2. (`VK_STORE_FAVORITE_2`) FavoriteStore2, /// Store current program or content as favorite 3. (`VK_STORE_FAVORITE_3`) FavoriteStore3, /// Toggle display of program or content guide. (`VK_GUIDE`, `KEYCODE_GUIDE`) Guide, /// If guide is active and displayed, then display next day’s content. (`VK_NEXT_DAY`) GuideNextDay, /// If guide is active and displayed, then display previous day’s content. (`VK_PREV_DAY`) GuidePreviousDay, /// Toggle display of information about currently selected context or media. (`VK_INFO`, /// `KEYCODE_INFO`) Info, /// Toggle instant replay. (`VK_INSTANT_REPLAY`) InstantReplay, /// Launch linked content, if available and appropriate. (`VK_LINK`) Link, /// List the current program. (`VK_LIST`) ListProgram, /// Toggle display listing of currently available live content or programs. (`VK_LIVE`) LiveContent, /// Lock or unlock current content or program. (`VK_LOCK`) Lock, /// Show a list of media applications: audio/video players and image viewers. (`VK_APPS`) /// /// Note: Do not confuse this key value with the Windows' `VK_APPS` / `VK_CONTEXT_MENU` key, /// which is encoded as `"ContextMenu"`. MediaApps, /// Audio track key. (`KEYCODE_MEDIA_AUDIO_TRACK`) MediaAudioTrack, /// Select previously selected channel or media. (`VK_LAST`, `KEYCODE_LAST_CHANNEL`) MediaLast, /// Skip backward to next content or program. (`KEYCODE_MEDIA_SKIP_BACKWARD`) MediaSkipBackward, /// Skip forward to next content or program. (`VK_SKIP`, `KEYCODE_MEDIA_SKIP_FORWARD`) MediaSkipForward, /// Step backward to next content or program. (`KEYCODE_MEDIA_STEP_BACKWARD`) MediaStepBackward, /// Step forward to next content or program. (`KEYCODE_MEDIA_STEP_FORWARD`) MediaStepForward, /// Media top menu. (`KEYCODE_MEDIA_TOP_MENU`) MediaTopMenu, /// Navigate in. (`KEYCODE_NAVIGATE_IN`) NavigateIn, /// Navigate to next key. (`KEYCODE_NAVIGATE_NEXT`) NavigateNext, /// Navigate out. (`KEYCODE_NAVIGATE_OUT`) NavigateOut, /// Navigate to previous key. (`KEYCODE_NAVIGATE_PREVIOUS`) NavigatePrevious, /// Cycle to next favorite channel (in favorites list). (`VK_NEXT_FAVORITE_CHANNEL`) NextFavoriteChannel, /// Cycle to next user profile (if there are multiple user profiles). (`VK_USER`) NextUserProfile, /// Access on-demand content or programs. (`VK_ON_DEMAND`) OnDemand, /// Pairing key to pair devices. (`KEYCODE_PAIRING`) Pairing, /// Move picture-in-picture window down. (`VK_PINP_DOWN`) PinPDown, /// Move picture-in-picture window. (`VK_PINP_MOVE`) PinPMove, /// Toggle display of picture-in-picture window. (`VK_PINP_TOGGLE`) PinPToggle, /// Move picture-in-picture window up. (`VK_PINP_UP`) PinPUp, /// Decrease media playback speed. (`VK_PLAY_SPEED_DOWN`) PlaySpeedDown, /// Reset playback to normal speed. (`VK_PLAY_SPEED_RESET`) PlaySpeedReset, /// Increase media playback speed. (`VK_PLAY_SPEED_UP`) PlaySpeedUp, /// Toggle random media or content shuffle mode. (`VK_RANDOM_TOGGLE`) RandomToggle, /// Not a physical key, but this key code is sent when the remote control battery is low. /// (`VK_RC_LOW_BATTERY`) RcLowBattery, /// Toggle or cycle between media recording speeds. (`VK_RECORD_SPEED_NEXT`) RecordSpeedNext, /// Toggle RF (radio frequency) input bypass mode (pass RF input directly to the RF output). /// (`VK_RF_BYPASS`) RfBypass, /// Toggle scan channels mode. (`VK_SCAN_CHANNELS_TOGGLE`) ScanChannelsToggle, /// Advance display screen mode to next available mode. (`VK_SCREEN_MODE_NEXT`) ScreenModeNext, /// Toggle display of device settings screen. (`VK_SETTINGS`, `KEYCODE_SETTINGS`) Settings, /// Toggle split screen mode. (`VK_SPLIT_SCREEN_TOGGLE`) SplitScreenToggle, /// Switch the input mode on an external STB (set top box). (`KEYCODE_STB_INPUT`) STBInput, /// Toggle the power on an external STB (set top box). (`KEYCODE_STB_POWER`) STBPower, /// Toggle display of subtitles, if available. (`VK_SUBTITLE`) Subtitle, /// Toggle display of teletext, if available (`VK_TELETEXT`, `KEYCODE_TV_TELETEXT`). Teletext, /// Advance video mode to next available mode. (`VK_VIDEO_MODE_NEXT`) VideoModeNext, /// Cause device to identify itself in some manner, e.g., audibly or visibly. (`VK_WINK`) Wink, /// Toggle between full-screen and scaled content, or alter magnification level. (`VK_ZOOM`, /// `KEYCODE_TV_ZOOM_MODE`) ZoomToggle, /// General-purpose function key. /// Usually found at the top of the keyboard. F1, /// General-purpose function key. /// Usually found at the top of the keyboard. F2, /// General-purpose function key. /// Usually found at the top of the keyboard. F3, /// General-purpose function key. /// Usually found at the top of the keyboard. F4, /// General-purpose function key. /// Usually found at the top of the keyboard. F5, /// General-purpose function key. /// Usually found at the top of the keyboard. F6, /// General-purpose function key. /// Usually found at the top of the keyboard. F7, /// General-purpose function key. /// Usually found at the top of the keyboard. F8, /// General-purpose function key. /// Usually found at the top of the keyboard. F9, /// General-purpose function key. /// Usually found at the top of the keyboard. F10, /// General-purpose function key. /// Usually found at the top of the keyboard. F11, /// General-purpose function key. /// Usually found at the top of the keyboard. F12, /// General-purpose function key. /// Usually found at the top of the keyboard. F13, /// General-purpose function key. /// Usually found at the top of the keyboard. F14, /// General-purpose function key. /// Usually found at the top of the keyboard. F15, /// General-purpose function key. /// Usually found at the top of the keyboard. F16, /// General-purpose function key. /// Usually found at the top of the keyboard. F17, /// General-purpose function key. /// Usually found at the top of the keyboard. F18, /// General-purpose function key. /// Usually found at the top of the keyboard. F19, /// General-purpose function key. /// Usually found at the top of the keyboard. F20, /// General-purpose function key. /// Usually found at the top of the keyboard. F21, /// General-purpose function key. /// Usually found at the top of the keyboard. F22, /// General-purpose function key. /// Usually found at the top of the keyboard. F23, /// General-purpose function key. /// Usually found at the top of the keyboard. F24, /// General-purpose function key. F25, /// General-purpose function key. F26, /// General-purpose function key. F27, /// General-purpose function key. F28, /// General-purpose function key. F29, /// General-purpose function key. F30, /// General-purpose function key. F31, /// General-purpose function key. F32, /// General-purpose function key. F33, /// General-purpose function key. F34, /// General-purpose function key. F35, } /// Code representing the location of a physical key. /// /// This mostly conforms to the UI Events Specification's [`KeyboardEvent.code`] with a few /// exceptions: /// - The keys that the specification calls "MetaLeft" and "MetaRight" are named "SuperLeft" and /// "SuperRight" here. /// - The key that the specification calls "Super" is reported as `Unidentified` here. /// /// [`KeyboardEvent.code`]: https://w3c.github.io/uievents-code/#code-value-tables #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[allow(missing_docs)] #[non_exhaustive] pub enum Code { /// ` on a US keyboard. This is also called a backtick or grave. /// This is the 半角/全角/漢字 /// (hankaku/zenkaku/kanji) key on Japanese keyboards Backquote, /// Used for both the US \\ (on the 101-key layout) and also for the key /// located between the " and Enter keys on row C of the 102-, /// 104- and 106-key layouts. /// Labeled # on a UK (102) keyboard. Backslash, /// [ on a US keyboard. BracketLeft, /// ] on a US keyboard. BracketRight, /// , on a US keyboard. Comma, /// 0 on a US keyboard. Digit0, /// 1 on a US keyboard. Digit1, /// 2 on a US keyboard. Digit2, /// 3 on a US keyboard. Digit3, /// 4 on a US keyboard. Digit4, /// 5 on a US keyboard. Digit5, /// 6 on a US keyboard. Digit6, /// 7 on a US keyboard. Digit7, /// 8 on a US keyboard. Digit8, /// 9 on a US keyboard. Digit9, /// = on a US keyboard. Equal, /// Located between the left Shift and Z keys. /// Labeled \\ on a UK keyboard. IntlBackslash, /// Located between the / and right Shift keys. /// Labeled \\ (ro) on a Japanese keyboard. IntlRo, /// Located between the = and Backspace keys. /// Labeled ¥ (yen) on a Japanese keyboard. \\ on a /// Russian keyboard. IntlYen, /// a on a US keyboard. /// Labeled q on an AZERTY (e.g., French) keyboard. KeyA, /// b on a US keyboard. KeyB, /// c on a US keyboard. KeyC, /// d on a US keyboard. KeyD, /// e on a US keyboard. KeyE, /// f on a US keyboard. KeyF, /// g on a US keyboard. KeyG, /// h on a US keyboard. KeyH, /// i on a US keyboard. KeyI, /// j on a US keyboard. KeyJ, /// k on a US keyboard. KeyK, /// l on a US keyboard. KeyL, /// m on a US keyboard. KeyM, /// n on a US keyboard. KeyN, /// o on a US keyboard. KeyO, /// p on a US keyboard. KeyP, /// q on a US keyboard. /// Labeled a on an AZERTY (e.g., French) keyboard. KeyQ, /// r on a US keyboard. KeyR, /// s on a US keyboard. KeyS, /// t on a US keyboard. KeyT, /// u on a US keyboard. KeyU, /// v on a US keyboard. KeyV, /// w on a US keyboard. /// Labeled z on an AZERTY (e.g., French) keyboard. KeyW, /// x on a US keyboard. KeyX, /// y on a US keyboard. /// Labeled z on a QWERTZ (e.g., German) keyboard. KeyY, /// z on a US keyboard. /// Labeled w on an AZERTY (e.g., French) keyboard, and y on a /// QWERTZ (e.g., German) keyboard. KeyZ, /// - on a US keyboard. Minus, /// . on a US keyboard. Period, /// ' on a US keyboard. Quote, /// ; on a US keyboard. Semicolon, /// / on a US keyboard. Slash, /// Alt, Option, or . AltLeft, /// Alt, Option, or . /// This is labeled AltGr on many keyboard layouts. AltRight, /// Backspace or . /// Labeled Delete on Apple keyboards. Backspace, /// CapsLock or CapsLock, /// The application context menu key, which is typically found between the right /// Super key and the right Control key. ContextMenu, /// Control or ControlLeft, /// Control or ControlRight, /// Enter or . Labeled Return on Apple keyboards. Enter, /// The Windows, , Command, or other OS symbol key. SuperLeft, /// The Windows, , Command, or other OS symbol key. SuperRight, /// Shift or ShiftLeft, /// Shift or ShiftRight, /// (space) Space, /// Tab or Tab, /// Japanese: (henkan) Convert, /// Japanese: カタカナ/ひらがな/ローマ字 /// (katakana/hiragana/romaji) KanaMode, /// Korean: HangulMode 한/영 (han/yeong) /// /// Japanese (Mac keyboard): (kana) Lang1, /// Korean: Hanja (hanja) /// /// Japanese (Mac keyboard): (eisu) Lang2, /// Japanese (word-processing keyboard): Katakana Lang3, /// Japanese (word-processing keyboard): Hiragana Lang4, /// Japanese (word-processing keyboard): Zenkaku/Hankaku Lang5, /// Japanese: 無変換 (muhenkan) NonConvert, /// . The forward delete key. /// Note that on Apple keyboards, the key labelled Delete on the main part of /// the keyboard is encoded as [`Backspace`]. /// /// [`Backspace`]: Self::Backspace Delete, /// Page Down, End, or End, /// Help. Not present on standard PC keyboards. Help, /// Home or Home, /// Insert or Ins. Not present on Apple keyboards. Insert, /// Page Down, PgDn, or PageDown, /// Page Up, PgUp, or PageUp, /// ArrowDown, /// ArrowLeft, /// ArrowRight, /// ArrowUp, /// On the Mac, this is used for the numpad Clear key. NumLock, /// 0 Ins on a keyboard. 0 on a phone or remote control Numpad0, /// 1 End on a keyboard. 1 or 1 QZ on a phone or remote /// control Numpad1, /// 2 ↓ on a keyboard. 2 ABC on a phone or remote control Numpad2, /// 3 PgDn on a keyboard. 3 DEF on a phone or remote control Numpad3, /// 4 ← on a keyboard. 4 GHI on a phone or remote control Numpad4, /// 5 on a keyboard. 5 JKL on a phone or remote control Numpad5, /// 6 → on a keyboard. 6 MNO on a phone or remote control Numpad6, /// 7 Home on a keyboard. 7 PQRS or 7 PRS on a phone /// or remote control Numpad7, /// 8 ↑ on a keyboard. 8 TUV on a phone or remote control Numpad8, /// 9 PgUp on a keyboard. 9 WXYZ or 9 WXY on a phone /// or remote control Numpad9, /// + NumpadAdd, /// Found on the Microsoft Natural Keyboard. NumpadBackspace, /// C or A (All Clear). Also for use with numpads that have a /// Clear key that is separate from the NumLock key. On the Mac, the /// numpad Clear key is encoded as [`NumLock`]. /// /// [`NumLock`]: Self::NumLock NumpadClear, /// C (Clear Entry) NumpadClearEntry, /// , (thousands separator). For locales where the thousands separator /// is a "." (e.g., Brazil), this key may generate a .. NumpadComma, /// . Del. For locales where the decimal separator is "," (e.g., /// Brazil), this key may generate a ,. NumpadDecimal, /// / NumpadDivide, NumpadEnter, /// = NumpadEqual, /// # on a phone or remote control device. This key is typically found /// below the 9 key and to the right of the 0 key. NumpadHash, /// M Add current entry to the value stored in memory. NumpadMemoryAdd, /// M Clear the value stored in memory. NumpadMemoryClear, /// M Replace the current entry with the value stored in memory. NumpadMemoryRecall, /// M Replace the value stored in memory with the current entry. NumpadMemoryStore, /// M Subtract current entry from the value stored in memory. NumpadMemorySubtract, /// * on a keyboard. For use with numpads that provide mathematical /// operations (+, - * and /). /// /// Use `NumpadStar` for the * key on phones and remote controls. NumpadMultiply, /// ( Found on the Microsoft Natural Keyboard. NumpadParenLeft, /// ) Found on the Microsoft Natural Keyboard. NumpadParenRight, /// * on a phone or remote control device. /// /// This key is typically found below the 7 key and to the left of /// the 0 key. /// /// Use "NumpadMultiply" for the * key on /// numeric keypads. NumpadStar, /// - NumpadSubtract, /// Esc or Escape, /// Fn This is typically a hardware key that does not generate a separate code. Fn, /// FLock or FnLock. Function Lock key. Found on the Microsoft /// Natural Keyboard. FnLock, /// PrtScr SysRq or Print Screen PrintScreen, /// Scroll Lock ScrollLock, /// Pause Break Pause, /// Some laptops place this key to the left of the key. /// /// This also the "back" button (triangle) on Android. BrowserBack, BrowserFavorites, /// Some laptops place this key to the right of the key. BrowserForward, /// The "home" button on Android. BrowserHome, BrowserRefresh, BrowserSearch, BrowserStop, /// Eject or . This key is placed in the function section on some Apple /// keyboards. Eject, /// Sometimes labelled My Computer on the keyboard LaunchApp1, /// Sometimes labelled Calculator on the keyboard LaunchApp2, LaunchMail, MediaPlayPause, MediaSelect, MediaStop, MediaTrackNext, MediaTrackPrevious, /// This key is placed in the function section on some Apple keyboards, replacing the /// Eject key. Power, Sleep, AudioVolumeDown, AudioVolumeMute, AudioVolumeUp, WakeUp, // Legacy modifier key. Also called "Super" in certain places. Meta, // Legacy modifier key. Hyper, Turbo, Abort, Resume, Suspend, /// Found on Sun’s USB keyboard. Again, /// Found on Sun’s USB keyboard. Copy, /// Found on Sun’s USB keyboard. Cut, /// Found on Sun’s USB keyboard. Find, /// Found on Sun’s USB keyboard. Open, /// Found on Sun’s USB keyboard. Paste, /// Found on Sun’s USB keyboard. Props, /// Found on Sun’s USB keyboard. Select, /// Found on Sun’s USB keyboard. Undo, /// Use for dedicated ひらがな key found on some Japanese word processing keyboards. Hiragana, /// Use for dedicated カタカナ key found on some Japanese word processing keyboards. Katakana, /// General-purpose function key. /// Usually found at the top of the keyboard. F1, /// General-purpose function key. /// Usually found at the top of the keyboard. F2, /// General-purpose function key. /// Usually found at the top of the keyboard. F3, /// General-purpose function key. /// Usually found at the top of the keyboard. F4, /// General-purpose function key. /// Usually found at the top of the keyboard. F5, /// General-purpose function key. /// Usually found at the top of the keyboard. F6, /// General-purpose function key. /// Usually found at the top of the keyboard. F7, /// General-purpose function key. /// Usually found at the top of the keyboard. F8, /// General-purpose function key. /// Usually found at the top of the keyboard. F9, /// General-purpose function key. /// Usually found at the top of the keyboard. F10, /// General-purpose function key. /// Usually found at the top of the keyboard. F11, /// General-purpose function key. /// Usually found at the top of the keyboard. F12, /// General-purpose function key. /// Usually found at the top of the keyboard. F13, /// General-purpose function key. /// Usually found at the top of the keyboard. F14, /// General-purpose function key. /// Usually found at the top of the keyboard. F15, /// General-purpose function key. /// Usually found at the top of the keyboard. F16, /// General-purpose function key. /// Usually found at the top of the keyboard. F17, /// General-purpose function key. /// Usually found at the top of the keyboard. F18, /// General-purpose function key. /// Usually found at the top of the keyboard. F19, /// General-purpose function key. /// Usually found at the top of the keyboard. F20, /// General-purpose function key. /// Usually found at the top of the keyboard. F21, /// General-purpose function key. /// Usually found at the top of the keyboard. F22, /// General-purpose function key. /// Usually found at the top of the keyboard. F23, /// General-purpose function key. /// Usually found at the top of the keyboard. F24, /// General-purpose function key. F25, /// General-purpose function key. F26, /// General-purpose function key. F27, /// General-purpose function key. F28, /// General-purpose function key. F29, /// General-purpose function key. F30, /// General-purpose function key. F31, /// General-purpose function key. F32, /// General-purpose function key. F33, /// General-purpose function key. F34, /// General-purpose function key. F35, } /// Contains the platform-native physical key identifier. /// /// The exact values vary from platform to platform (which is part of why this is a per-platform /// enum), but the values are primarily tied to the key's physical location on the keyboard. /// /// This enum is primarily used to store raw keycodes when Winit doesn't map a given native /// physical key identifier to a meaningful [`Code`] variant. In the presence of identifiers we /// haven't mapped for you yet, this lets you use use [`Code`] to: /// /// - Correctly match key press and release events. /// - On non-web platforms, support assigning keybinds to virtually any key through a UI. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum NativeCode { /// An unidentified code. Unidentified, /// An Android "scancode". Android(u32), /// A macOS "scancode". MacOS(u16), /// A Windows "scancode". Windows(u16), /// An XKB "keycode". Xkb(u32), } /// Represents the location of a physical key. /// /// This type is a superset of [`Code`], including an [`Unidentified`][Self::Unidentified] /// variant. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum Physical { /// A known key code Code(Code), /// This variant is used when the key cannot be translated to a [`Code`] /// /// The native keycode is provided (if available) so you're able to more reliably match /// key-press and key-release events by hashing the [`Physical`] key. It is also possible to use /// this for keybinds for non-standard keys, but such keybinds are tied to a given platform. Unidentified(NativeCode), } impl PartialEq for Physical { #[inline] fn eq(&self, rhs: &Code) -> bool { match self { Physical::Code(code) => code == rhs, Physical::Unidentified(_) => false, } } } impl PartialEq for Code { #[inline] fn eq(&self, rhs: &Physical) -> bool { rhs == self } } impl PartialEq for Physical { #[inline] fn eq(&self, rhs: &NativeCode) -> bool { match self { Physical::Unidentified(code) => code == rhs, Physical::Code(_) => false, } } } impl PartialEq for NativeCode { #[inline] fn eq(&self, rhs: &Physical) -> bool { rhs == self } } ================================================ FILE: core/src/keyboard/location.rs ================================================ /// The location of a key on the keyboard. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Location { /// The standard group of keys on the keyboard. Standard, /// The left side of the keyboard. Left, /// The right side of the keyboard. Right, /// The numpad of the keyboard. Numpad, } ================================================ FILE: core/src/keyboard/modifiers.rs ================================================ use bitflags::bitflags; bitflags! { /// The current state of the keyboard modifiers. #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Modifiers: u32{ /// The "shift" key. const SHIFT = 0b100; // const LSHIFT = 0b010 << 0; // const RSHIFT = 0b001 << 0; // /// The "control" key. const CTRL = 0b100 << 3; // const LCTRL = 0b010 << 3; // const RCTRL = 0b001 << 3; // /// The "alt" key. const ALT = 0b100 << 6; // const LALT = 0b010 << 6; // const RALT = 0b001 << 6; // /// The "windows" key on Windows, "command" key on Mac, and /// "super" key on Linux. const LOGO = 0b100 << 9; // const LLOGO = 0b010 << 9; // const RLOGO = 0b001 << 9; /// No modifiers const NONE = 0; } } impl Modifiers { /// The "command" key. /// /// This is normally the main modifier to be used for hotkeys. /// /// On macOS, this is equivalent to `Self::LOGO`. /// Otherwise, this is equivalent to `Self::CTRL`. pub const COMMAND: Self = if cfg!(target_os = "macos") { Self::LOGO } else { Self::CTRL }; /// Returns true if the [`SHIFT`] key is pressed in the [`Modifiers`]. /// /// [`SHIFT`]: Self::SHIFT pub fn shift(self) -> bool { self.contains(Self::SHIFT) } /// Returns true if the [`CTRL`] key is pressed in the [`Modifiers`]. /// /// [`CTRL`]: Self::CTRL pub fn control(self) -> bool { self.contains(Self::CTRL) } /// Returns true if the [`ALT`] key is pressed in the [`Modifiers`]. /// /// [`ALT`]: Self::ALT pub fn alt(self) -> bool { self.contains(Self::ALT) } /// Returns true if the [`LOGO`] key is pressed in the [`Modifiers`]. /// /// [`LOGO`]: Self::LOGO pub fn logo(self) -> bool { self.contains(Self::LOGO) } /// Returns true if a "command key" is pressed in the [`Modifiers`]. /// /// The "command key" is the main modifier key used to issue commands in the /// current platform. Specifically: /// /// - It is the `logo` or command key (⌘) on macOS /// - It is the `control` key on other platforms pub fn command(self) -> bool { #[cfg(target_os = "macos")] let is_pressed = self.logo(); #[cfg(not(target_os = "macos"))] let is_pressed = self.control(); is_pressed } /// Returns true if the "jump key" is pressed in the [`Modifiers`]. /// /// The "jump key" is the modifier key used to widen text motions. It is the `Alt` /// key in macOS and the `Ctrl` key in other platforms. pub fn jump(self) -> bool { if cfg!(target_os = "macos") { self.alt() } else { self.control() } } /// Returns true if the "command key" is pressed on a macOS device. /// /// This is relevant for macOS-specific actions (e.g. `⌘ + ArrowLeft` moves the cursor /// to the beginning of the line). pub fn macos_command(self) -> bool { if cfg!(target_os = "macos") { self.logo() } else { false } } } ================================================ FILE: core/src/keyboard.rs ================================================ //! Listen to keyboard events. pub mod key; mod event; mod location; mod modifiers; pub use event::Event; pub use key::Key; pub use location::Location; pub use modifiers::Modifiers; ================================================ FILE: core/src/layout/DRUID_LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: core/src/layout/flex.rs ================================================ //! Distribute elements using a flex-based layout. // This code is heavily inspired by the [`druid`] codebase. // // [`druid`]: https://github.com/xi-editor/druid // // Copyright 2018 The xi-editor Authors, Héctor Ramón // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. use crate::Element; use crate::layout::{Limits, Node}; use crate::widget; use crate::{Alignment, Length, Padding, Point, Size}; /// The main axis of a flex layout. #[derive(Debug)] pub enum Axis { /// The horizontal axis Horizontal, /// The vertical axis Vertical, } impl Axis { fn main(&self, size: Size) -> f32 { match self { Axis::Horizontal => size.width, Axis::Vertical => size.height, } } fn cross(&self, size: Size) -> f32 { match self { Axis::Horizontal => size.height, Axis::Vertical => size.width, } } fn pack(&self, main: T, cross: T) -> (T, T) { match self { Axis::Horizontal => (main, cross), Axis::Vertical => (cross, main), } } } /// Computes the flex layout with the given axis and limits, applying spacing, /// padding and alignment to the items as needed. /// /// It returns a new layout [`Node`]. pub fn resolve( axis: Axis, renderer: &Renderer, limits: &Limits, width: Length, height: Length, padding: Padding, spacing: f32, align_items: Alignment, items: &mut [Element<'_, Message, Theme, Renderer>], trees: &mut [widget::Tree], ) -> Node where Renderer: crate::Renderer, { let limits = limits.width(width).height(height).shrink(padding); let total_spacing = spacing * items.len().saturating_sub(1) as f32; let max_cross = axis.cross(limits.max()); let (main_compress, cross_compress) = { let compression = limits.compression(); axis.pack(compression.width, compression.height) }; let compression = { let (compress_x, compress_y) = axis.pack(main_compress, false); Size::new(compress_x, compress_y) }; let mut fill_main_sum = 0; let mut some_fill_cross = false; let mut cross = if cross_compress { 0.0 } else { max_cross }; let mut available = axis.main(limits.max()) - total_spacing; let mut nodes: Vec = Vec::with_capacity(items.len()); nodes.resize(items.len(), Node::default()); // FIRST PASS // We lay out non-fluid elements in the main axis. // If we need to compress the cross axis, then we skip any of these elements // that are also fluid in the cross axis. for (i, (child, tree)) in items.iter_mut().zip(trees.iter_mut()).enumerate() { let (fill_main_factor, fill_cross_factor) = { let size = child.as_widget().size(); axis.pack(size.width.fill_factor(), size.height.fill_factor()) }; if (main_compress || fill_main_factor == 0) && (!cross_compress || fill_cross_factor == 0) { let (max_width, max_height) = axis.pack( available, if fill_cross_factor == 0 { max_cross } else { cross }, ); let child_limits = Limits::with_compression(Size::ZERO, Size::new(max_width, max_height), compression); let layout = child.as_widget_mut().layout(tree, renderer, &child_limits); let size = layout.size(); available -= axis.main(size); cross = cross.max(axis.cross(size)); nodes[i] = layout; } else { fill_main_sum += fill_main_factor; some_fill_cross = some_fill_cross || fill_cross_factor != 0; } } // SECOND PASS (conditional) // If we must compress the cross axis and there are fluid elements in the // cross axis, we lay out any of these elements that are also non-fluid in // the main axis (i.e. the ones we deliberately skipped in the first pass). // // We use the maximum cross length obtained in the first pass as the maximum // cross limit. // // We can defer the layout of any elements that have a fixed size in the main axis, // allowing them to use the cross calculations of the next pass. if cross_compress && some_fill_cross { for (i, (child, tree)) in items.iter_mut().zip(trees.iter_mut()).enumerate() { let (main_size, cross_size) = { let size = child.as_widget().size(); axis.pack(size.width, size.height) }; if (main_compress || main_size.fill_factor() == 0) && cross_size.fill_factor() != 0 { if let Length::Fixed(main) = main_size { available -= main; continue; } let (max_width, max_height) = axis.pack(available, cross); let child_limits = Limits::with_compression( Size::ZERO, Size::new(max_width, max_height), compression, ); let layout = child.as_widget_mut().layout(tree, renderer, &child_limits); let size = layout.size(); available -= axis.main(size); cross = cross.max(axis.cross(size)); nodes[i] = layout; } } } let remaining = available.max(0.0); // THIRD PASS (conditional) // We lay out the elements that are fluid in the main axis. // We use the remaining space to evenly allocate space based on fill factors. if !main_compress { for (i, (child, tree)) in items.iter_mut().zip(trees.iter_mut()).enumerate() { let (fill_main_factor, fill_cross_factor) = { let size = child.as_widget().size(); axis.pack(size.width.fill_factor(), size.height.fill_factor()) }; if fill_main_factor != 0 { let max_main = remaining * fill_main_factor as f32 / fill_main_sum as f32; let max_main = if max_main.is_nan() { f32::INFINITY } else { max_main }; let min_main = if max_main.is_infinite() { 0.0 } else { max_main }; let (min_width, min_height) = axis.pack(min_main, 0.0); let (max_width, max_height) = axis.pack( max_main, if fill_cross_factor == 0 { max_cross } else { cross }, ); let child_limits = Limits::with_compression( Size::new(min_width, min_height), Size::new(max_width, max_height), compression, ); let layout = child.as_widget_mut().layout(tree, renderer, &child_limits); cross = cross.max(axis.cross(layout.size())); nodes[i] = layout; } } } // FOURTH PASS (conditional) // We lay out any elements that were deferred in the second pass. // These are elements that must be compressed in their cross axis and have // a fixed length in the main axis. if cross_compress && some_fill_cross { for (i, (child, tree)) in items.iter_mut().zip(trees).enumerate() { let (main_size, cross_size) = { let size = child.as_widget().size(); axis.pack(size.width, size.height) }; if cross_size.fill_factor() != 0 { let Length::Fixed(main) = main_size else { continue; }; let (max_width, max_height) = axis.pack(main, cross); let child_limits = Limits::new(Size::ZERO, Size::new(max_width, max_height)); let layout = child.as_widget_mut().layout(tree, renderer, &child_limits); let size = layout.size(); cross = cross.max(axis.cross(size)); nodes[i] = layout; } } } let pad = axis.pack(padding.left, padding.top); let mut main = pad.0; // FIFTH PASS // We align all the laid out nodes in the cross axis, if needed. for (i, node) in nodes.iter_mut().enumerate() { if i > 0 { main += spacing; } let (x, y) = axis.pack(main, pad.1); node.move_to_mut(Point::new(x, y)); match axis { Axis::Horizontal => { node.align_mut(Alignment::Start, align_items, Size::new(0.0, cross)); } Axis::Vertical => { node.align_mut(align_items, Alignment::Start, Size::new(cross, 0.0)); } } let size = node.size(); main += axis.main(size); } let (intrinsic_width, intrinsic_height) = axis.pack(main - pad.0, cross); let size = limits.resolve(width, height, Size::new(intrinsic_width, intrinsic_height)); Node::with_children(size.expand(padding), nodes) } ================================================ FILE: core/src/layout/limits.rs ================================================ #![allow(clippy::manual_clamp)] use crate::{Length, Size}; /// A set of size constraints for layouting. #[derive(Debug, Clone, Copy, PartialEq)] pub struct Limits { min: Size, max: Size, compression: Size, } impl Limits { /// No limits pub const NONE: Limits = Limits { min: Size::ZERO, max: Size::INFINITE, compression: Size::new(false, false), }; /// Creates new [`Limits`] with the given minimum and maximum [`Size`]. pub const fn new(min: Size, max: Size) -> Limits { Limits::with_compression(min, max, Size::new(false, false)) } /// Creates new [`Limits`] with the given minimun and maximum [`Size`], and /// whether fluid lengths should be compressed to intrinsic dimensions. pub const fn with_compression(min: Size, max: Size, compress: Size) -> Self { Limits { min, max, compression: compress, } } /// Returns the minimum [`Size`] of the [`Limits`]. pub fn min(&self) -> Size { self.min } /// Returns the maximum [`Size`] of the [`Limits`]. pub fn max(&self) -> Size { self.max } /// Returns the compression of the [`Limits`]. pub fn compression(&self) -> Size { self.compression } /// Applies a width constraint to the current [`Limits`]. pub fn width(mut self, width: impl Into) -> Limits { match width.into() { Length::Shrink => { self.compression.width = true; } Length::Fixed(amount) => { let new_width = amount.min(self.max.width).max(self.min.width); self.min.width = new_width; self.max.width = new_width; self.compression.width = false; } Length::Fill | Length::FillPortion(_) => {} } self } /// Applies a height constraint to the current [`Limits`]. pub fn height(mut self, height: impl Into) -> Limits { match height.into() { Length::Shrink => { self.compression.height = true; } Length::Fixed(amount) => { let new_height = amount.min(self.max.height).max(self.min.height); self.min.height = new_height; self.max.height = new_height; self.compression.height = false; } Length::Fill | Length::FillPortion(_) => {} } self } /// Applies a minimum width constraint to the current [`Limits`]. pub fn min_width(mut self, min_width: f32) -> Limits { self.min.width = self.min.width.max(min_width).min(self.max.width); self } /// Applies a maximum width constraint to the current [`Limits`]. pub fn max_width(mut self, max_width: f32) -> Limits { self.max.width = self.max.width.min(max_width).max(self.min.width); self } /// Applies a minimum height constraint to the current [`Limits`]. pub fn min_height(mut self, min_height: f32) -> Limits { self.min.height = self.min.height.max(min_height).min(self.max.height); self } /// Applies a maximum height constraint to the current [`Limits`]. pub fn max_height(mut self, max_height: f32) -> Limits { self.max.height = self.max.height.min(max_height).max(self.min.height); self } /// Shrinks the current [`Limits`] by the given [`Size`]. pub fn shrink(&self, size: impl Into) -> Limits { let size = size.into(); let min = Size::new( (self.min().width - size.width).max(0.0), (self.min().height - size.height).max(0.0), ); let max = Size::new( (self.max().width - size.width).max(0.0), (self.max().height - size.height).max(0.0), ); Limits { min, max, compression: self.compression, } } /// Removes the minimum [`Size`] constraint for the current [`Limits`]. pub fn loose(&self) -> Limits { Limits { min: Size::ZERO, max: self.max, compression: self.compression, } } /// Computes the resulting [`Size`] that fits the [`Limits`] given /// some width and height requirements and the intrinsic size of /// some content. pub fn resolve( &self, width: impl Into, height: impl Into, intrinsic_size: Size, ) -> Size { let width = match width.into() { Length::Fill | Length::FillPortion(_) if !self.compression.width => self.max.width, Length::Fixed(amount) => amount.min(self.max.width).max(self.min.width), _ => intrinsic_size.width.min(self.max.width).max(self.min.width), }; let height = match height.into() { Length::Fill | Length::FillPortion(_) if !self.compression.height => self.max.height, Length::Fixed(amount) => amount.min(self.max.height).max(self.min.height), _ => intrinsic_size .height .min(self.max.height) .max(self.min.height), }; Size::new(width, height) } } ================================================ FILE: core/src/layout/node.rs ================================================ use crate::{Alignment, Padding, Point, Rectangle, Size, Vector}; /// The bounds of an element and its children. #[derive(Debug, Clone, Default)] pub struct Node { bounds: Rectangle, children: Vec, } impl Node { /// Creates a new [`Node`] with the given [`Size`]. pub const fn new(size: Size) -> Self { Self::with_children(size, Vec::new()) } /// Creates a new [`Node`] with the given [`Size`] and children. pub const fn with_children(size: Size, children: Vec) -> Self { Node { bounds: Rectangle { x: 0.0, y: 0.0, width: size.width, height: size.height, }, children, } } /// Creates a new [`Node`] that wraps a single child with some [`Padding`]. pub fn container(child: Self, padding: Padding) -> Self { Self::with_children( child.bounds.size().expand(padding), vec![child.move_to(Point::new(padding.left, padding.top))], ) } /// Returns the [`Size`] of the [`Node`]. pub fn size(&self) -> Size { Size::new(self.bounds.width, self.bounds.height) } /// Returns the bounds of the [`Node`]. pub fn bounds(&self) -> Rectangle { self.bounds } /// Returns the children of the [`Node`]. pub fn children(&self) -> &[Node] { &self.children } /// Aligns the [`Node`] in the given space. pub fn align(mut self, align_x: Alignment, align_y: Alignment, space: Size) -> Self { self.align_mut(align_x, align_y, space); self } /// Mutable reference version of [`Self::align`]. pub fn align_mut(&mut self, align_x: Alignment, align_y: Alignment, space: Size) { match align_x { Alignment::Start => {} Alignment::Center => { self.bounds.x += (space.width - self.bounds.width) / 2.0; } Alignment::End => { self.bounds.x += space.width - self.bounds.width; } } match align_y { Alignment::Start => {} Alignment::Center => { self.bounds.y += (space.height - self.bounds.height) / 2.0; } Alignment::End => { self.bounds.y += space.height - self.bounds.height; } } } /// Moves the [`Node`] to the given position. pub fn move_to(mut self, position: impl Into) -> Self { self.move_to_mut(position); self } /// Mutable reference version of [`Self::move_to`]. pub fn move_to_mut(&mut self, position: impl Into) { let position = position.into(); self.bounds.x = position.x; self.bounds.y = position.y; } /// Translates the [`Node`] by the given translation. pub fn translate(mut self, translation: impl Into) -> Self { self.translate_mut(translation); self } /// Translates the [`Node`] by the given translation. pub fn translate_mut(&mut self, translation: impl Into) { self.bounds = self.bounds + translation.into(); } } ================================================ FILE: core/src/layout.rs ================================================ //! Position your widgets properly. mod limits; mod node; pub mod flex; pub use limits::Limits; pub use node::Node; use crate::{Length, Padding, Point, Rectangle, Size, Vector}; /// The bounds of a [`Node`] and its children, using absolute coordinates. #[derive(Debug, Clone, Copy)] pub struct Layout<'a> { position: Point, node: &'a Node, } impl<'a> Layout<'a> { /// Creates a new [`Layout`] for the given [`Node`] at the origin. pub fn new(node: &'a Node) -> Self { Self::with_offset(Vector::new(0.0, 0.0), node) } /// Creates a new [`Layout`] for the given [`Node`] with the provided offset /// from the origin. pub fn with_offset(offset: Vector, node: &'a Node) -> Self { let bounds = node.bounds(); Self { position: Point::new(bounds.x, bounds.y) + offset, node, } } /// Returns the position of the [`Layout`]. pub fn position(&self) -> Point { self.position } /// Returns the bounds of the [`Layout`]. /// /// The returned [`Rectangle`] describes the position and size of a /// [`Node`]. pub fn bounds(&self) -> Rectangle { let bounds = self.node.bounds(); Rectangle { x: self.position.x, y: self.position.y, width: bounds.width, height: bounds.height, } } /// Returns an iterator over the children of this [`Layout`]. pub fn children(self) -> impl DoubleEndedIterator> + ExactSizeIterator { self.node.children().iter().map(move |node| { Layout::with_offset(Vector::new(self.position.x, self.position.y), node) }) } /// Returns the [`Layout`] of the child at the given index. /// /// This can be useful if you ever need to access children out of order /// for layering purposes. /// /// # Panics /// Panics if index is out of bounds. pub fn child(self, index: usize) -> Layout<'a> { let node = &self.node.children()[index]; Layout::with_offset(Vector::new(self.position.x, self.position.y), node) } } /// Produces a [`Node`] with two children nodes one right next to each other. pub fn next_to_each_other( limits: &Limits, spacing: f32, left: impl FnOnce(&Limits) -> Node, right: impl FnOnce(&Limits) -> Node, ) -> Node { let left_node = left(limits); let left_size = left_node.size(); let right_limits = limits.shrink(Size::new(left_size.width + spacing, 0.0)); let right_node = right(&right_limits); let right_size = right_node.size(); let (left_y, right_y) = if left_size.height > right_size.height { (0.0, (left_size.height - right_size.height) / 2.0) } else { ((right_size.height - left_size.height) / 2.0, 0.0) }; Node::with_children( Size::new( left_size.width + spacing + right_size.width, left_size.height.max(right_size.height), ), vec![ left_node.move_to(Point::new(0.0, left_y)), right_node.move_to(Point::new(left_size.width + spacing, right_y)), ], ) } /// Computes the resulting [`Node`] that fits the [`Limits`] given /// some width and height requirements and no intrinsic size. pub fn atomic(limits: &Limits, width: impl Into, height: impl Into) -> Node { let width = width.into(); let height = height.into(); Node::new(limits.resolve(width, height, Size::ZERO)) } /// Computes the resulting [`Node`] that fits the [`Limits`] given /// some width and height requirements and a closure that produces /// the intrinsic [`Size`] inside the given [`Limits`]. pub fn sized( limits: &Limits, width: impl Into, height: impl Into, f: impl FnOnce(&Limits) -> Size, ) -> Node { let width = width.into(); let height = height.into(); let limits = limits.width(width).height(height); let intrinsic_size = f(&limits); Node::new(limits.resolve(width, height, intrinsic_size)) } /// Computes the resulting [`Node`] that fits the [`Limits`] given /// some width and height requirements and a closure that produces /// the content [`Node`] inside the given [`Limits`]. pub fn contained( limits: &Limits, width: impl Into, height: impl Into, f: impl FnOnce(&Limits) -> Node, ) -> Node { let width = width.into(); let height = height.into(); let limits = limits.width(width).height(height); let content = f(&limits); Node::with_children(limits.resolve(width, height, content.size()), vec![content]) } /// Computes the [`Node`] that fits the [`Limits`] given some width, height, and /// [`Padding`] requirements and a closure that produces the content [`Node`] /// inside the given [`Limits`]. pub fn padded( limits: &Limits, width: impl Into, height: impl Into, padding: impl Into, layout: impl FnOnce(&Limits) -> Node, ) -> Node { positioned(limits, width, height, padding, layout, |content, _| content) } /// Computes a [`padded`] [`Node`] with a positioning step. pub fn positioned( limits: &Limits, width: impl Into, height: impl Into, padding: impl Into, layout: impl FnOnce(&Limits) -> Node, position: impl FnOnce(Node, Size) -> Node, ) -> Node { let width = width.into(); let height = height.into(); let padding = padding.into(); let limits = limits.width(width).height(height); let content = layout(&limits.shrink(padding)); let padding = padding.fit(content.size(), limits.max()); let size = limits .shrink(padding) .resolve(width, height, content.size()); Node::with_children( size.expand(padding), vec![position(content.move_to((padding.left, padding.top)), size)], ) } ================================================ FILE: core/src/length.rs ================================================ use crate::Pixels; /// The strategy used to fill space in a specific dimension. #[derive(Debug, Clone, Copy, PartialEq)] pub enum Length { /// Fill all the remaining space Fill, /// Fill a portion of the remaining space relative to other elements. /// /// Let's say we have two elements: one with `FillPortion(2)` and one with /// `FillPortion(3)`. The first will get 2 portions of the available space, /// while the second one would get 3. /// /// `Length::Fill` is equivalent to `Length::FillPortion(1)`. FillPortion(u16), /// Fill the least amount of space Shrink, /// Fill a fixed amount of space Fixed(f32), } impl Length { /// Returns the _fill factor_ of the [`Length`]. /// /// The _fill factor_ is a relative unit describing how much of the /// remaining space should be filled when compared to other elements. It /// is only meant to be used by layout engines. pub fn fill_factor(&self) -> u16 { match self { Length::Fill => 1, Length::FillPortion(factor) => *factor, Length::Shrink => 0, Length::Fixed(_) => 0, } } /// Returns `true` if the [`Length`] is either [`Length::Fill`] or /// [`Length::FillPortion`]. pub fn is_fill(&self) -> bool { self.fill_factor() != 0 } /// Returns the "fluid" variant of the [`Length`]. /// /// Specifically: /// - [`Length::Shrink`] if [`Length::Shrink`] or [`Length::Fixed`]. /// - [`Length::Fill`] otherwise. pub fn fluid(&self) -> Self { match self { Length::Fill | Length::FillPortion(_) => Length::Fill, Length::Shrink | Length::Fixed(_) => Length::Shrink, } } /// Adapts the [`Length`] so it can contain the other [`Length`] and /// match its fluidity. #[inline] pub fn enclose(self, other: Length) -> Self { match (self, other) { (Length::Shrink, Length::Fill | Length::FillPortion(_)) => other, _ => self, } } } impl From for Length { fn from(amount: Pixels) -> Self { Length::Fixed(f32::from(amount)) } } impl From for Length { fn from(amount: f32) -> Self { Length::Fixed(amount) } } impl From for Length { fn from(units: u32) -> Self { Length::Fixed(units as f32) } } ================================================ FILE: core/src/lib.rs ================================================ //! The core library of [Iced]. //! //! This library holds basic types that can be reused and re-exported in //! different runtime implementations. //! //! ![The foundations of the Iced ecosystem](https://github.com/iced-rs/iced/blob/0525d76ff94e828b7b21634fa94a747022001c83/docs/graphs/foundations.png?raw=true) //! //! [Iced]: https://github.com/iced-rs/iced #![doc( html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg" )] pub mod alignment; pub mod animation; pub mod border; pub mod clipboard; pub mod event; pub mod font; pub mod gradient; pub mod image; pub mod input_method; pub mod keyboard; pub mod layout; pub mod mouse; pub mod overlay; pub mod padding; pub mod renderer; pub mod svg; pub mod text; pub mod theme; pub mod time; pub mod touch; pub mod widget; pub mod window; mod angle; mod background; mod color; mod content_fit; mod element; mod length; mod pixels; mod point; mod rectangle; mod rotation; mod settings; mod shadow; mod shell; mod size; mod transformation; mod vector; pub use alignment::Alignment; pub use angle::{Degrees, Radians}; pub use animation::Animation; pub use background::Background; pub use border::Border; pub use clipboard::Clipboard; pub use color::Color; pub use content_fit::ContentFit; pub use element::Element; pub use event::Event; pub use font::Font; pub use gradient::Gradient; pub use image::Image; pub use input_method::InputMethod; pub use layout::Layout; pub use length::Length; pub use overlay::Overlay; pub use padding::Padding; pub use pixels::Pixels; pub use point::Point; pub use rectangle::Rectangle; pub use renderer::Renderer; pub use rotation::Rotation; pub use settings::Settings; pub use shadow::Shadow; pub use shell::Shell; pub use size::Size; pub use svg::Svg; pub use text::Text; pub use theme::Theme; pub use transformation::Transformation; pub use vector::Vector; pub use widget::Widget; pub use bytes::Bytes; pub use smol_str::SmolStr; pub use std::convert::Infallible as Never; /// A function that can _never_ be called. /// /// This is useful to turn generic types into anything /// you want by coercing them into a type with no possible /// values. pub fn never(never: Never) -> T { match never {} } /// A trait extension for binary functions (`Fn(A, B) -> O`). /// /// It enables you to use a bunch of nifty functional programming paradigms /// that work well with iced. pub trait Function { /// Applies the given first argument to a binary function and returns /// a new function that takes the other argument. /// /// This lets you partially "apply" a function—equivalent to currying, /// but it only works with binary functions. If you want to apply an /// arbitrary number of arguments, create a little struct for them. /// /// # When is this useful? /// Sometimes you will want to identify the source or target /// of some message in your user interface. This can be achieved through /// normal means by defining a closure and moving the identifier /// inside: /// /// ```rust /// # let element: Option<()> = Some(()); /// # enum Message { ButtonPressed(u32, ()) } /// let id = 123; /// /// # let _ = { /// element.map(move |result| Message::ButtonPressed(id, result)) /// # }; /// ``` /// /// That's quite a mouthful. [`with`](Self::with) lets you write: /// /// ```rust /// # use iced_core::Function; /// # let element: Option<()> = Some(()); /// # enum Message { ButtonPressed(u32, ()) } /// let id = 123; /// /// # let _ = { /// element.map(Message::ButtonPressed.with(id)) /// # }; /// ``` /// /// Effectively creating the same closure that partially applies /// the `id` to the message—but much more concise! fn with(self, prefix: A) -> impl Fn(B) -> O; } impl Function for F where F: Fn(A, B) -> O, Self: Sized, A: Clone, { fn with(self, prefix: A) -> impl Fn(B) -> O { move |result| self(prefix.clone(), result) } } ================================================ FILE: core/src/mouse/button.rs ================================================ /// The button of a mouse. #[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)] pub enum Button { /// The left mouse button. Left, /// The right mouse button. Right, /// The middle (wheel) button. Middle, /// The back mouse button. Back, /// The forward mouse button. Forward, /// Some other button. Other(u16), } ================================================ FILE: core/src/mouse/click.rs ================================================ //! Track mouse clicks. use crate::mouse::Button; use crate::time::Instant; use crate::{Point, Transformation}; use std::ops::Mul; /// A mouse click. #[derive(Debug, Clone, Copy)] pub struct Click { kind: Kind, button: Button, position: Point, time: Instant, } /// The kind of mouse click. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Kind { /// A single click Single, /// A double click Double, /// A triple click Triple, } impl Kind { fn next(self) -> Kind { match self { Kind::Single => Kind::Double, Kind::Double => Kind::Triple, Kind::Triple => Kind::Double, } } } impl Click { /// Creates a new [`Click`] with the given position and previous last /// [`Click`]. pub fn new(position: Point, button: Button, previous: Option) -> Click { let time = Instant::now(); let kind = if let Some(previous) = previous { if previous.is_consecutive(position, time) && button == previous.button { previous.kind.next() } else { Kind::Single } } else { Kind::Single }; Click { kind, button, position, time, } } /// Returns the [`Kind`] of [`Click`]. pub fn kind(&self) -> Kind { self.kind } /// Returns the position of the [`Click`]. pub fn position(&self) -> Point { self.position } fn is_consecutive(&self, new_position: Point, time: Instant) -> bool { let duration = if time > self.time { Some(time - self.time) } else { None }; self.position.distance(new_position) < 6.0 && duration .map(|duration| duration.as_millis() <= 300) .unwrap_or(false) } } impl Mul for Click { type Output = Click; fn mul(self, transformation: Transformation) -> Click { Click { kind: self.kind, button: self.button, position: self.position * transformation, time: self.time, } } } ================================================ FILE: core/src/mouse/cursor.rs ================================================ use crate::{Point, Rectangle, Transformation, Vector}; /// The mouse cursor state. #[derive(Debug, Clone, Copy, PartialEq, Default)] pub enum Cursor { /// The cursor has a defined position. Available(Point), /// The cursor has a defined position, but it's levitating over a layer above. Levitating(Point), /// The cursor is currently unavailable (i.e. out of bounds or busy). #[default] Unavailable, } impl Cursor { /// Returns the absolute position of the [`Cursor`], if available. pub fn position(self) -> Option { match self { Cursor::Available(position) => Some(position), Cursor::Levitating(_) | Cursor::Unavailable => None, } } /// Returns the absolute position of the [`Cursor`], if available and inside /// the given bounds. /// /// If the [`Cursor`] is not over the provided bounds, this method will /// return `None`. pub fn position_over(self, bounds: Rectangle) -> Option { self.position().filter(|p| bounds.contains(*p)) } /// Returns the relative position of the [`Cursor`] inside the given bounds, /// if available. /// /// If the [`Cursor`] is not over the provided bounds, this method will /// return `None`. pub fn position_in(self, bounds: Rectangle) -> Option { self.position_over(bounds) .map(|p| p - Vector::new(bounds.x, bounds.y)) } /// Returns the relative position of the [`Cursor`] from the given origin, /// if available. pub fn position_from(self, origin: Point) -> Option { self.position().map(|p| p - Vector::new(origin.x, origin.y)) } /// Returns true if the [`Cursor`] is over the given `bounds`. pub fn is_over(self, bounds: Rectangle) -> bool { self.position_over(bounds).is_some() } /// Returns true if the [`Cursor`] is levitating over a layer above. pub fn is_levitating(self) -> bool { matches!(self, Self::Levitating(_)) } /// Makes the [`Cursor`] levitate over a layer above. pub fn levitate(self) -> Self { match self { Self::Available(position) => Self::Levitating(position), _ => self, } } /// Brings the [`Cursor`] back to the current layer. pub fn land(self) -> Self { match self { Cursor::Levitating(position) => Cursor::Available(position), _ => self, } } } impl std::ops::Add for Cursor { type Output = Self; fn add(self, translation: Vector) -> Self::Output { match self { Cursor::Available(point) => Cursor::Available(point + translation), Cursor::Levitating(point) => Cursor::Levitating(point + translation), Cursor::Unavailable => Cursor::Unavailable, } } } impl std::ops::Sub for Cursor { type Output = Self; fn sub(self, translation: Vector) -> Self::Output { match self { Cursor::Available(point) => Cursor::Available(point - translation), Cursor::Levitating(point) => Cursor::Levitating(point - translation), Cursor::Unavailable => Cursor::Unavailable, } } } impl std::ops::Mul for Cursor { type Output = Self; fn mul(self, transformation: Transformation) -> Self { match self { Self::Available(position) => Self::Available(position * transformation), Self::Levitating(position) => Self::Levitating(position * transformation), Self::Unavailable => Self::Unavailable, } } } ================================================ FILE: core/src/mouse/event.rs ================================================ use crate::Point; use super::Button; /// A mouse event. /// /// _**Note:** This type is largely incomplete! If you need to track /// additional events, feel free to [open an issue] and share your use case!_ /// /// [open an issue]: https://github.com/iced-rs/iced/issues #[derive(Debug, Clone, Copy, PartialEq)] pub enum Event { /// The mouse cursor entered the window. CursorEntered, /// The mouse cursor left the window. CursorLeft, /// The mouse cursor was moved CursorMoved { /// The new position of the mouse cursor position: Point, }, /// A mouse button was pressed. ButtonPressed(Button), /// A mouse button was released. ButtonReleased(Button), /// The mouse wheel was scrolled. WheelScrolled { /// The scroll movement. delta: ScrollDelta, }, } /// A scroll movement. #[derive(Debug, Clone, Copy, PartialEq)] pub enum ScrollDelta { /// A line-based scroll movement Lines { /// The number of horizontal lines scrolled x: f32, /// The number of vertical lines scrolled y: f32, }, /// A pixel-based scroll movement Pixels { /// The number of horizontal pixels scrolled x: f32, /// The number of vertical pixels scrolled y: f32, }, } ================================================ FILE: core/src/mouse/interaction.rs ================================================ /// The interaction of a mouse cursor. #[derive(Debug, Eq, PartialEq, Clone, Copy, PartialOrd, Ord, Default)] #[allow(missing_docs)] pub enum Interaction { #[default] None, Hidden, Idle, ContextMenu, Help, Pointer, Progress, Wait, Cell, Crosshair, Text, Alias, Copy, Move, NoDrop, NotAllowed, Grab, Grabbing, ResizingHorizontally, ResizingVertically, ResizingDiagonallyUp, ResizingDiagonallyDown, ResizingColumn, ResizingRow, AllScroll, ZoomIn, ZoomOut, } ================================================ FILE: core/src/mouse.rs ================================================ //! Handle mouse events. pub mod click; mod button; mod cursor; mod event; mod interaction; pub use button::Button; pub use click::Click; pub use cursor::Cursor; pub use event::{Event, ScrollDelta}; pub use interaction::Interaction; ================================================ FILE: core/src/overlay/element.rs ================================================ pub use crate::Overlay; use crate::layout; use crate::mouse; use crate::renderer; use crate::widget; use crate::{Event, Layout, Shell, Size}; /// A generic [`Overlay`]. pub struct Element<'a, Message, Theme, Renderer> { overlay: Box + 'a>, } impl<'a, Message, Theme, Renderer> Element<'a, Message, Theme, Renderer> where Renderer: crate::Renderer, { /// Creates a new [`Element`] containing the given [`Overlay`]. pub fn new(overlay: Box + 'a>) -> Self { Self { overlay } } /// Returns a reference to the [`Overlay`] of the [`Element`], pub fn as_overlay(&self) -> &dyn Overlay { self.overlay.as_ref() } /// Returns a mutable reference to the [`Overlay`] of the [`Element`], pub fn as_overlay_mut(&mut self) -> &mut dyn Overlay { self.overlay.as_mut() } /// Applies a transformation to the produced message of the [`Element`]. pub fn map(self, f: &'a dyn Fn(Message) -> B) -> Element<'a, B, Theme, Renderer> where Message: 'a, Theme: 'a, Renderer: 'a, B: 'a, { Element { overlay: Box::new(Map::new(self.overlay, f)), } } } struct Map<'a, A, B, Theme, Renderer> { content: Box + 'a>, mapper: &'a dyn Fn(A) -> B, } impl<'a, A, B, Theme, Renderer> Map<'a, A, B, Theme, Renderer> { pub fn new( content: Box + 'a>, mapper: &'a dyn Fn(A) -> B, ) -> Map<'a, A, B, Theme, Renderer> { Map { content, mapper } } } impl Overlay for Map<'_, A, B, Theme, Renderer> where Renderer: crate::Renderer, { fn layout(&mut self, renderer: &Renderer, bounds: Size) -> layout::Node { self.content.layout(renderer, bounds) } fn operate( &mut self, layout: Layout<'_>, renderer: &Renderer, operation: &mut dyn widget::Operation, ) { self.content.operate(layout, renderer, operation); } fn update( &mut self, event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, shell: &mut Shell<'_, B>, ) { let mut local_messages = Vec::new(); let mut local_shell = Shell::new(&mut local_messages); self.content .update(event, layout, cursor, renderer, &mut local_shell); shell.merge(local_shell, self.mapper); } fn mouse_interaction( &self, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, ) -> mouse::Interaction { self.content.mouse_interaction(layout, cursor, renderer) } fn draw( &self, renderer: &mut Renderer, theme: &Theme, style: &renderer::Style, layout: Layout<'_>, cursor: mouse::Cursor, ) { self.content.draw(renderer, theme, style, layout, cursor); } fn overlay<'a>( &'a mut self, layout: Layout<'a>, renderer: &Renderer, ) -> Option> { self.content .overlay(layout, renderer) .map(|overlay| overlay.map(self.mapper)) } } ================================================ FILE: core/src/overlay/group.rs ================================================ use crate::layout; use crate::mouse; use crate::overlay; use crate::renderer; use crate::widget; use crate::{Event, Layout, Overlay, Shell, Size}; /// An [`Overlay`] container that displays multiple overlay [`overlay::Element`] /// children. pub struct Group<'a, Message, Theme, Renderer> { children: Vec>, } impl<'a, Message, Theme, Renderer> Group<'a, Message, Theme, Renderer> where Message: 'a, Theme: 'a, Renderer: 'a + crate::Renderer, { /// Creates an empty [`Group`]. pub fn new() -> Self { Self::default() } /// Creates a [`Group`] with the given elements. pub fn with_children( mut children: Vec>, ) -> Self { use std::cmp; children.sort_unstable_by(|a, b| { a.as_overlay() .index() .partial_cmp(&b.as_overlay().index()) .unwrap_or(cmp::Ordering::Equal) }); Group { children } } /// Turns the [`Group`] into an overlay [`overlay::Element`]. pub fn overlay(self) -> overlay::Element<'a, Message, Theme, Renderer> { overlay::Element::new(Box::new(self)) } } impl<'a, Message, Theme, Renderer> Default for Group<'a, Message, Theme, Renderer> where Message: 'a, Theme: 'a, Renderer: 'a + crate::Renderer, { fn default() -> Self { Self::with_children(Vec::new()) } } impl Overlay for Group<'_, Message, Theme, Renderer> where Renderer: crate::Renderer, { fn layout(&mut self, renderer: &Renderer, bounds: Size) -> layout::Node { layout::Node::with_children( bounds, self.children .iter_mut() .map(|child| child.as_overlay_mut().layout(renderer, bounds)) .collect(), ) } fn update( &mut self, event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, shell: &mut Shell<'_, Message>, ) { for (child, layout) in self.children.iter_mut().zip(layout.children()) { child .as_overlay_mut() .update(event, layout, cursor, renderer, shell); } } fn draw( &self, renderer: &mut Renderer, theme: &Theme, style: &renderer::Style, layout: Layout<'_>, cursor: mouse::Cursor, ) { for (child, layout) in self.children.iter().zip(layout.children()) { child .as_overlay() .draw(renderer, theme, style, layout, cursor); } } fn mouse_interaction( &self, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, ) -> mouse::Interaction { self.children .iter() .zip(layout.children()) .map(|(child, layout)| { child .as_overlay() .mouse_interaction(layout, cursor, renderer) }) .max() .unwrap_or_default() } fn operate( &mut self, layout: Layout<'_>, renderer: &Renderer, operation: &mut dyn widget::Operation, ) { operation.traverse(&mut |operation| { self.children .iter_mut() .zip(layout.children()) .for_each(|(child, layout)| { child.as_overlay_mut().operate(layout, renderer, operation); }); }); } fn overlay<'a>( &'a mut self, layout: Layout<'a>, renderer: &Renderer, ) -> Option> { let children = self .children .iter_mut() .zip(layout.children()) .filter_map(|(child, layout)| child.as_overlay_mut().overlay(layout, renderer)) .collect::>(); (!children.is_empty()).then(|| Group::with_children(children).overlay()) } fn index(&self) -> f32 { self.children .first() .map(|child| child.as_overlay().index()) .unwrap_or(1.0) } } impl<'a, Message, Theme, Renderer> From> for overlay::Element<'a, Message, Theme, Renderer> where Message: 'a, Theme: 'a, Renderer: 'a + crate::Renderer, { fn from(group: Group<'a, Message, Theme, Renderer>) -> Self { group.overlay() } } ================================================ FILE: core/src/overlay/nested.rs ================================================ use crate::event; use crate::layout; use crate::mouse; use crate::overlay; use crate::renderer; use crate::widget; use crate::{Event, Layout, Shell, Size}; /// An overlay container that displays nested overlays pub struct Nested<'a, Message, Theme, Renderer> { overlay: overlay::Element<'a, Message, Theme, Renderer>, } impl<'a, Message, Theme, Renderer> Nested<'a, Message, Theme, Renderer> where Renderer: renderer::Renderer, { /// Creates a nested overlay from the provided [`overlay::Element`] pub fn new(element: overlay::Element<'a, Message, Theme, Renderer>) -> Self { Self { overlay: element } } /// Returns the layout [`Node`] of the [`Nested`] overlay. /// /// [`Node`]: layout::Node pub fn layout(&mut self, renderer: &Renderer, bounds: Size) -> layout::Node { fn recurse( element: &mut overlay::Element<'_, Message, Theme, Renderer>, renderer: &Renderer, bounds: Size, ) -> layout::Node where Renderer: renderer::Renderer, { let overlay = element.as_overlay_mut(); let node = overlay.layout(renderer, bounds); let nested_node = overlay .overlay(Layout::new(&node), renderer) .as_mut() .map(|nested| recurse(nested, renderer, bounds)); if let Some(nested_node) = nested_node { layout::Node::with_children(node.size(), vec![node, nested_node]) } else { layout::Node::with_children(node.size(), vec![node]) } } recurse(&mut self.overlay, renderer, bounds) } /// Draws the [`Nested`] overlay using the associated `Renderer`. pub fn draw( &mut self, renderer: &mut Renderer, theme: &Theme, style: &renderer::Style, layout: Layout<'_>, cursor: mouse::Cursor, ) { fn recurse( element: &mut overlay::Element<'_, Message, Theme, Renderer>, layout: Layout<'_>, renderer: &mut Renderer, theme: &Theme, style: &renderer::Style, cursor: mouse::Cursor, ) where Renderer: renderer::Renderer, { let mut layouts = layout.children(); if let Some(layout) = layouts.next() { let nested_layout = layouts.next(); let overlay = element.as_overlay_mut(); let is_over = cursor .position() .zip(nested_layout) .and_then(|(cursor_position, nested_layout)| { overlay.overlay(layout, renderer).map(|nested| { nested.as_overlay().mouse_interaction( nested_layout.children().next().unwrap(), mouse::Cursor::Available(cursor_position), renderer, ) != mouse::Interaction::None }) }) .unwrap_or_default(); renderer.with_layer(layout.bounds(), |renderer| { overlay.draw( renderer, theme, style, layout, if is_over { mouse::Cursor::Unavailable } else { cursor }, ); }); if let Some((mut nested, nested_layout)) = overlay.overlay(layout, renderer).zip(nested_layout) { recurse(&mut nested, nested_layout, renderer, theme, style, cursor); } } } recurse(&mut self.overlay, layout, renderer, theme, style, cursor); } /// Applies a [`widget::Operation`] to the [`Nested`] overlay. pub fn operate( &mut self, layout: Layout<'_>, renderer: &Renderer, operation: &mut dyn widget::Operation, ) { fn recurse( element: &mut overlay::Element<'_, Message, Theme, Renderer>, layout: Layout<'_>, renderer: &Renderer, operation: &mut dyn widget::Operation, ) where Renderer: renderer::Renderer, { let mut layouts = layout.children(); if let Some(layout) = layouts.next() { let overlay = element.as_overlay_mut(); overlay.operate(layout, renderer, operation); if let Some((mut nested, nested_layout)) = overlay.overlay(layout, renderer).zip(layouts.next()) { recurse(&mut nested, nested_layout, renderer, operation); } } } recurse(&mut self.overlay, layout, renderer, operation); } /// Processes a runtime [`Event`]. pub fn update( &mut self, event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, shell: &mut Shell<'_, Message>, ) { fn recurse( element: &mut overlay::Element<'_, Message, Theme, Renderer>, layout: Layout<'_>, event: &Event, cursor: mouse::Cursor, renderer: &Renderer, shell: &mut Shell<'_, Message>, ) -> bool where Renderer: renderer::Renderer, { let mut layouts = layout.children(); if let Some(layout) = layouts.next() { let overlay = element.as_overlay_mut(); let nested_is_over = if let Some((mut nested, nested_layout)) = overlay.overlay(layout, renderer).zip(layouts.next()) { recurse(&mut nested, nested_layout, event, cursor, renderer, shell) } else { false }; if shell.event_status() == event::Status::Ignored { let is_over = nested_is_over || cursor .position() .map(|cursor_position| { overlay.mouse_interaction( layout, mouse::Cursor::Available(cursor_position), renderer, ) != mouse::Interaction::None }) .unwrap_or_default(); overlay.update( event, layout, if nested_is_over { mouse::Cursor::Unavailable } else { cursor }, renderer, shell, ); is_over } else { nested_is_over } } else { false } } let _ = recurse(&mut self.overlay, layout, event, cursor, renderer, shell); } /// Returns the current [`mouse::Interaction`] of the [`Nested`] overlay. pub fn mouse_interaction( &mut self, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, ) -> mouse::Interaction { fn recurse( element: &mut overlay::Element<'_, Message, Theme, Renderer>, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, ) -> Option where Renderer: renderer::Renderer, { let mut layouts = layout.children(); let layout = layouts.next()?; let overlay = element.as_overlay_mut(); Some( overlay .overlay(layout, renderer) .zip(layouts.next()) .and_then(|(mut overlay, layout)| { recurse(&mut overlay, layout, cursor, renderer) }) .unwrap_or_else(|| overlay.mouse_interaction(layout, cursor, renderer)), ) } recurse(&mut self.overlay, layout, cursor, renderer).unwrap_or_default() } } ================================================ FILE: core/src/overlay.rs ================================================ //! Display interactive elements on top of other widgets. mod element; mod group; mod nested; pub use element::Element; pub use group::Group; pub use nested::Nested; use crate::layout; use crate::mouse; use crate::renderer; use crate::widget; use crate::widget::Tree; use crate::{Event, Layout, Rectangle, Shell, Size, Vector}; /// An interactive component that can be displayed on top of other widgets. pub trait Overlay where Renderer: crate::Renderer, { /// Returns the layout [`Node`] of the [`Overlay`]. /// /// This [`Node`] is used by the runtime to compute the [`Layout`] of the /// user interface. /// /// [`Node`]: layout::Node fn layout(&mut self, renderer: &Renderer, bounds: Size) -> layout::Node; /// Draws the [`Overlay`] using the associated `Renderer`. fn draw( &self, renderer: &mut Renderer, theme: &Theme, style: &renderer::Style, layout: Layout<'_>, cursor: mouse::Cursor, ); /// Applies a [`widget::Operation`] to the [`Overlay`]. fn operate( &mut self, _layout: Layout<'_>, _renderer: &Renderer, _operation: &mut dyn widget::Operation, ) { } /// Processes a runtime [`Event`]. /// /// By default, it does nothing. fn update( &mut self, _event: &Event, _layout: Layout<'_>, _cursor: mouse::Cursor, _renderer: &Renderer, _shell: &mut Shell<'_, Message>, ) { } /// Returns the current [`mouse::Interaction`] of the [`Overlay`]. /// /// By default, it returns [`mouse::Interaction::None`]. fn mouse_interaction( &self, _layout: Layout<'_>, _cursor: mouse::Cursor, _renderer: &Renderer, ) -> mouse::Interaction { mouse::Interaction::None } /// Returns the nested overlay of the [`Overlay`], if there is any. fn overlay<'a>( &'a mut self, _layout: Layout<'a>, _renderer: &Renderer, ) -> Option> { None } /// The index of the overlay. /// /// Overlays with a higher index will be rendered on top of overlays with /// a lower index. /// /// By default, it returns `1.0`. fn index(&self) -> f32 { 1.0 } } /// Returns a [`Group`] of overlay [`Element`] children. /// /// This method will generally only be used by advanced users that are /// implementing the [`Widget`](crate::Widget) trait. pub fn from_children<'a, Message, Theme, Renderer>( children: &'a mut [crate::Element<'_, Message, Theme, Renderer>], tree: &'a mut Tree, layout: Layout<'a>, renderer: &Renderer, viewport: &Rectangle, translation: Vector, ) -> Option> where Renderer: crate::Renderer, { let children = children .iter_mut() .zip(&mut tree.children) .zip(layout.children()) .filter_map(|((child, state), layout)| { child .as_widget_mut() .overlay(state, layout, renderer, viewport, translation) }) .collect::>(); (!children.is_empty()).then(|| Group::with_children(children).overlay()) } ================================================ FILE: core/src/padding.rs ================================================ //! Space stuff around the perimeter. use crate::{Pixels, Size}; /// An amount of space to pad for each side of a box /// /// You can leverage the `From` trait to build [`Padding`] conveniently: /// /// ``` /// # use iced_core::Padding; /// # /// let padding = Padding::from(20); // 20px on all sides /// let padding = Padding::from([10, 20]); // top/bottom, left/right /// ``` /// /// Normally, the `padding` method of a widget will ask for an `Into`, /// so you can easily write: /// /// ``` /// # use iced_core::Padding; /// # /// # struct Widget; /// # /// impl Widget { /// # pub fn new() -> Self { Self } /// # /// pub fn padding(mut self, padding: impl Into) -> Self { /// // ... /// self /// } /// } /// /// let widget = Widget::new().padding(20); // 20px on all sides /// let widget = Widget::new().padding([10, 20]); // top/bottom, left/right /// ``` #[derive(Debug, Copy, Clone, PartialEq, Default)] pub struct Padding { /// Top padding pub top: f32, /// Right padding pub right: f32, /// Bottom padding pub bottom: f32, /// Left padding pub left: f32, } /// Create a [`Padding`] that is equal on all sides. pub fn all(padding: impl Into) -> Padding { Padding::new(padding.into().0) } /// Create some top [`Padding`]. pub fn top(padding: impl Into) -> Padding { Padding::default().top(padding) } /// Create some bottom [`Padding`]. pub fn bottom(padding: impl Into) -> Padding { Padding::default().bottom(padding) } /// Create some left [`Padding`]. pub fn left(padding: impl Into) -> Padding { Padding::default().left(padding) } /// Create some right [`Padding`]. pub fn right(padding: impl Into) -> Padding { Padding::default().right(padding) } /// Create some [`Padding`] with equal left and right sides. pub fn horizontal(padding: impl Into) -> Padding { Padding::default().horizontal(padding) } /// Create some [`Padding`] with equal top and bottom sides. pub fn vertical(padding: impl Into) -> Padding { Padding::default().vertical(padding) } impl Padding { /// Padding of zero pub const ZERO: Padding = Padding { top: 0.0, right: 0.0, bottom: 0.0, left: 0.0, }; /// Create a [`Padding`] that is equal on all sides. pub const fn new(padding: f32) -> Padding { Padding { top: padding, right: padding, bottom: padding, left: padding, } } /// Sets the [`top`] of the [`Padding`]. /// /// [`top`]: Self::top pub fn top(self, top: impl Into) -> Self { Self { top: top.into().0, ..self } } /// Sets the [`bottom`] of the [`Padding`]. /// /// [`bottom`]: Self::bottom pub fn bottom(self, bottom: impl Into) -> Self { Self { bottom: bottom.into().0, ..self } } /// Sets the [`left`] of the [`Padding`]. /// /// [`left`]: Self::left pub fn left(self, left: impl Into) -> Self { Self { left: left.into().0, ..self } } /// Sets the [`right`] of the [`Padding`]. /// /// [`right`]: Self::right pub fn right(self, right: impl Into) -> Self { Self { right: right.into().0, ..self } } /// Sets the [`left`] and [`right`] of the [`Padding`]. /// /// [`left`]: Self::left /// [`right`]: Self::right pub fn horizontal(self, horizontal: impl Into) -> Self { let horizontal = horizontal.into(); Self { left: horizontal.0, right: horizontal.0, ..self } } /// Sets the [`top`] and [`bottom`] of the [`Padding`]. /// /// [`top`]: Self::top /// [`bottom`]: Self::bottom pub fn vertical(self, vertical: impl Into) -> Self { let vertical = vertical.into(); Self { top: vertical.0, bottom: vertical.0, ..self } } /// Returns the total amount of horizontal [`Padding`]. pub fn x(self) -> f32 { self.left + self.right } /// Returns the total amount of vertical [`Padding`]. pub fn y(self) -> f32 { self.top + self.bottom } /// Fits the [`Padding`] between the provided `inner` and `outer` [`Size`]. pub fn fit(self, inner: Size, outer: Size) -> Self { let available = (outer - inner).max(Size::ZERO); let new_top = self.top.min(available.height); let new_left = self.left.min(available.width); Padding { top: new_top, bottom: self.bottom.min(available.height - new_top), left: new_left, right: self.right.min(available.width - new_left), } } } impl From for Padding { fn from(p: u16) -> Self { Padding { top: f32::from(p), right: f32::from(p), bottom: f32::from(p), left: f32::from(p), } } } impl From<[u16; 2]> for Padding { fn from(p: [u16; 2]) -> Self { Padding { top: f32::from(p[0]), right: f32::from(p[1]), bottom: f32::from(p[0]), left: f32::from(p[1]), } } } impl From for Padding { fn from(p: f32) -> Self { Padding { top: p, right: p, bottom: p, left: p, } } } impl From<[f32; 2]> for Padding { fn from(p: [f32; 2]) -> Self { Padding { top: p[0], right: p[1], bottom: p[0], left: p[1], } } } impl From for Size { fn from(padding: Padding) -> Self { Self::new(padding.x(), padding.y()) } } impl From for Padding { fn from(pixels: Pixels) -> Self { Self::from(pixels.0) } } ================================================ FILE: core/src/pixels.rs ================================================ /// An amount of logical pixels. /// /// Normally used to represent an amount of space, or the size of something. /// /// This type is normally asked as an argument in a generic way /// (e.g. `impl Into`) and, since `Pixels` implements `From` both for /// `f32` and `u16`, you should be able to provide both integers and float /// literals as needed. #[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] pub struct Pixels(pub f32); impl Pixels { /// Zero pixels. pub const ZERO: Self = Self(0.0); } impl From for Pixels { fn from(amount: f32) -> Self { Self(amount) } } impl From for Pixels { fn from(amount: u32) -> Self { Self(amount as f32) } } impl From for f32 { fn from(pixels: Pixels) -> Self { pixels.0 } } impl std::ops::Add for Pixels { type Output = Pixels; fn add(self, rhs: Self) -> Self { Pixels(self.0 + rhs.0) } } impl std::ops::Add for Pixels { type Output = Pixels; fn add(self, rhs: f32) -> Self { Pixels(self.0 + rhs) } } impl std::ops::Mul for Pixels { type Output = Pixels; fn mul(self, rhs: Self) -> Self { Pixels(self.0 * rhs.0) } } impl std::ops::Mul for Pixels { type Output = Pixels; fn mul(self, rhs: f32) -> Self { Pixels(self.0 * rhs) } } impl std::ops::Div for Pixels { type Output = Pixels; fn div(self, rhs: Self) -> Self { Pixels(self.0 / rhs.0) } } impl std::ops::Div for Pixels { type Output = Pixels; fn div(self, rhs: f32) -> Self { Pixels(self.0 / rhs) } } impl std::ops::Div for Pixels { type Output = Pixels; fn div(self, rhs: u32) -> Self { Pixels(self.0 / rhs as f32) } } ================================================ FILE: core/src/point.rs ================================================ use crate::Vector; use num_traits::{Float, Num}; use std::fmt; /// A 2D point. #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub struct Point { /// The X coordinate. pub x: T, /// The Y coordinate. pub y: T, } impl Point { /// The origin (i.e. a [`Point`] at (0, 0)). pub const ORIGIN: Self = Self::new(0.0, 0.0); } impl Point { /// Creates a new [`Point`] with the given coordinates. pub const fn new(x: T, y: T) -> Self { Self { x, y } } /// Computes the distance to another [`Point`]. pub fn distance(&self, to: Self) -> T where T: Float, { let a = self.x - to.x; let b = self.y - to.y; a.hypot(b) } } impl From<[T; 2]> for Point where T: Num, { fn from([x, y]: [T; 2]) -> Self { Point { x, y } } } impl From<(T, T)> for Point where T: Num, { fn from((x, y): (T, T)) -> Self { Self { x, y } } } impl From> for [T; 2] { fn from(point: Point) -> [T; 2] { [point.x, point.y] } } impl std::ops::Add> for Point where T: std::ops::Add, { type Output = Self; fn add(self, vector: Vector) -> Self { Self { x: self.x + vector.x, y: self.y + vector.y, } } } impl std::ops::AddAssign> for Point where T: std::ops::AddAssign, { fn add_assign(&mut self, vector: Vector) { self.x += vector.x; self.y += vector.y; } } impl std::ops::Sub> for Point where T: std::ops::Sub, { type Output = Self; fn sub(self, vector: Vector) -> Self { Self { x: self.x - vector.x, y: self.y - vector.y, } } } impl std::ops::SubAssign> for Point where T: std::ops::SubAssign, { fn sub_assign(&mut self, vector: Vector) { self.x -= vector.x; self.y -= vector.y; } } impl std::ops::Sub> for Point where T: std::ops::Sub, { type Output = Vector; fn sub(self, point: Self) -> Vector { Vector::new(self.x - point.x, self.y - point.y) } } impl fmt::Display for Point where T: fmt::Display, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Point {{ x: {}, y: {} }}", self.x, self.y) } } impl Point { /// Rounds the [`Point`] coordinates. pub fn round(self) -> Self { Point { x: self.x.round(), y: self.y.round(), } } /// Snaps the [`Point`] to __unsigned__ integer coordinates. pub fn snap(self) -> Point { Point { x: self.x.round() as u32, y: self.y.round() as u32, } } } ================================================ FILE: core/src/rectangle.rs ================================================ use crate::alignment; use crate::{Padding, Point, Radians, Size, Vector}; /// An axis-aligned rectangle. #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub struct Rectangle { /// X coordinate of the top-left corner. pub x: T, /// Y coordinate of the top-left corner. pub y: T, /// Width of the rectangle. pub width: T, /// Height of the rectangle. pub height: T, } impl Rectangle where T: Default, { /// Creates a new [`Rectangle`] with its top-left corner at the origin /// and with the provided [`Size`]. pub fn with_size(size: Size) -> Self { Self { x: T::default(), y: T::default(), width: size.width, height: size.height, } } } impl Rectangle { /// A rectangle starting at negative infinity and with infinite width and height. pub const INFINITE: Self = Self::new( Point::new(f32::NEG_INFINITY, f32::NEG_INFINITY), Size::INFINITE, ); /// Creates a new [`Rectangle`] with its top-left corner in the given /// [`Point`] and with the provided [`Size`]. pub const fn new(top_left: Point, size: Size) -> Self { Self { x: top_left.x, y: top_left.y, width: size.width, height: size.height, } } /// Creates a new square [`Rectangle`] with the center at the origin and /// with the given radius. pub fn with_radius(radius: f32) -> Self { Self { x: -radius, y: -radius, width: radius * 2.0, height: radius * 2.0, } } /// Creates a new axis-aligned [`Rectangle`] from the given vertices; returning the /// rotation in [`Radians`] that must be applied to the axis-aligned [`Rectangle`] /// to obtain the desired result. pub fn with_vertices( top_left: Point, top_right: Point, bottom_left: Point, ) -> (Rectangle, Radians) { let width = (top_right.x - top_left.x).hypot(top_right.y - top_left.y); let height = (bottom_left.x - top_left.x).hypot(bottom_left.y - top_left.y); let rotation = (top_right.y - top_left.y).atan2(top_right.x - top_left.x); let rotation = if rotation < 0.0 { 2.0 * std::f32::consts::PI + rotation } else { rotation }; let position = { let center = Point::new( (top_right.x + bottom_left.x) / 2.0, (top_right.y + bottom_left.y) / 2.0, ); let rotation = -rotation - std::f32::consts::PI * 2.0; Point::new( center.x + (top_left.x - center.x) * rotation.cos() - (top_left.y - center.y) * rotation.sin(), center.y + (top_left.x - center.x) * rotation.sin() + (top_left.y - center.y) * rotation.cos(), ) }; ( Rectangle::new(position, Size::new(width, height)), Radians(rotation), ) } /// Returns the [`Point`] at the center of the [`Rectangle`]. pub fn center(&self) -> Point { Point::new(self.center_x(), self.center_y()) } /// Returns the X coordinate of the [`Point`] at the center of the /// [`Rectangle`]. pub fn center_x(&self) -> f32 { self.x + self.width / 2.0 } /// Returns the Y coordinate of the [`Point`] at the center of the /// [`Rectangle`]. pub fn center_y(&self) -> f32 { self.y + self.height / 2.0 } /// Returns the position of the top left corner of the [`Rectangle`]. pub fn position(&self) -> Point { Point::new(self.x, self.y) } /// Returns the [`Size`] of the [`Rectangle`]. pub fn size(&self) -> Size { Size::new(self.width, self.height) } /// Returns the area of the [`Rectangle`]. pub fn area(&self) -> f32 { self.width * self.height } /// Returns true if the given [`Point`] is contained in the [`Rectangle`]. /// Excludes the right and bottom edges. pub fn contains(&self, point: Point) -> bool { self.x <= point.x && point.x < self.x + self.width && self.y <= point.y && point.y < self.y + self.height } /// Returns the minimum distance from the given [`Point`] to any of the edges /// of the [`Rectangle`]. pub fn distance(&self, point: Point) -> f32 { let center = self.center(); let distance_x = ((point.x - center.x).abs() - self.width / 2.0).max(0.0); let distance_y = ((point.y - center.y).abs() - self.height / 2.0).max(0.0); distance_x.hypot(distance_y) } /// Computes the offset that must be applied to the [`Rectangle`] to be placed /// inside the given `container`. pub fn offset(&self, container: &Rectangle) -> Vector { let Some(intersection) = self.intersection(container) else { return Vector::ZERO; }; let left = intersection.x - self.x; let top = intersection.y - self.y; Vector::new( if left > 0.0 { left } else { intersection.x + intersection.width - self.x - self.width }, if top > 0.0 { top } else { intersection.y + intersection.height - self.y - self.height }, ) } /// Returns true if the current [`Rectangle`] is within the given /// `container`. Includes the right and bottom edges. pub fn is_within(&self, container: &Rectangle) -> bool { self.x >= container.x && self.y >= container.y && self.x + self.width <= container.x + container.width && self.y + self.height <= container.y + container.height } /// Computes the intersection with the given [`Rectangle`]. pub fn intersection(&self, other: &Rectangle) -> Option> { let x = self.x.max(other.x); let y = self.y.max(other.y); let lower_right_x = (self.x + self.width).min(other.x + other.width); let lower_right_y = (self.y + self.height).min(other.y + other.height); let width = lower_right_x - x; let height = lower_right_y - y; if width > 0.0 && height > 0.0 { Some(Rectangle { x, y, width, height, }) } else { None } } /// Returns whether the [`Rectangle`] intersects with the given one. pub fn intersects(&self, other: &Self) -> bool { self.intersection(other).is_some() } /// Computes the union with the given [`Rectangle`]. pub fn union(&self, other: &Self) -> Self { let x = self.x.min(other.x); let y = self.y.min(other.y); let lower_right_x = (self.x + self.width).max(other.x + other.width); let lower_right_y = (self.y + self.height).max(other.y + other.height); let width = lower_right_x - x; let height = lower_right_y - y; Rectangle { x, y, width, height, } } /// Rounds the [`Rectangle`] coordinates. pub fn round(self) -> Self { let top_left = self.position().round(); let bottom_right = (self.position() + Vector::from(self.size())).round(); Self { x: top_left.x, y: top_left.y, width: bottom_right.x - top_left.x, height: bottom_right.y - top_left.y, } } /// Snaps the [`Rectangle`] to __unsigned__ integer coordinates. pub fn snap(self) -> Option> { let rounded = self.round(); if rounded.width < 1.0 || rounded.height < 1.0 { return None; } Some(Rectangle { x: rounded.x as u32, y: rounded.y as u32, width: rounded.width as u32, height: rounded.height as u32, }) } /// Expands the [`Rectangle`] a given amount. pub fn expand(self, padding: impl Into) -> Self { let padding = padding.into(); Self { x: self.x - padding.left, y: self.y - padding.top, width: self.width + padding.x(), height: self.height + padding.y(), } } /// Shrinks the [`Rectangle`] a given amount. pub fn shrink(self, padding: impl Into) -> Self { let padding = padding.into(); Self { x: self.x + padding.left, y: self.y + padding.top, width: self.width - padding.x(), height: self.height - padding.y(), } } /// Rotates the [`Rectangle`] and returns the smallest [`Rectangle`] /// containing it. pub fn rotate(self, rotation: Radians) -> Self { let size = self.size().rotate(rotation); let position = Point::new( self.center_x() - size.width / 2.0, self.center_y() - size.height / 2.0, ); Self::new(position, size) } /// Scales the [`Rectangle`] without changing its position, effectively /// "zooming" it. pub fn zoom(self, zoom: f32) -> Self { Self { x: self.x - (self.width * (zoom - 1.0)) / 2.0, y: self.y - (self.height * (zoom - 1.0)) / 2.0, width: self.width * zoom, height: self.height * zoom, } } /// Returns the top-left position to render an object of the given [`Size`]. /// inside the [`Rectangle`] that is anchored to the edge or corner /// defined by the alignment arguments. pub fn anchor( &self, size: Size, align_x: impl Into, align_y: impl Into, ) -> Point { let x = match align_x.into() { alignment::Horizontal::Left => self.x, alignment::Horizontal::Center => self.x + (self.width - size.width) / 2.0, alignment::Horizontal::Right => self.x + self.width - size.width, }; let y = match align_y.into() { alignment::Vertical::Top => self.y, alignment::Vertical::Center => self.y + (self.height - size.height) / 2.0, alignment::Vertical::Bottom => self.y + self.height - size.height, }; Point::new(x, y) } } impl std::ops::Mul for Rectangle { type Output = Self; fn mul(self, scale: f32) -> Self { Self { x: self.x * scale, y: self.y * scale, width: self.width * scale, height: self.height * scale, } } } impl From> for Rectangle { fn from(rectangle: Rectangle) -> Rectangle { Rectangle { x: rectangle.x as f32, y: rectangle.y as f32, width: rectangle.width as f32, height: rectangle.height as f32, } } } impl std::ops::Add> for Rectangle where T: std::ops::Add, { type Output = Rectangle; fn add(self, translation: Vector) -> Self { Rectangle { x: self.x + translation.x, y: self.y + translation.y, ..self } } } impl std::ops::Sub> for Rectangle where T: std::ops::Sub, { type Output = Rectangle; fn sub(self, translation: Vector) -> Self { Rectangle { x: self.x - translation.x, y: self.y - translation.y, ..self } } } ================================================ FILE: core/src/renderer/null.rs ================================================ use crate::alignment; use crate::image::{self, Image}; use crate::renderer::{self, Renderer}; use crate::svg; use crate::text::{self, Text}; use crate::{Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation}; impl Renderer for () { fn start_layer(&mut self, _bounds: Rectangle) {} fn end_layer(&mut self) {} fn start_transformation(&mut self, _transformation: Transformation) {} fn end_transformation(&mut self) {} fn fill_quad(&mut self, _quad: renderer::Quad, _background: impl Into) {} fn allocate_image( &mut self, handle: &image::Handle, callback: impl FnOnce(Result) + Send + 'static, ) { #[allow(unsafe_code)] callback(Ok(unsafe { image::allocate(handle, Size::new(100, 100)) })); } fn hint(&mut self, _scale_factor: f32) {} fn scale_factor(&self) -> Option { None } fn reset(&mut self, _new_bounds: Rectangle) {} } impl text::Renderer for () { type Font = Font; type Paragraph = (); type Editor = (); const ICON_FONT: Font = Font::DEFAULT; const CHECKMARK_ICON: char = '0'; const ARROW_DOWN_ICON: char = '0'; const SCROLL_UP_ICON: char = '0'; const SCROLL_DOWN_ICON: char = '0'; const SCROLL_LEFT_ICON: char = '0'; const SCROLL_RIGHT_ICON: char = '0'; const ICED_LOGO: char = '0'; fn default_font(&self) -> Self::Font { Font::default() } fn default_size(&self) -> Pixels { Pixels(16.0) } fn fill_paragraph( &mut self, _paragraph: &Self::Paragraph, _position: Point, _color: Color, _clip_bounds: Rectangle, ) { } fn fill_editor( &mut self, _editor: &Self::Editor, _position: Point, _color: Color, _clip_bounds: Rectangle, ) { } fn fill_text( &mut self, _paragraph: Text, _position: Point, _color: Color, _clip_bounds: Rectangle, ) { } } impl text::Paragraph for () { type Font = Font; fn with_text(_text: Text<&str>) -> Self {} fn with_spans(_text: Text<&[text::Span<'_, Link, Self::Font>], Self::Font>) -> Self {} fn resize(&mut self, _new_bounds: Size) {} fn compare(&self, _text: Text<()>) -> text::Difference { text::Difference::None } fn hint_factor(&self) -> Option { None } fn size(&self) -> Pixels { Pixels(16.0) } fn font(&self) -> Font { Font::DEFAULT } fn line_height(&self) -> text::LineHeight { text::LineHeight::default() } fn align_x(&self) -> text::Alignment { text::Alignment::Default } fn align_y(&self) -> alignment::Vertical { alignment::Vertical::Top } fn wrapping(&self) -> text::Wrapping { text::Wrapping::default() } fn ellipsis(&self) -> text::Ellipsis { text::Ellipsis::default() } fn shaping(&self) -> text::Shaping { text::Shaping::default() } fn grapheme_position(&self, _line: usize, _index: usize) -> Option { None } fn bounds(&self) -> Size { Size::ZERO } fn min_bounds(&self) -> Size { Size::ZERO } fn hit_test(&self, _point: Point) -> Option { None } fn hit_span(&self, _point: Point) -> Option { None } fn span_bounds(&self, _index: usize) -> Vec { vec![] } } impl text::Editor for () { type Font = Font; fn with_text(_text: &str) -> Self {} fn is_empty(&self) -> bool { true } fn cursor(&self) -> text::editor::Cursor { text::editor::Cursor { position: text::editor::Position { line: 0, column: 0 }, selection: None, } } fn selection(&self) -> text::editor::Selection { text::editor::Selection::Caret(Point::ORIGIN) } fn copy(&self) -> Option { None } fn line(&self, _index: usize) -> Option> { None } fn line_count(&self) -> usize { 0 } fn perform(&mut self, _action: text::editor::Action) {} fn move_to(&mut self, _cursor: text::editor::Cursor) {} fn bounds(&self) -> Size { Size::ZERO } fn hint_factor(&self) -> Option { None } fn min_bounds(&self) -> Size { Size::ZERO } fn update( &mut self, _new_bounds: Size, _new_font: Self::Font, _new_size: Pixels, _new_line_height: text::LineHeight, _new_wrapping: text::Wrapping, _new_hint_factor: Option, _new_highlighter: &mut impl text::Highlighter, ) { } fn highlight( &mut self, _font: Self::Font, _highlighter: &mut H, _format_highlight: impl Fn(&H::Highlight) -> text::highlighter::Format, ) { } } impl image::Renderer for () { type Handle = image::Handle; fn load_image(&self, handle: &Self::Handle) -> Result { #[allow(unsafe_code)] Ok(unsafe { image::allocate(handle, Size::new(100, 100)) }) } fn measure_image(&self, _handle: &Self::Handle) -> Option> { Some(Size::new(100, 100)) } fn draw_image(&mut self, _image: Image, _bounds: Rectangle, _clip_bounds: Rectangle) {} } impl svg::Renderer for () { fn measure_svg(&self, _handle: &svg::Handle) -> Size { Size::default() } fn draw_svg(&mut self, _svg: svg::Svg, _bounds: Rectangle, _clip_bounds: Rectangle) {} } impl renderer::Headless for () { async fn new(_settings: renderer::Settings, _backend: Option<&str>) -> Option where Self: Sized, { Some(()) } fn name(&self) -> String { "null renderer".to_owned() } fn screenshot( &mut self, _size: Size, _scale_factor: f32, _background_color: Color, ) -> Vec { Vec::new() } } ================================================ FILE: core/src/renderer.rs ================================================ //! Write your own renderer. #[cfg(debug_assertions)] mod null; use crate::image; use crate::{ Background, Border, Color, Font, Pixels, Rectangle, Shadow, Size, Transformation, Vector, }; /// Whether anti-aliasing should be avoided by snapping primitive coordinates to the /// pixel grid. pub const CRISP: bool = cfg!(feature = "crisp"); /// A component that can be used by widgets to draw themselves on a screen. pub trait Renderer { /// Starts recording a new layer. fn start_layer(&mut self, bounds: Rectangle); /// Ends recording a new layer. /// /// The new layer will clip its contents to the provided `bounds`. fn end_layer(&mut self); /// Draws the primitives recorded in the given closure in a new layer. /// /// The layer will clip its contents to the provided `bounds`. fn with_layer(&mut self, bounds: Rectangle, f: impl FnOnce(&mut Self)) { self.start_layer(bounds); f(self); self.end_layer(); } /// Starts recording with a new [`Transformation`]. fn start_transformation(&mut self, transformation: Transformation); /// Ends recording a new layer. /// /// The new layer will clip its contents to the provided `bounds`. fn end_transformation(&mut self); /// Applies a [`Transformation`] to the primitives recorded in the given closure. fn with_transformation(&mut self, transformation: Transformation, f: impl FnOnce(&mut Self)) { self.start_transformation(transformation); f(self); self.end_transformation(); } /// Applies a translation to the primitives recorded in the given closure. fn with_translation(&mut self, translation: Vector, f: impl FnOnce(&mut Self)) { self.with_transformation(Transformation::translate(translation.x, translation.y), f); } /// Fills a [`Quad`] with the provided [`Background`]. fn fill_quad(&mut self, quad: Quad, background: impl Into); /// Creates an [`image::Allocation`] for the given [`image::Handle`] and calls the given callback with it. fn allocate_image( &mut self, handle: &image::Handle, callback: impl FnOnce(Result) + Send + 'static, ); /// Provides hints to the [`Renderer`] about the rendering target. /// /// This may be used internally by the [`Renderer`] to perform optimizations /// and/or improve rendering quality. /// /// For instance, providing a `scale_factor` may be used by some renderers to /// perform metrics hinting internally in physical coordinates while keeping /// layout coordinates logical and, therefore, maintain linearity. fn hint(&mut self, scale_factor: f32); /// Returns the last scale factor provided as a [`hint`](Self::hint). fn scale_factor(&self) -> Option; /// Resets the [`Renderer`] to start drawing in the `new_bounds` from scratch. fn reset(&mut self, new_bounds: Rectangle); /// Polls any concurrent computations that may be pending in the [`Renderer`]. /// /// By default, it does nothing. fn tick(&mut self) {} } /// A polygon with four sides. #[derive(Debug, Clone, Copy, PartialEq)] pub struct Quad { /// The bounds of the [`Quad`]. pub bounds: Rectangle, /// The [`Border`] of the [`Quad`]. The border is drawn on the inside of the [`Quad`]. pub border: Border, /// The [`Shadow`] of the [`Quad`]. pub shadow: Shadow, /// Whether the [`Quad`] should be snapped to the pixel grid. pub snap: bool, } impl Default for Quad { fn default() -> Self { Self { bounds: Rectangle::with_size(Size::ZERO), border: Border::default(), shadow: Shadow::default(), snap: CRISP, } } } /// The styling attributes of a [`Renderer`]. #[derive(Debug, Clone, Copy, PartialEq)] pub struct Style { /// The text color pub text_color: Color, } impl Default for Style { fn default() -> Self { Style { text_color: Color::BLACK, } } } /// A headless renderer is a renderer that can render offscreen without /// a window nor a compositor. pub trait Headless { /// Creates a new [`Headless`] renderer; fn new(settings: Settings, backend: Option<&str>) -> impl Future> where Self: Sized; /// Returns the unique name of the renderer. /// /// This name may be used by testing libraries to uniquely identify /// snapshots. fn name(&self) -> String; /// Draws offscreen into a screenshot, returning a collection of /// bytes representing the rendered pixels in RGBA order. fn screenshot( &mut self, size: Size, scale_factor: f32, background_color: Color, ) -> Vec; } /// The settings of a [`Renderer`]. #[derive(Debug, Clone, Copy, PartialEq)] pub struct Settings { /// The default [`Font`] to use. pub default_font: Font, /// The default size of text. /// /// By default, it will be set to `16.0`. pub default_text_size: Pixels, } impl Default for Settings { fn default() -> Self { Self { default_font: Font::DEFAULT, default_text_size: Pixels(16.0), } } } ================================================ FILE: core/src/rotation.rs ================================================ //! Control the rotation of some content (like an image) within a space. use crate::{Degrees, Radians, Size}; /// The strategy used to rotate the content. /// /// This is used to control the behavior of the layout when the content is rotated /// by a certain angle. #[derive(Debug, Clone, Copy, PartialEq)] pub enum Rotation { /// The element will float while rotating. The layout will be kept exactly as it was /// before the rotation. /// /// This is especially useful when used for animations, as it will avoid the /// layout being shifted or resized when smoothly i.e. an icon. /// /// This is the default. Floating(Radians), /// The element will be solid while rotating. The layout will be adjusted to fit /// the rotated content. /// /// This allows you to rotate an image and have the layout adjust to fit the new /// size of the image. Solid(Radians), } impl Rotation { /// Returns the angle of the [`Rotation`] in [`Radians`]. pub fn radians(self) -> Radians { match self { Rotation::Floating(radians) | Rotation::Solid(radians) => radians, } } /// Returns a mutable reference to the angle of the [`Rotation`] in [`Radians`]. pub fn radians_mut(&mut self) -> &mut Radians { match self { Rotation::Floating(radians) | Rotation::Solid(radians) => radians, } } /// Returns the angle of the [`Rotation`] in [`Degrees`]. pub fn degrees(self) -> Degrees { Degrees(self.radians().0.to_degrees()) } /// Applies the [`Rotation`] to the given [`Size`], returning /// the minimum [`Size`] containing the rotated one. pub fn apply(self, size: Size) -> Size { match self { Self::Floating(_) => size, Self::Solid(rotation) => size.rotate(rotation), } } } impl Default for Rotation { fn default() -> Self { Self::Floating(Radians(0.0)) } } impl From for Rotation { fn from(radians: Radians) -> Self { Self::Floating(radians) } } impl From for Rotation { fn from(radians: f32) -> Self { Self::Floating(Radians(radians)) } } ================================================ FILE: core/src/settings.rs ================================================ //! Configure your application. use crate::renderer; use crate::{Font, Pixels}; use std::borrow::Cow; /// The settings of an iced program. #[derive(Debug, Clone)] pub struct Settings { /// The identifier of the application. /// /// If provided, this identifier may be used to identify the application or /// communicate with it through the windowing system. pub id: Option, /// The fonts to load on boot. pub fonts: Vec>, /// The default [`Font`] to be used. /// /// By default, it uses [`Family::SansSerif`](crate::font::Family::SansSerif). pub default_font: Font, /// The text size that will be used by default. /// /// The default value is `16.0`. pub default_text_size: Pixels, /// If set to true, the renderer will try to perform antialiasing for some /// primitives. /// /// Enabling it can produce a smoother result in some widgets, like the /// `canvas` widget, at a performance cost. /// /// By default, it is enabled. pub antialiasing: bool, /// Whether or not to attempt to synchronize rendering when possible. /// /// Disabling it can improve rendering performance on some platforms. /// /// By default, it is enabled. pub vsync: bool, } impl Default for Settings { fn default() -> Self { let renderer = renderer::Settings::default(); Self { id: None, fonts: Vec::new(), default_font: renderer.default_font, default_text_size: renderer.default_text_size, antialiasing: true, vsync: true, } } } impl From<&Settings> for renderer::Settings { fn from(settings: &Settings) -> Self { Self { default_font: settings.default_font, default_text_size: settings.default_text_size, } } } ================================================ FILE: core/src/shadow.rs ================================================ use crate::{Color, Vector}; /// A shadow. #[derive(Debug, Clone, Copy, PartialEq, Default)] pub struct Shadow { /// The color of the shadow. pub color: Color, /// The offset of the shadow. pub offset: Vector, /// The blur radius of the shadow. pub blur_radius: f32, } ================================================ FILE: core/src/shell.rs ================================================ use crate::clipboard; use crate::event; use crate::window; use crate::{Clipboard, InputMethod}; /// A connection to the state of a shell. /// /// A [`Widget`] can leverage a [`Shell`] to trigger changes in an application, /// like publishing messages or invalidating the current layout. /// /// [`Widget`]: crate::Widget #[derive(Debug)] pub struct Shell<'a, Message> { messages: &'a mut Vec, event_status: event::Status, redraw_request: window::RedrawRequest, input_method: InputMethod, is_layout_invalid: bool, are_widgets_invalid: bool, clipboard: Clipboard, } impl<'a, Message> Shell<'a, Message> { /// Creates a new [`Shell`] with the provided buffer of messages. pub fn new(messages: &'a mut Vec) -> Self { Self { messages, event_status: event::Status::Ignored, redraw_request: window::RedrawRequest::Wait, is_layout_invalid: false, are_widgets_invalid: false, input_method: InputMethod::Disabled, clipboard: Clipboard { reads: Vec::new(), write: None, }, } } /// Returns true if the [`Shell`] contains no published messages #[must_use] pub fn is_empty(&self) -> bool { self.messages.is_empty() } /// Publish the given `Message` for an application to process it. pub fn publish(&mut self, message: Message) { self.messages.push(message); } /// Marks the current event as captured. Prevents "event bubbling". /// /// A widget should capture an event when no ancestor should /// handle it. pub fn capture_event(&mut self) { self.event_status = event::Status::Captured; } /// Returns the current [`event::Status`] of the [`Shell`]. #[must_use] pub fn event_status(&self) -> event::Status { self.event_status } /// Returns whether the current event has been captured. #[must_use] pub fn is_event_captured(&self) -> bool { self.event_status == event::Status::Captured } /// Requests a new frame to be drawn as soon as possible. pub fn request_redraw(&mut self) { self.redraw_request = window::RedrawRequest::NextFrame; } /// Requests a new frame to be drawn at the given [`window::RedrawRequest`]. pub fn request_redraw_at(&mut self, redraw_request: impl Into) { self.redraw_request = self.redraw_request.min(redraw_request.into()); } /// Returns the request a redraw should happen, if any. #[must_use] pub fn redraw_request(&self) -> window::RedrawRequest { self.redraw_request } /// Replaces the redraw request of the [`Shell`]; without conflict resolution. /// /// This is useful if you want to overwrite the redraw request to a previous value. /// Since it's a fairly advanced use case and should rarely be used, it is a static /// method. pub fn replace_redraw_request(shell: &mut Self, redraw_request: window::RedrawRequest) { shell.redraw_request = redraw_request; } /// Requests the runtime to read the clipboard contents expecting the given [`clipboard::Kind`]. /// /// The runtime will produce a [`clipboard::Event::Read`] when the contents have been read. pub fn read_clipboard(&mut self, kind: clipboard::Kind) { self.clipboard.reads.push(kind); } /// Requests the runtime to write the given [`clipboard::Content`] to the clipboard. /// /// The runtime will produce a [`clipboard::Event::Written`] when the contents have been written. pub fn write_clipboard(&mut self, content: clipboard::Content) { self.clipboard.write = Some(content); } /// Returns the [`Clipboard`] requests of the [`Shell`], mutably. pub fn clipboard_mut(&mut self) -> &mut Clipboard { &mut self.clipboard } /// Requests the current [`InputMethod`] strategy. /// /// __Important__: This request will only be honored by the /// [`Shell`] only during a [`window::Event::RedrawRequested`]. pub fn request_input_method>(&mut self, ime: &InputMethod) { self.input_method.merge(ime); } /// Returns the current [`InputMethod`] strategy. #[must_use] pub fn input_method(&self) -> &InputMethod { &self.input_method } /// Returns the current [`InputMethod`] strategy. #[must_use] pub fn input_method_mut(&mut self) -> &mut InputMethod { &mut self.input_method } /// Returns whether the current layout is invalid or not. #[must_use] pub fn is_layout_invalid(&self) -> bool { self.is_layout_invalid } /// Invalidates the current application layout. /// /// The shell will relayout the application widgets. pub fn invalidate_layout(&mut self) { self.is_layout_invalid = true; } /// Triggers the given function if the layout is invalid, cleaning it in the /// process. pub fn revalidate_layout(&mut self, f: impl FnOnce()) { if self.is_layout_invalid { self.is_layout_invalid = false; f(); } } /// Returns whether the widgets of the current application have been /// invalidated. #[must_use] pub fn are_widgets_invalid(&self) -> bool { self.are_widgets_invalid } /// Invalidates the current application widgets. /// /// The shell will rebuild and relayout the widget tree. pub fn invalidate_widgets(&mut self) { self.are_widgets_invalid = true; } /// Merges the current [`Shell`] with another one by applying the given /// function to the messages of the latter. /// /// This method is useful for composition. pub fn merge(&mut self, mut other: Shell<'_, B>, f: impl Fn(B) -> Message) { self.messages.extend(other.messages.drain(..).map(f)); self.is_layout_invalid = self.is_layout_invalid || other.is_layout_invalid; self.are_widgets_invalid = self.are_widgets_invalid || other.are_widgets_invalid; self.redraw_request = self.redraw_request.min(other.redraw_request); self.event_status = self.event_status.merge(other.event_status); self.input_method.merge(&other.input_method); self.clipboard.merge(&mut other.clipboard); } } ================================================ FILE: core/src/size.rs ================================================ use crate::{Length, Radians, Vector}; /// An amount of space in 2 dimensions. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] pub struct Size { /// The width. pub width: T, /// The height. pub height: T, } impl Size { /// Creates a new [`Size`] with the given width and height. pub const fn new(width: T, height: T) -> Self { Size { width, height } } } impl Size { /// A [`Size`] with zero width and height. pub const ZERO: Size = Size::new(0., 0.); /// A [`Size`] with a width and height of 1 unit. pub const UNIT: Size = Size::new(1., 1.); /// A [`Size`] with infinite width and height. pub const INFINITE: Size = Size::new(f32::INFINITY, f32::INFINITY); /// Returns the minimum of each component of this size and another. pub fn min(self, other: Self) -> Self { Size { width: self.width.min(other.width), height: self.height.min(other.height), } } /// Returns the maximum of each component of this size and another. pub fn max(self, other: Self) -> Self { Size { width: self.width.max(other.width), height: self.height.max(other.height), } } /// Expands this [`Size`] by the given amount. pub fn expand(self, other: impl Into) -> Self { let other = other.into(); Size { width: self.width + other.width, height: self.height + other.height, } } /// Rotates this [`Size`] and returns the minimum [`Size`] /// containing it. pub fn rotate(self, rotation: Radians) -> Size { let radians = f32::from(rotation); Size { width: (self.width * radians.cos()).abs() + (self.height * radians.sin()).abs(), height: (self.width * radians.sin()).abs() + (self.height * radians.cos()).abs(), } } /// Applies an aspect ratio to this [`Size`] without /// exceeding its bounds. pub const fn ratio(self, aspect_ratio: f32) -> Size { Size { width: (self.height * aspect_ratio).min(self.width), height: (self.width / aspect_ratio).min(self.height), } } } impl Size { /// Returns true if either `width` or `height` are 0-sized. #[inline] pub fn is_void(&self) -> bool { matches!(self.width, Length::Fixed(0.0)) || matches!(self.height, Length::Fixed(0.0)) } } impl From<[T; 2]> for Size { fn from([width, height]: [T; 2]) -> Self { Size { width, height } } } impl From<(T, T)> for Size { fn from((width, height): (T, T)) -> Self { Self { width, height } } } impl From<(u32, u32)> for Size { fn from((width, height): (u32, u32)) -> Self { Size::new(width as f32, height as f32) } } impl From> for Size { fn from(vector: Vector) -> Self { Size { width: vector.x, height: vector.y, } } } impl From> for [T; 2] { fn from(size: Size) -> Self { [size.width, size.height] } } impl From> for Vector { fn from(size: Size) -> Self { Vector::new(size.width, size.height) } } impl std::ops::Add for Size where T: std::ops::Add, { type Output = Size; fn add(self, rhs: Self) -> Self::Output { Size { width: self.width + rhs.width, height: self.height + rhs.height, } } } impl std::ops::Sub for Size where T: std::ops::Sub, { type Output = Size; fn sub(self, rhs: Self) -> Self::Output { Size { width: self.width - rhs.width, height: self.height - rhs.height, } } } impl std::ops::Mul for Size where T: std::ops::Mul + Copy, { type Output = Size; fn mul(self, rhs: T) -> Self::Output { Size { width: self.width * rhs, height: self.height * rhs, } } } impl std::ops::Div for Size where T: std::ops::Div + Copy, { type Output = Size; fn div(self, rhs: T) -> Self::Output { Size { width: self.width / rhs, height: self.height / rhs, } } } impl std::ops::Mul> for Size where T: std::ops::Mul + Copy, { type Output = Size; fn mul(self, scale: Vector) -> Self::Output { Size { width: self.width * scale.x, height: self.height * scale.y, } } } ================================================ FILE: core/src/svg.rs ================================================ //! Load and draw vector graphics. use crate::{Color, Radians, Rectangle, Size}; use rustc_hash::FxHasher; use std::borrow::Cow; use std::hash::{Hash, Hasher as _}; use std::path::PathBuf; use std::sync::Arc; /// A raster image that can be drawn. #[derive(Debug, Clone, PartialEq)] pub struct Svg { /// The handle of the [`Svg`]. pub handle: H, /// The [`Color`] filter to be applied to the [`Svg`]. /// /// If some [`Color`] is set, the whole [`Svg`] will be /// painted with it—ignoring any intrinsic colors. /// /// This can be useful for coloring icons programmatically /// (e.g. with a theme). pub color: Option, /// The rotation to be applied to the image; on its center. pub rotation: Radians, /// The opacity of the [`Svg`]. /// /// 0 means transparent. 1 means opaque. pub opacity: f32, } impl Svg { /// Creates a new [`Svg`] with the given handle. pub fn new(handle: impl Into) -> Self { Self { handle: handle.into(), color: None, rotation: Radians(0.0), opacity: 1.0, } } /// Sets the [`Color`] filter of the [`Svg`]. pub fn color(mut self, color: impl Into) -> Self { self.color = Some(color.into()); self } /// Sets the rotation of the [`Svg`]. pub fn rotation(mut self, rotation: impl Into) -> Self { self.rotation = rotation.into(); self } /// Sets the opacity of the [`Svg`]. pub fn opacity(mut self, opacity: impl Into) -> Self { self.opacity = opacity.into(); self } } impl From<&Handle> for Svg { fn from(handle: &Handle) -> Self { Svg::new(handle.clone()) } } /// A handle of Svg data. #[derive(Debug, Clone, PartialEq, Eq)] pub struct Handle { id: u64, data: Arc, } impl Handle { /// Creates an SVG [`Handle`] pointing to the vector image of the given /// path. pub fn from_path(path: impl Into) -> Handle { Self::from_data(Data::Path(path.into())) } /// Creates an SVG [`Handle`] from raw bytes containing either an SVG string /// or gzip compressed data. /// /// This is useful if you already have your SVG data in-memory, maybe /// because you downloaded or generated it procedurally. pub fn from_memory(bytes: impl Into>) -> Handle { Self::from_data(Data::Bytes(bytes.into())) } fn from_data(data: Data) -> Handle { let mut hasher = FxHasher::default(); data.hash(&mut hasher); Handle { id: hasher.finish(), data: Arc::new(data), } } /// Returns the unique identifier of the [`Handle`]. pub fn id(&self) -> u64 { self.id } /// Returns a reference to the SVG [`Data`]. pub fn data(&self) -> &Data { &self.data } } impl From for Handle where T: Into, { fn from(path: T) -> Handle { Handle::from_path(path.into()) } } impl Hash for Handle { fn hash(&self, state: &mut H) { self.id.hash(state); } } /// The data of a vectorial image. #[derive(Clone, Hash, PartialEq, Eq)] pub enum Data { /// File data Path(PathBuf), /// In-memory data /// /// Can contain an SVG string or a gzip compressed data. Bytes(Cow<'static, [u8]>), } impl std::fmt::Debug for Data { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Data::Path(path) => write!(f, "Path({path:?})"), Data::Bytes(_) => write!(f, "Bytes(...)"), } } } /// A [`Renderer`] that can render vector graphics. /// /// [renderer]: crate::renderer pub trait Renderer: crate::Renderer { /// Returns the default dimensions of an SVG for the given [`Handle`]. fn measure_svg(&self, handle: &Handle) -> Size; /// Draws an SVG with the given [`Handle`], an optional [`Color`] filter, and inside the provided `bounds`. fn draw_svg(&mut self, svg: Svg, bounds: Rectangle, clip_bounds: Rectangle); } ================================================ FILE: core/src/text/editor.rs ================================================ //! Edit text. use crate::text::highlighter::{self, Highlighter}; use crate::text::{LineHeight, Wrapping}; use crate::{Pixels, Point, Rectangle, Size}; use std::borrow::Cow; use std::sync::Arc; /// A component that can be used by widgets to edit multi-line text. pub trait Editor: Sized + Default { /// The font of the [`Editor`]. type Font: Copy + PartialEq + Default; /// Creates a new [`Editor`] laid out with the given text. fn with_text(text: &str) -> Self; /// Returns true if the [`Editor`] has no contents. fn is_empty(&self) -> bool; /// Returns the current [`Cursor`] of the [`Editor`]. fn cursor(&self) -> Cursor; /// Returns the current [`Selection`] of the [`Editor`]. fn selection(&self) -> Selection; /// Returns the current selected text of the [`Editor`]. fn copy(&self) -> Option; /// Returns the text of the given line in the [`Editor`], if it exists. fn line(&self, index: usize) -> Option>; /// Returns the amount of lines in the [`Editor`]. fn line_count(&self) -> usize; /// Performs an [`Action`] on the [`Editor`]. fn perform(&mut self, action: Action); /// Moves the cursor to the given position. fn move_to(&mut self, cursor: Cursor); /// Returns the current boundaries of the [`Editor`]. fn bounds(&self) -> Size; /// Returns the minimum boundaries to fit the current contents of /// the [`Editor`]. fn min_bounds(&self) -> Size; /// Returns the hint factor of the [`Editor`]. fn hint_factor(&self) -> Option; /// Updates the [`Editor`] with some new attributes. fn update( &mut self, new_bounds: Size, new_font: Self::Font, new_size: Pixels, new_line_height: LineHeight, new_wrapping: Wrapping, new_hint_factor: Option, new_highlighter: &mut impl Highlighter, ); /// Runs a text [`Highlighter`] in the [`Editor`]. fn highlight( &mut self, font: Self::Font, highlighter: &mut H, format_highlight: impl Fn(&H::Highlight) -> highlighter::Format, ); } /// An interaction with an [`Editor`]. #[derive(Debug, Clone, PartialEq)] pub enum Action { /// Apply a [`Motion`]. Move(Motion), /// Select text with a given [`Motion`]. Select(Motion), /// Select the word at the current cursor. SelectWord, /// Select the line at the current cursor. SelectLine, /// Select the entire buffer. SelectAll, /// Perform an [`Edit`]. Edit(Edit), /// Click the [`Editor`] at the given [`Point`]. Click(Point), /// Drag the mouse on the [`Editor`] to the given [`Point`]. Drag(Point), /// Scroll the [`Editor`] a certain amount of lines. Scroll { /// The amount of lines to scroll. lines: i32, }, } impl Action { /// Returns whether the [`Action`] is an editing action. pub fn is_edit(&self) -> bool { matches!(self, Self::Edit(_)) } } /// An action that edits text. #[derive(Debug, Clone, PartialEq)] pub enum Edit { /// Insert the given character. Insert(char), /// Paste the given text. Paste(Arc), /// Break the current line. Enter, /// Indent the current line. Indent, /// Unindent the current line. Unindent, /// Delete the previous character. Backspace, /// Delete the next character. Delete, } /// A cursor movement. #[derive(Debug, Clone, Copy, PartialEq)] pub enum Motion { /// Move left. Left, /// Move right. Right, /// Move up. Up, /// Move down. Down, /// Move to the left boundary of a word. WordLeft, /// Move to the right boundary of a word. WordRight, /// Move to the start of the line. Home, /// Move to the end of the line. End, /// Move to the start of the previous window. PageUp, /// Move to the start of the next window. PageDown, /// Move to the start of the text. DocumentStart, /// Move to the end of the text. DocumentEnd, } impl Motion { /// Widens the [`Motion`], if possible. pub fn widen(self) -> Self { match self { Self::Left => Self::WordLeft, Self::Right => Self::WordRight, Self::Home => Self::DocumentStart, Self::End => Self::DocumentEnd, _ => self, } } /// Returns the [`Direction`] of the [`Motion`]. pub fn direction(&self) -> Direction { match self { Self::Left | Self::Up | Self::WordLeft | Self::Home | Self::PageUp | Self::DocumentStart => Direction::Left, Self::Right | Self::Down | Self::WordRight | Self::End | Self::PageDown | Self::DocumentEnd => Direction::Right, } } } /// A direction in some text. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Direction { /// <- Left, /// -> Right, } /// The cursor of an [`Editor`]. #[derive(Debug, Clone)] pub enum Selection { /// Cursor without a selection Caret(Point), /// Cursor selecting a range of text Range(Vec), } /// The range of an [`Editor`]. #[derive(Debug, Clone, Copy, PartialEq)] pub struct Cursor { /// The cursor position. pub position: Position, /// The selection position, if any. pub selection: Option, } /// A cursor position in an [`Editor`]. #[derive(Debug, Clone, Copy, PartialEq)] pub struct Position { /// The line of text. pub line: usize, /// The column in the line. pub column: usize, } /// A line of an [`Editor`]. #[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct Line<'a> { /// The raw text of the [`Line`]. pub text: Cow<'a, str>, /// The line ending of the [`Line`]. pub ending: LineEnding, } /// The line ending of a [`Line`]. #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] pub enum LineEnding { /// Use `\n` for line ending (POSIX-style) #[default] Lf, /// Use `\r\n` for line ending (Windows-style) CrLf, /// Use `\r` for line ending (many legacy systems) Cr, /// Use `\n\r` for line ending (some legacy systems) LfCr, /// No line ending None, } impl LineEnding { /// Gets the string representation of the [`LineEnding`]. pub fn as_str(self) -> &'static str { match self { Self::Lf => "\n", Self::CrLf => "\r\n", Self::Cr => "\r", Self::LfCr => "\n\r", Self::None => "", } } } ================================================ FILE: core/src/text/highlighter.rs ================================================ //! Highlight text. use crate::Color; use std::ops::Range; /// A type capable of highlighting text. /// /// A [`Highlighter`] highlights lines in sequence. When a line changes, /// it must be notified and the lines after the changed one must be fed /// again to the [`Highlighter`]. pub trait Highlighter: 'static { /// The settings to configure the [`Highlighter`]. type Settings: PartialEq + Clone; /// The output of the [`Highlighter`]. type Highlight; /// The highlight iterator type. type Iterator<'a>: Iterator, Self::Highlight)> where Self: 'a; /// Creates a new [`Highlighter`] from its [`Self::Settings`]. fn new(settings: &Self::Settings) -> Self; /// Updates the [`Highlighter`] with some new [`Self::Settings`]. fn update(&mut self, new_settings: &Self::Settings); /// Notifies the [`Highlighter`] that the line at the given index has changed. fn change_line(&mut self, line: usize); /// Highlights the given line. /// /// If a line changed prior to this, the first line provided here will be the /// line that changed. fn highlight_line(&mut self, line: &str) -> Self::Iterator<'_>; /// Returns the current line of the [`Highlighter`]. /// /// If `change_line` has been called, this will normally be the least index /// that changed. fn current_line(&self) -> usize; } /// A highlighter that highlights nothing. #[derive(Debug, Clone, Copy)] pub struct PlainText; impl Highlighter for PlainText { type Settings = (); type Highlight = (); type Iterator<'a> = std::iter::Empty<(Range, Self::Highlight)>; fn new(_settings: &Self::Settings) -> Self { Self } fn update(&mut self, _new_settings: &Self::Settings) {} fn change_line(&mut self, _line: usize) {} fn highlight_line(&mut self, _line: &str) -> Self::Iterator<'_> { std::iter::empty() } fn current_line(&self) -> usize { usize::MAX } } /// The format of some text. #[derive(Debug, Clone, Copy, PartialEq)] pub struct Format { /// The [`Color`] of the text. pub color: Option, /// The `Font` of the text. pub font: Option, } impl Default for Format { fn default() -> Self { Self { color: None, font: None, } } } ================================================ FILE: core/src/text/paragraph.rs ================================================ //! Draw paragraphs. use crate::alignment; use crate::text::{ Alignment, Difference, Ellipsis, Hit, LineHeight, Shaping, Span, Text, Wrapping, }; use crate::{Pixels, Point, Rectangle, Size}; /// A text paragraph. pub trait Paragraph: Sized + Default { /// The font of this [`Paragraph`]. type Font: Copy + PartialEq; /// Creates a new [`Paragraph`] laid out with the given [`Text`]. fn with_text(text: Text<&str, Self::Font>) -> Self; /// Creates a new [`Paragraph`] laid out with the given [`Text`]. fn with_spans(text: Text<&[Span<'_, Link, Self::Font>], Self::Font>) -> Self; /// Lays out the [`Paragraph`] with some new boundaries. fn resize(&mut self, new_bounds: Size); /// Compares the [`Paragraph`] with some desired [`Text`] and returns the /// [`Difference`]. fn compare(&self, text: Text<(), Self::Font>) -> Difference; /// Returns the text size of the [`Paragraph`] in [`Pixels`]. fn size(&self) -> Pixels; /// Returns the hint factor of the [`Paragraph`]. fn hint_factor(&self) -> Option; /// Returns the font of the [`Paragraph`]. fn font(&self) -> Self::Font; /// Returns the [`LineHeight`] of the [`Paragraph`]. fn line_height(&self) -> LineHeight; /// Returns the horizontal alignment of the [`Paragraph`]. fn align_x(&self) -> Alignment; /// Returns the vertical alignment of the [`Paragraph`]. fn align_y(&self) -> alignment::Vertical; /// Returns the [`Wrapping`] strategy of the [`Paragraph`]> fn wrapping(&self) -> Wrapping; /// Returns the [`Ellipsis`] strategy of the [`Paragraph`]> fn ellipsis(&self) -> Ellipsis; /// Returns the [`Shaping`] strategy of the [`Paragraph`]> fn shaping(&self) -> Shaping; /// Returns the available bounds used to layout the [`Paragraph`]. fn bounds(&self) -> Size; /// Returns the minimum boundaries that can fit the contents of the /// [`Paragraph`]. fn min_bounds(&self) -> Size; /// Tests whether the provided point is within the boundaries of the /// [`Paragraph`], returning information about the nearest character. fn hit_test(&self, point: Point) -> Option; /// Tests whether the provided point is within the boundaries of a /// [`Span`] in the [`Paragraph`], returning the index of the [`Span`] /// that was hit. fn hit_span(&self, point: Point) -> Option; /// Returns all bounds for the provided [`Span`] index of the [`Paragraph`]. /// A [`Span`] can have multiple bounds for each line it's on. fn span_bounds(&self, index: usize) -> Vec; /// Returns the distance to the given grapheme index in the [`Paragraph`]. fn grapheme_position(&self, line: usize, index: usize) -> Option; /// Returns the minimum width that can fit the contents of the [`Paragraph`]. fn min_width(&self) -> f32 { self.min_bounds().width } /// Returns the minimum height that can fit the contents of the [`Paragraph`]. fn min_height(&self) -> f32 { self.min_bounds().height } } /// A [`Paragraph`] of plain text. #[derive(Debug, Clone, Default)] pub struct Plain { raw: P, content: String, } impl Plain

{ /// Creates a new [`Plain`] paragraph. pub fn new(text: Text) -> Self { Self { raw: P::with_text(text.as_ref()), content: text.content, } } /// Updates the plain [`Paragraph`] to match the given [`Text`], if needed. /// /// Returns true if the [`Paragraph`] changed. pub fn update(&mut self, text: Text<&str, P::Font>) -> bool { if self.content != text.content { text.content.clone_into(&mut self.content); self.raw = P::with_text(text); return true; } match self.raw.compare(text.with_content(())) { Difference::None => false, Difference::Bounds => { self.raw.resize(text.bounds); true } Difference::Shape => { self.raw = P::with_text(text); true } } } /// Returns the horizontal alignment of the [`Paragraph`]. pub fn align_x(&self) -> Alignment { self.raw.align_x() } /// Returns the vertical alignment of the [`Paragraph`]. pub fn align_y(&self) -> alignment::Vertical { self.raw.align_y() } /// Returns the minimum boundaries that can fit the contents of the /// [`Paragraph`]. pub fn min_bounds(&self) -> Size { self.raw.min_bounds() } /// Returns the minimum width that can fit the contents of the /// [`Paragraph`]. pub fn min_width(&self) -> f32 { self.raw.min_width() } /// Returns the minimum height that can fit the contents of the /// [`Paragraph`]. pub fn min_height(&self) -> f32 { self.raw.min_height() } /// Returns the cached [`Paragraph`]. pub fn raw(&self) -> &P { &self.raw } /// Returns the current content of the plain [`Paragraph`]. pub fn content(&self) -> &str { &self.content } /// Returns the [`Paragraph`] as a [`Text`] definition. pub fn as_text(&self) -> Text<&str, P::Font> { Text { content: &self.content, bounds: self.raw.bounds(), size: self.raw.size(), line_height: self.raw.line_height(), font: self.raw.font(), align_x: self.raw.align_x(), align_y: self.raw.align_y(), shaping: self.raw.shaping(), wrapping: self.raw.wrapping(), ellipsis: self.raw.ellipsis(), hint_factor: self.raw.hint_factor(), } } } ================================================ FILE: core/src/text.rs ================================================ //! Draw and interact with text. pub mod editor; pub mod highlighter; pub mod paragraph; pub use editor::Editor; pub use highlighter::Highlighter; pub use paragraph::Paragraph; use crate::alignment; use crate::{Background, Border, Color, Padding, Pixels, Point, Rectangle, Size}; use std::borrow::Cow; use std::hash::{Hash, Hasher}; /// A paragraph. #[derive(Debug, Clone, Copy)] pub struct Text { /// The content of the paragraph. pub content: Content, /// The bounds of the paragraph. pub bounds: Size, /// The size of the [`Text`] in logical pixels. pub size: Pixels, /// The line height of the [`Text`]. pub line_height: LineHeight, /// The font of the [`Text`]. pub font: Font, /// The horizontal alignment of the [`Text`]. pub align_x: Alignment, /// The vertical alignment of the [`Text`]. pub align_y: alignment::Vertical, /// The [`Shaping`] strategy of the [`Text`]. pub shaping: Shaping, /// The [`Wrapping`] strategy of the [`Text`]. pub wrapping: Wrapping, /// The [`Ellipsis`] strategy of the [`Text`]. pub ellipsis: Ellipsis, /// The scale factor that may be used to internally scale the layout /// calculation of the [`Paragraph`] and leverage metrics hinting. /// /// Effectively, this defines the "base" layout that will be used for /// linear scaling. /// /// If `None`, hinting will be disabled and subpixel positioning will be /// performed. pub hint_factor: Option, } impl Text where Font: Copy, { /// Returns a new [`Text`] replacing only the content with the /// given value. pub fn with_content(&self, content: T) -> Text { Text { content, bounds: self.bounds, size: self.size, line_height: self.line_height, font: self.font, align_x: self.align_x, align_y: self.align_y, shaping: self.shaping, wrapping: self.wrapping, ellipsis: self.ellipsis, hint_factor: self.hint_factor, } } } impl Text where Content: AsRef, Font: Copy, { /// Returns a borrowed version of [`Text`]. pub fn as_ref(&self) -> Text<&str, Font> { self.with_content(self.content.as_ref()) } } /// The alignment of some text. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] pub enum Alignment { /// No specific alignment. /// /// Left-to-right text will be aligned to the left, while /// right-to-left text will be aligned to the right. #[default] Default, /// Align text to the left. Left, /// Center text. Center, /// Align text to the right. Right, /// Justify text. Justified, } impl From for Alignment { fn from(alignment: alignment::Horizontal) -> Self { match alignment { alignment::Horizontal::Left => Self::Left, alignment::Horizontal::Center => Self::Center, alignment::Horizontal::Right => Self::Right, } } } impl From for Alignment { fn from(alignment: crate::Alignment) -> Self { match alignment { crate::Alignment::Start => Self::Left, crate::Alignment::Center => Self::Center, crate::Alignment::End => Self::Right, } } } impl From for alignment::Horizontal { fn from(alignment: Alignment) -> Self { match alignment { Alignment::Default | Alignment::Left | Alignment::Justified => { alignment::Horizontal::Left } Alignment::Center => alignment::Horizontal::Center, Alignment::Right => alignment::Horizontal::Right, } } } /// The shaping strategy of some text. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum Shaping { /// Auto-detect the best shaping strategy from the text. /// /// This strategy will use [`Basic`](Self::Basic) shaping if the /// text consists of only ASCII characters; otherwise, it will /// use [`Advanced`](Self::Advanced) shaping. /// /// This is the default, if neither the `basic-shaping` nor `advanced-shaping` /// features are enabled. Auto, /// No shaping and no font fallback. /// /// This shaping strategy is very cheap, but it will not display complex /// scripts properly nor try to find missing glyphs in your system fonts. /// /// You should use this strategy when you have complete control of the text /// and the font you are displaying in your application. /// /// This will be the default if the `basic-shaping` feature is enabled and /// the `advanced-shaping` feature is disabled. Basic, /// Advanced text shaping and font fallback. /// /// You will need to enable this flag if the text contains a complex /// script, the font used needs it, and/or multiple fonts in your system /// may be needed to display all of the glyphs. /// /// Advanced shaping is expensive! You should only enable it when necessary. /// /// This will be the default if the `advanced-shaping` feature is enabled. Advanced, } impl Default for Shaping { fn default() -> Self { if cfg!(feature = "advanced-shaping") { Self::Advanced } else if cfg!(feature = "basic-shaping") { Self::Basic } else { Self::Auto } } } /// The wrapping strategy of some text. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] pub enum Wrapping { /// No wrapping. None, /// Wraps at the word level. /// /// This is the default. #[default] Word, /// Wraps at the glyph level. Glyph, /// Wraps at the word level, or fallback to glyph level if a word can't fit on a line by itself. WordOrGlyph, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] /// The ellipsis strategy of some text. pub enum Ellipsis { /// No ellipsis. /// /// This is the default. #[default] None, /// Ellipsize the start of the last visual line in the text. Start, /// Ellipsize the middle of the last visual line in the text. Middle, /// Ellipsize the end of the last visual line in the text. End, } /// The height of a line of text in a paragraph. #[derive(Debug, Clone, Copy, PartialEq)] pub enum LineHeight { /// A factor of the size of the text. Relative(f32), /// An absolute height in logical pixels. Absolute(Pixels), } impl LineHeight { /// Returns the [`LineHeight`] in absolute logical pixels. pub fn to_absolute(self, text_size: Pixels) -> Pixels { match self { Self::Relative(factor) => Pixels(factor * text_size.0), Self::Absolute(pixels) => pixels, } } } impl Default for LineHeight { fn default() -> Self { Self::Relative(1.3) } } impl From for LineHeight { fn from(factor: f32) -> Self { Self::Relative(factor) } } impl From for LineHeight { fn from(pixels: Pixels) -> Self { Self::Absolute(pixels) } } impl Hash for LineHeight { fn hash(&self, state: &mut H) { match self { Self::Relative(factor) => { state.write_u8(0); factor.to_bits().hash(state); } Self::Absolute(pixels) => { state.write_u8(1); f32::from(*pixels).to_bits().hash(state); } } } } /// The result of hit testing on text. #[derive(Debug, Clone, Copy, PartialEq)] pub enum Hit { /// The point was within the bounds of the returned character index. CharOffset(usize), } impl Hit { /// Computes the cursor position of the [`Hit`] . pub fn cursor(self) -> usize { match self { Self::CharOffset(i) => i, } } } /// The difference detected in some text. /// /// You will obtain a [`Difference`] when you [`compare`] a [`Paragraph`] with some /// [`Text`]. /// /// [`compare`]: Paragraph::compare #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Difference { /// No difference. /// /// The text can be reused as it is! None, /// A bounds difference. /// /// This normally means a relayout is necessary, but the shape of the text can /// be reused. Bounds, /// A shape difference. /// /// The contents, alignment, sizes, fonts, or any other essential attributes /// of the shape of the text have changed. A complete reshape and relayout of /// the text is necessary. Shape, } /// A renderer capable of measuring and drawing [`Text`]. pub trait Renderer: crate::Renderer { /// The font type used. type Font: Copy + PartialEq; /// The [`Paragraph`] of this [`Renderer`]. type Paragraph: Paragraph + 'static; /// The [`Editor`] of this [`Renderer`]. type Editor: Editor + 'static; /// The icon font of the backend. const ICON_FONT: Self::Font; /// The `char` representing a ✔ icon in the [`ICON_FONT`]. /// /// [`ICON_FONT`]: Self::ICON_FONT const CHECKMARK_ICON: char; /// The `char` representing a ▼ icon in the built-in [`ICON_FONT`]. /// /// [`ICON_FONT`]: Self::ICON_FONT const ARROW_DOWN_ICON: char; /// The `char` representing a ^ icon in the built-in [`ICON_FONT`]. /// /// [`ICON_FONT`]: Self::ICON_FONT const SCROLL_UP_ICON: char; /// The `char` representing a v icon in the built-in [`ICON_FONT`]. /// /// [`ICON_FONT`]: Self::ICON_FONT const SCROLL_DOWN_ICON: char; /// The `char` representing a < icon in the built-in [`ICON_FONT`]. /// /// [`ICON_FONT`]: Self::ICON_FONT const SCROLL_LEFT_ICON: char; /// The `char` representing a > icon in the built-in [`ICON_FONT`]. /// /// [`ICON_FONT`]: Self::ICON_FONT const SCROLL_RIGHT_ICON: char; /// The 'char' representing the iced logo in the built-in ['ICON_FONT']. /// /// ['ICON_FONT']: Self::ICON_FONT const ICED_LOGO: char; /// Returns the default [`Self::Font`]. fn default_font(&self) -> Self::Font; /// Returns the default size of [`Text`]. fn default_size(&self) -> Pixels; /// Draws the given [`Paragraph`] at the given position and with the given /// [`Color`]. fn fill_paragraph( &mut self, text: &Self::Paragraph, position: Point, color: Color, clip_bounds: Rectangle, ); /// Draws the given [`Editor`] at the given position and with the given /// [`Color`]. fn fill_editor( &mut self, editor: &Self::Editor, position: Point, color: Color, clip_bounds: Rectangle, ); /// Draws the given [`Text`] at the given position and with the given /// [`Color`]. fn fill_text( &mut self, text: Text, position: Point, color: Color, clip_bounds: Rectangle, ); } /// A span of text. #[derive(Debug, Clone)] pub struct Span<'a, Link = (), Font = crate::Font> { /// The [`Fragment`] of text. pub text: Fragment<'a>, /// The size of the [`Span`] in [`Pixels`]. pub size: Option, /// The [`LineHeight`] of the [`Span`]. pub line_height: Option, /// The font of the [`Span`]. pub font: Option, /// The [`Color`] of the [`Span`]. pub color: Option, /// The link of the [`Span`]. pub link: Option, /// The [`Highlight`] of the [`Span`]. pub highlight: Option, /// The [`Padding`] of the [`Span`]. /// /// Currently, it only affects the bounds of the [`Highlight`]. pub padding: Padding, /// Whether the [`Span`] should be underlined or not. pub underline: bool, /// Whether the [`Span`] should be struck through or not. pub strikethrough: bool, } /// A text highlight. #[derive(Debug, Clone, Copy, PartialEq)] pub struct Highlight { /// The [`Background`] of the highlight. pub background: Background, /// The [`Border`] of the highlight. pub border: Border, } impl<'a, Link, Font> Span<'a, Link, Font> { /// Creates a new [`Span`] of text with the given text fragment. pub fn new(fragment: impl IntoFragment<'a>) -> Self { Self { text: fragment.into_fragment(), ..Self::default() } } /// Sets the size of the [`Span`]. pub fn size(mut self, size: impl Into) -> Self { self.size = Some(size.into()); self } /// Sets the [`LineHeight`] of the [`Span`]. pub fn line_height(mut self, line_height: impl Into) -> Self { self.line_height = Some(line_height.into()); self } /// Sets the font of the [`Span`]. pub fn font(mut self, font: impl Into) -> Self { self.font = Some(font.into()); self } /// Sets the font of the [`Span`], if any. pub fn font_maybe(mut self, font: Option>) -> Self { self.font = font.map(Into::into); self } /// Sets the [`Color`] of the [`Span`]. pub fn color(mut self, color: impl Into) -> Self { self.color = Some(color.into()); self } /// Sets the [`Color`] of the [`Span`], if any. pub fn color_maybe(mut self, color: Option>) -> Self { self.color = color.map(Into::into); self } /// Sets the link of the [`Span`]. pub fn link(mut self, link: impl Into) -> Self { self.link = Some(link.into()); self } /// Sets the link of the [`Span`], if any. pub fn link_maybe(mut self, link: Option>) -> Self { self.link = link.map(Into::into); self } /// Sets the [`Background`] of the [`Span`]. pub fn background(self, background: impl Into) -> Self { self.background_maybe(Some(background)) } /// Sets the [`Background`] of the [`Span`], if any. pub fn background_maybe(mut self, background: Option>) -> Self { let Some(background) = background else { return self; }; match &mut self.highlight { Some(highlight) => { highlight.background = background.into(); } None => { self.highlight = Some(Highlight { background: background.into(), border: Border::default(), }); } } self } /// Sets the [`Border`] of the [`Span`]. pub fn border(self, border: impl Into) -> Self { self.border_maybe(Some(border)) } /// Sets the [`Border`] of the [`Span`], if any. pub fn border_maybe(mut self, border: Option>) -> Self { let Some(border) = border else { return self; }; match &mut self.highlight { Some(highlight) => { highlight.border = border.into(); } None => { self.highlight = Some(Highlight { border: border.into(), background: Background::Color(Color::TRANSPARENT), }); } } self } /// Sets the [`Padding`] of the [`Span`]. /// /// It only affects the [`background`] and [`border`] of the /// [`Span`], currently. /// /// [`background`]: Self::background /// [`border`]: Self::border pub fn padding(mut self, padding: impl Into) -> Self { self.padding = padding.into(); self } /// Sets whether the [`Span`] should be underlined or not. pub fn underline(mut self, underline: bool) -> Self { self.underline = underline; self } /// Sets whether the [`Span`] should be struck through or not. pub fn strikethrough(mut self, strikethrough: bool) -> Self { self.strikethrough = strikethrough; self } /// Turns the [`Span`] into a static one. pub fn to_static(self) -> Span<'static, Link, Font> { Span { text: Cow::Owned(self.text.into_owned()), size: self.size, line_height: self.line_height, font: self.font, color: self.color, link: self.link, highlight: self.highlight, padding: self.padding, underline: self.underline, strikethrough: self.strikethrough, } } } impl Default for Span<'_, Link, Font> { fn default() -> Self { Self { text: Cow::default(), size: None, line_height: None, font: None, color: None, link: None, highlight: None, padding: Padding::default(), underline: false, strikethrough: false, } } } impl<'a, Link, Font> From<&'a str> for Span<'a, Link, Font> { fn from(value: &'a str) -> Self { Span::new(value) } } impl PartialEq for Span<'_, Link, Font> { fn eq(&self, other: &Self) -> bool { self.text == other.text && self.size == other.size && self.line_height == other.line_height && self.font == other.font && self.color == other.color } } /// A fragment of [`Text`]. /// /// This is just an alias to a string that may be either /// borrowed or owned. pub type Fragment<'a> = Cow<'a, str>; /// A trait for converting a value to some text [`Fragment`]. pub trait IntoFragment<'a> { /// Converts the value to some text [`Fragment`]. fn into_fragment(self) -> Fragment<'a>; } impl<'a> IntoFragment<'a> for Fragment<'a> { fn into_fragment(self) -> Fragment<'a> { self } } impl<'a> IntoFragment<'a> for &'a Fragment<'_> { fn into_fragment(self) -> Fragment<'a> { Fragment::Borrowed(self) } } impl<'a> IntoFragment<'a> for &'a str { fn into_fragment(self) -> Fragment<'a> { Fragment::Borrowed(self) } } impl<'a> IntoFragment<'a> for &'a String { fn into_fragment(self) -> Fragment<'a> { Fragment::Borrowed(self.as_str()) } } impl<'a> IntoFragment<'a> for String { fn into_fragment(self) -> Fragment<'a> { Fragment::Owned(self) } } macro_rules! into_fragment { ($type:ty) => { impl<'a> IntoFragment<'a> for $type { fn into_fragment(self) -> Fragment<'a> { Fragment::Owned(self.to_string()) } } impl<'a> IntoFragment<'a> for &$type { fn into_fragment(self) -> Fragment<'a> { Fragment::Owned(self.to_string()) } } }; } into_fragment!(char); into_fragment!(bool); into_fragment!(u8); into_fragment!(u16); into_fragment!(u32); into_fragment!(u64); into_fragment!(u128); into_fragment!(usize); into_fragment!(i8); into_fragment!(i16); into_fragment!(i32); into_fragment!(i64); into_fragment!(i128); into_fragment!(isize); into_fragment!(f32); into_fragment!(f64); ================================================ FILE: core/src/theme/palette.rs ================================================ //! Define the colors of a theme. use crate::{Color, color}; use std::sync::LazyLock; /// An extended set of colors generated from a [`Seed`]. #[derive(Debug, Clone, Copy, PartialEq)] pub struct Palette { /// The set of background colors. pub background: Background, /// The set of primary colors. pub primary: Swatch, /// The set of secondary colors. pub secondary: Swatch, /// The set of success colors. pub success: Swatch, /// The set of warning colors. pub warning: Swatch, /// The set of danger colors. pub danger: Swatch, /// Whether the palette is dark or not. pub is_dark: bool, } impl Palette { /// Generates a [`Palette`] from the given [`Seed`]. pub fn generate(palette: Seed) -> Self { Self { background: Background::new(palette.background, palette.text), primary: Swatch::generate(palette.primary, palette.background, palette.text), secondary: Swatch::derive(palette.background, palette.text), success: Swatch::generate(palette.success, palette.background, palette.text), warning: Swatch::generate(palette.warning, palette.background, palette.text), danger: Swatch::generate(palette.danger, palette.background, palette.text), is_dark: is_dark(palette.background), } } } /// A pair of background and text colors. #[derive(Debug, Clone, Copy, PartialEq)] pub struct Pair { /// The background color. pub color: Color, /// The text color. /// /// It's guaranteed to be readable on top of the background [`color`]. /// /// [`color`]: Self::color pub text: Color, } impl Pair { /// Creates a new [`Pair`] from a background [`Color`] and some text [`Color`]. pub fn new(color: Color, text: Color) -> Self { Self { color, text: readable(color, text), } } } /// A set of background colors. #[derive(Debug, Clone, Copy, PartialEq)] pub struct Background { /// The base background color. pub base: Pair, /// The weakest version of the base background color. pub weakest: Pair, /// A weaker version of the base background color. pub weaker: Pair, /// A weak version of the base background color. pub weak: Pair, /// A neutral version of the base background color, between weak and strong. pub neutral: Pair, /// A strong version of the base background color. pub strong: Pair, /// A stronger version of the base background color. pub stronger: Pair, /// The strongest version of the base background color. pub strongest: Pair, } impl Background { /// Generates a set of [`Background`] colors from the base and text colors. pub fn new(base: Color, text: Color) -> Self { let weakest = deviate(base, 0.03); let weaker = deviate(base, 0.07); let weak = deviate(base, 0.1); let neutral = deviate(base, 0.125); let strong = deviate(base, 0.15); let stronger = deviate(base, 0.175); let strongest = deviate(base, 0.20); Self { base: Pair::new(base, text), weakest: Pair::new(weakest, text), weaker: Pair::new(weaker, text), weak: Pair::new(weak, text), neutral: Pair::new(neutral, text), strong: Pair::new(strong, text), stronger: Pair::new(stronger, text), strongest: Pair::new(strongest, text), } } } /// A color sample in a palette of colors. #[derive(Debug, Clone, Copy, PartialEq)] pub struct Swatch { /// The base color. pub base: Pair, /// A weaker version of the base color. pub weak: Pair, /// A stronger version of the base color. pub strong: Pair, } impl Swatch { /// Generates a [`Swatch`] from a base, background and text color. pub fn generate(base: Color, background: Color, text: Color) -> Self { let weak = base.mix(background, 0.4); let strong = deviate(base, 0.1); Self { base: Pair::new(base, text), weak: Pair::new(weak, text), strong: Pair::new(strong, text), } } /// Derives a [`Swatch`] from a base color and text color. pub fn derive(base: Color, text: Color) -> Self { let factor = if is_dark(base) { 0.2 } else { 0.4 }; let weak = deviate(base, 0.1).mix(text, factor); let strong = deviate(base, 0.3).mix(text, factor); let base = deviate(base, 0.2).mix(text, factor); Self { base: Pair::new(base, text), weak: Pair::new(weak, text), strong: Pair::new(strong, text), } } } /// The base set of colors of a [`Palette`]. #[derive(Debug, Clone, Copy, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Seed { /// The background [`Color`] of the [`Palette`]. pub background: Color, /// The text [`Color`] of the [`Palette`]. pub text: Color, /// The primary [`Color`] of the [`Palette`]. pub primary: Color, /// The success [`Color`] of the [`Palette`]. pub success: Color, /// The warning [`Color`] of the [`Palette`]. pub warning: Color, /// The danger [`Color`] of the [`Palette`]. pub danger: Color, } impl Seed { /// The built-in light variant of a [`Palette`]. pub const LIGHT: Self = Self { background: Color::WHITE, text: Color::BLACK, primary: color!(0x5865F2), success: color!(0x12664f), warning: color!(0xb77e33), danger: color!(0xc3423f), }; /// The built-in dark variant of a [`Palette`]. pub const DARK: Self = Self { background: color!(0x2B2D31), text: Color::from_rgb(0.90, 0.90, 0.90), primary: color!(0x5865F2), success: color!(0x12664f), warning: color!(0xffc14e), danger: color!(0xc3423f), }; /// The built-in [Dracula] variant of a [`Palette`]. /// /// [Dracula]: https://draculatheme.com pub const DRACULA: Self = Self { background: color!(0x282A36), // BACKGROUND text: color!(0xf8f8f2), // FOREGROUND primary: color!(0xbd93f9), // PURPLE success: color!(0x50fa7b), // GREEN warning: color!(0xf1fa8c), // YELLOW danger: color!(0xff5555), // RED }; /// The built-in [Nord] variant of a [`Palette`]. /// /// [Nord]: https://www.nordtheme.com/docs/colors-and-palettes pub const NORD: Self = Self { background: color!(0x2e3440), // nord0 text: color!(0xeceff4), // nord6 primary: color!(0x8fbcbb), // nord7 success: color!(0xa3be8c), // nord14 warning: color!(0xebcb8b), // nord13 danger: color!(0xbf616a), // nord11 }; /// The built-in [Solarized] Light variant of a [`Palette`]. /// /// [Solarized]: https://ethanschoonover.com/solarized pub const SOLARIZED_LIGHT: Self = Self { background: color!(0xfdf6e3), // base3 text: color!(0x657b83), // base00 primary: color!(0x2aa198), // cyan success: color!(0x859900), // green warning: color!(0xb58900), // yellow danger: color!(0xdc322f), // red }; /// The built-in [Solarized] Dark variant of a [`Palette`]. /// /// [Solarized]: https://ethanschoonover.com/solarized pub const SOLARIZED_DARK: Self = Self { background: color!(0x002b36), // base03 text: color!(0x839496), // base0 primary: color!(0x2aa198), // cyan success: color!(0x859900), // green warning: color!(0xb58900), // yellow danger: color!(0xdc322f), // red }; /// The built-in [Gruvbox] Light variant of a [`Palette`]. /// /// [Gruvbox]: https://github.com/morhetz/gruvbox pub const GRUVBOX_LIGHT: Self = Self { background: color!(0xfbf1c7), // light BG_0 text: color!(0x282828), // light FG0_29 primary: color!(0x458588), // light BLUE_4 success: color!(0x98971a), // light GREEN_2 warning: color!(0xd79921), // light YELLOW_3 danger: color!(0xcc241d), // light RED_1 }; /// The built-in [Gruvbox] Dark variant of a [`Palette`]. /// /// [Gruvbox]: https://github.com/morhetz/gruvbox pub const GRUVBOX_DARK: Self = Self { background: color!(0x282828), // dark BG_0 text: color!(0xfbf1c7), // dark FG0_29 primary: color!(0x458588), // dark BLUE_4 success: color!(0x98971a), // dark GREEN_2 warning: color!(0xd79921), // dark YELLOW_3 danger: color!(0xcc241d), // dark RED_1 }; /// The built-in [Catppuccin] Latte variant of a [`Palette`]. /// /// [Catppuccin]: https://github.com/catppuccin/catppuccin pub const CATPPUCCIN_LATTE: Self = Self { background: color!(0xeff1f5), // Base text: color!(0x4c4f69), // Text primary: color!(0x1e66f5), // Blue success: color!(0x40a02b), // Green warning: color!(0xdf8e1d), // Yellow danger: color!(0xd20f39), // Red }; /// The built-in [Catppuccin] Frappé variant of a [`Palette`]. /// /// [Catppuccin]: https://github.com/catppuccin/catppuccin pub const CATPPUCCIN_FRAPPE: Self = Self { background: color!(0x303446), // Base text: color!(0xc6d0f5), // Text primary: color!(0x8caaee), // Blue success: color!(0xa6d189), // Green warning: color!(0xe5c890), // Yellow danger: color!(0xe78284), // Red }; /// The built-in [Catppuccin] Macchiato variant of a [`Palette`]. /// /// [Catppuccin]: https://github.com/catppuccin/catppuccin pub const CATPPUCCIN_MACCHIATO: Self = Self { background: color!(0x24273a), // Base text: color!(0xcad3f5), // Text primary: color!(0x8aadf4), // Blue success: color!(0xa6da95), // Green warning: color!(0xeed49f), // Yellow danger: color!(0xed8796), // Red }; /// The built-in [Catppuccin] Mocha variant of a [`Palette`]. /// /// [Catppuccin]: https://github.com/catppuccin/catppuccin pub const CATPPUCCIN_MOCHA: Self = Self { background: color!(0x1e1e2e), // Base text: color!(0xcdd6f4), // Text primary: color!(0x89b4fa), // Blue success: color!(0xa6e3a1), // Green warning: color!(0xf9e2af), // Yellow danger: color!(0xf38ba8), // Red }; /// The built-in [Tokyo Night] variant of a [`Palette`]. /// /// [Tokyo Night]: https://github.com/enkia/tokyo-night-vscode-theme pub const TOKYO_NIGHT: Self = Self { background: color!(0x1a1b26), // Background (Night) text: color!(0x9aa5ce), // Text primary: color!(0x2ac3de), // Blue success: color!(0x9ece6a), // Green warning: color!(0xe0af68), // Yellow danger: color!(0xf7768e), // Red }; /// The built-in [Tokyo Night] Storm variant of a [`Palette`]. /// /// [Tokyo Night]: https://github.com/enkia/tokyo-night-vscode-theme pub const TOKYO_NIGHT_STORM: Self = Self { background: color!(0x24283b), // Background (Storm) text: color!(0x9aa5ce), // Text primary: color!(0x2ac3de), // Blue success: color!(0x9ece6a), // Green warning: color!(0xe0af68), // Yellow danger: color!(0xf7768e), // Red }; /// The built-in [Tokyo Night] Light variant of a [`Palette`]. /// /// [Tokyo Night]: https://github.com/enkia/tokyo-night-vscode-theme pub const TOKYO_NIGHT_LIGHT: Self = Self { background: color!(0xd5d6db), // Background text: color!(0x565a6e), // Text primary: color!(0x166775), // Blue success: color!(0x485e30), // Green warning: color!(0x8f5e15), // Yellow danger: color!(0x8c4351), // Red }; /// The built-in [Kanagawa] Wave variant of a [`Palette`]. /// /// [Kanagawa]: https://github.com/rebelot/kanagawa.nvim pub const KANAGAWA_WAVE: Self = Self { background: color!(0x1f1f28), // Sumi Ink 3 text: color!(0xDCD7BA), // Fuji White primary: color!(0x7FB4CA), // Wave Blue success: color!(0x76946A), // Autumn Green warning: color!(0xff9e3b), // Ronin Yellow danger: color!(0xC34043), // Autumn Red }; /// The built-in [Kanagawa] Dragon variant of a [`Palette`]. /// /// [Kanagawa]: https://github.com/rebelot/kanagawa.nvim pub const KANAGAWA_DRAGON: Self = Self { background: color!(0x181616), // Dragon Black 3 text: color!(0xc5c9c5), // Dragon White primary: color!(0x223249), // Wave Blue 1 success: color!(0x8a9a7b), // Dragon Green 2 warning: color!(0xff9e3b), // Ronin Yellow danger: color!(0xc4746e), // Dragon Red }; /// The built-in [Kanagawa] Lotus variant of a [`Palette`]. /// /// [Kanagawa]: https://github.com/rebelot/kanagawa.nvim pub const KANAGAWA_LOTUS: Self = Self { background: color!(0xf2ecbc), // Lotus White 3 text: color!(0x545464), // Lotus Ink 1 primary: color!(0x4d699b), // Lotus Blue success: color!(0x6f894e), // Lotus Green warning: color!(0xe98a00), // Lotus Orange 2 danger: color!(0xc84053), // Lotus Red }; /// The built-in [Moonfly] variant of a [`Palette`]. /// /// [Moonfly]: https://github.com/bluz71/vim-moonfly-colors pub const MOONFLY: Self = Self { background: color!(0x080808), // Background text: color!(0xbdbdbd), // Foreground primary: color!(0x80a0ff), // Blue (normal) success: color!(0x8cc85f), // Green (normal) warning: color!(0xe3c78a), // Yellow (normal) danger: color!(0xff5454), // Red (normal) }; /// The built-in [Nightfly] variant of a [`Palette`]. /// /// [Nightfly]: https://github.com/bluz71/vim-nightfly-colors pub const NIGHTFLY: Self = Self { background: color!(0x011627), // Background text: color!(0xbdc1c6), // Foreground primary: color!(0x82aaff), // Blue (normal) success: color!(0xa1cd5e), // Green (normal) warning: color!(0xe3d18a), // Yellow (normal) danger: color!(0xfc514e), // Red (normal) }; /// The built-in [Oxocarbon] variant of a [`Palette`]. /// /// [Oxocarbon]: https://github.com/nyoom-engineering/oxocarbon.nvim pub const OXOCARBON: Self = Self { background: color!(0x232323), text: color!(0xd0d0d0), primary: color!(0x00b4ff), success: color!(0x00c15a), warning: color!(0xbe95ff), // Base 14 danger: color!(0xf62d0f), }; /// The built-in [Ferra] variant of a [`Palette`]. /// /// [Ferra]: https://github.com/casperstorm/ferra pub const FERRA: Self = Self { background: color!(0x2b292d), text: color!(0xfecdb2), primary: color!(0xd1d1e0), success: color!(0xb1b695), warning: color!(0xf5d76e), // Honey danger: color!(0xe06b75), }; } /// The built-in light variant of a [`Palette`]. pub static LIGHT: LazyLock = LazyLock::new(|| Palette::generate(Seed::LIGHT)); /// The built-in dark variant of a [`Palette`]. pub static DARK: LazyLock = LazyLock::new(|| Palette::generate(Seed::DARK)); /// The built-in Dracula variant of a [`Palette`]. pub static DRACULA: LazyLock = LazyLock::new(|| Palette::generate(Seed::DRACULA)); /// The built-in Nord variant of a [`Palette`]. pub static NORD: LazyLock = LazyLock::new(|| Palette::generate(Seed::NORD)); /// The built-in Solarized Light variant of a [`Palette`]. pub static SOLARIZED_LIGHT: LazyLock = LazyLock::new(|| Palette::generate(Seed::SOLARIZED_LIGHT)); /// The built-in Solarized Dark variant of a [`Palette`]. pub static SOLARIZED_DARK: LazyLock = LazyLock::new(|| Palette::generate(Seed::SOLARIZED_DARK)); /// The built-in Gruvbox Light variant of a [`Palette`]. pub static GRUVBOX_LIGHT: LazyLock = LazyLock::new(|| Palette::generate(Seed::GRUVBOX_LIGHT)); /// The built-in Gruvbox Dark variant of a [`Palette`]. pub static GRUVBOX_DARK: LazyLock = LazyLock::new(|| Palette::generate(Seed::GRUVBOX_DARK)); /// The built-in Catppuccin Latte variant of a [`Palette`]. pub static CATPPUCCIN_LATTE: LazyLock = LazyLock::new(|| Palette::generate(Seed::CATPPUCCIN_LATTE)); /// The built-in Catppuccin Frappé variant of a [`Palette`]. pub static CATPPUCCIN_FRAPPE: LazyLock = LazyLock::new(|| Palette::generate(Seed::CATPPUCCIN_FRAPPE)); /// The built-in Catppuccin Macchiato variant of a [`Palette`]. pub static CATPPUCCIN_MACCHIATO: LazyLock = LazyLock::new(|| Palette::generate(Seed::CATPPUCCIN_MACCHIATO)); /// The built-in Catppuccin Mocha variant of a [`Palette`]. pub static CATPPUCCIN_MOCHA: LazyLock = LazyLock::new(|| Palette::generate(Seed::CATPPUCCIN_MOCHA)); /// The built-in Tokyo Night variant of a [`Palette`]. pub static TOKYO_NIGHT: LazyLock = LazyLock::new(|| Palette::generate(Seed::TOKYO_NIGHT)); /// The built-in Tokyo Night Storm variant of a [`Palette`]. pub static TOKYO_NIGHT_STORM: LazyLock = LazyLock::new(|| Palette::generate(Seed::TOKYO_NIGHT_STORM)); /// The built-in Tokyo Night variant of a [`Palette`]. pub static TOKYO_NIGHT_LIGHT: LazyLock = LazyLock::new(|| Palette::generate(Seed::TOKYO_NIGHT_LIGHT)); /// The built-in Kanagawa Wave variant of a [`Palette`]. pub static KANAGAWA_WAVE: LazyLock = LazyLock::new(|| Palette::generate(Seed::KANAGAWA_WAVE)); /// The built-in Kanagawa Dragon variant of a [`Palette`]. pub static KANAGAWA_DRAGON: LazyLock = LazyLock::new(|| Palette::generate(Seed::KANAGAWA_DRAGON)); /// The built-in Kanagawa Lotus variant of a [`Palette`]. pub static KANAGAWA_LOTUS: LazyLock = LazyLock::new(|| Palette::generate(Seed::KANAGAWA_LOTUS)); /// The built-in Moonfly variant of a [`Palette`]. pub static MOONFLY: LazyLock = LazyLock::new(|| Palette::generate(Seed::MOONFLY)); /// The built-in Nightfly variant of a [`Palette`]. pub static NIGHTFLY: LazyLock = LazyLock::new(|| Palette::generate(Seed::NIGHTFLY)); /// The built-in Oxocarbon variant of a [`Palette`]. pub static OXOCARBON: LazyLock = LazyLock::new(|| Palette::generate(Seed::OXOCARBON)); /// The built-in Ferra variant of a [`Palette`]. pub static FERRA: LazyLock = LazyLock::new(|| Palette::generate(Seed::FERRA)); struct Oklch { l: f32, c: f32, h: f32, a: f32, } /// Darkens a [`Color`] by the given factor. pub fn darken(color: Color, amount: f32) -> Color { let mut oklch = to_oklch(color); // We try to bump the chroma a bit for more colorful palettes if oklch.c > 0.0 && oklch.c < (1.0 - oklch.l) / 2.0 { // Formula empirically and cluelessly derived oklch.c *= 1.0 + (0.2 / oklch.c).min(100.0) * amount; } oklch.l = if oklch.l - amount < 0.0 { 0.0 } else { oklch.l - amount }; from_oklch(oklch) } /// Lightens a [`Color`] by the given factor. pub fn lighten(color: Color, amount: f32) -> Color { let mut oklch = to_oklch(color); // We try to bump the chroma a bit for more colorful palettes // Formula empirically and cluelessly derived oklch.c *= 1.0 + 2.0 * amount / oklch.l.max(0.05); oklch.l = if oklch.l + amount > 1.0 { 1.0 } else { oklch.l + amount }; from_oklch(oklch) } /// Deviates a [`Color`] by the given factor. Lightens if the [`Color`] is /// dark, darkens otherwise. pub fn deviate(color: Color, amount: f32) -> Color { if is_dark(color) { lighten(color, amount) } else { darken(color, amount) } } /// Computes a [`Color`] from the given text color that is /// readable on top of the given background color. pub fn readable(background: Color, text: Color) -> Color { if text.is_readable_on(background) { return text; } let improve = if is_dark(background) { lighten } else { darken }; // TODO: Compute factor from relative contrast value let candidate = improve(text, 0.1); if candidate.is_readable_on(background) { return candidate; } let candidate = improve(text, 0.2); if candidate.is_readable_on(background) { return candidate; } let white_contrast = background.relative_contrast(Color::WHITE); let black_contrast = background.relative_contrast(Color::BLACK); if white_contrast >= black_contrast { Color::WHITE.mix(background, 0.05) } else { Color::BLACK.mix(background, 0.05) } } /// Returns true if the [`Color`] is dark. pub fn is_dark(color: Color) -> bool { to_oklch(color).l < 0.6 } // https://en.wikipedia.org/wiki/Oklab_color_space#Conversions_between_color_spaces fn to_oklch(color: Color) -> Oklch { let [r, g, b, alpha] = color.into_linear(); // linear RGB → LMS let l = 0.41222146 * r + 0.53633255 * g + 0.051445995 * b; let m = 0.2119035 * r + 0.6806995 * g + 0.10739696 * b; let s = 0.08830246 * r + 0.28171885 * g + 0.6299787 * b; // Nonlinear transform (cube root) let l_ = l.cbrt(); let m_ = m.cbrt(); let s_ = s.cbrt(); // LMS → Oklab let l = 0.21045426 * l_ + 0.7936178 * m_ - 0.004072047 * s_; let a = 1.9779985 * l_ - 2.4285922 * m_ + 0.4505937 * s_; let b = 0.025904037 * l_ + 0.78277177 * m_ - 0.80867577 * s_; // Oklab → Oklch let c = (a * a + b * b).sqrt(); let h = b.atan2(a); // radians Oklch { l, c, h, a: alpha } } // https://en.wikipedia.org/wiki/Oklab_color_space#Conversions_between_color_spaces fn from_oklch(oklch: Oklch) -> Color { let Oklch { l, c, h, a: alpha } = oklch; let a = c * h.cos(); let b = c * h.sin(); // Oklab → LMS (nonlinear) let l_ = l + 0.39633778 * a + 0.21580376 * b; let m_ = l - 0.105561346 * a - 0.06385417 * b; let s_ = l - 0.08948418 * a - 1.2914855 * b; // Cubing back let l = l_ * l_ * l_; let m = m_ * m_ * m_; let s = s_ * s_ * s_; let r = 4.0767417 * l - 3.3077116 * m + 0.23096994 * s; let g = -1.268438 * l + 2.6097574 * m - 0.34131938 * s; let b = -0.0041960863 * l - 0.7034186 * m + 1.7076147 * s; Color::from_linear_rgba( r.clamp(0.0, 1.0), g.clamp(0.0, 1.0), b.clamp(0.0, 1.0), alpha, ) } ================================================ FILE: core/src/theme.rs ================================================ //! Use the built-in theme and styles. pub mod palette; pub use palette::Palette; use crate::Color; use std::borrow::Cow; use std::fmt; use std::sync::Arc; /// A built-in theme. #[derive(Debug, Clone, PartialEq)] pub enum Theme { /// The built-in light variant. Light, /// The built-in dark variant. Dark, /// The built-in Dracula variant. Dracula, /// The built-in Nord variant. Nord, /// The built-in Solarized Light variant. SolarizedLight, /// The built-in Solarized Dark variant. SolarizedDark, /// The built-in Gruvbox Light variant. GruvboxLight, /// The built-in Gruvbox Dark variant. GruvboxDark, /// The built-in Catppuccin Latte variant. CatppuccinLatte, /// The built-in Catppuccin Frappé variant. CatppuccinFrappe, /// The built-in Catppuccin Macchiato variant. CatppuccinMacchiato, /// The built-in Catppuccin Mocha variant. CatppuccinMocha, /// The built-in Tokyo Night variant. TokyoNight, /// The built-in Tokyo Night Storm variant. TokyoNightStorm, /// The built-in Tokyo Night Light variant. TokyoNightLight, /// The built-in Kanagawa Wave variant. KanagawaWave, /// The built-in Kanagawa Dragon variant. KanagawaDragon, /// The built-in Kanagawa Lotus variant. KanagawaLotus, /// The built-in Moonfly variant. Moonfly, /// The built-in Nightfly variant. Nightfly, /// The built-in Oxocarbon variant. Oxocarbon, /// The built-in Ferra variant: Ferra, /// A [`Theme`] that uses a [`Custom`] palette. Custom(Arc), } impl Theme { /// A list with all the defined themes. pub const ALL: &'static [Self] = &[ Self::Light, Self::Dark, Self::Dracula, Self::Nord, Self::SolarizedLight, Self::SolarizedDark, Self::GruvboxLight, Self::GruvboxDark, Self::CatppuccinLatte, Self::CatppuccinFrappe, Self::CatppuccinMacchiato, Self::CatppuccinMocha, Self::TokyoNight, Self::TokyoNightStorm, Self::TokyoNightLight, Self::KanagawaWave, Self::KanagawaDragon, Self::KanagawaLotus, Self::Moonfly, Self::Nightfly, Self::Oxocarbon, Self::Ferra, ]; /// Creates a new custom [`Theme`] from the given [`Seed`](palette::Seed). pub fn custom(name: impl Into>, seed: palette::Seed) -> Self { Self::custom_with_fn(name, seed, Palette::generate) } /// Creates a new custom [`Theme`] from the given [`Seed`](palette::Seed), with /// a custom generator of a [`Palette`]. pub fn custom_with_fn( name: impl Into>, palette: palette::Seed, generate: impl FnOnce(palette::Seed) -> Palette, ) -> Self { Self::Custom(Arc::new(Custom::with_fn(name, palette, generate))) } /// Returns the [`Palette`] of the [`Theme`]. pub fn palette(&self) -> &palette::Palette { match self { Self::Light => &palette::LIGHT, Self::Dark => &palette::DARK, Self::Dracula => &palette::DRACULA, Self::Nord => &palette::NORD, Self::SolarizedLight => &palette::SOLARIZED_LIGHT, Self::SolarizedDark => &palette::SOLARIZED_DARK, Self::GruvboxLight => &palette::GRUVBOX_LIGHT, Self::GruvboxDark => &palette::GRUVBOX_DARK, Self::CatppuccinLatte => &palette::CATPPUCCIN_LATTE, Self::CatppuccinFrappe => &palette::CATPPUCCIN_FRAPPE, Self::CatppuccinMacchiato => &palette::CATPPUCCIN_MACCHIATO, Self::CatppuccinMocha => &palette::CATPPUCCIN_MOCHA, Self::TokyoNight => &palette::TOKYO_NIGHT, Self::TokyoNightStorm => &palette::TOKYO_NIGHT_STORM, Self::TokyoNightLight => &palette::TOKYO_NIGHT_LIGHT, Self::KanagawaWave => &palette::KANAGAWA_WAVE, Self::KanagawaDragon => &palette::KANAGAWA_DRAGON, Self::KanagawaLotus => &palette::KANAGAWA_LOTUS, Self::Moonfly => &palette::MOONFLY, Self::Nightfly => &palette::NIGHTFLY, Self::Oxocarbon => &palette::OXOCARBON, Self::Ferra => &palette::FERRA, Self::Custom(custom) => &custom.palette, } } /// Returns the [`Seed`](palette::Seed) of the [`Theme`]. pub fn seed(&self) -> palette::Seed { match self { Self::Light => palette::Seed::LIGHT, Self::Dark => palette::Seed::DARK, Self::Dracula => palette::Seed::DRACULA, Self::Nord => palette::Seed::NORD, Self::SolarizedLight => palette::Seed::SOLARIZED_LIGHT, Self::SolarizedDark => palette::Seed::SOLARIZED_DARK, Self::GruvboxLight => palette::Seed::GRUVBOX_LIGHT, Self::GruvboxDark => palette::Seed::GRUVBOX_DARK, Self::CatppuccinLatte => palette::Seed::CATPPUCCIN_LATTE, Self::CatppuccinFrappe => palette::Seed::CATPPUCCIN_FRAPPE, Self::CatppuccinMacchiato => palette::Seed::CATPPUCCIN_MACCHIATO, Self::CatppuccinMocha => palette::Seed::CATPPUCCIN_MOCHA, Self::TokyoNight => palette::Seed::TOKYO_NIGHT, Self::TokyoNightStorm => palette::Seed::TOKYO_NIGHT_STORM, Self::TokyoNightLight => palette::Seed::TOKYO_NIGHT_LIGHT, Self::KanagawaWave => palette::Seed::KANAGAWA_WAVE, Self::KanagawaDragon => palette::Seed::KANAGAWA_DRAGON, Self::KanagawaLotus => palette::Seed::KANAGAWA_LOTUS, Self::Moonfly => palette::Seed::MOONFLY, Self::Nightfly => palette::Seed::NIGHTFLY, Self::Oxocarbon => palette::Seed::OXOCARBON, Self::Ferra => palette::Seed::FERRA, Self::Custom(custom) => custom.seed, } } } impl fmt::Display for Theme { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(self.name()) } } /// A [`Theme`] with a customized [`Palette`]. #[derive(Debug, Clone, PartialEq)] pub struct Custom { name: Cow<'static, str>, seed: palette::Seed, palette: Palette, } impl Custom { /// Creates a [`Custom`] theme from the given [`Seed`](palette::Seed). pub fn new(name: String, seed: palette::Seed) -> Self { Self::with_fn(name, seed, Palette::generate) } /// Creates a [`Custom`] theme from the given [`Seed`](palette::Seed) with /// a custom generator of a [`Palette`]. pub fn with_fn( name: impl Into>, seed: palette::Seed, generate: impl FnOnce(palette::Seed) -> Palette, ) -> Self { Self { name: name.into(), seed, palette: generate(seed), } } } impl fmt::Display for Custom { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.name) } } /// A theme mode, denoting the tone or brightness of a theme. #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum Mode { /// No specific tone. #[default] None, /// A mode referring to themes with light tones. Light, /// A mode referring to themes with dark tones. Dark, } /// The base style of a theme. #[derive(Debug, Clone, Copy, PartialEq)] pub struct Style { /// The background [`Color`] of the application. pub background_color: Color, /// The default text [`Color`] of the application. pub text_color: Color, } /// The default blank style of a theme. pub trait Base { /// Returns the default theme for the preferred [`Mode`]. fn default(preference: Mode) -> Self; /// Returns the [`Mode`] of the theme. fn mode(&self) -> Mode; /// Returns the default base [`Style`] of the theme. fn base(&self) -> Style; /// Returns the [`Seed`](palette::Seed) of the theme. /// /// This may be used by the runtime to recreate a [`Theme`] for /// debugging purposes; like displaying performance metrics or devtools. fn seed(&self) -> Option; /// Returns the unique name of the theme. /// /// This name may be used to efficiently detect theme /// changes in some widgets. fn name(&self) -> &str; } impl Base for Theme { fn default(preference: Mode) -> Self { use std::env; use std::sync::OnceLock; static SYSTEM: OnceLock> = OnceLock::new(); let system = SYSTEM.get_or_init(|| { let name = env::var("ICED_THEME").ok()?; Theme::ALL .iter() .find(|theme| theme.to_string() == name) .cloned() }); if let Some(system) = system { return system.clone(); } match preference { Mode::None | Mode::Light => Self::Light, Mode::Dark => Self::Dark, } } fn mode(&self) -> Mode { if self.palette().is_dark { Mode::Dark } else { Mode::Light } } fn base(&self) -> Style { default(self) } fn seed(&self) -> Option { Some(self.seed()) } fn name(&self) -> &str { match self { Self::Light => "Light", Self::Dark => "Dark", Self::Dracula => "Dracula", Self::Nord => "Nord", Self::SolarizedLight => "Solarized Light", Self::SolarizedDark => "Solarized Dark", Self::GruvboxLight => "Gruvbox Light", Self::GruvboxDark => "Gruvbox Dark", Self::CatppuccinLatte => "Catppuccin Latte", Self::CatppuccinFrappe => "Catppuccin Frappé", Self::CatppuccinMacchiato => "Catppuccin Macchiato", Self::CatppuccinMocha => "Catppuccin Mocha", Self::TokyoNight => "Tokyo Night", Self::TokyoNightStorm => "Tokyo Night Storm", Self::TokyoNightLight => "Tokyo Night Light", Self::KanagawaWave => "Kanagawa Wave", Self::KanagawaDragon => "Kanagawa Dragon", Self::KanagawaLotus => "Kanagawa Lotus", Self::Moonfly => "Moonfly", Self::Nightfly => "Nightfly", Self::Oxocarbon => "Oxocarbon", Self::Ferra => "Ferra", Self::Custom(custom) => &custom.name, } } } /// The default [`Style`] of a built-in [`Theme`]. pub fn default(theme: &Theme) -> Style { let palette = theme.palette(); Style { background_color: palette.background.base.color, text_color: palette.background.base.text, } } ================================================ FILE: core/src/time.rs ================================================ //! Keep track of time, both in native and web platforms! pub use web_time::Duration; pub use web_time::Instant; pub use web_time::SystemTime; /// Creates a [`Duration`] representing the given amount of milliseconds. pub fn milliseconds(milliseconds: u64) -> Duration { Duration::from_millis(milliseconds) } /// Creates a [`Duration`] representing the given amount of seconds. pub fn seconds(seconds: u64) -> Duration { Duration::from_secs(seconds) } /// Creates a [`Duration`] representing the given amount of minutes. pub fn minutes(minutes: u64) -> Duration { seconds(minutes * 60) } /// Creates a [`Duration`] representing the given amount of hours. pub fn hours(hours: u64) -> Duration { minutes(hours * 60) } /// Creates a [`Duration`] representing the given amount of days. pub fn days(days: u64) -> Duration { hours(days * 24) } ================================================ FILE: core/src/touch.rs ================================================ //! Build touch events. use crate::Point; /// A touch interaction. #[derive(Debug, Clone, Copy, PartialEq)] #[allow(missing_docs)] pub enum Event { /// A touch interaction was started. FingerPressed { id: Finger, position: Point }, /// An on-going touch interaction was moved. FingerMoved { id: Finger, position: Point }, /// A touch interaction was ended. FingerLifted { id: Finger, position: Point }, /// A touch interaction was canceled. FingerLost { id: Finger, position: Point }, } /// A unique identifier representing a finger on a touch interaction. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct Finger(pub u64); ================================================ FILE: core/src/transformation.rs ================================================ use crate::{Point, Rectangle, Size, Vector}; use glam::{Mat4, Vec3, Vec4}; use std::ops::Mul; /// A 2D transformation matrix. #[derive(Debug, Clone, Copy, PartialEq)] pub struct Transformation(Mat4); impl Transformation { /// A [`Transformation`] that preserves whatever is transformed. pub const IDENTITY: Self = Self(Mat4::IDENTITY); /// Creates an orthographic projection. #[rustfmt::skip] pub fn orthographic(width: u32, height: u32) -> Self{ Self(Mat4::orthographic_rh_gl( 0.0, width as f32, height as f32, 0.0, -1.0, 1.0 )) } /// Creates a translate transformation. pub fn translate(x: f32, y: f32) -> Self { Self(Mat4::from_translation(Vec3::new(x, y, 0.0))) } /// Creates a uniform scaling transformation. pub fn scale(scaling: f32) -> Self { Self(Mat4::from_scale(Vec3::new(scaling, scaling, 1.0))) } /// Returns the inverse of the [`Transformation`]. pub fn inverse(self) -> Self { Self(self.0.inverse()) } /// Returns the scale factor of the [`Transformation`]. pub fn scale_factor(&self) -> f32 { self.0.x_axis.x } /// Returns the translation of the [`Transformation`]. pub fn translation(&self) -> Vector { Vector::new(self.0.w_axis.x, self.0.w_axis.y) } } impl Default for Transformation { fn default() -> Self { Transformation::IDENTITY } } impl Mul for Transformation { type Output = Self; fn mul(self, rhs: Self) -> Self { Self(self.0 * rhs.0) } } impl Mul for Point { type Output = Self; fn mul(self, transformation: Transformation) -> Self { let point = transformation .0 .mul_vec4(Vec4::new(self.x, self.y, 1.0, 1.0)); Point::new(point.x, point.y) } } impl Mul for Vector { type Output = Self; fn mul(self, transformation: Transformation) -> Self { let new_vector = transformation .0 .mul_vec4(Vec4::new(self.x, self.y, 1.0, 0.0)); Vector::new(new_vector.x, new_vector.y) } } impl Mul for Size { type Output = Self; fn mul(self, transformation: Transformation) -> Self { let new_size = transformation .0 .mul_vec4(Vec4::new(self.width, self.height, 1.0, 0.0)); Size::new(new_size.x, new_size.y) } } impl Mul for Rectangle { type Output = Self; fn mul(self, transformation: Transformation) -> Self { let position = self.position(); let size = self.size(); Self::new(position * transformation, size * transformation) } } impl AsRef<[f32; 16]> for Transformation { fn as_ref(&self) -> &[f32; 16] { self.0.as_ref() } } impl From for [f32; 16] { fn from(t: Transformation) -> [f32; 16] { *t.as_ref() } } impl From for Mat4 { fn from(transformation: Transformation) -> Self { transformation.0 } } ================================================ FILE: core/src/vector.rs ================================================ /// A 2D vector. #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub struct Vector { /// The X component of the [`Vector`] pub x: T, /// The Y component of the [`Vector`] pub y: T, } impl Vector { /// Creates a new [`Vector`] with the given components. pub const fn new(x: T, y: T) -> Self { Self { x, y } } } impl Vector { /// The zero [`Vector`]. pub const ZERO: Self = Self::new(0.0, 0.0); /// Rounds the [`Vector`]. pub fn round(self) -> Self { Self { x: self.x.round(), y: self.y.round(), } } } impl std::ops::Neg for Vector where T: std::ops::Neg, { type Output = Self; fn neg(self) -> Self::Output { Self::new(-self.x, -self.y) } } impl std::ops::Add for Vector where T: std::ops::Add, { type Output = Self; fn add(self, b: Self) -> Self { Self::new(self.x + b.x, self.y + b.y) } } impl std::ops::AddAssign for Vector where T: std::ops::AddAssign, { fn add_assign(&mut self, b: Self) { self.x += b.x; self.y += b.y; } } impl std::ops::Sub for Vector where T: std::ops::Sub, { type Output = Self; fn sub(self, b: Self) -> Self { Self::new(self.x - b.x, self.y - b.y) } } impl std::ops::SubAssign for Vector where T: std::ops::SubAssign, { fn sub_assign(&mut self, b: Self) { self.x -= b.x; self.y -= b.y; } } impl std::ops::Mul for Vector where T: std::ops::Mul + Copy, { type Output = Self; fn mul(self, scale: T) -> Self { Self::new(self.x * scale, self.y * scale) } } impl std::ops::MulAssign for Vector where T: std::ops::MulAssign + Copy, { fn mul_assign(&mut self, scale: T) { self.x *= scale; self.y *= scale; } } impl std::ops::Div for Vector where T: std::ops::Div + Copy, { type Output = Self; fn div(self, scale: T) -> Self { Self::new(self.x / scale, self.y / scale) } } impl std::ops::DivAssign for Vector where T: std::ops::DivAssign + Copy, { fn div_assign(&mut self, scale: T) { self.x /= scale; self.y /= scale; } } impl From<[T; 2]> for Vector { fn from([x, y]: [T; 2]) -> Self { Self::new(x, y) } } impl From> for [T; 2] where T: Copy, { fn from(other: Vector) -> Self { [other.x, other.y] } } ================================================ FILE: core/src/widget/id.rs ================================================ use std::borrow; use std::sync::atomic::{self, AtomicUsize}; static NEXT_ID: AtomicUsize = AtomicUsize::new(0); /// The identifier of a generic widget. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Id(Internal); impl Id { /// Creates a new [`Id`] from a static `str`. pub const fn new(id: &'static str) -> Self { Self(Internal::Custom(borrow::Cow::Borrowed(id))) } /// Creates a unique [`Id`]. /// /// This function produces a different [`Id`] every time it is called. pub fn unique() -> Self { let id = NEXT_ID.fetch_add(1, atomic::Ordering::Relaxed); Self(Internal::Unique(id)) } } impl From<&'static str> for Id { fn from(value: &'static str) -> Self { Self::new(value) } } impl From for Id { fn from(value: String) -> Self { Self(Internal::Custom(borrow::Cow::Owned(value))) } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] enum Internal { Unique(usize), Custom(borrow::Cow<'static, str>), } #[cfg(test)] mod tests { use super::Id; #[test] fn unique_generates_different_ids() { let a = Id::unique(); let b = Id::unique(); assert_ne!(a, b); } } ================================================ FILE: core/src/widget/operation/focusable.rs ================================================ //! Operate on widgets that can be focused. use crate::Rectangle; use crate::widget::Id; use crate::widget::operation::{self, Operation, Outcome}; /// The internal state of a widget that can be focused. pub trait Focusable { /// Returns whether the widget is focused or not. fn is_focused(&self) -> bool; /// Focuses the widget. fn focus(&mut self); /// Unfocuses the widget. fn unfocus(&mut self); } /// A summary of the focusable widgets present on a widget tree. #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub struct Count { /// The index of the current focused widget, if any. pub focused: Option, /// The total amount of focusable widgets. pub total: usize, } /// Produces an [`Operation`] that focuses the widget with the given [`Id`]. pub fn focus(target: Id) -> impl Operation { struct Focus { target: Id, } impl Operation for Focus { fn focusable(&mut self, id: Option<&Id>, _bounds: Rectangle, state: &mut dyn Focusable) { match id { Some(id) if id == &self.target => { state.focus(); } _ => { state.unfocus(); } } } fn traverse(&mut self, operate: &mut dyn FnMut(&mut dyn Operation)) { operate(self); } } Focus { target } } /// Produces an [`Operation`] that unfocuses the focused widget. pub fn unfocus() -> impl Operation { struct Unfocus; impl Operation for Unfocus { fn focusable(&mut self, _id: Option<&Id>, _bounds: Rectangle, state: &mut dyn Focusable) { state.unfocus(); } fn traverse(&mut self, operate: &mut dyn FnMut(&mut dyn Operation)) { operate(self); } } Unfocus } /// Produces an [`Operation`] that generates a [`Count`] and chains it with the /// provided function to build a new [`Operation`]. pub fn count() -> impl Operation { struct CountFocusable { count: Count, } impl Operation for CountFocusable { fn focusable(&mut self, _id: Option<&Id>, _bounds: Rectangle, state: &mut dyn Focusable) { if state.is_focused() { self.count.focused = Some(self.count.total); } self.count.total += 1; } fn traverse(&mut self, operate: &mut dyn FnMut(&mut dyn Operation)) { operate(self); } fn finish(&self) -> Outcome { Outcome::Some(self.count) } } CountFocusable { count: Count::default(), } } /// Produces an [`Operation`] that searches for the current focused widget, and /// - if found, focuses the previous focusable widget. /// - if not found, focuses the last focusable widget. pub fn focus_previous() -> impl Operation where T: Send + 'static, { struct FocusPrevious { count: Count, current: usize, } impl Operation for FocusPrevious { fn focusable(&mut self, _id: Option<&Id>, _bounds: Rectangle, state: &mut dyn Focusable) { if self.count.total == 0 { return; } match self.count.focused { None if self.current == self.count.total - 1 => state.focus(), Some(0) if self.current == 0 => state.unfocus(), Some(0) => {} Some(focused) if focused == self.current => state.unfocus(), Some(focused) if focused - 1 == self.current => state.focus(), _ => {} } self.current += 1; } fn traverse(&mut self, operate: &mut dyn FnMut(&mut dyn Operation)) { operate(self); } } operation::then(count(), |count| FocusPrevious { count, current: 0 }) } /// Produces an [`Operation`] that searches for the current focused widget, and /// - if found, focuses the next focusable widget. /// - if not found, focuses the first focusable widget. pub fn focus_next() -> impl Operation where T: Send + 'static, { struct FocusNext { count: Count, current: usize, } impl Operation for FocusNext { fn focusable(&mut self, _id: Option<&Id>, _bounds: Rectangle, state: &mut dyn Focusable) { match self.count.focused { None if self.current == 0 => state.focus(), Some(focused) if focused == self.current => state.unfocus(), Some(focused) if focused + 1 == self.current => state.focus(), _ => {} } self.current += 1; } fn traverse(&mut self, operate: &mut dyn FnMut(&mut dyn Operation)) { operate(self); } } operation::then(count(), |count| FocusNext { count, current: 0 }) } /// Produces an [`Operation`] that searches for the current focused widget /// and stores its ID. This ignores widgets that do not have an ID. pub fn find_focused() -> impl Operation { struct FindFocused { focused: Option, } impl Operation for FindFocused { fn focusable(&mut self, id: Option<&Id>, _bounds: Rectangle, state: &mut dyn Focusable) { if state.is_focused() && id.is_some() { self.focused = id.cloned(); } } fn traverse(&mut self, operate: &mut dyn FnMut(&mut dyn Operation)) { operate(self); } fn finish(&self) -> Outcome { if let Some(id) = &self.focused { Outcome::Some(id.clone()) } else { Outcome::None } } } FindFocused { focused: None } } /// Produces an [`Operation`] that searches for the focusable widget /// and stores whether it is focused or not. This ignores widgets that /// do not have an ID. pub fn is_focused(target: Id) -> impl Operation { struct IsFocused { target: Id, is_focused: Option, } impl Operation for IsFocused { fn focusable(&mut self, id: Option<&Id>, _bounds: Rectangle, state: &mut dyn Focusable) { if id.is_some_and(|id| *id == self.target) { self.is_focused = Some(state.is_focused()); } } fn traverse(&mut self, operate: &mut dyn FnMut(&mut dyn Operation)) { if self.is_focused.is_some() { return; } operate(self); } fn finish(&self) -> Outcome { self.is_focused.map_or(Outcome::None, Outcome::Some) } } IsFocused { target, is_focused: None, } } ================================================ FILE: core/src/widget/operation/scrollable.rs ================================================ //! Operate on widgets that can be scrolled. use crate::widget::{Id, Operation}; use crate::{Rectangle, Vector}; /// The internal state of a widget that can be scrolled. pub trait Scrollable { /// Snaps the scroll of the widget to the given `percentage` along the horizontal & vertical axis. fn snap_to(&mut self, offset: RelativeOffset>); /// Scroll the widget to the given [`AbsoluteOffset`] along the horizontal & vertical axis. fn scroll_to(&mut self, offset: AbsoluteOffset>); /// Scroll the widget by the given [`AbsoluteOffset`] along the horizontal & vertical axis. fn scroll_by(&mut self, offset: AbsoluteOffset, bounds: Rectangle, content_bounds: Rectangle); } /// Produces an [`Operation`] that snaps the widget with the given [`Id`] to /// the provided `percentage`. pub fn snap_to(target: Id, offset: RelativeOffset>) -> impl Operation { struct SnapTo { target: Id, offset: RelativeOffset>, } impl Operation for SnapTo { fn traverse(&mut self, operate: &mut dyn FnMut(&mut dyn Operation)) { operate(self); } fn scrollable( &mut self, id: Option<&Id>, _bounds: Rectangle, _content_bounds: Rectangle, _translation: Vector, state: &mut dyn Scrollable, ) { if Some(&self.target) == id { state.snap_to(self.offset); } } } SnapTo { target, offset } } /// Produces an [`Operation`] that scrolls the widget with the given [`Id`] to /// the provided [`AbsoluteOffset`]. pub fn scroll_to(target: Id, offset: AbsoluteOffset>) -> impl Operation { struct ScrollTo { target: Id, offset: AbsoluteOffset>, } impl Operation for ScrollTo { fn traverse(&mut self, operate: &mut dyn FnMut(&mut dyn Operation)) { operate(self); } fn scrollable( &mut self, id: Option<&Id>, _bounds: Rectangle, _content_bounds: Rectangle, _translation: Vector, state: &mut dyn Scrollable, ) { if Some(&self.target) == id { state.scroll_to(self.offset); } } } ScrollTo { target, offset } } /// Produces an [`Operation`] that scrolls the widget with the given [`Id`] by /// the provided [`AbsoluteOffset`]. pub fn scroll_by(target: Id, offset: AbsoluteOffset) -> impl Operation { struct ScrollBy { target: Id, offset: AbsoluteOffset, } impl Operation for ScrollBy { fn traverse(&mut self, operate: &mut dyn FnMut(&mut dyn Operation)) { operate(self); } fn scrollable( &mut self, id: Option<&Id>, bounds: Rectangle, content_bounds: Rectangle, _translation: Vector, state: &mut dyn Scrollable, ) { if Some(&self.target) == id { state.scroll_by(self.offset, bounds, content_bounds); } } } ScrollBy { target, offset } } /// The amount of absolute offset in each direction of a [`Scrollable`]. #[derive(Debug, Clone, Copy, PartialEq, Default)] pub struct AbsoluteOffset { /// The amount of horizontal offset pub x: T, /// The amount of vertical offset pub y: T, } impl From for AbsoluteOffset> { fn from(offset: AbsoluteOffset) -> Self { Self { x: Some(offset.x), y: Some(offset.y), } } } /// The amount of relative offset in each direction of a [`Scrollable`]. /// /// A value of `0.0` means start, while `1.0` means end. #[derive(Debug, Clone, Copy, PartialEq, Default)] pub struct RelativeOffset { /// The amount of horizontal offset pub x: T, /// The amount of vertical offset pub y: T, } impl RelativeOffset { /// A relative offset that points to the top-left of a [`Scrollable`]. pub const START: Self = Self { x: 0.0, y: 0.0 }; /// A relative offset that points to the bottom-right of a [`Scrollable`]. pub const END: Self = Self { x: 1.0, y: 1.0 }; } impl From for RelativeOffset> { fn from(offset: RelativeOffset) -> Self { Self { x: Some(offset.x), y: Some(offset.y), } } } ================================================ FILE: core/src/widget/operation/text_input.rs ================================================ //! Operate on widgets that have text input. use crate::Rectangle; use crate::widget::Id; use crate::widget::operation::Operation; /// The internal state of a widget that has text input. pub trait TextInput { /// Returns the current _visible_ text of the text input /// /// Normally, this is either its value or its placeholder. fn text(&self) -> &str; /// Moves the cursor of the text input to the front of the input text. fn move_cursor_to_front(&mut self); /// Moves the cursor of the text input to the end of the input text. fn move_cursor_to_end(&mut self); /// Moves the cursor of the text input to an arbitrary location. fn move_cursor_to(&mut self, position: usize); /// Selects all the content of the text input. fn select_all(&mut self); /// Selects the given content range of the text input. fn select_range(&mut self, start: usize, end: usize); } /// Produces an [`Operation`] that moves the cursor of the widget with the given [`Id`] to the /// front. pub fn move_cursor_to_front(target: Id) -> impl Operation { struct MoveCursor { target: Id, } impl Operation for MoveCursor { fn text_input(&mut self, id: Option<&Id>, _bounds: Rectangle, state: &mut dyn TextInput) { match id { Some(id) if id == &self.target => { state.move_cursor_to_front(); } _ => {} } } fn traverse(&mut self, operate: &mut dyn FnMut(&mut dyn Operation)) { operate(self); } } MoveCursor { target } } /// Produces an [`Operation`] that moves the cursor of the widget with the given [`Id`] to the /// end. pub fn move_cursor_to_end(target: Id) -> impl Operation { struct MoveCursor { target: Id, } impl Operation for MoveCursor { fn text_input(&mut self, id: Option<&Id>, _bounds: Rectangle, state: &mut dyn TextInput) { match id { Some(id) if id == &self.target => { state.move_cursor_to_end(); } _ => {} } } fn traverse(&mut self, operate: &mut dyn FnMut(&mut dyn Operation)) { operate(self); } } MoveCursor { target } } /// Produces an [`Operation`] that moves the cursor of the widget with the given [`Id`] to the /// provided position. pub fn move_cursor_to(target: Id, position: usize) -> impl Operation { struct MoveCursor { target: Id, position: usize, } impl Operation for MoveCursor { fn text_input(&mut self, id: Option<&Id>, _bounds: Rectangle, state: &mut dyn TextInput) { match id { Some(id) if id == &self.target => { state.move_cursor_to(self.position); } _ => {} } } fn traverse(&mut self, operate: &mut dyn FnMut(&mut dyn Operation)) { operate(self); } } MoveCursor { target, position } } /// Produces an [`Operation`] that selects all the content of the widget with the given [`Id`]. pub fn select_all(target: Id) -> impl Operation { struct MoveCursor { target: Id, } impl Operation for MoveCursor { fn text_input(&mut self, id: Option<&Id>, _bounds: Rectangle, state: &mut dyn TextInput) { match id { Some(id) if id == &self.target => { state.select_all(); } _ => {} } } fn traverse(&mut self, operate: &mut dyn FnMut(&mut dyn Operation)) { operate(self); } } MoveCursor { target } } /// Produces an [`Operation`] that selects the given content range of the widget with the given [`Id`]. pub fn select_range(target: Id, start: usize, end: usize) -> impl Operation { struct SelectRange { target: Id, start: usize, end: usize, } impl Operation for SelectRange { fn text_input(&mut self, id: Option<&Id>, _bounds: Rectangle, state: &mut dyn TextInput) { match id { Some(id) if id == &self.target => { state.select_range(self.start, self.end); } _ => {} } } fn traverse(&mut self, operate: &mut dyn FnMut(&mut dyn Operation)) { operate(self); } } SelectRange { target, start, end } } ================================================ FILE: core/src/widget/operation.rs ================================================ //! Query or update internal widget state. pub mod focusable; pub mod scrollable; pub mod text_input; pub use focusable::Focusable; pub use scrollable::Scrollable; pub use text_input::TextInput; use crate::widget::Id; use crate::{Rectangle, Vector}; use std::any::Any; use std::fmt; use std::marker::PhantomData; use std::sync::Arc; /// A piece of logic that can traverse the widget tree of an application in /// order to query or update some widget state. pub trait Operation: Send { /// Requests further traversal of the widget tree to keep operating. /// /// The provided `operate` closure may be called by an [`Operation`] /// to return control to the widget tree and keep traversing it. If /// the closure is not called, the children of the widget asking for /// traversal will be skipped. fn traverse(&mut self, operate: &mut dyn FnMut(&mut dyn Operation)); /// Operates on a widget that contains other widgets. fn container(&mut self, _id: Option<&Id>, _bounds: Rectangle) {} /// Operates on a widget that can be scrolled. fn scrollable( &mut self, _id: Option<&Id>, _bounds: Rectangle, _content_bounds: Rectangle, _translation: Vector, _state: &mut dyn Scrollable, ) { } /// Operates on a widget that can be focused. fn focusable(&mut self, _id: Option<&Id>, _bounds: Rectangle, _state: &mut dyn Focusable) {} /// Operates on a widget that has text input. fn text_input(&mut self, _id: Option<&Id>, _bounds: Rectangle, _state: &mut dyn TextInput) {} /// Operates on a widget that contains some text. fn text(&mut self, _id: Option<&Id>, _bounds: Rectangle, _text: &str) {} /// Operates on a custom widget with some state. fn custom(&mut self, _id: Option<&Id>, _bounds: Rectangle, _state: &mut dyn Any) {} /// Finishes the [`Operation`] and returns its [`Outcome`]. fn finish(&self) -> Outcome { Outcome::None } } impl Operation for Box where T: Operation + ?Sized, { fn traverse(&mut self, operate: &mut dyn FnMut(&mut dyn Operation)) { self.as_mut().traverse(operate); } fn container(&mut self, id: Option<&Id>, bounds: Rectangle) { self.as_mut().container(id, bounds); } fn focusable(&mut self, id: Option<&Id>, bounds: Rectangle, state: &mut dyn Focusable) { self.as_mut().focusable(id, bounds, state); } fn scrollable( &mut self, id: Option<&Id>, bounds: Rectangle, content_bounds: Rectangle, translation: Vector, state: &mut dyn Scrollable, ) { self.as_mut() .scrollable(id, bounds, content_bounds, translation, state); } fn text_input(&mut self, id: Option<&Id>, bounds: Rectangle, state: &mut dyn TextInput) { self.as_mut().text_input(id, bounds, state); } fn text(&mut self, id: Option<&Id>, bounds: Rectangle, text: &str) { self.as_mut().text(id, bounds, text); } fn custom(&mut self, id: Option<&Id>, bounds: Rectangle, state: &mut dyn Any) { self.as_mut().custom(id, bounds, state); } fn finish(&self) -> Outcome { self.as_ref().finish() } } /// The result of an [`Operation`]. pub enum Outcome { /// The [`Operation`] produced no result. None, /// The [`Operation`] produced some result. Some(T), /// The [`Operation`] needs to be followed by another [`Operation`]. Chain(Box>), } impl fmt::Debug for Outcome where T: fmt::Debug, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::None => write!(f, "Outcome::None"), Self::Some(output) => write!(f, "Outcome::Some({output:?})"), Self::Chain(_) => write!(f, "Outcome::Chain(...)"), } } } /// Wraps the [`Operation`] in a black box, erasing its returning type. pub fn black_box<'a, T, O>(operation: &'a mut dyn Operation) -> impl Operation + 'a where T: 'a, { struct BlackBox<'a, T> { operation: &'a mut dyn Operation, } impl Operation for BlackBox<'_, T> { fn traverse(&mut self, operate: &mut dyn FnMut(&mut dyn Operation)) where Self: Sized, { self.operation.traverse(&mut |operation| { operate(&mut BlackBox { operation }); }); } fn container(&mut self, id: Option<&Id>, bounds: Rectangle) { self.operation.container(id, bounds); } fn focusable(&mut self, id: Option<&Id>, bounds: Rectangle, state: &mut dyn Focusable) { self.operation.focusable(id, bounds, state); } fn scrollable( &mut self, id: Option<&Id>, bounds: Rectangle, content_bounds: Rectangle, translation: Vector, state: &mut dyn Scrollable, ) { self.operation .scrollable(id, bounds, content_bounds, translation, state); } fn text_input(&mut self, id: Option<&Id>, bounds: Rectangle, state: &mut dyn TextInput) { self.operation.text_input(id, bounds, state); } fn text(&mut self, id: Option<&Id>, bounds: Rectangle, text: &str) { self.operation.text(id, bounds, text); } fn custom(&mut self, id: Option<&Id>, bounds: Rectangle, state: &mut dyn Any) { self.operation.custom(id, bounds, state); } fn finish(&self) -> Outcome { Outcome::None } } BlackBox { operation } } /// Maps the output of an [`Operation`] using the given function. pub fn map( operation: impl Operation, f: impl Fn(A) -> B + Send + Sync + 'static, ) -> impl Operation where A: 'static, B: 'static, { struct Map { operation: O, f: Arc B + Send + Sync>, } impl Operation for Map where O: Operation, A: 'static, B: 'static, { fn traverse(&mut self, operate: &mut dyn FnMut(&mut dyn Operation)) { struct MapRef<'a, A> { operation: &'a mut dyn Operation, } impl Operation for MapRef<'_, A> { fn traverse(&mut self, operate: &mut dyn FnMut(&mut dyn Operation)) { self.operation.traverse(&mut |operation| { operate(&mut MapRef { operation }); }); } fn container(&mut self, id: Option<&Id>, bounds: Rectangle) { let Self { operation, .. } = self; operation.container(id, bounds); } fn scrollable( &mut self, id: Option<&Id>, bounds: Rectangle, content_bounds: Rectangle, translation: Vector, state: &mut dyn Scrollable, ) { self.operation .scrollable(id, bounds, content_bounds, translation, state); } fn focusable( &mut self, id: Option<&Id>, bounds: Rectangle, state: &mut dyn Focusable, ) { self.operation.focusable(id, bounds, state); } fn text_input( &mut self, id: Option<&Id>, bounds: Rectangle, state: &mut dyn TextInput, ) { self.operation.text_input(id, bounds, state); } fn text(&mut self, id: Option<&Id>, bounds: Rectangle, text: &str) { self.operation.text(id, bounds, text); } fn custom(&mut self, id: Option<&Id>, bounds: Rectangle, state: &mut dyn Any) { self.operation.custom(id, bounds, state); } } self.operation.traverse(&mut |operation| { operate(&mut MapRef { operation }); }); } fn container(&mut self, id: Option<&Id>, bounds: Rectangle) { self.operation.container(id, bounds); } fn focusable(&mut self, id: Option<&Id>, bounds: Rectangle, state: &mut dyn Focusable) { self.operation.focusable(id, bounds, state); } fn scrollable( &mut self, id: Option<&Id>, bounds: Rectangle, content_bounds: Rectangle, translation: Vector, state: &mut dyn Scrollable, ) { self.operation .scrollable(id, bounds, content_bounds, translation, state); } fn text_input(&mut self, id: Option<&Id>, bounds: Rectangle, state: &mut dyn TextInput) { self.operation.text_input(id, bounds, state); } fn text(&mut self, id: Option<&Id>, bounds: Rectangle, text: &str) { self.operation.text(id, bounds, text); } fn custom(&mut self, id: Option<&Id>, bounds: Rectangle, state: &mut dyn Any) { self.operation.custom(id, bounds, state); } fn finish(&self) -> Outcome { match self.operation.finish() { Outcome::None => Outcome::None, Outcome::Some(output) => Outcome::Some((self.f)(output)), Outcome::Chain(next) => Outcome::Chain(Box::new(Map { operation: next, f: self.f.clone(), })), } } } Map { operation, f: Arc::new(f), } } /// Chains the output of an [`Operation`] with the provided function to /// build a new [`Operation`]. pub fn then(operation: impl Operation + 'static, f: fn(A) -> O) -> impl Operation where A: 'static, B: Send + 'static, O: Operation + 'static, { struct Chain where T: Operation, O: Operation, { operation: T, next: fn(A) -> O, _result: PhantomData, } impl Operation for Chain where T: Operation + 'static, O: Operation + 'static, A: 'static, B: Send + 'static, { fn traverse(&mut self, operate: &mut dyn FnMut(&mut dyn Operation)) { self.operation.traverse(&mut |operation| { operate(&mut black_box(operation)); }); } fn container(&mut self, id: Option<&Id>, bounds: Rectangle) { self.operation.container(id, bounds); } fn focusable(&mut self, id: Option<&Id>, bounds: Rectangle, state: &mut dyn Focusable) { self.operation.focusable(id, bounds, state); } fn scrollable( &mut self, id: Option<&Id>, bounds: Rectangle, content_bounds: Rectangle, translation: crate::Vector, state: &mut dyn Scrollable, ) { self.operation .scrollable(id, bounds, content_bounds, translation, state); } fn text_input(&mut self, id: Option<&Id>, bounds: Rectangle, state: &mut dyn TextInput) { self.operation.text_input(id, bounds, state); } fn text(&mut self, id: Option<&Id>, bounds: Rectangle, text: &str) { self.operation.text(id, bounds, text); } fn custom(&mut self, id: Option<&Id>, bounds: Rectangle, state: &mut dyn Any) { self.operation.custom(id, bounds, state); } fn finish(&self) -> Outcome { match self.operation.finish() { Outcome::None => Outcome::None, Outcome::Some(value) => Outcome::Chain(Box::new((self.next)(value))), Outcome::Chain(operation) => Outcome::Chain(Box::new(then(operation, self.next))), } } } Chain { operation, next: f, _result: PhantomData, } } /// Produces an [`Operation`] that applies the given [`Operation`] to the /// children of a container with the given [`Id`]. pub fn scope(target: Id, operation: impl Operation + 'static) -> impl Operation { struct ScopedOperation { target: Id, current: Option, operation: Box>, } impl Operation for ScopedOperation { fn traverse(&mut self, operate: &mut dyn FnMut(&mut dyn Operation)) { if self.current.as_ref() == Some(&self.target) { self.operation.as_mut().traverse(operate); } else { operate(self); } self.current = None; } fn container(&mut self, id: Option<&Id>, _bounds: Rectangle) { self.current = id.cloned(); } fn finish(&self) -> Outcome { match self.operation.finish() { Outcome::Chain(next) => Outcome::Chain(Box::new(ScopedOperation { target: self.target.clone(), current: None, operation: next, })), outcome => outcome, } } } ScopedOperation { target, current: None, operation: Box::new(operation), } } ================================================ FILE: core/src/widget/text.rs ================================================ //! Text widgets display information through writing. //! //! # Example //! ```no_run //! # mod iced { pub mod widget { pub fn text(t: T) -> iced_core::widget::Text<'static, iced_core::Theme, ()> { unimplemented!() } } //! # pub use iced_core::color; } //! # pub type State = (); //! # pub type Element<'a, Message> = iced_core::Element<'a, Message, iced_core::Theme, ()>; //! use iced::widget::text; //! use iced::color; //! //! enum Message { //! // ... //! } //! //! fn view(state: &State) -> Element<'_, Message> { //! text("Hello, this is iced!") //! .size(20) //! .color(color!(0x0000ff)) //! .into() //! } //! ``` use crate::alignment; use crate::layout; use crate::mouse; use crate::renderer; use crate::text; use crate::text::paragraph::{self, Paragraph}; use crate::widget::tree::{self, Tree}; use crate::{Color, Element, Layout, Length, Pixels, Rectangle, Size, Theme, Widget}; pub use text::{Alignment, Ellipsis, LineHeight, Shaping, Wrapping}; /// A bunch of text. /// /// # Example /// ```no_run /// # mod iced { pub mod widget { pub fn text(t: T) -> iced_core::widget::Text<'static, iced_core::Theme, ()> { unimplemented!() } } /// # pub use iced_core::color; } /// # pub type State = (); /// # pub type Element<'a, Message> = iced_core::Element<'a, Message, iced_core::Theme, ()>; /// use iced::widget::text; /// use iced::color; /// /// enum Message { /// // ... /// } /// /// fn view(state: &State) -> Element<'_, Message> { /// text("Hello, this is iced!") /// .size(20) /// .color(color!(0x0000ff)) /// .into() /// } /// ``` #[must_use] pub struct Text<'a, Theme, Renderer> where Theme: Catalog, Renderer: text::Renderer, { fragment: text::Fragment<'a>, format: Format, class: Theme::Class<'a>, } impl<'a, Theme, Renderer> Text<'a, Theme, Renderer> where Theme: Catalog, Renderer: text::Renderer, { /// Create a new fragment of [`Text`] with the given contents. pub fn new(fragment: impl text::IntoFragment<'a>) -> Self { Text { fragment: fragment.into_fragment(), format: Format::default(), class: Theme::default(), } } /// Sets the size of the [`Text`]. pub fn size(mut self, size: impl Into) -> Self { self.format.size = Some(size.into()); self } /// Sets the [`LineHeight`] of the [`Text`]. pub fn line_height(mut self, line_height: impl Into) -> Self { self.format.line_height = line_height.into(); self } /// Sets the [`Font`] of the [`Text`]. /// /// [`Font`]: crate::text::Renderer::Font pub fn font(mut self, font: impl Into) -> Self { self.format.font = Some(font.into()); self } /// Sets the [`Font`] of the [`Text`], if `Some`. /// /// [`Font`]: crate::text::Renderer::Font pub fn font_maybe(mut self, font: Option>) -> Self { self.format.font = font.map(Into::into); self } /// Sets the width of the [`Text`] boundaries. pub fn width(mut self, width: impl Into) -> Self { self.format.width = width.into(); self } /// Sets the height of the [`Text`] boundaries. pub fn height(mut self, height: impl Into) -> Self { self.format.height = height.into(); self } /// Centers the [`Text`], both horizontally and vertically. pub fn center(self) -> Self { self.align_x(alignment::Horizontal::Center) .align_y(alignment::Vertical::Center) } /// Sets the [`alignment::Horizontal`] of the [`Text`]. pub fn align_x(mut self, alignment: impl Into) -> Self { self.format.align_x = alignment.into(); self } /// Sets the [`alignment::Vertical`] of the [`Text`]. pub fn align_y(mut self, alignment: impl Into) -> Self { self.format.align_y = alignment.into(); self } /// Sets the [`Shaping`] strategy of the [`Text`]. pub fn shaping(mut self, shaping: Shaping) -> Self { self.format.shaping = shaping; self } /// Sets the [`Wrapping`] strategy of the [`Text`]. pub fn wrapping(mut self, wrapping: Wrapping) -> Self { self.format.wrapping = wrapping; self } /// Sets the [`Ellipsis`] strategy of the [`Text`]. pub fn ellipsis(mut self, ellipsis: Ellipsis) -> Self { self.format.ellipsis = ellipsis; self } /// Sets the style of the [`Text`]. pub fn style(mut self, style: impl Fn(&Theme) -> Style + 'a) -> Self where Theme::Class<'a>: From>, { self.class = (Box::new(style) as StyleFn<'a, Theme>).into(); self } /// Sets the [`Color`] of the [`Text`]. pub fn color(self, color: impl Into) -> Self where Theme::Class<'a>: From>, { self.color_maybe(Some(color)) } /// Sets the [`Color`] of the [`Text`], if `Some`. pub fn color_maybe(self, color: Option>) -> Self where Theme::Class<'a>: From>, { let color = color.map(Into::into); self.style(move |_theme| Style { color }) } /// Sets the style class of the [`Text`]. #[cfg(feature = "advanced")] pub fn class(mut self, class: impl Into>) -> Self { self.class = class.into(); self } } /// The internal state of a [`Text`] widget. pub type State

= paragraph::Plain

; impl Widget for Text<'_, Theme, Renderer> where Theme: Catalog, Renderer: text::Renderer, { fn tag(&self) -> tree::Tag { tree::Tag::of::>() } fn state(&self) -> tree::State { tree::State::new(paragraph::Plain::::default()) } fn size(&self) -> Size { Size { width: self.format.width, height: self.format.height, } } fn layout( &mut self, tree: &mut Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { layout( tree.state.downcast_mut::>(), renderer, limits, &self.fragment, self.format, ) } fn draw( &self, tree: &Tree, renderer: &mut Renderer, theme: &Theme, defaults: &renderer::Style, layout: Layout<'_>, _cursor_position: mouse::Cursor, viewport: &Rectangle, ) { let state = tree.state.downcast_ref::>(); let style = theme.style(&self.class); draw( renderer, defaults, layout.bounds(), state.raw(), style, viewport, ); } fn operate( &mut self, _tree: &mut Tree, layout: Layout<'_>, _renderer: &Renderer, operation: &mut dyn super::Operation, ) { operation.text(None, layout.bounds(), &self.fragment); } } /// The format of some [`Text`]. /// /// Check out the methods of the [`Text`] widget /// to learn more about each field. #[derive(Debug, Clone, Copy)] #[allow(missing_docs)] pub struct Format { pub width: Length, pub height: Length, pub size: Option, pub font: Option, pub line_height: LineHeight, pub align_x: text::Alignment, pub align_y: alignment::Vertical, pub shaping: Shaping, pub wrapping: Wrapping, pub ellipsis: Ellipsis, } impl Default for Format { fn default() -> Self { Self { size: None, line_height: LineHeight::default(), font: None, width: Length::Shrink, height: Length::Shrink, align_x: text::Alignment::Default, align_y: alignment::Vertical::Top, shaping: Shaping::default(), wrapping: Wrapping::default(), ellipsis: Ellipsis::default(), } } } /// Produces the [`layout::Node`] of a [`Text`] widget. pub fn layout( paragraph: &mut paragraph::Plain, renderer: &Renderer, limits: &layout::Limits, content: &str, format: Format, ) -> layout::Node where Renderer: text::Renderer, { layout::sized(limits, format.width, format.height, |limits| { let bounds = limits.max(); let size = format.size.unwrap_or_else(|| renderer.default_size()); let font = format.font.unwrap_or_else(|| renderer.default_font()); let _ = paragraph.update(text::Text { content, bounds, size, line_height: format.line_height, font, align_x: format.align_x, align_y: format.align_y, shaping: format.shaping, wrapping: format.wrapping, ellipsis: format.ellipsis, hint_factor: renderer.scale_factor(), }); paragraph.min_bounds() }) } /// Draws text using the same logic as the [`Text`] widget. pub fn draw( renderer: &mut Renderer, style: &renderer::Style, bounds: Rectangle, paragraph: &Renderer::Paragraph, appearance: Style, viewport: &Rectangle, ) where Renderer: text::Renderer, { let anchor = bounds.anchor( paragraph.min_bounds(), paragraph.align_x(), paragraph.align_y(), ); renderer.fill_paragraph( paragraph, anchor, appearance.color.unwrap_or(style.text_color), *viewport, ); } impl<'a, Message, Theme, Renderer> From> for Element<'a, Message, Theme, Renderer> where Theme: Catalog + 'a, Renderer: text::Renderer + 'a, { fn from(text: Text<'a, Theme, Renderer>) -> Element<'a, Message, Theme, Renderer> { Element::new(text) } } impl<'a, Theme, Renderer> From<&'a str> for Text<'a, Theme, Renderer> where Theme: Catalog + 'a, Renderer: text::Renderer, { fn from(content: &'a str) -> Self { Self::new(content) } } impl<'a, Message, Theme, Renderer> From<&'a str> for Element<'a, Message, Theme, Renderer> where Theme: Catalog + 'a, Renderer: text::Renderer + 'a, { fn from(content: &'a str) -> Self { Text::from(content).into() } } /// The appearance of some text. #[derive(Debug, Clone, Copy, PartialEq, Default)] pub struct Style { /// The [`Color`] of the text. /// /// The default, `None`, means using the inherited color. pub color: Option, } /// The theme catalog of a [`Text`]. pub trait Catalog: Sized { /// The item class of this [`Catalog`]. type Class<'a>; /// The default class produced by this [`Catalog`]. fn default<'a>() -> Self::Class<'a>; /// The [`Style`] of a class with the given status. fn style(&self, item: &Self::Class<'_>) -> Style; } /// A styling function for a [`Text`]. /// /// This is just a boxed closure: `Fn(&Theme, Status) -> Style`. pub type StyleFn<'a, Theme> = Box Style + 'a>; impl Catalog for Theme { type Class<'a> = StyleFn<'a, Self>; fn default<'a>() -> Self::Class<'a> { Box::new(|_theme| Style::default()) } fn style(&self, class: &Self::Class<'_>) -> Style { class(self) } } /// The default text styling; color is inherited. pub fn default(_theme: &Theme) -> Style { Style { color: None } } /// Text with the default base color. pub fn base(theme: &Theme) -> Style { Style { color: Some(theme.seed().text), } } /// Text conveying some important information, like an action. pub fn primary(theme: &Theme) -> Style { Style { color: Some(theme.seed().primary), } } /// Text conveying some secondary information, like a footnote. pub fn secondary(theme: &Theme) -> Style { Style { color: Some(theme.palette().secondary.base.color), } } /// Text conveying some positive information, like a successful event. pub fn success(theme: &Theme) -> Style { Style { color: Some(theme.seed().success), } } /// Text conveying some mildly negative information, like a warning. pub fn warning(theme: &Theme) -> Style { Style { color: Some(theme.seed().warning), } } /// Text conveying some negative information, like an error. pub fn danger(theme: &Theme) -> Style { Style { color: Some(theme.seed().danger), } } ================================================ FILE: core/src/widget/tree.rs ================================================ //! Store internal widget state in a state tree to ensure continuity. use crate::Widget; use std::any::{self, Any}; use std::borrow::Borrow; use std::fmt; /// A persistent state widget tree. /// /// A [`Tree`] is normally associated with a specific widget in the widget tree. #[derive(Debug)] pub struct Tree { /// The tag of the [`Tree`]. pub tag: Tag, /// The [`State`] of the [`Tree`]. pub state: State, /// The children of the root widget of the [`Tree`]. pub children: Vec, } impl Tree { /// Creates an empty, stateless [`Tree`] with no children. pub fn empty() -> Self { Self { tag: Tag::stateless(), state: State::None, children: Vec::new(), } } /// Creates a new [`Tree`] for the provided [`Widget`]. pub fn new<'a, Message, Theme, Renderer>( widget: impl Borrow + 'a>, ) -> Self where Renderer: crate::Renderer, { let widget = widget.borrow(); Self { tag: widget.tag(), state: widget.state(), children: widget.children(), } } /// Reconciles the current tree with the provided [`Widget`]. /// /// If the tag of the [`Widget`] matches the tag of the [`Tree`], then the /// [`Widget`] proceeds with the reconciliation (i.e. [`Widget::diff`] is called). /// /// Otherwise, the whole [`Tree`] is recreated. /// /// [`Widget::diff`]: crate::Widget::diff pub fn diff<'a, Message, Theme, Renderer>( &mut self, new: impl Borrow + 'a>, ) where Renderer: crate::Renderer, { if self.tag == new.borrow().tag() { new.borrow().diff(self); } else { *self = Self::new(new); } } /// Reconciles the children of the tree with the provided list of widgets. pub fn diff_children<'a, Message, Theme, Renderer>( &mut self, new_children: &[impl Borrow + 'a>], ) where Renderer: crate::Renderer, { self.diff_children_custom( new_children, |tree, widget| tree.diff(widget.borrow()), |widget| Self::new(widget.borrow()), ); } /// Reconciles the children of the tree with the provided list of widgets using custom /// logic both for diffing and creating new widget state. pub fn diff_children_custom( &mut self, new_children: &[T], diff: impl Fn(&mut Tree, &T), new_state: impl Fn(&T) -> Self, ) { if self.children.len() > new_children.len() { self.children.truncate(new_children.len()); } for (child_state, new) in self.children.iter_mut().zip(new_children.iter()) { diff(child_state, new); } if self.children.len() < new_children.len() { self.children .extend(new_children[self.children.len()..].iter().map(new_state)); } } } /// Reconciles the `current_children` with the provided list of widgets using /// custom logic both for diffing and creating new widget state. /// /// The algorithm will try to minimize the impact of diffing by querying the /// `maybe_changed` closure. pub fn diff_children_custom_with_search( current_children: &mut Vec, new_children: &[T], diff: impl Fn(&mut Tree, &T), maybe_changed: impl Fn(usize) -> bool, new_state: impl Fn(&T) -> Tree, ) { if new_children.is_empty() { current_children.clear(); return; } if current_children.is_empty() { current_children.extend(new_children.iter().map(new_state)); return; } let first_maybe_changed = maybe_changed(0); let last_maybe_changed = maybe_changed(current_children.len() - 1); if current_children.len() > new_children.len() { if !first_maybe_changed && last_maybe_changed { current_children.truncate(new_children.len()); } else { let difference_index = if first_maybe_changed { 0 } else { (1..current_children.len()) .find(|&i| maybe_changed(i)) .unwrap_or(0) }; let _ = current_children.splice( difference_index..difference_index + (current_children.len() - new_children.len()), std::iter::empty(), ); } } if current_children.len() < new_children.len() { let first_maybe_changed = maybe_changed(0); let last_maybe_changed = maybe_changed(current_children.len() - 1); if !first_maybe_changed && last_maybe_changed { current_children.extend(new_children[current_children.len()..].iter().map(new_state)); } else { let difference_index = if first_maybe_changed { 0 } else { (1..current_children.len()) .find(|&i| maybe_changed(i)) .unwrap_or(0) }; let _ = current_children.splice( difference_index..difference_index, new_children[difference_index ..difference_index + (new_children.len() - current_children.len())] .iter() .map(new_state), ); } } // TODO: Merge loop with extend logic (?) for (child_state, new) in current_children.iter_mut().zip(new_children.iter()) { diff(child_state, new); } } /// The identifier of some widget state. #[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)] pub struct Tag(any::TypeId); impl Tag { /// Creates a [`Tag`] for a state of type `T`. pub fn of() -> Self where T: 'static, { Self(any::TypeId::of::()) } /// Creates a [`Tag`] for a stateless widget. pub fn stateless() -> Self { Self::of::<()>() } } /// The internal [`State`] of a widget. pub enum State { /// No meaningful internal state. None, /// Some meaningful internal state. Some(Box), } impl State { /// Creates a new [`State`]. pub fn new(state: T) -> Self where T: 'static, { State::Some(Box::new(state)) } /// Downcasts the [`State`] to `T` and returns a reference to it. /// /// # Panics /// This method will panic if the downcast fails or the [`State`] is [`State::None`]. pub fn downcast_ref(&self) -> &T where T: 'static, { match self { State::None => panic!("Downcast on stateless state"), State::Some(state) => state.downcast_ref().expect("Downcast widget state"), } } /// Downcasts the [`State`] to `T` and returns a mutable reference to it. /// /// # Panics /// This method will panic if the downcast fails or the [`State`] is [`State::None`]. pub fn downcast_mut(&mut self) -> &mut T where T: 'static, { match self { State::None => panic!("Downcast on stateless state"), State::Some(state) => state.downcast_mut().expect("Downcast widget state"), } } } impl fmt::Debug for State { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::None => write!(f, "State::None"), Self::Some(_) => write!(f, "State::Some"), } } } ================================================ FILE: core/src/widget.rs ================================================ //! Create custom widgets and operate on them. pub mod operation; pub mod text; pub mod tree; mod id; pub use id::Id; pub use operation::Operation; pub use text::Text; pub use tree::Tree; use crate::layout::{self, Layout}; use crate::mouse; use crate::overlay; use crate::renderer; use crate::{Event, Length, Rectangle, Shell, Size, Vector}; /// A component that displays information and allows interaction. /// /// If you want to build your own widgets, you will need to implement this /// trait. /// /// # Examples /// The repository has some [examples] showcasing how to implement a custom /// widget: /// /// - [`custom_widget`], a demonstration of how to build a custom widget that /// draws a circle. /// - [`geometry`], a custom widget showcasing how to draw geometry with the /// `Mesh2D` primitive in [`iced_wgpu`]. /// /// [examples]: https://github.com/iced-rs/iced/tree/master/examples /// [`custom_widget`]: https://github.com/iced-rs/iced/tree/master/examples/custom_widget /// [`geometry`]: https://github.com/iced-rs/iced/tree/master/examples/geometry /// [`iced_wgpu`]: https://github.com/iced-rs/iced/tree/master/wgpu pub trait Widget where Renderer: crate::Renderer, { /// Returns the [`Size`] of the [`Widget`] in lengths. fn size(&self) -> Size; /// Returns a [`Size`] hint for laying out the [`Widget`]. /// /// This hint may be used by some widget containers to adjust their sizing strategy /// during construction. fn size_hint(&self) -> Size { self.size() } /// Returns the [`layout::Node`] of the [`Widget`]. /// /// This [`layout::Node`] is used by the runtime to compute the [`Layout`] of the /// user interface. fn layout( &mut self, tree: &mut Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node; /// Draws the [`Widget`] using the associated `Renderer`. fn draw( &self, tree: &Tree, renderer: &mut Renderer, theme: &Theme, style: &renderer::Style, layout: Layout<'_>, cursor: mouse::Cursor, viewport: &Rectangle, ); /// Returns the [`Tag`] of the [`Widget`]. /// /// [`Tag`]: tree::Tag fn tag(&self) -> tree::Tag { tree::Tag::stateless() } /// Returns the [`State`] of the [`Widget`]. /// /// [`State`]: tree::State fn state(&self) -> tree::State { tree::State::None } /// Returns the state [`Tree`] of the children of the [`Widget`]. fn children(&self) -> Vec { Vec::new() } /// Reconciles the [`Widget`] with the provided [`Tree`]. fn diff(&self, tree: &mut Tree) { tree.children.clear(); } /// Applies an [`Operation`] to the [`Widget`]. fn operate( &mut self, _tree: &mut Tree, _layout: Layout<'_>, _renderer: &Renderer, _operation: &mut dyn Operation, ) { } /// Processes a runtime [`Event`]. /// /// By default, it does nothing. fn update( &mut self, _tree: &mut Tree, _event: &Event, _layout: Layout<'_>, _cursor: mouse::Cursor, _renderer: &Renderer, _shell: &mut Shell<'_, Message>, _viewport: &Rectangle, ) { } /// Returns the current [`mouse::Interaction`] of the [`Widget`]. /// /// By default, it returns [`mouse::Interaction::None`]. fn mouse_interaction( &self, _tree: &Tree, _layout: Layout<'_>, _cursor: mouse::Cursor, _viewport: &Rectangle, _renderer: &Renderer, ) -> mouse::Interaction { mouse::Interaction::None } /// Returns the overlay of the [`Widget`], if there is any. fn overlay<'a>( &'a mut self, _tree: &'a mut Tree, _layout: Layout<'a>, _renderer: &Renderer, _viewport: &Rectangle, _translation: Vector, ) -> Option> { None } } ================================================ FILE: core/src/window/direction.rs ================================================ /// The cardinal directions relative to the center of a window. #[derive(Debug, Clone, Copy)] pub enum Direction { /// Points to the top edge of a window. North, /// Points to the bottom edge of a window. South, /// Points to the right edge of a window. East, /// Points to the left edge of a window. West, /// Points to the top-right corner of a window. NorthEast, /// Points to the top-left corner of a window. NorthWest, /// Points to the bottom-right corner of a window. SouthEast, /// Points to the bottom-left corner of a window. SouthWest, } ================================================ FILE: core/src/window/event.rs ================================================ use crate::time::Instant; use crate::{Point, Size}; use std::path::PathBuf; /// A window-related event. #[derive(PartialEq, Clone, Debug)] pub enum Event { /// A window was opened. Opened { /// The position of the opened window. This is relative to the top-left corner of the desktop /// the window is on, including virtual desktops. Refers to window's "outer" position, /// or the window area, in logical pixels. /// /// **Note**: Not available in Wayland. position: Option, /// The size of the created window. This is its "inner" size, or the size of the /// client area, in logical pixels. size: Size, /// The scale factor of the created window. scale_factor: f32, }, /// A window was closed. Closed, /// A window was moved. Moved(Point), /// A window was resized. Resized(Size), /// A window changed its scale factor. Rescaled(f32), /// A window redraw was requested. /// /// The [`Instant`] contains the current time. RedrawRequested(Instant), /// The user has requested for the window to close. CloseRequested, /// A window was focused. Focused, /// A window was unfocused. Unfocused, /// A file is being hovered over the window. /// /// When the user hovers multiple files at once, this event will be emitted /// for each file separately. /// /// ## Platform-specific /// /// - **Wayland:** Not implemented. FileHovered(PathBuf), /// A file has been dropped into the window. /// /// When the user drops multiple files at once, this event will be emitted /// for each file separately. /// /// ## Platform-specific /// /// - **Wayland:** Not implemented. FileDropped(PathBuf), /// A file was hovered, but has exited the window. /// /// There will be a single `FilesHoveredLeft` event triggered even if /// multiple files were hovered. /// /// ## Platform-specific /// /// - **Wayland:** Not implemented. FilesHoveredLeft, } ================================================ FILE: core/src/window/icon.rs ================================================ //! Change the icon of a window. use crate::Size; use std::mem; /// Builds an [`Icon`] from its RGBA pixels in the `sRGB` color space. pub fn from_rgba(rgba: Vec, width: u32, height: u32) -> Result { const PIXEL_SIZE: usize = mem::size_of::() * 4; if !rgba.len().is_multiple_of(PIXEL_SIZE) { return Err(Error::ByteCountNotDivisibleBy4 { byte_count: rgba.len(), }); } let pixel_count = rgba.len() / PIXEL_SIZE; if pixel_count != (width * height) as usize { return Err(Error::DimensionsVsPixelCount { width, height, width_x_height: (width * height) as usize, pixel_count, }); } Ok(Icon { rgba, size: Size::new(width, height), }) } /// An window icon normally used for the titlebar or taskbar. #[derive(Clone)] pub struct Icon { rgba: Vec, size: Size, } impl Icon { /// Returns the raw data of the [`Icon`]. pub fn into_raw(self) -> (Vec, Size) { (self.rgba, self.size) } } impl std::fmt::Debug for Icon { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("Icon") .field("rgba", &format!("{} pixels", self.rgba.len() / 4)) .field("size", &self.size) .finish() } } #[derive(Debug, thiserror::Error)] /// An error produced when using [`from_rgba`] with invalid arguments. pub enum Error { /// Produced when the length of the `rgba` argument isn't divisible by 4, thus `rgba` can't be /// safely interpreted as 32bpp RGBA pixels. #[error( "The provided RGBA data (with length {byte_count}) isn't divisible \ by 4. Therefore, it cannot be safely interpreted as 32bpp RGBA pixels" )] ByteCountNotDivisibleBy4 { /// The length of the provided RGBA data. byte_count: usize, }, /// Produced when the number of pixels (`rgba.len() / 4`) isn't equal to `width * height`. /// At least one of your arguments is incorrect. #[error( "The number of RGBA pixels ({pixel_count}) does not match the \ provided dimensions ({width}x{height})." )] DimensionsVsPixelCount { /// The provided width. width: u32, /// The provided height. height: u32, /// The product of `width` and `height`. width_x_height: usize, /// The amount of pixels of the provided RGBA data. pixel_count: usize, }, } ================================================ FILE: core/src/window/id.rs ================================================ use std::fmt; use std::hash::Hash; use std::sync::atomic::{self, AtomicU64}; /// The id of the window. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Id(u64); static COUNT: AtomicU64 = AtomicU64::new(1); impl Id { /// Creates a new unique window [`Id`]. pub fn unique() -> Id { Id(COUNT.fetch_add(1, atomic::Ordering::Relaxed)) } } impl fmt::Display for Id { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(f) } } ================================================ FILE: core/src/window/level.rs ================================================ /// A window level groups windows with respect to their z-position. /// /// The relative ordering between windows in different window levels is fixed. /// The z-order of a window within the same window level may change dynamically /// on user interaction. #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum Level { /// The default behavior. #[default] Normal, /// The window will always be below normal windows. /// /// This is useful for a widget-based app. AlwaysOnBottom, /// The window will always be on top of normal windows. AlwaysOnTop, } ================================================ FILE: core/src/window/mode.rs ================================================ /// The mode of a window-based application. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Mode { /// The application appears in its own window. Windowed, /// The application takes the whole screen of its current monitor. Fullscreen, /// The application is hidden Hidden, } ================================================ FILE: core/src/window/position.rs ================================================ use crate::{Point, Size}; /// The position of a window in a given screen. #[derive(Debug, Clone, Copy, Default)] pub enum Position { /// The platform-specific default position for a new window. #[default] Default, /// The window is completely centered on the screen. Centered, /// The window is positioned with specific coordinates: `(X, Y)`. /// /// When the decorations of the window are enabled, Windows 10 will add some /// invisible padding to the window. This padding gets included in the /// position. So if you have decorations enabled and want the window to be /// at (0, 0) you would have to set the position to /// `(PADDING_X, PADDING_Y)`. Specific(Point), /// Like [`Specific`], but the window is positioned with the specific coordinates returned by the function. /// /// The function receives the window size and the monitor's resolution as input. /// /// [`Specific`]: Self::Specific SpecificWith(fn(Size, Size) -> Point), } ================================================ FILE: core/src/window/redraw_request.rs ================================================ use crate::time::Instant; /// A request to redraw a window. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub enum RedrawRequest { /// Redraw the next frame. NextFrame, /// Redraw at the given time. At(Instant), /// No redraw is needed. Wait, } impl From for RedrawRequest { fn from(time: Instant) -> Self { Self::At(time) } } #[cfg(test)] mod tests { use super::*; use crate::time::Duration; #[test] fn ordering() { let now = Instant::now(); let later = now + Duration::from_millis(10); assert_eq!(RedrawRequest::NextFrame, RedrawRequest::NextFrame); assert_eq!(RedrawRequest::At(now), RedrawRequest::At(now)); assert!(RedrawRequest::NextFrame < RedrawRequest::At(now)); assert!(RedrawRequest::At(now) > RedrawRequest::NextFrame); assert!(RedrawRequest::At(now) < RedrawRequest::At(later)); assert!(RedrawRequest::At(later) > RedrawRequest::At(now)); assert!(RedrawRequest::NextFrame <= RedrawRequest::NextFrame); assert!(RedrawRequest::NextFrame <= RedrawRequest::At(now)); assert!(RedrawRequest::At(now) >= RedrawRequest::NextFrame); assert!(RedrawRequest::At(now) <= RedrawRequest::At(now)); assert!(RedrawRequest::At(now) <= RedrawRequest::At(later)); assert!(RedrawRequest::At(later) >= RedrawRequest::At(now)); assert!(RedrawRequest::Wait > RedrawRequest::NextFrame); assert!(RedrawRequest::Wait > RedrawRequest::At(later)); } } ================================================ FILE: core/src/window/screenshot.rs ================================================ //! Take screenshots of a window. use crate::{Bytes, Rectangle, Size}; use std::fmt::{Debug, Formatter}; /// Data of a screenshot, captured with `window::screenshot()`. /// /// The `bytes` of this screenshot will always be ordered as `RGBA` in the `sRGB` color space. #[derive(Clone)] pub struct Screenshot { /// The RGBA bytes of the [`Screenshot`]. pub rgba: Bytes, /// The size of the [`Screenshot`] in physical pixels. pub size: Size, /// The scale factor of the [`Screenshot`]. This can be useful when converting between widget /// bounds (which are in logical pixels) to crop screenshots. pub scale_factor: f32, } impl Debug for Screenshot { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!( f, "Screenshot: {{ \n bytes: {}\n scale: {}\n size: {:?} }}", self.rgba.len(), self.scale_factor, self.size ) } } impl Screenshot { /// Creates a new [`Screenshot`]. pub fn new(rgba: impl Into, size: Size, scale_factor: f32) -> Self { Self { rgba: rgba.into(), size, scale_factor, } } /// Crops a [`Screenshot`] to the provided `region`. This will always be relative to the /// top-left corner of the [`Screenshot`]. pub fn crop(&self, region: Rectangle) -> Result { if region.width == 0 || region.height == 0 { return Err(CropError::Zero); } if region.x + region.width > self.size.width || region.y + region.height > self.size.height { return Err(CropError::OutOfBounds); } // Image is always RGBA8 = 4 bytes per pixel const PIXEL_SIZE: usize = 4; let bytes_per_row = self.size.width as usize * PIXEL_SIZE; let row_range = region.y as usize..(region.y + region.height) as usize; let column_range = region.x as usize * PIXEL_SIZE..(region.x + region.width) as usize * PIXEL_SIZE; let chopped = self.rgba .chunks(bytes_per_row) .enumerate() .fold(vec![], |mut acc, (row, bytes)| { if row_range.contains(&row) { acc.extend(&bytes[column_range.clone()]); } acc }); Ok(Self { rgba: Bytes::from(chopped), size: Size::new(region.width, region.height), scale_factor: self.scale_factor, }) } } impl AsRef<[u8]> for Screenshot { fn as_ref(&self) -> &[u8] { &self.rgba } } impl From for Bytes { fn from(screenshot: Screenshot) -> Self { screenshot.rgba } } #[derive(Debug, thiserror::Error)] /// Errors that can occur when cropping a [`Screenshot`]. pub enum CropError { #[error("The cropped region is out of bounds.")] /// The cropped region's size is out of bounds. OutOfBounds, #[error("The cropped region is not visible.")] /// The cropped region's size is zero. Zero, } ================================================ FILE: core/src/window/settings/linux.rs ================================================ //! Platform specific settings for Linux. /// The platform specific window settings of an application. #[derive(Debug, Clone, PartialEq, Eq, Default)] pub struct PlatformSpecific { /// Sets the application id of the window. /// /// As a best practice, it is suggested to select an application id that match /// the basename of the application’s .desktop file. pub application_id: String, /// Whether bypass the window manager mapping for x11 windows /// /// This flag is particularly useful for creating UI elements that need precise /// positioning and immediate display without window manager interference. pub override_redirect: bool, } ================================================ FILE: core/src/window/settings/macos.rs ================================================ //! Platform specific settings for macOS. /// The platform specific window settings of an application. #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub struct PlatformSpecific { /// Hides the window title. pub title_hidden: bool, /// Makes the titlebar transparent and allows the content to appear behind it. pub titlebar_transparent: bool, /// Makes the window content appear behind the titlebar. pub fullsize_content_view: bool, } ================================================ FILE: core/src/window/settings/other.rs ================================================ /// The platform specific window settings of an application. #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub struct PlatformSpecific; ================================================ FILE: core/src/window/settings/wasm.rs ================================================ //! Platform specific settings for WebAssembly. /// The platform specific window settings of an application. #[derive(Debug, Clone, PartialEq, Eq)] pub struct PlatformSpecific { /// The identifier of a DOM element that will be replaced with the /// application. /// /// If set to `None`, the application will be appended to the HTML body. /// /// By default, it is set to `"iced"`. pub target: Option, } impl Default for PlatformSpecific { fn default() -> Self { Self { target: Some(String::from("iced")), } } } ================================================ FILE: core/src/window/settings/windows.rs ================================================ //! Platform specific settings for Windows. /// The platform specific window settings of an application. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct PlatformSpecific { /// Drag and drop support pub drag_and_drop: bool, /// Whether show or hide the window icon in the taskbar. pub skip_taskbar: bool, /// Shows or hides the background drop shadow for undecorated windows. /// /// The shadow is hidden by default. /// Enabling the shadow causes a thin 1px line to appear on the top of the window. pub undecorated_shadow: bool, /// Sets the preferred style of the window corners. /// /// Supported starting with Windows 11 Build 22000. pub corner_preference: CornerPreference, } impl Default for PlatformSpecific { fn default() -> Self { Self { drag_and_drop: true, skip_taskbar: false, undecorated_shadow: false, corner_preference: Default::default(), } } } /// Describes how the corners of a window should look like. #[repr(i32)] #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] pub enum CornerPreference { /// Corresponds to `DWMWCP_DEFAULT`. /// /// Let the system decide when to round window corners. #[default] Default = 0, /// Corresponds to `DWMWCP_DONOTROUND`. /// /// Never round window corners. DoNotRound = 1, /// Corresponds to `DWMWCP_ROUND`. /// /// Round the corners, if appropriate. Round = 2, /// Corresponds to `DWMWCP_ROUNDSMALL`. /// /// Round the corners if appropriate, with a small radius. RoundSmall = 3, } ================================================ FILE: core/src/window/settings.rs ================================================ //! Configure your windows. #[cfg(target_os = "windows")] #[path = "settings/windows.rs"] pub mod platform; #[cfg(target_os = "macos")] #[path = "settings/macos.rs"] mod platform; #[cfg(target_os = "linux")] #[path = "settings/linux.rs"] mod platform; #[cfg(target_arch = "wasm32")] #[path = "settings/wasm.rs"] mod platform; #[cfg(not(any( target_os = "windows", target_os = "macos", target_os = "linux", target_arch = "wasm32" )))] #[path = "settings/other.rs"] mod platform; use crate::Size; use crate::window::{Icon, Level, Position}; pub use platform::PlatformSpecific; /// The window settings of an application. #[derive(Debug, Clone)] pub struct Settings { /// The initial logical dimensions of the window. pub size: Size, /// Whether the window should start maximized. pub maximized: bool, /// Whether the window should start fullscreen. pub fullscreen: bool, /// The initial position of the window. pub position: Position, /// The minimum size of the window. pub min_size: Option, /// The maximum size of the window. pub max_size: Option, /// Whether the window should be visible or not. pub visible: bool, /// Whether the window should be resizable or not. pub resizable: bool, /// Whether the title bar has Close button or not pub closeable: bool, /// Whether the title bar has Minimize button or not pub minimizable: bool, /// Whether the window should have a border, a title bar, etc. or not. pub decorations: bool, /// Whether the window should be transparent. pub transparent: bool, /// Whether the window should have blurry background. /// /// Note that the blurry effect is applied to the transparent window. You need to enable /// [`Settings::transparent`] and set a proper opacity value to the background color with /// `Application::style`. /// /// This option is only supported on macOS and Linux. Please read the [winit document][winit] /// for more details. /// /// [winit]: https://docs.rs/winit/0.30/winit/window/struct.Window.html#method.set_blur pub blur: bool, /// The window [`Level`]. pub level: Level, /// The icon of the window. pub icon: Option, /// Platform specific settings. pub platform_specific: PlatformSpecific, /// Whether the window will close when the user requests it, e.g. when a user presses the /// close button. /// /// This can be useful if you want to have some behavior that executes before the window is /// actually destroyed. If you disable this, you must manually close the window with the /// `window::close` command. /// /// By default this is enabled. pub exit_on_close_request: bool, } impl Default for Settings { fn default() -> Self { Self { size: Size::new(1024.0, 768.0), maximized: false, fullscreen: false, position: Position::default(), min_size: None, max_size: None, visible: true, resizable: true, minimizable: true, closeable: true, decorations: true, transparent: false, blur: false, level: Level::default(), icon: None, exit_on_close_request: true, platform_specific: PlatformSpecific::default(), } } } ================================================ FILE: core/src/window/user_attention.rs ================================================ /// The type of user attention to request. /// /// ## Platform-specific /// /// - **X11:** Sets the WM's `XUrgencyHint`. No distinction between [`Critical`] and [`Informational`]. /// /// [`Critical`]: Self::Critical /// [`Informational`]: Self::Informational #[derive(Debug, Clone, Copy)] pub enum UserAttention { /// ## Platform-specific /// /// - **macOS:** Bounces the dock icon until the application is in focus. /// - **Windows:** Flashes both the window and the taskbar button until the application is in focus. Critical, /// ## Platform-specific /// /// - **macOS:** Bounces the dock icon once. /// - **Windows:** Flashes the taskbar button until the application is in focus. Informational, } ================================================ FILE: core/src/window.rs ================================================ //! Build window-based GUI applications. pub mod icon; pub mod screenshot; pub mod settings; mod direction; mod event; mod id; mod level; mod mode; mod position; mod redraw_request; mod user_attention; pub use direction::Direction; pub use event::Event; pub use icon::Icon; pub use id::Id; pub use level::Level; pub use mode::Mode; pub use position::Position; pub use redraw_request::RedrawRequest; pub use screenshot::Screenshot; pub use settings::Settings; pub use user_attention::UserAttention; ================================================ FILE: debug/Cargo.toml ================================================ [package] name = "iced_debug" description = "A pluggable API for debugging iced applications" version.workspace = true edition.workspace = true authors.workspace = true license.workspace = true repository.workspace = true homepage.workspace = true categories.workspace = true keywords.workspace = true [features] enable = ["dep:iced_beacon"] hot = ["enable", "dep:cargo-hot"] [dependencies] iced_core.workspace = true iced_futures.workspace = true log.workspace = true [target.'cfg(not(target_arch = "wasm32"))'.dependencies] iced_beacon.workspace = true iced_beacon.optional = true cargo-hot.workspace = true cargo-hot.optional = true ================================================ FILE: debug/src/lib.rs ================================================ pub use iced_core as core; pub use iced_futures as futures; use crate::core::theme::palette; use crate::core::window; use crate::futures::Subscription; pub use internal::Span; #[derive(Debug, Clone, Copy)] pub struct Metadata { pub name: &'static str, pub theme: Option, pub can_time_travel: bool, } #[derive(Debug, Clone, Copy)] pub enum Primitive { Quad, Triangle, Shader, Image, Text, } #[derive(Debug, Clone, Copy)] pub enum Command { RewindTo { message: usize }, GoLive, } pub fn enable() { internal::enable(); } pub fn disable() { internal::disable(); } pub fn init(metadata: Metadata) { internal::init(metadata); hot::init(); } pub fn quit() -> bool { internal::quit() } pub fn theme_changed(f: impl FnOnce() -> Option) { internal::theme_changed(f); } pub fn tasks_spawned(amount: usize) { internal::tasks_spawned(amount); } pub fn subscriptions_tracked(amount: usize) { internal::subscriptions_tracked(amount); } pub fn layers_rendered(amount: impl FnOnce() -> usize) { internal::layers_rendered(amount); } pub fn boot() -> Span { internal::boot() } pub fn update(message: &impl std::fmt::Debug) -> Span { internal::update(message) } pub fn view(window: window::Id) -> Span { internal::view(window) } pub fn layout(window: window::Id) -> Span { internal::layout(window) } pub fn interact(window: window::Id) -> Span { internal::interact(window) } pub fn draw(window: window::Id) -> Span { internal::draw(window) } pub fn prepare(primitive: Primitive) -> Span { internal::prepare(primitive) } pub fn render(primitive: Primitive) -> Span { internal::render(primitive) } pub fn present(window: window::Id) -> Span { internal::present(window) } pub fn time(name: impl Into) -> Span { internal::time(name) } pub fn time_with(name: impl Into, f: impl FnOnce() -> T) -> T { let span = time(name); let result = f(); span.finish(); result } pub fn commands() -> Subscription { internal::commands() } pub fn hot(f: impl FnOnce() -> O) -> O { hot::call(f) } pub fn on_hotpatch(f: impl Fn() + Send + Sync + 'static) { hot::on_hotpatch(f) } pub fn is_stale() -> bool { hot::is_stale() } #[cfg(all(feature = "enable", not(target_arch = "wasm32")))] mod internal { use crate::core::theme::palette; use crate::core::time::Instant; use crate::core::window; use crate::futures::Subscription; use crate::futures::futures::Stream; use crate::{Command, Metadata, Primitive}; use iced_beacon as beacon; use beacon::client::{self, Client}; use beacon::span; use beacon::span::present; use std::sync::atomic::{self, AtomicBool, AtomicUsize}; use std::sync::{LazyLock, RwLock}; pub fn init(metadata: Metadata) { let name = metadata.name.split("::").next().unwrap_or(metadata.name); *METADATA.write().expect("Write application metadata") = client::Metadata { name, theme: metadata.theme, can_time_travel: metadata.can_time_travel, }; } pub fn quit() -> bool { if BEACON.is_connected() { BEACON.quit(); true } else { false } } pub fn theme_changed(f: impl FnOnce() -> Option) { let Some(palette) = f() else { return; }; if METADATA.read().expect("Read last palette").theme.as_ref() != Some(&palette) { log(client::Event::ThemeChanged(palette)); METADATA.write().expect("Write last palette").theme = Some(palette); } } pub fn tasks_spawned(amount: usize) { log(client::Event::CommandsSpawned(amount)); } pub fn subscriptions_tracked(amount: usize) { log(client::Event::SubscriptionsTracked(amount)); } pub fn layers_rendered(amount: impl FnOnce() -> usize) { log(client::Event::LayersRendered(amount())); } pub fn boot() -> Span { span(span::Stage::Boot) } pub fn update(message: &impl std::fmt::Debug) -> Span { let span = span(span::Stage::Update); let number = LAST_UPDATE.fetch_add(1, atomic::Ordering::Relaxed); let start = Instant::now(); let message = format!("{message:?}"); let elapsed = start.elapsed(); if elapsed.as_millis() >= 1 { log::warn!("Slow `Debug` implementation of `Message` (took {elapsed:?})!"); } let message = if message.len() > 49 { message .chars() .take(49) .chain("...".chars()) .collect::() } else { message }; log(client::Event::MessageLogged { number, message }); span } pub fn view(window: window::Id) -> Span { span(span::Stage::View(window)) } pub fn layout(window: window::Id) -> Span { span(span::Stage::Layout(window)) } pub fn interact(window: window::Id) -> Span { span(span::Stage::Interact(window)) } pub fn draw(window: window::Id) -> Span { span(span::Stage::Draw(window)) } pub fn prepare(primitive: Primitive) -> Span { span(span::Stage::Prepare(to_primitive(primitive))) } pub fn render(primitive: Primitive) -> Span { span(span::Stage::Render(to_primitive(primitive))) } pub fn present(window: window::Id) -> Span { span(span::Stage::Present(window)) } pub fn time(name: impl Into) -> Span { span(span::Stage::Custom(name.into())) } pub fn enable() { ENABLED.store(true, atomic::Ordering::Relaxed); } pub fn disable() { ENABLED.store(false, atomic::Ordering::Relaxed); } pub fn commands() -> Subscription { fn listen_for_commands() -> impl Stream { use crate::futures::futures::stream; stream::unfold(BEACON.subscribe(), async move |mut receiver| { let command = match receiver.recv().await? { client::Command::RewindTo { message } => Command::RewindTo { message }, client::Command::GoLive => Command::GoLive, }; Some((command, receiver)) }) } Subscription::run(listen_for_commands) } fn span(span: span::Stage) -> Span { log(client::Event::SpanStarted(span.clone())); Span { span, start: Instant::now(), } } fn to_primitive(primitive: Primitive) -> present::Primitive { match primitive { Primitive::Quad => present::Primitive::Quad, Primitive::Triangle => present::Primitive::Triangle, Primitive::Shader => present::Primitive::Shader, Primitive::Text => present::Primitive::Text, Primitive::Image => present::Primitive::Image, } } fn log(event: client::Event) { if ENABLED.load(atomic::Ordering::Relaxed) { BEACON.log(event); } } #[derive(Debug)] pub struct Span { span: span::Stage, start: Instant, } impl Span { pub fn finish(self) { log(client::Event::SpanFinished(self.span, self.start.elapsed())); } } static BEACON: LazyLock = LazyLock::new(|| { let metadata = METADATA.read().expect("Read application metadata"); client::connect(metadata.clone()) }); static METADATA: RwLock = RwLock::new(client::Metadata { name: "", theme: None, can_time_travel: false, }); static LAST_UPDATE: AtomicUsize = AtomicUsize::new(0); static ENABLED: AtomicBool = AtomicBool::new(true); } #[cfg(any(not(feature = "enable"), target_arch = "wasm32"))] mod internal { use crate::core::theme::palette; use crate::core::window; use crate::futures::Subscription; use crate::{Command, Metadata, Primitive}; pub fn enable() {} pub fn disable() {} pub fn init(_metadata: Metadata) {} pub fn quit() -> bool { false } pub fn theme_changed(_f: impl FnOnce() -> Option) {} pub fn tasks_spawned(_amount: usize) {} pub fn subscriptions_tracked(_amount: usize) {} pub fn layers_rendered(_amount: impl FnOnce() -> usize) {} pub fn boot() -> Span { Span } pub fn update(_message: &impl std::fmt::Debug) -> Span { Span } pub fn view(_window: window::Id) -> Span { Span } pub fn layout(_window: window::Id) -> Span { Span } pub fn interact(_window: window::Id) -> Span { Span } pub fn draw(_window: window::Id) -> Span { Span } pub fn prepare(_primitive: Primitive) -> Span { Span } pub fn render(_primitive: Primitive) -> Span { Span } pub fn present(_window: window::Id) -> Span { Span } pub fn time(_name: impl Into) -> Span { Span } pub fn commands() -> Subscription { Subscription::none() } #[derive(Debug)] pub struct Span; impl Span { pub fn finish(self) {} } } #[cfg(all(feature = "hot", not(target_arch = "wasm32")))] mod hot { use std::collections::BTreeSet; use std::sync::atomic::{self, AtomicBool}; use std::sync::{Arc, Mutex, OnceLock}; static IS_STALE: AtomicBool = AtomicBool::new(false); static HOT_FUNCTIONS_PENDING: Mutex> = Mutex::new(BTreeSet::new()); static HOT_FUNCTIONS: OnceLock> = OnceLock::new(); pub fn init() { cargo_hot::connect(); cargo_hot::subsecond::register_handler(Arc::new(|| { if HOT_FUNCTIONS.get().is_none() { HOT_FUNCTIONS .set(std::mem::take( &mut HOT_FUNCTIONS_PENDING.lock().expect("Lock hot functions"), )) .expect("Set hot functions"); } IS_STALE.store(false, atomic::Ordering::Relaxed); })); } pub fn call(f: impl FnOnce() -> O) -> O { let mut f = Some(f); // The `move` here is important. Hotpatching will not work // otherwise. let mut f = cargo_hot::subsecond::HotFn::current(move || { f.take().expect("Hot function is stale")() }); let address = f.ptr_address().0; if let Some(hot_functions) = HOT_FUNCTIONS.get() { if hot_functions.contains(&address) { IS_STALE.store(true, atomic::Ordering::Relaxed); } } else { let _ = HOT_FUNCTIONS_PENDING .lock() .expect("Lock hot functions") .insert(address); } f.call(()) } pub fn on_hotpatch(f: impl Fn() + Send + Sync + 'static) { cargo_hot::subsecond::register_handler(Arc::new(f)); } pub fn is_stale() -> bool { IS_STALE.load(atomic::Ordering::Relaxed) } } #[cfg(any(not(feature = "hot"), target_arch = "wasm32"))] mod hot { pub fn init() {} pub fn call(f: impl FnOnce() -> O) -> O { f() } pub fn on_hotpatch(_f: impl Fn()) {} pub fn is_stale() -> bool { false } } ================================================ FILE: devtools/Cargo.toml ================================================ [package] name = "iced_devtools" description = "Attachable developer tools for any iced program" version.workspace = true authors.workspace = true edition.workspace = true license.workspace = true repository.workspace = true homepage.workspace = true categories.workspace = true keywords.workspace = true rust-version.workspace = true [lints] workspace = true [features] time-travel = ["iced_program/time-travel"] [dependencies] iced_debug.workspace = true iced_widget.workspace = true log.workspace = true iced_program.workspace = true iced_program.features = ["debug"] ================================================ FILE: devtools/src/comet.rs ================================================ use crate::runtime::task::{self, Task}; use std::process; pub const COMPATIBLE_REVISION: &str = "fbef808eed51562f0ea601d8fc7c715bea9cfd0b"; pub fn launch() -> Task { task::try_blocking(|mut sender| { let cargo_install = process::Command::new("cargo") .args(["install", "--list"]) .output()?; let installed_packages = String::from_utf8_lossy(&cargo_install.stdout); for line in installed_packages.lines() { if !line.starts_with("iced_comet ") { continue; } let Some((_, revision)) = line.rsplit_once("?rev=") else { return Err(launch::Error::Outdated { revision: None }); }; let Some((revision, _)) = revision.rsplit_once("#") else { return Err(launch::Error::Outdated { revision: None }); }; if revision != COMPATIBLE_REVISION { return Err(launch::Error::Outdated { revision: Some(revision.to_owned()), }); } let _ = process::Command::new("iced_comet") .stdin(process::Stdio::null()) .stdout(process::Stdio::null()) .stderr(process::Stdio::null()) .spawn()?; let _ = sender.try_send(()); return Ok(()); } Err(launch::Error::NotFound) }) } pub fn install() -> Task { task::try_blocking(|mut sender| { use std::io::{BufRead, BufReader}; use std::process::{Command, Stdio}; let mut install = Command::new("cargo") .args([ "install", "--locked", "--git", "https://github.com/iced-rs/comet.git", "--rev", COMPATIBLE_REVISION, ]) .stdin(Stdio::null()) .stdout(Stdio::null()) .stderr(Stdio::piped()) .spawn()?; let mut stderr = BufReader::new(install.stderr.take().expect("stderr must be piped")); let mut log = String::new(); while let Ok(n) = stderr.read_line(&mut log) { if n == 0 { let status = install.wait()?; if status.success() { break; } else { return Err(install::Error::ProcessFailed(status)); } } let _ = sender.try_send(install::Event::Logged(log.trim_end().to_owned())); log.clear(); } let _ = sender.try_send(install::Event::Finished); Ok(()) }) } pub mod launch { use std::io; use std::sync::Arc; pub type Result = std::result::Result<(), Error>; #[derive(Debug, Clone)] pub enum Error { NotFound, Outdated { revision: Option }, IoFailed(Arc), } impl From for Error { fn from(error: io::Error) -> Self { Self::IoFailed(Arc::new(error)) } } } pub mod install { use std::io; use std::process; use std::sync::Arc; pub type Result = std::result::Result; #[derive(Debug, Clone)] pub enum Event { Logged(String), Finished, } #[derive(Debug, Clone)] pub enum Error { ProcessFailed(process::ExitStatus), IoFailed(Arc), } impl From for Error { fn from(error: io::Error) -> Self { Self::IoFailed(Arc::new(error)) } } } ================================================ FILE: devtools/src/lib.rs ================================================ #![allow(missing_docs)] use iced_debug as debug; use iced_program as program; use iced_program::runtime; use iced_program::runtime::futures; use iced_widget as widget; use iced_widget::core; mod comet; mod time_machine; use crate::core::border; use crate::core::keyboard; use crate::core::theme::{self, Theme}; use crate::core::time::seconds; use crate::core::window; use crate::core::{Alignment::Center, Color, Element, Font, Length::Fill, Settings}; use crate::futures::Subscription; use crate::program::Program; use crate::program::message; use crate::runtime::task::{self, Task}; use crate::time_machine::TimeMachine; use crate::widget::{ bottom_right, button, center, column, container, opaque, row, scrollable, space, stack, text, themer, }; use std::fmt; use std::thread; pub fn attach(program: P) -> Attach

{ Attach { program } } /// A [`Program`] with some devtools attached to it. #[derive(Debug)] pub struct Attach

{ /// The original [`Program`] managed by these devtools. pub program: P, } impl

Program for Attach

where P: Program + 'static, P::Message: std::fmt::Debug + message::MaybeClone, { type State = DevTools

; type Message = Event

; type Theme = P::Theme; type Renderer = P::Renderer; type Executor = P::Executor; fn name() -> &'static str { P::name() } fn settings(&self) -> Settings { self.program.settings() } fn window(&self) -> Option { self.program.window() } fn boot(&self) -> (Self::State, Task) { let (state, boot) = self.program.boot(); let (devtools, task) = DevTools::new(state); ( devtools, Task::batch([boot.map(Event::Program), task.map(Event::Message)]), ) } fn update(&self, state: &mut Self::State, message: Self::Message) -> Task { state.update(&self.program, message) } fn view<'a>( &self, state: &'a Self::State, window: window::Id, ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> { state.view(&self.program, window) } fn title(&self, state: &Self::State, window: window::Id) -> String { state.title(&self.program, window) } fn subscription(&self, state: &Self::State) -> Subscription { state.subscription(&self.program) } fn theme(&self, state: &Self::State, window: window::Id) -> Option { state.theme(&self.program, window) } fn style(&self, state: &Self::State, theme: &Self::Theme) -> theme::Style { state.style(&self.program, theme) } fn scale_factor(&self, state: &Self::State, window: window::Id) -> f32 { state.scale_factor(&self.program, window) } } /// The state of the devtools. pub struct DevTools

where P: Program, { state: P::State, show_notification: bool, time_machine: TimeMachine

, mode: Mode, } #[derive(Debug, Clone)] pub enum Message { HideNotification, ToggleComet, CometLaunched(comet::launch::Result), InstallComet, Installing(comet::install::Result), CancelSetup, } enum Mode { Hidden, Setup(Setup), } enum Setup { Idle { goal: Goal }, Running { logs: Vec }, } enum Goal { Installation, Update { revision: Option }, } impl

DevTools

where P: Program + 'static, P::Message: std::fmt::Debug + message::MaybeClone, { pub fn new(state: P::State) -> (Self, Task) { ( Self { state, mode: Mode::Hidden, show_notification: true, time_machine: TimeMachine::new(), }, Task::batch([task::blocking(|mut sender| { thread::sleep(seconds(2)); let _ = sender.try_send(()); }) .map(|_| Message::HideNotification)]), ) } pub fn title(&self, program: &P, window: window::Id) -> String { program.title(&self.state, window) } pub fn update(&mut self, program: &P, event: Event

) -> Task> { match event { Event::Message(message) => match message { Message::HideNotification => { self.show_notification = false; Task::none() } Message::ToggleComet => { if let Mode::Setup(setup) = &self.mode { if matches!(setup, Setup::Idle { .. }) { self.mode = Mode::Hidden; } Task::none() } else if debug::quit() { Task::none() } else { comet::launch() .map(Message::CometLaunched) .map(Event::Message) } } Message::CometLaunched(Ok(())) => Task::none(), Message::CometLaunched(Err(error)) => { match error { comet::launch::Error::NotFound => { self.mode = Mode::Setup(Setup::Idle { goal: Goal::Installation, }); } comet::launch::Error::Outdated { revision } => { self.mode = Mode::Setup(Setup::Idle { goal: Goal::Update { revision }, }); } comet::launch::Error::IoFailed(error) => { log::error!("comet failed to run: {error}"); } } Task::none() } Message::InstallComet => { self.mode = Mode::Setup(Setup::Running { logs: Vec::new() }); comet::install() .map(Message::Installing) .map(Event::Message) } Message::Installing(Ok(installation)) => { let Mode::Setup(Setup::Running { logs }) = &mut self.mode else { return Task::none(); }; match installation { comet::install::Event::Logged(log) => { logs.push(log); Task::none() } comet::install::Event::Finished => { self.mode = Mode::Hidden; comet::launch().discard() } } } Message::Installing(Err(error)) => { let Mode::Setup(Setup::Running { logs }) = &mut self.mode else { return Task::none(); }; match error { comet::install::Error::ProcessFailed(status) => { logs.push(format!("process failed with {status}")); } comet::install::Error::IoFailed(error) => { logs.push(error.to_string()); } } Task::none() } Message::CancelSetup => { self.mode = Mode::Hidden; Task::none() } }, Event::Program(message) => { self.time_machine.push(&message); if self.time_machine.is_rewinding() { debug::enable(); } let span = debug::update(&message); let task = program.update(&mut self.state, message); debug::tasks_spawned(task.units()); span.finish(); if self.time_machine.is_rewinding() { debug::disable(); } task.map(Event::Program) } Event::Command(command) => { match command { debug::Command::RewindTo { message } => { self.time_machine.rewind(program, message); } debug::Command::GoLive => { self.time_machine.go_to_present(); } } Task::none() } Event::Discard => Task::none(), } } pub fn view( &self, program: &P, window: window::Id, ) -> Element<'_, Event

, P::Theme, P::Renderer> { let state = self.state(); let view = { let view = program.view(state, window); if self.time_machine.is_rewinding() { view.map(|_| Event::Discard) } else { view.map(Event::Program) } }; let theme = || { program .theme(state, window) .as_ref() .and_then(theme::Base::seed) .map(|seed| Theme::custom("iced devtools", seed)) }; let setup = if let Mode::Setup(setup) = &self.mode { let stage: Element<'_, _, Theme, P::Renderer> = match setup { Setup::Idle { goal } => self::setup(goal), Setup::Running { logs } => installation(logs), }; let setup = center( container(stage) .padding(20) .max_width(500) .style(container::bordered_box), ) .padding(10) .style(|_theme| container::Style::default().background(Color::BLACK.scale_alpha(0.8))); Some(themer(theme(), opaque(setup).map(Event::Message))) } else { None }; let notification = self .show_notification .then(|| text("Press F12 to open debug metrics")) .or_else(|| { debug::is_stale() .then(|| text("Types have changed. Restart to re-enable hotpatching.")) }) .map(|notification| { themer( theme(), bottom_right(opaque( container(notification).padding(10).style(container::dark), )), ) }); stack![view, setup, notification] .width(Fill) .height(Fill) .into() } pub fn subscription(&self, program: &P) -> Subscription> { let subscription = program.subscription(&self.state).map(Event::Program); debug::subscriptions_tracked(subscription.units()); let hotkeys = futures::keyboard::listen() .filter_map(|event| match event { keyboard::Event::KeyPressed { modified_key: keyboard::Key::Named(keyboard::key::Named::F12), .. } => Some(Message::ToggleComet), _ => None, }) .map(Event::Message); let commands = debug::commands().map(Event::Command); Subscription::batch([subscription, hotkeys, commands]) } pub fn theme(&self, program: &P, window: window::Id) -> Option { program.theme(self.state(), window) } pub fn style(&self, program: &P, theme: &P::Theme) -> theme::Style { program.style(self.state(), theme) } pub fn scale_factor(&self, program: &P, window: window::Id) -> f32 { program.scale_factor(self.state(), window) } pub fn state(&self) -> &P::State { self.time_machine.state().unwrap_or(&self.state) } } pub enum Event

where P: Program, { Message(Message), Program(P::Message), Command(debug::Command), Discard, } impl

fmt::Debug for Event

where P: Program, P::Message: std::fmt::Debug, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Message(message) => message.fmt(f), Self::Program(message) => message.fmt(f), Self::Command(command) => command.fmt(f), Self::Discard => f.write_str("Discard"), } } } fn setup(goal: &Goal) -> Element<'_, Message, Theme, Renderer> where Renderer: program::Renderer + 'static, { let controls = row![ button(text("Cancel").center().width(Fill)) .width(100) .on_press(Message::CancelSetup) .style(button::danger), space::horizontal(), button( text(match goal { Goal::Installation => "Install", Goal::Update { .. } => "Update", }) .center() .width(Fill) ) .width(100) .on_press(Message::InstallComet) .style(button::success), ]; let command = container( text!( "cargo install --locked \\ --git https://github.com/iced-rs/comet.git \\ --rev {}", comet::COMPATIBLE_REVISION ) .size(14) .font(Font::MONOSPACE), ) .width(Fill) .padding(5) .style(container::dark); Element::from(match goal { Goal::Installation => column![ text("comet is not installed!").size(20), "In order to display performance \ metrics, the comet debugger must \ be installed in your system.", "The comet debugger is an official \ companion tool that helps you debug \ your iced applications.", column![ "Do you wish to install it with the \ following command?", command ] .spacing(10), controls, ] .spacing(20), Goal::Update { revision } => { let comparison = column![ row![ "Installed revision:", space::horizontal(), inline_code(revision.as_deref().unwrap_or("Unknown")) ] .align_y(Center), row![ "Compatible revision:", space::horizontal(), inline_code(comet::COMPATIBLE_REVISION), ] .align_y(Center) ] .spacing(5); column![ text("comet is out of date!").size(20), comparison, column![ "Do you wish to update it with the following \ command?", command ] .spacing(10), controls, ] .spacing(20) } }) } fn installation<'a, Renderer>(logs: &'a [String]) -> Element<'a, Message, Theme, Renderer> where Renderer: program::Renderer + 'a, { column![ text("Installing comet...").size(20), container( scrollable( column( logs.iter() .map(|log| { text(log).size(12).font(Font::MONOSPACE).into() }) ) .spacing(3), ) .spacing(10) .width(Fill) .height(300) .anchor_bottom(), ) .padding(10) .style(container::dark) ] .spacing(20) .into() } fn inline_code<'a, Renderer>( code: impl text::IntoFragment<'a>, ) -> Element<'a, Message, Theme, Renderer> where Renderer: program::Renderer + 'a, { container(text(code).size(12).font(Font::MONOSPACE)) .style(|_theme| { container::Style::default() .background(Color::BLACK) .border(border::rounded(2)) }) .padding([2, 4]) .into() } ================================================ FILE: devtools/src/time_machine.rs ================================================ use crate::Program; #[cfg(feature = "time-travel")] pub struct TimeMachine

where P: Program, { state: Option, messages: Vec, } #[cfg(feature = "time-travel")] impl

TimeMachine

where P: Program, P::Message: Clone, { pub fn new() -> Self { Self { state: None, messages: Vec::new(), } } pub fn is_rewinding(&self) -> bool { self.state.is_some() } pub fn push(&mut self, message: &P::Message) { self.messages.push(message.clone()); } pub fn rewind(&mut self, program: &P, message: usize) { crate::debug::disable(); let (mut state, _) = program.boot(); if message < self.messages.len() { // TODO: Run concurrently (?) for message in &self.messages[0..message] { let _ = program.update(&mut state, message.clone()); } } self.state = Some(state); } pub fn go_to_present(&mut self) { self.state = None; crate::debug::enable(); } pub fn state(&self) -> Option<&P::State> { self.state.as_ref() } } #[cfg(not(feature = "time-travel"))] pub struct TimeMachine

where P: Program, { _program: std::marker::PhantomData

, } #[cfg(not(feature = "time-travel"))] impl

TimeMachine

where P: Program, { pub fn new() -> Self { Self { _program: std::marker::PhantomData, } } pub fn is_rewinding(&self) -> bool { false } pub fn push(&mut self, _message: &P::Message) {} pub fn rewind(&mut self, _program: &P, _message: usize) {} pub fn go_to_present(&mut self) {} pub fn state(&self) -> Option<&P::State> { None } } ================================================ FILE: docs/redirect.html ================================================ Redirecting...

If you are not redirected automatically, follow this link.

================================================ FILE: docs/release_summary.py ================================================ import requests import os def get_merged_prs(repo, milestone, token): url = f'https://api.github.com/repos/{repo}/pulls' params = { 'state': 'closed', 'per_page': 100, # Number of items per page, adjust as needed } headers = {'Authorization': f'token {token}'} all_prs = [] page = 1 while True: params['page'] = page response = requests.get(url, params=params, headers=headers) response.raise_for_status() prs = response.json() if not prs: break # No more pages all_prs.extend([pr for pr in prs if pr['merged_at'] and (pr['milestone'] or {}).get('title', '') == milestone]) page += 1 return all_prs def categorize_prs(prs): categorized_prs = {'addition': [], 'change': [], 'fix': []} for pr in prs: labels = [label['name'] for label in pr['labels']] if 'addition' in labels or 'feature' in labels: categorized_prs['addition'].append(pr) elif 'fix' in labels or 'bug' in labels: categorized_prs['fix'].append(pr) elif 'change' in labels or 'improvement' in labels: categorized_prs['change'].append(pr) return categorized_prs def get_authors(prs): authors = set() for pr in prs: authors.add(pr['user']['login']) return sorted(authors, key=str.casefold) def main(): repo = 'iced-rs/iced' milestone = '0.12' token = os.environ['GITHUB_TOKEN'] prs = get_merged_prs(repo, milestone, token) categorized_prs = categorize_prs(prs) for category, items in categorized_prs.items(): print(f"### {category.capitalize()}") for pr in items: print(f"- {pr['title']}. [#{pr['number']}](https://github.com/{repo}/pull/{pr['number']})") print("") print("") authors = get_authors(prs) print("Many thanks to...") for author in authors: print(f"- @{author}") if __name__ == "__main__": main() ================================================ FILE: examples/README.md ================================================ # Examples __Iced moves fast and the `master` branch can contain breaking changes!__ If you want to browse examples that are compatible with the latest release, then [switch to the `latest` branch](https://github.com/iced-rs/iced/tree/latest/examples#examples). ## [Tour](tour) A simple UI tour that can run both on native platforms and the web! It showcases different widgets that can be built using Iced. The __[`main`](tour/src/main.rs)__ file contains all the code of the example! All the cross-platform GUI is defined in terms of __state__, __messages__, __update logic__ and __view logic__. [`iced_winit`]: ../winit [`iced_native`]: ../native [`iced_wgpu`]: ../wgpu [`iced_web`]: https://github.com/iced-rs/iced_web [`winit`]: https://github.com/rust-windowing/winit [`wgpu`]: https://github.com/gfx-rs/wgpu You can run the native version with `cargo run`: ``` cargo run --package tour ``` ## [Todos](todos) A todos tracker inspired by [TodoMVC]. It showcases dynamic layout, text input, checkboxes, scrollables, icons, and async actions! It automatically saves your tasks in the background, even if you did not finish typing them. The example code is located in the __[`main`](todos/src/main.rs)__ file. You can run the native version with `cargo run`: ``` cargo run --package todos ``` [TodoMVC]: http://todomvc.com/ ## [Game of Life](game_of_life) An interactive version of the [Game of Life], invented by [John Horton Conway]. It runs a simulation in a background thread while allowing interaction with a `Canvas` that displays an infinite grid with zooming, panning, and drawing support. The relevant code is located in the __[`main`](game_of_life/src/main.rs)__ file.
You can run it with `cargo run`: ``` cargo run --package game_of_life ``` [Game of Life]: https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life [John Horton Conway]: https://en.wikipedia.org/wiki/John_Horton_Conway ## [Styling](styling) An example showcasing custom styling with a light and dark theme. The example code is located in the __[`main`](styling/src/main.rs)__ file.
You can run it with `cargo run`: ``` cargo run --package styling ``` ## Extras A bunch of simpler examples exist: - [`bezier_tool`](bezier_tool), a Paint-like tool for drawing Bézier curves using the `Canvas` widget. - [`clock`](clock), an application that uses the `Canvas` widget to draw a clock and its hands to display the current time. - [`color_palette`](color_palette), a color palette generator based on a user-defined root color. - [`counter`](counter), the classic counter example explained in the [`README`](../README.md). - [`custom_widget`](custom_widget), a demonstration of how to build a custom widget that draws a circle. - [`download_progress`](download_progress), a basic application that asynchronously downloads a dummy file of 100 MB and tracks the download progress. - [`events`](events), a log of native events displayed using a conditional `Subscription`. - [`geometry`](geometry), a custom widget showcasing how to draw geometry with the `Mesh2D` primitive in [`iced_wgpu`](../wgpu). - [`integration`](integration), a demonstration of how to integrate Iced in an existing [`wgpu`] application. - [`pane_grid`](pane_grid), a grid of panes that can be split, resized, and reorganized. - [`pick_list`](pick_list), a dropdown list of selectable options. - [`pokedex`](pokedex), an application that displays a random Pokédex entry (sprite included!) by using the [PokéAPI]. - [`progress_bar`](progress_bar), a simple progress bar that can be filled by using a slider. - [`scrollable`](scrollable), a showcase of various scrollable content configurations. - [`sierpinski_triangle`](sierpinski_triangle), a [sierpiński triangle](https://en.wikipedia.org/wiki/Sierpi%C5%84ski_triangle) Emulator, use `Canvas` and `Slider`. - [`solar_system`](solar_system), an animated solar system drawn using the `Canvas` widget and showcasing how to compose different transforms. - [`stopwatch`](stopwatch), a watch with start/stop and reset buttons showcasing how to listen to time. - [`svg`](svg), an application that renders the [Ghostscript Tiger] by leveraging the `Svg` widget. All of them are packaged in their own crate and, therefore, can be run using `cargo`: ``` cargo run --package ``` [`lyon`]: https://github.com/nical/lyon [PokéAPI]: https://pokeapi.co/ [Ghostscript Tiger]: https://commons.wikimedia.org/wiki/File:Ghostscript_Tiger.svg [`wgpu`]: https://github.com/gfx-rs/wgpu ## [Coffee] Since [Iced was born in May 2019], it has been powering the user interfaces in [Coffee], an experimental 2D game engine.
[Iced was born in May 2019]: https://github.com/hecrj/coffee/pull/35 [`ui` module]: https://docs.rs/coffee/0.3.2/coffee/ui/index.html [Coffee]: https://github.com/hecrj/coffee ================================================ FILE: examples/arc/Cargo.toml ================================================ [package] name = "arc" version = "0.1.0" authors = ["ThatsNoMoon "] edition = "2024" publish = false [dependencies] iced.workspace = true iced.features = ["canvas", "tokio", "debug"] ================================================ FILE: examples/arc/README.md ================================================ ## Arc An application that uses the `Canvas` widget to draw a rotating arc. This is a simple demo for https://github.com/iced-rs/iced/pull/1358. The __[`main`]__ file contains all the code of the example. You can run it with `cargo run`: ``` cargo run --package arc ``` [`main`]: src/main.rs ================================================ FILE: examples/arc/src/main.rs ================================================ use std::{f32::consts::PI, time::Instant}; use iced::mouse; use iced::widget::canvas::{self, Cache, Canvas, Geometry, Path, Stroke, stroke}; use iced::window; use iced::{Element, Fill, Point, Rectangle, Renderer, Subscription, Theme}; pub fn main() -> iced::Result { iced::application(Arc::new, Arc::update, Arc::view) .subscription(Arc::subscription) .theme(Theme::Dark) .run() } struct Arc { start: Instant, cache: Cache, } #[derive(Debug, Clone, Copy)] enum Message { Tick, } impl Arc { fn new() -> Self { Arc { start: Instant::now(), cache: Cache::default(), } } fn update(&mut self, _: Message) { self.cache.clear(); } fn view(&self) -> Element<'_, Message> { Canvas::new(self).width(Fill).height(Fill).into() } fn subscription(&self) -> Subscription { window::frames().map(|_| Message::Tick) } } impl canvas::Program for Arc { type State = (); fn draw( &self, _state: &Self::State, renderer: &Renderer, theme: &Theme, bounds: Rectangle, _cursor: mouse::Cursor, ) -> Vec { let geometry = self.cache.draw(renderer, bounds.size(), |frame| { let palette = theme.seed(); let center = frame.center(); let radius = frame.width().min(frame.height()) / 5.0; let start = Point::new(center.x, center.y - radius); let angle = (self.start.elapsed().as_millis() % 10_000) as f32 / 10_000.0 * 2.0 * PI; let end = Point::new( center.x + radius * angle.cos(), center.y + radius * angle.sin(), ); let circles = Path::new(|b| { b.circle(start, 10.0); b.move_to(end); b.circle(end, 10.0); }); frame.fill(&circles, palette.text); let path = Path::new(|b| { b.move_to(start); b.arc_to(center, end, 50.0); b.line_to(end); }); frame.stroke( &path, Stroke { style: stroke::Style::Solid(palette.text), width: 10.0, ..Stroke::default() }, ); }); vec![geometry] } } ================================================ FILE: examples/bezier_tool/Cargo.toml ================================================ [package] name = "bezier_tool" version = "0.1.0" authors = ["Héctor Ramón Jiménez "] edition = "2024" publish = false [dependencies] iced.workspace = true iced.features = ["canvas", "debug"] ================================================ FILE: examples/bezier_tool/README.md ================================================ ## Bézier tool A Paint-like tool for drawing Bézier curves using the `Canvas` widget. The __[`main`]__ file contains all the code of the example.
You can run it with `cargo run`: ``` cargo run --package bezier_tool ``` [`main`]: src/main.rs ================================================ FILE: examples/bezier_tool/src/main.rs ================================================ //! This example showcases an interactive `Canvas` for drawing Bézier curves. use iced::widget::{button, container, hover, right, space}; use iced::{Element, Theme}; pub fn main() -> iced::Result { iced::application(Example::default, Example::update, Example::view) .theme(Theme::CatppuccinMocha) .run() } #[derive(Default)] struct Example { bezier: bezier::State, curves: Vec, } #[derive(Debug, Clone, Copy)] enum Message { AddCurve(bezier::Curve), Clear, } impl Example { fn update(&mut self, message: Message) { match message { Message::AddCurve(curve) => { self.curves.push(curve); self.bezier.request_redraw(); } Message::Clear => { self.bezier = bezier::State::default(); self.curves.clear(); } } } fn view(&self) -> Element<'_, Message> { container(hover( self.bezier.view(&self.curves).map(Message::AddCurve), if self.curves.is_empty() { container(space::horizontal()) } else { right( button("Clear") .style(button::danger) .on_press(Message::Clear), ) .padding(10) }, )) .padding(20) .into() } } mod bezier { use iced::mouse; use iced::widget::canvas::{self, Canvas, Event, Frame, Geometry, Path, Stroke}; use iced::{Element, Fill, Point, Rectangle, Renderer, Theme}; #[derive(Default)] pub struct State { cache: canvas::Cache, } impl State { pub fn view<'a>(&'a self, curves: &'a [Curve]) -> Element<'a, Curve> { Canvas::new(Bezier { state: self, curves, }) .width(Fill) .height(Fill) .into() } pub fn request_redraw(&mut self) { self.cache.clear(); } } struct Bezier<'a> { state: &'a State, curves: &'a [Curve], } impl canvas::Program for Bezier<'_> { type State = Option; fn update( &self, state: &mut Self::State, event: &Event, bounds: Rectangle, cursor: mouse::Cursor, ) -> Option> { let cursor_position = cursor.position_in(bounds)?; match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => Some( match *state { None => { *state = Some(Pending::One { from: cursor_position, }); canvas::Action::request_redraw() } Some(Pending::One { from }) => { *state = Some(Pending::Two { from, to: cursor_position, }); canvas::Action::request_redraw() } Some(Pending::Two { from, to }) => { *state = None; canvas::Action::publish(Curve { from, to, control: cursor_position, }) } } .and_capture(), ), Event::Mouse(mouse::Event::CursorMoved { .. }) if state.is_some() => { Some(canvas::Action::request_redraw()) } _ => None, } } fn draw( &self, state: &Self::State, renderer: &Renderer, theme: &Theme, bounds: Rectangle, cursor: mouse::Cursor, ) -> Vec { let content = self.state.cache.draw(renderer, bounds.size(), |frame| { Curve::draw_all(self.curves, frame, theme); frame.stroke( &Path::rectangle(Point::ORIGIN, frame.size()), Stroke::default() .with_width(2.0) .with_color(theme.seed().text), ); }); if let Some(pending) = state { vec![content, pending.draw(renderer, theme, bounds, cursor)] } else { vec![content] } } fn mouse_interaction( &self, _state: &Self::State, bounds: Rectangle, cursor: mouse::Cursor, ) -> mouse::Interaction { if cursor.is_over(bounds) { mouse::Interaction::Crosshair } else { mouse::Interaction::default() } } } #[derive(Debug, Clone, Copy)] pub struct Curve { from: Point, to: Point, control: Point, } impl Curve { fn draw_all(curves: &[Curve], frame: &mut Frame, theme: &Theme) { let curves = Path::new(|p| { for curve in curves { p.move_to(curve.from); p.quadratic_curve_to(curve.control, curve.to); } }); frame.stroke( &curves, Stroke::default() .with_width(2.0) .with_color(theme.seed().text), ); } } #[derive(Debug, Clone, Copy)] enum Pending { One { from: Point }, Two { from: Point, to: Point }, } impl Pending { fn draw( &self, renderer: &Renderer, theme: &Theme, bounds: Rectangle, cursor: mouse::Cursor, ) -> Geometry { let mut frame = Frame::new(renderer, bounds.size()); if let Some(cursor_position) = cursor.position_in(bounds) { match *self { Pending::One { from } => { let line = Path::line(from, cursor_position); frame.stroke( &line, Stroke::default() .with_width(2.0) .with_color(theme.seed().text), ); } Pending::Two { from, to } => { let curve = Curve { from, to, control: cursor_position, }; Curve::draw_all(&[curve], &mut frame, theme); } }; } frame.into_geometry() } } } ================================================ FILE: examples/changelog/Cargo.toml ================================================ [package] name = "changelog" version = "0.1.0" authors = ["Héctor Ramón Jiménez "] edition = "2024" publish = false [lints.clippy] large_enum_variant = "allow" [dependencies] iced.workspace = true iced.features = ["tokio", "markdown", "highlighter", "debug"] log.workspace = true thiserror.workspace = true tokio.features = ["fs", "process"] tokio.workspace = true serde.workspace = true serde.features = ["derive"] jiff = "0.2" webbrowser = "1" tracing-subscriber = "0.3" [dependencies.reqwest] version = "0.12" features = ["json"] ================================================ FILE: examples/changelog/src/changelog.rs ================================================ use jiff::Timestamp; use serde::Deserialize; use tokio::fs; use tokio::process; use std::collections::{BTreeMap, BTreeSet}; use std::env; use std::fmt; use std::io; use std::sync::Arc; #[derive(Debug, Clone)] pub struct Changelog { ids: Vec, added: Vec, changed: Vec, fixed: Vec, removed: Vec, authors: Vec, contributions: BTreeMap, } impl Changelog { pub fn new() -> Self { Self { ids: Vec::new(), added: Vec::new(), changed: Vec::new(), fixed: Vec::new(), removed: Vec::new(), authors: Vec::new(), contributions: BTreeMap::new(), } } pub async fn list() -> Result<(Self, Vec), Error> { let mut changelog = Self::new(); { let markdown = fs::read_to_string("CHANGELOG.md").await?; if let Some(unreleased) = markdown.split("\n## ").nth(1) { let sections = unreleased.split("\n\n"); for section in sections { if section.starts_with("Many thanks to...") { for author in section.lines().skip(1) { let author = author.trim_start_matches("- @"); if author.is_empty() { continue; } changelog.authors.push(author.to_owned()); } continue; } let Some((_, rest)) = section.split_once("### ") else { continue; }; let Some((name, rest)) = rest.split_once("\n") else { continue; }; let category = match name { "Added" => Category::Added, "Fixed" => Category::Fixed, "Changed" => Category::Changed, "Removed" => Category::Removed, _ => continue, }; for entry in rest.lines() { let Some((_, id)) = entry.split_once("[#") else { continue; }; let Some((id, _)) = id.split_once(']') else { continue; }; let Ok(id): Result = id.parse() else { continue; }; changelog.ids.push(id); let target = match category { Category::Added => &mut changelog.added, Category::Changed => &mut changelog.changed, Category::Fixed => &mut changelog.fixed, Category::Removed => &mut changelog.removed, }; target.push(entry.to_owned()); } } } } let mut candidates = Contribution::list().await?; for candidate in &candidates { *changelog .contributions .entry(candidate.author.clone()) .or_default() += 1; } for author in &changelog.authors { if !changelog.contributions.contains_key(author) { changelog.contributions.insert(author.clone(), 1); } } for reviewed_entry in changelog.entries() { candidates.retain(|candidate| candidate.id != reviewed_entry); } Ok((changelog, candidates)) } pub async fn save(self) -> Result<(), Error> { let markdown = fs::read_to_string("CHANGELOG.md").await?; let Some((header, rest)) = markdown.split_once("\n## ") else { return Err(Error::InvalidFormat); }; let Some((_unreleased, rest)) = rest.split_once("\n## ") else { return Err(Error::InvalidFormat); }; let unreleased = format!("\n## [Unreleased]\n{self}"); let rest = format!("\n## {rest}"); let changelog = [header, &unreleased, &rest].concat(); fs::write("CHANGELOG.md", changelog).await?; Ok(()) } pub fn len(&self) -> usize { self.ids.len() } pub fn entries(&self) -> impl Iterator + '_ { self.ids.iter().copied() } pub fn push(&mut self, entry: Entry) { self.ids.push(entry.id); let item = format!( "- {title}. [#{id}](https://github.com/iced-rs/iced/pull/{id})", title = entry.title, id = entry.id ); let target = match entry.category { Category::Added => &mut self.added, Category::Changed => &mut self.changed, Category::Fixed => &mut self.fixed, Category::Removed => &mut self.removed, }; target.push(item); let _ = self.contributions.entry(entry.author.clone()).or_default(); if entry.author != "hecrj" && !self.authors.contains(&entry.author) { self.authors.push(entry.author); } self.authors.sort_by(|a, b| { self.contributions .get(a) .copied() .unwrap_or_default() .cmp(&self.contributions.get(b).copied().unwrap_or_default()) .reverse() .then(a.to_lowercase().cmp(&b.to_lowercase())) }); } } impl fmt::Display for Changelog { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn section(category: Category, entries: &[String]) -> String { if entries.is_empty() { return String::new(); } format!("### {category}\n{list}\n", list = entries.join("\n")) } fn thank_you<'a>(authors: impl IntoIterator) -> String { let mut list = String::new(); for author in authors { list.push_str(&format!("- @{author}\n")); } format!("Many thanks to...\n{list}") } let changelog = [ section(Category::Added, &self.added), section(Category::Changed, &self.changed), section(Category::Fixed, &self.fixed), section(Category::Removed, &self.removed), thank_you(self.authors.iter().map(String::as_str)), ] .into_iter() .filter(|section| !section.is_empty()) .collect::>() .join("\n"); f.write_str(&changelog) } } #[derive(Debug, Clone)] pub struct Entry { pub id: u64, pub title: String, pub category: Category, pub author: String, } impl Entry { pub fn new(title: &str, category: Category, pull_request: &PullRequest) -> Option { let title = title.strip_suffix(".").unwrap_or(title); if title.is_empty() { return None; }; Some(Self { id: pull_request.id, title: title.to_owned(), category, author: pull_request.author.clone(), }) } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Category { Added, Changed, Fixed, Removed, } impl Category { pub const ALL: &'static [Self] = &[Self::Added, Self::Changed, Self::Fixed, Self::Removed]; pub fn guess(label: &str) -> Option { Some(match label { "feature" | "addition" => Self::Added, "change" => Self::Changed, "bug" | "fix" => Self::Fixed, _ => None?, }) } } impl fmt::Display for Category { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(match self { Category::Added => "Added", Category::Changed => "Changed", Category::Fixed => "Fixed", Category::Removed => "Removed", }) } } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct Contribution { pub id: u64, pub author: String, } impl Contribution { pub async fn list() -> Result, Error> { let output = process::Command::new("git") .args([ "log", "--oneline", "--grep", "#[0-9]*", "origin/latest..HEAD", ]) .output() .await?; let log = String::from_utf8_lossy(&output.stdout); let mut contributions: Vec<_> = log .lines() .filter(|title| !title.is_empty()) .filter_map(|title| { let (_, pull_request) = title.split_once('#')?; let (pull_request, _) = pull_request.split_once([')', ' '])?; let (author, _) = title.split_once('/').unwrap_or_default(); let (_, author) = author.rsplit_once(' ').unwrap_or_default(); Some(Contribution { id: pull_request.parse().ok()?, author: author.to_owned(), }) }) .collect(); let mut unique = BTreeSet::from_iter(contributions.clone()); contributions.retain_mut(|contribution| unique.remove(contribution)); Ok(contributions) } } #[derive(Debug, Clone)] pub struct PullRequest { pub id: u64, pub title: String, pub description: Option, pub labels: Vec, pub author: String, pub created_at: Timestamp, } impl PullRequest { pub async fn fetch(contribution: Contribution) -> Result { let request = reqwest::Client::new() .request( reqwest::Method::GET, format!( "https://api.github.com/repos/iced-rs/iced/pulls/{}", contribution.id ), ) .header("User-Agent", "iced changelog generator") .header( "Authorization", format!( "Bearer {}", env::var("GITHUB_TOKEN").map_err(|_| Error::GitHubTokenNotFound)? ), ); #[derive(Deserialize)] struct Schema { title: String, body: Option, user: User, labels: Vec