diff options
author | 2023-05-11 16:45:08 +0200 | |
---|---|---|
committer | 2023-05-11 16:45:08 +0200 | |
commit | 669f7cc74b2e7918e86a8197916f503f2d3d9b93 (patch) | |
tree | acb365358235be6ce115b50db9404d890b6e77a6 | |
parent | bc62013b6cde52174bf4c4286939cf170bfa7760 (diff) | |
parent | 63d3fc6996b848e10e77e6924bfebdf6ba82852e (diff) | |
download | iced-669f7cc74b2e7918e86a8197916f503f2d3d9b93.tar.gz iced-669f7cc74b2e7918e86a8197916f503f2d3d9b93.tar.bz2 iced-669f7cc74b2e7918e86a8197916f503f2d3d9b93.zip |
Merge pull request #1830 from iced-rs/advanced-text
Advanced text
-rw-r--r-- | .github/workflows/test.yml | 4 | ||||
-rw-r--r-- | Cargo.toml | 49 | ||||
-rw-r--r-- | README.md | 36 | ||||
-rw-r--r-- | core/Cargo.toml | 2 | ||||
-rw-r--r-- | core/src/clipboard.rs | 23 | ||||
-rw-r--r-- | core/src/element.rs (renamed from native/src/element.rs) | 67 | ||||
-rw-r--r-- | core/src/event.rs (renamed from native/src/event.rs) | 2 | ||||
-rw-r--r-- | core/src/font.rs | 111 | ||||
-rw-r--r-- | core/src/gradient.rs (renamed from graphics/src/gradient.rs) | 0 | ||||
-rw-r--r-- | core/src/gradient/linear.rs (renamed from graphics/src/gradient/linear.rs) | 0 | ||||
-rw-r--r-- | core/src/hasher.rs (renamed from native/src/hasher.rs) | 0 | ||||
-rw-r--r-- | core/src/image.rs (renamed from native/src/image.rs) | 12 | ||||
-rw-r--r-- | core/src/layout.rs (renamed from native/src/layout.rs) | 0 | ||||
-rw-r--r-- | core/src/layout/DRUID_LICENSE (renamed from native/src/layout/DRUID_LICENSE) | 0 | ||||
-rw-r--r-- | core/src/layout/flex.rs (renamed from native/src/layout/flex.rs) | 0 | ||||
-rw-r--r-- | core/src/layout/limits.rs (renamed from native/src/layout/limits.rs) | 0 | ||||
-rw-r--r-- | core/src/layout/node.rs (renamed from native/src/layout/node.rs) | 0 | ||||
-rw-r--r-- | core/src/length.rs | 8 | ||||
-rw-r--r-- | core/src/lib.rs | 28 | ||||
-rw-r--r-- | core/src/mouse.rs | 3 | ||||
-rw-r--r-- | core/src/mouse/click.rs (renamed from native/src/mouse/click.rs) | 0 | ||||
-rw-r--r-- | core/src/overlay.rs (renamed from native/src/overlay.rs) | 3 | ||||
-rw-r--r-- | core/src/overlay/element.rs (renamed from native/src/overlay/element.rs) | 0 | ||||
-rw-r--r-- | core/src/overlay/group.rs (renamed from native/src/overlay/group.rs) | 4 | ||||
-rw-r--r-- | core/src/pixels.rs | 6 | ||||
-rw-r--r-- | core/src/rectangle.rs | 48 | ||||
-rw-r--r-- | core/src/renderer.rs (renamed from native/src/renderer.rs) | 1 | ||||
-rw-r--r-- | core/src/renderer/null.rs (renamed from native/src/renderer/null.rs) | 20 | ||||
-rw-r--r-- | core/src/shell.rs (renamed from native/src/shell.rs) | 0 | ||||
-rw-r--r-- | core/src/size.rs | 2 | ||||
-rw-r--r-- | core/src/svg.rs (renamed from native/src/svg.rs) | 4 | ||||
-rw-r--r-- | core/src/text.rs | 212 | ||||
-rw-r--r-- | core/src/touch.rs (renamed from native/src/touch.rs) | 0 | ||||
-rw-r--r-- | core/src/widget.rs (renamed from native/src/widget.rs) | 90 | ||||
-rw-r--r-- | core/src/widget/id.rs (renamed from native/src/widget/id.rs) | 2 | ||||
-rw-r--r-- | core/src/widget/operation.rs | 226 | ||||
-rw-r--r-- | core/src/widget/operation/focusable.rs (renamed from native/src/widget/operation/focusable.rs) | 0 | ||||
-rw-r--r-- | core/src/widget/operation/scrollable.rs (renamed from native/src/widget/operation/scrollable.rs) | 0 | ||||
-rw-r--r-- | core/src/widget/operation/text_input.rs (renamed from native/src/widget/operation/text_input.rs) | 0 | ||||
-rw-r--r-- | core/src/widget/text.rs (renamed from native/src/widget/text.rs) | 110 | ||||
-rw-r--r-- | core/src/widget/tree.rs (renamed from native/src/widget/tree.rs) | 0 | ||||
-rw-r--r-- | core/src/window.rs | 13 | ||||
-rw-r--r-- | core/src/window/event.rs (renamed from native/src/window/event.rs) | 0 | ||||
-rw-r--r-- | core/src/window/icon.rs (renamed from native/src/window/icon.rs) | 0 | ||||
-rw-r--r-- | core/src/window/mode.rs (renamed from native/src/window/mode.rs) | 0 | ||||
-rw-r--r-- | core/src/window/redraw_request.rs (renamed from native/src/window/redraw_request.rs) | 0 | ||||
-rw-r--r-- | core/src/window/user_attention.rs (renamed from native/src/window/user_attention.rs) | 0 | ||||
-rw-r--r-- | examples/README.md | 3 | ||||
-rw-r--r-- | examples/arc/src/main.rs | 7 | ||||
-rw-r--r-- | examples/bezier_tool/src/main.rs | 23 | ||||
-rw-r--r-- | examples/checkbox/fonts/icons.ttf | bin | 1272 -> 1784 bytes | |||
-rw-r--r-- | examples/checkbox/src/main.rs | 32 | ||||
-rw-r--r-- | examples/clock/src/main.rs | 9 | ||||
-rw-r--r-- | examples/color_palette/src/main.rs | 7 | ||||
-rw-r--r-- | examples/component/Cargo.toml | 4 | ||||
-rw-r--r-- | examples/component/src/main.rs | 22 | ||||
-rw-r--r-- | examples/custom_quad/Cargo.toml | 3 | ||||
-rw-r--r-- | examples/custom_quad/src/main.rs | 8 | ||||
-rw-r--r-- | examples/custom_widget/Cargo.toml | 3 | ||||
-rw-r--r-- | examples/custom_widget/src/main.rs | 8 | ||||
-rw-r--r-- | examples/download_progress/Cargo.toml | 2 | ||||
-rw-r--r-- | examples/download_progress/src/download.rs | 2 | ||||
-rw-r--r-- | examples/events/Cargo.toml | 1 | ||||
-rw-r--r-- | examples/events/src/main.rs | 9 | ||||
-rw-r--r-- | examples/game_of_life/src/main.rs | 73 | ||||
-rw-r--r-- | examples/geometry/Cargo.toml | 3 | ||||
-rw-r--r-- | examples/geometry/src/main.rs | 44 | ||||
-rw-r--r-- | examples/integration/.gitignore (renamed from examples/integration_wgpu/.gitignore) | 0 | ||||
-rw-r--r-- | examples/integration/Cargo.toml (renamed from examples/integration_wgpu/Cargo.toml) | 6 | ||||
-rw-r--r-- | examples/integration/README.md (renamed from examples/integration_wgpu/README.md) | 0 | ||||
-rw-r--r-- | examples/integration/index.html (renamed from examples/integration_wgpu/index.html) | 4 | ||||
-rw-r--r-- | examples/integration/src/controls.rs (renamed from examples/integration_wgpu/src/controls.rs) | 10 | ||||
-rw-r--r-- | examples/integration/src/main.rs (renamed from examples/integration_wgpu/src/main.rs) | 122 | ||||
-rw-r--r-- | examples/integration/src/scene.rs (renamed from examples/integration_wgpu/src/scene.rs) | 2 | ||||
-rw-r--r-- | examples/integration/src/shader/frag.wgsl (renamed from examples/integration_wgpu/src/shader/frag.wgsl) | 0 | ||||
-rw-r--r-- | examples/integration/src/shader/vert.wgsl (renamed from examples/integration_wgpu/src/shader/vert.wgsl) | 0 | ||||
-rw-r--r-- | examples/integration_opengl/Cargo.toml | 12 | ||||
-rw-r--r-- | examples/integration_opengl/README.md | 16 | ||||
-rw-r--r-- | examples/integration_opengl/src/controls.rs | 101 | ||||
-rw-r--r-- | examples/integration_opengl/src/main.rs | 187 | ||||
-rw-r--r-- | examples/integration_opengl/src/scene.rs | 102 | ||||
-rw-r--r-- | examples/lazy/Cargo.toml | 3 | ||||
-rw-r--r-- | examples/lazy/src/main.rs | 3 | ||||
-rw-r--r-- | examples/modal/Cargo.toml | 3 | ||||
-rw-r--r-- | examples/modal/src/main.rs | 44 | ||||
-rw-r--r-- | examples/modern_art/src/main.rs | 5 | ||||
-rw-r--r-- | examples/multitouch/src/main.rs | 7 | ||||
-rw-r--r-- | examples/pane_grid/Cargo.toml | 4 | ||||
-rw-r--r-- | examples/pane_grid/src/main.rs | 8 | ||||
-rw-r--r-- | examples/sierpinski_triangle/src/main.rs | 7 | ||||
-rw-r--r-- | examples/solar_system/src/main.rs | 26 | ||||
-rw-r--r-- | examples/toast/Cargo.toml | 3 | ||||
-rw-r--r-- | examples/toast/src/main.rs | 24 | ||||
-rw-r--r-- | examples/todos/fonts/icons.ttf | bin | 5596 -> 5732 bytes | |||
-rw-r--r-- | examples/todos/src/main.rs | 36 | ||||
-rw-r--r-- | examples/tour/fonts/icons.ttf | bin | 1612 -> 0 bytes | |||
-rw-r--r-- | examples/tour/src/main.rs | 11 | ||||
-rw-r--r-- | examples/url_handler/Cargo.toml | 1 | ||||
-rw-r--r-- | examples/url_handler/src/main.rs | 10 | ||||
-rw-r--r-- | examples/websocket/Cargo.toml | 2 | ||||
-rw-r--r-- | examples/websocket/src/echo.rs | 4 | ||||
-rw-r--r-- | examples/websocket/src/echo/server.rs | 2 | ||||
-rw-r--r-- | futures/Cargo.toml | 4 | ||||
-rw-r--r-- | futures/src/backend/native/async_std.rs | 14 | ||||
-rw-r--r-- | futures/src/backend/native/smol.rs | 14 | ||||
-rw-r--r-- | futures/src/backend/native/tokio.rs | 14 | ||||
-rw-r--r-- | futures/src/backend/wasm/wasm_bindgen.rs | 14 | ||||
-rw-r--r-- | futures/src/command.rs | 70 | ||||
-rw-r--r-- | futures/src/lib.rs | 3 | ||||
-rw-r--r-- | futures/src/runtime.rs | 25 | ||||
-rw-r--r-- | futures/src/subscription.rs | 349 | ||||
-rw-r--r-- | futures/src/subscription/tracker.rs | 51 | ||||
-rw-r--r-- | glow/Cargo.toml | 51 | ||||
-rw-r--r-- | glow/README.md | 51 | ||||
-rw-r--r-- | glow/src/backend.rs | 280 | ||||
-rw-r--r-- | glow/src/image.rs | 254 | ||||
-rw-r--r-- | glow/src/image/storage.rs | 78 | ||||
-rw-r--r-- | glow/src/lib.rs | 53 | ||||
-rw-r--r-- | glow/src/program.rs | 133 | ||||
-rw-r--r-- | glow/src/quad.rs | 74 | ||||
-rw-r--r-- | glow/src/quad/compatibility.rs | 349 | ||||
-rw-r--r-- | glow/src/quad/core.rs | 244 | ||||
-rw-r--r-- | glow/src/settings.rs | 61 | ||||
-rw-r--r-- | glow/src/shader/common/gradient.frag | 59 | ||||
-rw-r--r-- | glow/src/shader/common/gradient.vert | 9 | ||||
-rw-r--r-- | glow/src/shader/common/image.frag | 22 | ||||
-rw-r--r-- | glow/src/shader/common/image.vert | 9 | ||||
-rw-r--r-- | glow/src/shader/common/solid.frag | 18 | ||||
-rw-r--r-- | glow/src/shader/common/solid.vert | 11 | ||||
-rw-r--r-- | glow/src/shader/compatibility/quad.frag | 83 | ||||
-rw-r--r-- | glow/src/shader/compatibility/quad.vert | 46 | ||||
-rw-r--r-- | glow/src/shader/core/quad.frag | 95 | ||||
-rw-r--r-- | glow/src/shader/core/quad.vert | 52 | ||||
-rw-r--r-- | glow/src/text.rs | 257 | ||||
-rw-r--r-- | glow/src/triangle.rs | 595 | ||||
-rw-r--r-- | glow/src/window.rs | 4 | ||||
-rw-r--r-- | glow/src/window/compositor.rs | 111 | ||||
-rw-r--r-- | glutin/Cargo.toml | 42 | ||||
-rw-r--r-- | glutin/README.md | 29 | ||||
-rw-r--r-- | glutin/src/application.rs | 508 | ||||
-rw-r--r-- | glutin/src/lib.rs | 33 | ||||
-rw-r--r-- | graphics/Cargo.toml | 56 | ||||
-rw-r--r-- | graphics/fonts/Lato-Regular.ttf | bin | 75136 -> 0 bytes | |||
-rw-r--r-- | graphics/fonts/OFL.txt | 93 | ||||
-rw-r--r-- | graphics/src/backend.rs | 20 | ||||
-rw-r--r-- | graphics/src/compositor.rs (renamed from graphics/src/window/compositor.rs) | 8 | ||||
-rw-r--r-- | graphics/src/damage.rs | 147 | ||||
-rw-r--r-- | graphics/src/font.rs | 35 | ||||
-rw-r--r-- | graphics/src/font/source.rs | 45 | ||||
-rw-r--r-- | graphics/src/geometry.rs | 33 | ||||
-rw-r--r-- | graphics/src/geometry/fill.rs (renamed from graphics/src/widget/canvas/fill.rs) | 19 | ||||
-rw-r--r-- | graphics/src/geometry/path.rs (renamed from graphics/src/widget/canvas/path.rs) | 54 | ||||
-rw-r--r-- | graphics/src/geometry/path/arc.rs (renamed from graphics/src/widget/canvas/path/arc.rs) | 2 | ||||
-rw-r--r-- | graphics/src/geometry/path/builder.rs (renamed from graphics/src/widget/canvas/path/builder.rs) | 27 | ||||
-rw-r--r-- | graphics/src/geometry/stroke.rs (renamed from graphics/src/widget/canvas/stroke.rs) | 24 | ||||
-rw-r--r-- | graphics/src/geometry/style.rs (renamed from graphics/src/widget/canvas/style.rs) | 2 | ||||
-rw-r--r-- | graphics/src/geometry/text.rs (renamed from graphics/src/widget/canvas/text.rs) | 13 | ||||
-rw-r--r-- | graphics/src/image.rs | 99 | ||||
-rw-r--r-- | graphics/src/image/raster.rs | 242 | ||||
-rw-r--r-- | graphics/src/image/storage.rs | 2 | ||||
-rw-r--r-- | graphics/src/lib.rs | 30 | ||||
-rw-r--r-- | graphics/src/overlay/menu.rs | 3 | ||||
-rw-r--r-- | graphics/src/primitive.rs | 220 | ||||
-rw-r--r-- | graphics/src/renderer.rs | 98 | ||||
-rw-r--r-- | graphics/src/triangle.rs | 32 | ||||
-rw-r--r-- | graphics/src/viewport.rs | 4 | ||||
-rw-r--r-- | graphics/src/widget/canvas/geometry.rs | 24 | ||||
-rw-r--r-- | graphics/src/window.rs | 10 | ||||
-rw-r--r-- | graphics/src/window/gl_compositor.rs | 71 | ||||
-rw-r--r-- | lazy/Cargo.toml | 18 | ||||
-rw-r--r-- | native/README.md | 37 | ||||
-rw-r--r-- | native/src/mouse.rs | 6 | ||||
-rw-r--r-- | native/src/runtime.rs | 18 | ||||
-rw-r--r-- | native/src/subscription.rs | 283 | ||||
-rw-r--r-- | native/src/text.rs | 114 | ||||
-rw-r--r-- | native/src/widget/action.rs | 154 | ||||
-rw-r--r-- | native/src/widget/helpers.rs | 326 | ||||
-rw-r--r-- | native/src/widget/operation.rs | 112 | ||||
-rw-r--r-- | native/src/window.rs | 33 | ||||
-rw-r--r-- | renderer/Cargo.toml | 28 | ||||
-rw-r--r-- | renderer/src/backend.rs | 106 | ||||
-rw-r--r-- | renderer/src/compositor.rs | 214 | ||||
-rw-r--r-- | renderer/src/geometry.rs | 174 | ||||
-rw-r--r-- | renderer/src/geometry/cache.rs (renamed from graphics/src/widget/canvas/cache.rs) | 56 | ||||
-rw-r--r-- | renderer/src/lib.rs | 19 | ||||
-rw-r--r-- | renderer/src/settings.rs | 31 | ||||
-rw-r--r-- | renderer/src/widget.rs (renamed from graphics/src/widget.rs) | 5 | ||||
-rw-r--r-- | runtime/Cargo.toml (renamed from native/Cargo.toml) | 11 | ||||
-rw-r--r-- | runtime/README.md | 18 | ||||
-rw-r--r-- | runtime/src/clipboard.rs (renamed from native/src/clipboard.rs) | 37 | ||||
-rw-r--r-- | runtime/src/command.rs (renamed from native/src/command.rs) | 55 | ||||
-rw-r--r-- | runtime/src/command/action.rs (renamed from native/src/command/action.rs) | 24 | ||||
-rw-r--r-- | runtime/src/debug/basic.rs (renamed from native/src/debug/basic.rs) | 2 | ||||
-rw-r--r-- | runtime/src/debug/null.rs (renamed from native/src/debug/null.rs) | 0 | ||||
-rw-r--r-- | runtime/src/font.rs | 19 | ||||
-rw-r--r-- | runtime/src/keyboard.rs (renamed from native/src/keyboard.rs) | 0 | ||||
-rw-r--r-- | runtime/src/lib.rs (renamed from native/src/lib.rs) | 45 | ||||
-rw-r--r-- | runtime/src/program.rs (renamed from native/src/program.rs) | 7 | ||||
-rw-r--r-- | runtime/src/program/state.rs (renamed from native/src/program/state.rs) | 18 | ||||
-rw-r--r-- | runtime/src/system.rs (renamed from native/src/system.rs) | 0 | ||||
-rw-r--r-- | runtime/src/system/action.rs (renamed from native/src/system/action.rs) | 0 | ||||
-rw-r--r-- | runtime/src/system/information.rs (renamed from native/src/system/information.rs) | 0 | ||||
-rw-r--r-- | runtime/src/user_interface.rs (renamed from native/src/user_interface.rs) | 89 | ||||
-rw-r--r-- | runtime/src/window.rs (renamed from winit/src/window.rs) | 74 | ||||
-rw-r--r-- | runtime/src/window/action.rs (renamed from native/src/window/action.rs) | 4 | ||||
-rw-r--r-- | src/advanced.rs | 16 | ||||
-rw-r--r-- | src/application.rs | 15 | ||||
-rw-r--r-- | src/clipboard.rs | 3 | ||||
-rw-r--r-- | src/element.rs | 5 | ||||
-rw-r--r-- | src/error.rs | 16 | ||||
-rw-r--r-- | src/executor.rs | 14 | ||||
-rw-r--r-- | src/keyboard.rs | 2 | ||||
-rw-r--r-- | src/lib.rs | 143 | ||||
-rw-r--r-- | src/mouse.rs | 2 | ||||
-rw-r--r-- | src/overlay.rs | 18 | ||||
-rw-r--r-- | src/result.rs | 6 | ||||
-rw-r--r-- | src/settings.rs | 32 | ||||
-rw-r--r-- | src/touch.rs | 2 | ||||
-rw-r--r-- | src/widget.rs | 246 | ||||
-rw-r--r-- | src/window.rs | 1 | ||||
-rw-r--r-- | src/window/icon.rs | 4 | ||||
-rw-r--r-- | style/src/lib.rs | 3 | ||||
-rw-r--r-- | style/src/text.rs | 20 | ||||
-rw-r--r-- | style/src/theme.rs | 2 | ||||
-rw-r--r-- | tiny_skia/Cargo.toml | 39 | ||||
-rw-r--r-- | tiny_skia/src/backend.rs | 698 | ||||
-rw-r--r-- | tiny_skia/src/geometry.rs | 300 | ||||
-rw-r--r-- | tiny_skia/src/lib.rs | 26 | ||||
-rw-r--r-- | tiny_skia/src/raster.rs | 116 | ||||
-rw-r--r-- | tiny_skia/src/settings.rs | 24 | ||||
-rw-r--r-- | tiny_skia/src/text.rs | 463 | ||||
-rw-r--r-- | tiny_skia/src/vector.rs | 183 | ||||
-rw-r--r-- | tiny_skia/src/window.rs | 3 | ||||
-rw-r--r-- | tiny_skia/src/window/compositor.rs | 158 | ||||
-rw-r--r-- | wgpu/Cargo.toml | 58 | ||||
-rw-r--r-- | wgpu/fonts/Iced-Icons.ttf (renamed from graphics/fonts/Icons.ttf) | bin | 5032 -> 5108 bytes | |||
-rw-r--r-- | wgpu/src/backend.rs | 415 | ||||
-rw-r--r-- | wgpu/src/buffer.rs | 86 | ||||
-rw-r--r-- | wgpu/src/buffer/dynamic.rs | 23 | ||||
-rw-r--r-- | wgpu/src/buffer/static.rs | 32 | ||||
-rw-r--r-- | wgpu/src/geometry.rs (renamed from graphics/src/widget/canvas/frame.rs) | 169 | ||||
-rw-r--r-- | wgpu/src/image.rs | 283 | ||||
-rw-r--r-- | wgpu/src/image/atlas.rs | 215 | ||||
-rw-r--r-- | wgpu/src/image/atlas/allocation.rs | 3 | ||||
-rw-r--r-- | wgpu/src/image/atlas/allocator.rs | 4 | ||||
-rw-r--r-- | wgpu/src/image/atlas/entry.rs | 9 | ||||
-rw-r--r-- | wgpu/src/image/raster.rs | 121 | ||||
-rw-r--r-- | wgpu/src/image/vector.rs (renamed from graphics/src/image/vector.rs) | 50 | ||||
-rw-r--r-- | wgpu/src/layer.rs (renamed from graphics/src/layer.rs) | 96 | ||||
-rw-r--r-- | wgpu/src/layer/image.rs (renamed from graphics/src/layer/image.rs) | 6 | ||||
-rw-r--r-- | wgpu/src/layer/mesh.rs (renamed from graphics/src/layer/mesh.rs) | 8 | ||||
-rw-r--r-- | wgpu/src/layer/quad.rs (renamed from graphics/src/layer/quad.rs) | 0 | ||||
-rw-r--r-- | wgpu/src/layer/text.rs (renamed from graphics/src/layer/text.rs) | 14 | ||||
-rw-r--r-- | wgpu/src/lib.rs | 16 | ||||
-rw-r--r-- | wgpu/src/quad.rs | 233 | ||||
-rw-r--r-- | wgpu/src/settings.rs | 55 | ||||
-rw-r--r-- | wgpu/src/text.rs | 596 | ||||
-rw-r--r-- | wgpu/src/triangle.rs | 655 | ||||
-rw-r--r-- | wgpu/src/triangle/msaa.rs | 8 | ||||
-rw-r--r-- | wgpu/src/window.rs | 3 | ||||
-rw-r--r-- | wgpu/src/window/compositor.rs | 208 | ||||
-rw-r--r-- | widget/Cargo.toml | 37 | ||||
-rw-r--r-- | widget/src/button.rs (renamed from native/src/widget/button.rs) | 36 | ||||
-rw-r--r-- | widget/src/canvas.rs (renamed from graphics/src/widget/canvas.rs) | 132 | ||||
-rw-r--r-- | widget/src/canvas/cursor.rs (renamed from graphics/src/widget/canvas/cursor.rs) | 2 | ||||
-rw-r--r-- | widget/src/canvas/event.rs (renamed from graphics/src/widget/canvas/event.rs) | 8 | ||||
-rw-r--r-- | widget/src/canvas/program.rs (renamed from graphics/src/widget/canvas/program.rs) | 27 | ||||
-rw-r--r-- | widget/src/checkbox.rs (renamed from native/src/widget/checkbox.rs) | 89 | ||||
-rw-r--r-- | widget/src/column.rs (renamed from native/src/widget/column.rs) | 20 | ||||
-rw-r--r-- | widget/src/container.rs (renamed from native/src/widget/container.rs) | 28 | ||||
-rw-r--r-- | widget/src/helpers.rs | 371 | ||||
-rw-r--r-- | widget/src/image.rs (renamed from native/src/widget/image.rs) | 17 | ||||
-rw-r--r-- | widget/src/image/viewer.rs (renamed from native/src/widget/image/viewer.rs) | 14 | ||||
-rw-r--r-- | widget/src/lazy.rs (renamed from lazy/src/lazy.rs) | 50 | ||||
-rw-r--r-- | widget/src/lazy/cache.rs (renamed from lazy/src/cache.rs) | 4 | ||||
-rw-r--r-- | widget/src/lazy/component.rs (renamed from lazy/src/component.rs) | 34 | ||||
-rw-r--r-- | widget/src/lazy/helpers.rs (renamed from lazy/src/lib.rs) | 45 | ||||
-rw-r--r-- | widget/src/lazy/responsive.rs (renamed from lazy/src/responsive.rs) | 40 | ||||
-rw-r--r-- | widget/src/lib.rs | 125 | ||||
-rw-r--r-- | widget/src/mouse_area.rs (renamed from native/src/widget/mouse_area.rs) | 20 | ||||
-rw-r--r-- | widget/src/overlay.rs (renamed from graphics/src/overlay.rs) | 0 | ||||
-rw-r--r-- | widget/src/overlay/menu.rs (renamed from native/src/overlay/menu.rs) | 105 | ||||
-rw-r--r-- | widget/src/pane_grid.rs (renamed from native/src/widget/pane_grid.rs) | 42 | ||||
-rw-r--r-- | widget/src/pane_grid/axis.rs (renamed from native/src/widget/pane_grid/axis.rs) | 2 | ||||
-rw-r--r-- | widget/src/pane_grid/configuration.rs (renamed from native/src/widget/pane_grid/configuration.rs) | 2 | ||||
-rw-r--r-- | widget/src/pane_grid/content.rs (renamed from native/src/widget/pane_grid/content.rs) | 30 | ||||
-rw-r--r-- | widget/src/pane_grid/direction.rs (renamed from native/src/widget/pane_grid/direction.rs) | 0 | ||||
-rw-r--r-- | widget/src/pane_grid/draggable.rs (renamed from native/src/widget/pane_grid/draggable.rs) | 2 | ||||
-rw-r--r-- | widget/src/pane_grid/node.rs (renamed from native/src/widget/pane_grid/node.rs) | 4 | ||||
-rw-r--r-- | widget/src/pane_grid/pane.rs (renamed from native/src/widget/pane_grid/pane.rs) | 0 | ||||
-rw-r--r-- | widget/src/pane_grid/split.rs (renamed from native/src/widget/pane_grid/split.rs) | 0 | ||||
-rw-r--r-- | widget/src/pane_grid/state.rs (renamed from native/src/widget/pane_grid/state.rs) | 6 | ||||
-rw-r--r-- | widget/src/pane_grid/title_bar.rs (renamed from native/src/widget/pane_grid/title_bar.rs) | 24 | ||||
-rw-r--r-- | widget/src/pick_list.rs (renamed from native/src/widget/pick_list.rs) | 152 | ||||
-rw-r--r-- | widget/src/progress_bar.rs (renamed from native/src/widget/progress_bar.rs) | 26 | ||||
-rw-r--r-- | widget/src/qr_code.rs (renamed from graphics/src/widget/qr_code.rs) | 100 | ||||
-rw-r--r-- | widget/src/radio.rs (renamed from native/src/widget/radio.rs) | 79 | ||||
-rw-r--r-- | widget/src/row.rs (renamed from native/src/widget/row.rs) | 20 | ||||
-rw-r--r-- | widget/src/rule.rs (renamed from native/src/widget/rule.rs) | 20 | ||||
-rw-r--r-- | widget/src/scrollable.rs (renamed from native/src/widget/scrollable.rs) | 168 | ||||
-rw-r--r-- | widget/src/slider.rs (renamed from native/src/widget/slider.rs) | 34 | ||||
-rw-r--r-- | widget/src/space.rs (renamed from native/src/widget/space.rs) | 13 | ||||
-rw-r--r-- | widget/src/svg.rs (renamed from native/src/widget/svg.rs) | 14 | ||||
-rw-r--r-- | widget/src/text.rs | 6 | ||||
-rw-r--r-- | widget/src/text_input.rs (renamed from native/src/widget/text_input.rs) | 145 | ||||
-rw-r--r-- | widget/src/text_input/cursor.rs (renamed from native/src/widget/text_input/cursor.rs) | 2 | ||||
-rw-r--r-- | widget/src/text_input/editor.rs (renamed from native/src/widget/text_input/editor.rs) | 2 | ||||
-rw-r--r-- | widget/src/text_input/value.rs (renamed from native/src/widget/text_input/value.rs) | 0 | ||||
-rw-r--r-- | widget/src/toggler.rs (renamed from native/src/widget/toggler.rs) | 69 | ||||
-rw-r--r-- | widget/src/tooltip.rs (renamed from native/src/widget/tooltip.rs) | 37 | ||||
-rw-r--r-- | widget/src/vertical_slider.rs (renamed from native/src/widget/vertical_slider.rs) | 39 | ||||
-rw-r--r-- | winit/Cargo.toml | 14 | ||||
-rw-r--r-- | winit/src/application.rs | 116 | ||||
-rw-r--r-- | winit/src/application/state.rs | 14 | ||||
-rw-r--r-- | winit/src/clipboard.rs | 17 | ||||
-rw-r--r-- | winit/src/conversion.rs | 11 | ||||
-rw-r--r-- | winit/src/error.rs | 7 | ||||
-rw-r--r-- | winit/src/lib.rs | 9 | ||||
-rw-r--r-- | winit/src/proxy.rs | 2 | ||||
-rw-r--r-- | winit/src/settings.rs | 11 | ||||
-rw-r--r-- | winit/src/system.rs | 7 |
321 files changed, 8696 insertions, 9513 deletions
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 38b81842..a9a9b3f9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -37,5 +37,5 @@ jobs: run: cargo build --package tour --target wasm32-unknown-unknown - name: Check compilation of `todos` example run: cargo build --package todos --target wasm32-unknown-unknown - - name: Check compilation of `integration_wgpu` example - run: cargo build --package integration_wgpu --target wasm32-unknown-unknown + - name: Check compilation of `integration` example + run: cargo build --package integration --target wasm32-unknown-unknown @@ -13,20 +13,18 @@ categories = ["gui"] [features] default = ["wgpu"] +# Enable the `wgpu` GPU-accelerated renderer backend +wgpu = ["iced_renderer/wgpu"] # Enables the `Image` widget -image = ["iced_wgpu?/image", "iced_glow?/image", "image_rs"] +image = ["iced_widget/image", "image_rs"] # Enables the `Svg` widget -svg = ["iced_wgpu?/svg", "iced_glow?/svg"] +svg = ["iced_widget/svg"] # Enables the `Canvas` widget -canvas = ["iced_graphics/canvas"] +canvas = ["iced_widget/canvas"] # Enables the `QRCode` widget -qr_code = ["iced_graphics/qr_code"] -# Enables the `iced_wgpu` renderer -wgpu = ["iced_wgpu"] -# Enables using system fonts -default_system_font = ["iced_wgpu?/default_system_font", "iced_glow?/default_system_font"] -# Enables the `iced_glow` renderer. Overrides `iced_wgpu` -glow = ["iced_glow", "iced_glutin"] +qr_code = ["iced_widget/qr_code"] +# Enables lazy widgets +lazy = ["iced_widget/lazy"] # Enables a debug view in native platforms (press F12) debug = ["iced_winit/debug"] # Enables `tokio` as the `executor::Default` on native platforms @@ -39,13 +37,8 @@ smol = ["iced_futures/smol"] palette = ["iced_core/palette"] # Enables querying system information system = ["iced_winit/system"] -# Enables chrome traces -chrome-trace = [ - "iced_winit/chrome-trace", - "iced_glutin?/trace", - "iced_wgpu?/tracing", - "iced_glow?/tracing", -] +# Enables the advanced module +advanced = [] [badges] maintenance = { status = "actively-developed" } @@ -55,12 +48,12 @@ members = [ "core", "futures", "graphics", - "glow", - "glutin", - "lazy", - "native", + "runtime", + "renderer", "style", + "tiny_skia", "wgpu", + "widget", "winit", "examples/*", ] @@ -68,24 +61,16 @@ members = [ [dependencies] iced_core = { version = "0.9", path = "core" } iced_futures = { version = "0.6", path = "futures" } -iced_native = { version = "0.10", path = "native" } -iced_graphics = { version = "0.8", path = "graphics" } +iced_renderer = { version = "0.1", path = "renderer" } +iced_widget = { version = "0.1", path = "widget" } iced_winit = { version = "0.9", path = "winit", features = ["application"] } -iced_glutin = { version = "0.8", path = "glutin", optional = true } -iced_glow = { version = "0.8", path = "glow", optional = true } -thiserror = "1.0" +thiserror = "1" [dependencies.image_rs] version = "0.24" package = "image" optional = true -[target.'cfg(not(target_arch = "wasm32"))'.dependencies] -iced_wgpu = { version = "0.10", path = "wgpu", optional = true } - -[target.'cfg(target_arch = "wasm32")'.dependencies] -iced_wgpu = { version = "0.10", path = "wgpu", features = ["webgl"], optional = true } - [package.metadata.docs.rs] rustdoc-args = ["--cfg", "docsrs"] features = ["image", "svg", "canvas", "qr_code"] @@ -35,9 +35,9 @@ Inspired by [Elm]. * 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 [`glow`] + * Two [built-in renderers] leveraging [`wgpu`] and [`tiny-skia`] * [`iced_wgpu`] supporting Vulkan, Metal and DX12 - * [`iced_glow`] supporting OpenGL 2.1+ and OpenGL ES 2.0+ + * [`iced_tiny_skia`] offering a software alternative as a fallback * A [windowing shell] * A [web runtime] leveraging the DOM @@ -52,9 +52,9 @@ __Iced is currently experimental software.__ [Take a look at the roadmap], [Modular ecosystem]: ECOSYSTEM.md [renderer-agnostic native runtime]: native/ [`wgpu`]: https://github.com/gfx-rs/wgpu -[`glow`]: https://github.com/grovesNL/glow +[`tiny-skia`]: https://github.com/RazrFalcon/tiny-skia [`iced_wgpu`]: wgpu/ -[`iced_glow`]: glow/ +[`iced_tiny_skia`]: tiny_skia/ [built-in renderers]: ECOSYSTEM.md#Renderers [windowing shell]: winit/ [`dodrio`]: https://github.com/fitzgen/dodrio @@ -196,34 +196,6 @@ end-user-oriented GUI library, while keeping [the ecosystem] modular: [`ggez`]: https://github.com/ggez/ggez [the ecosystem]: ECOSYSTEM.md -## Troubleshooting - -### `GraphicsAdapterNotFound` - -This occurs when the selected [built-in renderer] is not able to create a context. - -Often this will occur while using [`iced_wgpu`] as the renderer without -supported hardware (needs Vulkan, Metal or DX12). In this case, you could try using the -[`iced_glow`] renderer: - -First, check if it works with - -```console -cargo run --features iced/glow --package game_of_life -``` - -and then use it in your project with - -```toml -iced = { version = "0.9", default-features = false, features = ["glow"] } -``` - -__NOTE:__ Chances are you have hardware that supports at least OpenGL 2.1 or OpenGL ES 2.0, -but if you don't, right now there's no software fallback, so it means your hardware -doesn't support Iced. - -[built-in renderer]: https://github.com/iced-rs/iced/blob/master/ECOSYSTEM.md#Renderers - ## Contributing / Feedback Contributions are greatly appreciated! If you want to contribute, please diff --git a/core/Cargo.toml b/core/Cargo.toml index 3a00a53e..92d9773f 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -9,6 +9,8 @@ repository = "https://github.com/iced-rs/iced" [dependencies] bitflags = "1.2" +thiserror = "1" +twox-hash = { version = "1.5", default-features = false } [dependencies.palette] version = "0.6" diff --git a/core/src/clipboard.rs b/core/src/clipboard.rs new file mode 100644 index 00000000..081b4004 --- /dev/null +++ b/core/src/clipboard.rs @@ -0,0 +1,23 @@ +//! Access the clipboard. + +/// A buffer for short-term storage and transfer within and between +/// applications. +pub trait Clipboard { + /// Reads the current content of the [`Clipboard`] as text. + fn read(&self) -> Option<String>; + + /// Writes the given text contents to the [`Clipboard`]. + fn write(&mut self, contents: String); +} + +/// A null implementation of the [`Clipboard`] trait. +#[derive(Debug, Clone, Copy)] +pub struct Null; + +impl Clipboard for Null { + fn read(&self) -> Option<String> { + None + } + + fn write(&mut self, _contents: String) {} +} diff --git a/native/src/element.rs b/core/src/element.rs index 0a677d20..98c53737 100644 --- a/native/src/element.rs +++ b/core/src/element.rs @@ -90,41 +90,65 @@ impl<'a, Message, Renderer> Element<'a, Message, Renderer> { /// We compose the previous __messages__ with the index of the counter /// producing them. Let's implement our __view logic__ now: /// - /// ``` + /// ```no_run /// # mod counter { - /// # type Text<'a> = iced_native::widget::Text<'a, iced_native::renderer::Null>; - /// # /// # #[derive(Debug, Clone, Copy)] /// # pub enum Message {} /// # pub struct Counter; /// # /// # impl Counter { - /// # pub fn view(&mut self) -> Text { - /// # Text::new("") + /// # pub fn view( + /// # &self, + /// # ) -> iced_core::Element<Message, iced_core::renderer::Null> { + /// # unimplemented!() /// # } /// # } /// # } /// # - /// # mod iced_wgpu { - /// # pub use iced_native::renderer::Null as Renderer; - /// # } + /// # mod iced { + /// # pub use iced_core::renderer::Null as Renderer; + /// # pub use iced_core::Element; /// # - /// # use counter::Counter; + /// # pub mod widget { + /// # pub struct Row<Message> { + /// # _t: std::marker::PhantomData<Message>, + /// # } /// # - /// # struct ManyCounters { - /// # counters: Vec<Counter>, - /// # } + /// # impl<Message> Row<Message> { + /// # pub fn new() -> Self { + /// # unimplemented!() + /// # } /// # - /// # #[derive(Debug, Clone, Copy)] - /// # pub enum Message { - /// # Counter(usize, counter::Message) + /// # pub fn spacing(mut self, _: u32) -> Self { + /// # unimplemented!() + /// # } + /// # + /// # pub fn push( + /// # mut self, + /// # _: iced_core::Element<Message, iced_core::renderer::Null>, + /// # ) -> Self { + /// # unimplemented!() + /// # } + /// # } + /// # } /// # } - /// use iced_native::Element; - /// use iced_native::widget::Row; - /// use iced_wgpu::Renderer; + /// # + /// use counter::Counter; + /// + /// use iced::widget::Row; + /// use iced::{Element, Renderer}; + /// + /// struct ManyCounters { + /// counters: Vec<Counter>, + /// } + /// + /// #[derive(Debug, Clone, Copy)] + /// pub enum Message { + /// Counter(usize, counter::Message), + /// } /// /// impl ManyCounters { - /// pub fn view(&mut self) -> Row<Message, Renderer> { + /// pub fn view(&mut self) -> Row<Message> { /// // We can quickly populate a `Row` by folding over our counters /// self.counters.iter_mut().enumerate().fold( /// Row::new().spacing(20), @@ -137,9 +161,10 @@ impl<'a, Message, Renderer> Element<'a, Message, Renderer> { /// // Here we turn our `Element<counter::Message>` into /// // an `Element<Message>` by combining the `index` and the /// // message of the `element`. - /// element.map(move |message| Message::Counter(index, message)) + /// element + /// .map(move |message| Message::Counter(index, message)), /// ) - /// } + /// }, /// ) /// } /// } diff --git a/native/src/event.rs b/core/src/event.rs index bcfaf891..953cd73f 100644 --- a/native/src/event.rs +++ b/core/src/event.rs @@ -62,7 +62,7 @@ impl Status { /// `Captured` takes precedence over `Ignored`: /// /// ``` - /// use iced_native::event::Status; + /// use iced_core::event::Status; /// /// assert_eq!(Status::Ignored.merge(Status::Ignored), Status::Ignored); /// assert_eq!(Status::Ignored.merge(Status::Captured), Status::Captured); diff --git a/core/src/font.rs b/core/src/font.rs index d8c34e5a..bb425fd6 100644 --- a/core/src/font.rs +++ b/core/src/font.rs @@ -1,19 +1,102 @@ +//! Load and use fonts. +use std::hash::Hash; + /// A font. -#[derive(Debug, Clone, Copy, Default)] -pub enum Font { - /// The default font. - /// - /// This is normally a font configured in a renderer or loaded from the - /// system. +#[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, + /// Whether if the [`Font`] is monospaced or not. + pub monospaced: bool, +} + +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, + monospaced: false, + }; + + /// A monospaced font with normal [`Weight`]. + pub const MONOSPACE: Font = Font { + family: Family::Monospace, + monospaced: true, + ..Self::DEFAULT + }; + + /// Creates a non-monospaced [`Font`] with the given [`Family::Name`] and + /// normal [`Weight`]. + pub const fn with_name(name: &'static str) -> Self { + Font { + family: Family::Name(name), + ..Self::DEFAULT + } + } +} + +/// 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] - 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, +} - /// An external font. - External { - /// The name of the external font - name: &'static str, +/// 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 bytes of the external font - bytes: &'static [u8], - }, +/// 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, } diff --git a/graphics/src/gradient.rs b/core/src/gradient.rs index 61e919d6..61e919d6 100644 --- a/graphics/src/gradient.rs +++ b/core/src/gradient.rs diff --git a/graphics/src/gradient/linear.rs b/core/src/gradient/linear.rs index c886db47..c886db47 100644 --- a/graphics/src/gradient/linear.rs +++ b/core/src/gradient/linear.rs diff --git a/native/src/hasher.rs b/core/src/hasher.rs index fa52f16d..fa52f16d 100644 --- a/native/src/hasher.rs +++ b/core/src/hasher.rs diff --git a/native/src/image.rs b/core/src/image.rs index 70fbade0..85d9d475 100644 --- a/native/src/image.rs +++ b/core/src/image.rs @@ -6,7 +6,7 @@ use std::path::PathBuf; use std::sync::Arc; /// A handle of some image data. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct Handle { id: u64, data: Data, @@ -110,6 +110,14 @@ impl std::hash::Hash for Bytes { } } +impl PartialEq for Bytes { + fn eq(&self, other: &Self) -> bool { + self.as_ref() == other.as_ref() + } +} + +impl Eq for Bytes {} + impl AsRef<[u8]> for Bytes { fn as_ref(&self) -> &[u8] { self.0.as_ref().as_ref() @@ -125,7 +133,7 @@ impl std::ops::Deref for Bytes { } /// The data of a raster image. -#[derive(Clone, Hash)] +#[derive(Clone, PartialEq, Eq, Hash)] pub enum Data { /// File data Path(PathBuf), diff --git a/native/src/layout.rs b/core/src/layout.rs index 04954fb9..04954fb9 100644 --- a/native/src/layout.rs +++ b/core/src/layout.rs diff --git a/native/src/layout/DRUID_LICENSE b/core/src/layout/DRUID_LICENSE index d6456956..d6456956 100644 --- a/native/src/layout/DRUID_LICENSE +++ b/core/src/layout/DRUID_LICENSE diff --git a/native/src/layout/flex.rs b/core/src/layout/flex.rs index 8b967849..8b967849 100644 --- a/native/src/layout/flex.rs +++ b/core/src/layout/flex.rs diff --git a/native/src/layout/limits.rs b/core/src/layout/limits.rs index 5d3c1556..5d3c1556 100644 --- a/native/src/layout/limits.rs +++ b/core/src/layout/limits.rs diff --git a/native/src/layout/node.rs b/core/src/layout/node.rs index 2b44a7d5..2b44a7d5 100644 --- a/native/src/layout/node.rs +++ b/core/src/layout/node.rs diff --git a/core/src/length.rs b/core/src/length.rs index bb925c4b..3adb996e 100644 --- a/core/src/length.rs +++ b/core/src/length.rs @@ -1,3 +1,5 @@ +use crate::Pixels; + /// The strategy used to fill space in a specific dimension. #[derive(Debug, Clone, Copy, PartialEq)] pub enum Length { @@ -36,6 +38,12 @@ impl Length { } } +impl From<Pixels> for Length { + fn from(amount: Pixels) -> Self { + Length::Fixed(f32::from(amount)) + } +} + impl From<f32> for Length { fn from(amount: f32) -> Self { Length::Fixed(amount) diff --git a/core/src/lib.rs b/core/src/lib.rs index da3cb874..89dfb828 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -25,31 +25,57 @@ #![forbid(unsafe_code, rust_2018_idioms)] #![allow(clippy::inherent_to_string, clippy::type_complexity)] pub mod alignment; +pub mod clipboard; +pub mod event; +pub mod font; +pub mod gradient; +pub mod image; pub mod keyboard; +pub mod layout; pub mod mouse; +pub mod overlay; +pub mod renderer; +pub mod svg; +pub mod text; pub mod time; +pub mod touch; +pub mod widget; +pub mod window; mod background; mod color; mod content_fit; -mod font; +mod element; +mod hasher; mod length; mod padding; mod pixels; mod point; mod rectangle; +mod shell; mod size; mod vector; pub use alignment::Alignment; pub use background::Background; +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 hasher::Hasher; +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 shell::Shell; pub use size::Size; +pub use text::Text; pub use vector::Vector; +pub use widget::Widget; diff --git a/core/src/mouse.rs b/core/src/mouse.rs index 48214f65..0c405ce6 100644 --- a/core/src/mouse.rs +++ b/core/src/mouse.rs @@ -1,8 +1,11 @@ //! Handle mouse events. +pub mod click; + mod button; mod event; mod interaction; pub use button::Button; +pub use click::Click; pub use event::{Event, ScrollDelta}; pub use interaction::Interaction; diff --git a/native/src/mouse/click.rs b/core/src/mouse/click.rs index 4a7d796c..4a7d796c 100644 --- a/native/src/mouse/click.rs +++ b/core/src/mouse/click.rs diff --git a/native/src/overlay.rs b/core/src/overlay.rs index 6cada416..b9f3c735 100644 --- a/native/src/overlay.rs +++ b/core/src/overlay.rs @@ -2,11 +2,8 @@ mod element; mod group; -pub mod menu; - pub use element::Element; pub use group::Group; -pub use menu::Menu; use crate::event::{self, Event}; use crate::layout; diff --git a/native/src/overlay/element.rs b/core/src/overlay/element.rs index 237d25d1..237d25d1 100644 --- a/native/src/overlay/element.rs +++ b/core/src/overlay/element.rs diff --git a/native/src/overlay/group.rs b/core/src/overlay/group.rs index 1126f0cf..0c48df34 100644 --- a/native/src/overlay/group.rs +++ b/core/src/overlay/group.rs @@ -1,12 +1,10 @@ -use iced_core::{Point, Rectangle, Size}; - use crate::event; use crate::layout; use crate::mouse; use crate::overlay; use crate::renderer; use crate::widget; -use crate::{Clipboard, Event, Layout, Overlay, Shell}; +use crate::{Clipboard, Event, Layout, Overlay, Point, Rectangle, Shell, Size}; /// An [`Overlay`] container that displays multiple overlay [`overlay::Element`] /// children. diff --git a/core/src/pixels.rs b/core/src/pixels.rs index e42cd9f9..6a9e5c88 100644 --- a/core/src/pixels.rs +++ b/core/src/pixels.rs @@ -20,3 +20,9 @@ impl From<u16> for Pixels { Self(f32::from(amount)) } } + +impl From<Pixels> for f32 { + fn from(pixels: Pixels) -> Self { + pixels.0 + } +} diff --git a/core/src/rectangle.rs b/core/src/rectangle.rs index 4fe91519..7ff324cb 100644 --- a/core/src/rectangle.rs +++ b/core/src/rectangle.rs @@ -66,6 +66,11 @@ impl Rectangle<f32> { 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`]. pub fn contains(&self, point: Point) -> bool { self.x <= point.x @@ -74,6 +79,15 @@ impl Rectangle<f32> { && point.y <= self.y + self.height } + /// Returns true if the current [`Rectangle`] is completely within the given + /// `container`. + pub fn is_within(&self, container: &Rectangle) -> bool { + container.contains(self.position()) + && container.contains( + self.position() + Vector::new(self.width, self.height), + ) + } + /// Computes the intersection with the given [`Rectangle`]. pub fn intersection( &self, @@ -100,6 +114,30 @@ impl Rectangle<f32> { } } + /// 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, + } + } + /// Snaps the [`Rectangle`] to __unsigned__ integer coordinates. pub fn snap(self) -> Rectangle<u32> { Rectangle { @@ -109,6 +147,16 @@ impl Rectangle<f32> { height: self.height as u32, } } + + /// Expands the [`Rectangle`] a given amount. + pub fn expand(self, amount: f32) -> Self { + Self { + x: self.x - amount, + y: self.y - amount, + width: self.width + amount * 2.0, + height: self.height + amount * 2.0, + } + } } impl std::ops::Mul<f32> for Rectangle<f32> { diff --git a/native/src/renderer.rs b/core/src/renderer.rs index 2ac78982..d6247e39 100644 --- a/native/src/renderer.rs +++ b/core/src/renderer.rs @@ -1,6 +1,7 @@ //! Write your own renderer. #[cfg(debug_assertions)] mod null; + #[cfg(debug_assertions)] pub use null::Null; diff --git a/native/src/renderer/null.rs b/core/src/renderer/null.rs index 9376d540..f0cc952e 100644 --- a/native/src/renderer/null.rs +++ b/core/src/renderer/null.rs @@ -1,6 +1,8 @@ use crate::renderer::{self, Renderer}; use crate::text::{self, Text}; -use crate::{Background, Font, Point, Rectangle, Size, Theme, Vector}; +use crate::{Background, Font, Point, Rectangle, Size, Vector}; + +use std::borrow::Cow; /// A renderer that does nothing. /// @@ -16,7 +18,7 @@ impl Null { } impl Renderer for Null { - type Theme = Theme; + type Theme = (); fn with_layer(&mut self, _bounds: Rectangle, _f: impl FnOnce(&mut Self)) {} @@ -40,20 +42,28 @@ impl Renderer for Null { impl text::Renderer for Null { type Font = Font; - const ICON_FONT: Font = Font::Default; + const ICON_FONT: Font = Font::DEFAULT; const CHECKMARK_ICON: char = '0'; const ARROW_DOWN_ICON: char = '0'; + fn default_font(&self) -> Self::Font { + Font::default() + } + fn default_size(&self) -> f32 { - 20.0 + 16.0 } + fn load_font(&mut self, _font: Cow<'static, [u8]>) {} + fn measure( &self, _content: &str, _size: f32, + _line_height: text::LineHeight, _font: Font, _bounds: Size, + _shaping: text::Shaping, ) -> (f32, f32) { (0.0, 20.0) } @@ -62,8 +72,10 @@ impl text::Renderer for Null { &self, _contents: &str, _size: f32, + _line_height: text::LineHeight, _font: Self::Font, _bounds: Size, + _shaping: text::Shaping, _point: Point, _nearest_only: bool, ) -> Option<text::Hit> { diff --git a/native/src/shell.rs b/core/src/shell.rs index 74a5c616..74a5c616 100644 --- a/native/src/shell.rs +++ b/core/src/shell.rs diff --git a/core/src/size.rs b/core/src/size.rs index fbe940ef..7ef2f602 100644 --- a/core/src/size.rs +++ b/core/src/size.rs @@ -1,7 +1,7 @@ use crate::{Padding, Vector}; /// An amount of space in 2 dimensions. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct Size<T = f32> { /// The width. pub width: T, diff --git a/native/src/svg.rs b/core/src/svg.rs index 9b98877a..54e9434e 100644 --- a/native/src/svg.rs +++ b/core/src/svg.rs @@ -7,7 +7,7 @@ use std::path::PathBuf; use std::sync::Arc; /// A handle of Svg data. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct Handle { id: u64, data: Arc<Data>, @@ -57,7 +57,7 @@ impl Hash for Handle { } /// The data of a vectorial image. -#[derive(Clone, Hash)] +#[derive(Clone, Hash, PartialEq, Eq)] pub enum Data { /// File data Path(PathBuf), diff --git a/core/src/text.rs b/core/src/text.rs new file mode 100644 index 00000000..c154cc27 --- /dev/null +++ b/core/src/text.rs @@ -0,0 +1,212 @@ +//! Draw and interact with text. +use crate::alignment; +use crate::{Color, Pixels, Point, Rectangle, Size}; + +use std::borrow::Cow; +use std::hash::{Hash, Hasher}; + +/// A paragraph. +#[derive(Debug, Clone, Copy)] +pub struct Text<'a, Font> { + /// The content of the paragraph. + pub content: &'a str, + + /// The bounds of the paragraph. + pub bounds: Rectangle, + + /// The size of the [`Text`] in logical pixels. + pub size: f32, + + /// The line height of the [`Text`]. + pub line_height: LineHeight, + + /// The color of the [`Text`]. + pub color: Color, + + /// The font of the [`Text`]. + pub font: Font, + + /// The horizontal alignment of the [`Text`]. + pub horizontal_alignment: alignment::Horizontal, + + /// The vertical alignment of the [`Text`]. + pub vertical_alignment: alignment::Vertical, + + /// The [`Shaping`] strategy of the [`Text`]. + pub shaping: Shaping, +} + +/// The shaping strategy of some text. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +pub enum Shaping { + /// 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 is the default. + #[default] + 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. + Advanced, +} + +/// 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<f32> for LineHeight { + fn from(factor: f32) -> Self { + Self::Relative(factor) + } +} + +impl From<Pixels> for LineHeight { + fn from(pixels: Pixels) -> Self { + Self::Absolute(pixels) + } +} + +impl Hash for LineHeight { + fn hash<H: Hasher>(&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, + } + } +} + +/// A renderer capable of measuring and drawing [`Text`]. +pub trait Renderer: crate::Renderer { + /// The font type used. + type Font: Copy; + + /// 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; + + /// Returns the default [`Self::Font`]. + fn default_font(&self) -> Self::Font; + + /// Returns the default size of [`Text`]. + fn default_size(&self) -> f32; + + /// Measures the text in the given bounds and returns the minimum boundaries + /// that can fit the contents. + fn measure( + &self, + content: &str, + size: f32, + line_height: LineHeight, + font: Self::Font, + bounds: Size, + shaping: Shaping, + ) -> (f32, f32); + + /// Measures the width of the text as if it were laid out in a single line. + fn measure_width( + &self, + content: &str, + size: f32, + font: Self::Font, + shaping: Shaping, + ) -> f32 { + let (width, _) = self.measure( + content, + size, + LineHeight::Absolute(Pixels(size)), + font, + Size::INFINITY, + shaping, + ); + + width + } + + /// Tests whether the provided point is within the boundaries of text + /// laid out with the given parameters, returning information about + /// the nearest character. + /// + /// If `nearest_only` is true, the hit test does not consider whether the + /// the point is interior to any glyph bounds, returning only the character + /// with the nearest centeroid. + fn hit_test( + &self, + contents: &str, + size: f32, + line_height: LineHeight, + font: Self::Font, + bounds: Size, + shaping: Shaping, + point: Point, + nearest_only: bool, + ) -> Option<Hit>; + + /// Loads a [`Self::Font`] from its bytes. + fn load_font(&mut self, font: Cow<'static, [u8]>); + + /// Draws the given [`Text`]. + fn fill_text(&mut self, text: Text<'_, Self::Font>); +} diff --git a/native/src/touch.rs b/core/src/touch.rs index 18120644..18120644 100644 --- a/native/src/touch.rs +++ b/core/src/touch.rs diff --git a/native/src/widget.rs b/core/src/widget.rs index 6b83f1fa..769f8659 100644 --- a/native/src/widget.rs +++ b/core/src/widget.rs @@ -1,101 +1,21 @@ -//! Use the built-in widgets or create your own. -//! -//! # Built-in widgets -//! Every built-in drawable widget has its own module with a `Renderer` trait -//! that must be implemented by a [renderer] before being able to use it as -//! a [`Widget`]. -//! -//! # Custom widgets -//! If you want to implement a custom widget, you simply need to implement the -//! [`Widget`] trait. You can use the API of the built-in widgets as a guide or -//! source of inspiration. -//! -//! [renderer]: crate::renderer -pub mod button; -pub mod checkbox; -pub mod column; -pub mod container; -pub mod helpers; -pub mod image; -pub mod mouse_area; +//! Create custom widgets and operate on them. pub mod operation; -pub mod pane_grid; -pub mod pick_list; -pub mod progress_bar; -pub mod radio; -pub mod row; -pub mod rule; -pub mod scrollable; -pub mod slider; -pub mod space; -pub mod svg; pub mod text; -pub mod text_input; -pub mod toggler; -pub mod tooltip; pub mod tree; -pub mod vertical_slider; -mod action; mod id; -#[doc(no_inline)] -pub use button::Button; -#[doc(no_inline)] -pub use checkbox::Checkbox; -#[doc(no_inline)] -pub use column::Column; -#[doc(no_inline)] -pub use container::Container; -#[doc(no_inline)] -pub use helpers::*; -#[doc(no_inline)] -pub use image::Image; -#[doc(no_inline)] -pub use mouse_area::MouseArea; -#[doc(no_inline)] -pub use pane_grid::PaneGrid; -#[doc(no_inline)] -pub use pick_list::PickList; -#[doc(no_inline)] -pub use progress_bar::ProgressBar; -#[doc(no_inline)] -pub use radio::Radio; -#[doc(no_inline)] -pub use row::Row; -#[doc(no_inline)] -pub use rule::Rule; -#[doc(no_inline)] -pub use scrollable::Scrollable; -#[doc(no_inline)] -pub use slider::Slider; -#[doc(no_inline)] -pub use space::Space; -#[doc(no_inline)] -pub use svg::Svg; -#[doc(no_inline)] -pub use text::Text; -#[doc(no_inline)] -pub use text_input::TextInput; -#[doc(no_inline)] -pub use toggler::Toggler; -#[doc(no_inline)] -pub use tooltip::Tooltip; -#[doc(no_inline)] -pub use tree::Tree; -#[doc(no_inline)] -pub use vertical_slider::VerticalSlider; - -pub use action::Action; pub use id::Id; pub use operation::Operation; +pub use text::Text; +pub use tree::Tree; use crate::event::{self, Event}; -use crate::layout; +use crate::layout::{self, Layout}; use crate::mouse; use crate::overlay; use crate::renderer; -use crate::{Clipboard, Layout, Length, Point, Rectangle, Shell}; +use crate::{Clipboard, Length, Point, Rectangle, Shell}; /// A component that displays information and allows interaction. /// diff --git a/native/src/widget/id.rs b/core/src/widget/id.rs index 4b8fedf1..ae739bb7 100644 --- a/native/src/widget/id.rs +++ b/core/src/widget/id.rs @@ -24,7 +24,7 @@ impl Id { } #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum Internal { +enum Internal { Unique(usize), Custom(borrow::Cow<'static, str>), } diff --git a/core/src/widget/operation.rs b/core/src/widget/operation.rs new file mode 100644 index 00000000..ad188c36 --- /dev/null +++ b/core/src/widget/operation.rs @@ -0,0 +1,226 @@ +//! 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 std::any::Any; +use std::fmt; +use std::rc::Rc; + +/// 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<T> { + /// Operates on a widget that contains other widgets. + /// + /// The `operate_on_children` function can be called to return control to + /// the widget tree and keep traversing it. + fn container( + &mut self, + id: Option<&Id>, + operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>), + ); + + /// Operates on a widget that can be focused. + fn focusable(&mut self, _state: &mut dyn Focusable, _id: Option<&Id>) {} + + /// Operates on a widget that can be scrolled. + fn scrollable(&mut self, _state: &mut dyn Scrollable, _id: Option<&Id>) {} + + /// Operates on a widget that has text input. + fn text_input(&mut self, _state: &mut dyn TextInput, _id: Option<&Id>) {} + + /// Operates on a custom widget with some state. + fn custom(&mut self, _state: &mut dyn Any, _id: Option<&Id>) {} + + /// Finishes the [`Operation`] and returns its [`Outcome`]. + fn finish(&self) -> Outcome<T> { + Outcome::None + } +} + +/// The result of an [`Operation`]. +pub enum Outcome<T> { + /// The [`Operation`] produced no result. + None, + + /// The [`Operation`] produced some result. + Some(T), + + /// The [`Operation`] needs to be followed by another [`Operation`]. + Chain(Box<dyn Operation<T>>), +} + +impl<T> fmt::Debug for Outcome<T> +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(...)"), + } + } +} + +/// Maps the output of an [`Operation`] using the given function. +pub fn map<A, B>( + operation: Box<dyn Operation<A>>, + f: impl Fn(A) -> B + 'static, +) -> impl Operation<B> +where + A: 'static, + B: 'static, +{ + #[allow(missing_debug_implementations)] + struct Map<A, B> { + operation: Box<dyn Operation<A>>, + f: Rc<dyn Fn(A) -> B>, + } + + impl<A, B> Operation<B> for Map<A, B> + where + A: 'static, + B: 'static, + { + fn container( + &mut self, + id: Option<&Id>, + operate_on_children: &mut dyn FnMut(&mut dyn Operation<B>), + ) { + struct MapRef<'a, A> { + operation: &'a mut dyn Operation<A>, + } + + impl<'a, A, B> Operation<B> for MapRef<'a, A> { + fn container( + &mut self, + id: Option<&Id>, + operate_on_children: &mut dyn FnMut(&mut dyn Operation<B>), + ) { + let Self { operation, .. } = self; + + operation.container(id, &mut |operation| { + operate_on_children(&mut MapRef { operation }); + }); + } + + fn scrollable( + &mut self, + state: &mut dyn Scrollable, + id: Option<&Id>, + ) { + self.operation.scrollable(state, id); + } + + fn focusable( + &mut self, + state: &mut dyn Focusable, + id: Option<&Id>, + ) { + self.operation.focusable(state, id); + } + + fn text_input( + &mut self, + state: &mut dyn TextInput, + id: Option<&Id>, + ) { + self.operation.text_input(state, id); + } + + fn custom(&mut self, state: &mut dyn Any, id: Option<&Id>) { + self.operation.custom(state, id); + } + } + + let Self { operation, .. } = self; + + MapRef { + operation: operation.as_mut(), + } + .container(id, operate_on_children); + } + + fn focusable(&mut self, state: &mut dyn Focusable, id: Option<&Id>) { + self.operation.focusable(state, id); + } + + fn scrollable(&mut self, state: &mut dyn Scrollable, id: Option<&Id>) { + self.operation.scrollable(state, id); + } + + fn text_input(&mut self, state: &mut dyn TextInput, id: Option<&Id>) { + self.operation.text_input(state, id); + } + + fn custom(&mut self, state: &mut dyn Any, id: Option<&Id>) { + self.operation.custom(state, id); + } + + fn finish(&self) -> Outcome<B> { + 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: Rc::new(f), + } +} + +/// Produces an [`Operation`] that applies the given [`Operation`] to the +/// children of a container with the given [`Id`]. +pub fn scope<T: 'static>( + target: Id, + operation: impl Operation<T> + 'static, +) -> impl Operation<T> { + struct ScopedOperation<Message> { + target: Id, + operation: Box<dyn Operation<Message>>, + } + + impl<Message: 'static> Operation<Message> for ScopedOperation<Message> { + fn container( + &mut self, + id: Option<&Id>, + operate_on_children: &mut dyn FnMut(&mut dyn Operation<Message>), + ) { + if id == Some(&self.target) { + operate_on_children(self.operation.as_mut()); + } else { + operate_on_children(self); + } + } + + fn finish(&self) -> Outcome<Message> { + match self.operation.finish() { + Outcome::Chain(next) => { + Outcome::Chain(Box::new(ScopedOperation { + target: self.target.clone(), + operation: next, + })) + } + outcome => outcome, + } + } + } + + ScopedOperation { + target, + operation: Box::new(operation), + } +} diff --git a/native/src/widget/operation/focusable.rs b/core/src/widget/operation/focusable.rs index 312e4894..312e4894 100644 --- a/native/src/widget/operation/focusable.rs +++ b/core/src/widget/operation/focusable.rs diff --git a/native/src/widget/operation/scrollable.rs b/core/src/widget/operation/scrollable.rs index f947344d..f947344d 100644 --- a/native/src/widget/operation/scrollable.rs +++ b/core/src/widget/operation/scrollable.rs diff --git a/native/src/widget/operation/text_input.rs b/core/src/widget/operation/text_input.rs index 4c773e99..4c773e99 100644 --- a/native/src/widget/operation/text_input.rs +++ b/core/src/widget/operation/text_input.rs diff --git a/native/src/widget/text.rs b/core/src/widget/text.rs index 3fee48f2..90af88b7 100644 --- a/native/src/widget/text.rs +++ b/core/src/widget/text.rs @@ -4,27 +4,15 @@ use crate::layout; use crate::renderer; use crate::text; use crate::widget::Tree; -use crate::{Element, Layout, Length, Pixels, Point, Rectangle, Size, Widget}; +use crate::{ + Color, Element, Layout, Length, Pixels, Point, Rectangle, Size, Widget, +}; use std::borrow::Cow; -pub use iced_style::text::{Appearance, StyleSheet}; +pub use text::{LineHeight, Shaping}; /// A paragraph of text. -/// -/// # Example -/// -/// ``` -/// # use iced_native::Color; -/// # -/// # type Text<'a> = iced_native::widget::Text<'a, iced_native::renderer::Null>; -/// # -/// Text::new("I <3 iced!") -/// .size(40) -/// .style(Color::from([0.0, 0.0, 1.0])); -/// ``` -/// -///  #[allow(missing_debug_implementations)] pub struct Text<'a, Renderer> where @@ -33,11 +21,13 @@ where { content: Cow<'a, str>, size: Option<f32>, + line_height: LineHeight, width: Length, height: Length, horizontal_alignment: alignment::Horizontal, vertical_alignment: alignment::Vertical, - font: Renderer::Font, + font: Option<Renderer::Font>, + shaping: Shaping, style: <Renderer::Theme as StyleSheet>::Style, } @@ -51,11 +41,13 @@ where Text { content: content.into(), size: None, - font: Default::default(), + line_height: LineHeight::default(), + font: None, width: Length::Shrink, height: Length::Shrink, horizontal_alignment: alignment::Horizontal::Left, vertical_alignment: alignment::Vertical::Top, + shaping: Shaping::Basic, style: Default::default(), } } @@ -66,11 +58,17 @@ where self } + /// Sets the [`LineHeight`] of the [`Text`]. + pub fn line_height(mut self, line_height: impl Into<LineHeight>) -> Self { + self.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<Renderer::Font>) -> Self { - self.font = font.into(); + self.font = Some(font.into()); self } @@ -112,6 +110,12 @@ where self.vertical_alignment = alignment; self } + + /// Sets the [`Shaping`] strategy of the [`Text`]. + pub fn shaping(mut self, shaping: Shaping) -> Self { + self.shaping = shaping; + self + } } impl<'a, Message, Renderer> Widget<Message, Renderer> for Text<'a, Renderer> @@ -138,8 +142,14 @@ where let bounds = limits.max(); - let (width, height) = - renderer.measure(&self.content, size, self.font.clone(), bounds); + let (width, height) = renderer.measure( + &self.content, + size, + self.line_height, + self.font.unwrap_or_else(|| renderer.default_font()), + bounds, + self.shaping, + ); let size = limits.resolve(Size::new(width, height)); @@ -162,10 +172,12 @@ where layout, &self.content, self.size, - self.font.clone(), - theme.appearance(self.style), + self.line_height, + self.font, + theme.appearance(self.style.clone()), self.horizontal_alignment, self.vertical_alignment, + self.shaping, ); } } @@ -186,10 +198,12 @@ pub fn draw<Renderer>( layout: Layout<'_>, content: &str, size: Option<f32>, - font: Renderer::Font, + line_height: LineHeight, + font: Option<Renderer::Font>, appearance: Appearance, horizontal_alignment: alignment::Horizontal, vertical_alignment: alignment::Vertical, + shaping: Shaping, ) where Renderer: text::Renderer, { @@ -207,14 +221,18 @@ pub fn draw<Renderer>( alignment::Vertical::Bottom => bounds.y + bounds.height, }; - renderer.fill_text(crate::text::Text { + let size = size.unwrap_or_else(|| renderer.default_size()); + + renderer.fill_text(crate::Text { content, - size: size.unwrap_or_else(|| renderer.default_size()), + size, + line_height, bounds: Rectangle { x, y, ..bounds }, color: appearance.color.unwrap_or(style.text_color), - font, + font: font.unwrap_or_else(|| renderer.default_font()), horizontal_alignment, vertical_alignment, + shaping, }); } @@ -238,22 +256,52 @@ where Self { content: self.content.clone(), size: self.size, + line_height: self.line_height, width: self.width, height: self.height, horizontal_alignment: self.horizontal_alignment, vertical_alignment: self.vertical_alignment, - font: self.font.clone(), - style: self.style, + font: self.font, + style: self.style.clone(), + shaping: self.shaping, } } } +impl<'a, Renderer> From<&'a str> for Text<'a, Renderer> +where + Renderer: text::Renderer, + Renderer::Theme: StyleSheet, +{ + fn from(content: &'a str) -> Self { + Self::new(content) + } +} + impl<'a, Message, Renderer> From<&'a str> for Element<'a, Message, Renderer> where Renderer: text::Renderer + 'a, Renderer::Theme: StyleSheet, { - fn from(contents: &'a str) -> Self { - Text::new(contents).into() + fn from(content: &'a str) -> Self { + Text::from(content).into() } } + +/// The style sheet of some text. +pub trait StyleSheet { + /// The supported style of the [`StyleSheet`]. + type Style: Default + Clone; + + /// Produces the [`Appearance`] of some text. + fn appearance(&self, style: Self::Style) -> Appearance; +} + +/// The apperance of some text. +#[derive(Debug, Clone, Copy, Default)] +pub struct Appearance { + /// The [`Color`] of the text. + /// + /// The default, `None`, means using the inherited color. + pub color: Option<Color>, +} diff --git a/native/src/widget/tree.rs b/core/src/widget/tree.rs index 0af40c33..0af40c33 100644 --- a/native/src/widget/tree.rs +++ b/core/src/widget/tree.rs diff --git a/core/src/window.rs b/core/src/window.rs new file mode 100644 index 00000000..81bd7e3d --- /dev/null +++ b/core/src/window.rs @@ -0,0 +1,13 @@ +//! Build window-based GUI applications. +pub mod icon; + +mod event; +mod mode; +mod redraw_request; +mod user_attention; + +pub use event::Event; +pub use icon::Icon; +pub use mode::Mode; +pub use redraw_request::RedrawRequest; +pub use user_attention::UserAttention; diff --git a/native/src/window/event.rs b/core/src/window/event.rs index e2fb5e66..e2fb5e66 100644 --- a/native/src/window/event.rs +++ b/core/src/window/event.rs diff --git a/native/src/window/icon.rs b/core/src/window/icon.rs index 31868ecf..31868ecf 100644 --- a/native/src/window/icon.rs +++ b/core/src/window/icon.rs diff --git a/native/src/window/mode.rs b/core/src/window/mode.rs index fdce8e23..fdce8e23 100644 --- a/native/src/window/mode.rs +++ b/core/src/window/mode.rs diff --git a/native/src/window/redraw_request.rs b/core/src/window/redraw_request.rs index 3b4f0fd3..3b4f0fd3 100644 --- a/native/src/window/redraw_request.rs +++ b/core/src/window/redraw_request.rs diff --git a/native/src/window/user_attention.rs b/core/src/window/user_attention.rs index b03dfeef..b03dfeef 100644 --- a/native/src/window/user_attention.rs +++ b/core/src/window/user_attention.rs diff --git a/examples/README.md b/examples/README.md index 74cf145b..111e8910 100644 --- a/examples/README.md +++ b/examples/README.md @@ -93,8 +93,7 @@ A bunch of simpler examples exist: - [`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_opengl`](integration_opengl), a demonstration of how to integrate Iced in an existing OpenGL application. -- [`integration_wgpu`](integration_wgpu), a demonstration of how to integrate Iced in an existing [`wgpu`] application. +- [`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]. diff --git a/examples/arc/src/main.rs b/examples/arc/src/main.rs index 7b6ea0e1..80ad0b5b 100644 --- a/examples/arc/src/main.rs +++ b/examples/arc/src/main.rs @@ -5,8 +5,8 @@ use iced::widget::canvas::{ self, stroke, Cache, Canvas, Cursor, Geometry, Path, Stroke, }; use iced::{ - Application, Command, Element, Length, Point, Rectangle, Settings, - Subscription, Theme, + Application, Command, Element, Length, Point, Rectangle, Renderer, + Settings, Subscription, Theme, }; pub fn main() -> iced::Result { @@ -75,11 +75,12 @@ impl<Message> canvas::Program<Message> for Arc { fn draw( &self, _state: &Self::State, + renderer: &Renderer, theme: &Theme, bounds: Rectangle, _cursor: Cursor, ) -> Vec<Geometry> { - let geometry = self.cache.draw(bounds.size(), |frame| { + let geometry = self.cache.draw(renderer, bounds.size(), |frame| { let palette = theme.palette(); let center = frame.center(); diff --git a/examples/bezier_tool/src/main.rs b/examples/bezier_tool/src/main.rs index 7c3916d4..f1c83a16 100644 --- a/examples/bezier_tool/src/main.rs +++ b/examples/bezier_tool/src/main.rs @@ -64,7 +64,7 @@ mod bezier { use iced::widget::canvas::{ self, Canvas, Cursor, Frame, Geometry, Path, Stroke, }; - use iced::{Element, Length, Point, Rectangle, Theme}; + use iced::{Element, Length, Point, Rectangle, Renderer, Theme}; #[derive(Default)] pub struct State { @@ -152,22 +152,26 @@ mod bezier { fn draw( &self, state: &Self::State, + renderer: &Renderer, _theme: &Theme, bounds: Rectangle, cursor: Cursor, ) -> Vec<Geometry> { - let content = - self.state.cache.draw(bounds.size(), |frame: &mut Frame| { + let content = self.state.cache.draw( + renderer, + bounds.size(), + |frame: &mut Frame| { Curve::draw_all(self.curves, frame); frame.stroke( &Path::rectangle(Point::ORIGIN, frame.size()), Stroke::default().with_width(2.0), ); - }); + }, + ); if let Some(pending) = state { - let pending_curve = pending.draw(bounds, cursor); + let pending_curve = pending.draw(renderer, bounds, cursor); vec![content, pending_curve] } else { @@ -216,8 +220,13 @@ mod bezier { } impl Pending { - fn draw(&self, bounds: Rectangle, cursor: Cursor) -> Geometry { - let mut frame = Frame::new(bounds.size()); + fn draw( + &self, + renderer: &Renderer, + bounds: Rectangle, + cursor: Cursor, + ) -> Geometry { + let mut frame = Frame::new(renderer, bounds.size()); if let Some(cursor_position) = cursor.position_in(&bounds) { match *self { diff --git a/examples/checkbox/fonts/icons.ttf b/examples/checkbox/fonts/icons.ttf Binary files differindex a2046844..82f28481 100644 --- a/examples/checkbox/fonts/icons.ttf +++ b/examples/checkbox/fonts/icons.ttf diff --git a/examples/checkbox/src/main.rs b/examples/checkbox/src/main.rs index 09950bb8..ef61a974 100644 --- a/examples/checkbox/src/main.rs +++ b/examples/checkbox/src/main.rs @@ -1,10 +1,9 @@ -use iced::widget::{checkbox, column, container}; -use iced::{Element, Font, Length, Sandbox, Settings}; +use iced::executor; +use iced::font::{self, Font}; +use iced::widget::{checkbox, column, container, text}; +use iced::{Application, Command, Element, Length, Settings, Theme}; -const ICON_FONT: Font = Font::External { - name: "Icons", - bytes: include_bytes!("../fonts/icons.ttf"), -}; +const ICON_FONT: Font = Font::with_name("icons"); pub fn main() -> iced::Result { Example::run(Settings::default()) @@ -20,24 +19,35 @@ struct Example { enum Message { DefaultChecked(bool), CustomChecked(bool), + FontLoaded(Result<(), font::Error>), } -impl Sandbox for Example { +impl Application for Example { type Message = Message; + type Flags = (); + type Executor = executor::Default; + type Theme = Theme; - fn new() -> Self { - Default::default() + fn new(_flags: Self::Flags) -> (Self, Command<Message>) { + ( + Self::default(), + font::load(include_bytes!("../fonts/icons.ttf").as_ref()) + .map(Message::FontLoaded), + ) } fn title(&self) -> String { String::from("Checkbox - Iced") } - fn update(&mut self, message: Message) { + fn update(&mut self, message: Message) -> Command<Message> { match message { Message::DefaultChecked(value) => self.default_checkbox = value, Message::CustomChecked(value) => self.custom_checkbox = value, + Message::FontLoaded(_) => (), } + + Command::none() } fn view(&self) -> Element<Message> { @@ -49,6 +59,8 @@ impl Sandbox for Example { font: ICON_FONT, code_point: '\u{e901}', size: None, + line_height: text::LineHeight::Relative(1.0), + shaping: text::Shaping::Basic, }); let content = column![default_checkbox, custom_checkbox].spacing(22); diff --git a/examples/clock/src/main.rs b/examples/clock/src/main.rs index a389c54f..6425e2da 100644 --- a/examples/clock/src/main.rs +++ b/examples/clock/src/main.rs @@ -4,8 +4,8 @@ use iced::widget::canvas::{ }; use iced::widget::{canvas, container}; use iced::{ - Application, Color, Command, Element, Length, Point, Rectangle, Settings, - Subscription, Theme, Vector, + Application, Color, Command, Element, Length, Point, Rectangle, Renderer, + Settings, Subscription, Theme, Vector, }; pub fn main() -> iced::Result { @@ -83,17 +83,18 @@ impl Application for Clock { } } -impl<Message> canvas::Program<Message> for Clock { +impl<Message> canvas::Program<Message, Renderer> for Clock { type State = (); fn draw( &self, _state: &Self::State, + renderer: &Renderer, _theme: &Theme, bounds: Rectangle, _cursor: Cursor, ) -> Vec<Geometry> { - let clock = self.clock.draw(bounds.size(), |frame| { + let clock = self.clock.draw(renderer, bounds.size(), |frame| { let center = frame.center(); let radius = frame.width().min(frame.height()) / 2.0; diff --git a/examples/color_palette/src/main.rs b/examples/color_palette/src/main.rs index a2df36c2..5c4304ee 100644 --- a/examples/color_palette/src/main.rs +++ b/examples/color_palette/src/main.rs @@ -1,8 +1,8 @@ use iced::widget::canvas::{self, Canvas, Cursor, Frame, Geometry, Path}; use iced::widget::{column, row, text, Slider}; use iced::{ - alignment, Alignment, Color, Element, Length, Point, Rectangle, Sandbox, - Settings, Size, Vector, + alignment, Alignment, Color, Element, Length, Point, Rectangle, Renderer, + Sandbox, Settings, Size, Vector, }; use palette::{self, convert::FromColor, Hsl, Srgb}; use std::marker::PhantomData; @@ -243,11 +243,12 @@ impl<Message> canvas::Program<Message> for Theme { fn draw( &self, _state: &Self::State, + renderer: &Renderer, _theme: &iced::Theme, bounds: Rectangle, _cursor: Cursor, ) -> Vec<Geometry> { - let theme = self.canvas_cache.draw(bounds.size(), |frame| { + let theme = self.canvas_cache.draw(renderer, bounds.size(), |frame| { self.draw(frame); }); diff --git a/examples/component/Cargo.toml b/examples/component/Cargo.toml index dd435201..9db1e6b4 100644 --- a/examples/component/Cargo.toml +++ b/examples/component/Cargo.toml @@ -6,6 +6,4 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../..", features = ["debug"] } -iced_native = { path = "../../native" } -iced_lazy = { path = "../../lazy" } +iced = { path = "../..", features = ["debug", "lazy"] } diff --git a/examples/component/src/main.rs b/examples/component/src/main.rs index 21c2747c..010321a9 100644 --- a/examples/component/src/main.rs +++ b/examples/component/src/main.rs @@ -47,9 +47,8 @@ impl Sandbox for Component { mod numeric_input { use iced::alignment::{self, Alignment}; - use iced::widget::{self, button, row, text, text_input}; - use iced::{Element, Length}; - use iced_lazy::{self, Component}; + use iced::widget::{button, component, row, text, text_input, Component}; + use iced::{Element, Length, Renderer}; pub struct NumericInput<Message> { value: Option<u32>, @@ -82,13 +81,7 @@ mod numeric_input { } } - impl<Message, Renderer> Component<Message, Renderer> for NumericInput<Message> - where - Renderer: iced_native::text::Renderer + 'static, - Renderer::Theme: widget::button::StyleSheet - + widget::text_input::StyleSheet - + widget::text::StyleSheet, - { + impl<Message> Component<Message, Renderer> for NumericInput<Message> { type State = (); type Event = Event; @@ -152,17 +145,12 @@ mod numeric_input { } } - impl<'a, Message, Renderer> From<NumericInput<Message>> - for Element<'a, Message, Renderer> + impl<'a, Message> From<NumericInput<Message>> for Element<'a, Message, Renderer> where Message: 'a, - Renderer: 'static + iced_native::text::Renderer, - Renderer::Theme: widget::button::StyleSheet - + widget::text_input::StyleSheet - + widget::text::StyleSheet, { fn from(numeric_input: NumericInput<Message>) -> Self { - iced_lazy::component(numeric_input) + component(numeric_input) } } } diff --git a/examples/custom_quad/Cargo.toml b/examples/custom_quad/Cargo.toml index 39154786..f097c2dd 100644 --- a/examples/custom_quad/Cargo.toml +++ b/examples/custom_quad/Cargo.toml @@ -6,5 +6,4 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../.." } -iced_native = { path = "../../native" } +iced = { path = "../..", features = ["advanced"] } diff --git a/examples/custom_quad/src/main.rs b/examples/custom_quad/src/main.rs index 6509887c..b07f42ce 100644 --- a/examples/custom_quad/src/main.rs +++ b/examples/custom_quad/src/main.rs @@ -1,9 +1,9 @@ //! This example showcases a drawing a quad. mod quad { - use iced_native::layout::{self, Layout}; - use iced_native::renderer; - use iced_native::widget::{self, Widget}; - use iced_native::{Color, Element, Length, Point, Rectangle, Size}; + use iced::advanced::layout::{self, Layout}; + use iced::advanced::renderer; + use iced::advanced::widget::{self, Widget}; + use iced::{Color, Element, Length, Point, Rectangle, Size}; pub struct CustomQuad { size: f32, diff --git a/examples/custom_widget/Cargo.toml b/examples/custom_widget/Cargo.toml index 067aab4a..dda0efe8 100644 --- a/examples/custom_widget/Cargo.toml +++ b/examples/custom_widget/Cargo.toml @@ -6,5 +6,4 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../.." } -iced_native = { path = "../../native" } +iced = { path = "../..", features = ["advanced"] } diff --git a/examples/custom_widget/src/main.rs b/examples/custom_widget/src/main.rs index f6bb3b1e..7854548c 100644 --- a/examples/custom_widget/src/main.rs +++ b/examples/custom_widget/src/main.rs @@ -9,10 +9,10 @@ mod circle { // Of course, you can choose to make the implementation renderer-agnostic, // if you wish to, by creating your own `Renderer` trait, which could be // implemented by `iced_wgpu` and other renderers. - use iced_native::layout::{self, Layout}; - use iced_native::renderer; - use iced_native::widget::{self, Widget}; - use iced_native::{Color, Element, Length, Point, Rectangle, Size}; + use iced::advanced::layout::{self, Layout}; + use iced::advanced::renderer; + use iced::advanced::widget::{self, Widget}; + use iced::{Color, Element, Length, Point, Rectangle, Size}; pub struct Circle { radius: f32, diff --git a/examples/download_progress/Cargo.toml b/examples/download_progress/Cargo.toml index f38679ea..212832f4 100644 --- a/examples/download_progress/Cargo.toml +++ b/examples/download_progress/Cargo.toml @@ -7,8 +7,6 @@ publish = false [dependencies] iced = { path = "../..", features = ["tokio"] } -iced_native = { path = "../../native" } -iced_futures = { path = "../../futures" } [dependencies.reqwest] version = "0.11" diff --git a/examples/download_progress/src/download.rs b/examples/download_progress/src/download.rs index cd7647e8..3b11cb76 100644 --- a/examples/download_progress/src/download.rs +++ b/examples/download_progress/src/download.rs @@ -1,4 +1,4 @@ -use iced_native::subscription; +use iced::subscription; use std::hash::Hash; diff --git a/examples/events/Cargo.toml b/examples/events/Cargo.toml index 8c56e471..15ffc0af 100644 --- a/examples/events/Cargo.toml +++ b/examples/events/Cargo.toml @@ -7,4 +7,3 @@ publish = false [dependencies] iced = { path = "../..", features = ["debug"] } -iced_native = { path = "../../native" } diff --git a/examples/events/src/main.rs b/examples/events/src/main.rs index 1b97018e..7f3a5e1d 100644 --- a/examples/events/src/main.rs +++ b/examples/events/src/main.rs @@ -1,12 +1,13 @@ use iced::alignment; use iced::executor; +use iced::subscription; use iced::widget::{button, checkbox, container, text, Column}; use iced::window; +use iced::Event; use iced::{ Alignment, Application, Command, Element, Length, Settings, Subscription, Theme, }; -use iced_native::Event; pub fn main() -> iced::Result { Events::run(Settings { @@ -17,13 +18,13 @@ pub fn main() -> iced::Result { #[derive(Debug, Default)] struct Events { - last: Vec<iced_native::Event>, + last: Vec<Event>, enabled: bool, } #[derive(Debug, Clone)] enum Message { - EventOccurred(iced_native::Event), + EventOccurred(Event), Toggled(bool), Exit, } @@ -70,7 +71,7 @@ impl Application for Events { } fn subscription(&self) -> Subscription<Message> { - iced_native::subscription::events().map(Message::EventOccurred) + subscription::events().map(Message::EventOccurred) } fn view(&self) -> Element<Message> { diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index ed911160..eab8908b 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -145,7 +145,7 @@ impl Application for GameOfLife { self.grid .view() .map(move |message| Message::Grid(message, version)), - controls + controls, ]; container(content) @@ -211,8 +211,8 @@ mod grid { Cache, Canvas, Cursor, Frame, Geometry, Path, Text, }; use iced::{ - alignment, mouse, Color, Element, Length, Point, Rectangle, Size, - Theme, Vector, + alignment, mouse, Color, Element, Length, Point, Rectangle, Renderer, + Size, Theme, Vector, }; use rustc_hash::{FxHashMap, FxHashSet}; use std::future::Future; @@ -536,13 +536,14 @@ mod grid { fn draw( &self, _interaction: &Interaction, + renderer: &Renderer, _theme: &Theme, bounds: Rectangle, cursor: Cursor, ) -> Vec<Geometry> { let center = Vector::new(bounds.width / 2.0, bounds.height / 2.0); - let life = self.life_cache.draw(bounds.size(), |frame| { + let life = self.life_cache.draw(renderer, bounds.size(), |frame| { let background = Path::rectangle(Point::ORIGIN, frame.size()); frame.fill(&background, Color::from_rgb8(0x40, 0x44, 0x4B)); @@ -565,7 +566,7 @@ mod grid { }); let overlay = { - let mut frame = Frame::new(bounds.size()); + let mut frame = Frame::new(renderer, bounds.size()); let hovered_cell = cursor.position_in(&bounds).map(|position| { @@ -626,38 +627,40 @@ mod grid { if self.scaling < 0.2 || !self.show_lines { vec![life, overlay] } else { - let grid = self.grid_cache.draw(bounds.size(), |frame| { - frame.translate(center); - frame.scale(self.scaling); - frame.translate(self.translation); - frame.scale(Cell::SIZE as f32); - - let region = self.visible_region(frame.size()); - let rows = region.rows(); - let columns = region.columns(); - let (total_rows, total_columns) = - (rows.clone().count(), columns.clone().count()); - let width = 2.0 / Cell::SIZE as f32; - let color = Color::from_rgb8(70, 74, 83); - - frame.translate(Vector::new(-width / 2.0, -width / 2.0)); + let grid = + self.grid_cache.draw(renderer, bounds.size(), |frame| { + frame.translate(center); + frame.scale(self.scaling); + frame.translate(self.translation); + frame.scale(Cell::SIZE as f32); - for row in region.rows() { - frame.fill_rectangle( - Point::new(*columns.start() as f32, row as f32), - Size::new(total_columns as f32, width), - color, - ); - } + let region = self.visible_region(frame.size()); + let rows = region.rows(); + let columns = region.columns(); + let (total_rows, total_columns) = + (rows.clone().count(), columns.clone().count()); + let width = 2.0 / Cell::SIZE as f32; + let color = Color::from_rgb8(70, 74, 83); + + frame + .translate(Vector::new(-width / 2.0, -width / 2.0)); + + for row in region.rows() { + frame.fill_rectangle( + Point::new(*columns.start() as f32, row as f32), + Size::new(total_columns as f32, width), + color, + ); + } - for column in region.columns() { - frame.fill_rectangle( - Point::new(column as f32, *rows.start() as f32), - Size::new(width, total_rows as f32), - color, - ); - } - }); + for column in region.columns() { + frame.fill_rectangle( + Point::new(column as f32, *rows.start() as f32), + Size::new(width, total_rows as f32), + color, + ); + } + }); vec![life, grid, overlay] } diff --git a/examples/geometry/Cargo.toml b/examples/geometry/Cargo.toml index 22ede0e0..79fe52d5 100644 --- a/examples/geometry/Cargo.toml +++ b/examples/geometry/Cargo.toml @@ -6,6 +6,5 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../.." } -iced_native = { path = "../../native" } +iced = { path = "../..", features = ["advanced"] } iced_graphics = { path = "../../graphics" } diff --git a/examples/geometry/src/main.rs b/examples/geometry/src/main.rs index 9bacce7f..5cb41184 100644 --- a/examples/geometry/src/main.rs +++ b/examples/geometry/src/main.rs @@ -1,23 +1,13 @@ //! This example showcases a simple native custom widget that renders using //! arbitrary low-level geometry. mod rainbow { - // For now, to implement a custom native widget you will need to add - // `iced_native` and `iced_wgpu` to your dependencies. - // - // Then, you simply need to define your widget type and implement the - // `iced_native::Widget` trait with the `iced_wgpu::Renderer`. - // - // Of course, you can choose to make the implementation renderer-agnostic, - // if you wish to, by creating your own `Renderer` trait, which could be - // implemented by `iced_wgpu` and other renderers. - use iced_graphics::renderer::{self, Renderer}; - use iced_graphics::triangle::ColoredVertex2D; - use iced_graphics::{Backend, Primitive}; - - use iced_native::layout; - use iced_native::widget::{self, Widget}; - use iced_native::{ - Element, Layout, Length, Point, Rectangle, Size, Vector, + use iced_graphics::primitive::{ColoredVertex2D, Primitive}; + + use iced::advanced::layout::{self, Layout}; + use iced::advanced::renderer; + use iced::advanced::widget::{self, Widget}; + use iced::{ + Element, Length, Point, Rectangle, Renderer, Size, Theme, Vector, }; #[derive(Debug, Clone, Copy, Default)] @@ -27,10 +17,7 @@ mod rainbow { Rainbow } - impl<Message, B, T> Widget<Message, Renderer<B, T>> for Rainbow - where - B: Backend, - { + impl<Message> Widget<Message, Renderer> for Rainbow { fn width(&self) -> Length { Length::Fill } @@ -41,7 +28,7 @@ mod rainbow { fn layout( &self, - _renderer: &Renderer<B, T>, + _renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { let size = limits.width(Length::Fill).resolve(Size::ZERO); @@ -52,15 +39,15 @@ mod rainbow { fn draw( &self, _tree: &widget::Tree, - renderer: &mut Renderer<B, T>, - _theme: &T, + renderer: &mut Renderer, + _theme: &Theme, _style: &renderer::Style, layout: Layout<'_>, cursor_position: Point, _viewport: &Rectangle, ) { - use iced_graphics::triangle::Mesh2D; - use iced_native::Renderer as _; + use iced::advanced::Renderer as _; + use iced_graphics::primitive::Mesh2D; let b = layout.bounds(); @@ -151,10 +138,7 @@ mod rainbow { } } - impl<'a, Message, B, T> From<Rainbow> for Element<'a, Message, Renderer<B, T>> - where - B: Backend, - { + impl<'a, Message> From<Rainbow> for Element<'a, Message, Renderer> { fn from(rainbow: Rainbow) -> Self { Self::new(rainbow) } diff --git a/examples/integration_wgpu/.gitignore b/examples/integration/.gitignore index e188dc28..e188dc28 100644 --- a/examples/integration_wgpu/.gitignore +++ b/examples/integration/.gitignore diff --git a/examples/integration_wgpu/Cargo.toml b/examples/integration/Cargo.toml index c0e4fd81..22914742 100644 --- a/examples/integration_wgpu/Cargo.toml +++ b/examples/integration/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "integration_wgpu" +name = "integration" version = "0.1.0" authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] edition = "2021" @@ -7,7 +7,9 @@ publish = false [dependencies] iced_winit = { path = "../../winit" } -iced_wgpu = { path = "../../wgpu", features = ["webgl"] } +iced_wgpu = { path = "../../wgpu" } +iced_widget = { path = "../../widget" } +iced_renderer = { path = "../../renderer", features = ["wgpu"] } env_logger = "0.10" [target.'cfg(target_arch = "wasm32")'.dependencies] diff --git a/examples/integration_wgpu/README.md b/examples/integration/README.md index ece9ba1e..ece9ba1e 100644 --- a/examples/integration_wgpu/README.md +++ b/examples/integration/README.md diff --git a/examples/integration_wgpu/index.html b/examples/integration/index.html index 461e67a4..920bc4a0 100644 --- a/examples/integration_wgpu/index.html +++ b/examples/integration/index.html @@ -8,8 +8,8 @@ <h1>integration_wgpu</h1> <canvas id="iced_canvas"></canvas> <script type="module"> - import init from "./integration_wgpu.js"; - init('./integration_wgpu_bg.wasm'); + import init from "./integration.js"; + init('./integration_bg.wasm'); </script> <style> body { diff --git a/examples/integration_wgpu/src/controls.rs b/examples/integration/src/controls.rs index 42623b15..14e53ede 100644 --- a/examples/integration_wgpu/src/controls.rs +++ b/examples/integration/src/controls.rs @@ -1,6 +1,8 @@ use iced_wgpu::Renderer; -use iced_winit::widget::{slider, text_input, Column, Row, Text}; -use iced_winit::{Alignment, Color, Command, Element, Length, Program}; +use iced_widget::{slider, text_input, Column, Row, Text}; +use iced_winit::core::{Alignment, Color, Element, Length}; +use iced_winit::runtime::{Command, Program}; +use iced_winit::style::Theme; pub struct Controls { background_color: Color, @@ -27,7 +29,7 @@ impl Controls { } impl Program for Controls { - type Renderer = Renderer; + type Renderer = Renderer<Theme>; type Message = Message; fn update(&mut self, message: Message) -> Command<Message> { @@ -43,7 +45,7 @@ impl Program for Controls { Command::none() } - fn view(&self) -> Element<Message, Renderer> { + fn view(&self) -> Element<Message, Renderer<Theme>> { let background_color = self.background_color; let text = &self.text; diff --git a/examples/integration_wgpu/src/main.rs b/examples/integration/src/main.rs index 15901db8..c935aca7 100644 --- a/examples/integration_wgpu/src/main.rs +++ b/examples/integration/src/main.rs @@ -4,11 +4,14 @@ mod scene; use controls::Controls; use scene::Scene; -use iced_wgpu::{wgpu, Backend, Renderer, Settings, Viewport}; -use iced_winit::{ - conversion, futures, program, renderer, winit, Clipboard, Color, Debug, - Size, -}; +use iced_wgpu::graphics::Viewport; +use iced_wgpu::{wgpu, Backend, Renderer, Settings}; +use iced_winit::core::renderer; +use iced_winit::core::{Color, Size}; +use iced_winit::runtime::program; +use iced_winit::runtime::Debug; +use iced_winit::style::Theme; +use iced_winit::{conversion, futures, winit, Clipboard}; use winit::{ dpi::PhysicalPosition, @@ -34,7 +37,7 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> { .and_then(|win| win.document()) .and_then(|doc| doc.get_element_by_id("iced_canvas")) .and_then(|element| element.dyn_into::<HtmlCanvasElement>().ok()) - .expect("Canvas with id `iced_canvas` is missing") + .expect("Get canvas element") }; #[cfg(not(target_arch = "wasm32"))] env_logger::init(); @@ -74,47 +77,49 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> { }); let surface = unsafe { instance.create_surface(&window) }?; - let (format, (device, queue)) = futures::executor::block_on(async { - let adapter = wgpu::util::initialize_adapter_from_env_or_default( - &instance, - backend, - Some(&surface), - ) - .await - .expect("No suitable GPU adapters found on the system!"); - - let adapter_features = adapter.features(); - - #[cfg(target_arch = "wasm32")] - let needed_limits = wgpu::Limits::downlevel_webgl2_defaults() - .using_resolution(adapter.limits()); - - #[cfg(not(target_arch = "wasm32"))] - let needed_limits = wgpu::Limits::default(); - - let capabilities = surface.get_capabilities(&adapter); - - ( - capabilities - .formats - .iter() - .copied() - .find(wgpu::TextureFormat::is_srgb) - .or_else(|| capabilities.formats.first().copied()) - .expect("Get preferred format"), - adapter - .request_device( - &wgpu::DeviceDescriptor { - label: None, - features: adapter_features & wgpu::Features::default(), - limits: needed_limits, - }, - None, - ) - .await - .expect("Request device"), - ) - }); + let (format, (device, queue)) = + futures::futures::executor::block_on(async { + let adapter = wgpu::util::initialize_adapter_from_env_or_default( + &instance, + backend, + Some(&surface), + ) + .await + .expect("Create adapter"); + + let adapter_features = adapter.features(); + + #[cfg(target_arch = "wasm32")] + let needed_limits = wgpu::Limits::downlevel_webgl2_defaults() + .using_resolution(adapter.limits()); + + #[cfg(not(target_arch = "wasm32"))] + let needed_limits = wgpu::Limits::default(); + + let capabilities = surface.get_capabilities(&adapter); + + ( + capabilities + .formats + .iter() + .copied() + .find(wgpu::TextureFormat::is_srgb) + .or_else(|| capabilities.formats.first().copied()) + .expect("Get preferred format"), + adapter + .request_device( + &wgpu::DeviceDescriptor { + label: None, + features: adapter_features + & wgpu::Features::default(), + limits: needed_limits, + }, + None, + ) + .await + .expect("Request device"), + ) + }); surface.configure( &device, @@ -131,17 +136,18 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> { let mut resized = false; - // Initialize staging belt - let mut staging_belt = wgpu::util::StagingBelt::new(5 * 1024); - // Initialize scene and GUI controls let scene = Scene::new(&device, format); let controls = Controls::new(); // Initialize iced let mut debug = Debug::new(); - let mut renderer = - Renderer::new(Backend::new(&device, Settings::default(), format)); + let mut renderer = Renderer::new(Backend::new( + &device, + &queue, + Settings::default(), + format, + )); let mut state = program::State::new( controls, @@ -193,7 +199,7 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> { viewport.scale_factor(), ), &mut renderer, - &iced_wgpu::Theme::Dark, + &Theme::Dark, &renderer::Style { text_color: Color::WHITE }, &mut clipboard, &mut debug, @@ -221,7 +227,7 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> { height: size.height, present_mode: wgpu::PresentMode::AutoVsync, alpha_mode: wgpu::CompositeAlphaMode::Auto, - view_formats: vec![] + view_formats: vec![], }, ); @@ -254,8 +260,9 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> { renderer.with_primitives(|backend, primitive| { backend.present( &device, - &mut staging_belt, + &queue, &mut encoder, + None, &view, primitive, &viewport, @@ -264,7 +271,6 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> { }); // Then we submit the work - staging_belt.finish(); queue.submit(Some(encoder.finish())); frame.present(); @@ -274,10 +280,6 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> { state.mouse_interaction(), ), ); - - // And recall staging buffers - staging_belt.recall(); - } Err(error) => match error { wgpu::SurfaceError::OutOfMemory => { diff --git a/examples/integration_wgpu/src/scene.rs b/examples/integration/src/scene.rs index 3e41fbda..90c7efbf 100644 --- a/examples/integration_wgpu/src/scene.rs +++ b/examples/integration/src/scene.rs @@ -1,5 +1,5 @@ use iced_wgpu::wgpu; -use iced_winit::Color; +use iced_winit::core::Color; pub struct Scene { pipeline: wgpu::RenderPipeline, diff --git a/examples/integration_wgpu/src/shader/frag.wgsl b/examples/integration/src/shader/frag.wgsl index cf27bb56..cf27bb56 100644 --- a/examples/integration_wgpu/src/shader/frag.wgsl +++ b/examples/integration/src/shader/frag.wgsl diff --git a/examples/integration_wgpu/src/shader/vert.wgsl b/examples/integration/src/shader/vert.wgsl index e353e6ba..e353e6ba 100644 --- a/examples/integration_wgpu/src/shader/vert.wgsl +++ b/examples/integration/src/shader/vert.wgsl diff --git a/examples/integration_opengl/Cargo.toml b/examples/integration_opengl/Cargo.toml deleted file mode 100644 index 6dac999c..00000000 --- a/examples/integration_opengl/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "integration_opengl" -version = "0.1.0" -authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] -edition = "2021" -publish = false - -[dependencies] -iced_glutin = { path = "../../glutin" } -iced_glow = { path = "../../glow" } -iced_winit = { path = "../../winit" } -env_logger = "0.8" diff --git a/examples/integration_opengl/README.md b/examples/integration_opengl/README.md deleted file mode 100644 index b7c2c074..00000000 --- a/examples/integration_opengl/README.md +++ /dev/null @@ -1,16 +0,0 @@ -## OpenGL integration - -A demonstration of how to integrate Iced in an existing graphical OpenGL application. - -The __[`main`]__ file contains all the code of the example. - -<div align="center"> - <a href="https://imgbox.com/9P9ETcod" target="_blank"><img src="https://images2.imgbox.com/2a/51/9P9ETcod_o.gif" alt="image host"/></a> -</div> - -You can run it with `cargo run`: -``` -cargo run --package integration_opengl -``` - -[`main`]: src/main.rs diff --git a/examples/integration_opengl/src/controls.rs b/examples/integration_opengl/src/controls.rs deleted file mode 100644 index c3648f44..00000000 --- a/examples/integration_opengl/src/controls.rs +++ /dev/null @@ -1,101 +0,0 @@ -use iced_glow::Renderer; -use iced_glutin::widget::Slider; -use iced_glutin::widget::{Column, Row, Text}; -use iced_glutin::{Alignment, Color, Command, Element, Length, Program}; - -pub struct Controls { - background_color: Color, -} - -#[derive(Debug, Clone)] -pub enum Message { - BackgroundColorChanged(Color), -} - -impl Controls { - pub fn new() -> Controls { - Controls { - background_color: Color::BLACK, - } - } - - pub fn background_color(&self) -> Color { - self.background_color - } -} - -impl Program for Controls { - type Renderer = Renderer; - type Message = Message; - - fn update(&mut self, message: Message) -> Command<Message> { - match message { - Message::BackgroundColorChanged(color) => { - self.background_color = color; - } - } - - Command::none() - } - - fn view(&self) -> Element<Message, Renderer> { - let background_color = self.background_color; - - let sliders = Row::new() - .width(500) - .spacing(20) - .push( - Slider::new(0.0..=1.0, background_color.r, move |r| { - Message::BackgroundColorChanged(Color { - r, - ..background_color - }) - }) - .step(0.01), - ) - .push( - Slider::new(0.0..=1.0, background_color.g, move |g| { - Message::BackgroundColorChanged(Color { - g, - ..background_color - }) - }) - .step(0.01), - ) - .push( - Slider::new(0.0..=1.0, background_color.b, move |b| { - Message::BackgroundColorChanged(Color { - b, - ..background_color - }) - }) - .step(0.01), - ); - - Row::new() - .width(Length::Fill) - .height(Length::Fill) - .align_items(Alignment::End) - .push( - Column::new() - .width(Length::Fill) - .align_items(Alignment::End) - .push( - Column::new() - .padding(10) - .spacing(10) - .push( - Text::new("Background color") - .style(Color::WHITE), - ) - .push(sliders) - .push( - Text::new(format!("{background_color:?}")) - .size(14) - .style(Color::WHITE), - ), - ), - ) - .into() - } -} diff --git a/examples/integration_opengl/src/main.rs b/examples/integration_opengl/src/main.rs deleted file mode 100644 index f161c8a0..00000000 --- a/examples/integration_opengl/src/main.rs +++ /dev/null @@ -1,187 +0,0 @@ -mod controls; -mod scene; - -use controls::Controls; -use scene::Scene; - -use glow::*; -use glutin::dpi::PhysicalPosition; -use glutin::event::{Event, ModifiersState, WindowEvent}; -use glutin::event_loop::ControlFlow; -use iced_glow::glow; -use iced_glow::{Backend, Renderer, Settings, Viewport}; -use iced_glutin::conversion; -use iced_glutin::glutin; -use iced_glutin::renderer; -use iced_glutin::{program, Clipboard, Color, Debug, Size}; - -pub fn main() { - env_logger::init(); - let (gl, event_loop, windowed_context, shader_version) = { - let el = glutin::event_loop::EventLoop::new(); - - let wb = glutin::window::WindowBuilder::new() - .with_title("OpenGL integration example") - .with_inner_size(glutin::dpi::LogicalSize::new(1024.0, 768.0)); - - let windowed_context = glutin::ContextBuilder::new() - .with_vsync(true) - .build_windowed(wb, &el) - .unwrap(); - - unsafe { - let windowed_context = windowed_context.make_current().unwrap(); - - let gl = glow::Context::from_loader_function(|s| { - windowed_context.get_proc_address(s) as *const _ - }); - - // Enable auto-conversion from/to sRGB - gl.enable(glow::FRAMEBUFFER_SRGB); - - // Enable alpha blending - gl.enable(glow::BLEND); - gl.blend_func(glow::SRC_ALPHA, glow::ONE_MINUS_SRC_ALPHA); - - // Disable multisampling by default - gl.disable(glow::MULTISAMPLE); - - (gl, el, windowed_context, "#version 410") - } - }; - - let physical_size = windowed_context.window().inner_size(); - let mut viewport = Viewport::with_physical_size( - Size::new(physical_size.width, physical_size.height), - windowed_context.window().scale_factor(), - ); - - let mut cursor_position = PhysicalPosition::new(-1.0, -1.0); - let mut modifiers = ModifiersState::default(); - let mut clipboard = Clipboard::connect(windowed_context.window()); - - let mut renderer = Renderer::new(Backend::new(&gl, Settings::default())); - - let mut debug = Debug::new(); - - let controls = Controls::new(); - let mut state = program::State::new( - controls, - viewport.logical_size(), - &mut renderer, - &mut debug, - ); - let mut resized = false; - - let scene = Scene::new(&gl, shader_version); - - event_loop.run(move |event, _, control_flow| { - *control_flow = ControlFlow::Wait; - - match event { - Event::WindowEvent { event, .. } => { - match event { - WindowEvent::CursorMoved { position, .. } => { - cursor_position = position; - } - WindowEvent::ModifiersChanged(new_modifiers) => { - modifiers = new_modifiers; - } - WindowEvent::Resized(physical_size) => { - viewport = Viewport::with_physical_size( - Size::new( - physical_size.width, - physical_size.height, - ), - windowed_context.window().scale_factor(), - ); - - resized = true; - } - WindowEvent::CloseRequested => { - scene.cleanup(&gl); - *control_flow = ControlFlow::Exit - } - _ => (), - } - - // Map window event to iced event - if let Some(event) = iced_winit::conversion::window_event( - &event, - windowed_context.window().scale_factor(), - modifiers, - ) { - state.queue_event(event); - } - } - Event::MainEventsCleared => { - // If there are events pending - if !state.is_queue_empty() { - // We update iced - let _ = state.update( - viewport.logical_size(), - conversion::cursor_position( - cursor_position, - viewport.scale_factor(), - ), - &mut renderer, - &iced_glow::Theme::Dark, - &renderer::Style { - text_color: Color::WHITE, - }, - &mut clipboard, - &mut debug, - ); - - // and request a redraw - windowed_context.window().request_redraw(); - } - } - Event::RedrawRequested(_) => { - if resized { - let size = windowed_context.window().inner_size(); - - unsafe { - gl.viewport( - 0, - 0, - size.width as i32, - size.height as i32, - ); - } - - resized = false; - } - - let program = state.program(); - { - // We clear the frame - scene.clear(&gl, program.background_color()); - - // Draw the scene - scene.draw(&gl); - } - - // And then iced on top - renderer.with_primitives(|backend, primitive| { - backend.present( - &gl, - primitive, - &viewport, - &debug.overlay(), - ); - }); - - // Update the mouse cursor - windowed_context.window().set_cursor_icon( - iced_winit::conversion::mouse_interaction( - state.mouse_interaction(), - ), - ); - - windowed_context.swap_buffers().unwrap(); - } - _ => (), - } - }); -} diff --git a/examples/integration_opengl/src/scene.rs b/examples/integration_opengl/src/scene.rs deleted file mode 100644 index c1d05b65..00000000 --- a/examples/integration_opengl/src/scene.rs +++ /dev/null @@ -1,102 +0,0 @@ -use glow::*; -use iced_glow::glow; -use iced_glow::Color; - -pub struct Scene { - program: glow::Program, - vertex_array: glow::VertexArray, -} - -impl Scene { - pub fn new(gl: &glow::Context, shader_version: &str) -> Self { - unsafe { - let vertex_array = gl - .create_vertex_array() - .expect("Cannot create vertex array"); - gl.bind_vertex_array(Some(vertex_array)); - - let program = gl.create_program().expect("Cannot create program"); - - let (vertex_shader_source, fragment_shader_source) = ( - r#"const vec2 verts[3] = vec2[3]( - vec2(0.5f, 1.0f), - vec2(0.0f, 0.0f), - vec2(1.0f, 0.0f) - ); - out vec2 vert; - void main() { - vert = verts[gl_VertexID]; - gl_Position = vec4(vert - 0.5, 0.0, 1.0); - }"#, - r#"precision highp float; - in vec2 vert; - out vec4 color; - void main() { - color = vec4(vert, 0.5, 1.0); - }"#, - ); - - let shader_sources = [ - (glow::VERTEX_SHADER, vertex_shader_source), - (glow::FRAGMENT_SHADER, fragment_shader_source), - ]; - - let mut shaders = Vec::with_capacity(shader_sources.len()); - - for (shader_type, shader_source) in shader_sources.iter() { - let shader = gl - .create_shader(*shader_type) - .expect("Cannot create shader"); - gl.shader_source( - shader, - &format!("{shader_version}\n{shader_source}"), - ); - gl.compile_shader(shader); - if !gl.get_shader_compile_status(shader) { - panic!("{}", gl.get_shader_info_log(shader)); - } - gl.attach_shader(program, shader); - shaders.push(shader); - } - - gl.link_program(program); - if !gl.get_program_link_status(program) { - panic!("{}", gl.get_program_info_log(program)); - } - - for shader in shaders { - gl.detach_shader(program, shader); - gl.delete_shader(shader); - } - - gl.use_program(Some(program)); - Self { - program, - vertex_array, - } - } - } - - pub fn clear(&self, gl: &glow::Context, background_color: Color) { - let [r, g, b, a] = background_color.into_linear(); - unsafe { - gl.clear_color(r, g, b, a); - gl.clear(glow::COLOR_BUFFER_BIT); - } - } - - pub fn draw(&self, gl: &glow::Context) { - unsafe { - gl.bind_vertex_array(Some(self.vertex_array)); - gl.use_program(Some(self.program)); - gl.draw_arrays(glow::TRIANGLES, 0, 3); - } - } - - pub fn cleanup(&self, gl: &glow::Context) { - unsafe { - gl.delete_program(self.program); - gl.delete_vertex_array(self.vertex_array); - } - } -} diff --git a/examples/lazy/Cargo.toml b/examples/lazy/Cargo.toml index 79255c25..e03e89a9 100644 --- a/examples/lazy/Cargo.toml +++ b/examples/lazy/Cargo.toml @@ -6,5 +6,4 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../..", features = ["debug"] } -iced_lazy = { path = "../../lazy" } +iced = { path = "../..", features = ["debug", "lazy"] } diff --git a/examples/lazy/src/main.rs b/examples/lazy/src/main.rs index 55b13bcf..c6baa6a1 100644 --- a/examples/lazy/src/main.rs +++ b/examples/lazy/src/main.rs @@ -1,10 +1,9 @@ use iced::theme; use iced::widget::{ - button, column, horizontal_space, pick_list, row, scrollable, text, + button, column, horizontal_space, lazy, pick_list, row, scrollable, text, text_input, }; use iced::{Element, Length, Sandbox, Settings}; -use iced_lazy::lazy; use std::collections::HashSet; use std::hash::Hash; diff --git a/examples/modal/Cargo.toml b/examples/modal/Cargo.toml index 8770acac..3ac61e6a 100644 --- a/examples/modal/Cargo.toml +++ b/examples/modal/Cargo.toml @@ -6,5 +6,4 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../..", features = [] } -iced_native = { path = "../../native" } +iced = { path = "../..", features = ["advanced"] } diff --git a/examples/modal/src/main.rs b/examples/modal/src/main.rs index 49038475..f48afb69 100644 --- a/examples/modal/src/main.rs +++ b/examples/modal/src/main.rs @@ -176,12 +176,15 @@ impl App { } mod modal { - use iced_native::alignment::Alignment; - use iced_native::widget::{self, Tree}; - use iced_native::{ - event, layout, mouse, overlay, renderer, Clipboard, Color, Element, - Event, Layout, Length, Point, Rectangle, Shell, Size, Widget, - }; + use iced::advanced::layout::{self, Layout}; + use iced::advanced::overlay; + use iced::advanced::renderer; + use iced::advanced::widget::{self, Widget}; + use iced::advanced::{self, Clipboard, Shell}; + use iced::alignment::Alignment; + use iced::event; + use iced::mouse; + use iced::{Color, Element, Event, Length, Point, Rectangle, Size}; /// A widget that centers a modal element over some base element pub struct Modal<'a, Message, Renderer> { @@ -216,14 +219,17 @@ mod modal { impl<'a, Message, Renderer> Widget<Message, Renderer> for Modal<'a, Message, Renderer> where - Renderer: iced_native::Renderer, + Renderer: advanced::Renderer, Message: Clone, { - fn children(&self) -> Vec<Tree> { - vec![Tree::new(&self.base), Tree::new(&self.modal)] + fn children(&self) -> Vec<widget::Tree> { + vec![ + widget::Tree::new(&self.base), + widget::Tree::new(&self.modal), + ] } - fn diff(&self, tree: &mut Tree) { + fn diff(&self, tree: &mut widget::Tree) { tree.diff_children(&[&self.base, &self.modal]); } @@ -245,7 +251,7 @@ mod modal { fn on_event( &mut self, - state: &mut Tree, + state: &mut widget::Tree, event: Event, layout: Layout<'_>, cursor_position: Point, @@ -266,9 +272,9 @@ mod modal { fn draw( &self, - state: &Tree, + state: &widget::Tree, renderer: &mut Renderer, - theme: &<Renderer as iced_native::Renderer>::Theme, + theme: &<Renderer as advanced::Renderer>::Theme, style: &renderer::Style, layout: Layout<'_>, cursor_position: Point, @@ -287,7 +293,7 @@ mod modal { fn overlay<'b>( &'b mut self, - state: &'b mut Tree, + state: &'b mut widget::Tree, layout: Layout<'_>, _renderer: &Renderer, ) -> Option<overlay::Element<'b, Message, Renderer>> { @@ -304,7 +310,7 @@ mod modal { fn mouse_interaction( &self, - state: &Tree, + state: &widget::Tree, layout: Layout<'_>, cursor_position: Point, viewport: &Rectangle, @@ -321,7 +327,7 @@ mod modal { fn operate( &self, - state: &mut Tree, + state: &mut widget::Tree, layout: Layout<'_>, renderer: &Renderer, operation: &mut dyn widget::Operation<Message>, @@ -337,7 +343,7 @@ mod modal { struct Overlay<'a, 'b, Message, Renderer> { content: &'b mut Element<'a, Message, Renderer>, - tree: &'b mut Tree, + tree: &'b mut widget::Tree, size: Size, on_blur: Option<Message>, } @@ -345,7 +351,7 @@ mod modal { impl<'a, 'b, Message, Renderer> overlay::Overlay<Message, Renderer> for Overlay<'a, 'b, Message, Renderer> where - Renderer: iced_native::Renderer, + Renderer: advanced::Renderer, Message: Clone, { fn layout( @@ -467,7 +473,7 @@ mod modal { impl<'a, Message, Renderer> From<Modal<'a, Message, Renderer>> for Element<'a, Message, Renderer> where - Renderer: 'a + iced_native::Renderer, + Renderer: 'a + advanced::Renderer, Message: 'a + Clone, { fn from(modal: Modal<'a, Message, Renderer>) -> Self { diff --git a/examples/modern_art/src/main.rs b/examples/modern_art/src/main.rs index 28ed3e21..a43a2b2b 100644 --- a/examples/modern_art/src/main.rs +++ b/examples/modern_art/src/main.rs @@ -55,17 +55,18 @@ impl Application for ModernArt { } } -impl<Message> canvas::Program<Message> for ModernArt { +impl<Message> canvas::Program<Message, Renderer> for ModernArt { type State = (); fn draw( &self, _state: &Self::State, + renderer: &Renderer, _theme: &Theme, bounds: Rectangle, _cursor: Cursor, ) -> Vec<Geometry> { - let geometry = self.cache.draw(bounds.size(), |frame| { + let geometry = self.cache.draw(renderer, bounds.size(), |frame| { let num_squares = thread_rng().gen_range(0..1200); let mut i = 0; diff --git a/examples/multitouch/src/main.rs b/examples/multitouch/src/main.rs index f5faae0f..7df6c929 100644 --- a/examples/multitouch/src/main.rs +++ b/examples/multitouch/src/main.rs @@ -6,7 +6,7 @@ use iced::widget::canvas::stroke::{self, Stroke}; use iced::widget::canvas::{self, Canvas, Cursor, Geometry}; use iced::{ executor, touch, window, Application, Color, Command, Element, Length, - Point, Rectangle, Settings, Subscription, Theme, + Point, Rectangle, Renderer, Settings, Subscription, Theme, }; use std::collections::HashMap; @@ -95,7 +95,7 @@ impl Application for Multitouch { } } -impl canvas::Program<Message> for State { +impl canvas::Program<Message, Renderer> for State { type State = (); fn update( @@ -125,11 +125,12 @@ impl canvas::Program<Message> for State { fn draw( &self, _state: &Self::State, + renderer: &Renderer, _theme: &Theme, bounds: Rectangle, _cursor: Cursor, ) -> Vec<Geometry> { - let fingerweb = self.cache.draw(bounds.size(), |frame| { + let fingerweb = self.cache.draw(renderer, bounds.size(), |frame| { if self.fingers.len() < 2 { return; } diff --git a/examples/pane_grid/Cargo.toml b/examples/pane_grid/Cargo.toml index dfd6dfa9..4c0bf072 100644 --- a/examples/pane_grid/Cargo.toml +++ b/examples/pane_grid/Cargo.toml @@ -6,6 +6,4 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../..", features = ["debug"] } -iced_native = { path = "../../native" } -iced_lazy = { path = "../../lazy" } +iced = { path = "../..", features = ["debug", "lazy"] } diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs index c9f1376c..dfb80853 100644 --- a/examples/pane_grid/src/main.rs +++ b/examples/pane_grid/src/main.rs @@ -1,14 +1,16 @@ use iced::alignment::{self, Alignment}; +use iced::event::{self, Event}; use iced::executor; use iced::keyboard; +use iced::subscription; use iced::theme::{self, Theme}; use iced::widget::pane_grid::{self, PaneGrid}; -use iced::widget::{button, column, container, row, scrollable, text}; +use iced::widget::{ + button, column, container, responsive, row, scrollable, text, +}; use iced::{ Application, Color, Command, Element, Length, Settings, Size, Subscription, }; -use iced_lazy::responsive; -use iced_native::{event, subscription, Event}; pub fn main() -> iced::Result { Example::run(Settings::default()) diff --git a/examples/sierpinski_triangle/src/main.rs b/examples/sierpinski_triangle/src/main.rs index 1d25d171..4faac6d6 100644 --- a/examples/sierpinski_triangle/src/main.rs +++ b/examples/sierpinski_triangle/src/main.rs @@ -5,8 +5,8 @@ use iced::widget::canvas::event::{self, Event}; use iced::widget::canvas::{self, Canvas}; use iced::widget::{column, row, slider, text}; use iced::{ - Application, Color, Command, Length, Point, Rectangle, Settings, Size, - Theme, + Application, Color, Command, Length, Point, Rectangle, Renderer, Settings, + Size, Theme, }; use rand::Rng; @@ -134,11 +134,12 @@ impl canvas::Program<Message> for SierpinskiGraph { fn draw( &self, _state: &Self::State, + renderer: &Renderer, _theme: &Theme, bounds: Rectangle, _cursor: canvas::Cursor, ) -> Vec<canvas::Geometry> { - let geom = self.cache.draw(bounds.size(), |frame| { + let geom = self.cache.draw(renderer, bounds.size(), |frame| { frame.stroke( &canvas::Path::rectangle(Point::ORIGIN, frame.size()), canvas::Stroke::default(), diff --git a/examples/solar_system/src/main.rs b/examples/solar_system/src/main.rs index 9a4ee754..f2606feb 100644 --- a/examples/solar_system/src/main.rs +++ b/examples/solar_system/src/main.rs @@ -15,8 +15,8 @@ use iced::widget::canvas::stroke::{self, Stroke}; use iced::widget::canvas::{Cursor, Path}; use iced::window; use iced::{ - Application, Color, Command, Element, Length, Point, Rectangle, Settings, - Size, Subscription, Vector, + Application, Color, Command, Element, Length, Point, Rectangle, Renderer, + Settings, Size, Subscription, Vector, }; use std::time::Instant; @@ -156,24 +156,26 @@ impl<Message> canvas::Program<Message> for State { fn draw( &self, _state: &Self::State, + renderer: &Renderer, _theme: &Theme, bounds: Rectangle, _cursor: Cursor, ) -> Vec<canvas::Geometry> { use std::f32::consts::PI; - let background = self.space_cache.draw(bounds.size(), |frame| { - let stars = Path::new(|path| { - for (p, size) in &self.stars { - path.rectangle(*p, Size::new(*size, *size)); - } - }); + let background = + self.space_cache.draw(renderer, bounds.size(), |frame| { + let stars = Path::new(|path| { + for (p, size) in &self.stars { + path.rectangle(*p, Size::new(*size, *size)); + } + }); - frame.translate(frame.center() - Point::ORIGIN); - frame.fill(&stars, Color::WHITE); - }); + frame.translate(frame.center() - Point::ORIGIN); + frame.fill(&stars, Color::WHITE); + }); - let system = self.system_cache.draw(bounds.size(), |frame| { + let system = self.system_cache.draw(renderer, bounds.size(), |frame| { let center = frame.center(); let sun = Path::circle(center, Self::SUN_RADIUS); diff --git a/examples/toast/Cargo.toml b/examples/toast/Cargo.toml index f1f986aa..f703572c 100644 --- a/examples/toast/Cargo.toml +++ b/examples/toast/Cargo.toml @@ -6,5 +6,4 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../..", features = [] } -iced_native = { path = "../../native" } +iced = { path = "../..", features = ["advanced"] } diff --git a/examples/toast/src/main.rs b/examples/toast/src/main.rs index b4b4e007..9d859258 100644 --- a/examples/toast/src/main.rs +++ b/examples/toast/src/main.rs @@ -178,17 +178,23 @@ mod toast { use std::fmt; use std::time::{Duration, Instant}; + use iced::advanced; + use iced::advanced::layout::{self, Layout}; + use iced::advanced::overlay; + use iced::advanced::renderer; + use iced::advanced::widget::{self, Operation, Tree}; + use iced::advanced::{Clipboard, Shell, Widget}; + use iced::event::{self, Event}; + use iced::mouse; use iced::theme; use iced::widget::{ button, column, container, horizontal_rule, horizontal_space, row, text, }; + use iced::window; use iced::{ Alignment, Element, Length, Point, Rectangle, Renderer, Size, Theme, Vector, }; - use iced_native::widget::{tree, Operation, Tree}; - use iced_native::{event, layout, mouse, overlay, renderer, window}; - use iced_native::{Clipboard, Event, Layout, Shell, Widget}; pub const DEFAULT_TIMEOUT: u64 = 5; @@ -326,13 +332,13 @@ mod toast { self.content.as_widget().layout(renderer, limits) } - fn tag(&self) -> tree::Tag { + fn tag(&self) -> widget::tree::Tag { struct Marker(Vec<Instant>); - iced_native::widget::tree::Tag::of::<Marker>() + widget::tree::Tag::of::<Marker>() } - fn state(&self) -> tree::State { - iced_native::widget::tree::State::new(Vec::<Option<Instant>>::new()) + fn state(&self) -> widget::tree::State { + widget::tree::State::new(Vec::<Option<Instant>>::new()) } fn children(&self) -> Vec<Tree> { @@ -586,7 +592,7 @@ mod toast { fn draw( &self, renderer: &mut Renderer, - theme: &<Renderer as iced_native::Renderer>::Theme, + theme: &<Renderer as advanced::Renderer>::Theme, style: &renderer::Style, layout: Layout<'_>, cursor_position: Point, @@ -615,7 +621,7 @@ mod toast { &mut self, layout: Layout<'_>, renderer: &Renderer, - operation: &mut dyn iced_native::widget::Operation<Message>, + operation: &mut dyn widget::Operation<Message>, ) { operation.container(None, &mut |operation| { self.toasts diff --git a/examples/todos/fonts/icons.ttf b/examples/todos/fonts/icons.ttf Binary files differindex 4498299d..7b65fd36 100644 --- a/examples/todos/fonts/icons.ttf +++ b/examples/todos/fonts/icons.ttf diff --git a/examples/todos/src/main.rs b/examples/todos/src/main.rs index 99cdb8f9..6ad7b4fb 100644 --- a/examples/todos/src/main.rs +++ b/examples/todos/src/main.rs @@ -1,5 +1,6 @@ use iced::alignment::{self, Alignment}; use iced::event::{self, Event}; +use iced::font::{self, Font}; use iced::keyboard::{self, KeyCode, Modifiers}; use iced::subscription; use iced::theme::{self, Theme}; @@ -9,7 +10,7 @@ use iced::widget::{ }; use iced::window; use iced::{Application, Element}; -use iced::{Color, Command, Font, Length, Settings, Subscription}; +use iced::{Color, Command, Length, Settings, Subscription}; use once_cell::sync::Lazy; use serde::{Deserialize, Serialize}; @@ -44,6 +45,7 @@ struct State { #[derive(Debug, Clone)] enum Message { Loaded(Result<SavedState, LoadError>), + FontLoaded(Result<(), font::Error>), Saved(Result<(), SaveError>), InputChanged(String), CreateTask, @@ -62,7 +64,11 @@ impl Application for Todos { fn new(_flags: ()) -> (Todos, Command<Message>) { ( Todos::Loading, - Command::perform(SavedState::load(), Message::Loaded), + Command::batch(vec![ + font::load(include_bytes!("../fonts/icons.ttf").as_slice()) + .map(Message::FontLoaded), + Command::perform(SavedState::load(), Message::Loaded), + ]), ) } @@ -358,7 +364,8 @@ impl Task { self.completed, TaskMessage::Completed, ) - .width(Length::Fill); + .width(Length::Fill) + .text_shaping(text::Shaping::Advanced); row![ checkbox, @@ -381,10 +388,14 @@ impl Task { row![ text_input, - button(row![delete_icon(), "Delete"].spacing(10)) - .on_press(TaskMessage::Delete) - .padding(10) - .style(theme::Button::Destructive) + button( + row![delete_icon(), "Delete"] + .spacing(10) + .align_items(Alignment::Center) + ) + .on_press(TaskMessage::Delete) + .padding(10) + .style(theme::Button::Destructive) ] .spacing(20) .align_items(Alignment::Center) @@ -398,7 +409,7 @@ fn view_controls(tasks: &[Task], current_filter: Filter) -> Element<Message> { let tasks_left = tasks.iter().filter(|task| !task.completed).count(); let filter_button = |label, filter, current_filter| { - let label = text(label).size(16); + let label = text(label); let button = button(label).style(if filter == current_filter { theme::Button::Primary @@ -415,8 +426,7 @@ fn view_controls(tasks: &[Task], current_filter: Filter) -> Element<Message> { tasks_left, if tasks_left == 1 { "task" } else { "tasks" } )) - .width(Length::Fill) - .size(16), + .width(Length::Fill), row![ filter_button("All", Filter::All, current_filter), filter_button("Active", Filter::Active, current_filter), @@ -477,17 +487,13 @@ fn empty_message(message: &str) -> Element<'_, Message> { } // Fonts -const ICONS: Font = Font::External { - name: "Icons", - bytes: include_bytes!("../../todos/fonts/icons.ttf"), -}; +const ICONS: Font = Font::with_name("Iced-Todos-Icons"); fn icon(unicode: char) -> Text<'static> { text(unicode.to_string()) .font(ICONS) .width(20) .horizontal_alignment(alignment::Horizontal::Center) - .size(20) } fn edit_icon() -> Text<'static> { diff --git a/examples/tour/fonts/icons.ttf b/examples/tour/fonts/icons.ttf Binary files differdeleted file mode 100644 index bfe8a24b..00000000 --- a/examples/tour/fonts/icons.ttf +++ /dev/null diff --git a/examples/tour/src/main.rs b/examples/tour/src/main.rs index 16ee19c0..9c38ad0e 100644 --- a/examples/tour/src/main.rs +++ b/examples/tour/src/main.rs @@ -565,11 +565,6 @@ impl<'a> Step { is_secure: bool, is_showing_icon: bool, ) -> Column<'a, StepMessage> { - const ICON_FONT: Font = Font::External { - name: "Icons", - bytes: include_bytes!("../fonts/icons.ttf"), - }; - let mut text_input = text_input("Type something to continue...", value) .on_input(StepMessage::InputChanged) .padding(10) @@ -577,9 +572,9 @@ impl<'a> Step { if is_showing_icon { text_input = text_input.icon(text_input::Icon { - font: ICON_FONT, - code_point: '\u{E900}', - size: Some(35.0), + font: Font::default(), + code_point: '🚀', + size: Some(28.0), spacing: 10.0, side: text_input::Side::Right, }); diff --git a/examples/url_handler/Cargo.toml b/examples/url_handler/Cargo.toml index 63c7ec27..4dcff92d 100644 --- a/examples/url_handler/Cargo.toml +++ b/examples/url_handler/Cargo.toml @@ -7,4 +7,3 @@ publish = false [dependencies] iced = { path = "../.." } -iced_native = { path = "../../native" } diff --git a/examples/url_handler/src/main.rs b/examples/url_handler/src/main.rs index 3257b519..f63fa06a 100644 --- a/examples/url_handler/src/main.rs +++ b/examples/url_handler/src/main.rs @@ -1,12 +1,10 @@ +use iced::event::{Event, MacOS, PlatformSpecific}; use iced::executor; +use iced::subscription; use iced::widget::{container, text}; use iced::{ Application, Command, Element, Length, Settings, Subscription, Theme, }; -use iced_native::{ - event::{MacOS, PlatformSpecific}, - Event, -}; pub fn main() -> iced::Result { App::run(Settings::default()) @@ -19,7 +17,7 @@ struct App { #[derive(Debug, Clone)] enum Message { - EventOccurred(iced_native::Event), + EventOccurred(Event), } impl Application for App { @@ -52,7 +50,7 @@ impl Application for App { } fn subscription(&self) -> Subscription<Message> { - iced_native::subscription::events().map(Message::EventOccurred) + subscription::events().map(Message::EventOccurred) } fn view(&self) -> Element<Message> { diff --git a/examples/websocket/Cargo.toml b/examples/websocket/Cargo.toml index c25f067b..03b240c6 100644 --- a/examples/websocket/Cargo.toml +++ b/examples/websocket/Cargo.toml @@ -7,8 +7,6 @@ publish = false [dependencies] iced = { path = "../..", features = ["tokio", "debug"] } -iced_native = { path = "../../native" } -iced_futures = { path = "../../futures" } once_cell = "1.15" [dependencies.async-tungstenite] diff --git a/examples/websocket/src/echo.rs b/examples/websocket/src/echo.rs index f9807172..281ed4bd 100644 --- a/examples/websocket/src/echo.rs +++ b/examples/websocket/src/echo.rs @@ -1,7 +1,7 @@ pub mod server; -use iced_futures::futures; -use iced_native::subscription::{self, Subscription}; +use iced::futures; +use iced::subscription::{self, Subscription}; use futures::channel::mpsc; use futures::sink::SinkExt; diff --git a/examples/websocket/src/echo/server.rs b/examples/websocket/src/echo/server.rs index dd234984..168a635e 100644 --- a/examples/websocket/src/echo/server.rs +++ b/examples/websocket/src/echo/server.rs @@ -1,4 +1,4 @@ -use iced_futures::futures; +use iced::futures; use futures::channel::mpsc; use futures::{SinkExt, StreamExt}; diff --git a/futures/Cargo.toml b/futures/Cargo.toml index e4d355ee..f636a304 100644 --- a/futures/Cargo.toml +++ b/futures/Cargo.toml @@ -16,6 +16,10 @@ thread-pool = ["futures/thread-pool"] [dependencies] log = "0.4" +[dependencies.iced_core] +version = "0.9" +path = "../core" + [dependencies.futures] version = "0.3" diff --git a/futures/src/backend/native/async_std.rs b/futures/src/backend/native/async_std.rs index b324dbf1..52b0e914 100644 --- a/futures/src/backend/native/async_std.rs +++ b/futures/src/backend/native/async_std.rs @@ -18,28 +18,26 @@ impl crate::Executor for Executor { pub mod time { //! Listen and react to time. + use crate::core::Hasher; use crate::subscription::{self, Subscription}; /// Returns a [`Subscription`] that produces messages at a set interval. /// /// The first message is produced after a `duration`, and then continues to /// produce more messages every `duration` after that. - pub fn every<H: std::hash::Hasher, E>( + pub fn every( duration: std::time::Duration, - ) -> Subscription<H, E, std::time::Instant> { + ) -> Subscription<std::time::Instant> { Subscription::from_recipe(Every(duration)) } #[derive(Debug)] struct Every(std::time::Duration); - impl<H, E> subscription::Recipe<H, E> for Every - where - H: std::hash::Hasher, - { + impl subscription::Recipe for Every { type Output = std::time::Instant; - fn hash(&self, state: &mut H) { + fn hash(&self, state: &mut Hasher) { use std::hash::Hash; std::any::TypeId::of::<Self>().hash(state); @@ -48,7 +46,7 @@ pub mod time { fn stream( self: Box<Self>, - _input: futures::stream::BoxStream<'static, E>, + _input: subscription::EventStream, ) -> futures::stream::BoxStream<'static, Self::Output> { use futures::stream::StreamExt; diff --git a/futures/src/backend/native/smol.rs b/futures/src/backend/native/smol.rs index d5201cde..30bc8291 100644 --- a/futures/src/backend/native/smol.rs +++ b/futures/src/backend/native/smol.rs @@ -19,28 +19,26 @@ impl crate::Executor for Executor { pub mod time { //! Listen and react to time. + use crate::core::Hasher; use crate::subscription::{self, Subscription}; /// Returns a [`Subscription`] that produces messages at a set interval. /// /// The first message is produced after a `duration`, and then continues to /// produce more messages every `duration` after that. - pub fn every<H: std::hash::Hasher, E>( + pub fn every( duration: std::time::Duration, - ) -> Subscription<H, E, std::time::Instant> { + ) -> Subscription<std::time::Instant> { Subscription::from_recipe(Every(duration)) } #[derive(Debug)] struct Every(std::time::Duration); - impl<H, E> subscription::Recipe<H, E> for Every - where - H: std::hash::Hasher, - { + impl subscription::Recipe for Every { type Output = std::time::Instant; - fn hash(&self, state: &mut H) { + fn hash(&self, state: &mut Hasher) { use std::hash::Hash; std::any::TypeId::of::<Self>().hash(state); @@ -49,7 +47,7 @@ pub mod time { fn stream( self: Box<Self>, - _input: futures::stream::BoxStream<'static, E>, + _input: subscription::EventStream, ) -> futures::stream::BoxStream<'static, Self::Output> { use futures::stream::StreamExt; diff --git a/futures/src/backend/native/tokio.rs b/futures/src/backend/native/tokio.rs index dd818bd1..4698a105 100644 --- a/futures/src/backend/native/tokio.rs +++ b/futures/src/backend/native/tokio.rs @@ -22,28 +22,26 @@ impl crate::Executor for Executor { pub mod time { //! Listen and react to time. + use crate::core::Hasher; use crate::subscription::{self, Subscription}; /// Returns a [`Subscription`] that produces messages at a set interval. /// /// The first message is produced after a `duration`, and then continues to /// produce more messages every `duration` after that. - pub fn every<H: std::hash::Hasher, E>( + pub fn every( duration: std::time::Duration, - ) -> Subscription<H, E, std::time::Instant> { + ) -> Subscription<std::time::Instant> { Subscription::from_recipe(Every(duration)) } #[derive(Debug)] struct Every(std::time::Duration); - impl<H, E> subscription::Recipe<H, E> for Every - where - H: std::hash::Hasher, - { + impl subscription::Recipe for Every { type Output = std::time::Instant; - fn hash(&self, state: &mut H) { + fn hash(&self, state: &mut Hasher) { use std::hash::Hash; std::any::TypeId::of::<Self>().hash(state); @@ -52,7 +50,7 @@ pub mod time { fn stream( self: Box<Self>, - _input: futures::stream::BoxStream<'static, E>, + _input: subscription::EventStream, ) -> futures::stream::BoxStream<'static, Self::Output> { use futures::stream::StreamExt; diff --git a/futures/src/backend/wasm/wasm_bindgen.rs b/futures/src/backend/wasm/wasm_bindgen.rs index b726501a..2666f1b4 100644 --- a/futures/src/backend/wasm/wasm_bindgen.rs +++ b/futures/src/backend/wasm/wasm_bindgen.rs @@ -16,6 +16,7 @@ impl crate::Executor for Executor { pub mod time { //! Listen and react to time. + use crate::core::Hasher; use crate::subscription::{self, Subscription}; use crate::BoxStream; @@ -23,22 +24,19 @@ pub mod time { /// /// The first message is produced after a `duration`, and then continues to /// produce more messages every `duration` after that. - pub fn every<H: std::hash::Hasher, E>( + pub fn every( duration: std::time::Duration, - ) -> Subscription<H, E, wasm_timer::Instant> { + ) -> Subscription<wasm_timer::Instant> { Subscription::from_recipe(Every(duration)) } #[derive(Debug)] struct Every(std::time::Duration); - impl<H, E> subscription::Recipe<H, E> for Every - where - H: std::hash::Hasher, - { + impl subscription::Recipe for Every { type Output = wasm_timer::Instant; - fn hash(&self, state: &mut H) { + fn hash(&self, state: &mut Hasher) { use std::hash::Hash; std::any::TypeId::of::<Self>().hash(state); @@ -47,7 +45,7 @@ pub mod time { fn stream( self: Box<Self>, - _input: BoxStream<E>, + _input: subscription::EventStream, ) -> BoxStream<Self::Output> { use futures::stream::StreamExt; diff --git a/futures/src/command.rs b/futures/src/command.rs deleted file mode 100644 index 3d1ec3f9..00000000 --- a/futures/src/command.rs +++ /dev/null @@ -1,70 +0,0 @@ -/// A set of asynchronous actions to be performed by some runtime. -#[must_use = "`Command` must be returned to runtime to take effect"] -#[derive(Debug)] -pub struct Command<T>(Internal<T>); - -#[derive(Debug)] -enum Internal<T> { - None, - Single(T), - Batch(Vec<T>), -} - -impl<T> Command<T> { - /// Creates an empty [`Command`]. - /// - /// In other words, a [`Command`] that does nothing. - pub const fn none() -> Self { - Self(Internal::None) - } - - /// Creates a [`Command`] that performs a single action. - pub const fn single(action: T) -> Self { - Self(Internal::Single(action)) - } - - /// Creates a [`Command`] that performs the actions of all the given - /// commands. - /// - /// Once this command is run, all the commands will be executed at once. - pub fn batch(commands: impl IntoIterator<Item = Command<T>>) -> Self { - let mut batch = Vec::new(); - - for Command(command) in commands { - match command { - Internal::None => {} - Internal::Single(command) => batch.push(command), - Internal::Batch(commands) => batch.extend(commands), - } - } - - Self(Internal::Batch(batch)) - } - - /// Applies a transformation to the result of a [`Command`]. - pub fn map<A>(self, f: impl Fn(T) -> A) -> Command<A> - where - T: 'static, - { - let Command(command) = self; - - match command { - Internal::None => Command::none(), - Internal::Single(action) => Command::single(f(action)), - Internal::Batch(batch) => { - Command(Internal::Batch(batch.into_iter().map(f).collect())) - } - } - } - - /// Returns all of the actions of the [`Command`]. - pub fn actions(self) -> Vec<T> { - let Command(command) = self; - - match command { - Internal::None => Vec::new(), - Internal::Single(action) => vec![action], - Internal::Batch(batch) => batch, - } - } -} diff --git a/futures/src/lib.rs b/futures/src/lib.rs index c0982db7..397fc2d2 100644 --- a/futures/src/lib.rs +++ b/futures/src/lib.rs @@ -18,8 +18,8 @@ #![allow(clippy::inherent_to_string, clippy::type_complexity)] #![cfg_attr(docsrs, feature(doc_cfg))] pub use futures; +pub use iced_core as core; -mod command; mod maybe_send; mod runtime; @@ -27,7 +27,6 @@ pub mod backend; pub mod executor; pub mod subscription; -pub use command::Command; pub use executor::Executor; pub use maybe_send::MaybeSend; pub use platform::*; diff --git a/futures/src/runtime.rs b/futures/src/runtime.rs index 24f9f241..2241a494 100644 --- a/futures/src/runtime.rs +++ b/futures/src/runtime.rs @@ -1,6 +1,7 @@ //! Run commands and keep track of subscriptions. +use crate::core::event::{self, Event}; use crate::subscription; -use crate::{BoxFuture, Executor, MaybeSend, Subscription}; +use crate::{BoxFuture, Executor, MaybeSend}; use futures::{channel::mpsc, Sink}; use std::marker::PhantomData; @@ -12,18 +13,15 @@ use std::marker::PhantomData; /// /// [`Command`]: crate::Command #[derive(Debug)] -pub struct Runtime<Hasher, Event, Executor, Sender, Message> { +pub struct Runtime<Executor, Sender, Message> { executor: Executor, sender: Sender, - subscriptions: subscription::Tracker<Hasher, Event>, + subscriptions: subscription::Tracker, _message: PhantomData<Message>, } -impl<Hasher, Event, Executor, Sender, Message> - Runtime<Hasher, Event, Executor, Sender, Message> +impl<Executor, Sender, Message> Runtime<Executor, Sender, Message> where - Hasher: std::hash::Hasher + Default, - Event: Send + Clone + 'static, Executor: self::Executor, Sender: Sink<Message, Error = mpsc::SendError> + Unpin @@ -79,7 +77,9 @@ where /// [`Tracker::update`]: subscription::Tracker::update pub fn track( &mut self, - subscription: Subscription<Hasher, Event, Message>, + recipes: impl IntoIterator< + Item = Box<dyn subscription::Recipe<Output = Message>>, + >, ) { let Runtime { executor, @@ -88,8 +88,9 @@ where .. } = self; - let futures = executor - .enter(|| subscriptions.update(subscription, sender.clone())); + let futures = executor.enter(|| { + subscriptions.update(recipes.into_iter(), sender.clone()) + }); for future in futures { executor.spawn(future); @@ -102,7 +103,7 @@ where /// See [`Tracker::broadcast`] to learn more. /// /// [`Tracker::broadcast`]: subscription::Tracker::broadcast - pub fn broadcast(&mut self, event: Event) { - self.subscriptions.broadcast(event); + pub fn broadcast(&mut self, event: Event, status: event::Status) { + self.subscriptions.broadcast(event, status); } } diff --git a/futures/src/subscription.rs b/futures/src/subscription.rs index 18c66a5a..801c2694 100644 --- a/futures/src/subscription.rs +++ b/futures/src/subscription.rs @@ -3,7 +3,20 @@ mod tracker; pub use tracker::Tracker; -use crate::BoxStream; +use crate::core::event::{self, Event}; +use crate::core::window; +use crate::core::Hasher; +use crate::futures::{Future, Stream}; +use crate::{BoxStream, MaybeSend}; + +use futures::channel::mpsc; +use futures::never::Never; +use std::hash::Hash; + +/// A stream of runtime events. +/// +/// It is the input of a [`Subscription`]. +pub type EventStream = BoxStream<(Event, event::Status)>; /// A request to listen to external events. /// @@ -16,19 +29,13 @@ use crate::BoxStream; /// For instance, you can use a [`Subscription`] to listen to a WebSocket /// connection, keyboard presses, mouse events, time ticks, etc. /// -/// This type is normally aliased by runtimes with a specific `Event` and/or -/// `Hasher`. -/// /// [`Command`]: crate::Command #[must_use = "`Subscription` must be returned to runtime to take effect"] -pub struct Subscription<Hasher, Event, Output> { - recipes: Vec<Box<dyn Recipe<Hasher, Event, Output = Output>>>, +pub struct Subscription<Message> { + recipes: Vec<Box<dyn Recipe<Output = Message>>>, } -impl<H, E, O> Subscription<H, E, O> -where - H: std::hash::Hasher, -{ +impl<Message> Subscription<Message> { /// Returns an empty [`Subscription`] that will not produce any output. pub fn none() -> Self { Self { @@ -38,7 +45,7 @@ where /// Creates a [`Subscription`] from a [`Recipe`] describing it. pub fn from_recipe( - recipe: impl Recipe<H, E, Output = O> + 'static, + recipe: impl Recipe<Output = Message> + 'static, ) -> Self { Self { recipes: vec![Box::new(recipe)], @@ -48,7 +55,7 @@ where /// Batches all the provided subscriptions and returns the resulting /// [`Subscription`]. pub fn batch( - subscriptions: impl IntoIterator<Item = Subscription<H, E, O>>, + subscriptions: impl IntoIterator<Item = Subscription<Message>>, ) -> Self { Self { recipes: subscriptions @@ -59,18 +66,16 @@ where } /// Returns the different recipes of the [`Subscription`]. - pub fn recipes(self) -> Vec<Box<dyn Recipe<H, E, Output = O>>> { + pub fn into_recipes(self) -> Vec<Box<dyn Recipe<Output = Message>>> { self.recipes } /// Adds a value to the [`Subscription`] context. /// /// The value will be part of the identity of a [`Subscription`]. - pub fn with<T>(mut self, value: T) -> Subscription<H, E, (T, O)> + pub fn with<T>(mut self, value: T) -> Subscription<(T, Message)> where - H: 'static, - E: 'static, - O: 'static, + Message: 'static, T: std::hash::Hash + Clone + Send + Sync + 'static, { Subscription { @@ -79,18 +84,16 @@ where .drain(..) .map(|recipe| { Box::new(With::new(recipe, value.clone())) - as Box<dyn Recipe<H, E, Output = (T, O)>> + as Box<dyn Recipe<Output = (T, Message)>> }) .collect(), } } /// Transforms the [`Subscription`] output with the given function. - pub fn map<A>(mut self, f: fn(O) -> A) -> Subscription<H, E, A> + pub fn map<A>(mut self, f: fn(Message) -> A) -> Subscription<A> where - H: 'static, - E: 'static, - O: 'static, + Message: 'static, A: 'static, { Subscription { @@ -98,15 +101,14 @@ where .recipes .drain(..) .map(|recipe| { - Box::new(Map::new(recipe, f)) - as Box<dyn Recipe<H, E, Output = A>> + Box::new(Map::new(recipe, f)) as Box<dyn Recipe<Output = A>> }) .collect(), } } } -impl<I, O, H> std::fmt::Debug for Subscription<I, O, H> { +impl<Message> std::fmt::Debug for Subscription<Message> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("Subscription").finish() } @@ -129,7 +131,7 @@ impl<I, O, H> std::fmt::Debug for Subscription<I, O, H> { /// [examples]: https://github.com/iced-rs/iced/tree/0.9/examples /// [`download_progress`]: https://github.com/iced-rs/iced/tree/0.9/examples/download_progress /// [`stopwatch`]: https://github.com/iced-rs/iced/tree/0.9/examples/stopwatch -pub trait Recipe<Hasher: std::hash::Hasher, Event> { +pub trait Recipe { /// The events that will be produced by a [`Subscription`] with this /// [`Recipe`]. type Output; @@ -141,45 +143,33 @@ pub trait Recipe<Hasher: std::hash::Hasher, Event> { /// Executes the [`Recipe`] and produces the stream of events of its /// [`Subscription`]. - /// - /// It receives some stream of generic events, which is normally defined by - /// shells. - fn stream( - self: Box<Self>, - input: BoxStream<Event>, - ) -> BoxStream<Self::Output>; + fn stream(self: Box<Self>, input: EventStream) -> BoxStream<Self::Output>; } -struct Map<Hasher, Event, A, B> { - recipe: Box<dyn Recipe<Hasher, Event, Output = A>>, +struct Map<A, B> { + recipe: Box<dyn Recipe<Output = A>>, mapper: fn(A) -> B, } -impl<H, E, A, B> Map<H, E, A, B> { - fn new( - recipe: Box<dyn Recipe<H, E, Output = A>>, - mapper: fn(A) -> B, - ) -> Self { +impl<A, B> Map<A, B> { + fn new(recipe: Box<dyn Recipe<Output = A>>, mapper: fn(A) -> B) -> Self { Map { recipe, mapper } } } -impl<H, E, A, B> Recipe<H, E> for Map<H, E, A, B> +impl<A, B> Recipe for Map<A, B> where A: 'static, B: 'static, - H: std::hash::Hasher, { type Output = B; - fn hash(&self, state: &mut H) { - use std::hash::Hash; - + fn hash(&self, state: &mut Hasher) { self.recipe.hash(state); self.mapper.hash(state); } - fn stream(self: Box<Self>, input: BoxStream<E>) -> BoxStream<Self::Output> { + fn stream(self: Box<Self>, input: EventStream) -> BoxStream<Self::Output> { use futures::StreamExt; let mapper = self.mapper; @@ -188,34 +178,31 @@ where } } -struct With<Hasher, Event, A, B> { - recipe: Box<dyn Recipe<Hasher, Event, Output = A>>, +struct With<A, B> { + recipe: Box<dyn Recipe<Output = A>>, value: B, } -impl<H, E, A, B> With<H, E, A, B> { - fn new(recipe: Box<dyn Recipe<H, E, Output = A>>, value: B) -> Self { +impl<A, B> With<A, B> { + fn new(recipe: Box<dyn Recipe<Output = A>>, value: B) -> Self { With { recipe, value } } } -impl<H, E, A, B> Recipe<H, E> for With<H, E, A, B> +impl<A, B> Recipe for With<A, B> where A: 'static, B: 'static + std::hash::Hash + Clone + Send + Sync, - H: std::hash::Hasher, { type Output = (B, A); - fn hash(&self, state: &mut H) { - use std::hash::Hash; - + fn hash(&self, state: &mut Hasher) { std::any::TypeId::of::<B>().hash(state); self.value.hash(state); self.recipe.hash(state); } - fn stream(self: Box<Self>, input: BoxStream<E>) -> BoxStream<Self::Output> { + fn stream(self: Box<Self>, input: EventStream) -> BoxStream<Self::Output> { use futures::StreamExt; let value = self.value; @@ -227,3 +214,253 @@ where ) } } + +/// Returns a [`Subscription`] to all the ignored runtime events. +/// +/// This subscription will notify your application of any [`Event`] that was +/// not captured by any widget. +pub fn events() -> Subscription<Event> { + events_with(|event, status| match status { + event::Status::Ignored => Some(event), + event::Status::Captured => None, + }) +} + +/// Returns a [`Subscription`] that filters all the runtime events with the +/// provided function, producing messages accordingly. +/// +/// This subscription will call the provided function for every [`Event`] +/// handled by the runtime. If the function: +/// +/// - Returns `None`, the [`Event`] will be discarded. +/// - Returns `Some` message, the `Message` will be produced. +pub fn events_with<Message>( + f: fn(Event, event::Status) -> Option<Message>, +) -> Subscription<Message> +where + Message: 'static + MaybeSend, +{ + #[derive(Hash)] + struct EventsWith; + + Subscription::from_recipe(Runner { + id: (EventsWith, f), + spawn: move |events| { + use futures::future; + use futures::stream::StreamExt; + + events.filter_map(move |(event, status)| { + future::ready(match event { + Event::Window(window::Event::RedrawRequested(_)) => None, + _ => f(event, status), + }) + }) + }, + }) +} + +/// Returns a [`Subscription`] that produces a message for every runtime event, +/// including the redraw request events. +/// +/// **Warning:** This [`Subscription`], if unfiltered, may produce messages in +/// an infinite loop. +pub fn raw_events<Message>( + f: fn(Event, event::Status) -> Option<Message>, +) -> Subscription<Message> +where + Message: 'static + MaybeSend, +{ + #[derive(Hash)] + struct RawEvents; + + Subscription::from_recipe(Runner { + id: (RawEvents, f), + spawn: move |events| { + use futures::future; + use futures::stream::StreamExt; + + events.filter_map(move |(event, status)| { + future::ready(f(event, status)) + }) + }, + }) +} + +/// Returns a [`Subscription`] that will call the given function to create and +/// asynchronously run the given [`Stream`]. +pub fn run<S, Message>(builder: fn() -> S) -> Subscription<Message> +where + S: Stream<Item = Message> + MaybeSend + 'static, + Message: 'static, +{ + Subscription::from_recipe(Runner { + id: builder, + spawn: move |_| builder(), + }) +} + +/// Returns a [`Subscription`] that will create and asynchronously run the +/// given [`Stream`]. +/// +/// The `id` will be used to uniquely identify the [`Subscription`]. +pub fn run_with_id<I, S, Message>(id: I, stream: S) -> Subscription<Message> +where + I: Hash + 'static, + S: Stream<Item = Message> + MaybeSend + 'static, + Message: 'static, +{ + Subscription::from_recipe(Runner { + id, + spawn: move |_| stream, + }) +} + +/// Returns a [`Subscription`] that will create and asynchronously run a +/// [`Stream`] that will call the provided closure to produce every `Message`. +/// +/// The `id` will be used to uniquely identify the [`Subscription`]. +pub fn unfold<I, T, Fut, Message>( + id: I, + initial: T, + mut f: impl FnMut(T) -> Fut + MaybeSend + Sync + 'static, +) -> Subscription<Message> +where + I: Hash + 'static, + T: MaybeSend + 'static, + Fut: Future<Output = (Message, T)> + MaybeSend + 'static, + Message: 'static + MaybeSend, +{ + use futures::future::FutureExt; + + run_with_id( + id, + futures::stream::unfold(initial, move |state| f(state).map(Some)), + ) +} + +/// Creates a [`Subscription`] that publishes the events sent from a [`Future`] +/// to an [`mpsc::Sender`] with the given bounds. +/// +/// # Creating an asynchronous worker with bidirectional communication +/// You can leverage this helper to create a [`Subscription`] that spawns +/// an asynchronous worker in the background and establish a channel of +/// communication with an `iced` application. +/// +/// You can achieve this by creating an `mpsc` channel inside the closure +/// and returning the `Sender` as a `Message` for the `Application`: +/// +/// ``` +/// use iced_futures::subscription::{self, Subscription}; +/// use iced_futures::futures::channel::mpsc; +/// use iced_futures::futures::sink::SinkExt; +/// +/// pub enum Event { +/// Ready(mpsc::Sender<Input>), +/// WorkFinished, +/// // ... +/// } +/// +/// enum Input { +/// DoSomeWork, +/// // ... +/// } +/// +/// enum State { +/// Starting, +/// Ready(mpsc::Receiver<Input>), +/// } +/// +/// fn some_worker() -> Subscription<Event> { +/// struct SomeWorker; +/// +/// subscription::channel(std::any::TypeId::of::<SomeWorker>(), 100, |mut output| async move { +/// let mut state = State::Starting; +/// +/// loop { +/// match &mut state { +/// State::Starting => { +/// // Create channel +/// let (sender, receiver) = mpsc::channel(100); +/// +/// // Send the sender back to the application +/// output.send(Event::Ready(sender)).await; +/// +/// // We are ready to receive messages +/// state = State::Ready(receiver); +/// } +/// State::Ready(receiver) => { +/// use iced_futures::futures::StreamExt; +/// +/// // Read next input sent from `Application` +/// let input = receiver.select_next_some().await; +/// +/// match input { +/// Input::DoSomeWork => { +/// // Do some async work... +/// +/// // Finally, we can optionally produce a message to tell the +/// // `Application` the work is done +/// output.send(Event::WorkFinished).await; +/// } +/// } +/// } +/// } +/// } +/// }) +/// } +/// ``` +/// +/// Check out the [`websocket`] example, which showcases this pattern to maintain a WebSocket +/// connection open. +/// +/// [`websocket`]: https://github.com/iced-rs/iced/tree/0.9/examples/websocket +pub fn channel<I, Fut, Message>( + id: I, + size: usize, + f: impl Fn(mpsc::Sender<Message>) -> Fut + MaybeSend + Sync + 'static, +) -> Subscription<Message> +where + I: Hash + 'static, + Fut: Future<Output = Never> + MaybeSend + 'static, + Message: 'static + MaybeSend, +{ + use futures::stream::{self, StreamExt}; + + Subscription::from_recipe(Runner { + id, + spawn: move |_| { + let (sender, receiver) = mpsc::channel(size); + + let runner = stream::once(f(sender)).map(|_| unreachable!()); + + stream::select(receiver, runner) + }, + }) +} + +struct Runner<I, F, S, Message> +where + F: FnOnce(EventStream) -> S, + S: Stream<Item = Message>, +{ + id: I, + spawn: F, +} + +impl<I, S, F, Message> Recipe for Runner<I, F, S, Message> +where + I: Hash + 'static, + F: FnOnce(EventStream) -> S, + S: Stream<Item = Message> + MaybeSend + 'static, +{ + type Output = Message; + + fn hash(&self, state: &mut Hasher) { + std::any::TypeId::of::<I>().hash(state); + self.id.hash(state); + } + + fn stream(self: Box<Self>, input: EventStream) -> BoxStream<Self::Output> { + crate::boxed_stream((self.spawn)(input)) + } +} diff --git a/futures/src/subscription/tracker.rs b/futures/src/subscription/tracker.rs index 9fe110b0..ae71cd25 100644 --- a/futures/src/subscription/tracker.rs +++ b/futures/src/subscription/tracker.rs @@ -1,38 +1,35 @@ -use crate::{BoxFuture, MaybeSend, Subscription}; +use crate::core::event::{self, Event}; +use crate::core::Hasher; +use crate::subscription::Recipe; +use crate::{BoxFuture, MaybeSend}; -use futures::{ - channel::mpsc, - sink::{Sink, SinkExt}, -}; -use std::{collections::HashMap, marker::PhantomData}; +use futures::channel::mpsc; +use futures::sink::{Sink, SinkExt}; + +use std::collections::HashMap; +use std::hash::Hasher as _; /// A registry of subscription streams. /// /// If you have an application that continuously returns a [`Subscription`], /// you can use a [`Tracker`] to keep track of the different recipes and keep /// its executions alive. -#[derive(Debug)] -pub struct Tracker<Hasher, Event> { - subscriptions: HashMap<u64, Execution<Event>>, - _hasher: PhantomData<Hasher>, +#[derive(Debug, Default)] +pub struct Tracker { + subscriptions: HashMap<u64, Execution>, } #[derive(Debug)] -pub struct Execution<Event> { +pub struct Execution { _cancel: futures::channel::oneshot::Sender<()>, - listener: Option<futures::channel::mpsc::Sender<Event>>, + listener: Option<futures::channel::mpsc::Sender<(Event, event::Status)>>, } -impl<Hasher, Event> Tracker<Hasher, Event> -where - Hasher: std::hash::Hasher + Default, - Event: 'static + Send + Clone, -{ +impl Tracker { /// Creates a new empty [`Tracker`]. pub fn new() -> Self { Self { subscriptions: HashMap::new(), - _hasher: PhantomData, } } @@ -56,7 +53,7 @@ where /// [`Recipe`]: crate::subscription::Recipe pub fn update<Message, Receiver>( &mut self, - subscription: Subscription<Hasher, Event, Message>, + recipes: impl Iterator<Item = Box<dyn Recipe<Output = Message>>>, receiver: Receiver, ) -> Vec<BoxFuture<()>> where @@ -70,8 +67,6 @@ where use futures::stream::StreamExt; let mut futures: Vec<BoxFuture<()>> = Vec::new(); - - let recipes = subscription.recipes(); let mut alive = std::collections::HashSet::new(); for recipe in recipes { @@ -142,12 +137,12 @@ where /// currently open. /// /// [`Recipe::stream`]: crate::subscription::Recipe::stream - pub fn broadcast(&mut self, event: Event) { + pub fn broadcast(&mut self, event: Event, status: event::Status) { self.subscriptions .values_mut() .filter_map(|connection| connection.listener.as_mut()) .for_each(|listener| { - if let Err(error) = listener.try_send(event.clone()) { + if let Err(error) = listener.try_send((event.clone(), status)) { log::warn!( "Error sending event to subscription: {:?}", error @@ -156,13 +151,3 @@ where }); } } - -impl<Hasher, Event> Default for Tracker<Hasher, Event> -where - Hasher: std::hash::Hasher + Default, - Event: 'static + Send + Clone, -{ - fn default() -> Self { - Self::new() - } -} diff --git a/glow/Cargo.toml b/glow/Cargo.toml deleted file mode 100644 index facc668a..00000000 --- a/glow/Cargo.toml +++ /dev/null @@ -1,51 +0,0 @@ -[package] -name = "iced_glow" -version = "0.8.0" -authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] -edition = "2021" -description = "A glow renderer for iced" -license = "MIT AND OFL-1.1" -repository = "https://github.com/iced-rs/iced" - -[features] -svg = ["iced_graphics/svg"] -image = ["iced_graphics/image"] -png = ["iced_graphics/png"] -jpeg = ["iced_graphics/jpeg"] -jpeg_rayon = ["iced_graphics/jpeg_rayon"] -gif = ["iced_graphics/gif"] -webp = ["iced_graphics/webp"] -pnm = ["iced_graphics/pnm"] -ico = ["iced_graphics/ico"] -bmp = ["iced_graphics/bmp"] -hdr = ["iced_graphics/hdr"] -dds = ["iced_graphics/dds"] -farbfeld = ["iced_graphics/farbfeld"] -canvas = ["iced_graphics/canvas"] -qr_code = ["iced_graphics/qr_code"] -default_system_font = ["iced_graphics/font-source"] - -[dependencies] -glow = "0.11.1" -glow_glyph = "0.5.0" -glyph_brush = "0.7" -euclid = "0.22" -bytemuck = "1.4" -log = "0.4" - -[dependencies.iced_native] -version = "0.10" -path = "../native" - -[dependencies.iced_graphics] -version = "0.8" -path = "../graphics" -features = ["font-fallback", "font-icons", "opengl"] - -[dependencies.tracing] -version = "0.1.6" -optional = true - -[package.metadata.docs.rs] -rustdoc-args = ["--cfg", "docsrs"] -all-features = true diff --git a/glow/README.md b/glow/README.md deleted file mode 100644 index aa3d4d12..00000000 --- a/glow/README.md +++ /dev/null @@ -1,51 +0,0 @@ -# `iced_glow` -[][documentation] -[](https://crates.io/crates/iced_glow) -[](https://github.com/iced-rs/iced/blob/master/LICENSE) -[](https://discord.gg/3xZJ65GAhd) - -`iced_glow` is a [`glow`] renderer for [`iced_native`]. This renderer supports OpenGL 3.0+ and OpenGL ES 2.0. - -This renderer is mostly used as a fallback for hardware that doesn't support [`wgpu`] (Vulkan, Metal or DX12). - -Currently, `iced_glow` supports the following primitives: -- Text, which is rendered using [`glow_glyph`]. No shaping at all. -- Quads or rectangles, with rounded borders and a solid background color. -- Clip areas, useful to implement scrollables or hide overflowing content. -- Meshes of triangles, useful to draw geometry freely. - -<p align="center"> - <img alt="The native target" src="../docs/graphs/native.png" width="80%"> -</p> - -[documentation]: https://docs.rs/iced_glow -[`iced_native`]: ../native -[`glow`]: https://github.com/grovesNL/glow -[`wgpu`]: https://github.com/gfx-rs/wgpu -[`glow_glyph`]: https://github.com/hecrj/glow_glyph - -## Installation -Add `iced_glow` as a dependency in your `Cargo.toml`: - -```toml -iced_glow = "0.8" -``` - -__Iced moves fast and the `master` branch can contain breaking changes!__ If -you want to learn about a specific release, check out [the release list]. - -[the release list]: https://github.com/iced-rs/iced/releases - -## Current limitations - -The current implementation is quite naive, it uses: - -- A different pipeline/shader for each primitive -- A very simplistic layer model: every `Clip` primitive will generate new layers -- _Many_ render passes instead of preparing everything upfront -- A glyph cache that is trimmed incorrectly when there are multiple layers (a [`glyph_brush`] limitation) - -Some of these issues are already being worked on! If you want to help, [get in touch!] - -[get in touch!]: ../CONTRIBUTING.md -[`glyph_brush`]: https://github.com/alexheretic/glyph-brush diff --git a/glow/src/backend.rs b/glow/src/backend.rs deleted file mode 100644 index 36a34eda..00000000 --- a/glow/src/backend.rs +++ /dev/null @@ -1,280 +0,0 @@ -#[cfg(any(feature = "image", feature = "svg"))] -use crate::image; -use crate::quad; -use crate::text; -use crate::{program, triangle}; -use crate::{Settings, Transformation, Viewport}; - -use iced_graphics::backend; -use iced_graphics::font; -use iced_graphics::{Layer, Primitive}; -use iced_native::alignment; -use iced_native::{Font, Size}; - -/// A [`glow`] graphics backend for [`iced`]. -/// -/// [`glow`]: https://github.com/grovesNL/glow -/// [`iced`]: https://github.com/iced-rs/iced -#[derive(Debug)] -pub struct Backend { - #[cfg(any(feature = "image", feature = "svg"))] - image_pipeline: image::Pipeline, - quad_pipeline: quad::Pipeline, - text_pipeline: text::Pipeline, - triangle_pipeline: triangle::Pipeline, - default_text_size: f32, -} - -impl Backend { - /// Creates a new [`Backend`]. - pub fn new(gl: &glow::Context, settings: Settings) -> Self { - let text_pipeline = text::Pipeline::new( - gl, - settings.default_font, - settings.text_multithreading, - ); - - let shader_version = program::Version::new(gl); - - #[cfg(any(feature = "image", feature = "svg"))] - let image_pipeline = image::Pipeline::new(gl, &shader_version); - let quad_pipeline = quad::Pipeline::new(gl, &shader_version); - let triangle_pipeline = triangle::Pipeline::new(gl, &shader_version); - - Self { - #[cfg(any(feature = "image", feature = "svg"))] - image_pipeline, - quad_pipeline, - text_pipeline, - triangle_pipeline, - default_text_size: settings.default_text_size, - } - } - - /// Draws the provided primitives in the default framebuffer. - /// - /// The text provided as overlay will be rendered on top of the primitives. - /// This is useful for rendering debug information. - pub fn present<T: AsRef<str>>( - &mut self, - gl: &glow::Context, - primitives: &[Primitive], - viewport: &Viewport, - overlay_text: &[T], - ) { - let viewport_size = viewport.physical_size(); - let scale_factor = viewport.scale_factor() as f32; - let projection = viewport.projection(); - - let mut layers = Layer::generate(primitives, viewport); - layers.push(Layer::overlay(overlay_text, viewport)); - - for layer in layers { - self.flush( - gl, - scale_factor, - projection, - &layer, - viewport_size.height, - ); - } - - #[cfg(any(feature = "image", feature = "svg"))] - self.image_pipeline.trim_cache(gl); - } - - fn flush( - &mut self, - gl: &glow::Context, - scale_factor: f32, - transformation: Transformation, - layer: &Layer<'_>, - target_height: u32, - ) { - let mut bounds = (layer.bounds * scale_factor).snap(); - - if bounds.width < 1 || bounds.height < 1 { - return; - } - - bounds.height = bounds.height.min(target_height); - - if !layer.quads.is_empty() { - self.quad_pipeline.draw( - gl, - target_height, - &layer.quads, - transformation, - scale_factor, - bounds, - ); - } - - if !layer.meshes.is_empty() { - let scaled = transformation - * Transformation::scale(scale_factor, scale_factor); - - self.triangle_pipeline.draw( - &layer.meshes, - gl, - target_height, - scaled, - scale_factor, - ); - } - - #[cfg(any(feature = "image", feature = "svg"))] - if !layer.images.is_empty() { - let scaled = transformation - * Transformation::scale(scale_factor, scale_factor); - - self.image_pipeline.draw( - gl, - target_height, - scaled, - scale_factor, - &layer.images, - bounds, - ); - } - - if !layer.text.is_empty() { - for text in layer.text.iter() { - // Target physical coordinates directly to avoid blurry text - let text = glow_glyph::Section { - // TODO: We `round` here to avoid rerasterizing text when - // its position changes slightly. This can make text feel a - // bit "jumpy". We may be able to do better once we improve - // our text rendering/caching pipeline. - screen_position: ( - (text.bounds.x * scale_factor).round(), - (text.bounds.y * scale_factor).round(), - ), - // TODO: Fix precision issues with some scale factors. - // - // The `ceil` here can cause some words to render on the - // same line when they should not. - // - // Ideally, `wgpu_glyph` should be able to compute layout - // using logical positions, and then apply the proper - // scaling when rendering. This would ensure that both - // measuring and rendering follow the same layout rules. - bounds: ( - (text.bounds.width * scale_factor).ceil(), - (text.bounds.height * scale_factor).ceil(), - ), - text: vec![glow_glyph::Text { - text: text.content, - scale: glow_glyph::ab_glyph::PxScale { - x: text.size * scale_factor, - y: text.size * scale_factor, - }, - font_id: self.text_pipeline.find_font(text.font), - extra: glow_glyph::Extra { - color: text.color, - z: 0.0, - }, - }], - layout: glow_glyph::Layout::default() - .h_align(match text.horizontal_alignment { - alignment::Horizontal::Left => { - glow_glyph::HorizontalAlign::Left - } - alignment::Horizontal::Center => { - glow_glyph::HorizontalAlign::Center - } - alignment::Horizontal::Right => { - glow_glyph::HorizontalAlign::Right - } - }) - .v_align(match text.vertical_alignment { - alignment::Vertical::Top => { - glow_glyph::VerticalAlign::Top - } - alignment::Vertical::Center => { - glow_glyph::VerticalAlign::Center - } - alignment::Vertical::Bottom => { - glow_glyph::VerticalAlign::Bottom - } - }), - }; - - self.text_pipeline.queue(text); - } - - self.text_pipeline.draw_queued( - gl, - transformation, - glow_glyph::Region { - x: bounds.x, - y: target_height - (bounds.y + bounds.height), - width: bounds.width, - height: bounds.height, - }, - ); - } - } -} - -impl iced_graphics::Backend for Backend { - fn trim_measurements(&mut self) { - self.text_pipeline.trim_measurement_cache() - } -} - -impl backend::Text for Backend { - const ICON_FONT: Font = font::ICONS; - const CHECKMARK_ICON: char = font::CHECKMARK_ICON; - const ARROW_DOWN_ICON: char = font::ARROW_DOWN_ICON; - - fn default_size(&self) -> f32 { - self.default_text_size - } - - fn measure( - &self, - contents: &str, - size: f32, - font: Font, - bounds: Size, - ) -> (f32, f32) { - self.text_pipeline.measure(contents, size, font, bounds) - } - - fn hit_test( - &self, - contents: &str, - size: f32, - font: Font, - bounds: Size, - point: iced_native::Point, - nearest_only: bool, - ) -> Option<text::Hit> { - self.text_pipeline.hit_test( - contents, - size, - font, - bounds, - point, - nearest_only, - ) - } -} - -#[cfg(feature = "image")] -impl backend::Image for Backend { - fn dimensions(&self, handle: &iced_native::image::Handle) -> Size<u32> { - self.image_pipeline.dimensions(handle) - } -} - -#[cfg(feature = "svg")] -impl backend::Svg for Backend { - fn viewport_dimensions( - &self, - handle: &iced_native::svg::Handle, - ) -> Size<u32> { - self.image_pipeline.viewport_dimensions(handle) - } -} diff --git a/glow/src/image.rs b/glow/src/image.rs deleted file mode 100644 index d3a25b5b..00000000 --- a/glow/src/image.rs +++ /dev/null @@ -1,254 +0,0 @@ -mod storage; - -use storage::Storage; - -pub use iced_graphics::triangle::{Mesh2D, Vertex2D}; - -use crate::program::{self, Shader}; -use crate::Transformation; - -#[cfg(feature = "image")] -use iced_graphics::image::raster; - -#[cfg(feature = "svg")] -use iced_graphics::image::vector; - -use iced_graphics::layer; -use iced_graphics::Rectangle; -use iced_graphics::Size; - -use glow::HasContext; - -use std::cell::RefCell; - -#[cfg(feature = "tracing")] -use tracing::info_span; - -#[derive(Debug)] -pub(crate) struct Pipeline { - program: <glow::Context as HasContext>::Program, - vertex_array: <glow::Context as HasContext>::VertexArray, - vertex_buffer: <glow::Context as HasContext>::Buffer, - transform_location: <glow::Context as HasContext>::UniformLocation, - storage: Storage, - #[cfg(feature = "image")] - raster_cache: RefCell<raster::Cache<Storage>>, - #[cfg(feature = "svg")] - vector_cache: RefCell<vector::Cache<Storage>>, -} - -impl Pipeline { - pub fn new( - gl: &glow::Context, - shader_version: &program::Version, - ) -> Pipeline { - let program = unsafe { - let vertex_shader = Shader::vertex( - gl, - shader_version, - include_str!("shader/common/image.vert"), - ); - let fragment_shader = Shader::fragment( - gl, - shader_version, - include_str!("shader/common/image.frag"), - ); - - program::create( - gl, - &[vertex_shader, fragment_shader], - &[(0, "i_Position")], - ) - }; - - let transform_location = - unsafe { gl.get_uniform_location(program, "u_Transform") } - .expect("Get transform location"); - - unsafe { - gl.use_program(Some(program)); - - let transform: [f32; 16] = Transformation::identity().into(); - gl.uniform_matrix_4_f32_slice( - Some(&transform_location), - false, - &transform, - ); - - gl.use_program(None); - } - - let vertex_buffer = - unsafe { gl.create_buffer().expect("Create vertex buffer") }; - let vertex_array = - unsafe { gl.create_vertex_array().expect("Create vertex array") }; - - unsafe { - gl.bind_vertex_array(Some(vertex_array)); - gl.bind_buffer(glow::ARRAY_BUFFER, Some(vertex_buffer)); - - let vertices = &[0u8, 0, 1, 0, 0, 1, 1, 1]; - gl.buffer_data_size( - glow::ARRAY_BUFFER, - vertices.len() as i32, - glow::STATIC_DRAW, - ); - gl.buffer_sub_data_u8_slice( - glow::ARRAY_BUFFER, - 0, - bytemuck::cast_slice(vertices), - ); - - gl.enable_vertex_attrib_array(0); - gl.vertex_attrib_pointer_f32( - 0, - 2, - glow::UNSIGNED_BYTE, - false, - 0, - 0, - ); - - gl.bind_buffer(glow::ARRAY_BUFFER, None); - gl.bind_vertex_array(None); - } - - Pipeline { - program, - vertex_array, - vertex_buffer, - transform_location, - storage: Storage::default(), - #[cfg(feature = "image")] - raster_cache: RefCell::new(raster::Cache::default()), - #[cfg(feature = "svg")] - vector_cache: RefCell::new(vector::Cache::default()), - } - } - - #[cfg(feature = "image")] - pub fn dimensions(&self, handle: &iced_native::image::Handle) -> Size<u32> { - self.raster_cache.borrow_mut().load(handle).dimensions() - } - - #[cfg(feature = "svg")] - pub fn viewport_dimensions( - &self, - handle: &iced_native::svg::Handle, - ) -> Size<u32> { - let mut cache = self.vector_cache.borrow_mut(); - let svg = cache.load(handle); - - svg.viewport_dimensions() - } - - pub fn draw( - &mut self, - mut gl: &glow::Context, - target_height: u32, - transformation: Transformation, - _scale_factor: f32, - images: &[layer::Image], - layer_bounds: Rectangle<u32>, - ) { - #[cfg(feature = "tracing")] - let _ = info_span!("Glow::Image", "DRAW").entered(); - - unsafe { - gl.use_program(Some(self.program)); - gl.bind_vertex_array(Some(self.vertex_array)); - gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.vertex_buffer)); - gl.enable(glow::SCISSOR_TEST); - } - - #[cfg(feature = "image")] - let mut raster_cache = self.raster_cache.borrow_mut(); - - #[cfg(feature = "svg")] - let mut vector_cache = self.vector_cache.borrow_mut(); - - for image in images { - let (entry, bounds) = match &image { - #[cfg(feature = "image")] - layer::Image::Raster { handle, bounds } => ( - raster_cache.upload(handle, &mut gl, &mut self.storage), - bounds, - ), - #[cfg(not(feature = "image"))] - layer::Image::Raster { handle: _, bounds } => (None, bounds), - - #[cfg(feature = "svg")] - layer::Image::Vector { - handle, - color, - bounds, - } => { - let size = [bounds.width, bounds.height]; - ( - vector_cache.upload( - handle, - *color, - size, - _scale_factor, - &mut gl, - &mut self.storage, - ), - bounds, - ) - } - - #[cfg(not(feature = "svg"))] - layer::Image::Vector { bounds, .. } => (None, bounds), - }; - - unsafe { - gl.scissor( - layer_bounds.x as i32, - (target_height - (layer_bounds.y + layer_bounds.height)) - as i32, - layer_bounds.width as i32, - layer_bounds.height as i32, - ); - - if let Some(storage::Entry { texture, .. }) = entry { - gl.bind_texture(glow::TEXTURE_2D, Some(*texture)) - } else { - continue; - } - - let translate = Transformation::translate(bounds.x, bounds.y); - let scale = Transformation::scale(bounds.width, bounds.height); - let transformation = transformation * translate * scale; - let matrix: [f32; 16] = transformation.into(); - gl.uniform_matrix_4_f32_slice( - Some(&self.transform_location), - false, - &matrix, - ); - - gl.draw_arrays(glow::TRIANGLE_STRIP, 0, 4); - - gl.bind_texture(glow::TEXTURE_2D, None); - } - } - - unsafe { - gl.bind_buffer(glow::ARRAY_BUFFER, None); - gl.bind_vertex_array(None); - gl.use_program(None); - gl.disable(glow::SCISSOR_TEST); - } - } - - pub fn trim_cache(&mut self, mut gl: &glow::Context) { - #[cfg(feature = "image")] - self.raster_cache - .borrow_mut() - .trim(&mut self.storage, &mut gl); - - #[cfg(feature = "svg")] - self.vector_cache - .borrow_mut() - .trim(&mut self.storage, &mut gl); - } -} diff --git a/glow/src/image/storage.rs b/glow/src/image/storage.rs deleted file mode 100644 index 9bc20641..00000000 --- a/glow/src/image/storage.rs +++ /dev/null @@ -1,78 +0,0 @@ -use iced_graphics::image; -use iced_graphics::Size; - -use glow::HasContext; - -#[derive(Debug, Default)] -pub struct Storage; - -impl image::Storage for Storage { - type Entry = Entry; - type State<'a> = &'a glow::Context; - - fn upload( - &mut self, - width: u32, - height: u32, - data: &[u8], - gl: &mut &glow::Context, - ) -> Option<Self::Entry> { - unsafe { - let texture = gl.create_texture().expect("create texture"); - gl.bind_texture(glow::TEXTURE_2D, Some(texture)); - gl.tex_image_2d( - glow::TEXTURE_2D, - 0, - glow::SRGB8_ALPHA8 as i32, - width as i32, - height as i32, - 0, - glow::RGBA, - glow::UNSIGNED_BYTE, - Some(data), - ); - gl.tex_parameter_i32( - glow::TEXTURE_2D, - glow::TEXTURE_WRAP_S, - glow::CLAMP_TO_EDGE as _, - ); - gl.tex_parameter_i32( - glow::TEXTURE_2D, - glow::TEXTURE_WRAP_T, - glow::CLAMP_TO_EDGE as _, - ); - gl.tex_parameter_i32( - glow::TEXTURE_2D, - glow::TEXTURE_MIN_FILTER, - glow::LINEAR as _, - ); - gl.tex_parameter_i32( - glow::TEXTURE_2D, - glow::TEXTURE_MAG_FILTER, - glow::LINEAR as _, - ); - gl.bind_texture(glow::TEXTURE_2D, None); - - Some(Entry { - size: Size::new(width, height), - texture, - }) - } - } - - fn remove(&mut self, entry: &Entry, gl: &mut &glow::Context) { - unsafe { gl.delete_texture(entry.texture) } - } -} - -#[derive(Debug)] -pub struct Entry { - size: Size<u32>, - pub(super) texture: glow::NativeTexture, -} - -impl image::storage::Entry for Entry { - fn size(&self) -> Size<u32> { - self.size - } -} diff --git a/glow/src/lib.rs b/glow/src/lib.rs deleted file mode 100644 index 4614abfd..00000000 --- a/glow/src/lib.rs +++ /dev/null @@ -1,53 +0,0 @@ -//! A [`glow`] renderer for [`iced_native`]. -//! -//!  -//! -//! [`glow`]: https://github.com/grovesNL/glow -//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.9/native -#![doc( - html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg" -)] -#![deny( - missing_debug_implementations, - missing_docs, - unused_results, - clippy::extra_unused_lifetimes, - clippy::from_over_into, - clippy::needless_borrow, - clippy::new_without_default, - clippy::useless_conversion -)] -#![forbid(rust_2018_idioms)] -#![allow(clippy::inherent_to_string, clippy::type_complexity)] -#![cfg_attr(docsrs, feature(doc_cfg))] - -pub use glow; - -mod backend; -#[cfg(any(feature = "image", feature = "svg"))] -mod image; -mod program; -mod quad; -mod text; -mod triangle; - -pub mod settings; -pub mod window; - -pub use backend::Backend; -pub use settings::Settings; - -pub(crate) use iced_graphics::Transformation; - -pub use iced_graphics::{Error, Viewport}; -pub use iced_native::Theme; - -pub use iced_native::alignment; -pub use iced_native::{Alignment, Background, Color, Command, Length, Vector}; - -/// A [`glow`] graphics renderer for [`iced`]. -/// -/// [`glow`]: https://github.com/grovesNL/glow -/// [`iced`]: https://github.com/iced-rs/iced -pub type Renderer<Theme = iced_native::Theme> = - iced_graphics::Renderer<Backend, Theme>; diff --git a/glow/src/program.rs b/glow/src/program.rs deleted file mode 100644 index 95437fcd..00000000 --- a/glow/src/program.rs +++ /dev/null @@ -1,133 +0,0 @@ -use glow::HasContext; - -/// The [`Version`] of a `Program`. -pub struct Version { - vertex: String, - fragment: String, -} - -impl Version { - pub fn new(gl: &glow::Context) -> Version { - let version = gl.version(); - - let (vertex, fragment) = match ( - version.major, - version.minor, - version.is_embedded, - ) { - // OpenGL 3.0+ - (3, 0 | 1 | 2, false) => ( - format!("#version 1{}0\n#extension GL_ARB_explicit_attrib_location : enable", version.minor + 3), - format!( - "#version 1{}0\n#extension GL_ARB_explicit_attrib_location : enable\n#define HIGHER_THAN_300 1", - version.minor + 3 - ), - ), - // OpenGL 3.3+ - (3 | 4, _, false) => ( - format!("#version {}{}0\n#extension GL_ARB_explicit_attrib_location : enable", version.major, version.minor), - format!( - "#version {}{}0\n#extension GL_ARB_explicit_attrib_location : enable\n#define HIGHER_THAN_300 1", - version.major, version.minor - ), - ), - // OpenGL ES 3.0+ - (3, _, true) => ( - format!("#version 3{}0 es", version.minor), - format!( - "#version 3{}0 es\n#define HIGHER_THAN_300 1", - version.minor - ), - ), - // OpenGL ES 2.0+ - (2, _, true) => ( - String::from( - "#version 100\n#define in attribute\n#define out varying", - ), - String::from("#version 100\n#define in varying"), - ), - // OpenGL 2.1 - (2, _, false) => ( - String::from( - "#version 120\n#define in attribute\n#define out varying", - ), - String::from("#version 120\n#define in varying"), - ), - // OpenGL 1.1+ - _ => panic!("Incompatible context version: {version:?}"), - }; - - log::info!("Shader directive: {}", vertex.lines().next().unwrap()); - - Version { vertex, fragment } - } -} - -pub struct Shader(<glow::Context as HasContext>::Shader); - -impl Shader { - fn compile(gl: &glow::Context, stage: u32, content: &str) -> Shader { - unsafe { - let shader = gl.create_shader(stage).expect("Cannot create shader"); - - gl.shader_source(shader, content); - gl.compile_shader(shader); - - if !gl.get_shader_compile_status(shader) { - panic!("{}", gl.get_shader_info_log(shader)); - } - - Shader(shader) - } - } - - /// Creates a vertex [`Shader`]. - pub fn vertex( - gl: &glow::Context, - version: &Version, - content: &'static str, - ) -> Self { - let content = format!("{}\n{}", version.vertex, content); - - Shader::compile(gl, glow::VERTEX_SHADER, &content) - } - - /// Creates a fragment [`Shader`]. - pub fn fragment( - gl: &glow::Context, - version: &Version, - content: &'static str, - ) -> Self { - let content = format!("{}\n{}", version.fragment, content); - - Shader::compile(gl, glow::FRAGMENT_SHADER, &content) - } -} - -pub unsafe fn create( - gl: &glow::Context, - shaders: &[Shader], - attributes: &[(u32, &str)], -) -> <glow::Context as HasContext>::Program { - let program = gl.create_program().expect("Cannot create program"); - - for shader in shaders { - gl.attach_shader(program, shader.0); - } - - for (i, name) in attributes { - gl.bind_attrib_location(program, *i, name); - } - - gl.link_program(program); - if !gl.get_program_link_status(program) { - panic!("{}", gl.get_program_info_log(program)); - } - - for shader in shaders { - gl.detach_shader(program, shader.0); - gl.delete_shader(shader.0); - } - - program -} diff --git a/glow/src/quad.rs b/glow/src/quad.rs deleted file mode 100644 index 67d9a098..00000000 --- a/glow/src/quad.rs +++ /dev/null @@ -1,74 +0,0 @@ -mod compatibility; -mod core; - -use crate::program; -use crate::Transformation; -use glow::HasContext; -use iced_graphics::layer; -use iced_native::Rectangle; - -#[cfg(feature = "tracing")] -use tracing::info_span; - -#[derive(Debug)] -pub enum Pipeline { - Core(core::Pipeline), - Compatibility(compatibility::Pipeline), -} - -impl Pipeline { - pub fn new( - gl: &glow::Context, - shader_version: &program::Version, - ) -> Pipeline { - let gl_version = gl.version(); - - // OpenGL 3.0+ and OpenGL ES 3.0+ have instancing (which is what separates `core` from `compatibility`) - if gl_version.major >= 3 { - log::info!("Mode: core"); - Pipeline::Core(core::Pipeline::new(gl, shader_version)) - } else { - log::info!("Mode: compatibility"); - Pipeline::Compatibility(compatibility::Pipeline::new( - gl, - shader_version, - )) - } - } - - pub fn draw( - &mut self, - gl: &glow::Context, - target_height: u32, - instances: &[layer::Quad], - transformation: Transformation, - scale: f32, - bounds: Rectangle<u32>, - ) { - #[cfg(feature = "tracing")] - let _ = info_span!("Glow::Quad", "DRAW").enter(); - - match self { - Pipeline::Core(pipeline) => { - pipeline.draw( - gl, - target_height, - instances, - transformation, - scale, - bounds, - ); - } - Pipeline::Compatibility(pipeline) => { - pipeline.draw( - gl, - target_height, - instances, - transformation, - scale, - bounds, - ); - } - } - } -} diff --git a/glow/src/quad/compatibility.rs b/glow/src/quad/compatibility.rs deleted file mode 100644 index e909162c..00000000 --- a/glow/src/quad/compatibility.rs +++ /dev/null @@ -1,349 +0,0 @@ -use crate::program::{self, Shader}; -use crate::Transformation; -use glow::HasContext; -use iced_graphics::layer; -use iced_native::Rectangle; - -// Only change `MAX_QUADS`, otherwise you could cause problems -// by splitting a triangle into different render passes. -const MAX_QUADS: usize = 100_000; -const MAX_VERTICES: usize = MAX_QUADS * 4; -const MAX_INDICES: usize = MAX_QUADS * 6; - -#[derive(Debug)] -pub struct Pipeline { - program: <glow::Context as HasContext>::Program, - vertex_array: <glow::Context as HasContext>::VertexArray, - vertex_buffer: <glow::Context as HasContext>::Buffer, - index_buffer: <glow::Context as HasContext>::Buffer, - transform_location: <glow::Context as HasContext>::UniformLocation, - scale_location: <glow::Context as HasContext>::UniformLocation, - screen_height_location: <glow::Context as HasContext>::UniformLocation, - current_transform: Transformation, - current_scale: f32, - current_target_height: u32, -} - -impl Pipeline { - pub fn new( - gl: &glow::Context, - shader_version: &program::Version, - ) -> Pipeline { - let program = unsafe { - let vertex_shader = Shader::vertex( - gl, - shader_version, - include_str!("../shader/compatibility/quad.vert"), - ); - let fragment_shader = Shader::fragment( - gl, - shader_version, - include_str!("../shader/compatibility/quad.frag"), - ); - - program::create( - gl, - &[vertex_shader, fragment_shader], - &[ - (0, "i_Pos"), - (1, "i_Scale"), - (2, "i_Color"), - (3, "i_BorderColor"), - (4, "i_BorderRadius"), - (5, "i_BorderWidth"), - ], - ) - }; - - let transform_location = - unsafe { gl.get_uniform_location(program, "u_Transform") } - .expect("Get transform location"); - - let scale_location = - unsafe { gl.get_uniform_location(program, "u_Scale") } - .expect("Get scale location"); - - let screen_height_location = - unsafe { gl.get_uniform_location(program, "u_ScreenHeight") } - .expect("Get target height location"); - - unsafe { - gl.use_program(Some(program)); - - gl.uniform_matrix_4_f32_slice( - Some(&transform_location), - false, - Transformation::identity().as_ref(), - ); - - gl.uniform_1_f32(Some(&scale_location), 1.0); - gl.uniform_1_f32(Some(&screen_height_location), 0.0); - - gl.use_program(None); - } - - let (vertex_array, vertex_buffer, index_buffer) = - unsafe { create_buffers(gl, MAX_VERTICES) }; - - Pipeline { - program, - vertex_array, - vertex_buffer, - index_buffer, - transform_location, - scale_location, - screen_height_location, - current_transform: Transformation::identity(), - current_scale: 1.0, - current_target_height: 0, - } - } - - pub fn draw( - &mut self, - gl: &glow::Context, - target_height: u32, - instances: &[layer::Quad], - transformation: Transformation, - scale: f32, - bounds: Rectangle<u32>, - ) { - // TODO: Remove this allocation (probably by changing the shader and removing the need of two `position`) - let vertices: Vec<Vertex> = - instances.iter().flat_map(Vertex::from_quad).collect(); - - // TODO: Remove this allocation (or allocate only when needed) - let indices: Vec<i32> = (0..instances.len().min(MAX_QUADS) as i32) - .flat_map(|i| { - [i * 4, 1 + i * 4, 2 + i * 4, 2 + i * 4, 1 + i * 4, 3 + i * 4] - }) - .cycle() - .take(instances.len() * 6) - .collect(); - - unsafe { - gl.enable(glow::SCISSOR_TEST); - gl.scissor( - bounds.x as i32, - (target_height - (bounds.y + bounds.height)) as i32, - bounds.width as i32, - bounds.height as i32, - ); - - gl.use_program(Some(self.program)); - gl.bind_vertex_array(Some(self.vertex_array)); - gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.vertex_buffer)); - gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(self.index_buffer)); - } - - if transformation != self.current_transform { - unsafe { - gl.uniform_matrix_4_f32_slice( - Some(&self.transform_location), - false, - transformation.as_ref(), - ); - - self.current_transform = transformation; - } - } - - if scale != self.current_scale { - unsafe { - gl.uniform_1_f32(Some(&self.scale_location), scale); - } - - self.current_scale = scale; - } - - if target_height != self.current_target_height { - unsafe { - gl.uniform_1_f32( - Some(&self.screen_height_location), - target_height as f32, - ); - } - - self.current_target_height = target_height; - } - - let passes = vertices - .chunks(MAX_VERTICES) - .zip(indices.chunks(MAX_INDICES)); - - for (vertices, indices) in passes { - unsafe { - gl.buffer_sub_data_u8_slice( - glow::ARRAY_BUFFER, - 0, - bytemuck::cast_slice(vertices), - ); - - gl.buffer_sub_data_u8_slice( - glow::ELEMENT_ARRAY_BUFFER, - 0, - bytemuck::cast_slice(indices), - ); - - gl.draw_elements( - glow::TRIANGLES, - indices.len() as i32, - glow::UNSIGNED_INT, - 0, - ); - } - } - - unsafe { - gl.bind_vertex_array(None); - gl.use_program(None); - gl.disable(glow::SCISSOR_TEST); - } - } -} - -unsafe fn create_buffers( - gl: &glow::Context, - size: usize, -) -> ( - <glow::Context as HasContext>::VertexArray, - <glow::Context as HasContext>::Buffer, - <glow::Context as HasContext>::Buffer, -) { - let vertex_array = gl.create_vertex_array().expect("Create vertex array"); - let vertex_buffer = gl.create_buffer().expect("Create vertex buffer"); - let index_buffer = gl.create_buffer().expect("Create index buffer"); - - gl.bind_vertex_array(Some(vertex_array)); - - gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(index_buffer)); - gl.buffer_data_size( - glow::ELEMENT_ARRAY_BUFFER, - 12 * size as i32, - glow::DYNAMIC_DRAW, - ); - - gl.bind_buffer(glow::ARRAY_BUFFER, Some(vertex_buffer)); - gl.buffer_data_size( - glow::ARRAY_BUFFER, - (size * Vertex::SIZE) as i32, - glow::DYNAMIC_DRAW, - ); - - let stride = Vertex::SIZE as i32; - - gl.enable_vertex_attrib_array(0); - gl.vertex_attrib_pointer_f32(0, 2, glow::FLOAT, false, stride, 0); - - gl.enable_vertex_attrib_array(1); - gl.vertex_attrib_pointer_f32(1, 2, glow::FLOAT, false, stride, 4 * 2); - - gl.enable_vertex_attrib_array(2); - gl.vertex_attrib_pointer_f32(2, 4, glow::FLOAT, false, stride, 4 * (2 + 2)); - - gl.enable_vertex_attrib_array(3); - gl.vertex_attrib_pointer_f32( - 3, - 4, - glow::FLOAT, - false, - stride, - 4 * (2 + 2 + 4), - ); - - gl.enable_vertex_attrib_array(4); - gl.vertex_attrib_pointer_f32( - 4, - 4, - glow::FLOAT, - false, - stride, - 4 * (2 + 2 + 4 + 4), - ); - - gl.enable_vertex_attrib_array(5); - gl.vertex_attrib_pointer_f32( - 5, - 1, - glow::FLOAT, - false, - stride, - 4 * (2 + 2 + 4 + 4 + 4), - ); - - gl.enable_vertex_attrib_array(6); - gl.vertex_attrib_pointer_f32( - 6, - 2, - glow::FLOAT, - false, - stride, - 4 * (2 + 2 + 4 + 4 + 4 + 1), - ); - - gl.bind_vertex_array(None); - gl.bind_buffer(glow::ARRAY_BUFFER, None); - gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, None); - - (vertex_array, vertex_buffer, index_buffer) -} - -/// The vertex of a colored rectangle with a border. -/// -/// This type can be directly uploaded to GPU memory. -#[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)] -#[repr(C)] -pub struct Vertex { - /// The position of the [`Vertex`]. - pub position: [f32; 2], - - /// The size of the [`Vertex`]. - pub size: [f32; 2], - - /// The color of the [`Vertex`], in __linear RGB__. - pub color: [f32; 4], - - /// The border color of the [`Vertex`], in __linear RGB__. - pub border_color: [f32; 4], - - /// The border radius of the [`Vertex`]. - pub border_radius: [f32; 4], - - /// The border width of the [`Vertex`]. - pub border_width: f32, - - /// The __quad__ position of the [`Vertex`]. - pub q_position: [f32; 2], -} - -impl Vertex { - const SIZE: usize = std::mem::size_of::<Self>(); - - fn from_quad(quad: &layer::Quad) -> [Vertex; 4] { - let base = Vertex { - position: quad.position, - size: quad.size, - color: quad.color, - border_color: quad.color, - border_radius: quad.border_radius, - border_width: quad.border_width, - q_position: [0.0, 0.0], - }; - - [ - base, - Self { - q_position: [0.0, 1.0], - ..base - }, - Self { - q_position: [1.0, 0.0], - ..base - }, - Self { - q_position: [1.0, 1.0], - ..base - }, - ] - } -} diff --git a/glow/src/quad/core.rs b/glow/src/quad/core.rs deleted file mode 100644 index 89036530..00000000 --- a/glow/src/quad/core.rs +++ /dev/null @@ -1,244 +0,0 @@ -use crate::program::{self, Shader}; -use crate::Transformation; -use glow::HasContext; -use iced_graphics::layer; -use iced_native::Rectangle; - -const MAX_INSTANCES: usize = 100_000; - -#[derive(Debug)] -pub struct Pipeline { - program: <glow::Context as HasContext>::Program, - vertex_array: <glow::Context as HasContext>::VertexArray, - instances: <glow::Context as HasContext>::Buffer, - transform_location: <glow::Context as HasContext>::UniformLocation, - scale_location: <glow::Context as HasContext>::UniformLocation, - screen_height_location: <glow::Context as HasContext>::UniformLocation, - current_transform: Transformation, - current_scale: f32, - current_target_height: u32, -} - -impl Pipeline { - pub fn new( - gl: &glow::Context, - shader_version: &program::Version, - ) -> Pipeline { - let program = unsafe { - let vertex_shader = Shader::vertex( - gl, - shader_version, - include_str!("../shader/core/quad.vert"), - ); - let fragment_shader = Shader::fragment( - gl, - shader_version, - include_str!("../shader/core/quad.frag"), - ); - - program::create( - gl, - &[vertex_shader, fragment_shader], - &[ - (0, "i_Pos"), - (1, "i_Scale"), - (2, "i_Color"), - (3, "i_BorderColor"), - (4, "i_BorderRadius"), - (5, "i_BorderWidth"), - ], - ) - }; - - let transform_location = - unsafe { gl.get_uniform_location(program, "u_Transform") } - .expect("Get transform location"); - - let scale_location = - unsafe { gl.get_uniform_location(program, "u_Scale") } - .expect("Get scale location"); - - let screen_height_location = - unsafe { gl.get_uniform_location(program, "u_ScreenHeight") } - .expect("Get target height location"); - - unsafe { - gl.use_program(Some(program)); - - gl.uniform_matrix_4_f32_slice( - Some(&transform_location), - false, - Transformation::identity().as_ref(), - ); - - gl.uniform_1_f32(Some(&scale_location), 1.0); - gl.uniform_1_f32(Some(&screen_height_location), 0.0); - - gl.use_program(None); - } - - let (vertex_array, instances) = - unsafe { create_instance_buffer(gl, MAX_INSTANCES) }; - - Pipeline { - program, - vertex_array, - instances, - transform_location, - scale_location, - screen_height_location, - current_transform: Transformation::identity(), - current_scale: 1.0, - current_target_height: 0, - } - } - - pub fn draw( - &mut self, - gl: &glow::Context, - target_height: u32, - instances: &[layer::Quad], - transformation: Transformation, - scale: f32, - bounds: Rectangle<u32>, - ) { - unsafe { - gl.enable(glow::SCISSOR_TEST); - gl.scissor( - bounds.x as i32, - (target_height - (bounds.y + bounds.height)) as i32, - bounds.width as i32, - bounds.height as i32, - ); - - gl.use_program(Some(self.program)); - gl.bind_vertex_array(Some(self.vertex_array)); - gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.instances)); - } - - if transformation != self.current_transform { - unsafe { - gl.uniform_matrix_4_f32_slice( - Some(&self.transform_location), - false, - transformation.as_ref(), - ); - - self.current_transform = transformation; - } - } - - if scale != self.current_scale { - unsafe { - gl.uniform_1_f32(Some(&self.scale_location), scale); - } - - self.current_scale = scale; - } - - if target_height != self.current_target_height { - unsafe { - gl.uniform_1_f32( - Some(&self.screen_height_location), - target_height as f32, - ); - } - - self.current_target_height = target_height; - } - - for instances in instances.chunks(MAX_INSTANCES) { - unsafe { - gl.buffer_sub_data_u8_slice( - glow::ARRAY_BUFFER, - 0, - bytemuck::cast_slice(instances), - ); - - gl.draw_arrays_instanced( - glow::TRIANGLE_STRIP, - 0, - 4, - instances.len() as i32, - ); - } - } - - unsafe { - gl.bind_vertex_array(None); - gl.use_program(None); - gl.disable(glow::SCISSOR_TEST); - } - } -} - -unsafe fn create_instance_buffer( - gl: &glow::Context, - size: usize, -) -> ( - <glow::Context as HasContext>::VertexArray, - <glow::Context as HasContext>::Buffer, -) { - let vertex_array = gl.create_vertex_array().expect("Create vertex array"); - let buffer = gl.create_buffer().expect("Create instance buffer"); - - gl.bind_vertex_array(Some(vertex_array)); - gl.bind_buffer(glow::ARRAY_BUFFER, Some(buffer)); - gl.buffer_data_size( - glow::ARRAY_BUFFER, - (size * std::mem::size_of::<layer::Quad>()) as i32, - glow::DYNAMIC_DRAW, - ); - - let stride = std::mem::size_of::<layer::Quad>() as i32; - - gl.enable_vertex_attrib_array(0); - gl.vertex_attrib_pointer_f32(0, 2, glow::FLOAT, false, stride, 0); - gl.vertex_attrib_divisor(0, 1); - - gl.enable_vertex_attrib_array(1); - gl.vertex_attrib_pointer_f32(1, 2, glow::FLOAT, false, stride, 4 * 2); - gl.vertex_attrib_divisor(1, 1); - - gl.enable_vertex_attrib_array(2); - gl.vertex_attrib_pointer_f32(2, 4, glow::FLOAT, false, stride, 4 * (2 + 2)); - gl.vertex_attrib_divisor(2, 1); - - gl.enable_vertex_attrib_array(3); - gl.vertex_attrib_pointer_f32( - 3, - 4, - glow::FLOAT, - false, - stride, - 4 * (2 + 2 + 4), - ); - gl.vertex_attrib_divisor(3, 1); - - gl.enable_vertex_attrib_array(4); - gl.vertex_attrib_pointer_f32( - 4, - 4, - glow::FLOAT, - false, - stride, - 4 * (2 + 2 + 4 + 4), - ); - gl.vertex_attrib_divisor(4, 1); - - gl.enable_vertex_attrib_array(5); - gl.vertex_attrib_pointer_f32( - 5, - 1, - glow::FLOAT, - false, - stride, - 4 * (2 + 2 + 4 + 4 + 4), - ); - gl.vertex_attrib_divisor(5, 1); - - gl.bind_vertex_array(None); - gl.bind_buffer(glow::ARRAY_BUFFER, None); - - (vertex_array, buffer) -} diff --git a/glow/src/settings.rs b/glow/src/settings.rs deleted file mode 100644 index 6aaa0d55..00000000 --- a/glow/src/settings.rs +++ /dev/null @@ -1,61 +0,0 @@ -//! Configure a renderer. -pub use iced_graphics::Antialiasing; - -/// The settings of a [`Backend`]. -/// -/// [`Backend`]: crate::Backend -#[derive(Clone, Copy, PartialEq)] -pub struct Settings { - /// The bytes of the font that will be used by default. - /// - /// If `None` is provided, a default system font will be chosen. - pub default_font: Option<&'static [u8]>, - - /// The default size of text. - /// - /// By default, it will be set to `20.0`. - pub default_text_size: f32, - - /// If enabled, spread text workload in multiple threads when multiple cores - /// are available. - /// - /// By default, it is disabled. - pub text_multithreading: bool, - - /// The antialiasing strategy that will be used for triangle primitives. - /// - /// By default, it is `None`. - pub antialiasing: Option<Antialiasing>, -} - -impl Default for Settings { - fn default() -> Settings { - Settings { - default_font: None, - default_text_size: 20.0, - text_multithreading: false, - antialiasing: None, - } - } -} - -impl std::fmt::Debug for Settings { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Settings") - // Instead of printing the font bytes, we simply show a `bool` indicating if using a default font or not. - .field("default_font", &self.default_font.is_some()) - .field("default_text_size", &self.default_text_size) - .field("text_multithreading", &self.text_multithreading) - .field("antialiasing", &self.antialiasing) - .finish() - } -} - -impl Settings { - /// Creates new [`Settings`] using environment configuration. - /// - /// Currently, this is equivalent to calling [`Settings::default`]. - pub fn from_env() -> Self { - Self::default() - } -} diff --git a/glow/src/shader/common/gradient.frag b/glow/src/shader/common/gradient.frag deleted file mode 100644 index 9af0cb6e..00000000 --- a/glow/src/shader/common/gradient.frag +++ /dev/null @@ -1,59 +0,0 @@ -#ifdef GL_ES -#ifdef GL_FRAGMENT_PRECISION_HIGH -precision highp float; -#else -precision mediump float; -#endif -#endif - -#ifdef HIGHER_THAN_300 -layout (location = 0) out vec4 fragColor; -#define gl_FragColor fragColor -#endif - -in vec2 raw_position; - -uniform vec4 gradient_direction; -uniform int color_stops_size; -// GLSL does not support dynamically sized arrays without SSBOs so this is capped to 16 stops -//stored as color(vec4) -> offset(vec4) sequentially; -uniform vec4 color_stops[32]; - -//TODO: rewrite without branching to make ALUs happy -void main() { - vec2 start = gradient_direction.xy; - vec2 end = gradient_direction.zw; - vec2 gradient_vec = vec2(end - start); - vec2 current_vec = vec2(raw_position.xy - start); - vec2 unit = normalize(gradient_vec); - float coord_offset = dot(unit, current_vec) / length(gradient_vec); - //if a gradient has a start/end stop that is identical, the mesh will have a transparent fill - gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0); - - float min_offset = color_stops[1].x; - float max_offset = color_stops[color_stops_size - 1].x; - - for (int i = 0; i < color_stops_size - 2; i += 2) { - float curr_offset = color_stops[i+1].x; - float next_offset = color_stops[i+3].x; - - if (coord_offset <= min_offset) { - //current coordinate is before the first defined offset, set it to the start color - gl_FragColor = color_stops[0]; - } - - if (curr_offset <= coord_offset && coord_offset <= next_offset) { - //current fragment is between the current offset processing & the next one, interpolate colors - gl_FragColor = mix(color_stops[i], color_stops[i+2], smoothstep( - curr_offset, - next_offset, - coord_offset - )); - } - - if (coord_offset >= max_offset) { - //current coordinate is before the last defined offset, set it to the last color - gl_FragColor = color_stops[color_stops_size - 2]; - } - } -} diff --git a/glow/src/shader/common/gradient.vert b/glow/src/shader/common/gradient.vert deleted file mode 100644 index fe505997..00000000 --- a/glow/src/shader/common/gradient.vert +++ /dev/null @@ -1,9 +0,0 @@ -uniform mat4 u_Transform; - -in vec2 i_Position; -out vec2 raw_position; - -void main() { - gl_Position = u_Transform * vec4(i_Position, 0.0, 1.0); - raw_position = i_Position; -} diff --git a/glow/src/shader/common/image.frag b/glow/src/shader/common/image.frag deleted file mode 100644 index 5e05abdf..00000000 --- a/glow/src/shader/common/image.frag +++ /dev/null @@ -1,22 +0,0 @@ -#ifdef GL_ES -#ifdef GL_FRAGMENT_PRECISION_HIGH -precision highp float; -#else -precision mediump float; -#endif -#endif - -uniform sampler2D tex; -in vec2 tex_pos; - -#ifdef HIGHER_THAN_300 -out vec4 fragColor; -#define gl_FragColor fragColor -#endif -#ifdef GL_ES -#define texture texture2D -#endif - -void main() { - gl_FragColor = texture(tex, tex_pos); -} diff --git a/glow/src/shader/common/image.vert b/glow/src/shader/common/image.vert deleted file mode 100644 index 93e541f2..00000000 --- a/glow/src/shader/common/image.vert +++ /dev/null @@ -1,9 +0,0 @@ -uniform mat4 u_Transform; - -in vec2 i_Position; -out vec2 tex_pos; - -void main() { - gl_Position = u_Transform * vec4(i_Position, 0.0, 1.0); - tex_pos = i_Position; -} diff --git a/glow/src/shader/common/solid.frag b/glow/src/shader/common/solid.frag deleted file mode 100644 index 174ffdd3..00000000 --- a/glow/src/shader/common/solid.frag +++ /dev/null @@ -1,18 +0,0 @@ -#ifdef GL_ES -#ifdef GL_FRAGMENT_PRECISION_HIGH -precision highp float; -#else -precision mediump float; -#endif -#endif - -#ifdef HIGHER_THAN_300 -out vec4 fragColor; -#define gl_FragColor fragColor -#endif - -in vec4 v_Color; - -void main() { - gl_FragColor = v_Color; -} diff --git a/glow/src/shader/common/solid.vert b/glow/src/shader/common/solid.vert deleted file mode 100644 index 59ed88e5..00000000 --- a/glow/src/shader/common/solid.vert +++ /dev/null @@ -1,11 +0,0 @@ -uniform mat4 u_Transform; - -in vec2 i_Position; -in vec4 i_Color; - -out vec4 v_Color; - -void main() { - gl_Position = u_Transform * vec4(i_Position, 0.0, 1.0); - v_Color = i_Color; -} diff --git a/glow/src/shader/compatibility/quad.frag b/glow/src/shader/compatibility/quad.frag deleted file mode 100644 index bb9d8122..00000000 --- a/glow/src/shader/compatibility/quad.frag +++ /dev/null @@ -1,83 +0,0 @@ -#ifdef GL_ES -#ifdef GL_FRAGMENT_PRECISION_HIGH -precision highp float; -#else -precision mediump float; -#endif -#endif - -uniform float u_ScreenHeight; - -varying vec4 v_Color; -varying vec4 v_BorderColor; -varying vec2 v_Pos; -varying vec2 v_Scale; -varying vec4 v_BorderRadius; -varying float v_BorderWidth; - -float _distance(vec2 frag_coord, vec2 position, vec2 size, float radius) -{ - // TODO: Try SDF approach: https://www.shadertoy.com/view/wd3XRN - vec2 inner_size = size - vec2(radius, radius) * 2.0; - vec2 top_left = position + vec2(radius, radius); - vec2 bottom_right = top_left + inner_size; - - vec2 top_left_distance = top_left - frag_coord; - vec2 bottom_right_distance = frag_coord - bottom_right; - - vec2 distance = vec2( - max(max(top_left_distance.x, bottom_right_distance.x), 0.0), - max(max(top_left_distance.y, bottom_right_distance.y), 0.0) - ); - - return sqrt(distance.x * distance.x + distance.y * distance.y); -} - -float selectBorderRadius(vec4 radi, vec2 position, vec2 center) -{ - float rx = radi.x; - float ry = radi.y; - rx = position.x > center.x ? radi.y : radi.x; - ry = position.x > center.x ? radi.z : radi.w; - rx = position.y > center.y ? ry : rx; - return rx; -} - -void main() { - vec2 fragCoord = vec2(gl_FragCoord.x, u_ScreenHeight - gl_FragCoord.y); - - float border_radius = selectBorderRadius( - v_BorderRadius, - fragCoord, - (v_Pos + v_Scale * 0.5).xy - ); - - float internal_border = max(border_radius - v_BorderWidth, 0.0); - - float internal_distance = _distance( - fragCoord, - v_Pos + vec2(v_BorderWidth), - v_Scale - vec2(v_BorderWidth * 2.0), - internal_border - ); - - float border_mix = smoothstep( - max(internal_border - 0.5, 0.0), - internal_border + 0.5, - internal_distance - ); - - vec4 mixed_color = mix(v_Color, v_BorderColor, border_mix); - - float d = _distance( - fragCoord, - v_Pos, - v_Scale, - border_radius - ); - - float radius_alpha = - 1.0 - smoothstep(max(border_radius - 0.5, 0.0), border_radius + 0.5, d); - - gl_FragColor = vec4(mixed_color.xyz, mixed_color.w * radius_alpha); -} diff --git a/glow/src/shader/compatibility/quad.vert b/glow/src/shader/compatibility/quad.vert deleted file mode 100644 index 89931f06..00000000 --- a/glow/src/shader/compatibility/quad.vert +++ /dev/null @@ -1,46 +0,0 @@ -uniform mat4 u_Transform; -uniform float u_Scale; - -attribute vec2 i_Pos; -attribute vec2 i_Scale; -attribute vec4 i_Color; -attribute vec4 i_BorderColor; -attribute vec4 i_BorderRadius; -attribute float i_BorderWidth; -attribute vec2 q_Pos; - -varying vec4 v_Color; -varying vec4 v_BorderColor; -varying vec2 v_Pos; -varying vec2 v_Scale; -varying vec4 v_BorderRadius; -varying float v_BorderWidth; - - -void main() { - vec2 p_Pos = i_Pos * u_Scale; - vec2 p_Scale = i_Scale * u_Scale; - - vec4 i_BorderRadius = vec4( - min(i_BorderRadius.x, min(i_Scale.x, i_Scale.y) / 2.0), - min(i_BorderRadius.y, min(i_Scale.x, i_Scale.y) / 2.0), - min(i_BorderRadius.z, min(i_Scale.x, i_Scale.y) / 2.0), - min(i_BorderRadius.w, min(i_Scale.x, i_Scale.y) / 2.0) - ); - - mat4 i_Transform = mat4( - vec4(p_Scale.x + 1.0, 0.0, 0.0, 0.0), - vec4(0.0, p_Scale.y + 1.0, 0.0, 0.0), - vec4(0.0, 0.0, 1.0, 0.0), - vec4(p_Pos - vec2(0.5, 0.5), 0.0, 1.0) - ); - - v_Color = i_Color; - v_BorderColor = i_BorderColor; - v_Pos = p_Pos; - v_Scale = p_Scale; - v_BorderRadius = i_BorderRadius * u_Scale; - v_BorderWidth = i_BorderWidth * u_Scale; - - gl_Position = u_Transform * i_Transform * vec4(q_Pos, 0.0, 1.0); -} diff --git a/glow/src/shader/core/quad.frag b/glow/src/shader/core/quad.frag deleted file mode 100644 index 71147aa5..00000000 --- a/glow/src/shader/core/quad.frag +++ /dev/null @@ -1,95 +0,0 @@ -#ifdef GL_ES -#ifdef GL_FRAGMENT_PRECISION_HIGH -precision highp float; -#else -precision mediump float; -#endif -#endif - -#ifdef HIGHER_THAN_300 -out vec4 fragColor; -#define gl_FragColor fragColor -#endif - -uniform float u_ScreenHeight; - -in vec4 v_Color; -in vec4 v_BorderColor; -in vec2 v_Pos; -in vec2 v_Scale; -in vec4 v_BorderRadius; -in float v_BorderWidth; - -float fDistance(vec2 frag_coord, vec2 position, vec2 size, float radius) -{ - // TODO: Try SDF approach: https://www.shadertoy.com/view/wd3XRN - vec2 inner_size = size - vec2(radius, radius) * 2.0; - vec2 top_left = position + vec2(radius, radius); - vec2 bottom_right = top_left + inner_size; - - vec2 top_left_distance = top_left - frag_coord; - vec2 bottom_right_distance = frag_coord - bottom_right; - - vec2 distance = vec2( - max(max(top_left_distance.x, bottom_right_distance.x), 0.0), - max(max(top_left_distance.y, bottom_right_distance.y), 0.0) - ); - - return sqrt(distance.x * distance.x + distance.y * distance.y); -} - -float selectBorderRadius(vec4 radi, vec2 position, vec2 center) -{ - float rx = radi.x; - float ry = radi.y; - rx = position.x > center.x ? radi.y : radi.x; - ry = position.x > center.x ? radi.z : radi.w; - rx = position.y > center.y ? ry : rx; - return rx; -} - -void main() { - vec4 mixed_color; - - vec2 fragCoord = vec2(gl_FragCoord.x, u_ScreenHeight - gl_FragCoord.y); - - float border_radius = selectBorderRadius( - v_BorderRadius, - fragCoord, - (v_Pos + v_Scale * 0.5).xy - ); - - // TODO: Remove branching (?) - if(v_BorderWidth > 0.0) { - float internal_border = max(border_radius - v_BorderWidth, 0.0); - - float internal_distance = fDistance( - fragCoord, - v_Pos + vec2(v_BorderWidth), - v_Scale - vec2(v_BorderWidth * 2.0), - internal_border - ); - - float border_mix = smoothstep( - max(internal_border - 0.5, 0.0), - internal_border + 0.5, - internal_distance - ); - - mixed_color = mix(v_Color, v_BorderColor, border_mix); - } else { - mixed_color = v_Color; - } - - float d = fDistance( - fragCoord, - v_Pos, - v_Scale, - border_radius - ); - - float radius_alpha = - 1.0 - smoothstep(max(border_radius - 0.5, 0.0), border_radius + 0.5, d); - - gl_FragColor = vec4(mixed_color.xyz, mixed_color.w * radius_alpha); -} diff --git a/glow/src/shader/core/quad.vert b/glow/src/shader/core/quad.vert deleted file mode 100644 index 17c3e641..00000000 --- a/glow/src/shader/core/quad.vert +++ /dev/null @@ -1,52 +0,0 @@ -uniform mat4 u_Transform; -uniform float u_Scale; - -in vec2 i_Pos; -in vec2 i_Scale; -in vec4 i_Color; -in vec4 i_BorderColor; -in vec4 i_BorderRadius; -in float i_BorderWidth; - -out vec4 v_Color; -out vec4 v_BorderColor; -out vec2 v_Pos; -out vec2 v_Scale; -out vec4 v_BorderRadius; -out float v_BorderWidth; - -vec2 positions[4] = vec2[]( - vec2(0.0, 0.0), - vec2(0.0, 1.0), - vec2(1.0, 0.0), - vec2(1.0, 1.0) -); - -void main() { - vec2 q_Pos = positions[gl_VertexID]; - vec2 p_Pos = i_Pos * u_Scale; - vec2 p_Scale = i_Scale * u_Scale; - - vec4 i_BorderRadius = vec4( - min(i_BorderRadius.x, min(i_Scale.x, i_Scale.y) / 2.0), - min(i_BorderRadius.y, min(i_Scale.x, i_Scale.y) / 2.0), - min(i_BorderRadius.z, min(i_Scale.x, i_Scale.y) / 2.0), - min(i_BorderRadius.w, min(i_Scale.x, i_Scale.y) / 2.0) - ); - - mat4 i_Transform = mat4( - vec4(p_Scale.x + 1.0, 0.0, 0.0, 0.0), - vec4(0.0, p_Scale.y + 1.0, 0.0, 0.0), - vec4(0.0, 0.0, 1.0, 0.0), - vec4(p_Pos - vec2(0.5, 0.5), 0.0, 1.0) - ); - - v_Color = i_Color; - v_BorderColor = i_BorderColor; - v_Pos = p_Pos; - v_Scale = p_Scale; - v_BorderRadius = i_BorderRadius * u_Scale; - v_BorderWidth = i_BorderWidth * u_Scale; - - gl_Position = u_Transform * i_Transform * vec4(q_Pos, 0.0, 1.0); -} diff --git a/glow/src/text.rs b/glow/src/text.rs deleted file mode 100644 index 37ccdece..00000000 --- a/glow/src/text.rs +++ /dev/null @@ -1,257 +0,0 @@ -use crate::Transformation; - -use iced_graphics::font; - -use glow_glyph::ab_glyph; -use std::{cell::RefCell, collections::HashMap}; - -pub use iced_native::text::Hit; - -#[derive(Debug)] -pub struct Pipeline { - draw_brush: RefCell<glow_glyph::GlyphBrush>, - draw_font_map: RefCell<HashMap<String, glow_glyph::FontId>>, - measure_brush: RefCell<glyph_brush::GlyphBrush<()>>, -} - -impl Pipeline { - pub fn new( - gl: &glow::Context, - default_font: Option<&[u8]>, - multithreading: bool, - ) -> Self { - let default_font = default_font.map(|slice| slice.to_vec()); - - // TODO: Font customization - #[cfg(feature = "default_system_font")] - let default_font = { - default_font.or_else(|| { - font::Source::new() - .load(&[font::Family::SansSerif, font::Family::Serif]) - .ok() - }) - }; - - let default_font = - default_font.unwrap_or_else(|| font::FALLBACK.to_vec()); - - let font = ab_glyph::FontArc::try_from_vec(default_font) - .unwrap_or_else(|_| { - log::warn!( - "System font failed to load. Falling back to \ - embedded font..." - ); - - ab_glyph::FontArc::try_from_slice(font::FALLBACK) - .expect("Load fallback font") - }); - - let draw_brush_builder = - glow_glyph::GlyphBrushBuilder::using_font(font.clone()) - .initial_cache_size((2048, 2048)) - .draw_cache_multithread(multithreading); - - #[cfg(target_arch = "wasm32")] - let draw_brush_builder = draw_brush_builder.draw_cache_align_4x4(true); - - let draw_brush = draw_brush_builder.build(gl); - - let measure_brush = - glyph_brush::GlyphBrushBuilder::using_font(font).build(); - - Pipeline { - draw_brush: RefCell::new(draw_brush), - draw_font_map: RefCell::new(HashMap::new()), - measure_brush: RefCell::new(measure_brush), - } - } - - pub fn queue(&mut self, section: glow_glyph::Section<'_>) { - self.draw_brush.borrow_mut().queue(section); - } - - pub fn draw_queued( - &mut self, - gl: &glow::Context, - transformation: Transformation, - region: glow_glyph::Region, - ) { - self.draw_brush - .borrow_mut() - .draw_queued_with_transform_and_scissoring( - gl, - transformation.into(), - region, - ) - .expect("Draw text"); - } - - pub fn measure( - &self, - content: &str, - size: f32, - font: iced_native::Font, - bounds: iced_native::Size, - ) -> (f32, f32) { - use glow_glyph::GlyphCruncher; - - let glow_glyph::FontId(font_id) = self.find_font(font); - - let section = glow_glyph::Section { - bounds: (bounds.width, bounds.height), - text: vec![glow_glyph::Text { - text: content, - scale: size.into(), - font_id: glow_glyph::FontId(font_id), - extra: glow_glyph::Extra::default(), - }], - ..Default::default() - }; - - if let Some(bounds) = - self.measure_brush.borrow_mut().glyph_bounds(section) - { - (bounds.width().ceil(), bounds.height().ceil()) - } else { - (0.0, 0.0) - } - } - - pub fn hit_test( - &self, - content: &str, - size: f32, - font: iced_native::Font, - bounds: iced_native::Size, - point: iced_native::Point, - nearest_only: bool, - ) -> Option<Hit> { - use glow_glyph::GlyphCruncher; - - let glow_glyph::FontId(font_id) = self.find_font(font); - - let section = glow_glyph::Section { - bounds: (bounds.width, bounds.height), - text: vec![glow_glyph::Text { - text: content, - scale: size.into(), - font_id: glow_glyph::FontId(font_id), - extra: glow_glyph::Extra::default(), - }], - ..Default::default() - }; - - let mut mb = self.measure_brush.borrow_mut(); - - // The underlying type is FontArc, so clones are cheap. - use ab_glyph::{Font, ScaleFont}; - let font = mb.fonts()[font_id].clone().into_scaled(size); - - // Implements an iterator over the glyph bounding boxes. - let bounds = mb.glyphs(section).map( - |glow_glyph::SectionGlyph { - byte_index, glyph, .. - }| { - ( - *byte_index, - iced_native::Rectangle::new( - iced_native::Point::new( - glyph.position.x - font.h_side_bearing(glyph.id), - glyph.position.y - font.ascent(), - ), - iced_native::Size::new( - font.h_advance(glyph.id), - font.ascent() - font.descent(), - ), - ), - ) - }, - ); - - // Implements computation of the character index based on the byte index - // within the input string. - let char_index = |byte_index| { - let mut b_count = 0; - for (i, utf8_len) in - content.chars().map(|c| c.len_utf8()).enumerate() - { - if byte_index < (b_count + utf8_len) { - return i; - } - b_count += utf8_len; - } - - byte_index - }; - - if !nearest_only { - for (idx, bounds) in bounds.clone() { - if bounds.contains(point) { - return Some(Hit::CharOffset(char_index(idx))); - } - } - } - - let nearest = bounds - .map(|(index, bounds)| (index, bounds.center())) - .min_by(|(_, center_a), (_, center_b)| { - center_a - .distance(point) - .partial_cmp(¢er_b.distance(point)) - .unwrap_or(std::cmp::Ordering::Greater) - }); - - nearest.map(|(idx, center)| { - Hit::NearestCharOffset(char_index(idx), point - center) - }) - } - - pub fn trim_measurement_cache(&mut self) { - // TODO: We should probably use a `GlyphCalculator` for this. However, - // it uses a lifetimed `GlyphCalculatorGuard` with side-effects on drop. - // This makes stuff quite inconvenient. A manual method for trimming the - // cache would make our lives easier. - loop { - let action = self - .measure_brush - .borrow_mut() - .process_queued(|_, _| {}, |_| {}); - - match action { - Ok(_) => break, - Err(glyph_brush::BrushError::TextureTooSmall { suggested }) => { - let (width, height) = suggested; - - self.measure_brush - .borrow_mut() - .resize_texture(width, height); - } - } - } - } - - pub fn find_font(&self, font: iced_native::Font) -> glow_glyph::FontId { - match font { - iced_native::Font::Default => glow_glyph::FontId(0), - iced_native::Font::External { name, bytes } => { - if let Some(font_id) = self.draw_font_map.borrow().get(name) { - return *font_id; - } - - let font = ab_glyph::FontArc::try_from_slice(bytes) - .expect("Load font"); - - let _ = self.measure_brush.borrow_mut().add_font(font.clone()); - - let font_id = self.draw_brush.borrow_mut().add_font(font); - - let _ = self - .draw_font_map - .borrow_mut() - .insert(String::from(name), font_id); - - font_id - } - } - } -} diff --git a/glow/src/triangle.rs b/glow/src/triangle.rs deleted file mode 100644 index 42c88455..00000000 --- a/glow/src/triangle.rs +++ /dev/null @@ -1,595 +0,0 @@ -//! Draw meshes of triangles. -use crate::program; -use crate::Transformation; - -use iced_graphics::gradient::Gradient; -use iced_graphics::layer::mesh::{self, Mesh}; -use iced_graphics::triangle::{ColoredVertex2D, Vertex2D}; - -use glow::HasContext; -use std::marker::PhantomData; - -#[cfg(feature = "tracing")] -use tracing::info_span; - -const DEFAULT_VERTICES: usize = 1_000; -const DEFAULT_INDICES: usize = 1_000; - -#[derive(Debug)] -pub(crate) struct Pipeline { - indices: Buffer<u32>, - solid: solid::Program, - gradient: gradient::Program, -} - -impl Pipeline { - pub fn new(gl: &glow::Context, shader_version: &program::Version) -> Self { - let mut indices = unsafe { - Buffer::new( - gl, - glow::ELEMENT_ARRAY_BUFFER, - glow::DYNAMIC_DRAW, - DEFAULT_INDICES, - ) - }; - - let solid = solid::Program::new(gl, shader_version); - let gradient = gradient::Program::new(gl, shader_version); - - unsafe { - gl.bind_vertex_array(Some(solid.vertex_array)); - indices.bind(gl, 0); - - gl.bind_vertex_array(Some(gradient.vertex_array)); - indices.bind(gl, 0); - - gl.bind_vertex_array(None); - } - - Self { - indices, - solid, - gradient, - } - } - - pub fn draw( - &mut self, - meshes: &[Mesh<'_>], - gl: &glow::Context, - target_height: u32, - transformation: Transformation, - scale_factor: f32, - ) { - #[cfg(feature = "tracing")] - let _ = info_span!("Glow::Triangle", "DRAW").enter(); - - unsafe { - gl.enable(glow::MULTISAMPLE); - gl.enable(glow::SCISSOR_TEST); - } - - // Count the total amount of vertices & indices we need to handle - let count = mesh::attribute_count_of(meshes); - - // Then we ensure the current attribute buffers are big enough, resizing if necessary - unsafe { - self.indices.bind(gl, count.indices); - } - - // We upload all the vertices and indices upfront - let mut solid_vertex_offset = 0; - let mut gradient_vertex_offset = 0; - let mut index_offset = 0; - - for mesh in meshes { - let indices = mesh.indices(); - - unsafe { - gl.buffer_sub_data_u8_slice( - glow::ELEMENT_ARRAY_BUFFER, - (index_offset * std::mem::size_of::<u32>()) as i32, - bytemuck::cast_slice(indices), - ); - - index_offset += indices.len(); - } - - match mesh { - Mesh::Solid { buffers, .. } => { - unsafe { - self.solid.vertices.bind(gl, count.solid_vertices); - - gl.buffer_sub_data_u8_slice( - glow::ARRAY_BUFFER, - (solid_vertex_offset - * std::mem::size_of::<ColoredVertex2D>()) - as i32, - bytemuck::cast_slice(&buffers.vertices), - ); - } - - solid_vertex_offset += buffers.vertices.len(); - } - Mesh::Gradient { buffers, .. } => { - unsafe { - self.gradient - .vertices - .bind(gl, count.gradient_vertices); - - gl.buffer_sub_data_u8_slice( - glow::ARRAY_BUFFER, - (gradient_vertex_offset - * std::mem::size_of::<Vertex2D>()) - as i32, - bytemuck::cast_slice(&buffers.vertices), - ); - } - - gradient_vertex_offset += buffers.vertices.len(); - } - } - } - - // Then we draw each mesh using offsets - let mut last_solid_vertex = 0; - let mut last_gradient_vertex = 0; - let mut last_index = 0; - - for mesh in meshes { - let indices = mesh.indices(); - let origin = mesh.origin(); - - let transform = - transformation * Transformation::translate(origin.x, origin.y); - - let clip_bounds = (mesh.clip_bounds() * scale_factor).snap(); - - unsafe { - gl.scissor( - clip_bounds.x as i32, - (target_height - (clip_bounds.y + clip_bounds.height)) - as i32, - clip_bounds.width as i32, - clip_bounds.height as i32, - ); - } - - match mesh { - Mesh::Solid { buffers, .. } => unsafe { - gl.use_program(Some(self.solid.program)); - gl.bind_vertex_array(Some(self.solid.vertex_array)); - - if transform != self.solid.uniforms.transform { - gl.uniform_matrix_4_f32_slice( - Some(&self.solid.uniforms.transform_location), - false, - transform.as_ref(), - ); - - self.solid.uniforms.transform = transform; - } - - gl.draw_elements_base_vertex( - glow::TRIANGLES, - indices.len() as i32, - glow::UNSIGNED_INT, - (last_index * std::mem::size_of::<u32>()) as i32, - last_solid_vertex as i32, - ); - - last_solid_vertex += buffers.vertices.len(); - }, - Mesh::Gradient { - buffers, gradient, .. - } => unsafe { - gl.use_program(Some(self.gradient.program)); - gl.bind_vertex_array(Some(self.gradient.vertex_array)); - - if transform != self.gradient.uniforms.transform { - gl.uniform_matrix_4_f32_slice( - Some(&self.gradient.uniforms.locations.transform), - false, - transform.as_ref(), - ); - - self.gradient.uniforms.transform = transform; - } - - if &self.gradient.uniforms.gradient != *gradient { - match gradient { - Gradient::Linear(linear) => { - gl.uniform_4_f32( - Some( - &self - .gradient - .uniforms - .locations - .gradient_direction, - ), - linear.start.x, - linear.start.y, - linear.end.x, - linear.end.y, - ); - - gl.uniform_1_i32( - Some( - &self - .gradient - .uniforms - .locations - .color_stops_size, - ), - (linear.color_stops.len() * 2) as i32, - ); - - let mut stops = [0.0; 128]; - - for (index, stop) in linear - .color_stops - .iter() - .enumerate() - .take(16) - { - let [r, g, b, a] = stop.color.into_linear(); - - stops[index * 8] = r; - stops[(index * 8) + 1] = g; - stops[(index * 8) + 2] = b; - stops[(index * 8) + 3] = a; - stops[(index * 8) + 4] = stop.offset; - stops[(index * 8) + 5] = 0.; - stops[(index * 8) + 6] = 0.; - stops[(index * 8) + 7] = 0.; - } - - gl.uniform_4_f32_slice( - Some( - &self - .gradient - .uniforms - .locations - .color_stops, - ), - &stops, - ); - } - } - - self.gradient.uniforms.gradient = (*gradient).clone(); - } - - gl.draw_elements_base_vertex( - glow::TRIANGLES, - indices.len() as i32, - glow::UNSIGNED_INT, - (last_index * std::mem::size_of::<u32>()) as i32, - last_gradient_vertex as i32, - ); - - last_gradient_vertex += buffers.vertices.len(); - }, - } - - last_index += indices.len(); - } - - unsafe { - gl.bind_vertex_array(None); - gl.disable(glow::SCISSOR_TEST); - gl.disable(glow::MULTISAMPLE); - } - } -} - -#[derive(Debug)] -pub struct Buffer<T> { - raw: <glow::Context as HasContext>::Buffer, - target: u32, - usage: u32, - size: usize, - phantom: PhantomData<T>, -} - -impl<T> Buffer<T> { - pub unsafe fn new( - gl: &glow::Context, - target: u32, - usage: u32, - size: usize, - ) -> Self { - let raw = gl.create_buffer().expect("Create buffer"); - - let mut buffer = Buffer { - raw, - target, - usage, - size: 0, - phantom: PhantomData, - }; - - buffer.bind(gl, size); - - buffer - } - - pub unsafe fn bind(&mut self, gl: &glow::Context, size: usize) { - gl.bind_buffer(self.target, Some(self.raw)); - - if self.size < size { - gl.buffer_data_size( - self.target, - (size * std::mem::size_of::<T>()) as i32, - self.usage, - ); - - self.size = size; - } - } -} - -mod solid { - use crate::program; - use crate::triangle; - use glow::{Context, HasContext, NativeProgram}; - use iced_graphics::triangle::ColoredVertex2D; - use iced_graphics::Transformation; - - #[derive(Debug)] - pub struct Program { - pub program: <Context as HasContext>::Program, - pub vertex_array: <glow::Context as HasContext>::VertexArray, - pub vertices: triangle::Buffer<ColoredVertex2D>, - pub uniforms: Uniforms, - } - - impl Program { - pub fn new(gl: &Context, shader_version: &program::Version) -> Self { - let program = unsafe { - let vertex_shader = program::Shader::vertex( - gl, - shader_version, - include_str!("shader/common/solid.vert"), - ); - - let fragment_shader = program::Shader::fragment( - gl, - shader_version, - include_str!("shader/common/solid.frag"), - ); - - program::create( - gl, - &[vertex_shader, fragment_shader], - &[(0, "i_Position"), (1, "i_Color")], - ) - }; - - let vertex_array = unsafe { - gl.create_vertex_array().expect("Create vertex array") - }; - - let vertices = unsafe { - triangle::Buffer::new( - gl, - glow::ARRAY_BUFFER, - glow::DYNAMIC_DRAW, - super::DEFAULT_VERTICES, - ) - }; - - unsafe { - gl.bind_vertex_array(Some(vertex_array)); - - let stride = std::mem::size_of::<ColoredVertex2D>() as i32; - - gl.enable_vertex_attrib_array(0); - gl.vertex_attrib_pointer_f32( - 0, - 2, - glow::FLOAT, - false, - stride, - 0, - ); - - gl.enable_vertex_attrib_array(1); - gl.vertex_attrib_pointer_f32( - 1, - 4, - glow::FLOAT, - false, - stride, - 4 * 2, - ); - - gl.bind_vertex_array(None); - }; - - Self { - program, - vertex_array, - vertices, - uniforms: Uniforms::new(gl, program), - } - } - } - - #[derive(Debug)] - pub struct Uniforms { - pub transform: Transformation, - pub transform_location: <Context as HasContext>::UniformLocation, - } - - impl Uniforms { - fn new(gl: &Context, program: NativeProgram) -> Self { - let transform = Transformation::identity(); - let transform_location = - unsafe { gl.get_uniform_location(program, "u_Transform") } - .expect("Solid - Get u_Transform."); - - unsafe { - gl.use_program(Some(program)); - - gl.uniform_matrix_4_f32_slice( - Some(&transform_location), - false, - transform.as_ref(), - ); - - gl.use_program(None); - } - - Self { - transform, - transform_location, - } - } - } -} - -mod gradient { - use crate::program; - use crate::triangle; - use glow::{Context, HasContext, NativeProgram}; - use iced_graphics::gradient::{self, Gradient}; - use iced_graphics::triangle::Vertex2D; - use iced_graphics::Transformation; - - #[derive(Debug)] - pub struct Program { - pub program: <Context as HasContext>::Program, - pub vertex_array: <glow::Context as HasContext>::VertexArray, - pub vertices: triangle::Buffer<Vertex2D>, - pub uniforms: Uniforms, - } - - impl Program { - pub fn new(gl: &Context, shader_version: &program::Version) -> Self { - let program = unsafe { - let vertex_shader = program::Shader::vertex( - gl, - shader_version, - include_str!("shader/common/gradient.vert"), - ); - - let fragment_shader = program::Shader::fragment( - gl, - shader_version, - include_str!("shader/common/gradient.frag"), - ); - - program::create( - gl, - &[vertex_shader, fragment_shader], - &[(0, "i_Position")], - ) - }; - - let vertex_array = unsafe { - gl.create_vertex_array().expect("Create vertex array") - }; - - let vertices = unsafe { - triangle::Buffer::new( - gl, - glow::ARRAY_BUFFER, - glow::DYNAMIC_DRAW, - super::DEFAULT_VERTICES, - ) - }; - - unsafe { - gl.bind_vertex_array(Some(vertex_array)); - - let stride = std::mem::size_of::<Vertex2D>() as i32; - - gl.enable_vertex_attrib_array(0); - gl.vertex_attrib_pointer_f32( - 0, - 2, - glow::FLOAT, - false, - stride, - 0, - ); - - gl.bind_vertex_array(None); - }; - - Self { - program, - vertex_array, - vertices, - uniforms: Uniforms::new(gl, program), - } - } - } - - #[derive(Debug)] - pub struct Uniforms { - pub gradient: Gradient, - pub transform: Transformation, - pub locations: Locations, - } - - #[derive(Debug)] - pub struct Locations { - pub gradient_direction: <Context as HasContext>::UniformLocation, - pub color_stops_size: <Context as HasContext>::UniformLocation, - //currently the maximum number of stops is 16 due to lack of SSBO in GL2.1 - pub color_stops: <Context as HasContext>::UniformLocation, - pub transform: <Context as HasContext>::UniformLocation, - } - - impl Uniforms { - fn new(gl: &Context, program: NativeProgram) -> Self { - let gradient_direction = unsafe { - gl.get_uniform_location(program, "gradient_direction") - } - .expect("Gradient - Get gradient_direction."); - - let color_stops_size = - unsafe { gl.get_uniform_location(program, "color_stops_size") } - .expect("Gradient - Get color_stops_size."); - - let color_stops = unsafe { - gl.get_uniform_location(program, "color_stops") - .expect("Gradient - Get color_stops.") - }; - - let transform = Transformation::identity(); - let transform_location = - unsafe { gl.get_uniform_location(program, "u_Transform") } - .expect("Solid - Get u_Transform."); - - unsafe { - gl.use_program(Some(program)); - - gl.uniform_matrix_4_f32_slice( - Some(&transform_location), - false, - transform.as_ref(), - ); - - gl.use_program(None); - } - - Self { - gradient: Gradient::Linear(gradient::Linear { - start: Default::default(), - end: Default::default(), - color_stops: vec![], - }), - transform: Transformation::identity(), - locations: Locations { - gradient_direction, - color_stops_size, - color_stops, - transform: transform_location, - }, - } - } - } -} diff --git a/glow/src/window.rs b/glow/src/window.rs deleted file mode 100644 index aac5fb9e..00000000 --- a/glow/src/window.rs +++ /dev/null @@ -1,4 +0,0 @@ -//! Display rendering results on windows. -mod compositor; - -pub use compositor::Compositor; diff --git a/glow/src/window/compositor.rs b/glow/src/window/compositor.rs deleted file mode 100644 index 20756032..00000000 --- a/glow/src/window/compositor.rs +++ /dev/null @@ -1,111 +0,0 @@ -use crate::{Backend, Color, Error, Renderer, Settings, Viewport}; - -use glow::HasContext; -use iced_graphics::{compositor, Antialiasing, Size}; - -use core::ffi::c_void; -use std::marker::PhantomData; - -/// A window graphics backend for iced powered by `glow`. -#[allow(missing_debug_implementations)] -pub struct Compositor<Theme> { - gl: glow::Context, - theme: PhantomData<Theme>, -} - -impl<Theme> iced_graphics::window::GLCompositor for Compositor<Theme> { - type Settings = Settings; - type Renderer = Renderer<Theme>; - - unsafe fn new( - settings: Self::Settings, - loader_function: impl FnMut(&str) -> *const c_void, - ) -> Result<(Self, Self::Renderer), Error> { - let gl = glow::Context::from_loader_function(loader_function); - - log::info!("{:#?}", settings); - - let version = gl.version(); - log::info!( - "OpenGL version: {:?} (Embedded: {})", - version, - version.is_embedded - ); - - let renderer = gl.get_parameter_string(glow::RENDERER); - log::info!("Renderer: {}", renderer); - - // Enable auto-conversion from/to sRGB - gl.enable(glow::FRAMEBUFFER_SRGB); - - // Enable alpha blending - gl.enable(glow::BLEND); - gl.blend_func_separate( - glow::SRC_ALPHA, - glow::ONE_MINUS_SRC_ALPHA, - glow::ONE, - glow::ONE_MINUS_SRC_ALPHA, - ); - - // Disable multisampling by default - gl.disable(glow::MULTISAMPLE); - - let renderer = Renderer::new(Backend::new(&gl, settings)); - - Ok(( - Self { - gl, - theme: PhantomData, - }, - renderer, - )) - } - - fn sample_count(settings: &Settings) -> u32 { - settings - .antialiasing - .map(Antialiasing::sample_count) - .unwrap_or(0) - } - - fn resize_viewport(&mut self, physical_size: Size<u32>) { - unsafe { - self.gl.viewport( - 0, - 0, - physical_size.width as i32, - physical_size.height as i32, - ); - } - } - - fn fetch_information(&self) -> compositor::Information { - let adapter = unsafe { self.gl.get_parameter_string(glow::RENDERER) }; - - compositor::Information { - backend: format!("{:?}", self.gl.version()), - adapter, - } - } - - fn present<T: AsRef<str>>( - &mut self, - renderer: &mut Self::Renderer, - viewport: &Viewport, - color: Color, - overlay: &[T], - ) { - let gl = &self.gl; - - let [r, g, b, a] = color.into_linear(); - - unsafe { - gl.clear_color(r, g, b, a); - gl.clear(glow::COLOR_BUFFER_BIT); - } - - renderer.with_primitives(|backend, primitive| { - backend.present(gl, primitive, viewport, overlay); - }); - } -} diff --git a/glutin/Cargo.toml b/glutin/Cargo.toml deleted file mode 100644 index 2858a088..00000000 --- a/glutin/Cargo.toml +++ /dev/null @@ -1,42 +0,0 @@ -[package] -name = "iced_glutin" -version = "0.8.0" -authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] -edition = "2021" -description = "A glutin runtime for Iced" -license = "MIT" -repository = "https://github.com/iced-rs/iced" -documentation = "https://docs.rs/iced_glutin" -keywords = ["gui", "ui", "graphics", "interface", "widgets"] -categories = ["gui"] - -[features] -trace = ["iced_winit/trace"] -debug = ["iced_winit/debug"] -system = ["iced_winit/system"] - -[dependencies] -log = "0.4" - -[dependencies.glutin] -version = "0.29" -git = "https://github.com/iced-rs/glutin" -rev = "da8d291486b4c9bec12487a46c119c4b1d386abf" - -[dependencies.iced_native] -version = "0.10" -path = "../native" - -[dependencies.iced_winit] -version = "0.9" -path = "../winit" -features = ["application"] - -[dependencies.iced_graphics] -version = "0.8" -path = "../graphics" -features = ["opengl"] - -[dependencies.tracing] -version = "0.1.6" -optional = true diff --git a/glutin/README.md b/glutin/README.md deleted file mode 100644 index 9c0ac459..00000000 --- a/glutin/README.md +++ /dev/null @@ -1,29 +0,0 @@ -# `iced_glutin` -[][documentation] -[](https://crates.io/crates/iced_glutin) -[](https://github.com/iced-rs/iced/blob/master/LICENSE) -[](https://discord.gg/3xZJ65GAhd) - -`iced_glutin` offers some convenient abstractions on top of [`iced_native`] to quickstart development when using [`glutin`]. - -It exposes a renderer-agnostic `Application` trait that can be implemented and then run with a simple call. The use of this trait is optional. A `conversion` module is provided for users that decide to implement a custom event loop. - -<p align="center"> - <img alt="The native target" src="../docs/graphs/native.png" width="80%"> -</p> - -[documentation]: https://docs.rs/iced_glutin -[`iced_native`]: ../native -[`glutin`]: https://github.com/rust-windowing/glutin - -## Installation -Add `iced_glutin` as a dependency in your `Cargo.toml`: - -```toml -iced_glutin = "0.8" -``` - -__Iced moves fast and the `master` branch can contain breaking changes!__ If -you want to learn about a specific release, check out [the release list]. - -[the release list]: https://github.com/iced-rs/iced/releases diff --git a/glutin/src/application.rs b/glutin/src/application.rs deleted file mode 100644 index 5921bdd0..00000000 --- a/glutin/src/application.rs +++ /dev/null @@ -1,508 +0,0 @@ -//! Create interactive, native cross-platform applications. -use crate::mouse; -use crate::{Error, Executor, Runtime}; - -pub use iced_winit::application::StyleSheet; -pub use iced_winit::Application; - -use iced_graphics::window; -use iced_winit::application; -use iced_winit::conversion; -use iced_winit::futures; -use iced_winit::futures::channel::mpsc; -use iced_winit::renderer; -use iced_winit::time::Instant; -use iced_winit::user_interface; -use iced_winit::{Clipboard, Command, Debug, Event, Proxy, Settings}; - -use glutin::window::Window; -use std::mem::ManuallyDrop; - -#[cfg(feature = "tracing")] -use tracing::{info_span, instrument::Instrument}; - -/// Runs an [`Application`] with an executor, compositor, and the provided -/// settings. -pub fn run<A, E, C>( - settings: Settings<A::Flags>, - compositor_settings: C::Settings, -) -> Result<(), Error> -where - A: Application + 'static, - E: Executor + 'static, - C: window::GLCompositor<Renderer = A::Renderer> + 'static, - <A::Renderer as iced_native::Renderer>::Theme: StyleSheet, -{ - use futures::task; - use futures::Future; - use glutin::event_loop::EventLoopBuilder; - use glutin::platform::run_return::EventLoopExtRunReturn; - use glutin::ContextBuilder; - - #[cfg(feature = "trace")] - let _guard = iced_winit::Profiler::init(); - - let mut debug = Debug::new(); - debug.startup_started(); - - #[cfg(feature = "tracing")] - let _ = info_span!("Application::Glutin", "RUN").entered(); - - let mut event_loop = EventLoopBuilder::with_user_event().build(); - let proxy = event_loop.create_proxy(); - - let runtime = { - let executor = E::new().map_err(Error::ExecutorCreationFailed)?; - let proxy = Proxy::new(event_loop.create_proxy()); - - Runtime::new(executor, proxy) - }; - - let (application, init_command) = { - let flags = settings.flags; - - runtime.enter(|| A::new(flags)) - }; - - let context = { - let builder = settings.window.into_builder( - &application.title(), - event_loop.primary_monitor(), - settings.id, - ); - - log::debug!("Window builder: {:#?}", builder); - - let opengl_builder = ContextBuilder::new() - .with_vsync(true) - .with_multisampling(C::sample_count(&compositor_settings) as u16); - - let opengles_builder = opengl_builder.clone().with_gl( - glutin::GlRequest::Specific(glutin::Api::OpenGlEs, (2, 0)), - ); - - let (first_builder, second_builder) = if settings.try_opengles_first { - (opengles_builder, opengl_builder) - } else { - (opengl_builder, opengles_builder) - }; - - log::info!("Trying first builder: {:#?}", first_builder); - - let context = first_builder - .build_windowed(builder.clone(), &event_loop) - .or_else(|_| { - log::info!("Trying second builder: {:#?}", second_builder); - second_builder.build_windowed(builder, &event_loop) - }) - .map_err(|error| { - use glutin::CreationError; - use iced_graphics::Error as ContextError; - - match error { - CreationError::Window(error) => { - Error::WindowCreationFailed(error) - } - CreationError::OpenGlVersionNotSupported => { - Error::GraphicsCreationFailed( - ContextError::VersionNotSupported, - ) - } - CreationError::NoAvailablePixelFormat => { - Error::GraphicsCreationFailed( - ContextError::NoAvailablePixelFormat, - ) - } - error => Error::GraphicsCreationFailed( - ContextError::BackendError(error.to_string()), - ), - } - })?; - - #[allow(unsafe_code)] - unsafe { - context.make_current().expect("Make OpenGL context current") - } - }; - - #[allow(unsafe_code)] - let (compositor, renderer) = unsafe { - C::new(compositor_settings, |address| { - context.get_proc_address(address) - })? - }; - - let (mut event_sender, event_receiver) = mpsc::unbounded(); - let (control_sender, mut control_receiver) = mpsc::unbounded(); - - let mut instance = Box::pin({ - let run_instance = run_instance::<A, E, C>( - application, - compositor, - renderer, - runtime, - proxy, - debug, - event_receiver, - control_sender, - context, - init_command, - settings.exit_on_close_request, - ); - - #[cfg(feature = "tracing")] - let run_instance = - run_instance.instrument(info_span!("Application", "LOOP")); - - run_instance - }); - - let mut context = task::Context::from_waker(task::noop_waker_ref()); - - let _ = event_loop.run_return(move |event, _, control_flow| { - use glutin::event_loop::ControlFlow; - - if let ControlFlow::ExitWithCode(_) = control_flow { - return; - } - - let event = match event { - glutin::event::Event::WindowEvent { - event: - glutin::event::WindowEvent::ScaleFactorChanged { - new_inner_size, - .. - }, - window_id, - } => Some(glutin::event::Event::WindowEvent { - event: glutin::event::WindowEvent::Resized(*new_inner_size), - window_id, - }), - _ => event.to_static(), - }; - - if let Some(event) = event { - event_sender.start_send(event).expect("Send event"); - - let poll = instance.as_mut().poll(&mut context); - - match poll { - task::Poll::Pending => { - if let Ok(Some(flow)) = control_receiver.try_next() { - *control_flow = flow; - } - } - task::Poll::Ready(_) => { - *control_flow = ControlFlow::Exit; - } - } - } - }); - - Ok(()) -} - -async fn run_instance<A, E, C>( - mut application: A, - mut compositor: C, - mut renderer: A::Renderer, - mut runtime: Runtime<E, Proxy<A::Message>, A::Message>, - mut proxy: glutin::event_loop::EventLoopProxy<A::Message>, - mut debug: Debug, - mut event_receiver: mpsc::UnboundedReceiver< - glutin::event::Event<'_, A::Message>, - >, - mut control_sender: mpsc::UnboundedSender<glutin::event_loop::ControlFlow>, - mut context: glutin::ContextWrapper<glutin::PossiblyCurrent, Window>, - init_command: Command<A::Message>, - exit_on_close_request: bool, -) where - A: Application + 'static, - E: Executor + 'static, - C: window::GLCompositor<Renderer = A::Renderer> + 'static, - <A::Renderer as iced_native::Renderer>::Theme: StyleSheet, -{ - use glutin::event; - use glutin::event_loop::ControlFlow; - use iced_winit::futures::stream::StreamExt; - - let mut clipboard = Clipboard::connect(context.window()); - let mut cache = user_interface::Cache::default(); - let mut state = application::State::new(&application, context.window()); - let mut viewport_version = state.viewport_version(); - let mut should_exit = false; - - application::run_command( - &application, - &mut cache, - &state, - &mut renderer, - init_command, - &mut runtime, - &mut clipboard, - &mut should_exit, - &mut proxy, - &mut debug, - context.window(), - || compositor.fetch_information(), - ); - runtime.track(application.subscription()); - - let mut user_interface = - ManuallyDrop::new(application::build_user_interface( - &application, - user_interface::Cache::default(), - &mut renderer, - state.logical_size(), - &mut debug, - )); - - let mut mouse_interaction = mouse::Interaction::default(); - let mut events = Vec::new(); - let mut messages = Vec::new(); - let mut redraw_pending = false; - - debug.startup_finished(); - - while let Some(event) = event_receiver.next().await { - match event { - event::Event::NewEvents(start_cause) => { - redraw_pending = matches!( - start_cause, - event::StartCause::Init - | event::StartCause::Poll - | event::StartCause::ResumeTimeReached { .. } - ); - } - event::Event::MainEventsCleared => { - if !redraw_pending && events.is_empty() && messages.is_empty() { - continue; - } - - debug.event_processing_started(); - - let (interface_state, statuses) = user_interface.update( - &events, - state.cursor_position(), - &mut renderer, - &mut clipboard, - &mut messages, - ); - - debug.event_processing_finished(); - - for event in events.drain(..).zip(statuses.into_iter()) { - runtime.broadcast(event); - } - - if !messages.is_empty() - || matches!( - interface_state, - user_interface::State::Outdated - ) - { - let mut cache = - ManuallyDrop::into_inner(user_interface).into_cache(); - - // Update application - application::update( - &mut application, - &mut cache, - &state, - &mut renderer, - &mut runtime, - &mut clipboard, - &mut should_exit, - &mut proxy, - &mut debug, - &mut messages, - context.window(), - || compositor.fetch_information(), - ); - - // Update window - state.synchronize(&application, context.window()); - - user_interface = - ManuallyDrop::new(application::build_user_interface( - &application, - cache, - &mut renderer, - state.logical_size(), - &mut debug, - )); - - if should_exit { - break; - } - } - - // TODO: Avoid redrawing all the time by forcing widgets to - // request redraws on state changes - // - // Then, we can use the `interface_state` here to decide if a redraw - // is needed right away, or simply wait until a specific time. - let redraw_event = Event::Window( - crate::window::Event::RedrawRequested(Instant::now()), - ); - - let (interface_state, _) = user_interface.update( - &[redraw_event.clone()], - state.cursor_position(), - &mut renderer, - &mut clipboard, - &mut messages, - ); - - debug.draw_started(); - let new_mouse_interaction = user_interface.draw( - &mut renderer, - state.theme(), - &renderer::Style { - text_color: state.text_color(), - }, - state.cursor_position(), - ); - debug.draw_finished(); - - if new_mouse_interaction != mouse_interaction { - context.window().set_cursor_icon( - conversion::mouse_interaction(new_mouse_interaction), - ); - - mouse_interaction = new_mouse_interaction; - } - - context.window().request_redraw(); - runtime - .broadcast((redraw_event, crate::event::Status::Ignored)); - - let _ = control_sender.start_send(match interface_state { - user_interface::State::Updated { - redraw_request: Some(redraw_request), - } => match redraw_request { - crate::window::RedrawRequest::NextFrame => { - ControlFlow::Poll - } - crate::window::RedrawRequest::At(at) => { - ControlFlow::WaitUntil(at) - } - }, - _ => ControlFlow::Wait, - }); - - redraw_pending = false; - } - event::Event::PlatformSpecific(event::PlatformSpecific::MacOS( - event::MacOS::ReceivedUrl(url), - )) => { - use iced_native::event; - events.push(iced_native::Event::PlatformSpecific( - event::PlatformSpecific::MacOS(event::MacOS::ReceivedUrl( - url, - )), - )); - } - event::Event::UserEvent(message) => { - messages.push(message); - } - event::Event::RedrawRequested(_) => { - #[cfg(feature = "tracing")] - let _ = info_span!("Application", "FRAME").entered(); - - debug.render_started(); - - #[allow(unsafe_code)] - unsafe { - if !context.is_current() { - context = context - .make_current() - .expect("Make OpenGL context current"); - } - } - - let current_viewport_version = state.viewport_version(); - - if viewport_version != current_viewport_version { - let physical_size = state.physical_size(); - let logical_size = state.logical_size(); - - debug.layout_started(); - user_interface = ManuallyDrop::new( - ManuallyDrop::into_inner(user_interface) - .relayout(logical_size, &mut renderer), - ); - debug.layout_finished(); - - debug.draw_started(); - let new_mouse_interaction = user_interface.draw( - &mut renderer, - state.theme(), - &renderer::Style { - text_color: state.text_color(), - }, - state.cursor_position(), - ); - debug.draw_finished(); - - if new_mouse_interaction != mouse_interaction { - context.window().set_cursor_icon( - conversion::mouse_interaction( - new_mouse_interaction, - ), - ); - - mouse_interaction = new_mouse_interaction; - } - - context.resize(glutin::dpi::PhysicalSize::new( - physical_size.width, - physical_size.height, - )); - - compositor.resize_viewport(physical_size); - - viewport_version = current_viewport_version; - } - - compositor.present( - &mut renderer, - state.viewport(), - state.background_color(), - &debug.overlay(), - ); - - context.swap_buffers().expect("Swap buffers"); - - debug.render_finished(); - - // TODO: Handle animations! - // Maybe we can use `ControlFlow::WaitUntil` for this. - } - event::Event::WindowEvent { - event: window_event, - .. - } => { - if application::requests_exit(&window_event, state.modifiers()) - && exit_on_close_request - { - break; - } - - state.update(context.window(), &window_event, &mut debug); - - if let Some(event) = conversion::window_event( - &window_event, - state.scale_factor(), - state.modifiers(), - ) { - events.push(event); - } - } - _ => {} - } - } - - // Manually drop the user interface - drop(ManuallyDrop::into_inner(user_interface)); -} diff --git a/glutin/src/lib.rs b/glutin/src/lib.rs deleted file mode 100644 index 33afd664..00000000 --- a/glutin/src/lib.rs +++ /dev/null @@ -1,33 +0,0 @@ -//! A windowing shell for [`iced`], on top of [`glutin`]. -//! -//!  -//! -//! [`iced`]: https://github.com/iced-rs/iced -//! [`glutin`]: https://github.com/rust-windowing/glutin -#![doc( - html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg" -)] -#![deny( - missing_docs, - missing_debug_implementations, - unsafe_code, - unused_results, - clippy::extra_unused_lifetimes, - clippy::from_over_into, - clippy::needless_borrow, - clippy::new_without_default, - clippy::useless_conversion -)] -#![forbid(rust_2018_idioms)] -#![allow(clippy::inherent_to_string, clippy::type_complexity)] -#![cfg_attr(docsrs, feature(doc_cfg))] - -pub use glutin; - -#[doc(no_inline)] -pub use iced_winit::*; - -pub mod application; - -#[doc(no_inline)] -pub use application::Application; diff --git a/graphics/Cargo.toml b/graphics/Cargo.toml index 57b291e7..125ea17d 100644 --- a/graphics/Cargo.toml +++ b/graphics/Cargo.toml @@ -11,26 +11,9 @@ keywords = ["gui", "ui", "graphics", "interface", "widgets"] categories = ["gui"] [features] -svg = ["resvg"] -image = ["png", "jpeg", "jpeg_rayon", "gif", "webp", "bmp"] -png = ["image_rs/png"] -jpeg = ["image_rs/jpeg"] -jpeg_rayon = ["image_rs/jpeg_rayon"] -gif = ["image_rs/gif"] -webp = ["image_rs/webp"] -pnm = ["image_rs/pnm"] -ico = ["image_rs/ico"] -bmp = ["image_rs/bmp"] -hdr = ["image_rs/hdr"] -dds = ["image_rs/dds"] -farbfeld = ["image_rs/farbfeld"] -canvas = ["lyon"] -qr_code = ["qrcode", "canvas"] -font-source = ["font-kit"] -font-fallback = [] -font-icons = [] +geometry = ["lyon_path"] opengl = [] -image_rs = ["kamadak-exif"] +image = ["dep:image", "kamadak-exif"] [dependencies] glam = "0.21.3" @@ -43,41 +26,26 @@ bitflags = "1.2" version = "1.4" features = ["derive"] -[dependencies.iced_native] -version = "0.10" -path = "../native" +[dependencies.iced_core] +version = "0.9" +path = "../core" -[dependencies.iced_style] -version = "0.8" -path = "../style" - -[dependencies.lyon] -version = "1.0" -optional = true - -[dependencies.qrcode] -version = "0.12" -optional = true -default-features = false - -[dependencies.font-kit] -version = "0.10" +[dependencies.tiny-skia] +version = "0.9" optional = true -[dependencies.image_rs] +[dependencies.image] version = "0.24" -package = "image" -default-features = false -optional = true - -[dependencies.resvg] -version = "0.29" optional = true [dependencies.kamadak-exif] version = "0.5" optional = true +[dependencies.lyon_path] +version = "1" +optional = true + [package.metadata.docs.rs] rustdoc-args = ["--cfg", "docsrs"] all-features = true diff --git a/graphics/fonts/Lato-Regular.ttf b/graphics/fonts/Lato-Regular.ttf Binary files differdeleted file mode 100644 index 33eba8b1..00000000 --- a/graphics/fonts/Lato-Regular.ttf +++ /dev/null diff --git a/graphics/fonts/OFL.txt b/graphics/fonts/OFL.txt deleted file mode 100644 index dfca0da4..00000000 --- a/graphics/fonts/OFL.txt +++ /dev/null @@ -1,93 +0,0 @@ -Copyright (c) 2010-2014 by tyPoland Lukasz Dziedzic (team@latofonts.com) with Reserved Font Name "Lato"
-
-This Font Software is licensed under the SIL Open Font License, Version 1.1.
-This license is copied below, and is also available with a FAQ at:
-http://scripts.sil.org/OFL
-
-
------------------------------------------------------------
-SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
------------------------------------------------------------
-
-PREAMBLE
-The goals of the Open Font License (OFL) are to stimulate worldwide
-development of collaborative font projects, to support the font creation
-efforts of academic and linguistic communities, and to provide a free and
-open framework in which fonts may be shared and improved in partnership
-with others.
-
-The OFL allows the licensed fonts to be used, studied, modified and
-redistributed freely as long as they are not sold by themselves. The
-fonts, including any derivative works, can be bundled, embedded,
-redistributed and/or sold with any software provided that any reserved
-names are not used by derivative works. The fonts and derivatives,
-however, cannot be released under any other type of license. The
-requirement for fonts to remain under this license does not apply
-to any document created using the fonts or their derivatives.
-
-DEFINITIONS
-"Font Software" refers to the set of files released by the Copyright
-Holder(s) under this license and clearly marked as such. This may
-include source files, build scripts and documentation.
-
-"Reserved Font Name" refers to any names specified as such after the
-copyright statement(s).
-
-"Original Version" refers to the collection of Font Software components as
-distributed by the Copyright Holder(s).
-
-"Modified Version" refers to any derivative made by adding to, deleting,
-or substituting -- in part or in whole -- any of the components of the
-Original Version, by changing formats or by porting the Font Software to a
-new environment.
-
-"Author" refers to any designer, engineer, programmer, technical
-writer or other person who contributed to the Font Software.
-
-PERMISSION & CONDITIONS
-Permission is hereby granted, free of charge, to any person obtaining
-a copy of the Font Software, to use, study, copy, merge, embed, modify,
-redistribute, and sell modified and unmodified copies of the Font
-Software, subject to the following conditions:
-
-1) Neither the Font Software nor any of its individual components,
-in Original or Modified Versions, may be sold by itself.
-
-2) Original or Modified Versions of the Font Software may be bundled,
-redistributed and/or sold with any software, provided that each copy
-contains the above copyright notice and this license. These can be
-included either as stand-alone text files, human-readable headers or
-in the appropriate machine-readable metadata fields within text or
-binary files as long as those fields can be easily viewed by the user.
-
-3) No Modified Version of the Font Software may use the Reserved Font
-Name(s) unless explicit written permission is granted by the corresponding
-Copyright Holder. This restriction only applies to the primary font name as
-presented to the users.
-
-4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
-Software shall not be used to promote, endorse or advertise any
-Modified Version, except to acknowledge the contribution(s) of the
-Copyright Holder(s) and the Author(s) or with their explicit written
-permission.
-
-5) The Font Software, modified or unmodified, in part or in whole,
-must be distributed entirely under this license, and must not be
-distributed under any other license. The requirement for fonts to
-remain under this license does not apply to any document created
-using the Font Software.
-
-TERMINATION
-This license becomes null and void if any of the above conditions are
-not met.
-
-DISCLAIMER
-THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
-MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
-OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
-COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
-INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
-DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
-OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/graphics/src/backend.rs b/graphics/src/backend.rs index 256b7ab5..ae89da06 100644 --- a/graphics/src/backend.rs +++ b/graphics/src/backend.rs @@ -1,8 +1,10 @@ //! Write a graphics backend. -use iced_native::image; -use iced_native::svg; -use iced_native::text; -use iced_native::{Font, Point, Size}; +use iced_core::image; +use iced_core::svg; +use iced_core::text; +use iced_core::{Font, Point, Size}; + +use std::borrow::Cow; /// The graphics backend of a [`Renderer`]. /// @@ -31,6 +33,9 @@ pub trait Text { /// [`ICON_FONT`]: Self::ICON_FONT const ARROW_DOWN_ICON: char; + /// Returns the default [`Font`]. + fn default_font(&self) -> Font; + /// Returns the default size of text. fn default_size(&self) -> f32; @@ -41,8 +46,10 @@ pub trait Text { &self, contents: &str, size: f32, + line_height: text::LineHeight, font: Font, bounds: Size, + shaping: text::Shaping, ) -> (f32, f32); /// Tests whether the provided point is within the boundaries of [`Text`] @@ -56,11 +63,16 @@ pub trait Text { &self, contents: &str, size: f32, + line_height: text::LineHeight, font: Font, bounds: Size, + shaping: text::Shaping, point: Point, nearest_only: bool, ) -> Option<text::Hit>; + + /// Loads a [`Font`] from its bytes. + fn load_font(&mut self, font: Cow<'static, [u8]>); } /// A graphics backend that supports image rendering. diff --git a/graphics/src/window/compositor.rs b/graphics/src/compositor.rs index db4ba45d..d55e801a 100644 --- a/graphics/src/window/compositor.rs +++ b/graphics/src/compositor.rs @@ -1,6 +1,8 @@ //! A compositor is responsible for initializing a renderer and managing window //! surfaces. -use crate::{Color, Error, Viewport}; +use crate::{Error, Viewport}; + +use iced_core::Color; use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle}; use thiserror::Error; @@ -11,7 +13,7 @@ pub trait Compositor: Sized { type Settings: Default; /// The iced renderer of the backend. - type Renderer: iced_native::Renderer; + type Renderer: iced_core::Renderer; /// The surface of the backend. type Surface; @@ -28,6 +30,8 @@ pub trait Compositor: Sized { fn create_surface<W: HasRawWindowHandle + HasRawDisplayHandle>( &mut self, window: &W, + width: u32, + height: u32, ) -> Self::Surface; /// Configures a new [`Surface`] with the given dimensions. diff --git a/graphics/src/damage.rs b/graphics/src/damage.rs new file mode 100644 index 00000000..c6b0f759 --- /dev/null +++ b/graphics/src/damage.rs @@ -0,0 +1,147 @@ +//! Track and compute the damage of graphical primitives. +use crate::core::{Rectangle, Size}; +use crate::Primitive; + +use std::sync::Arc; + +/// Computes the damage regions between the two given primitives. +pub fn regions(a: &Primitive, b: &Primitive) -> Vec<Rectangle> { + match (a, b) { + ( + Primitive::Group { + primitives: primitives_a, + }, + Primitive::Group { + primitives: primitives_b, + }, + ) => return list(primitives_a, primitives_b), + ( + Primitive::Clip { + bounds: bounds_a, + content: content_a, + .. + }, + Primitive::Clip { + bounds: bounds_b, + content: content_b, + .. + }, + ) => { + if bounds_a == bounds_b { + return regions(content_a, content_b) + .into_iter() + .filter_map(|r| r.intersection(&bounds_a.expand(1.0))) + .collect(); + } else { + return vec![bounds_a.expand(1.0), bounds_b.expand(1.0)]; + } + } + ( + Primitive::Translate { + translation: translation_a, + content: content_a, + }, + Primitive::Translate { + translation: translation_b, + content: content_b, + }, + ) => { + if translation_a == translation_b { + return regions(content_a, content_b) + .into_iter() + .map(|r| r + *translation_a) + .collect(); + } + } + ( + Primitive::Cache { content: content_a }, + Primitive::Cache { content: content_b }, + ) => { + if Arc::ptr_eq(content_a, content_b) { + return vec![]; + } + } + _ if a == b => return vec![], + _ => {} + } + + let bounds_a = a.bounds(); + let bounds_b = b.bounds(); + + if bounds_a == bounds_b { + vec![bounds_a] + } else { + vec![bounds_a, bounds_b] + } +} + +/// Computes the damage regions between the two given lists of primitives. +pub fn list(previous: &[Primitive], current: &[Primitive]) -> Vec<Rectangle> { + let damage = previous + .iter() + .zip(current) + .flat_map(|(a, b)| regions(a, b)); + + if previous.len() == current.len() { + damage.collect() + } else { + let (smaller, bigger) = if previous.len() < current.len() { + (previous, current) + } else { + (current, previous) + }; + + // Extend damage by the added/removed primitives + damage + .chain(bigger[smaller.len()..].iter().map(Primitive::bounds)) + .collect() + } +} + +/// Groups the given damage regions that are close together inside the given +/// bounds. +pub fn group( + mut damage: Vec<Rectangle>, + scale_factor: f32, + bounds: Size<u32>, +) -> Vec<Rectangle> { + use std::cmp::Ordering; + + const AREA_THRESHOLD: f32 = 20_000.0; + + let bounds = Rectangle { + x: 0.0, + y: 0.0, + width: bounds.width as f32, + height: bounds.height as f32, + }; + + damage.sort_by(|a, b| { + a.x.partial_cmp(&b.x) + .unwrap_or(Ordering::Equal) + .then_with(|| a.y.partial_cmp(&b.y).unwrap_or(Ordering::Equal)) + }); + + let mut output = Vec::new(); + let mut scaled = damage + .into_iter() + .filter_map(|region| (region * scale_factor).intersection(&bounds)) + .filter(|region| region.width >= 1.0 && region.height >= 1.0); + + if let Some(mut current) = scaled.next() { + for region in scaled { + let union = current.union(®ion); + + if union.area() - current.area() - region.area() <= AREA_THRESHOLD { + current = union; + } else { + output.push(current); + current = region; + } + } + + output.push(current); + } + + output +} diff --git a/graphics/src/font.rs b/graphics/src/font.rs deleted file mode 100644 index d55d0faf..00000000 --- a/graphics/src/font.rs +++ /dev/null @@ -1,35 +0,0 @@ -//! Find system fonts or use the built-in ones. -#[cfg(feature = "font-source")] -mod source; - -#[cfg(feature = "font-source")] -#[cfg_attr(docsrs, doc(cfg(feature = "font-source")))] -pub use source::Source; - -#[cfg(feature = "font-source")] -#[cfg_attr(docsrs, doc(cfg(feature = "font-source")))] -pub use font_kit::{ - error::SelectionError as LoadError, family_name::FamilyName as Family, -}; - -/// A built-in fallback font, for convenience. -#[cfg(feature = "font-fallback")] -#[cfg_attr(docsrs, doc(cfg(feature = "font-fallback")))] -pub const FALLBACK: &[u8] = include_bytes!("../fonts/Lato-Regular.ttf"); - -/// A built-in icon font, for convenience. -#[cfg(feature = "font-icons")] -#[cfg_attr(docsrs, doc(cfg(feature = "font-icons")))] -pub const ICONS: iced_native::Font = iced_native::Font::External { - name: "iced_wgpu icons", - bytes: include_bytes!("../fonts/Icons.ttf"), -}; - -/// The `char` representing a ✔ icon in the built-in [`ICONS`] font. -#[cfg(feature = "font-icons")] -#[cfg_attr(docsrs, doc(cfg(feature = "font-icons")))] -pub const CHECKMARK_ICON: char = '\u{F00C}'; - -/// The `char` representing a ▼ icon in the built-in [`ICONS`] font. -#[cfg(feature = "font-icons")] -pub const ARROW_DOWN_ICON: char = '\u{E800}'; diff --git a/graphics/src/font/source.rs b/graphics/src/font/source.rs deleted file mode 100644 index c0b50e1d..00000000 --- a/graphics/src/font/source.rs +++ /dev/null @@ -1,45 +0,0 @@ -use crate::font::{Family, LoadError}; - -/// A font source that can find and load system fonts. -#[allow(missing_debug_implementations)] -pub struct Source { - raw: font_kit::source::SystemSource, -} - -impl Source { - /// Creates a new [`Source`]. - pub fn new() -> Self { - Source { - raw: font_kit::source::SystemSource::new(), - } - } - - /// Finds and loads a font matching the set of provided family priorities. - pub fn load(&self, families: &[Family]) -> Result<Vec<u8>, LoadError> { - let font = self.raw.select_best_match( - families, - &font_kit::properties::Properties::default(), - )?; - - match font { - font_kit::handle::Handle::Path { path, .. } => { - use std::io::Read; - - let mut buf = Vec::new(); - let mut reader = std::fs::File::open(path).expect("Read font"); - let _ = reader.read_to_end(&mut buf); - - Ok(buf) - } - font_kit::handle::Handle::Memory { bytes, .. } => { - Ok(bytes.as_ref().clone()) - } - } - } -} - -impl Default for Source { - fn default() -> Self { - Self::new() - } -} diff --git a/graphics/src/geometry.rs b/graphics/src/geometry.rs new file mode 100644 index 00000000..88997288 --- /dev/null +++ b/graphics/src/geometry.rs @@ -0,0 +1,33 @@ +//! Build and draw geometry. +pub mod fill; +pub mod path; +pub mod stroke; + +mod style; +mod text; + +pub use fill::Fill; +pub use path::Path; +pub use stroke::{LineCap, LineDash, LineJoin, Stroke}; +pub use style::Style; +pub use text::Text; + +pub use crate::core::gradient::{self, Gradient}; + +use crate::Primitive; + +/// A bunch of shapes that can be drawn. +#[derive(Debug, Clone)] +pub struct Geometry(pub Primitive); + +impl From<Geometry> for Primitive { + fn from(geometry: Geometry) -> Self { + geometry.0 + } +} + +/// A renderer capable of drawing some [`Geometry`]. +pub trait Renderer: crate::core::Renderer { + /// Draws the given layers of [`Geometry`]. + fn draw(&mut self, layers: Vec<Geometry>); +} diff --git a/graphics/src/widget/canvas/fill.rs b/graphics/src/geometry/fill.rs index e954ebb5..2e8c1669 100644 --- a/graphics/src/widget/canvas/fill.rs +++ b/graphics/src/geometry/fill.rs @@ -1,7 +1,7 @@ //! Fill [crate::widget::canvas::Geometry] with a certain style. -use crate::{Color, Gradient}; +use iced_core::{Color, Gradient}; -pub use crate::widget::canvas::Style; +pub use crate::geometry::Style; /// The style used to fill geometry. #[derive(Debug, Clone)] @@ -19,14 +19,14 @@ pub struct Fill { /// By default, it is set to `NonZero`. /// /// [1]: https://www.w3.org/TR/SVG/painting.html#FillRuleProperty - pub rule: FillRule, + pub rule: Rule, } impl Default for Fill { fn default() -> Self { Self { style: Style::Solid(Color::BLACK), - rule: FillRule::NonZero, + rule: Rule::NonZero, } } } @@ -57,16 +57,7 @@ impl From<Gradient> for Fill { /// [1]: https://www.w3.org/TR/SVG/painting.html#FillRuleProperty #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[allow(missing_docs)] -pub enum FillRule { +pub enum Rule { NonZero, EvenOdd, } - -impl From<FillRule> for lyon::tessellation::FillRule { - fn from(rule: FillRule) -> lyon::tessellation::FillRule { - match rule { - FillRule::NonZero => lyon::tessellation::FillRule::NonZero, - FillRule::EvenOdd => lyon::tessellation::FillRule::EvenOdd, - } - } -} diff --git a/graphics/src/widget/canvas/path.rs b/graphics/src/geometry/path.rs index aeb2589e..3d8fc6fa 100644 --- a/graphics/src/widget/canvas/path.rs +++ b/graphics/src/geometry/path.rs @@ -7,18 +7,16 @@ mod builder; pub use arc::Arc; pub use builder::Builder; -use crate::widget::canvas::LineDash; +pub use lyon_path; -use iced_native::{Point, Size}; -use lyon::algorithms::walk::{walk_along_path, RepeatedPattern, WalkerEvent}; -use lyon::path::iterator::PathIterator; +use iced_core::{Point, Size}; /// An immutable set of points that may or may not be connected. /// /// A single [`Path`] can represent different kinds of 2D shapes! #[derive(Debug, Clone)] pub struct Path { - raw: lyon::path::Path, + raw: lyon_path::Path, } impl Path { @@ -55,55 +53,17 @@ impl Path { Self::new(|p| p.circle(center, radius)) } + /// Returns the internal [`lyon_path::Path`]. #[inline] - pub(crate) fn raw(&self) -> &lyon::path::Path { + pub fn raw(&self) -> &lyon_path::Path { &self.raw } + /// Returns the current [`Path`] with the given transform applied to it. #[inline] - pub(crate) fn transformed( - &self, - transform: &lyon::math::Transform, - ) -> Path { + pub fn transform(&self, transform: &lyon_path::math::Transform) -> Path { Path { raw: self.raw.clone().transformed(transform), } } } - -pub(super) fn dashed(path: &Path, line_dash: LineDash<'_>) -> Path { - Path::new(|builder| { - let segments_odd = (line_dash.segments.len() % 2 == 1) - .then(|| [line_dash.segments, line_dash.segments].concat()); - - let mut draw_line = false; - - walk_along_path( - path.raw().iter().flattened(0.01), - 0.0, - lyon::tessellation::StrokeOptions::DEFAULT_TOLERANCE, - &mut RepeatedPattern { - callback: |event: WalkerEvent<'_>| { - let point = Point { - x: event.position.x, - y: event.position.y, - }; - - if draw_line { - builder.line_to(point); - } else { - builder.move_to(point); - } - - draw_line = !draw_line; - - true - }, - index: line_dash.offset, - intervals: segments_odd - .as_deref() - .unwrap_or(line_dash.segments), - }, - ); - }) -} diff --git a/graphics/src/widget/canvas/path/arc.rs b/graphics/src/geometry/path/arc.rs index b8e72daf..2cdebb66 100644 --- a/graphics/src/widget/canvas/path/arc.rs +++ b/graphics/src/geometry/path/arc.rs @@ -1,5 +1,5 @@ //! Build and draw curves. -use iced_native::{Point, Vector}; +use iced_core::{Point, Vector}; /// A segment of a differentiable curve. #[derive(Debug, Clone, Copy)] diff --git a/graphics/src/widget/canvas/path/builder.rs b/graphics/src/geometry/path/builder.rs index 5121aa68..794dd3bc 100644 --- a/graphics/src/widget/canvas/path/builder.rs +++ b/graphics/src/geometry/path/builder.rs @@ -1,35 +1,38 @@ -use crate::widget::canvas::path::{arc, Arc, Path}; +use crate::geometry::path::{arc, Arc, Path}; -use iced_native::{Point, Size}; -use lyon::path::builder::SvgPathBuilder; +use iced_core::{Point, Size}; + +use lyon_path::builder::{self, SvgPathBuilder}; +use lyon_path::geom; +use lyon_path::math; /// A [`Path`] builder. /// /// Once a [`Path`] is built, it can no longer be mutated. #[allow(missing_debug_implementations)] pub struct Builder { - raw: lyon::path::builder::WithSvg<lyon::path::path::BuilderImpl>, + raw: builder::WithSvg<lyon_path::path::BuilderImpl>, } impl Builder { /// Creates a new [`Builder`]. pub fn new() -> Builder { Builder { - raw: lyon::path::Path::builder().with_svg(), + raw: lyon_path::Path::builder().with_svg(), } } /// Moves the starting point of a new sub-path to the given `Point`. #[inline] pub fn move_to(&mut self, point: Point) { - let _ = self.raw.move_to(lyon::math::Point::new(point.x, point.y)); + let _ = self.raw.move_to(math::Point::new(point.x, point.y)); } /// Connects the last point in the [`Path`] to the given `Point` with a /// straight line. #[inline] pub fn line_to(&mut self, point: Point) { - let _ = self.raw.line_to(lyon::math::Point::new(point.x, point.y)); + let _ = self.raw.line_to(math::Point::new(point.x, point.y)); } /// Adds an [`Arc`] to the [`Path`] from `start_angle` to `end_angle` in @@ -53,8 +56,6 @@ impl Builder { /// See [the HTML5 specification of `arcTo`](https://html.spec.whatwg.org/multipage/canvas.html#building-paths:dom-context-2d-arcto) /// for more details and examples. pub fn arc_to(&mut self, a: Point, b: Point, radius: f32) { - use lyon::{math, path}; - let start = self.raw.current_position(); let mid = math::Point::new(a.x, a.y); let end = math::Point::new(b.x, b.y); @@ -92,7 +93,7 @@ impl Builder { self.raw.arc_to( math::Vector::new(radius, radius), math::Angle::radians(0.0), - path::ArcFlags { + lyon_path::ArcFlags { large_arc: false, sweep, }, @@ -102,8 +103,6 @@ impl Builder { /// Adds an ellipse to the [`Path`] using a clockwise direction. pub fn ellipse(&mut self, arc: arc::Elliptical) { - use lyon::{geom, math}; - let arc = geom::Arc { center: math::Point::new(arc.center.x, arc.center.y), radii: math::Vector::new(arc.radii.x, arc.radii.y), @@ -128,8 +127,6 @@ impl Builder { control_b: Point, to: Point, ) { - use lyon::math; - let _ = self.raw.cubic_bezier_to( math::Point::new(control_a.x, control_a.y), math::Point::new(control_b.x, control_b.y), @@ -141,8 +138,6 @@ impl Builder { /// and its end point. #[inline] pub fn quadratic_curve_to(&mut self, control: Point, to: Point) { - use lyon::math; - let _ = self.raw.quadratic_bezier_to( math::Point::new(control.x, control.y), math::Point::new(to.x, to.y), diff --git a/graphics/src/widget/canvas/stroke.rs b/graphics/src/geometry/stroke.rs index 49f5701c..69a76e1c 100644 --- a/graphics/src/widget/canvas/stroke.rs +++ b/graphics/src/geometry/stroke.rs @@ -1,7 +1,7 @@ //! Create lines from a [crate::widget::canvas::Path] and assigns them various attributes/styles. -pub use crate::widget::canvas::Style; +pub use crate::geometry::Style; -use iced_native::Color; +use iced_core::Color; /// The style of a stroke. #[derive(Debug, Clone)] @@ -72,16 +72,6 @@ pub enum LineCap { Round, } -impl From<LineCap> for lyon::tessellation::LineCap { - fn from(line_cap: LineCap) -> lyon::tessellation::LineCap { - match line_cap { - LineCap::Butt => lyon::tessellation::LineCap::Butt, - LineCap::Square => lyon::tessellation::LineCap::Square, - LineCap::Round => lyon::tessellation::LineCap::Round, - } - } -} - /// The shape used at the corners of paths or basic shapes when they are /// stroked. #[derive(Debug, Clone, Copy, Default)] @@ -95,16 +85,6 @@ pub enum LineJoin { Bevel, } -impl From<LineJoin> for lyon::tessellation::LineJoin { - fn from(line_join: LineJoin) -> lyon::tessellation::LineJoin { - match line_join { - LineJoin::Miter => lyon::tessellation::LineJoin::Miter, - LineJoin::Round => lyon::tessellation::LineJoin::Round, - LineJoin::Bevel => lyon::tessellation::LineJoin::Bevel, - } - } -} - /// The dash pattern used when stroking the line. #[derive(Debug, Clone, Copy, Default)] pub struct LineDash<'a> { diff --git a/graphics/src/widget/canvas/style.rs b/graphics/src/geometry/style.rs index 6794f2e7..be9ee376 100644 --- a/graphics/src/widget/canvas/style.rs +++ b/graphics/src/geometry/style.rs @@ -1,4 +1,4 @@ -use crate::{Color, Gradient}; +use iced_core::{Color, Gradient}; /// The coloring style of some drawing. #[derive(Debug, Clone, PartialEq)] diff --git a/graphics/src/widget/canvas/text.rs b/graphics/src/geometry/text.rs index 056f8204..c584f3cd 100644 --- a/graphics/src/widget/canvas/text.rs +++ b/graphics/src/geometry/text.rs @@ -1,5 +1,6 @@ -use crate::alignment; -use crate::{Color, Font, Point}; +use crate::core::alignment; +use crate::core::text::{LineHeight, Shaping}; +use crate::core::{Color, Font, Point}; /// A bunch of text that can be drawn to a canvas #[derive(Debug, Clone)] @@ -19,12 +20,16 @@ pub struct Text { pub color: Color, /// The size of the text pub size: f32, + /// 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 horizontal_alignment: alignment::Horizontal, /// The vertical alignment of the text pub vertical_alignment: alignment::Vertical, + /// The shaping strategy of the text. + pub shaping: Shaping, } impl Default for Text { @@ -34,9 +39,11 @@ impl Default for Text { position: Point::ORIGIN, color: Color::BLACK, size: 16.0, - font: Font::Default, + line_height: LineHeight::Relative(1.2), + font: Font::default(), horizontal_alignment: alignment::Horizontal::Left, vertical_alignment: alignment::Vertical::Top, + shaping: Shaping::Basic, } } } diff --git a/graphics/src/image.rs b/graphics/src/image.rs index 04f4ff9d..6b43f4a8 100644 --- a/graphics/src/image.rs +++ b/graphics/src/image.rs @@ -1,10 +1,95 @@ -//! Render images. -#[cfg(feature = "image_rs")] -pub mod raster; +//! Load and operate on images. +use crate::core::image::{Data, Handle}; -#[cfg(feature = "svg")] -pub mod vector; +use bitflags::bitflags; -pub mod storage; +pub use ::image as image_rs; -pub use storage::Storage; +/// Tries to load an image by its [`Handle`]. +pub fn load(handle: &Handle) -> image_rs::ImageResult<image_rs::DynamicImage> { + match handle.data() { + Data::Path(path) => { + let image = ::image::open(path)?; + + let operation = std::fs::File::open(path) + .ok() + .map(std::io::BufReader::new) + .and_then(|mut reader| Operation::from_exif(&mut reader).ok()) + .unwrap_or_else(Operation::empty); + + Ok(operation.perform(image)) + } + Data::Bytes(bytes) => { + let image = ::image::load_from_memory(bytes)?; + let operation = + Operation::from_exif(&mut std::io::Cursor::new(bytes)) + .ok() + .unwrap_or_else(Operation::empty); + + Ok(operation.perform(image)) + } + Data::Rgba { + width, + height, + pixels, + } => { + if let Some(image) = image_rs::ImageBuffer::from_vec( + *width, + *height, + pixels.to_vec(), + ) { + Ok(image_rs::DynamicImage::ImageRgba8(image)) + } else { + Err(image_rs::error::ImageError::Limits( + image_rs::error::LimitError::from_kind( + image_rs::error::LimitErrorKind::DimensionError, + ), + )) + } + } + } +} + +bitflags! { + struct Operation: u8 { + const FLIP_HORIZONTALLY = 0b001; + const ROTATE_180 = 0b010; + const FLIP_DIAGONALLY = 0b100; + } +} + +impl Operation { + // Meaning of the returned value is described e.g. at: + // https://magnushoff.com/articles/jpeg-orientation/ + fn from_exif<R>(reader: &mut R) -> Result<Self, exif::Error> + where + R: std::io::BufRead + std::io::Seek, + { + let exif = exif::Reader::new().read_from_container(reader)?; + + Ok(exif + .get_field(exif::Tag::Orientation, exif::In::PRIMARY) + .and_then(|field| field.value.get_uint(0)) + .and_then(|value| u8::try_from(value).ok()) + .and_then(|value| Self::from_bits(value.saturating_sub(1))) + .unwrap_or_else(Self::empty)) + } + + fn perform(self, mut image: image::DynamicImage) -> image::DynamicImage { + use image::imageops; + + if self.contains(Self::FLIP_DIAGONALLY) { + imageops::flip_vertical_in_place(&mut image) + } + + if self.contains(Self::ROTATE_180) { + imageops::rotate180_in_place(&mut image); + } + + if self.contains(Self::FLIP_HORIZONTALLY) { + imageops::flip_horizontal_in_place(&mut image); + } + + image + } +} diff --git a/graphics/src/image/raster.rs b/graphics/src/image/raster.rs deleted file mode 100644 index da46c30f..00000000 --- a/graphics/src/image/raster.rs +++ /dev/null @@ -1,242 +0,0 @@ -//! Raster image loading and caching. -use crate::image::Storage; -use crate::Size; - -use iced_native::image; - -use bitflags::bitflags; -use std::collections::{HashMap, HashSet}; - -/// Entry in cache corresponding to an image handle -#[derive(Debug)] -pub enum Memory<T: Storage> { - /// Image data on host - Host(::image_rs::ImageBuffer<::image_rs::Rgba<u8>, Vec<u8>>), - /// Storage entry - Device(T::Entry), - /// Image not found - NotFound, - /// Invalid image data - Invalid, -} - -impl<T: Storage> Memory<T> { - /// Width and height of image - pub fn dimensions(&self) -> Size<u32> { - use crate::image::storage::Entry; - - match self { - Memory::Host(image) => { - let (width, height) = image.dimensions(); - - Size::new(width, height) - } - Memory::Device(entry) => entry.size(), - Memory::NotFound => Size::new(1, 1), - Memory::Invalid => Size::new(1, 1), - } - } -} - -/// Caches image raster data -#[derive(Debug)] -pub struct Cache<T: Storage> { - map: HashMap<u64, Memory<T>>, - hits: HashSet<u64>, -} - -impl<T: Storage> Cache<T> { - /// Load image - pub fn load(&mut self, handle: &image::Handle) -> &mut Memory<T> { - if self.contains(handle) { - return self.get(handle).unwrap(); - } - - let memory = match handle.data() { - image::Data::Path(path) => { - if let Ok(image) = image_rs::open(path) { - let operation = std::fs::File::open(path) - .ok() - .map(std::io::BufReader::new) - .and_then(|mut reader| { - Operation::from_exif(&mut reader).ok() - }) - .unwrap_or_else(Operation::empty); - - Memory::Host(operation.perform(image.to_rgba8())) - } else { - Memory::NotFound - } - } - image::Data::Bytes(bytes) => { - if let Ok(image) = image_rs::load_from_memory(bytes) { - let operation = - Operation::from_exif(&mut std::io::Cursor::new(bytes)) - .ok() - .unwrap_or_else(Operation::empty); - - Memory::Host(operation.perform(image.to_rgba8())) - } else { - Memory::Invalid - } - } - image::Data::Rgba { - width, - height, - pixels, - } => { - if let Some(image) = image_rs::ImageBuffer::from_vec( - *width, - *height, - pixels.to_vec(), - ) { - Memory::Host(image) - } else { - Memory::Invalid - } - } - }; - - self.insert(handle, memory); - self.get(handle).unwrap() - } - - /// Load image and upload raster data - pub fn upload( - &mut self, - handle: &image::Handle, - state: &mut T::State<'_>, - storage: &mut T, - ) -> Option<&T::Entry> { - let memory = self.load(handle); - - if let Memory::Host(image) = memory { - let (width, height) = image.dimensions(); - - let entry = storage.upload(width, height, image, state)?; - - *memory = Memory::Device(entry); - } - - if let Memory::Device(allocation) = memory { - Some(allocation) - } else { - None - } - } - - /// Trim cache misses from cache - pub fn trim(&mut self, storage: &mut T, state: &mut T::State<'_>) { - let hits = &self.hits; - - self.map.retain(|k, memory| { - let retain = hits.contains(k); - - if !retain { - if let Memory::Device(entry) = memory { - storage.remove(entry, state); - } - } - - retain - }); - - self.hits.clear(); - } - - fn get(&mut self, handle: &image::Handle) -> Option<&mut Memory<T>> { - let _ = self.hits.insert(handle.id()); - - self.map.get_mut(&handle.id()) - } - - fn insert(&mut self, handle: &image::Handle, memory: Memory<T>) { - let _ = self.map.insert(handle.id(), memory); - } - - fn contains(&self, handle: &image::Handle) -> bool { - self.map.contains_key(&handle.id()) - } -} - -impl<T: Storage> Default for Cache<T> { - fn default() -> Self { - Self { - map: HashMap::new(), - hits: HashSet::new(), - } - } -} - -bitflags! { - struct Operation: u8 { - const FLIP_HORIZONTALLY = 0b001; - const ROTATE_180 = 0b010; - const FLIP_DIAGONALLY = 0b100; - } -} - -impl Operation { - // Meaning of the returned value is described e.g. at: - // https://magnushoff.com/articles/jpeg-orientation/ - fn from_exif<R>(reader: &mut R) -> Result<Self, exif::Error> - where - R: std::io::BufRead + std::io::Seek, - { - let exif = exif::Reader::new().read_from_container(reader)?; - - Ok(exif - .get_field(exif::Tag::Orientation, exif::In::PRIMARY) - .and_then(|field| field.value.get_uint(0)) - .and_then(|value| u8::try_from(value).ok()) - .and_then(|value| Self::from_bits(value.saturating_sub(1))) - .unwrap_or_else(Self::empty)) - } - - fn perform<P>( - self, - image: image_rs::ImageBuffer<P, Vec<P::Subpixel>>, - ) -> image_rs::ImageBuffer<P, Vec<P::Subpixel>> - where - P: image_rs::Pixel + 'static, - { - use image_rs::imageops; - - let mut image = if self.contains(Self::FLIP_DIAGONALLY) { - flip_diagonally(image) - } else { - image - }; - - if self.contains(Self::ROTATE_180) { - imageops::rotate180_in_place(&mut image); - } - - if self.contains(Self::FLIP_HORIZONTALLY) { - imageops::flip_horizontal_in_place(&mut image); - } - - image - } -} - -fn flip_diagonally<I>( - image: I, -) -> image_rs::ImageBuffer<I::Pixel, Vec<<I::Pixel as image_rs::Pixel>::Subpixel>> -where - I: image_rs::GenericImage, - I::Pixel: 'static, -{ - let (width, height) = image.dimensions(); - let mut out = image_rs::ImageBuffer::new(height, width); - - for x in 0..width { - for y in 0..height { - let p = image.get_pixel(x, y); - - out.put_pixel(y, x, p); - } - } - - out -} diff --git a/graphics/src/image/storage.rs b/graphics/src/image/storage.rs index 1b5b5c35..4caa6141 100644 --- a/graphics/src/image/storage.rs +++ b/graphics/src/image/storage.rs @@ -1,5 +1,5 @@ //! Store images. -use crate::Size; +use iced_core::Size; use std::fmt::Debug; diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs index d39dd90c..91f50282 100644 --- a/graphics/src/lib.rs +++ b/graphics/src/lib.rs @@ -23,33 +23,31 @@ #![cfg_attr(docsrs, feature(doc_cfg))] mod antialiasing; mod error; -mod primitive; mod transformation; mod viewport; pub mod backend; -pub mod font; -pub mod gradient; -pub mod image; -pub mod layer; -pub mod overlay; +pub mod compositor; +pub mod damage; +pub mod primitive; pub mod renderer; -pub mod triangle; -pub mod widget; -pub mod window; + +#[cfg(feature = "geometry")] +pub mod geometry; + +#[cfg(feature = "image")] +pub mod image; pub use antialiasing::Antialiasing; pub use backend::Backend; +pub use compositor::Compositor; pub use error::Error; -pub use gradient::Gradient; -pub use layer::Layer; pub use primitive::Primitive; pub use renderer::Renderer; pub use transformation::Transformation; pub use viewport::Viewport; -pub use window::compositor; -pub use iced_native::alignment; -pub use iced_native::{ - Alignment, Background, Color, Font, Point, Rectangle, Size, Vector, -}; +#[cfg(feature = "geometry")] +pub use geometry::Geometry; + +pub use iced_core as core; diff --git a/graphics/src/overlay/menu.rs b/graphics/src/overlay/menu.rs deleted file mode 100644 index 8b489e5e..00000000 --- a/graphics/src/overlay/menu.rs +++ /dev/null @@ -1,3 +0,0 @@ -//! Build and show dropdown menus. - -pub use iced_style::menu::{Appearance, StyleSheet}; diff --git a/graphics/src/primitive.rs b/graphics/src/primitive.rs index cef422a2..d4446c87 100644 --- a/graphics/src/primitive.rs +++ b/graphics/src/primitive.rs @@ -1,24 +1,17 @@ -use iced_native::image; -use iced_native::svg; -use iced_native::{Background, Color, Font, Rectangle, Size, Vector}; - -use crate::alignment; -use crate::gradient::Gradient; -use crate::triangle; +//! Draw using different graphical primitives. +use crate::core::alignment; +use crate::core::image; +use crate::core::svg; +use crate::core::text; +use crate::core::{Background, Color, Font, Gradient, Rectangle, Size, Vector}; +use bytemuck::{Pod, Zeroable}; use std::sync::Arc; /// A rendering primitive. -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, PartialEq)] +#[non_exhaustive] pub enum Primitive { - /// An empty primitive - #[default] - None, - /// A group of primitives - Group { - /// The primitives of the group - primitives: Vec<Primitive>, - }, /// A text primitive Text { /// The contents of the text @@ -27,14 +20,18 @@ pub enum Primitive { bounds: Rectangle, /// The color of the text color: Color, - /// The size of the text + /// The size of the text in logical pixels size: f32, + /// The line height of the text + line_height: text::LineHeight, /// The font of the text font: Font, /// The horizontal alignment of the text horizontal_alignment: alignment::Horizontal, /// The vertical alignment of the text vertical_alignment: alignment::Vertical, + /// The shaping strategy of the text. + shaping: text::Shaping, }, /// A quad primitive Quad { @@ -67,27 +64,12 @@ pub enum Primitive { /// The bounds of the viewport bounds: Rectangle, }, - /// A clip primitive - Clip { - /// The bounds of the clip - bounds: Rectangle, - /// The content of the clip - content: Box<Primitive>, - }, - /// A primitive that applies a translation - Translate { - /// The translation vector - translation: Vector, - - /// The primitive to translate - content: Box<Primitive>, - }, /// A low-level primitive to render a mesh of triangles with a solid color. /// /// It can be used to render many kinds of geometry freely. SolidMesh { /// The vertices and indices of the mesh. - buffers: triangle::Mesh2D<triangle::ColoredVertex2D>, + buffers: Mesh2D<ColoredVertex2D>, /// The size of the drawable region of the mesh. /// @@ -99,7 +81,7 @@ pub enum Primitive { /// It can be used to render many kinds of geometry freely. GradientMesh { /// The vertices and indices of the mesh. - buffers: triangle::Mesh2D<triangle::Vertex2D>, + buffers: Mesh2D<Vertex2D>, /// The size of the drawable region of the mesh. /// @@ -109,12 +91,178 @@ pub enum Primitive { /// The [`Gradient`] to apply to the mesh. gradient: Gradient, }, + /// A [`tiny_skia`] path filled with some paint. + #[cfg(feature = "tiny-skia")] + Fill { + /// The path to fill. + path: tiny_skia::Path, + /// The paint to use. + paint: tiny_skia::Paint<'static>, + /// The fill rule to follow. + rule: tiny_skia::FillRule, + /// The transform to apply to the path. + transform: tiny_skia::Transform, + }, + /// A [`tiny_skia`] path stroked with some paint. + #[cfg(feature = "tiny-skia")] + Stroke { + /// The path to stroke. + path: tiny_skia::Path, + /// The paint to use. + paint: tiny_skia::Paint<'static>, + /// The stroke settings. + stroke: tiny_skia::Stroke, + /// The transform to apply to the path. + transform: tiny_skia::Transform, + }, + /// A group of primitives + Group { + /// The primitives of the group + primitives: Vec<Primitive>, + }, + /// A clip primitive + Clip { + /// The bounds of the clip + bounds: Rectangle, + /// The content of the clip + content: Box<Primitive>, + }, + /// A primitive that applies a translation + Translate { + /// The translation vector + translation: Vector, + + /// The primitive to translate + content: Box<Primitive>, + }, /// A cached primitive. /// /// This can be useful if you are implementing a widget where primitive /// generation is expensive. - Cached { + Cache { /// The cached primitive - cache: Arc<Primitive>, + content: Arc<Primitive>, }, } + +impl Primitive { + /// Creates a [`Primitive::Group`]. + pub fn group(primitives: Vec<Self>) -> Self { + Self::Group { primitives } + } + + /// Creates a [`Primitive::Clip`]. + pub fn clip(self, bounds: Rectangle) -> Self { + Self::Clip { + bounds, + content: Box::new(self), + } + } + + /// Creates a [`Primitive::Translate`]. + pub fn translate(self, translation: Vector) -> Self { + Self::Translate { + translation, + content: Box::new(self), + } + } + + /// Returns the bounds of the [`Primitive`]. + pub fn bounds(&self) -> Rectangle { + match self { + Self::Text { + bounds, + horizontal_alignment, + vertical_alignment, + .. + } => { + let mut bounds = *bounds; + + bounds.x = match horizontal_alignment { + alignment::Horizontal::Left => bounds.x, + alignment::Horizontal::Center => { + bounds.x - bounds.width / 2.0 + } + alignment::Horizontal::Right => bounds.x - bounds.width, + }; + + bounds.y = match vertical_alignment { + alignment::Vertical::Top => bounds.y, + alignment::Vertical::Center => { + bounds.y - bounds.height / 2.0 + } + alignment::Vertical::Bottom => bounds.y - bounds.height, + }; + + bounds.expand(1.5) + } + Self::Quad { bounds, .. } + | Self::Image { bounds, .. } + | Self::Svg { bounds, .. } => bounds.expand(1.0), + Self::Clip { bounds, .. } => bounds.expand(1.0), + Self::SolidMesh { size, .. } | Self::GradientMesh { size, .. } => { + Rectangle::with_size(*size) + } + #[cfg(feature = "tiny-skia")] + Self::Fill { path, .. } | Self::Stroke { path, .. } => { + let bounds = path.bounds(); + + Rectangle { + x: bounds.x(), + y: bounds.y(), + width: bounds.width(), + height: bounds.height(), + } + .expand(1.0) + } + Self::Group { primitives } => primitives + .iter() + .map(Self::bounds) + .fold(Rectangle::with_size(Size::ZERO), |a, b| { + Rectangle::union(&a, &b) + }), + Self::Translate { + translation, + content, + } => content.bounds() + *translation, + Self::Cache { content } => content.bounds(), + } + } +} + +/// A set of [`Vertex2D`] and indices representing a list of triangles. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Mesh2D<T> { + /// The vertices of the mesh + pub vertices: Vec<T>, + + /// The list of vertex indices that defines the triangles of the mesh. + /// + /// Therefore, this list should always have a length that is a multiple of 3. + pub indices: Vec<u32>, +} + +/// A two-dimensional vertex. +#[derive(Copy, Clone, Debug, PartialEq, Zeroable, Pod)] +#[repr(C)] +pub struct Vertex2D { + /// The vertex position in 2D space. + pub position: [f32; 2], +} + +/// A two-dimensional vertex with a color. +#[derive(Copy, Clone, Debug, PartialEq, Zeroable, Pod)] +#[repr(C)] +pub struct ColoredVertex2D { + /// The vertex position in 2D space. + pub position: [f32; 2], + + /// The color of the vertex in __linear__ RGBA. + pub color: [f32; 4], +} + +impl From<()> for Primitive { + fn from(_: ()) -> Self { + Self::Group { primitives: vec![] } + } +} diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs index 34b6eb1d..de905503 100644 --- a/graphics/src/renderer.rs +++ b/graphics/src/renderer.rs @@ -1,15 +1,17 @@ //! Create a renderer from a [`Backend`]. use crate::backend::{self, Backend}; -use crate::{Primitive, Vector}; -use iced_native::image; -use iced_native::layout; -use iced_native::renderer; -use iced_native::svg; -use iced_native::text::{self, Text}; -use iced_native::{Background, Color, Element, Font, Point, Rectangle, Size}; - -pub use iced_native::renderer::Style; - +use crate::Primitive; + +use iced_core::image; +use iced_core::layout; +use iced_core::renderer; +use iced_core::svg; +use iced_core::text::{self, Text}; +use iced_core::{ + Background, Color, Element, Font, Point, Rectangle, Size, Vector, +}; + +use std::borrow::Cow; use std::marker::PhantomData; /// A backend-agnostic renderer that supports all the built-in widgets. @@ -30,7 +32,7 @@ impl<B: Backend, T> Renderer<B, T> { } } - /// Returns the [`Backend`] of the [`Renderer`]. + /// Returns a reference to the [`Backend`] of the [`Renderer`]. pub fn backend(&self) -> &B { &self.backend } @@ -42,12 +44,15 @@ impl<B: Backend, T> Renderer<B, T> { /// Runs the given closure with the [`Backend`] and the recorded primitives /// of the [`Renderer`]. - pub fn with_primitives(&mut self, f: impl FnOnce(&mut B, &[Primitive])) { - f(&mut self.backend, &self.primitives); + pub fn with_primitives<O>( + &mut self, + f: impl FnOnce(&mut B, &[Primitive]) -> O, + ) -> O { + f(&mut self.backend, &self.primitives) } } -impl<B, T> iced_native::Renderer for Renderer<B, T> +impl<B, T> iced_core::Renderer for Renderer<B, T> where B: Backend, { @@ -66,19 +71,13 @@ where } fn with_layer(&mut self, bounds: Rectangle, f: impl FnOnce(&mut Self)) { - let current_primitives = std::mem::take(&mut self.primitives); + let current = std::mem::take(&mut self.primitives); f(self); - let layer_primitives = - std::mem::replace(&mut self.primitives, current_primitives); + let layer = std::mem::replace(&mut self.primitives, current); - self.primitives.push(Primitive::Clip { - bounds, - content: Box::new(Primitive::Group { - primitives: layer_primitives, - }), - }); + self.primitives.push(Primitive::group(layer).clip(bounds)); } fn with_translation( @@ -86,19 +85,14 @@ where translation: Vector, f: impl FnOnce(&mut Self), ) { - let current_primitives = std::mem::take(&mut self.primitives); + let current = std::mem::take(&mut self.primitives); f(self); - let layer_primitives = - std::mem::replace(&mut self.primitives, current_primitives); + let layer = std::mem::replace(&mut self.primitives, current); - self.primitives.push(Primitive::Translate { - translation, - content: Box::new(Primitive::Group { - primitives: layer_primitives, - }), - }); + self.primitives + .push(Primitive::group(layer).translate(translation)); } fn fill_quad( @@ -130,6 +124,10 @@ where const CHECKMARK_ICON: char = B::CHECKMARK_ICON; const ARROW_DOWN_ICON: char = B::ARROW_DOWN_ICON; + fn default_font(&self) -> Self::Font { + self.backend().default_font() + } + fn default_size(&self) -> f32 { self.backend().default_size() } @@ -138,40 +136,59 @@ where &self, content: &str, size: f32, + line_height: text::LineHeight, font: Font, bounds: Size, + shaping: text::Shaping, ) -> (f32, f32) { - self.backend().measure(content, size, font, bounds) + self.backend().measure( + content, + size, + line_height, + font, + bounds, + shaping, + ) } fn hit_test( &self, content: &str, size: f32, + line_height: text::LineHeight, font: Font, bounds: Size, + shaping: text::Shaping, point: Point, nearest_only: bool, ) -> Option<text::Hit> { self.backend().hit_test( content, size, + line_height, font, bounds, + shaping, point, nearest_only, ) } + fn load_font(&mut self, bytes: Cow<'static, [u8]>) { + self.backend.load_font(bytes); + } + fn fill_text(&mut self, text: Text<'_, Self::Font>) { self.primitives.push(Primitive::Text { content: text.content.to_string(), bounds: text.bounds, size: text.size, + line_height: text.line_height, color: text.color, font: text.font, horizontal_alignment: text.horizontal_alignment, vertical_alignment: text.vertical_alignment, + shaping: text.shaping, }); } } @@ -187,7 +204,7 @@ where } fn draw(&mut self, handle: image::Handle, bounds: Rectangle) { - self.draw_primitive(Primitive::Image { handle, bounds }) + self.primitives.push(Primitive::Image { handle, bounds }) } } @@ -205,10 +222,21 @@ where color: Option<Color>, bounds: Rectangle, ) { - self.draw_primitive(Primitive::Svg { + self.primitives.push(Primitive::Svg { handle, color, bounds, }) } } + +#[cfg(feature = "geometry")] +impl<B, T> crate::geometry::Renderer for Renderer<B, T> +where + B: Backend, +{ + fn draw(&mut self, layers: Vec<crate::Geometry>) { + self.primitives + .extend(layers.into_iter().map(crate::Geometry::into)); + } +} diff --git a/graphics/src/triangle.rs b/graphics/src/triangle.rs index f52b2339..09b61767 100644 --- a/graphics/src/triangle.rs +++ b/graphics/src/triangle.rs @@ -1,33 +1 @@ //! Draw geometry using meshes of triangles. -use bytemuck::{Pod, Zeroable}; - -/// A set of [`Vertex2D`] and indices representing a list of triangles. -#[derive(Clone, Debug)] -pub struct Mesh2D<T> { - /// The vertices of the mesh - pub vertices: Vec<T>, - - /// The list of vertex indices that defines the triangles of the mesh. - /// - /// Therefore, this list should always have a length that is a multiple of 3. - pub indices: Vec<u32>, -} - -/// A two-dimensional vertex. -#[derive(Copy, Clone, Debug, Zeroable, Pod)] -#[repr(C)] -pub struct Vertex2D { - /// The vertex position in 2D space. - pub position: [f32; 2], -} - -/// A two-dimensional vertex with a color. -#[derive(Copy, Clone, Debug, Zeroable, Pod)] -#[repr(C)] -pub struct ColoredVertex2D { - /// The vertex position in 2D space. - pub position: [f32; 2], - - /// The color of the vertex in __linear__ RGBA. - pub color: [f32; 4], -} diff --git a/graphics/src/viewport.rs b/graphics/src/viewport.rs index 2c0b541a..5792555d 100644 --- a/graphics/src/viewport.rs +++ b/graphics/src/viewport.rs @@ -1,4 +1,6 @@ -use crate::{Size, Transformation}; +use crate::Transformation; + +use iced_core::Size; /// A viewing region for displaying computer graphics. #[derive(Debug, Clone)] diff --git a/graphics/src/widget/canvas/geometry.rs b/graphics/src/widget/canvas/geometry.rs deleted file mode 100644 index e8ac621d..00000000 --- a/graphics/src/widget/canvas/geometry.rs +++ /dev/null @@ -1,24 +0,0 @@ -use crate::Primitive; - -/// A bunch of shapes that can be drawn. -/// -/// [`Geometry`] can be easily generated with a [`Frame`] or stored in a -/// [`Cache`]. -/// -/// [`Frame`]: crate::widget::canvas::Frame -/// [`Cache`]: crate::widget::canvas::Cache -#[derive(Debug, Clone)] -pub struct Geometry(Primitive); - -impl Geometry { - pub(crate) fn from_primitive(primitive: Primitive) -> Self { - Self(primitive) - } - - /// Turns the [`Geometry`] into a [`Primitive`]. - /// - /// This can be useful if you are building a custom widget. - pub fn into_primitive(self) -> Primitive { - self.0 - } -} diff --git a/graphics/src/window.rs b/graphics/src/window.rs deleted file mode 100644 index a38b81f3..00000000 --- a/graphics/src/window.rs +++ /dev/null @@ -1,10 +0,0 @@ -//! Draw graphics to window surfaces. -pub mod compositor; - -#[cfg(feature = "opengl")] -pub mod gl_compositor; - -pub use compositor::Compositor; - -#[cfg(feature = "opengl")] -pub use gl_compositor::GLCompositor; diff --git a/graphics/src/window/gl_compositor.rs b/graphics/src/window/gl_compositor.rs deleted file mode 100644 index a45a7ca1..00000000 --- a/graphics/src/window/gl_compositor.rs +++ /dev/null @@ -1,71 +0,0 @@ -//! A compositor is responsible for initializing a renderer and managing window -//! surfaces. -use crate::compositor::Information; -use crate::{Color, Error, Size, Viewport}; - -use core::ffi::c_void; - -/// A basic OpenGL compositor. -/// -/// A compositor is responsible for initializing a renderer and managing window -/// surfaces. -/// -/// For now, this compositor only deals with a single global surface -/// for drawing. However, the trait will most likely change in the near future -/// to handle multiple surfaces at once. -/// -/// If you implement an OpenGL renderer, you can implement this trait to ease -/// integration with existing windowing shells, like `iced_glutin`. -pub trait GLCompositor: Sized { - /// The renderer of the [`GLCompositor`]. - /// - /// This should point to your renderer type, which could be a type alias - /// of the [`Renderer`] provided in this crate with with a specific - /// [`Backend`]. - /// - /// [`Renderer`]: crate::Renderer - /// [`Backend`]: crate::Backend - type Renderer: iced_native::Renderer; - - /// The settings of the [`GLCompositor`]. - /// - /// It's up to you to decide the configuration supported by your renderer! - type Settings: Default; - - /// Creates a new [`GLCompositor`] and [`Renderer`] with the given - /// [`Settings`] and an OpenGL address loader function. - /// - /// # Safety - /// The `loader_function` should resolve to valid OpenGL bindings. - /// - /// [`Renderer`]: crate::Renderer - /// [`Backend`]: crate::Backend - /// [`Settings`]: Self::Settings - #[allow(unsafe_code)] - unsafe fn new( - settings: Self::Settings, - loader_function: impl FnMut(&str) -> *const c_void, - ) -> Result<(Self, Self::Renderer), Error>; - - /// Returns the amount of samples that should be used when configuring - /// an OpenGL context for this [`GLCompositor`]. - fn sample_count(settings: &Self::Settings) -> u32; - - /// Resizes the viewport of the [`GLCompositor`]. - fn resize_viewport(&mut self, physical_size: Size<u32>); - - /// Returns [`Information`] used by this [`GLCompositor`]. - fn fetch_information(&self) -> Information; - - /// Presents the primitives of the [`Renderer`] to the next frame of the - /// [`GLCompositor`]. - /// - /// [`Renderer`]: crate::Renderer - fn present<T: AsRef<str>>( - &mut self, - renderer: &mut Self::Renderer, - viewport: &Viewport, - background_color: Color, - overlay: &[T], - ); -} diff --git a/lazy/Cargo.toml b/lazy/Cargo.toml deleted file mode 100644 index 36cf3f12..00000000 --- a/lazy/Cargo.toml +++ /dev/null @@ -1,18 +0,0 @@ -[package] -name = "iced_lazy" -version = "0.6.1" -authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] -edition = "2021" -description = "Lazy widgets for Iced" -license = "MIT" -repository = "https://github.com/iced-rs/iced" -documentation = "https://docs.rs/iced_lazy" -keywords = ["gui", "ui", "graphics", "interface", "widgets"] -categories = ["gui"] - -[dependencies] -ouroboros = "0.13" - -[dependencies.iced_native] -version = "0.10" -path = "../native" diff --git a/native/README.md b/native/README.md deleted file mode 100644 index cf36b224..00000000 --- a/native/README.md +++ /dev/null @@ -1,37 +0,0 @@ -# `iced_native` -[][documentation] -[](https://crates.io/crates/iced_native) -[](https://github.com/iced-rs/iced/blob/master/LICENSE) -[](https://discord.gg/3xZJ65GAhd) - -`iced_native` takes [`iced_core`] and builds a native runtime on top of it, featuring: -- A custom layout engine, greatly inspired by [`druid`] -- Event handling for all the built-in widgets -- A renderer-agnostic API - -To achieve this, it introduces a bunch of reusable interfaces: -- A `Widget` trait, which is used to implement new widgets: from layout requirements to event and drawing logic. -- A bunch of `Renderer` traits, meant to keep the crate renderer-agnostic. -- A `Windowed` trait, leveraging [`raw-window-handle`], which can be implemented by graphical renderers that target _windows_. Window-based shells (like [`iced_winit`]) can use this trait to stay renderer-agnostic. - -<p align="center"> - <img alt="The native target" src="../docs/graphs/native.png" width="80%"> -</p> - -[documentation]: https://docs.rs/iced_native -[`iced_core`]: ../core -[`iced_winit`]: ../winit -[`druid`]: https://github.com/xi-editor/druid -[`raw-window-handle`]: https://github.com/rust-windowing/raw-window-handle - -## Installation -Add `iced_native` as a dependency in your `Cargo.toml`: - -```toml -iced_native = "0.10" -``` - -__Iced moves fast and the `master` branch can contain breaking changes!__ If -you want to learn about a specific release, check out [the release list]. - -[the release list]: https://github.com/iced-rs/iced/releases diff --git a/native/src/mouse.rs b/native/src/mouse.rs deleted file mode 100644 index 9ee406cf..00000000 --- a/native/src/mouse.rs +++ /dev/null @@ -1,6 +0,0 @@ -//! Track mouse events. - -pub mod click; - -pub use click::Click; -pub use iced_core::mouse::*; diff --git a/native/src/runtime.rs b/native/src/runtime.rs deleted file mode 100644 index 5b0a6925..00000000 --- a/native/src/runtime.rs +++ /dev/null @@ -1,18 +0,0 @@ -//! Run commands and subscriptions. -use crate::event::{self, Event}; -use crate::Hasher; - -/// A native runtime with a generic executor and receiver of results. -/// -/// It can be used by shells to easily spawn a [`Command`] or track a -/// [`Subscription`]. -/// -/// [`Command`]: crate::Command -/// [`Subscription`]: crate::Subscription -pub type Runtime<Executor, Receiver, Message> = iced_futures::Runtime< - Hasher, - (Event, event::Status), - Executor, - Receiver, - Message, ->; diff --git a/native/src/subscription.rs b/native/src/subscription.rs deleted file mode 100644 index 115ffc42..00000000 --- a/native/src/subscription.rs +++ /dev/null @@ -1,283 +0,0 @@ -//! Listen to external events in your application. -use crate::event::{self, Event}; -use crate::window; -use crate::Hasher; - -use iced_futures::futures::channel::mpsc; -use iced_futures::futures::never::Never; -use iced_futures::futures::{self, Future, Stream}; -use iced_futures::{BoxStream, MaybeSend}; - -use std::hash::Hash; - -/// A request to listen to external events. -/// -/// Besides performing async actions on demand with [`Command`], most -/// applications also need to listen to external events passively. -/// -/// A [`Subscription`] is normally provided to some runtime, like a [`Command`], -/// and it will generate events as long as the user keeps requesting it. -/// -/// For instance, you can use a [`Subscription`] to listen to a WebSocket -/// connection, keyboard presses, mouse events, time ticks, etc. -/// -/// [`Command`]: crate::Command -pub type Subscription<T> = - iced_futures::Subscription<Hasher, (Event, event::Status), T>; - -/// A stream of runtime events. -/// -/// It is the input of a [`Subscription`] in the native runtime. -pub type EventStream = BoxStream<(Event, event::Status)>; - -/// A native [`Subscription`] tracker. -pub type Tracker = - iced_futures::subscription::Tracker<Hasher, (Event, event::Status)>; - -pub use iced_futures::subscription::Recipe; - -/// Returns a [`Subscription`] to all the ignored runtime events. -/// -/// This subscription will notify your application of any [`Event`] that was -/// not captured by any widget. -pub fn events() -> Subscription<Event> { - events_with(|event, status| match status { - event::Status::Ignored => Some(event), - event::Status::Captured => None, - }) -} - -/// Returns a [`Subscription`] that filters all the runtime events with the -/// provided function, producing messages accordingly. -/// -/// This subscription will call the provided function for every [`Event`] -/// handled by the runtime. If the function: -/// -/// - Returns `None`, the [`Event`] will be discarded. -/// - Returns `Some` message, the `Message` will be produced. -pub fn events_with<Message>( - f: fn(Event, event::Status) -> Option<Message>, -) -> Subscription<Message> -where - Message: 'static + MaybeSend, -{ - #[derive(Hash)] - struct EventsWith; - - Subscription::from_recipe(Runner { - id: (EventsWith, f), - spawn: move |events| { - use futures::future; - use futures::stream::StreamExt; - - events.filter_map(move |(event, status)| { - future::ready(match event { - Event::Window(window::Event::RedrawRequested(_)) => None, - _ => f(event, status), - }) - }) - }, - }) -} - -pub(crate) fn raw_events<Message>( - f: fn(Event, event::Status) -> Option<Message>, -) -> Subscription<Message> -where - Message: 'static + MaybeSend, -{ - #[derive(Hash)] - struct RawEvents; - - Subscription::from_recipe(Runner { - id: (RawEvents, f), - spawn: move |events| { - use futures::future; - use futures::stream::StreamExt; - - events.filter_map(move |(event, status)| { - future::ready(f(event, status)) - }) - }, - }) -} - -/// Returns a [`Subscription`] that will call the given function to create and -/// asynchronously run the given [`Stream`]. -pub fn run<S, Message>(builder: fn() -> S) -> Subscription<Message> -where - S: Stream<Item = Message> + MaybeSend + 'static, - Message: 'static, -{ - Subscription::from_recipe(Runner { - id: builder, - spawn: move |_| builder(), - }) -} - -/// Returns a [`Subscription`] that will create and asynchronously run the -/// given [`Stream`]. -/// -/// The `id` will be used to uniquely identify the [`Subscription`]. -pub fn run_with_id<I, S, Message>(id: I, stream: S) -> Subscription<Message> -where - I: Hash + 'static, - S: Stream<Item = Message> + MaybeSend + 'static, - Message: 'static, -{ - Subscription::from_recipe(Runner { - id, - spawn: move |_| stream, - }) -} - -/// Returns a [`Subscription`] that will create and asynchronously run a -/// [`Stream`] that will call the provided closure to produce every `Message`. -/// -/// The `id` will be used to uniquely identify the [`Subscription`]. -pub fn unfold<I, T, Fut, Message>( - id: I, - initial: T, - mut f: impl FnMut(T) -> Fut + MaybeSend + Sync + 'static, -) -> Subscription<Message> -where - I: Hash + 'static, - T: MaybeSend + 'static, - Fut: Future<Output = (Message, T)> + MaybeSend + 'static, - Message: 'static + MaybeSend, -{ - use futures::future::FutureExt; - - run_with_id( - id, - futures::stream::unfold(initial, move |state| f(state).map(Some)), - ) -} - -/// Creates a [`Subscription`] that publishes the events sent from a [`Future`] -/// to an [`mpsc::Sender`] with the given bounds. -/// -/// # Creating an asynchronous worker with bidirectional communication -/// You can leverage this helper to create a [`Subscription`] that spawns -/// an asynchronous worker in the background and establish a channel of -/// communication with an `iced` application. -/// -/// You can achieve this by creating an `mpsc` channel inside the closure -/// and returning the `Sender` as a `Message` for the `Application`: -/// -/// ``` -/// use iced_native::subscription::{self, Subscription}; -/// use iced_native::futures::channel::mpsc; -/// use iced_native::futures::sink::SinkExt; -/// -/// pub enum Event { -/// Ready(mpsc::Sender<Input>), -/// WorkFinished, -/// // ... -/// } -/// -/// enum Input { -/// DoSomeWork, -/// // ... -/// } -/// -/// enum State { -/// Starting, -/// Ready(mpsc::Receiver<Input>), -/// } -/// -/// fn some_worker() -> Subscription<Event> { -/// struct SomeWorker; -/// -/// subscription::channel(std::any::TypeId::of::<SomeWorker>(), 100, |mut output| async move { -/// let mut state = State::Starting; -/// -/// loop { -/// match &mut state { -/// State::Starting => { -/// // Create channel -/// let (sender, receiver) = mpsc::channel(100); -/// -/// // Send the sender back to the application -/// output.send(Event::Ready(sender)).await; -/// -/// // We are ready to receive messages -/// state = State::Ready(receiver); -/// } -/// State::Ready(receiver) => { -/// use iced_native::futures::StreamExt; -/// -/// // Read next input sent from `Application` -/// let input = receiver.select_next_some().await; -/// -/// match input { -/// Input::DoSomeWork => { -/// // Do some async work... -/// -/// // Finally, we can optionally produce a message to tell the -/// // `Application` the work is done -/// output.send(Event::WorkFinished).await; -/// } -/// } -/// } -/// } -/// } -/// }) -/// } -/// ``` -/// -/// Check out the [`websocket`] example, which showcases this pattern to maintain a WebSocket -/// connection open. -/// -/// [`websocket`]: https://github.com/iced-rs/iced/tree/0.9/examples/websocket -pub fn channel<I, Fut, Message>( - id: I, - size: usize, - f: impl Fn(mpsc::Sender<Message>) -> Fut + MaybeSend + Sync + 'static, -) -> Subscription<Message> -where - I: Hash + 'static, - Fut: Future<Output = Never> + MaybeSend + 'static, - Message: 'static + MaybeSend, -{ - use futures::stream::{self, StreamExt}; - - Subscription::from_recipe(Runner { - id, - spawn: move |_| { - let (sender, receiver) = mpsc::channel(size); - - let runner = stream::once(f(sender)).map(|_| unreachable!()); - - stream::select(receiver, runner) - }, - }) -} - -struct Runner<I, F, S, Message> -where - F: FnOnce(EventStream) -> S, - S: Stream<Item = Message>, -{ - id: I, - spawn: F, -} - -impl<I, S, F, Message> Recipe<Hasher, (Event, event::Status)> - for Runner<I, F, S, Message> -where - I: Hash + 'static, - F: FnOnce(EventStream) -> S, - S: Stream<Item = Message> + MaybeSend + 'static, -{ - type Output = Message; - - fn hash(&self, state: &mut Hasher) { - std::any::TypeId::of::<I>().hash(state); - self.id.hash(state); - } - - fn stream(self: Box<Self>, input: EventStream) -> BoxStream<Self::Output> { - iced_futures::boxed_stream((self.spawn)(input)) - } -} diff --git a/native/src/text.rs b/native/src/text.rs deleted file mode 100644 index 55c3cfd3..00000000 --- a/native/src/text.rs +++ /dev/null @@ -1,114 +0,0 @@ -//! Draw and interact with text. -use crate::alignment; -use crate::{Color, Point, Rectangle, Size, Vector}; - -/// A paragraph. -#[derive(Debug, Clone, Copy)] -pub struct Text<'a, Font> { - /// The content of the paragraph. - pub content: &'a str, - - /// The bounds of the paragraph. - pub bounds: Rectangle, - - /// The size of the [`Text`]. - pub size: f32, - - /// The color of the [`Text`]. - pub color: Color, - - /// The font of the [`Text`]. - pub font: Font, - - /// The horizontal alignment of the [`Text`]. - pub horizontal_alignment: alignment::Horizontal, - - /// The vertical alignment of the [`Text`]. - pub vertical_alignment: alignment::Vertical, -} - -/// 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), - /// The provided point was not within the bounds of a glyph. The index - /// of the character with the closest centeroid position is returned, - /// as well as its delta. - NearestCharOffset(usize, Vector), -} - -impl Hit { - /// Computes the cursor position of the [`Hit`] . - pub fn cursor(self) -> usize { - match self { - Self::CharOffset(i) => i, - Self::NearestCharOffset(i, delta) => { - if delta.x > f32::EPSILON { - i + 1 - } else { - i - } - } - } - } -} - -/// A renderer capable of measuring and drawing [`Text`]. -pub trait Renderer: crate::Renderer { - /// The font type used. - type Font: Default + Clone; - - /// 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; - - /// Returns the default size of [`Text`]. - fn default_size(&self) -> f32; - - /// Measures the text in the given bounds and returns the minimum boundaries - /// that can fit the contents. - fn measure( - &self, - content: &str, - size: f32, - font: Self::Font, - bounds: Size, - ) -> (f32, f32); - - /// Measures the width of the text as if it were laid out in a single line. - fn measure_width(&self, content: &str, size: f32, font: Self::Font) -> f32 { - let (width, _) = self.measure(content, size, font, Size::INFINITY); - - width - } - - /// Tests whether the provided point is within the boundaries of text - /// laid out with the given parameters, returning information about - /// the nearest character. - /// - /// If `nearest_only` is true, the hit test does not consider whether the - /// the point is interior to any glyph bounds, returning only the character - /// with the nearest centeroid. - fn hit_test( - &self, - contents: &str, - size: f32, - font: Self::Font, - bounds: Size, - point: Point, - nearest_only: bool, - ) -> Option<Hit>; - - /// Draws the given [`Text`]. - fn fill_text(&mut self, text: Text<'_, Self::Font>); -} diff --git a/native/src/widget/action.rs b/native/src/widget/action.rs deleted file mode 100644 index 3f1b6b6c..00000000 --- a/native/src/widget/action.rs +++ /dev/null @@ -1,154 +0,0 @@ -use crate::widget::operation::{ - self, Focusable, Operation, Scrollable, TextInput, -}; -use crate::widget::Id; - -use iced_futures::MaybeSend; - -use std::any::Any; -use std::rc::Rc; - -/// An operation to be performed on the widget tree. -#[allow(missing_debug_implementations)] -pub struct Action<T>(Box<dyn Operation<T>>); - -impl<T> Action<T> { - /// Creates a new [`Action`] with the given [`Operation`]. - pub fn new(operation: impl Operation<T> + 'static) -> Self { - Self(Box::new(operation)) - } - - /// Maps the output of an [`Action`] using the given function. - pub fn map<A>( - self, - f: impl Fn(T) -> A + 'static + MaybeSend + Sync, - ) -> Action<A> - where - T: 'static, - A: 'static, - { - Action(Box::new(Map { - operation: self.0, - f: Rc::new(f), - })) - } - - /// Consumes the [`Action`] and returns the internal [`Operation`]. - pub fn into_operation(self) -> Box<dyn Operation<T>> { - self.0 - } -} - -#[allow(missing_debug_implementations)] -struct Map<A, B> { - operation: Box<dyn Operation<A>>, - f: Rc<dyn Fn(A) -> B>, -} - -impl<A, B> Operation<B> for Map<A, B> -where - A: 'static, - B: 'static, -{ - fn container( - &mut self, - id: Option<&Id>, - operate_on_children: &mut dyn FnMut(&mut dyn Operation<B>), - ) { - struct MapRef<'a, A> { - operation: &'a mut dyn Operation<A>, - } - - impl<'a, A, B> Operation<B> for MapRef<'a, A> { - fn container( - &mut self, - id: Option<&Id>, - operate_on_children: &mut dyn FnMut(&mut dyn Operation<B>), - ) { - let Self { operation, .. } = self; - - operation.container(id, &mut |operation| { - operate_on_children(&mut MapRef { operation }); - }); - } - - fn scrollable( - &mut self, - state: &mut dyn Scrollable, - id: Option<&Id>, - ) { - self.operation.scrollable(state, id); - } - - fn focusable( - &mut self, - state: &mut dyn Focusable, - id: Option<&Id>, - ) { - self.operation.focusable(state, id); - } - - fn text_input( - &mut self, - state: &mut dyn TextInput, - id: Option<&Id>, - ) { - self.operation.text_input(state, id); - } - - fn custom(&mut self, state: &mut dyn Any, id: Option<&Id>) { - self.operation.custom(state, id); - } - } - - let Self { operation, .. } = self; - - MapRef { - operation: operation.as_mut(), - } - .container(id, operate_on_children); - } - - fn focusable( - &mut self, - state: &mut dyn operation::Focusable, - id: Option<&Id>, - ) { - self.operation.focusable(state, id); - } - - fn scrollable( - &mut self, - state: &mut dyn operation::Scrollable, - id: Option<&Id>, - ) { - self.operation.scrollable(state, id); - } - - fn text_input( - &mut self, - state: &mut dyn operation::TextInput, - id: Option<&Id>, - ) { - self.operation.text_input(state, id); - } - - fn custom(&mut self, state: &mut dyn Any, id: Option<&Id>) { - self.operation.custom(state, id); - } - - fn finish(&self) -> operation::Outcome<B> { - match self.operation.finish() { - operation::Outcome::None => operation::Outcome::None, - operation::Outcome::Some(output) => { - operation::Outcome::Some((self.f)(output)) - } - operation::Outcome::Chain(next) => { - operation::Outcome::Chain(Box::new(Map { - operation: next, - f: self.f.clone(), - })) - } - } - } -} diff --git a/native/src/widget/helpers.rs b/native/src/widget/helpers.rs deleted file mode 100644 index 5f44e22c..00000000 --- a/native/src/widget/helpers.rs +++ /dev/null @@ -1,326 +0,0 @@ -//! Helper functions to create pure widgets. -use crate::overlay; -use crate::widget; -use crate::{Element, Length, Pixels}; - -use std::borrow::Cow; -use std::ops::RangeInclusive; - -/// Creates a [`Column`] with the given children. -/// -/// [`Column`]: widget::Column -#[macro_export] -macro_rules! column { - () => ( - $crate::widget::Column::new() - ); - ($($x:expr),+ $(,)?) => ( - $crate::widget::Column::with_children(vec![$($crate::Element::from($x)),+]) - ); -} - -/// Creates a [`Row`] with the given children. -/// -/// [`Row`]: widget::Row -#[macro_export] -macro_rules! row { - () => ( - $crate::widget::Row::new() - ); - ($($x:expr),+ $(,)?) => ( - $crate::widget::Row::with_children(vec![$($crate::Element::from($x)),+]) - ); -} - -/// Creates a new [`Container`] with the provided content. -/// -/// [`Container`]: widget::Container -pub fn container<'a, Message, Renderer>( - content: impl Into<Element<'a, Message, Renderer>>, -) -> widget::Container<'a, Message, Renderer> -where - Renderer: crate::Renderer, - Renderer::Theme: widget::container::StyleSheet, -{ - widget::Container::new(content) -} - -/// Creates a new [`Column`] with the given children. -/// -/// [`Column`]: widget::Column -pub fn column<Message, Renderer>( - children: Vec<Element<'_, Message, Renderer>>, -) -> widget::Column<'_, Message, Renderer> { - widget::Column::with_children(children) -} - -/// Creates a new [`Row`] with the given children. -/// -/// [`Row`]: widget::Row -pub fn row<Message, Renderer>( - children: Vec<Element<'_, Message, Renderer>>, -) -> widget::Row<'_, Message, Renderer> { - widget::Row::with_children(children) -} - -/// Creates a new [`Scrollable`] with the provided content. -/// -/// [`Scrollable`]: widget::Scrollable -pub fn scrollable<'a, Message, Renderer>( - content: impl Into<Element<'a, Message, Renderer>>, -) -> widget::Scrollable<'a, Message, Renderer> -where - Renderer: crate::Renderer, - Renderer::Theme: widget::scrollable::StyleSheet, -{ - widget::Scrollable::new(content) -} - -/// Creates a new [`Button`] with the provided content. -/// -/// [`Button`]: widget::Button -pub fn button<'a, Message, Renderer>( - content: impl Into<Element<'a, Message, Renderer>>, -) -> widget::Button<'a, Message, Renderer> -where - Renderer: crate::Renderer, - Renderer::Theme: widget::button::StyleSheet, - <Renderer::Theme as widget::button::StyleSheet>::Style: Default, -{ - widget::Button::new(content) -} - -/// Creates a new [`Tooltip`] with the provided content, tooltip text, and [`tooltip::Position`]. -/// -/// [`Tooltip`]: widget::Tooltip -/// [`tooltip::Position`]: widget::tooltip::Position -pub fn tooltip<'a, Message, Renderer>( - content: impl Into<Element<'a, Message, Renderer>>, - tooltip: impl ToString, - position: widget::tooltip::Position, -) -> widget::Tooltip<'a, Message, Renderer> -where - Renderer: crate::text::Renderer, - Renderer::Theme: widget::container::StyleSheet + widget::text::StyleSheet, -{ - widget::Tooltip::new(content, tooltip.to_string(), position) -} - -/// Creates a new [`Text`] widget with the provided content. -/// -/// [`Text`]: widget::Text -pub fn text<'a, Renderer>(text: impl ToString) -> widget::Text<'a, Renderer> -where - Renderer: crate::text::Renderer, - Renderer::Theme: widget::text::StyleSheet, -{ - widget::Text::new(text.to_string()) -} - -/// Creates a new [`Checkbox`]. -/// -/// [`Checkbox`]: widget::Checkbox -pub fn checkbox<'a, Message, Renderer>( - label: impl Into<String>, - is_checked: bool, - f: impl Fn(bool) -> Message + 'a, -) -> widget::Checkbox<'a, Message, Renderer> -where - Renderer: crate::text::Renderer, - Renderer::Theme: widget::checkbox::StyleSheet + widget::text::StyleSheet, -{ - widget::Checkbox::new(label, is_checked, f) -} - -/// Creates a new [`Radio`]. -/// -/// [`Radio`]: widget::Radio -pub fn radio<Message, Renderer, V>( - label: impl Into<String>, - value: V, - selected: Option<V>, - on_click: impl FnOnce(V) -> Message, -) -> widget::Radio<Message, Renderer> -where - Message: Clone, - Renderer: crate::text::Renderer, - Renderer::Theme: widget::radio::StyleSheet, - V: Copy + Eq, -{ - widget::Radio::new(label, value, selected, on_click) -} - -/// Creates a new [`Toggler`]. -/// -/// [`Toggler`]: widget::Toggler -pub fn toggler<'a, Message, Renderer>( - label: impl Into<Option<String>>, - is_checked: bool, - f: impl Fn(bool) -> Message + 'a, -) -> widget::Toggler<'a, Message, Renderer> -where - Renderer: crate::text::Renderer, - Renderer::Theme: widget::toggler::StyleSheet, -{ - widget::Toggler::new(label, is_checked, f) -} - -/// Creates a new [`TextInput`]. -/// -/// [`TextInput`]: widget::TextInput -pub fn text_input<'a, Message, Renderer>( - placeholder: &str, - value: &str, -) -> widget::TextInput<'a, Message, Renderer> -where - Message: Clone, - Renderer: crate::text::Renderer, - Renderer::Theme: widget::text_input::StyleSheet, -{ - widget::TextInput::new(placeholder, value) -} - -/// Creates a new [`Slider`]. -/// -/// [`Slider`]: widget::Slider -pub fn slider<'a, T, Message, Renderer>( - range: std::ops::RangeInclusive<T>, - value: T, - on_change: impl Fn(T) -> Message + 'a, -) -> widget::Slider<'a, T, Message, Renderer> -where - T: Copy + From<u8> + std::cmp::PartialOrd, - Message: Clone, - Renderer: crate::Renderer, - Renderer::Theme: widget::slider::StyleSheet, -{ - widget::Slider::new(range, value, on_change) -} - -/// Creates a new [`VerticalSlider`]. -/// -/// [`VerticalSlider`]: widget::VerticalSlider -pub fn vertical_slider<'a, T, Message, Renderer>( - range: std::ops::RangeInclusive<T>, - value: T, - on_change: impl Fn(T) -> Message + 'a, -) -> widget::VerticalSlider<'a, T, Message, Renderer> -where - T: Copy + From<u8> + std::cmp::PartialOrd, - Message: Clone, - Renderer: crate::Renderer, - Renderer::Theme: widget::slider::StyleSheet, -{ - widget::VerticalSlider::new(range, value, on_change) -} - -/// Creates a new [`PickList`]. -/// -/// [`PickList`]: widget::PickList -pub fn pick_list<'a, Message, Renderer, T>( - options: impl Into<Cow<'a, [T]>>, - selected: Option<T>, - on_selected: impl Fn(T) -> Message + 'a, -) -> widget::PickList<'a, T, Message, Renderer> -where - T: ToString + Eq + 'static, - [T]: ToOwned<Owned = Vec<T>>, - Renderer: crate::text::Renderer, - Renderer::Theme: widget::pick_list::StyleSheet - + widget::scrollable::StyleSheet - + overlay::menu::StyleSheet - + widget::container::StyleSheet, - <Renderer::Theme as overlay::menu::StyleSheet>::Style: - From<<Renderer::Theme as widget::pick_list::StyleSheet>::Style>, -{ - widget::PickList::new(options, selected, on_selected) -} - -/// Creates a new [`Image`]. -/// -/// [`Image`]: widget::Image -pub fn image<Handle>(handle: impl Into<Handle>) -> widget::Image<Handle> { - widget::Image::new(handle.into()) -} - -/// Creates a new horizontal [`Space`] with the given [`Length`]. -/// -/// [`Space`]: widget::Space -pub fn horizontal_space(width: impl Into<Length>) -> widget::Space { - widget::Space::with_width(width) -} - -/// Creates a new vertical [`Space`] with the given [`Length`]. -/// -/// [`Space`]: widget::Space -pub fn vertical_space(height: impl Into<Length>) -> widget::Space { - widget::Space::with_height(height) -} - -/// Creates a horizontal [`Rule`] with the given height. -/// -/// [`Rule`]: widget::Rule -pub fn horizontal_rule<Renderer>( - height: impl Into<Pixels>, -) -> widget::Rule<Renderer> -where - Renderer: crate::Renderer, - Renderer::Theme: widget::rule::StyleSheet, -{ - widget::Rule::horizontal(height) -} - -/// Creates a vertical [`Rule`] with the given width. -/// -/// [`Rule`]: widget::Rule -pub fn vertical_rule<Renderer>( - width: impl Into<Pixels>, -) -> widget::Rule<Renderer> -where - Renderer: crate::Renderer, - Renderer::Theme: widget::rule::StyleSheet, -{ - widget::Rule::vertical(width) -} - -/// Creates a new [`ProgressBar`]. -/// -/// It expects: -/// * an inclusive range of possible values, and -/// * the current value of the [`ProgressBar`]. -/// -/// [`ProgressBar`]: widget::ProgressBar -pub fn progress_bar<Renderer>( - range: RangeInclusive<f32>, - value: f32, -) -> widget::ProgressBar<Renderer> -where - Renderer: crate::Renderer, - Renderer::Theme: widget::progress_bar::StyleSheet, -{ - widget::ProgressBar::new(range, value) -} - -/// Creates a new [`Svg`] widget from the given [`Handle`]. -/// -/// [`Svg`]: widget::Svg -/// [`Handle`]: widget::svg::Handle -pub fn svg<Renderer>( - handle: impl Into<widget::svg::Handle>, -) -> widget::Svg<Renderer> -where - Renderer: crate::svg::Renderer, - Renderer::Theme: widget::svg::StyleSheet, -{ - widget::Svg::new(handle) -} - -/// A container intercepting mouse events. -pub fn mouse_area<'a, Message, Renderer>( - widget: impl Into<Element<'a, Message, Renderer>>, -) -> widget::MouseArea<'a, Message, Renderer> -where - Renderer: crate::Renderer, -{ - widget::MouseArea::new(widget) -} diff --git a/native/src/widget/operation.rs b/native/src/widget/operation.rs deleted file mode 100644 index 53688a21..00000000 --- a/native/src/widget/operation.rs +++ /dev/null @@ -1,112 +0,0 @@ -//! 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 std::any::Any; -use std::fmt; - -/// 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<T> { - /// Operates on a widget that contains other widgets. - /// - /// The `operate_on_children` function can be called to return control to - /// the widget tree and keep traversing it. - fn container( - &mut self, - id: Option<&Id>, - operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>), - ); - - /// Operates on a widget that can be focused. - fn focusable(&mut self, _state: &mut dyn Focusable, _id: Option<&Id>) {} - - /// Operates on a widget that can be scrolled. - fn scrollable(&mut self, _state: &mut dyn Scrollable, _id: Option<&Id>) {} - - /// Operates on a widget that has text input. - fn text_input(&mut self, _state: &mut dyn TextInput, _id: Option<&Id>) {} - - /// Operates on a custom widget with some state. - fn custom(&mut self, _state: &mut dyn Any, _id: Option<&Id>) {} - - /// Finishes the [`Operation`] and returns its [`Outcome`]. - fn finish(&self) -> Outcome<T> { - Outcome::None - } -} - -/// The result of an [`Operation`]. -pub enum Outcome<T> { - /// The [`Operation`] produced no result. - None, - - /// The [`Operation`] produced some result. - Some(T), - - /// The [`Operation`] needs to be followed by another [`Operation`]. - Chain(Box<dyn Operation<T>>), -} - -impl<T> fmt::Debug for Outcome<T> -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(...)"), - } - } -} - -/// Produces an [`Operation`] that applies the given [`Operation`] to the -/// children of a container with the given [`Id`]. -pub fn scoped<T: 'static>( - target: Id, - operation: impl Operation<T> + 'static, -) -> impl Operation<T> { - struct ScopedOperation<Message> { - target: Id, - operation: Box<dyn Operation<Message>>, - } - - impl<Message: 'static> Operation<Message> for ScopedOperation<Message> { - fn container( - &mut self, - id: Option<&Id>, - operate_on_children: &mut dyn FnMut(&mut dyn Operation<Message>), - ) { - if id == Some(&self.target) { - operate_on_children(self.operation.as_mut()); - } else { - operate_on_children(self); - } - } - - fn finish(&self) -> Outcome<Message> { - match self.operation.finish() { - Outcome::Chain(next) => { - Outcome::Chain(Box::new(ScopedOperation { - target: self.target.clone(), - operation: next, - })) - } - outcome => outcome, - } - } - } - - ScopedOperation { - target, - operation: Box::new(operation), - } -} diff --git a/native/src/window.rs b/native/src/window.rs deleted file mode 100644 index 1ae89dba..00000000 --- a/native/src/window.rs +++ /dev/null @@ -1,33 +0,0 @@ -//! Build window-based GUI applications. -mod action; -mod event; -mod mode; -mod redraw_request; -mod user_attention; - -pub mod icon; - -pub use action::Action; -pub use event::Event; -pub use icon::Icon; -pub use mode::Mode; -pub use redraw_request::RedrawRequest; -pub use user_attention::UserAttention; - -use crate::subscription::{self, Subscription}; -use crate::time::Instant; - -/// Subscribes to the frames of the window of the running application. -/// -/// The resulting [`Subscription`] will produce items at a rate equal to the -/// refresh rate of the window. Note that this rate may be variable, as it is -/// normally managed by the graphics driver and/or the OS. -/// -/// In any case, this [`Subscription`] is useful to smoothly draw application-driven -/// animations without missing any frames. -pub fn frames() -> Subscription<Instant> { - subscription::raw_events(|event, _status| match event { - crate::Event::Window(Event::RedrawRequested(at)) => Some(at), - _ => None, - }) -} diff --git a/renderer/Cargo.toml b/renderer/Cargo.toml new file mode 100644 index 00000000..640ac996 --- /dev/null +++ b/renderer/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "iced_renderer" +version = "0.1.0" +edition = "2021" + +[features] +wgpu = ["iced_wgpu"] +image = ["iced_tiny_skia/image", "iced_wgpu?/image"] +svg = ["iced_tiny_skia/svg", "iced_wgpu?/svg"] +geometry = ["iced_graphics/geometry", "iced_tiny_skia/geometry", "iced_wgpu?/geometry"] +tracing = ["iced_wgpu?/tracing"] + +[dependencies] +raw-window-handle = "0.5" +thiserror = "1" + +[dependencies.iced_graphics] +version = "0.8" +path = "../graphics" + +[dependencies.iced_tiny_skia] +version = "0.1" +path = "../tiny_skia" + +[dependencies.iced_wgpu] +version = "0.10" +path = "../wgpu" +optional = true diff --git a/renderer/src/backend.rs b/renderer/src/backend.rs new file mode 100644 index 00000000..c9d79851 --- /dev/null +++ b/renderer/src/backend.rs @@ -0,0 +1,106 @@ +use crate::core::text; +use crate::core::{Font, Point, Size}; +use crate::graphics::backend; + +use std::borrow::Cow; + +#[allow(clippy::large_enum_variant)] +pub enum Backend { + TinySkia(iced_tiny_skia::Backend), + #[cfg(feature = "wgpu")] + Wgpu(iced_wgpu::Backend), +} + +macro_rules! delegate { + ($backend:expr, $name:ident, $body:expr) => { + match $backend { + Self::TinySkia($name) => $body, + #[cfg(feature = "wgpu")] + Self::Wgpu($name) => $body, + } + }; +} + +impl iced_graphics::Backend for Backend { + fn trim_measurements(&mut self) { + delegate!(self, backend, backend.trim_measurements()); + } +} + +impl backend::Text for Backend { + const ICON_FONT: Font = Font::with_name("Iced-Icons"); + const CHECKMARK_ICON: char = '\u{f00c}'; + const ARROW_DOWN_ICON: char = '\u{e800}'; + + fn default_font(&self) -> Font { + delegate!(self, backend, backend.default_font()) + } + + fn default_size(&self) -> f32 { + delegate!(self, backend, backend.default_size()) + } + + fn measure( + &self, + contents: &str, + size: f32, + line_height: text::LineHeight, + font: Font, + bounds: Size, + shaping: text::Shaping, + ) -> (f32, f32) { + delegate!( + self, + backend, + backend.measure(contents, size, line_height, font, bounds, shaping) + ) + } + + fn hit_test( + &self, + contents: &str, + size: f32, + line_height: text::LineHeight, + font: Font, + bounds: Size, + shaping: text::Shaping, + position: Point, + nearest_only: bool, + ) -> Option<text::Hit> { + delegate!( + self, + backend, + backend.hit_test( + contents, + size, + line_height, + font, + bounds, + shaping, + position, + nearest_only, + ) + ) + } + + fn load_font(&mut self, font: Cow<'static, [u8]>) { + delegate!(self, backend, backend.load_font(font)); + } +} + +#[cfg(feature = "image")] +impl backend::Image for Backend { + fn dimensions(&self, handle: &crate::core::image::Handle) -> Size<u32> { + delegate!(self, backend, backend.dimensions(handle)) + } +} + +#[cfg(feature = "svg")] +impl backend::Svg for Backend { + fn viewport_dimensions( + &self, + handle: &crate::core::svg::Handle, + ) -> Size<u32> { + delegate!(self, backend, backend.viewport_dimensions(handle)) + } +} diff --git a/renderer/src/compositor.rs b/renderer/src/compositor.rs new file mode 100644 index 00000000..a353b8e4 --- /dev/null +++ b/renderer/src/compositor.rs @@ -0,0 +1,214 @@ +use crate::core::Color; +use crate::graphics::compositor::{Information, SurfaceError}; +use crate::graphics::{Error, Viewport}; +use crate::{Renderer, Settings}; + +use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle}; +use std::env; + +pub enum Compositor<Theme> { + TinySkia(iced_tiny_skia::window::Compositor<Theme>), + #[cfg(feature = "wgpu")] + Wgpu(iced_wgpu::window::Compositor<Theme>), +} + +pub enum Surface { + TinySkia(iced_tiny_skia::window::Surface), + #[cfg(feature = "wgpu")] + Wgpu(iced_wgpu::window::Surface), +} + +impl<Theme> crate::graphics::Compositor for Compositor<Theme> { + type Settings = Settings; + type Renderer = Renderer<Theme>; + type Surface = Surface; + + fn new<W: HasRawWindowHandle + HasRawDisplayHandle>( + settings: Self::Settings, + compatible_window: Option<&W>, + ) -> Result<(Self, Self::Renderer), Error> { + let candidates = + Candidate::list_from_env().unwrap_or(Candidate::default_list()); + + let mut error = Error::GraphicsAdapterNotFound; + + for candidate in candidates { + match candidate.build(settings, compatible_window) { + Ok((compositor, renderer)) => { + return Ok((compositor, renderer)) + } + Err(new_error) => { + error = new_error; + } + } + } + + Err(error) + } + + fn create_surface<W: HasRawWindowHandle + HasRawDisplayHandle>( + &mut self, + window: &W, + width: u32, + height: u32, + ) -> Surface { + match self { + Self::TinySkia(compositor) => Surface::TinySkia( + compositor.create_surface(window, width, height), + ), + #[cfg(feature = "wgpu")] + Self::Wgpu(compositor) => { + Surface::Wgpu(compositor.create_surface(window, width, height)) + } + } + } + + fn configure_surface( + &mut self, + surface: &mut Surface, + width: u32, + height: u32, + ) { + match (self, surface) { + (Self::TinySkia(compositor), Surface::TinySkia(surface)) => { + compositor.configure_surface(surface, width, height); + } + #[cfg(feature = "wgpu")] + (Self::Wgpu(compositor), Surface::Wgpu(surface)) => { + compositor.configure_surface(surface, width, height); + } + #[allow(unreachable_patterns)] + _ => panic!( + "The provided surface is not compatible with the compositor." + ), + } + } + + fn fetch_information(&self) -> Information { + match self { + Self::TinySkia(compositor) => compositor.fetch_information(), + #[cfg(feature = "wgpu")] + Self::Wgpu(compositor) => compositor.fetch_information(), + } + } + + fn present<T: AsRef<str>>( + &mut self, + renderer: &mut Self::Renderer, + surface: &mut Self::Surface, + viewport: &Viewport, + background_color: Color, + overlay: &[T], + ) -> Result<(), SurfaceError> { + renderer.with_primitives(|backend, primitives| { + match (self, backend, surface) { + ( + Self::TinySkia(_compositor), + crate::Backend::TinySkia(backend), + Surface::TinySkia(surface), + ) => iced_tiny_skia::window::compositor::present( + backend, + surface, + primitives, + viewport, + background_color, + overlay, + ), + #[cfg(feature = "wgpu")] + ( + Self::Wgpu(compositor), + crate::Backend::Wgpu(backend), + Surface::Wgpu(surface), + ) => iced_wgpu::window::compositor::present( + compositor, + backend, + surface, + primitives, + viewport, + background_color, + overlay, + ), + #[allow(unreachable_patterns)] + _ => panic!( + "The provided renderer or surface are not compatible \ + with the compositor." + ), + } + }) + } +} + +enum Candidate { + Wgpu, + TinySkia, +} + +impl Candidate { + fn default_list() -> Vec<Self> { + vec![ + #[cfg(feature = "wgpu")] + Self::Wgpu, + Self::TinySkia, + ] + } + + fn list_from_env() -> Option<Vec<Self>> { + let backends = env::var("ICED_BACKEND").ok()?; + + Some( + backends + .split(',') + .map(str::trim) + .map(|backend| match backend { + "wgpu" => Self::Wgpu, + "tiny-skia" => Self::TinySkia, + _ => panic!("unknown backend value: \"{backend}\""), + }) + .collect(), + ) + } + + fn build<Theme, W: HasRawWindowHandle + HasRawDisplayHandle>( + self, + settings: Settings, + _compatible_window: Option<&W>, + ) -> Result<(Compositor<Theme>, Renderer<Theme>), Error> { + match self { + Self::TinySkia => { + let (compositor, backend) = + iced_tiny_skia::window::compositor::new( + iced_tiny_skia::Settings { + default_font: settings.default_font, + default_text_size: settings.default_text_size, + }, + ); + + Ok(( + Compositor::TinySkia(compositor), + Renderer::new(crate::Backend::TinySkia(backend)), + )) + } + #[cfg(feature = "wgpu")] + Self::Wgpu => { + let (compositor, backend) = iced_wgpu::window::compositor::new( + iced_wgpu::Settings { + default_font: settings.default_font, + default_text_size: settings.default_text_size, + antialiasing: settings.antialiasing, + ..iced_wgpu::Settings::from_env() + }, + _compatible_window, + )?; + + Ok(( + Compositor::Wgpu(compositor), + Renderer::new(crate::Backend::Wgpu(backend)), + )) + } + #[cfg(not(feature = "wgpu"))] + Self::Wgpu => { + panic!("`wgpu` feature was not enabled in `iced_renderer`") + } + } + } +} diff --git a/renderer/src/geometry.rs b/renderer/src/geometry.rs new file mode 100644 index 00000000..26e2fed0 --- /dev/null +++ b/renderer/src/geometry.rs @@ -0,0 +1,174 @@ +mod cache; + +pub use cache::Cache; + +use crate::core::{Point, Rectangle, Size, Vector}; +use crate::graphics::geometry::{Fill, Geometry, Path, Stroke, Text}; +use crate::Backend; + +pub enum Frame { + TinySkia(iced_tiny_skia::geometry::Frame), + #[cfg(feature = "wgpu")] + Wgpu(iced_wgpu::geometry::Frame), +} + +macro_rules! delegate { + ($frame:expr, $name:ident, $body:expr) => { + match $frame { + Self::TinySkia($name) => $body, + #[cfg(feature = "wgpu")] + Self::Wgpu($name) => $body, + } + }; +} + +impl Frame { + pub fn new<Theme>(renderer: &crate::Renderer<Theme>, size: Size) -> Self { + match renderer.backend() { + Backend::TinySkia(_) => { + Frame::TinySkia(iced_tiny_skia::geometry::Frame::new(size)) + } + #[cfg(feature = "wgpu")] + Backend::Wgpu(_) => { + Frame::Wgpu(iced_wgpu::geometry::Frame::new(size)) + } + } + } + + /// Returns the width of the [`Frame`]. + #[inline] + pub fn width(&self) -> f32 { + delegate!(self, frame, frame.width()) + } + + /// Returns the height of the [`Frame`]. + #[inline] + pub fn height(&self) -> f32 { + delegate!(self, frame, frame.height()) + } + + /// Returns the dimensions of the [`Frame`]. + #[inline] + pub fn size(&self) -> Size { + delegate!(self, frame, frame.size()) + } + + /// Returns the coordinate of the center of the [`Frame`]. + #[inline] + pub fn center(&self) -> Point { + delegate!(self, frame, frame.center()) + } + + /// Draws the given [`Path`] on the [`Frame`] by filling it with the + /// provided style. + pub fn fill(&mut self, path: &Path, fill: impl Into<Fill>) { + delegate!(self, frame, frame.fill(path, fill)); + } + + /// Draws an axis-aligned rectangle given its top-left corner coordinate and + /// its `Size` on the [`Frame`] by filling it with the provided style. + pub fn fill_rectangle( + &mut self, + top_left: Point, + size: Size, + fill: impl Into<Fill>, + ) { + delegate!(self, frame, frame.fill_rectangle(top_left, size, fill)); + } + + /// Draws the stroke of the given [`Path`] on the [`Frame`] with the + /// provided style. + pub fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>) { + delegate!(self, frame, frame.stroke(path, stroke)); + } + + /// Draws the characters of the given [`Text`] on the [`Frame`], filling + /// them with the given color. + /// + /// __Warning:__ Text currently does not work well with rotations and scale + /// transforms! The position will be correctly transformed, but the + /// resulting glyphs will not be rotated or scaled properly. + /// + /// Additionally, all text will be rendered on top of all the layers of + /// a [`Canvas`]. Therefore, it is currently only meant to be used for + /// overlays, which is the most common use case. + /// + /// Support for vectorial text is planned, and should address all these + /// limitations. + /// + /// [`Canvas`]: crate::widget::Canvas + pub fn fill_text(&mut self, text: impl Into<Text>) { + delegate!(self, frame, frame.fill_text(text)); + } + + /// Stores the current transform of the [`Frame`] and executes the given + /// drawing operations, restoring the transform afterwards. + /// + /// This method is useful to compose transforms and perform drawing + /// operations in different coordinate systems. + #[inline] + pub fn with_save(&mut self, f: impl FnOnce(&mut Frame)) { + delegate!(self, frame, frame.push_transform()); + + f(self); + + delegate!(self, frame, frame.pop_transform()); + } + + /// Executes the given drawing operations within a [`Rectangle`] region, + /// clipping any geometry that overflows its bounds. Any transformations + /// performed are local to the provided closure. + /// + /// This method is useful to perform drawing operations that need to be + /// clipped. + #[inline] + pub fn with_clip(&mut self, region: Rectangle, f: impl FnOnce(&mut Frame)) { + let mut frame = match self { + Self::TinySkia(_) => Self::TinySkia( + iced_tiny_skia::geometry::Frame::new(region.size()), + ), + #[cfg(feature = "wgpu")] + Self::Wgpu(_) => { + Self::Wgpu(iced_wgpu::geometry::Frame::new(region.size())) + } + }; + + f(&mut frame); + + let origin = Point::new(region.x, region.y); + + match (self, frame) { + (Self::TinySkia(target), Self::TinySkia(frame)) => { + target.clip(frame, origin); + } + #[cfg(feature = "wgpu")] + (Self::Wgpu(target), Self::Wgpu(frame)) => { + target.clip(frame, origin); + } + #[allow(unreachable_patterns)] + _ => unreachable!(), + }; + } + + /// Applies a translation to the current transform of the [`Frame`]. + #[inline] + pub fn translate(&mut self, translation: Vector) { + delegate!(self, frame, frame.translate(translation)); + } + + /// Applies a rotation in radians to the current transform of the [`Frame`]. + #[inline] + pub fn rotate(&mut self, angle: f32) { + delegate!(self, frame, frame.rotate(angle)); + } + + /// Applies a scaling to the current transform of the [`Frame`]. + #[inline] + pub fn scale(&mut self, scale: f32) { + delegate!(self, frame, frame.scale(scale)); + } + + pub fn into_geometry(self) -> Geometry { + Geometry(delegate!(self, frame, frame.into_primitive())) + } +} diff --git a/graphics/src/widget/canvas/cache.rs b/renderer/src/geometry/cache.rs index 678b0f92..2a3534d0 100644 --- a/graphics/src/widget/canvas/cache.rs +++ b/renderer/src/geometry/cache.rs @@ -1,18 +1,10 @@ -use crate::widget::canvas::{Frame, Geometry}; -use crate::Primitive; +use crate::core::Size; +use crate::geometry::{Frame, Geometry}; +use crate::graphics::Primitive; +use crate::Renderer; -use iced_native::Size; -use std::{cell::RefCell, sync::Arc}; - -#[derive(Default)] -enum State { - #[default] - Empty, - Filled { - bounds: Size, - primitive: Arc<Primitive>, - }, -} +use std::cell::RefCell; +use std::sync::Arc; /// A simple cache that stores generated [`Geometry`] to avoid recomputation. /// @@ -23,6 +15,16 @@ pub struct Cache { state: RefCell<State>, } +#[derive(Debug, Default)] +enum State { + #[default] + Empty, + Filled { + bounds: Size, + primitive: Arc<Primitive>, + }, +} + impl Cache { /// Creates a new empty [`Cache`]. pub fn new() -> Self { @@ -46,8 +48,9 @@ impl Cache { /// Otherwise, the previously stored [`Geometry`] will be returned. The /// [`Cache`] is not cleared in this case. In other words, it will keep /// returning the stored [`Geometry`] if needed. - pub fn draw( + pub fn draw<Theme>( &self, + renderer: &Renderer<Theme>, bounds: Size, draw_fn: impl FnOnce(&mut Frame), ) -> Geometry { @@ -59,19 +62,19 @@ impl Cache { } = self.state.borrow().deref() { if *cached_bounds == bounds { - return Geometry::from_primitive(Primitive::Cached { - cache: primitive.clone(), + return Geometry(Primitive::Cache { + content: primitive.clone(), }); } } - let mut frame = Frame::new(bounds); + let mut frame = Frame::new(renderer, bounds); draw_fn(&mut frame); let primitive = { let geometry = frame.into_geometry(); - Arc::new(geometry.into_primitive()) + Arc::new(geometry.0) }; *self.state.borrow_mut() = State::Filled { @@ -79,19 +82,6 @@ impl Cache { primitive: primitive.clone(), }; - Geometry::from_primitive(Primitive::Cached { cache: primitive }) - } -} - -impl std::fmt::Debug for State { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - State::Empty => write!(f, "Empty"), - State::Filled { primitive, bounds } => f - .debug_struct("Filled") - .field("primitive", primitive) - .field("bounds", bounds) - .finish(), - } + Geometry(Primitive::Cache { content: primitive }) } } diff --git a/renderer/src/lib.rs b/renderer/src/lib.rs new file mode 100644 index 00000000..22ec7bd1 --- /dev/null +++ b/renderer/src/lib.rs @@ -0,0 +1,19 @@ +pub mod compositor; + +#[cfg(feature = "geometry")] +pub mod geometry; + +mod backend; +mod settings; + +pub use iced_graphics as graphics; +pub use iced_graphics::core; + +pub use backend::Backend; +pub use compositor::Compositor; +pub use settings::Settings; + +/// The default graphics renderer for [`iced`]. +/// +/// [`iced`]: https://github.com/iced-rs/iced +pub type Renderer<Theme> = iced_graphics::Renderer<Backend, Theme>; diff --git a/renderer/src/settings.rs b/renderer/src/settings.rs new file mode 100644 index 00000000..2e51f339 --- /dev/null +++ b/renderer/src/settings.rs @@ -0,0 +1,31 @@ +use crate::core::Font; +use crate::graphics::Antialiasing; + +/// The settings of a [`Backend`]. +/// +/// [`Backend`]: crate::Backend +#[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: f32, + + /// The antialiasing strategy that will be used for triangle primitives. + /// + /// By default, it is `None`. + pub antialiasing: Option<Antialiasing>, +} + +impl Default for Settings { + fn default() -> Settings { + Settings { + default_font: Font::default(), + default_text_size: 16.0, + antialiasing: None, + } + } +} diff --git a/graphics/src/widget.rs b/renderer/src/widget.rs index e7fab97c..6c0c2a83 100644 --- a/graphics/src/widget.rs +++ b/renderer/src/widget.rs @@ -1,16 +1,11 @@ -//! Use the graphical widgets supported out-of-the-box. #[cfg(feature = "canvas")] -#[cfg_attr(docsrs, doc(cfg(feature = "canvas")))] pub mod canvas; #[cfg(feature = "canvas")] -#[doc(no_inline)] pub use canvas::Canvas; #[cfg(feature = "qr_code")] -#[cfg_attr(docsrs, doc(cfg(feature = "qr_code")))] pub mod qr_code; #[cfg(feature = "qr_code")] -#[doc(no_inline)] pub use qr_code::QRCode; diff --git a/native/Cargo.toml b/runtime/Cargo.toml index 5ba06b67..a65f07f2 100644 --- a/native/Cargo.toml +++ b/runtime/Cargo.toml @@ -1,6 +1,6 @@ [package] -name = "iced_native" -version = "0.10.3" +name = "iced_runtime" +version = "0.1.0" authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] edition = "2021" description = "A renderer-agnostic library for native GUIs" @@ -11,9 +11,6 @@ repository = "https://github.com/iced-rs/iced" debug = [] [dependencies] -twox-hash = { version = "1.5", default-features = false } -unicode-segmentation = "1.6" -num-traits = "0.2" thiserror = "1" [dependencies.iced_core] @@ -24,7 +21,3 @@ path = "../core" version = "0.6" path = "../futures" features = ["thread-pool"] - -[dependencies.iced_style] -version = "0.8" -path = "../style" diff --git a/runtime/README.md b/runtime/README.md new file mode 100644 index 00000000..1b0fa857 --- /dev/null +++ b/runtime/README.md @@ -0,0 +1,18 @@ +# `iced_runtime` +[][documentation] +[](https://crates.io/crates/iced_native) +[](https://github.com/iced-rs/iced/blob/master/LICENSE) +[](https://discord.gg/3xZJ65GAhd) + +`iced_runtime` takes [`iced_core`] and builds a native runtime on top of it. + +[documentation]: https://docs.rs/iced_native +[`iced_core`]: ../core +[`iced_winit`]: ../winit +[`druid`]: https://github.com/xi-editor/druid +[`raw-window-handle`]: https://github.com/rust-windowing/raw-window-handle + +__Iced moves fast and the `master` branch can contain breaking changes!__ If +you want to learn about a specific release, check out [the release list]. + +[the release list]: https://github.com/iced-rs/iced/releases diff --git a/native/src/clipboard.rs b/runtime/src/clipboard.rs index c9105bc0..bc450912 100644 --- a/native/src/clipboard.rs +++ b/runtime/src/clipboard.rs @@ -1,30 +1,9 @@ //! Access the clipboard. -use iced_futures::MaybeSend; +use crate::command::{self, Command}; +use crate::futures::MaybeSend; use std::fmt; -/// A buffer for short-term storage and transfer within and between -/// applications. -pub trait Clipboard { - /// Reads the current content of the [`Clipboard`] as text. - fn read(&self) -> Option<String>; - - /// Writes the given text contents to the [`Clipboard`]. - fn write(&mut self, contents: String); -} - -/// A null implementation of the [`Clipboard`] trait. -#[derive(Debug, Clone, Copy)] -pub struct Null; - -impl Clipboard for Null { - fn read(&self) -> Option<String> { - None - } - - fn write(&mut self, _contents: String) {} -} - /// A clipboard action to be performed by some [`Command`]. /// /// [`Command`]: crate::Command @@ -60,3 +39,15 @@ impl<T> fmt::Debug for Action<T> { } } } + +/// Read the current contents of the clipboard. +pub fn read<Message>( + f: impl Fn(Option<String>) -> Message + 'static, +) -> Command<Message> { + Command::single(command::Action::Clipboard(Action::Read(Box::new(f)))) +} + +/// Write the given contents to the clipboard. +pub fn write<Message>(contents: String) -> Command<Message> { + Command::single(command::Action::Clipboard(Action::Write(contents))) +} diff --git a/native/src/command.rs b/runtime/src/command.rs index ca9d0b64..cd4c51ff 100644 --- a/native/src/command.rs +++ b/runtime/src/command.rs @@ -3,35 +3,39 @@ mod action; pub use action::Action; -use crate::widget; - -use iced_futures::MaybeSend; +use crate::core::widget; +use crate::futures::MaybeSend; use std::fmt; use std::future::Future; /// A set of asynchronous actions to be performed by some runtime. #[must_use = "`Command` must be returned to runtime to take effect"] -pub struct Command<T>(iced_futures::Command<Action<T>>); +pub struct Command<T>(Internal<Action<T>>); + +#[derive(Debug)] +enum Internal<T> { + None, + Single(T), + Batch(Vec<T>), +} impl<T> Command<T> { /// Creates an empty [`Command`]. /// /// In other words, a [`Command`] that does nothing. pub const fn none() -> Self { - Self(iced_futures::Command::none()) + Self(Internal::None) } /// Creates a [`Command`] that performs a single [`Action`]. pub const fn single(action: Action<T>) -> Self { - Self(iced_futures::Command::single(action)) + Self(Internal::Single(action)) } /// Creates a [`Command`] that performs a [`widget::Operation`]. pub fn widget(operation: impl widget::Operation<T> + 'static) -> Self { - Self(iced_futures::Command::single(Action::Widget( - widget::Action::new(operation), - ))) + Self::single(Action::Widget(Box::new(operation))) } /// Creates a [`Command`] that performs the action of the given future. @@ -49,9 +53,17 @@ impl<T> Command<T> { /// /// Once this command is run, all the commands will be executed at once. pub fn batch(commands: impl IntoIterator<Item = Command<T>>) -> Self { - Self(iced_futures::Command::batch( - commands.into_iter().map(|Command(command)| command), - )) + let mut batch = Vec::new(); + + for Command(command) in commands { + match command { + Internal::None => {} + Internal::Single(command) => batch.push(command), + Internal::Batch(commands) => batch.extend(commands), + } + } + + Self(Internal::Batch(batch)) } /// Applies a transformation to the result of a [`Command`]. @@ -63,16 +75,27 @@ impl<T> Command<T> { T: 'static, A: 'static, { - let Command(command) = self; - - Command(command.map(move |action| action.map(f.clone()))) + match self.0 { + Internal::None => Command::none(), + Internal::Single(action) => Command::single(action.map(f)), + Internal::Batch(batch) => Command(Internal::Batch( + batch + .into_iter() + .map(|action| action.map(f.clone())) + .collect(), + )), + } } /// Returns all of the actions of the [`Command`]. pub fn actions(self) -> Vec<Action<T>> { let Command(command) = self; - command.actions() + match command { + Internal::None => Vec::new(), + Internal::Single(action) => vec![action], + Internal::Batch(batch) => batch, + } } } diff --git a/native/src/command/action.rs b/runtime/src/command/action.rs index a51b8c21..6c74f0ef 100644 --- a/native/src/command/action.rs +++ b/runtime/src/command/action.rs @@ -1,10 +1,12 @@ use crate::clipboard; +use crate::core::widget; +use crate::font; use crate::system; -use crate::widget; use crate::window; use iced_futures::MaybeSend; +use std::borrow::Cow; use std::fmt; /// An action that a [`Command`] can perform. @@ -26,7 +28,16 @@ pub enum Action<T> { System(system::Action<T>), /// Run a widget action. - Widget(widget::Action<T>), + Widget(Box<dyn widget::Operation<T>>), + + /// Load a font from its bytes. + LoadFont { + /// The bytes of the font to load. + bytes: Cow<'static, [u8]>, + + /// The message to produce when the font has been loaded. + tagger: Box<dyn Fn(Result<(), font::Error>) -> T>, + }, } impl<T> Action<T> { @@ -48,7 +59,13 @@ impl<T> Action<T> { Self::Clipboard(action) => Action::Clipboard(action.map(f)), Self::Window(window) => Action::Window(window.map(f)), Self::System(system) => Action::System(system.map(f)), - Self::Widget(widget) => Action::Widget(widget.map(f)), + Self::Widget(operation) => { + Action::Widget(Box::new(widget::operation::map(operation, f))) + } + Self::LoadFont { bytes, tagger } => Action::LoadFont { + bytes, + tagger: Box::new(move |result| f(tagger(result))), + }, } } } @@ -63,6 +80,7 @@ impl<T> fmt::Debug for Action<T> { Self::Window(action) => write!(f, "Action::Window({action:?})"), Self::System(action) => write!(f, "Action::System({action:?})"), Self::Widget(_action) => write!(f, "Action::Widget"), + Self::LoadFont { .. } => write!(f, "Action::LoadFont"), } } } diff --git a/native/src/debug/basic.rs b/runtime/src/debug/basic.rs index 92f614da..32f725a1 100644 --- a/native/src/debug/basic.rs +++ b/runtime/src/debug/basic.rs @@ -1,5 +1,5 @@ #![allow(missing_docs)] -use crate::time; +use crate::core::time; use std::collections::VecDeque; diff --git a/native/src/debug/null.rs b/runtime/src/debug/null.rs index 2db0eebb..2db0eebb 100644 --- a/native/src/debug/null.rs +++ b/runtime/src/debug/null.rs diff --git a/runtime/src/font.rs b/runtime/src/font.rs new file mode 100644 index 00000000..15359694 --- /dev/null +++ b/runtime/src/font.rs @@ -0,0 +1,19 @@ +//! Load and use fonts. +pub use iced_core::font::*; + +use crate::command::{self, Command}; +use std::borrow::Cow; + +/// An error while loading a font. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Error {} + +/// Load a font from its bytes. +pub fn load( + bytes: impl Into<Cow<'static, [u8]>>, +) -> Command<Result<(), Error>> { + Command::single(command::Action::LoadFont { + bytes: bytes.into(), + tagger: Box::new(std::convert::identity), + }) +} diff --git a/native/src/keyboard.rs b/runtime/src/keyboard.rs index 012538e3..012538e3 100644 --- a/native/src/keyboard.rs +++ b/runtime/src/keyboard.rs diff --git a/native/src/lib.rs b/runtime/src/lib.rs index dc77950c..d3b84c7d 100644 --- a/native/src/lib.rs +++ b/runtime/src/lib.rs @@ -42,32 +42,16 @@ clippy::useless_conversion )] #![forbid(unsafe_code, rust_2018_idioms)] -#![allow(clippy::inherent_to_string, clippy::type_complexity)] #![cfg_attr(docsrs, feature(doc_cfg))] pub mod clipboard; pub mod command; -pub mod event; -pub mod image; +pub mod font; pub mod keyboard; -pub mod layout; -pub mod mouse; -pub mod overlay; pub mod program; -pub mod renderer; -pub mod subscription; -pub mod svg; pub mod system; -pub mod text; -pub mod touch; pub mod user_interface; -pub mod widget; pub mod window; -mod element; -mod hasher; -mod runtime; -mod shell; - // We disable debug capabilities on release builds unless the `debug` feature // is explicitly enabled. #[cfg(feature = "debug")] @@ -77,32 +61,11 @@ mod debug; #[path = "debug/null.rs"] mod debug; -pub use iced_core::alignment; -pub use iced_core::time; -pub use iced_core::{ - color, Alignment, Background, Color, ContentFit, Font, Length, Padding, - Pixels, Point, Rectangle, Size, Vector, -}; -pub use iced_futures::{executor, futures}; -pub use iced_style::application; -pub use iced_style::theme; - -#[doc(no_inline)] -pub use executor::Executor; +pub use iced_core as core; +pub use iced_futures as futures; -pub use clipboard::Clipboard; pub use command::Command; pub use debug::Debug; -pub use element::Element; -pub use event::Event; -pub use hasher::Hasher; -pub use layout::Layout; -pub use overlay::Overlay; +pub use font::Font; pub use program::Program; -pub use renderer::Renderer; -pub use runtime::Runtime; -pub use shell::Shell; -pub use subscription::Subscription; -pub use theme::Theme; pub use user_interface::UserInterface; -pub use widget::Widget; diff --git a/native/src/program.rs b/runtime/src/program.rs index c71c237f..44585cc5 100644 --- a/native/src/program.rs +++ b/runtime/src/program.rs @@ -1,5 +1,8 @@ //! Build interactive programs using The Elm Architecture. -use crate::{Command, Element, Renderer}; +use crate::Command; + +use iced_core::text; +use iced_core::{Element, Renderer}; mod state; @@ -8,7 +11,7 @@ pub use state::State; /// The core of a user interface application following The Elm Architecture. pub trait Program: Sized { /// The graphics backend to use to draw the [`Program`]. - type Renderer: Renderer; + type Renderer: Renderer + text::Renderer; /// The type of __messages__ your [`Program`] will produce. type Message: std::fmt::Debug + Send; diff --git a/native/src/program/state.rs b/runtime/src/program/state.rs index 8ae1cacb..2fa9934d 100644 --- a/native/src/program/state.rs +++ b/runtime/src/program/state.rs @@ -1,9 +1,9 @@ -use crate::application; -use crate::event::{self, Event}; -use crate::mouse; -use crate::renderer; +use crate::core::event::{self, Event}; +use crate::core::mouse; +use crate::core::renderer; +use crate::core::{Clipboard, Point, Size}; use crate::user_interface::{self, UserInterface}; -use crate::{Clipboard, Command, Debug, Point, Program, Size}; +use crate::{Command, Debug, Program}; /// The execution state of a [`Program`]. It leverages caching, event /// processing, and rendering primitive storage. @@ -22,7 +22,6 @@ where impl<P> State<P> where P: Program + 'static, - <P::Renderer as crate::Renderer>::Theme: application::StyleSheet, { /// Creates a new [`State`] with the provided [`Program`], initializing its /// primitive with the given logical bounds and renderer. @@ -91,7 +90,7 @@ where bounds: Size, cursor_position: Point, renderer: &mut P::Renderer, - theme: &<P::Renderer as crate::Renderer>::Theme, + theme: &<P::Renderer as iced_core::Renderer>::Theme, style: &renderer::Style, clipboard: &mut dyn Clipboard, debug: &mut Debug, @@ -182,10 +181,7 @@ fn build_user_interface<'a, P: Program>( renderer: &mut P::Renderer, size: Size, debug: &mut Debug, -) -> UserInterface<'a, P::Message, P::Renderer> -where - <P::Renderer as crate::Renderer>::Theme: application::StyleSheet, -{ +) -> UserInterface<'a, P::Message, P::Renderer> { debug.view_started(); let view = program.view(); debug.view_finished(); diff --git a/native/src/system.rs b/runtime/src/system.rs index 61c8ff29..61c8ff29 100644 --- a/native/src/system.rs +++ b/runtime/src/system.rs diff --git a/native/src/system/action.rs b/runtime/src/system/action.rs index dea9536f..dea9536f 100644 --- a/native/src/system/action.rs +++ b/runtime/src/system/action.rs diff --git a/native/src/system/information.rs b/runtime/src/system/information.rs index 93e7a5a4..93e7a5a4 100644 --- a/native/src/system/information.rs +++ b/runtime/src/system/information.rs diff --git a/native/src/user_interface.rs b/runtime/src/user_interface.rs index e5c90bbb..d9206134 100644 --- a/native/src/user_interface.rs +++ b/runtime/src/user_interface.rs @@ -1,14 +1,12 @@ //! Implement your own event loop to drive a user interface. -use crate::application; -use crate::event::{self, Event}; -use crate::layout; -use crate::mouse; -use crate::renderer; -use crate::widget; -use crate::window; -use crate::{ - Clipboard, Element, Layout, Point, Rectangle, Shell, Size, Vector, -}; +use crate::core::event::{self, Event}; +use crate::core::layout; +use crate::core::mouse; +use crate::core::renderer; +use crate::core::widget; +use crate::core::window; +use crate::core::{Clipboard, Point, Rectangle, Size, Vector}; +use crate::core::{Element, Layout, Shell}; /// A set of interactive graphical elements with a specific [`Layout`]. /// @@ -18,11 +16,10 @@ use crate::{ /// charge of using this type in your system in any way you want. /// /// # Example -/// The [`integration_opengl`] & [`integration_wgpu`] examples use a -/// [`UserInterface`] to integrate Iced in an existing graphical application. +/// The [`integration`] example uses a [`UserInterface`] to integrate Iced in an +/// existing graphical application. /// -/// [`integration_opengl`]: https://github.com/iced-rs/iced/tree/0.9/examples/integration_opengl -/// [`integration_wgpu`]: https://github.com/iced-rs/iced/tree/0.9/examples/integration_wgpu +/// [`integration`]: https://github.com/iced-rs/iced/tree/0.9/examples/integration #[allow(missing_debug_implementations)] pub struct UserInterface<'a, Message, Renderer> { root: Element<'a, Message, Renderer>, @@ -34,8 +31,7 @@ pub struct UserInterface<'a, Message, Renderer> { impl<'a, Message, Renderer> UserInterface<'a, Message, Renderer> where - Renderer: crate::Renderer, - Renderer::Theme: application::StyleSheet, + Renderer: crate::core::Renderer, { /// Builds a user interface for an [`Element`]. /// @@ -48,24 +44,21 @@ where /// is naive way to set up our application loop: /// /// ```no_run - /// use iced_native::Size; - /// use iced_native::user_interface::{self, UserInterface}; - /// use iced_wgpu::Renderer; - /// /// # mod iced_wgpu { - /// # pub use iced_native::renderer::Null as Renderer; + /// # pub use iced_runtime::core::renderer::Null as Renderer; /// # } /// # - /// # use iced_native::widget::Column; - /// # /// # pub struct Counter; /// # /// # impl Counter { /// # pub fn new() -> Self { Counter } - /// # pub fn view(&self) -> Column<(), Renderer> { - /// # Column::new() - /// # } + /// # pub fn view(&self) -> iced_core::Element<(), Renderer> { unimplemented!() } + /// # pub fn update(&mut self, _: ()) {} /// # } + /// use iced_runtime::core::Size; + /// use iced_runtime::user_interface::{self, UserInterface}; + /// use iced_wgpu::Renderer; + /// /// // Initialization /// let mut counter = Counter::new(); /// let mut cache = user_interface::Cache::new(); @@ -124,25 +117,21 @@ where /// completing [the previous example](#example): /// /// ```no_run - /// use iced_native::{clipboard, Size, Point}; - /// use iced_native::user_interface::{self, UserInterface}; - /// use iced_wgpu::Renderer; - /// /// # mod iced_wgpu { - /// # pub use iced_native::renderer::Null as Renderer; + /// # pub use iced_runtime::core::renderer::Null as Renderer; /// # } /// # - /// # use iced_native::widget::Column; - /// # /// # pub struct Counter; /// # /// # impl Counter { /// # pub fn new() -> Self { Counter } - /// # pub fn view(&self) -> Column<(), Renderer> { - /// # Column::new() - /// # } - /// # pub fn update(&mut self, message: ()) {} + /// # pub fn view(&self) -> iced_core::Element<(), Renderer> { unimplemented!() } + /// # pub fn update(&mut self, _: ()) {} /// # } + /// use iced_runtime::core::{clipboard, Size, Point}; + /// use iced_runtime::user_interface::{self, UserInterface}; + /// use iced_wgpu::Renderer; + /// /// let mut counter = Counter::new(); /// let mut cache = user_interface::Cache::new(); /// let mut renderer = Renderer::new(); @@ -357,27 +346,24 @@ where /// [completing the last example](#example-1): /// /// ```no_run - /// use iced_native::clipboard; - /// use iced_native::renderer; - /// use iced_native::user_interface::{self, UserInterface}; - /// use iced_native::{Size, Point, Theme}; - /// use iced_wgpu::Renderer; - /// /// # mod iced_wgpu { - /// # pub use iced_native::renderer::Null as Renderer; + /// # pub use iced_runtime::core::renderer::Null as Renderer; + /// # pub type Theme = (); /// # } /// # - /// # use iced_native::widget::Column; - /// # /// # pub struct Counter; /// # /// # impl Counter { /// # pub fn new() -> Self { Counter } - /// # pub fn view(&self) -> Column<(), Renderer> { - /// # Column::new() - /// # } - /// # pub fn update(&mut self, message: ()) {} + /// # pub fn view(&self) -> Element<(), Renderer> { unimplemented!() } + /// # pub fn update(&mut self, _: ()) {} /// # } + /// use iced_runtime::core::clipboard; + /// use iced_runtime::core::renderer; + /// use iced_runtime::core::{Element, Size, Point}; + /// use iced_runtime::user_interface::{self, UserInterface}; + /// use iced_wgpu::{Renderer, Theme}; + /// /// let mut counter = Counter::new(); /// let mut cache = user_interface::Cache::new(); /// let mut renderer = Renderer::new(); @@ -386,6 +372,7 @@ where /// let mut clipboard = clipboard::Null; /// let mut events = Vec::new(); /// let mut messages = Vec::new(); + /// let mut theme = Theme::default(); /// /// loop { /// // Obtain system events... @@ -407,7 +394,7 @@ where /// ); /// /// // Draw the user interface - /// let mouse_cursor = user_interface.draw(&mut renderer, &Theme::default(), &renderer::Style::default(), cursor_position); + /// let mouse_cursor = user_interface.draw(&mut renderer, &theme, &renderer::Style::default(), cursor_position); /// /// cache = user_interface.into_cache(); /// diff --git a/winit/src/window.rs b/runtime/src/window.rs index ba0180c8..833a1125 100644 --- a/winit/src/window.rs +++ b/runtime/src/window.rs @@ -1,70 +1,78 @@ -//! Interact with the window of your application. +//! Build window-based GUI applications. +mod action; + +pub use action::Action; + use crate::command::{self, Command}; -use iced_native::window; +use crate::core::time::Instant; +use crate::core::window::{Event, Icon, Mode, UserAttention}; +use crate::futures::subscription::{self, Subscription}; -pub use window::{ - frames, icon, Event, Icon, Mode, RedrawRequest, UserAttention, -}; +/// Subscribes to the frames of the window of the running application. +/// +/// The resulting [`Subscription`] will produce items at a rate equal to the +/// refresh rate of the window. Note that this rate may be variable, as it is +/// normally managed by the graphics driver and/or the OS. +/// +/// In any case, this [`Subscription`] is useful to smoothly draw application-driven +/// animations without missing any frames. +pub fn frames() -> Subscription<Instant> { + subscription::raw_events(|event, _status| match event { + iced_core::Event::Window(Event::RedrawRequested(at)) => Some(at), + _ => None, + }) +} /// Closes the current window and exits the application. pub fn close<Message>() -> Command<Message> { - Command::single(command::Action::Window(window::Action::Close)) + Command::single(command::Action::Window(Action::Close)) } /// Begins dragging the window while the left mouse button is held. pub fn drag<Message>() -> Command<Message> { - Command::single(command::Action::Window(window::Action::Drag)) + Command::single(command::Action::Window(Action::Drag)) } /// Resizes the window to the given logical dimensions. pub fn resize<Message>(width: u32, height: u32) -> Command<Message> { - Command::single(command::Action::Window(window::Action::Resize { - width, - height, - })) + Command::single(command::Action::Window(Action::Resize { width, height })) } /// Maximizes the window. pub fn maximize<Message>(maximized: bool) -> Command<Message> { - Command::single(command::Action::Window(window::Action::Maximize( - maximized, - ))) + Command::single(command::Action::Window(Action::Maximize(maximized))) } /// Minimes the window. pub fn minimize<Message>(minimized: bool) -> Command<Message> { - Command::single(command::Action::Window(window::Action::Minimize( - minimized, - ))) + Command::single(command::Action::Window(Action::Minimize(minimized))) } /// Moves a window to the given logical coordinates. pub fn move_to<Message>(x: i32, y: i32) -> Command<Message> { - Command::single(command::Action::Window(window::Action::Move { x, y })) + Command::single(command::Action::Window(Action::Move { x, y })) } /// Sets the [`Mode`] of the window. pub fn change_mode<Message>(mode: Mode) -> Command<Message> { - Command::single(command::Action::Window(window::Action::ChangeMode(mode))) + Command::single(command::Action::Window(Action::ChangeMode(mode))) } /// Fetches the current [`Mode`] of the window. pub fn fetch_mode<Message>( f: impl FnOnce(Mode) -> Message + 'static, ) -> Command<Message> { - Command::single(command::Action::Window(window::Action::FetchMode( - Box::new(f), - ))) + Command::single(command::Action::Window(Action::FetchMode(Box::new(f)))) } /// Toggles the window to maximized or back. pub fn toggle_maximize<Message>() -> Command<Message> { - Command::single(command::Action::Window(window::Action::ToggleMaximize)) + Command::single(command::Action::Window(Action::ToggleMaximize)) } /// Toggles the window decorations. pub fn toggle_decorations<Message>() -> Command<Message> { - Command::single(command::Action::Window(window::Action::ToggleDecorations)) + Command::single(command::Action::Window(Action::ToggleDecorations)) } /// Request user attention to the window, this has no effect if the application @@ -76,9 +84,9 @@ pub fn toggle_decorations<Message>() -> Command<Message> { pub fn request_user_attention<Message>( user_attention: Option<UserAttention>, ) -> Command<Message> { - Command::single(command::Action::Window( - window::Action::RequestUserAttention(user_attention), - )) + Command::single(command::Action::Window(Action::RequestUserAttention( + user_attention, + ))) } /// Brings the window to the front and sets input focus. Has no effect if the window is @@ -88,26 +96,22 @@ pub fn request_user_attention<Message>( /// you are certain that's what the user wants. Focus stealing can cause an extremely disruptive /// user experience. pub fn gain_focus<Message>() -> Command<Message> { - Command::single(command::Action::Window(window::Action::GainFocus)) + Command::single(command::Action::Window(Action::GainFocus)) } /// Changes whether or not the window will always be on top of other windows. pub fn change_always_on_top<Message>(on_top: bool) -> Command<Message> { - Command::single(command::Action::Window(window::Action::ChangeAlwaysOnTop( - on_top, - ))) + Command::single(command::Action::Window(Action::ChangeAlwaysOnTop(on_top))) } /// Fetches an identifier unique to the window. pub fn fetch_id<Message>( f: impl FnOnce(u64) -> Message + 'static, ) -> Command<Message> { - Command::single(command::Action::Window(window::Action::FetchId(Box::new( - f, - )))) + Command::single(command::Action::Window(Action::FetchId(Box::new(f)))) } /// Changes the [`Icon`] of the window. pub fn change_icon<Message>(icon: Icon) -> Command<Message> { - Command::single(command::Action::Window(window::Action::ChangeIcon(icon))) + Command::single(command::Action::Window(Action::ChangeIcon(icon))) } diff --git a/native/src/window/action.rs b/runtime/src/window/action.rs index 095a8eec..83b71c75 100644 --- a/native/src/window/action.rs +++ b/runtime/src/window/action.rs @@ -1,6 +1,6 @@ -use crate::window::{Icon, Mode, UserAttention}; +use crate::core::window::{Icon, Mode, UserAttention}; +use crate::futures::MaybeSend; -use iced_futures::MaybeSend; use std::fmt; /// An operation to be performed on some window. diff --git a/src/advanced.rs b/src/advanced.rs new file mode 100644 index 00000000..2071aed0 --- /dev/null +++ b/src/advanced.rs @@ -0,0 +1,16 @@ +//! Leverage advanced concepts like custom widgets. +pub use crate::core::image; +pub use crate::core::layout::{self, Layout}; +pub use crate::core::mouse; +pub use crate::core::overlay::{self, Overlay}; +pub use crate::core::renderer::{self, Renderer}; +pub use crate::core::svg; +pub use crate::core::text::{self, Text}; +pub use crate::core::widget::{self, Widget}; +pub use crate::core::{Clipboard, Hasher, Shell}; +pub use crate::renderer::graphics; + +pub mod subscription { + //! Write your own subscriptions. + pub use crate::runtime::futures::subscription::{EventStream, Recipe}; +} diff --git a/src/application.rs b/src/application.rs index 54d2e391..abf58fa3 100644 --- a/src/application.rs +++ b/src/application.rs @@ -1,7 +1,7 @@ //! Build interactive cross-platform applications. use crate::{Command, Element, Executor, Settings, Subscription}; -pub use iced_native::application::{Appearance, StyleSheet}; +pub use crate::style::application::{Appearance, StyleSheet}; /// An interactive cross-platform application. /// @@ -197,26 +197,25 @@ pub trait Application: Sized { let renderer_settings = crate::renderer::Settings { default_font: settings.default_font, default_text_size: settings.default_text_size, - text_multithreading: settings.text_multithreading, antialiasing: if settings.antialiasing { - Some(crate::renderer::settings::Antialiasing::MSAAx4) + Some(crate::graphics::Antialiasing::MSAAx4) } else { None }, - ..crate::renderer::Settings::from_env() + ..crate::renderer::Settings::default() }; - Ok(crate::runtime::application::run::< + Ok(crate::shell::application::run::< Instance<Self>, Self::Executor, - crate::renderer::window::Compositor<Self::Theme>, + crate::renderer::Compositor<Self::Theme>, >(settings.into(), renderer_settings)?) } } struct Instance<A: Application>(A); -impl<A> iced_winit::Program for Instance<A> +impl<A> crate::runtime::Program for Instance<A> where A: Application, { @@ -232,7 +231,7 @@ where } } -impl<A> crate::runtime::Application for Instance<A> +impl<A> crate::shell::Application for Instance<A> where A: Application, { diff --git a/src/clipboard.rs b/src/clipboard.rs deleted file mode 100644 index dde17051..00000000 --- a/src/clipboard.rs +++ /dev/null @@ -1,3 +0,0 @@ -//! Access the clipboard. -#[cfg(not(target_arch = "wasm32"))] -pub use crate::runtime::clipboard::{read, write}; diff --git a/src/element.rs b/src/element.rs deleted file mode 100644 index 2eb1bb4d..00000000 --- a/src/element.rs +++ /dev/null @@ -1,5 +0,0 @@ -/// A generic widget. -/// -/// This is an alias of an `iced_native` element with a default `Renderer`. -pub type Element<'a, Message, Renderer = crate::Renderer> = - crate::runtime::Element<'a, Message, Renderer>; diff --git a/src/error.rs b/src/error.rs index 0bfa3ff1..111bedf2 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,4 +1,6 @@ -use iced_futures::futures; +use crate::futures; +use crate::graphics; +use crate::shell; /// An error that occurred while running an application. #[derive(Debug, thiserror::Error)] @@ -13,19 +15,19 @@ pub enum Error { /// The application graphics context could not be created. #[error("the application graphics context could not be created")] - GraphicsCreationFailed(iced_graphics::Error), + GraphicsCreationFailed(graphics::Error), } -impl From<iced_winit::Error> for Error { - fn from(error: iced_winit::Error) -> Error { +impl From<shell::Error> for Error { + fn from(error: shell::Error) -> Error { match error { - iced_winit::Error::ExecutorCreationFailed(error) => { + shell::Error::ExecutorCreationFailed(error) => { Error::ExecutorCreationFailed(error) } - iced_winit::Error::WindowCreationFailed(error) => { + shell::Error::WindowCreationFailed(error) => { Error::WindowCreationFailed(Box::new(error)) } - iced_winit::Error::GraphicsCreationFailed(error) => { + shell::Error::GraphicsCreationFailed(error) => { Error::GraphicsCreationFailed(error) } } diff --git a/src/executor.rs b/src/executor.rs deleted file mode 100644 index 36ae274e..00000000 --- a/src/executor.rs +++ /dev/null @@ -1,14 +0,0 @@ -//! Choose your preferred executor to power your application. -pub use crate::runtime::Executor; - -/// A default cross-platform executor. -/// -/// - On native platforms, it will use: -/// - `iced_futures::backend::native::tokio` when the `tokio` feature is enabled. -/// - `iced_futures::backend::native::async-std` when the `async-std` feature is -/// enabled. -/// - `iced_futures::backend::native::smol` when the `smol` feature is enabled. -/// - `iced_futures::backend::native::thread_pool` otherwise. -/// -/// - On Wasm, it will use `iced_futures::backend::wasm::wasm_bindgen`. -pub type Default = iced_futures::backend::default::Executor; diff --git a/src/keyboard.rs b/src/keyboard.rs deleted file mode 100644 index 2134a66b..00000000 --- a/src/keyboard.rs +++ /dev/null @@ -1,2 +0,0 @@ -//! Listen and react to keyboard events. -pub use crate::runtime::keyboard::{Event, KeyCode, Modifiers}; @@ -164,58 +164,139 @@ #![forbid(rust_2018_idioms, unsafe_code)] #![allow(clippy::inherent_to_string, clippy::type_complexity)] #![cfg_attr(docsrs, feature(doc_cfg))] +use iced_widget::graphics; +use iced_widget::renderer; +use iced_widget::style; +use iced_winit as shell; +use iced_winit::core; +use iced_winit::runtime; + +pub use iced_futures::futures; -mod element; mod error; -mod result; mod sandbox; pub mod application; -pub mod clipboard; -pub mod executor; -pub mod keyboard; -pub mod mouse; -pub mod overlay; pub mod settings; pub mod time; -pub mod touch; -pub mod widget; pub mod window; -#[cfg(all(not(feature = "glow"), feature = "wgpu"))] -use iced_winit as runtime; +#[cfg(feature = "advanced")] +pub mod advanced; + +pub use style::theme; + +pub use crate::core::alignment; +pub use crate::core::event; +pub use crate::core::{ + color, Alignment, Background, Color, ContentFit, Length, Padding, Pixels, + Point, Rectangle, Size, Vector, +}; +pub use crate::runtime::Command; + +pub mod clipboard { + //! Access the clipboard. + pub use crate::runtime::clipboard::{read, write}; +} + +pub mod executor { + //! Choose your preferred executor to power your application. + pub use iced_futures::Executor; + + /// A default cross-platform executor. + /// + /// - On native platforms, it will use: + /// - `iced_futures::backend::native::tokio` when the `tokio` feature is enabled. + /// - `iced_futures::backend::native::async-std` when the `async-std` feature is + /// enabled. + /// - `iced_futures::backend::native::smol` when the `smol` feature is enabled. + /// - `iced_futures::backend::native::thread_pool` otherwise. + /// + /// - On Wasm, it will use `iced_futures::backend::wasm::wasm_bindgen`. + pub type Default = iced_futures::backend::default::Executor; +} + +pub mod font { + //! Load and use fonts. + pub use crate::core::font::*; + pub use crate::runtime::font::*; +} + +pub mod keyboard { + //! Listen and react to keyboard events. + pub use crate::core::keyboard::{Event, KeyCode, Modifiers}; +} -#[cfg(feature = "glow")] -use iced_glutin as runtime; +pub mod mouse { + //! Listen and react to mouse events. + pub use crate::core::mouse::{Button, Event, Interaction, ScrollDelta}; +} -#[cfg(all(not(feature = "glow"), feature = "wgpu"))] -use iced_wgpu as renderer; +pub mod subscription { + //! Listen to external events in your application. + pub use iced_futures::subscription::{ + channel, events, events_with, run, run_with_id, unfold, Subscription, + }; +} -#[cfg(feature = "glow")] -use iced_glow as renderer; +#[cfg(feature = "system")] +pub mod system { + //! Retrieve system information. + pub use crate::runtime::system::Information; + pub use crate::shell::system::*; +} + +pub mod overlay { + //! Display interactive elements on top of other widgets. + + /// A generic [`Overlay`]. + /// + /// This is an alias of an `iced_native` element with a default `Renderer`. + /// + /// [`Overlay`]: iced_native::Overlay + pub type Element<'a, Message, Renderer = crate::Renderer> = + crate::core::overlay::Element<'a, Message, Renderer>; + + pub use iced_widget::overlay::*; +} + +pub mod touch { + //! Listen and react to touch events. + pub use crate::core::touch::{Event, Finger}; +} -pub use iced_native::theme; -pub use runtime::event; -pub use runtime::subscription; +pub mod widget { + //! Use the built-in widgets or create your own. + pub use iced_widget::*; + + // We hide the re-exported modules by `iced_widget` + mod core {} + mod graphics {} + mod native {} + mod renderer {} + mod style {} +} pub use application::Application; -pub use element::Element; pub use error::Error; pub use event::Event; pub use executor::Executor; -pub use renderer::Renderer; -pub use result::Result; +pub use font::Font; pub use sandbox::Sandbox; pub use settings::Settings; pub use subscription::Subscription; pub use theme::Theme; -pub use runtime::alignment; -pub use runtime::futures; -pub use runtime::{ - color, Alignment, Background, Color, Command, ContentFit, Font, Length, - Padding, Point, Rectangle, Size, Vector, -}; +/// The default renderer. +pub type Renderer<Theme = style::Theme> = renderer::Renderer<Theme>; -#[cfg(feature = "system")] -pub use runtime::system; +/// A generic widget. +/// +/// This is an alias of an `iced_native` element with a default `Renderer`. +pub type Element<'a, Message, Renderer = crate::Renderer> = + crate::core::Element<'a, Message, Renderer>; + +/// The result of running an [`Application`]. +/// +/// [`Application`]: crate::Application +pub type Result = std::result::Result<(), Error>; diff --git a/src/mouse.rs b/src/mouse.rs deleted file mode 100644 index d61ed09a..00000000 --- a/src/mouse.rs +++ /dev/null @@ -1,2 +0,0 @@ -//! Listen and react to mouse events. -pub use crate::runtime::mouse::{Button, Event, Interaction, ScrollDelta}; diff --git a/src/overlay.rs b/src/overlay.rs deleted file mode 100644 index c0f4c492..00000000 --- a/src/overlay.rs +++ /dev/null @@ -1,18 +0,0 @@ -//! Display interactive elements on top of other widgets. - -/// A generic [`Overlay`]. -/// -/// This is an alias of an `iced_native` element with a default `Renderer`. -/// -/// [`Overlay`]: iced_native::Overlay -pub type Element<'a, Message, Renderer = crate::Renderer> = - iced_native::overlay::Element<'a, Message, Renderer>; - -pub mod menu { - //! Build and show dropdown menus. - pub use iced_native::overlay::menu::{Appearance, State, StyleSheet}; - - /// A widget that produces a message when clicked. - pub type Menu<'a, Message, Renderer = crate::Renderer> = - iced_native::overlay::Menu<'a, Message, Renderer>; -} diff --git a/src/result.rs b/src/result.rs deleted file mode 100644 index ef565bd6..00000000 --- a/src/result.rs +++ /dev/null @@ -1,6 +0,0 @@ -use crate::Error; - -/// The result of running an [`Application`]. -/// -/// [`Application`]: crate::Application -pub type Result = std::result::Result<(), Error>; diff --git a/src/settings.rs b/src/settings.rs index 0eb3e62d..0dd46584 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -1,5 +1,6 @@ //! Configure your application. use crate::window; +use crate::Font; /// The settings of an application. #[derive(Debug, Clone)] @@ -20,23 +21,16 @@ pub struct Settings<Flags> { /// [`Application`]: crate::Application pub flags: Flags, - /// The bytes of the font that will be used by default. + /// The default [`Font`] to be used. /// - /// If `None` is provided, a default system font will be chosen. - // TODO: Add `name` for web compatibility - pub default_font: Option<&'static [u8]>, + /// By default, it uses [`Font::SansSerif`]. + pub default_font: Font, /// The text size that will be used by default. /// - /// The default value is `20.0`. + /// The default value is `16.0`. pub default_text_size: f32, - /// If enabled, spread text workload in multiple threads when multiple cores - /// are available. - /// - /// By default, it is disabled. - pub text_multithreading: bool, - /// If set to true, the renderer will try to perform antialiasing for some /// primitives. /// @@ -55,15 +49,6 @@ pub struct Settings<Flags> { /// /// [`Application`]: crate::Application pub exit_on_close_request: bool, - - /// Whether the [`Application`] should try to build the context - /// using OpenGL ES first then OpenGL. - /// - /// By default, it is disabled. - /// **Note:** Only works for the `glow` backend. - /// - /// [`Application`]: crate::Application - pub try_opengles_first: bool, } impl<Flags> Settings<Flags> { @@ -79,10 +64,8 @@ impl<Flags> Settings<Flags> { window: default_settings.window, default_font: default_settings.default_font, default_text_size: default_settings.default_text_size, - text_multithreading: default_settings.text_multithreading, antialiasing: default_settings.antialiasing, exit_on_close_request: default_settings.exit_on_close_request, - try_opengles_first: default_settings.try_opengles_first, } } } @@ -97,11 +80,9 @@ where window: Default::default(), flags: Default::default(), default_font: Default::default(), - default_text_size: 20.0, - text_multithreading: false, + default_text_size: 16.0, antialiasing: false, exit_on_close_request: true, - try_opengles_first: false, } } } @@ -113,7 +94,6 @@ impl<Flags> From<Settings<Flags>> for iced_winit::Settings<Flags> { window: settings.window.into(), flags: settings.flags, exit_on_close_request: settings.exit_on_close_request, - try_opengles_first: settings.try_opengles_first, } } } diff --git a/src/touch.rs b/src/touch.rs index 0b77c386..f2bdfca8 100644 --- a/src/touch.rs +++ b/src/touch.rs @@ -1,2 +1,2 @@ //! Listen and react to touch events. -pub use crate::runtime::touch::{Event, Finger}; +pub use crate::core::touch::{Event, Finger}; diff --git a/src/widget.rs b/src/widget.rs deleted file mode 100644 index 1163904d..00000000 --- a/src/widget.rs +++ /dev/null @@ -1,246 +0,0 @@ -//! Display information and interactive controls in your application. -pub use iced_native::widget::helpers::*; - -pub use iced_native::{column, row}; - -/// A container that distributes its contents vertically. -pub type Column<'a, Message, Renderer = crate::Renderer> = - iced_native::widget::Column<'a, Message, Renderer>; - -/// A container that distributes its contents horizontally. -pub type Row<'a, Message, Renderer = crate::Renderer> = - iced_native::widget::Row<'a, Message, Renderer>; - -pub mod text { - //! Write some text for your users to read. - pub use iced_native::widget::text::{Appearance, StyleSheet}; - - /// A paragraph of text. - pub type Text<'a, Renderer = crate::Renderer> = - iced_native::widget::Text<'a, Renderer>; -} - -pub mod button { - //! Allow your users to perform actions by pressing a button. - pub use iced_native::widget::button::{Appearance, StyleSheet}; - - /// A widget that produces a message when clicked. - pub type Button<'a, Message, Renderer = crate::Renderer> = - iced_native::widget::Button<'a, Message, Renderer>; -} - -pub mod checkbox { - //! Show toggle controls using checkboxes. - pub use iced_native::widget::checkbox::{Appearance, Icon, StyleSheet}; - - /// A box that can be checked. - pub type Checkbox<'a, Message, Renderer = crate::Renderer> = - iced_native::widget::Checkbox<'a, Message, Renderer>; -} - -pub mod container { - //! Decorate content and apply alignment. - pub use iced_native::widget::container::{Appearance, StyleSheet}; - - /// An element decorating some content. - pub type Container<'a, Message, Renderer = crate::Renderer> = - iced_native::widget::Container<'a, Message, Renderer>; -} - -pub mod mouse_area { - //! Intercept mouse events on a widget. - - /// A container intercepting mouse events. - pub type MouseArea<'a, Message, Renderer = crate::Renderer> = - iced_native::widget::MouseArea<'a, Message, Renderer>; -} - -pub mod pane_grid { - //! Let your users split regions of your application and organize layout dynamically. - //! - //! [](https://gfycat.com/mixedflatjellyfish) - //! - //! # Example - //! The [`pane_grid` example] showcases how to use a [`PaneGrid`] with resizing, - //! drag and drop, and hotkey support. - //! - //! [`pane_grid` example]: https://github.com/iced-rs/iced/tree/0.9/examples/pane_grid - pub use iced_native::widget::pane_grid::{ - Axis, Configuration, Direction, DragEvent, Line, Node, Pane, - ResizeEvent, Split, State, StyleSheet, - }; - - /// A collection of panes distributed using either vertical or horizontal splits - /// to completely fill the space available. - /// - /// [](https://gfycat.com/mixedflatjellyfish) - pub type PaneGrid<'a, Message, Renderer = crate::Renderer> = - iced_native::widget::PaneGrid<'a, Message, Renderer>; - - /// The content of a [`Pane`]. - pub type Content<'a, Message, Renderer = crate::Renderer> = - iced_native::widget::pane_grid::Content<'a, Message, Renderer>; - - /// The title bar of a [`Pane`]. - pub type TitleBar<'a, Message, Renderer = crate::Renderer> = - iced_native::widget::pane_grid::TitleBar<'a, Message, Renderer>; -} - -pub mod pick_list { - //! Display a dropdown list of selectable values. - pub use iced_native::widget::pick_list::{ - Appearance, Handle, Icon, StyleSheet, - }; - - /// A widget allowing the selection of a single value from a list of options. - pub type PickList<'a, T, Message, Renderer = crate::Renderer> = - iced_native::widget::PickList<'a, T, Message, Renderer>; -} - -pub mod radio { - //! Create choices using radio buttons. - pub use iced_native::widget::radio::{Appearance, StyleSheet}; - - /// A circular button representing a choice. - pub type Radio<Message, Renderer = crate::Renderer> = - iced_native::widget::Radio<Message, Renderer>; -} - -pub mod scrollable { - //! Navigate an endless amount of content with a scrollbar. - pub use iced_native::widget::scrollable::{ - scroll_to, snap_to, style::Scrollbar, style::Scroller, AbsoluteOffset, - Id, Properties, RelativeOffset, StyleSheet, Viewport, - }; - - /// A widget that can vertically display an infinite amount of content - /// with a scrollbar. - pub type Scrollable<'a, Message, Renderer = crate::Renderer> = - iced_native::widget::Scrollable<'a, Message, Renderer>; -} - -pub mod toggler { - //! Show toggle controls using togglers. - pub use iced_native::widget::toggler::{Appearance, StyleSheet}; - - /// A toggler widget. - pub type Toggler<'a, Message, Renderer = crate::Renderer> = - iced_native::widget::Toggler<'a, Message, Renderer>; -} - -pub mod text_input { - //! Display fields that can be filled with text. - pub use iced_native::widget::text_input::{ - focus, move_cursor_to, move_cursor_to_end, move_cursor_to_front, - select_all, Appearance, Icon, Id, Side, StyleSheet, - }; - - /// A field that can be filled with text. - pub type TextInput<'a, Message, Renderer = crate::Renderer> = - iced_native::widget::TextInput<'a, Message, Renderer>; -} - -pub mod tooltip { - //! Display a widget over another. - pub use iced_native::widget::tooltip::Position; - - /// A widget allowing the selection of a single value from a list of options. - pub type Tooltip<'a, Message, Renderer = crate::Renderer> = - iced_native::widget::Tooltip<'a, Message, Renderer>; -} - -pub use iced_native::widget::progress_bar; -pub use iced_native::widget::rule; -pub use iced_native::widget::slider; -pub use iced_native::widget::vertical_slider; -pub use iced_native::widget::Space; - -pub use button::Button; -pub use checkbox::Checkbox; -pub use container::Container; -pub use pane_grid::PaneGrid; -pub use pick_list::PickList; -pub use progress_bar::ProgressBar; -pub use radio::Radio; -pub use rule::Rule; -pub use scrollable::Scrollable; -pub use slider::Slider; -pub use text::Text; -pub use text_input::TextInput; -pub use toggler::Toggler; -pub use tooltip::Tooltip; -pub use vertical_slider::VerticalSlider; - -#[cfg(feature = "canvas")] -#[cfg_attr(docsrs, doc(cfg(feature = "canvas")))] -pub use iced_graphics::widget::canvas; - -#[cfg(feature = "canvas")] -#[cfg_attr(docsrs, doc(cfg(feature = "canvas")))] -/// Creates a new [`Canvas`]. -pub fn canvas<P, Message, Theme>(program: P) -> Canvas<Message, Theme, P> -where - P: canvas::Program<Message, Theme>, -{ - Canvas::new(program) -} - -#[cfg(feature = "image")] -#[cfg_attr(docsrs, doc(cfg(feature = "image")))] -pub mod image { - //! Display images in your user interface. - pub use iced_native::image::Handle; - - /// A frame that displays an image. - pub type Image = iced_native::widget::Image<Handle>; - - pub use iced_native::widget::image::viewer; - pub use viewer::Viewer; -} - -#[cfg(feature = "qr_code")] -#[cfg_attr(docsrs, doc(cfg(feature = "qr_code")))] -pub use iced_graphics::widget::qr_code; - -#[cfg(feature = "svg")] -#[cfg_attr(docsrs, doc(cfg(feature = "svg")))] -pub mod svg { - //! Display vector graphics in your application. - pub use iced_native::svg::Handle; - pub use iced_native::widget::svg::{Appearance, StyleSheet, Svg}; -} - -#[cfg(feature = "canvas")] -#[cfg_attr(docsrs, doc(cfg(feature = "canvas")))] -pub use canvas::Canvas; - -#[cfg(feature = "image")] -#[cfg_attr(docsrs, doc(cfg(feature = "image")))] -pub use image::Image; - -#[cfg(feature = "qr_code")] -#[cfg_attr(docsrs, doc(cfg(feature = "qr_code")))] -pub use qr_code::QRCode; - -#[cfg(feature = "svg")] -#[cfg_attr(docsrs, doc(cfg(feature = "svg")))] -pub use svg::Svg; - -use crate::Command; -use iced_native::widget::operation; - -/// Focuses the previous focusable widget. -pub fn focus_previous<Message>() -> Command<Message> -where - Message: 'static, -{ - Command::widget(operation::focusable::focus_previous()) -} - -/// Focuses the next focusable widget. -pub fn focus_next<Message>() -> Command<Message> -where - Message: 'static, -{ - Command::widget(operation::focusable::focus_next()) -} diff --git a/src/window.rs b/src/window.rs index aba4bce8..e4601575 100644 --- a/src/window.rs +++ b/src/window.rs @@ -8,4 +8,5 @@ pub use icon::Icon; pub use position::Position; pub use settings::{PlatformSpecific, Settings}; +pub use crate::core::window::*; pub use crate::runtime::window::*; diff --git a/src/window/icon.rs b/src/window/icon.rs index e09dd048..b67b2ea3 100644 --- a/src/window/icon.rs +++ b/src/window/icon.rs @@ -1,7 +1,7 @@ //! Attach an icon to the window of your application. -pub use crate::runtime::window::icon::*; +pub use crate::core::window::icon::*; -use crate::runtime::window::icon; +use crate::core::window::icon; use std::io; diff --git a/style/src/lib.rs b/style/src/lib.rs index 59eb1eb8..286ff9db 100644 --- a/style/src/lib.rs +++ b/style/src/lib.rs @@ -18,7 +18,7 @@ #![deny(missing_docs, unused_results)] #![forbid(unsafe_code, rust_2018_idioms)] #![allow(clippy::inherent_to_string, clippy::type_complexity)] -pub use iced_core::{Background, Color}; +pub use iced_core as core; pub mod application; pub mod button; @@ -33,7 +33,6 @@ pub mod rule; pub mod scrollable; pub mod slider; pub mod svg; -pub mod text; pub mod text_input; pub mod theme; pub mod toggler; diff --git a/style/src/text.rs b/style/src/text.rs deleted file mode 100644 index f31c2306..00000000 --- a/style/src/text.rs +++ /dev/null @@ -1,20 +0,0 @@ -//! Change the appearance of text. -use iced_core::Color; - -/// The style sheet of some text. -pub trait StyleSheet { - /// The supported style of the [`StyleSheet`]. - type Style: Default + Copy; - - /// Produces the [`Appearance`] of some text. - fn appearance(&self, style: Self::Style) -> Appearance; -} - -/// The apperance of some text. -#[derive(Debug, Clone, Copy, Default)] -pub struct Appearance { - /// The [`Color`] of the text. - /// - /// The default, `None`, means using the inherited color. - pub color: Option<Color>, -} diff --git a/style/src/theme.rs b/style/src/theme.rs index 6711fd72..d9893bcf 100644 --- a/style/src/theme.rs +++ b/style/src/theme.rs @@ -8,6 +8,7 @@ use crate::application; use crate::button; use crate::checkbox; use crate::container; +use crate::core::widget::text; use crate::menu; use crate::pane_grid; use crate::pick_list; @@ -17,7 +18,6 @@ use crate::rule; use crate::scrollable; use crate::slider; use crate::svg; -use crate::text; use crate::text_input; use crate::toggler; diff --git a/tiny_skia/Cargo.toml b/tiny_skia/Cargo.toml new file mode 100644 index 00000000..400eee6a --- /dev/null +++ b/tiny_skia/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "iced_tiny_skia" +version = "0.1.0" +edition = "2021" + +[features] +image = ["iced_graphics/image"] +svg = ["resvg"] +geometry = ["iced_graphics/geometry"] + +[dependencies] +raw-window-handle = "0.5" +softbuffer = "0.2" +tiny-skia = "0.9" +bytemuck = "1" +rustc-hash = "1.1" +kurbo = "0.9" +log = "0.4" + +[dependencies.iced_graphics] +version = "0.8" +path = "../graphics" +features = ["tiny-skia"] + +[dependencies.cosmic-text] +git = "https://github.com/hecrj/cosmic-text.git" +rev = "b85d6a4f2376f8a8a7dadc0f8bcb89d4db10a1c9" + +[dependencies.twox-hash] +version = "1.6" +default-features = false + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies.twox-hash] +version = "1.6.1" +features = ["std"] + +[dependencies.resvg] +version = "0.32" +optional = true diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs new file mode 100644 index 00000000..d481bacd --- /dev/null +++ b/tiny_skia/src/backend.rs @@ -0,0 +1,698 @@ +use crate::core::text; +use crate::core::{Background, Color, Font, Point, Rectangle, Size, Vector}; +use crate::graphics::backend; +use crate::graphics::{Primitive, Viewport}; +use crate::Settings; + +use std::borrow::Cow; + +pub struct Backend { + default_font: Font, + default_text_size: f32, + text_pipeline: crate::text::Pipeline, + + #[cfg(feature = "image")] + raster_pipeline: crate::raster::Pipeline, + + #[cfg(feature = "svg")] + vector_pipeline: crate::vector::Pipeline, +} + +impl Backend { + pub fn new(settings: Settings) -> Self { + Self { + default_font: settings.default_font, + default_text_size: settings.default_text_size, + text_pipeline: crate::text::Pipeline::new(), + + #[cfg(feature = "image")] + raster_pipeline: crate::raster::Pipeline::new(), + + #[cfg(feature = "svg")] + vector_pipeline: crate::vector::Pipeline::new(), + } + } + + pub fn draw<T: AsRef<str>>( + &mut self, + pixels: &mut tiny_skia::PixmapMut<'_>, + clip_mask: &mut tiny_skia::Mask, + primitives: &[Primitive], + viewport: &Viewport, + damage: &[Rectangle], + background_color: Color, + overlay: &[T], + ) { + let physical_size = viewport.physical_size(); + let scale_factor = viewport.scale_factor() as f32; + + if !overlay.is_empty() { + let path = tiny_skia::PathBuilder::from_rect( + tiny_skia::Rect::from_xywh( + 0.0, + 0.0, + physical_size.width as f32, + physical_size.height as f32, + ) + .expect("Create damage rectangle"), + ); + + pixels.fill_path( + &path, + &tiny_skia::Paint { + shader: tiny_skia::Shader::SolidColor(into_color(Color { + a: 0.1, + ..background_color + })), + anti_alias: false, + ..Default::default() + }, + tiny_skia::FillRule::default(), + tiny_skia::Transform::identity(), + None, + ); + } + + for ®ion in damage { + let path = tiny_skia::PathBuilder::from_rect( + tiny_skia::Rect::from_xywh( + region.x, + region.y, + region.width, + region.height, + ) + .expect("Create damage rectangle"), + ); + + pixels.fill_path( + &path, + &tiny_skia::Paint { + shader: tiny_skia::Shader::SolidColor(into_color( + background_color, + )), + anti_alias: false, + ..Default::default() + }, + tiny_skia::FillRule::default(), + tiny_skia::Transform::identity(), + None, + ); + + adjust_clip_mask(clip_mask, region); + + for primitive in primitives { + self.draw_primitive( + primitive, + pixels, + clip_mask, + region, + scale_factor, + Vector::ZERO, + ); + } + + if !overlay.is_empty() { + pixels.stroke_path( + &path, + &tiny_skia::Paint { + shader: tiny_skia::Shader::SolidColor(into_color( + Color::from_rgb(1.0, 0.0, 0.0), + )), + anti_alias: false, + ..tiny_skia::Paint::default() + }, + &tiny_skia::Stroke { + width: 1.0, + ..tiny_skia::Stroke::default() + }, + tiny_skia::Transform::identity(), + None, + ); + } + } + + self.text_pipeline.trim_cache(); + + #[cfg(feature = "image")] + self.raster_pipeline.trim_cache(); + + #[cfg(feature = "svg")] + self.vector_pipeline.trim_cache(); + } + + fn draw_primitive( + &mut self, + primitive: &Primitive, + pixels: &mut tiny_skia::PixmapMut<'_>, + clip_mask: &mut tiny_skia::Mask, + clip_bounds: Rectangle, + scale_factor: f32, + translation: Vector, + ) { + match primitive { + Primitive::Quad { + bounds, + background, + border_radius, + border_width, + border_color, + } => { + let physical_bounds = (*bounds + translation) * scale_factor; + + if !clip_bounds.intersects(&physical_bounds) { + return; + } + + let clip_mask = (!physical_bounds.is_within(&clip_bounds)) + .then_some(clip_mask as &_); + + let transform = tiny_skia::Transform::from_translate( + translation.x, + translation.y, + ) + .post_scale(scale_factor, scale_factor); + + let path = rounded_rectangle(*bounds, *border_radius); + + pixels.fill_path( + &path, + &tiny_skia::Paint { + shader: match background { + Background::Color(color) => { + tiny_skia::Shader::SolidColor(into_color( + *color, + )) + } + }, + anti_alias: true, + ..tiny_skia::Paint::default() + }, + tiny_skia::FillRule::EvenOdd, + transform, + clip_mask, + ); + + if *border_width > 0.0 { + pixels.stroke_path( + &path, + &tiny_skia::Paint { + shader: tiny_skia::Shader::SolidColor(into_color( + *border_color, + )), + anti_alias: true, + ..tiny_skia::Paint::default() + }, + &tiny_skia::Stroke { + width: *border_width, + ..tiny_skia::Stroke::default() + }, + transform, + clip_mask, + ); + } + } + Primitive::Text { + content, + bounds, + color, + size, + line_height, + font, + horizontal_alignment, + vertical_alignment, + shaping, + } => { + let physical_bounds = + (primitive.bounds() + translation) * scale_factor; + + if !clip_bounds.intersects(&physical_bounds) { + return; + } + + let clip_mask = (!physical_bounds.is_within(&clip_bounds)) + .then_some(clip_mask as &_); + + self.text_pipeline.draw( + content, + *bounds + translation, + *color, + *size, + *line_height, + *font, + *horizontal_alignment, + *vertical_alignment, + *shaping, + scale_factor, + pixels, + clip_mask, + ); + } + #[cfg(feature = "image")] + Primitive::Image { handle, bounds } => { + let physical_bounds = (*bounds + translation) * scale_factor; + + if !clip_bounds.intersects(&physical_bounds) { + return; + } + + let clip_mask = (!physical_bounds.is_within(&clip_bounds)) + .then_some(clip_mask as &_); + + let transform = tiny_skia::Transform::from_translate( + translation.x, + translation.y, + ) + .post_scale(scale_factor, scale_factor); + + self.raster_pipeline + .draw(handle, *bounds, pixels, transform, clip_mask); + } + #[cfg(feature = "svg")] + Primitive::Svg { + handle, + bounds, + color, + } => { + let physical_bounds = (*bounds + translation) * scale_factor; + + if !clip_bounds.intersects(&physical_bounds) { + return; + } + + let clip_mask = (!physical_bounds.is_within(&clip_bounds)) + .then_some(clip_mask as &_); + + self.vector_pipeline.draw( + handle, + *color, + (*bounds + translation) * scale_factor, + pixels, + clip_mask, + ); + } + Primitive::Fill { + path, + paint, + rule, + transform, + } => { + let bounds = path.bounds(); + + let physical_bounds = (Rectangle { + x: bounds.x(), + y: bounds.y(), + width: bounds.width(), + height: bounds.height(), + } + translation) + * scale_factor; + + if !clip_bounds.intersects(&physical_bounds) { + return; + } + + let clip_mask = (!physical_bounds.is_within(&clip_bounds)) + .then_some(clip_mask as &_); + + pixels.fill_path( + path, + paint, + *rule, + transform + .post_translate(translation.x, translation.y) + .post_scale(scale_factor, scale_factor), + clip_mask, + ); + } + Primitive::Stroke { + path, + paint, + stroke, + transform, + } => { + let bounds = path.bounds(); + + let physical_bounds = (Rectangle { + x: bounds.x(), + y: bounds.y(), + width: bounds.width(), + height: bounds.height(), + } + translation) + * scale_factor; + + if !clip_bounds.intersects(&physical_bounds) { + return; + } + + let clip_mask = (!physical_bounds.is_within(&clip_bounds)) + .then_some(clip_mask as &_); + + pixels.stroke_path( + path, + paint, + stroke, + transform + .post_translate(translation.x, translation.y) + .post_scale(scale_factor, scale_factor), + clip_mask, + ); + } + Primitive::Group { primitives } => { + for primitive in primitives { + self.draw_primitive( + primitive, + pixels, + clip_mask, + clip_bounds, + scale_factor, + translation, + ); + } + } + Primitive::Translate { + translation: offset, + content, + } => { + self.draw_primitive( + content, + pixels, + clip_mask, + clip_bounds, + scale_factor, + translation + *offset, + ); + } + Primitive::Clip { bounds, content } => { + let bounds = (*bounds + translation) * scale_factor; + + if bounds == clip_bounds { + self.draw_primitive( + content, + pixels, + clip_mask, + bounds, + scale_factor, + translation, + ); + } else if let Some(bounds) = clip_bounds.intersection(&bounds) { + if bounds.x + bounds.width <= 0.0 + || bounds.y + bounds.height <= 0.0 + || bounds.x as u32 >= pixels.width() + || bounds.y as u32 >= pixels.height() + || bounds.width <= 1.0 + || bounds.height <= 1.0 + { + return; + } + + adjust_clip_mask(clip_mask, bounds); + + self.draw_primitive( + content, + pixels, + clip_mask, + bounds, + scale_factor, + translation, + ); + + adjust_clip_mask(clip_mask, clip_bounds); + } + } + Primitive::Cache { content } => { + self.draw_primitive( + content, + pixels, + clip_mask, + clip_bounds, + scale_factor, + translation, + ); + } + Primitive::SolidMesh { .. } | Primitive::GradientMesh { .. } => { + // Not supported! + // TODO: Draw a placeholder (?) + log::warn!( + "Unsupported primitive in `iced_tiny_skia`: {:?}", + primitive + ); + } + _ => { + // Not supported! + log::warn!( + "Unsupported primitive in `iced_tiny_skia`: {:?}", + primitive + ); + } + } + } +} + +fn into_color(color: Color) -> tiny_skia::Color { + tiny_skia::Color::from_rgba(color.b, color.g, color.r, color.a) + .expect("Convert color from iced to tiny_skia") +} + +fn rounded_rectangle( + bounds: Rectangle, + border_radius: [f32; 4], +) -> tiny_skia::Path { + let [top_left, top_right, bottom_right, bottom_left] = border_radius; + + if top_left == 0.0 + && top_right == 0.0 + && bottom_right == 0.0 + && bottom_left == 0.0 + { + return tiny_skia::PathBuilder::from_rect( + tiny_skia::Rect::from_xywh( + bounds.x, + bounds.y, + bounds.width, + bounds.height, + ) + .expect("Build quad rectangle"), + ); + } + + if top_left == top_right + && top_left == bottom_right + && top_left == bottom_left + && top_left == bounds.width / 2.0 + && top_left == bounds.height / 2.0 + { + return tiny_skia::PathBuilder::from_circle( + bounds.x + bounds.width / 2.0, + bounds.y + bounds.height / 2.0, + top_left, + ) + .expect("Build circle path"); + } + + let mut builder = tiny_skia::PathBuilder::new(); + + builder.move_to(bounds.x + top_left, bounds.y); + builder.line_to(bounds.x + bounds.width - top_right, bounds.y); + + if top_right > 0.0 { + arc_to( + &mut builder, + bounds.x + bounds.width - top_right, + bounds.y, + bounds.x + bounds.width, + bounds.y + top_right, + top_right, + ); + } + + maybe_line_to( + &mut builder, + bounds.x + bounds.width, + bounds.y + bounds.height - bottom_right, + ); + + if bottom_right > 0.0 { + arc_to( + &mut builder, + bounds.x + bounds.width, + bounds.y + bounds.height - bottom_right, + bounds.x + bounds.width - bottom_right, + bounds.y + bounds.height, + bottom_right, + ); + } + + maybe_line_to( + &mut builder, + bounds.x + bottom_left, + bounds.y + bounds.height, + ); + + if bottom_left > 0.0 { + arc_to( + &mut builder, + bounds.x + bottom_left, + bounds.y + bounds.height, + bounds.x, + bounds.y + bounds.height - bottom_left, + bottom_left, + ); + } + + maybe_line_to(&mut builder, bounds.x, bounds.y + top_left); + + if top_left > 0.0 { + arc_to( + &mut builder, + bounds.x, + bounds.y + top_left, + bounds.x + top_left, + bounds.y, + top_left, + ); + } + + builder.finish().expect("Build rounded rectangle path") +} + +fn maybe_line_to(path: &mut tiny_skia::PathBuilder, x: f32, y: f32) { + if path.last_point() != Some(tiny_skia::Point { x, y }) { + path.line_to(x, y); + } +} + +fn arc_to( + path: &mut tiny_skia::PathBuilder, + x_from: f32, + y_from: f32, + x_to: f32, + y_to: f32, + radius: f32, +) { + let svg_arc = kurbo::SvgArc { + from: kurbo::Point::new(f64::from(x_from), f64::from(y_from)), + to: kurbo::Point::new(f64::from(x_to), f64::from(y_to)), + radii: kurbo::Vec2::new(f64::from(radius), f64::from(radius)), + x_rotation: 0.0, + large_arc: false, + sweep: true, + }; + + match kurbo::Arc::from_svg_arc(&svg_arc) { + Some(arc) => { + arc.to_cubic_beziers(0.1, |p1, p2, p| { + path.cubic_to( + p1.x as f32, + p1.y as f32, + p2.x as f32, + p2.y as f32, + p.x as f32, + p.y as f32, + ); + }); + } + None => { + path.line_to(x_to, y_to); + } + } +} + +fn adjust_clip_mask(clip_mask: &mut tiny_skia::Mask, bounds: Rectangle) { + clip_mask.clear(); + + let path = { + let mut builder = tiny_skia::PathBuilder::new(); + builder.push_rect(bounds.x, bounds.y, bounds.width, bounds.height); + + builder.finish().unwrap() + }; + + clip_mask.fill_path( + &path, + tiny_skia::FillRule::EvenOdd, + false, + tiny_skia::Transform::default(), + ); +} + +impl iced_graphics::Backend for Backend { + fn trim_measurements(&mut self) { + self.text_pipeline.trim_measurement_cache(); + } +} + +impl backend::Text for Backend { + const ICON_FONT: Font = Font::with_name("Iced-Icons"); + const CHECKMARK_ICON: char = '\u{f00c}'; + const ARROW_DOWN_ICON: char = '\u{e800}'; + + fn default_font(&self) -> Font { + self.default_font + } + + fn default_size(&self) -> f32 { + self.default_text_size + } + + fn measure( + &self, + contents: &str, + size: f32, + line_height: text::LineHeight, + font: Font, + bounds: Size, + shaping: text::Shaping, + ) -> (f32, f32) { + self.text_pipeline.measure( + contents, + size, + line_height, + font, + bounds, + shaping, + ) + } + + fn hit_test( + &self, + contents: &str, + size: f32, + line_height: text::LineHeight, + font: Font, + bounds: Size, + shaping: text::Shaping, + point: Point, + nearest_only: bool, + ) -> Option<text::Hit> { + self.text_pipeline.hit_test( + contents, + size, + line_height, + font, + bounds, + shaping, + point, + nearest_only, + ) + } + + fn load_font(&mut self, font: Cow<'static, [u8]>) { + self.text_pipeline.load_font(font); + } +} + +#[cfg(feature = "image")] +impl backend::Image for Backend { + fn dimensions(&self, handle: &crate::core::image::Handle) -> Size<u32> { + self.raster_pipeline.dimensions(handle) + } +} + +#[cfg(feature = "svg")] +impl backend::Svg for Backend { + fn viewport_dimensions( + &self, + handle: &crate::core::svg::Handle, + ) -> Size<u32> { + self.vector_pipeline.viewport_dimensions(handle) + } +} diff --git a/tiny_skia/src/geometry.rs b/tiny_skia/src/geometry.rs new file mode 100644 index 00000000..a1fd7b60 --- /dev/null +++ b/tiny_skia/src/geometry.rs @@ -0,0 +1,300 @@ +use crate::core::Gradient; +use crate::core::{Point, Rectangle, Size, Vector}; +use crate::graphics::geometry::fill::{self, Fill}; +use crate::graphics::geometry::stroke::{self, Stroke}; +use crate::graphics::geometry::{Path, Style, Text}; +use crate::graphics::Primitive; + +pub struct Frame { + size: Size, + transform: tiny_skia::Transform, + stack: Vec<tiny_skia::Transform>, + primitives: Vec<Primitive>, +} + +impl Frame { + pub fn new(size: Size) -> Self { + Self { + size, + transform: tiny_skia::Transform::identity(), + stack: Vec::new(), + primitives: Vec::new(), + } + } + + pub fn width(&self) -> f32 { + self.size.width + } + + pub fn height(&self) -> f32 { + self.size.height + } + + pub fn size(&self) -> Size { + self.size + } + + pub fn center(&self) -> Point { + Point::new(self.size.width / 2.0, self.size.height / 2.0) + } + + pub fn fill(&mut self, path: &Path, fill: impl Into<Fill>) { + let Some(path) = convert_path(path) else { return }; + let fill = fill.into(); + + self.primitives.push(Primitive::Fill { + path, + paint: into_paint(fill.style), + rule: into_fill_rule(fill.rule), + transform: self.transform, + }); + } + + pub fn fill_rectangle( + &mut self, + top_left: Point, + size: Size, + fill: impl Into<Fill>, + ) { + let Some(path) = convert_path(&Path::rectangle(top_left, size)) else { return }; + let fill = fill.into(); + + self.primitives.push(Primitive::Fill { + path, + paint: tiny_skia::Paint { + anti_alias: false, + ..into_paint(fill.style) + }, + rule: into_fill_rule(fill.rule), + transform: self.transform, + }); + } + + pub fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>) { + let Some(path) = convert_path(path) else { return }; + + let stroke = stroke.into(); + let skia_stroke = into_stroke(&stroke); + + self.primitives.push(Primitive::Stroke { + path, + paint: into_paint(stroke.style), + stroke: skia_stroke, + transform: self.transform, + }); + } + + pub fn fill_text(&mut self, text: impl Into<Text>) { + let text = text.into(); + + let position = if self.transform.is_identity() { + text.position + } else { + let mut transformed = [tiny_skia::Point { + x: text.position.x, + y: text.position.y, + }]; + + self.transform.map_points(&mut transformed); + + Point::new(transformed[0].x, transformed[0].y) + }; + + // TODO: Use vectorial text instead of primitive + self.primitives.push(Primitive::Text { + content: text.content, + bounds: Rectangle { + x: position.x, + y: position.y, + width: f32::INFINITY, + height: f32::INFINITY, + }, + color: text.color, + size: text.size, + line_height: text.line_height, + font: text.font, + horizontal_alignment: text.horizontal_alignment, + vertical_alignment: text.vertical_alignment, + shaping: text.shaping, + }); + } + + pub fn push_transform(&mut self) { + self.stack.push(self.transform); + } + + pub fn pop_transform(&mut self) { + self.transform = self.stack.pop().expect("Pop transform"); + } + + pub fn clip(&mut self, frame: Self, at: Point) { + self.primitives.push(Primitive::Translate { + translation: Vector::new(at.x, at.y), + content: Box::new(frame.into_primitive()), + }); + } + + pub fn translate(&mut self, translation: Vector) { + self.transform = + self.transform.pre_translate(translation.x, translation.y); + } + + pub fn rotate(&mut self, angle: f32) { + self.transform = self + .transform + .pre_concat(tiny_skia::Transform::from_rotate(angle.to_degrees())); + } + + pub fn scale(&mut self, scale: f32) { + self.transform = self.transform.pre_scale(scale, scale); + } + + pub fn into_primitive(self) -> Primitive { + Primitive::Clip { + bounds: Rectangle::new(Point::ORIGIN, self.size), + content: Box::new(Primitive::Group { + primitives: self.primitives, + }), + } + } +} + +fn convert_path(path: &Path) -> Option<tiny_skia::Path> { + use iced_graphics::geometry::path::lyon_path; + + let mut builder = tiny_skia::PathBuilder::new(); + let mut last_point = Default::default(); + + for event in path.raw().iter() { + match event { + lyon_path::Event::Begin { at } => { + builder.move_to(at.x, at.y); + + last_point = at; + } + lyon_path::Event::Line { from, to } => { + if last_point != from { + builder.move_to(from.x, from.y); + } + + builder.line_to(to.x, to.y); + + last_point = to; + } + lyon_path::Event::Quadratic { from, ctrl, to } => { + if last_point != from { + builder.move_to(from.x, from.y); + } + + builder.quad_to(ctrl.x, ctrl.y, to.x, to.y); + + last_point = to; + } + lyon_path::Event::Cubic { + from, + ctrl1, + ctrl2, + to, + } => { + if last_point != from { + builder.move_to(from.x, from.y); + } + + builder + .cubic_to(ctrl1.x, ctrl1.y, ctrl2.x, ctrl2.y, to.x, to.y); + + last_point = to; + } + lyon_path::Event::End { close, .. } => { + if close { + builder.close(); + } + } + } + } + + let result = builder.finish(); + + #[cfg(debug_assertions)] + if result.is_none() { + log::warn!("Invalid path: {:?}", path.raw()); + } + + result +} + +pub fn into_paint(style: Style) -> tiny_skia::Paint<'static> { + tiny_skia::Paint { + shader: match style { + Style::Solid(color) => tiny_skia::Shader::SolidColor( + tiny_skia::Color::from_rgba(color.b, color.g, color.r, color.a) + .expect("Create color"), + ), + Style::Gradient(gradient) => match gradient { + Gradient::Linear(linear) => tiny_skia::LinearGradient::new( + tiny_skia::Point { + x: linear.start.x, + y: linear.start.y, + }, + tiny_skia::Point { + x: linear.end.x, + y: linear.end.y, + }, + linear + .color_stops + .into_iter() + .map(|stop| { + tiny_skia::GradientStop::new( + stop.offset, + tiny_skia::Color::from_rgba( + stop.color.b, + stop.color.g, + stop.color.r, + stop.color.a, + ) + .expect("Create color"), + ) + }) + .collect(), + tiny_skia::SpreadMode::Pad, + tiny_skia::Transform::identity(), + ) + .expect("Create linear gradient"), + }, + }, + anti_alias: true, + ..Default::default() + } +} + +pub fn into_fill_rule(rule: fill::Rule) -> tiny_skia::FillRule { + match rule { + fill::Rule::EvenOdd => tiny_skia::FillRule::EvenOdd, + fill::Rule::NonZero => tiny_skia::FillRule::Winding, + } +} + +pub fn into_stroke(stroke: &Stroke) -> tiny_skia::Stroke { + tiny_skia::Stroke { + width: stroke.width, + line_cap: match stroke.line_cap { + stroke::LineCap::Butt => tiny_skia::LineCap::Butt, + stroke::LineCap::Square => tiny_skia::LineCap::Square, + stroke::LineCap::Round => tiny_skia::LineCap::Round, + }, + line_join: match stroke.line_join { + stroke::LineJoin::Miter => tiny_skia::LineJoin::Miter, + stroke::LineJoin::Round => tiny_skia::LineJoin::Round, + stroke::LineJoin::Bevel => tiny_skia::LineJoin::Bevel, + }, + dash: if stroke.line_dash.segments.is_empty() { + None + } else { + tiny_skia::StrokeDash::new( + stroke.line_dash.segments.into(), + stroke.line_dash.offset as f32, + ) + }, + ..Default::default() + } +} diff --git a/tiny_skia/src/lib.rs b/tiny_skia/src/lib.rs new file mode 100644 index 00000000..83baef1c --- /dev/null +++ b/tiny_skia/src/lib.rs @@ -0,0 +1,26 @@ +pub mod window; + +mod backend; +mod settings; +mod text; + +#[cfg(feature = "image")] +mod raster; + +#[cfg(feature = "svg")] +mod vector; + +#[cfg(feature = "geometry")] +pub mod geometry; + +pub use iced_graphics as graphics; +pub use iced_graphics::core; + +pub use backend::Backend; +pub use settings::Settings; + +/// A [`tiny-skia`] graphics renderer for [`iced`]. +/// +/// [`tiny-skia`]: https://github.com/RazrFalcon/tiny-skia +/// [`iced`]: https://github.com/iced-rs/iced +pub type Renderer<Theme> = iced_graphics::Renderer<Backend, Theme>; diff --git a/tiny_skia/src/raster.rs b/tiny_skia/src/raster.rs new file mode 100644 index 00000000..3887ec8d --- /dev/null +++ b/tiny_skia/src/raster.rs @@ -0,0 +1,116 @@ +use crate::core::image as raster; +use crate::core::{Rectangle, Size}; +use crate::graphics; + +use rustc_hash::{FxHashMap, FxHashSet}; +use std::cell::RefCell; +use std::collections::hash_map; + +pub struct Pipeline { + cache: RefCell<Cache>, +} + +impl Pipeline { + pub fn new() -> Self { + Self { + cache: RefCell::new(Cache::default()), + } + } + + pub fn dimensions(&self, handle: &raster::Handle) -> Size<u32> { + if let Some(image) = self.cache.borrow_mut().allocate(handle) { + Size::new(image.width(), image.height()) + } else { + Size::new(0, 0) + } + } + + pub fn draw( + &mut self, + handle: &raster::Handle, + bounds: Rectangle, + pixels: &mut tiny_skia::PixmapMut<'_>, + transform: tiny_skia::Transform, + clip_mask: Option<&tiny_skia::Mask>, + ) { + if let Some(image) = self.cache.borrow_mut().allocate(handle) { + let width_scale = bounds.width / image.width() as f32; + let height_scale = bounds.height / image.height() as f32; + + let transform = transform.pre_scale(width_scale, height_scale); + + pixels.draw_pixmap( + (bounds.x / width_scale) as i32, + (bounds.y / height_scale) as i32, + image, + &tiny_skia::PixmapPaint { + quality: tiny_skia::FilterQuality::Bilinear, + ..Default::default() + }, + transform, + clip_mask, + ); + } + } + + pub fn trim_cache(&mut self) { + self.cache.borrow_mut().trim(); + } +} + +#[derive(Default)] +struct Cache { + entries: FxHashMap<u64, Option<Entry>>, + hits: FxHashSet<u64>, +} + +impl Cache { + pub fn allocate( + &mut self, + handle: &raster::Handle, + ) -> Option<tiny_skia::PixmapRef<'_>> { + let id = handle.id(); + + if let hash_map::Entry::Vacant(entry) = self.entries.entry(id) { + let image = graphics::image::load(handle).ok()?.into_rgba8(); + + let mut buffer = + vec![0u32; image.width() as usize * image.height() as usize]; + + for (i, pixel) in image.pixels().enumerate() { + let [r, g, b, a] = pixel.0; + + buffer[i] = tiny_skia::ColorU8::from_rgba(b, g, r, a) + .premultiply() + .get(); + } + + entry.insert(Some(Entry { + width: image.width(), + height: image.height(), + pixels: buffer, + })); + } + + self.hits.insert(id); + self.entries.get(&id).unwrap().as_ref().map(|entry| { + tiny_skia::PixmapRef::from_bytes( + bytemuck::cast_slice(&entry.pixels), + entry.width, + entry.height, + ) + .expect("Build pixmap from image bytes") + }) + } + + fn trim(&mut self) { + self.entries.retain(|key, _| self.hits.contains(key)); + self.hits.clear(); + } +} + +struct Entry { + width: u32, + height: u32, + pixels: Vec<u32>, +} diff --git a/tiny_skia/src/settings.rs b/tiny_skia/src/settings.rs new file mode 100644 index 00000000..abffbfe6 --- /dev/null +++ b/tiny_skia/src/settings.rs @@ -0,0 +1,24 @@ +use crate::core::Font; + +/// The settings of a [`Backend`]. +/// +/// [`Backend`]: crate::Backend +#[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: f32, +} + +impl Default for Settings { + fn default() -> Settings { + Settings { + default_font: Font::default(), + default_text_size: 16.0, + } + } +} diff --git a/tiny_skia/src/text.rs b/tiny_skia/src/text.rs new file mode 100644 index 00000000..a34c7317 --- /dev/null +++ b/tiny_skia/src/text.rs @@ -0,0 +1,463 @@ +use crate::core::alignment; +use crate::core::font::{self, Font}; +use crate::core::text::{Hit, LineHeight, Shaping}; +use crate::core::{Color, Pixels, Point, Rectangle, Size}; + +use rustc_hash::{FxHashMap, FxHashSet}; +use std::borrow::Cow; +use std::cell::RefCell; +use std::collections::hash_map; +use std::hash::{BuildHasher, Hash, Hasher}; +use std::sync::Arc; + +#[allow(missing_debug_implementations)] +pub struct Pipeline { + font_system: RefCell<cosmic_text::FontSystem>, + glyph_cache: GlyphCache, + measurement_cache: RefCell<Cache>, + render_cache: Cache, +} + +impl Pipeline { + pub fn new() -> Self { + Pipeline { + font_system: RefCell::new(cosmic_text::FontSystem::new_with_fonts( + [cosmic_text::fontdb::Source::Binary(Arc::new( + include_bytes!("../../wgpu/fonts/Iced-Icons.ttf") + .as_slice(), + ))] + .into_iter(), + )), + glyph_cache: GlyphCache::new(), + measurement_cache: RefCell::new(Cache::new()), + render_cache: Cache::new(), + } + } + + pub fn load_font(&mut self, bytes: Cow<'static, [u8]>) { + self.font_system.get_mut().db_mut().load_font_source( + cosmic_text::fontdb::Source::Binary(Arc::new(bytes.into_owned())), + ); + } + + pub fn draw( + &mut self, + content: &str, + bounds: Rectangle, + color: Color, + size: f32, + line_height: LineHeight, + font: Font, + horizontal_alignment: alignment::Horizontal, + vertical_alignment: alignment::Vertical, + shaping: Shaping, + scale_factor: f32, + pixels: &mut tiny_skia::PixmapMut<'_>, + clip_mask: Option<&tiny_skia::Mask>, + ) { + let line_height = + f32::from(line_height.to_absolute(Pixels(size))) * scale_factor; + + let bounds = bounds * scale_factor; + let size = size * scale_factor; + + let font_system = self.font_system.get_mut(); + let key = Key { + bounds: { + let size = bounds.size(); + + // TODO: Reuse buffers from layouting + Size::new(size.width.ceil(), size.height.ceil()) + }, + content, + font, + size, + line_height, + shaping, + }; + + let (_, buffer) = self.render_cache.allocate(font_system, key); + + let (total_lines, max_width) = buffer + .layout_runs() + .enumerate() + .fold((0, 0.0), |(_, max), (i, buffer)| { + (i + 1, buffer.line_w.max(max)) + }); + + let total_height = total_lines as f32 * line_height; + + let x = match horizontal_alignment { + alignment::Horizontal::Left => bounds.x, + alignment::Horizontal::Center => bounds.x - max_width / 2.0, + alignment::Horizontal::Right => bounds.x - max_width, + }; + + let y = match vertical_alignment { + alignment::Vertical::Top => bounds.y, + alignment::Vertical::Center => bounds.y - total_height / 2.0, + alignment::Vertical::Bottom => bounds.y - total_height, + }; + + // TODO: Subpixel glyph positioning + let x = x.round() as i32; + let y = y.round() as i32; + + let mut swash = cosmic_text::SwashCache::new(); + + for run in buffer.layout_runs() { + for glyph in run.glyphs { + if let Some((buffer, placement)) = self.glyph_cache.allocate( + glyph.cache_key, + color, + font_system, + &mut swash, + ) { + let pixmap = tiny_skia::PixmapRef::from_bytes( + buffer, + placement.width, + placement.height, + ) + .expect("Create glyph pixel map"); + + pixels.draw_pixmap( + x + glyph.x_int + placement.left, + y - glyph.y_int - placement.top + run.line_y as i32, + pixmap, + &tiny_skia::PixmapPaint::default(), + tiny_skia::Transform::identity(), + clip_mask, + ); + } + } + } + } + + pub fn trim_cache(&mut self) { + self.render_cache.trim(); + self.glyph_cache.trim(); + } + + pub fn measure( + &self, + content: &str, + size: f32, + line_height: LineHeight, + font: Font, + bounds: Size, + shaping: Shaping, + ) -> (f32, f32) { + let mut measurement_cache = self.measurement_cache.borrow_mut(); + + let line_height = f32::from(line_height.to_absolute(Pixels(size))); + + let (_, paragraph) = measurement_cache.allocate( + &mut self.font_system.borrow_mut(), + Key { + content, + size, + line_height, + font, + bounds, + shaping, + }, + ); + + let (total_lines, max_width) = paragraph + .layout_runs() + .enumerate() + .fold((0, 0.0), |(_, max), (i, buffer)| { + (i + 1, buffer.line_w.max(max)) + }); + + (max_width, line_height * total_lines as f32) + } + + pub fn hit_test( + &self, + content: &str, + size: f32, + line_height: LineHeight, + font: Font, + bounds: Size, + shaping: Shaping, + point: Point, + _nearest_only: bool, + ) -> Option<Hit> { + let mut measurement_cache = self.measurement_cache.borrow_mut(); + + let line_height = f32::from(line_height.to_absolute(Pixels(size))); + + let (_, paragraph) = measurement_cache.allocate( + &mut self.font_system.borrow_mut(), + Key { + content, + size, + line_height, + font, + bounds, + shaping, + }, + ); + + let cursor = paragraph.hit(point.x, point.y)?; + + Some(Hit::CharOffset(cursor.index)) + } + + pub fn trim_measurement_cache(&mut self) { + self.measurement_cache.borrow_mut().trim(); + } +} + +fn to_family(family: font::Family) -> cosmic_text::Family<'static> { + match family { + font::Family::Name(name) => cosmic_text::Family::Name(name), + font::Family::SansSerif => cosmic_text::Family::SansSerif, + font::Family::Serif => cosmic_text::Family::Serif, + font::Family::Cursive => cosmic_text::Family::Cursive, + font::Family::Fantasy => cosmic_text::Family::Fantasy, + font::Family::Monospace => cosmic_text::Family::Monospace, + } +} + +fn to_weight(weight: font::Weight) -> cosmic_text::Weight { + match weight { + font::Weight::Thin => cosmic_text::Weight::THIN, + font::Weight::ExtraLight => cosmic_text::Weight::EXTRA_LIGHT, + font::Weight::Light => cosmic_text::Weight::LIGHT, + font::Weight::Normal => cosmic_text::Weight::NORMAL, + font::Weight::Medium => cosmic_text::Weight::MEDIUM, + font::Weight::Semibold => cosmic_text::Weight::SEMIBOLD, + font::Weight::Bold => cosmic_text::Weight::BOLD, + font::Weight::ExtraBold => cosmic_text::Weight::EXTRA_BOLD, + font::Weight::Black => cosmic_text::Weight::BLACK, + } +} + +fn to_stretch(stretch: font::Stretch) -> cosmic_text::Stretch { + match stretch { + font::Stretch::UltraCondensed => cosmic_text::Stretch::UltraCondensed, + font::Stretch::ExtraCondensed => cosmic_text::Stretch::ExtraCondensed, + font::Stretch::Condensed => cosmic_text::Stretch::Condensed, + font::Stretch::SemiCondensed => cosmic_text::Stretch::SemiCondensed, + font::Stretch::Normal => cosmic_text::Stretch::Normal, + font::Stretch::SemiExpanded => cosmic_text::Stretch::SemiExpanded, + font::Stretch::Expanded => cosmic_text::Stretch::Expanded, + font::Stretch::ExtraExpanded => cosmic_text::Stretch::ExtraExpanded, + font::Stretch::UltraExpanded => cosmic_text::Stretch::UltraExpanded, + } +} + +fn to_shaping(shaping: Shaping) -> cosmic_text::Shaping { + match shaping { + Shaping::Basic => cosmic_text::Shaping::Basic, + Shaping::Advanced => cosmic_text::Shaping::Advanced, + } +} + +#[derive(Debug, Clone, Default)] +struct GlyphCache { + entries: FxHashMap< + (cosmic_text::CacheKey, [u8; 3]), + (Vec<u32>, cosmic_text::Placement), + >, + recently_used: FxHashSet<(cosmic_text::CacheKey, [u8; 3])>, + trim_count: usize, +} + +impl GlyphCache { + const TRIM_INTERVAL: usize = 300; + + fn new() -> Self { + GlyphCache::default() + } + + fn allocate( + &mut self, + cache_key: cosmic_text::CacheKey, + color: Color, + font_system: &mut cosmic_text::FontSystem, + swash: &mut cosmic_text::SwashCache, + ) -> Option<(&[u8], cosmic_text::Placement)> { + let [r, g, b, _a] = color.into_rgba8(); + let key = (cache_key, [r, g, b]); + + if let hash_map::Entry::Vacant(entry) = self.entries.entry(key) { + // TODO: Outline support + let image = swash.get_image_uncached(font_system, cache_key)?; + + let glyph_size = image.placement.width as usize + * image.placement.height as usize; + + if glyph_size == 0 { + return None; + } + + let mut buffer = vec![0u32; glyph_size]; + + match image.content { + cosmic_text::SwashContent::Mask => { + let mut i = 0; + + // TODO: Blend alpha + + for _y in 0..image.placement.height { + for _x in 0..image.placement.width { + buffer[i] = tiny_skia::ColorU8::from_rgba( + b, + g, + r, + image.data[i], + ) + .premultiply() + .get(); + + i += 1; + } + } + } + cosmic_text::SwashContent::Color => { + let mut i = 0; + + for _y in 0..image.placement.height { + for _x in 0..image.placement.width { + // TODO: Blend alpha + buffer[i >> 2] = tiny_skia::ColorU8::from_rgba( + image.data[i + 2], + image.data[i + 1], + image.data[i], + image.data[i + 3], + ) + .premultiply() + .get(); + + i += 4; + } + } + } + cosmic_text::SwashContent::SubpixelMask => { + // TODO + } + } + + entry.insert((buffer, image.placement)); + } + + self.recently_used.insert(key); + + self.entries.get(&key).map(|(buffer, placement)| { + (bytemuck::cast_slice(buffer.as_slice()), *placement) + }) + } + + pub fn trim(&mut self) { + if self.trim_count > Self::TRIM_INTERVAL { + self.entries + .retain(|key, _| self.recently_used.contains(key)); + + self.recently_used.clear(); + + self.trim_count = 0; + } else { + self.trim_count += 1; + } + } +} + +struct Cache { + entries: FxHashMap<KeyHash, cosmic_text::Buffer>, + recently_used: FxHashSet<KeyHash>, + hasher: HashBuilder, + trim_count: usize, +} + +#[cfg(not(target_arch = "wasm32"))] +type HashBuilder = twox_hash::RandomXxHashBuilder64; + +#[cfg(target_arch = "wasm32")] +type HashBuilder = std::hash::BuildHasherDefault<twox_hash::XxHash64>; + +impl Cache { + const TRIM_INTERVAL: usize = 300; + + fn new() -> Self { + Self { + entries: FxHashMap::default(), + recently_used: FxHashSet::default(), + hasher: HashBuilder::default(), + trim_count: 0, + } + } + + fn allocate( + &mut self, + font_system: &mut cosmic_text::FontSystem, + key: Key<'_>, + ) -> (KeyHash, &mut cosmic_text::Buffer) { + let hash = { + let mut hasher = self.hasher.build_hasher(); + + key.content.hash(&mut hasher); + key.size.to_bits().hash(&mut hasher); + key.line_height.to_bits().hash(&mut hasher); + key.font.hash(&mut hasher); + key.bounds.width.to_bits().hash(&mut hasher); + key.bounds.height.to_bits().hash(&mut hasher); + key.shaping.hash(&mut hasher); + + hasher.finish() + }; + + if let hash_map::Entry::Vacant(entry) = self.entries.entry(hash) { + let metrics = cosmic_text::Metrics::new(key.size, key.size * 1.2); + let mut buffer = cosmic_text::Buffer::new(font_system, metrics); + + buffer.set_size( + font_system, + key.bounds.width, + key.bounds.height.max(key.size * 1.2), + ); + buffer.set_text( + font_system, + key.content, + cosmic_text::Attrs::new() + .family(to_family(key.font.family)) + .weight(to_weight(key.font.weight)) + .stretch(to_stretch(key.font.stretch)), + to_shaping(key.shaping), + ); + + let _ = entry.insert(buffer); + } + + let _ = self.recently_used.insert(hash); + + (hash, self.entries.get_mut(&hash).unwrap()) + } + + fn trim(&mut self) { + if self.trim_count > Self::TRIM_INTERVAL { + self.entries + .retain(|key, _| self.recently_used.contains(key)); + + self.recently_used.clear(); + + self.trim_count = 0; + } else { + self.trim_count += 1; + } + } +} + +#[derive(Debug, Clone, Copy)] +struct Key<'a> { + content: &'a str, + size: f32, + line_height: f32, + font: Font, + bounds: Size, + shaping: Shaping, +} + +type KeyHash = u64; diff --git a/tiny_skia/src/vector.rs b/tiny_skia/src/vector.rs new file mode 100644 index 00000000..a3f3c2e3 --- /dev/null +++ b/tiny_skia/src/vector.rs @@ -0,0 +1,183 @@ +use crate::core::svg::{Data, Handle}; +use crate::core::{Color, Rectangle, Size}; + +use resvg::usvg; +use rustc_hash::{FxHashMap, FxHashSet}; + +use std::cell::RefCell; +use std::collections::hash_map; +use std::fs; + +pub struct Pipeline { + cache: RefCell<Cache>, +} + +impl Pipeline { + pub fn new() -> Self { + Self { + cache: RefCell::new(Cache::default()), + } + } + + pub fn viewport_dimensions(&self, handle: &Handle) -> Size<u32> { + self.cache + .borrow_mut() + .viewport_dimensions(handle) + .unwrap_or(Size::new(0, 0)) + } + + pub fn draw( + &mut self, + handle: &Handle, + color: Option<Color>, + bounds: Rectangle, + pixels: &mut tiny_skia::PixmapMut<'_>, + clip_mask: Option<&tiny_skia::Mask>, + ) { + if let Some(image) = self.cache.borrow_mut().draw( + handle, + color, + Size::new(bounds.width as u32, bounds.height as u32), + ) { + pixels.draw_pixmap( + bounds.x as i32, + bounds.y as i32, + image, + &tiny_skia::PixmapPaint::default(), + tiny_skia::Transform::identity(), + clip_mask, + ); + } + } + + pub fn trim_cache(&mut self) { + self.cache.borrow_mut().trim(); + } +} + +#[derive(Default)] +struct Cache { + trees: FxHashMap<u64, Option<resvg::usvg::Tree>>, + tree_hits: FxHashSet<u64>, + rasters: FxHashMap<RasterKey, tiny_skia::Pixmap>, + raster_hits: FxHashSet<RasterKey>, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +struct RasterKey { + id: u64, + color: Option<[u8; 4]>, + size: Size<u32>, +} + +impl Cache { + fn load(&mut self, handle: &Handle) -> Option<&usvg::Tree> { + use usvg::TreeParsing; + + let id = handle.id(); + + if let hash_map::Entry::Vacant(entry) = self.trees.entry(id) { + let svg = match handle.data() { + Data::Path(path) => { + fs::read_to_string(path).ok().and_then(|contents| { + usvg::Tree::from_str( + &contents, + &usvg::Options::default(), + ) + .ok() + }) + } + Data::Bytes(bytes) => { + usvg::Tree::from_data(bytes, &usvg::Options::default()).ok() + } + }; + + entry.insert(svg); + } + + self.tree_hits.insert(id); + self.trees.get(&id).unwrap().as_ref() + } + + fn viewport_dimensions(&mut self, handle: &Handle) -> Option<Size<u32>> { + let tree = self.load(handle)?; + + Some(Size::new( + tree.size.width() as u32, + tree.size.height() as u32, + )) + } + + fn draw( + &mut self, + handle: &Handle, + color: Option<Color>, + size: Size<u32>, + ) -> Option<tiny_skia::PixmapRef<'_>> { + if size.width == 0 || size.height == 0 { + return None; + } + + let key = RasterKey { + id: handle.id(), + color: color.map(Color::into_rgba8), + size, + }; + + #[allow(clippy::map_entry)] + if !self.rasters.contains_key(&key) { + let tree = self.load(handle)?; + + let mut image = tiny_skia::Pixmap::new(size.width, size.height)?; + + resvg::render( + tree, + if size.width > size.height { + resvg::FitTo::Width(size.width) + } else { + resvg::FitTo::Height(size.height) + }, + tiny_skia::Transform::default(), + image.as_mut(), + )?; + + if let Some([r, g, b, _]) = key.color { + // Apply color filter + for pixel in + bytemuck::cast_slice_mut::<u8, u32>(image.data_mut()) + { + *pixel = tiny_skia::ColorU8::from_rgba( + b, + g, + r, + (*pixel >> 24) as u8, + ) + .premultiply() + .get(); + } + } else { + // Swap R and B channels for `softbuffer` presentation + for pixel in + bytemuck::cast_slice_mut::<u8, u32>(image.data_mut()) + { + *pixel = *pixel & 0xFF00FF00 + | ((0x000000FF & *pixel) << 16) + | ((0x00FF0000 & *pixel) >> 16); + } + } + + self.rasters.insert(key, image); + } + + self.raster_hits.insert(key); + self.rasters.get(&key).map(tiny_skia::Pixmap::as_ref) + } + + fn trim(&mut self) { + self.trees.retain(|key, _| self.tree_hits.contains(key)); + self.rasters.retain(|key, _| self.raster_hits.contains(key)); + + self.tree_hits.clear(); + self.raster_hits.clear(); + } +} diff --git a/tiny_skia/src/window.rs b/tiny_skia/src/window.rs new file mode 100644 index 00000000..d8d9378e --- /dev/null +++ b/tiny_skia/src/window.rs @@ -0,0 +1,3 @@ +pub mod compositor; + +pub use compositor::{Compositor, Surface}; diff --git a/tiny_skia/src/window/compositor.rs b/tiny_skia/src/window/compositor.rs new file mode 100644 index 00000000..9999a188 --- /dev/null +++ b/tiny_skia/src/window/compositor.rs @@ -0,0 +1,158 @@ +use crate::core::{Color, Rectangle}; +use crate::graphics::compositor::{self, Information, SurfaceError}; +use crate::graphics::damage; +use crate::graphics::{Error, Primitive, Viewport}; +use crate::{Backend, Renderer, Settings}; + +use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle}; +use std::marker::PhantomData; + +pub struct Compositor<Theme> { + _theme: PhantomData<Theme>, +} + +pub struct Surface { + window: softbuffer::GraphicsContext, + buffer: Vec<u32>, + clip_mask: tiny_skia::Mask, + primitives: Option<Vec<Primitive>>, + background_color: Color, +} + +impl<Theme> crate::graphics::Compositor for Compositor<Theme> { + type Settings = Settings; + type Renderer = Renderer<Theme>; + type Surface = Surface; + + fn new<W: HasRawWindowHandle + HasRawDisplayHandle>( + settings: Self::Settings, + _compatible_window: Option<&W>, + ) -> Result<(Self, Self::Renderer), Error> { + let (compositor, backend) = new(settings); + + Ok((compositor, Renderer::new(backend))) + } + + fn create_surface<W: HasRawWindowHandle + HasRawDisplayHandle>( + &mut self, + window: &W, + width: u32, + height: u32, + ) -> Surface { + let window = + unsafe { softbuffer::GraphicsContext::new(window, window) } + .expect("Create softbuffer for window"); + + Surface { + window, + buffer: vec![0; width as usize * height as usize], + clip_mask: tiny_skia::Mask::new(width, height) + .expect("Create clip mask"), + primitives: None, + background_color: Color::BLACK, + } + } + + fn configure_surface( + &mut self, + surface: &mut Surface, + width: u32, + height: u32, + ) { + surface.buffer.resize((width * height) as usize, 0); + surface.clip_mask = + tiny_skia::Mask::new(width, height).expect("Create clip mask"); + surface.primitives = None; + } + + fn fetch_information(&self) -> Information { + Information { + adapter: String::from("CPU"), + backend: String::from("tiny-skia"), + } + } + + fn present<T: AsRef<str>>( + &mut self, + renderer: &mut Self::Renderer, + surface: &mut Self::Surface, + viewport: &Viewport, + background_color: Color, + overlay: &[T], + ) -> Result<(), SurfaceError> { + renderer.with_primitives(|backend, primitives| { + present( + backend, + surface, + primitives, + viewport, + background_color, + overlay, + ) + }) + } +} + +pub fn new<Theme>(settings: Settings) -> (Compositor<Theme>, Backend) { + ( + Compositor { + _theme: PhantomData, + }, + Backend::new(settings), + ) +} + +pub fn present<T: AsRef<str>>( + backend: &mut Backend, + surface: &mut Surface, + primitives: &[Primitive], + viewport: &Viewport, + background_color: Color, + overlay: &[T], +) -> Result<(), compositor::SurfaceError> { + let physical_size = viewport.physical_size(); + let scale_factor = viewport.scale_factor() as f32; + + let mut pixels = tiny_skia::PixmapMut::from_bytes( + bytemuck::cast_slice_mut(&mut surface.buffer), + physical_size.width, + physical_size.height, + ) + .expect("Create pixel map"); + + let damage = surface + .primitives + .as_deref() + .and_then(|last_primitives| { + (surface.background_color == background_color) + .then(|| damage::list(last_primitives, primitives)) + }) + .unwrap_or_else(|| vec![Rectangle::with_size(viewport.logical_size())]); + + if damage.is_empty() { + return Ok(()); + } + + surface.primitives = Some(primitives.to_vec()); + surface.background_color = background_color; + + let damage = damage::group(damage, scale_factor, physical_size); + + backend.draw( + &mut pixels, + &mut surface.clip_mask, + primitives, + viewport, + &damage, + background_color, + overlay, + ); + + surface.window.set_buffer( + &surface.buffer, + physical_size.width as u16, + physical_size.height as u16, + ); + + Ok(()) +} diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 1ce07e0a..41eb4c23 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -8,51 +8,43 @@ license = "MIT AND OFL-1.1" repository = "https://github.com/iced-rs/iced" [features] -svg = ["iced_graphics/svg"] +geometry = ["iced_graphics/geometry", "lyon"] image = ["iced_graphics/image"] -png = ["iced_graphics/png"] -jpeg = ["iced_graphics/jpeg"] -jpeg_rayon = ["iced_graphics/jpeg_rayon"] -gif = ["iced_graphics/gif"] -webp = ["iced_graphics/webp"] -pnm = ["iced_graphics/pnm"] -ico = ["iced_graphics/ico"] -bmp = ["iced_graphics/bmp"] -hdr = ["iced_graphics/hdr"] -dds = ["iced_graphics/dds"] -farbfeld = ["iced_graphics/farbfeld"] -canvas = ["iced_graphics/canvas"] -qr_code = ["iced_graphics/qr_code"] -default_system_font = ["iced_graphics/font-source"] -spirv = ["wgpu/spirv"] -webgl = ["wgpu/webgl"] +svg = ["resvg"] [dependencies] wgpu = "0.16" -wgpu_glyph = "0.20" -glyph_brush = "0.7" raw-window-handle = "0.5" log = "0.4" guillotiere = "0.6" futures = "0.3" bitflags = "1.2" +once_cell = "1.0" +rustc-hash = "1.1" + +[target.'cfg(target_arch = "wasm32")'.dependencies] +wgpu = { version = "0.16", features = ["webgl"] } + +[dependencies.twox-hash] +version = "1.6" +default-features = false + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies.twox-hash] +version = "1.6.1" +features = ["std"] [dependencies.bytemuck] version = "1.9" features = ["derive"] -[dependencies.iced_native] -version = "0.10" -path = "../native" - [dependencies.iced_graphics] version = "0.8" path = "../graphics" -features = ["font-fallback", "font-icons"] -[dependencies.tracing] -version = "0.1.6" -optional = true +[dependencies.glyphon] +version = "0.2" +git = "https://github.com/hecrj/glyphon.git" +rev = "f145067d292082abdd1f2b2481812d4a52c394ec" [dependencies.encase] version = "0.3.0" @@ -61,6 +53,18 @@ features = ["glam"] [dependencies.glam] version = "0.21.3" +[dependencies.lyon] +version = "1.0" +optional = true + +[dependencies.resvg] +version = "0.32" +optional = true + +[dependencies.tracing] +version = "0.1.6" +optional = true + [package.metadata.docs.rs] rustdoc-args = ["--cfg", "docsrs"] all-features = true diff --git a/graphics/fonts/Icons.ttf b/wgpu/fonts/Iced-Icons.ttf Binary files differindex 5e455b69..e3273141 100644 --- a/graphics/fonts/Icons.ttf +++ b/wgpu/fonts/Iced-Icons.ttf diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 6a299425..def80a81 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -1,14 +1,11 @@ +use crate::core; +use crate::core::{Color, Font, Point, Size}; +use crate::graphics::backend; +use crate::graphics::{Primitive, Transformation, Viewport}; use crate::quad; use crate::text; use crate::triangle; -use crate::{Settings, Transformation}; - -use iced_graphics::backend; -use iced_graphics::font; -use iced_graphics::layer::Layer; -use iced_graphics::{Primitive, Viewport}; -use iced_native::alignment; -use iced_native::{Font, Size}; +use crate::{Layer, Settings}; #[cfg(feature = "tracing")] use tracing::info_span; @@ -16,11 +13,13 @@ use tracing::info_span; #[cfg(any(feature = "image", feature = "svg"))] use crate::image; +use std::borrow::Cow; + /// A [`wgpu`] graphics backend for [`iced`]. /// /// [`wgpu`]: https://github.com/gfx-rs/wgpu-rs /// [`iced`]: https://github.com/iced-rs/iced -#[derive(Debug)] +#[allow(missing_debug_implementations)] pub struct Backend { quad_pipeline: quad::Pipeline, text_pipeline: text::Pipeline, @@ -29,6 +28,7 @@ pub struct Backend { #[cfg(any(feature = "image", feature = "svg"))] image_pipeline: image::Pipeline, + default_font: Font, default_text_size: f32, } @@ -36,16 +36,11 @@ impl Backend { /// Creates a new [`Backend`]. pub fn new( device: &wgpu::Device, + queue: &wgpu::Queue, settings: Settings, format: wgpu::TextureFormat, ) -> Self { - let text_pipeline = text::Pipeline::new( - device, - format, - settings.default_font, - settings.text_multithreading, - ); - + let text_pipeline = text::Pipeline::new(device, queue, format); let quad_pipeline = quad::Pipeline::new(device, format); let triangle_pipeline = triangle::Pipeline::new(device, format, settings.antialiasing); @@ -61,6 +56,7 @@ impl Backend { #[cfg(any(feature = "image", feature = "svg"))] image_pipeline, + default_font: settings.default_font, default_text_size: settings.default_text_size, } } @@ -72,8 +68,9 @@ impl Backend { pub fn present<T: AsRef<str>>( &mut self, device: &wgpu::Device, - staging_belt: &mut wgpu::util::StagingBelt, + queue: &wgpu::Queue, encoder: &mut wgpu::CommandEncoder, + clear_color: Option<Color>, frame: &wgpu::TextureView, primitives: &[Primitive], viewport: &Viewport, @@ -90,167 +87,245 @@ impl Backend { let mut layers = Layer::generate(primitives, viewport); layers.push(Layer::overlay(overlay_text, viewport)); + self.prepare( + device, + queue, + encoder, + scale_factor, + transformation, + &layers, + ); + + while !self.prepare_text( + device, + queue, + scale_factor, + target_size, + &layers, + ) {} + + self.render( + device, + encoder, + frame, + clear_color, + scale_factor, + target_size, + &layers, + ); + + self.quad_pipeline.end_frame(); + self.text_pipeline.end_frame(); + self.triangle_pipeline.end_frame(); + + #[cfg(any(feature = "image", feature = "svg"))] + self.image_pipeline.end_frame(); + } + + fn prepare_text( + &mut self, + device: &wgpu::Device, + queue: &wgpu::Queue, + scale_factor: f32, + target_size: Size<u32>, + layers: &[Layer<'_>], + ) -> bool { for layer in layers { - self.flush( - device, - scale_factor, - transformation, - &layer, - staging_belt, - encoder, - frame, - target_size, - ); + let bounds = (layer.bounds * scale_factor).snap(); + + if bounds.width < 1 || bounds.height < 1 { + continue; + } + + if !layer.text.is_empty() + && !self.text_pipeline.prepare( + device, + queue, + &layer.text, + layer.bounds, + scale_factor, + target_size, + ) + { + return false; + } } - #[cfg(any(feature = "image", feature = "svg"))] - self.image_pipeline.trim_cache(device, encoder); + true } - fn flush( + fn prepare( &mut self, device: &wgpu::Device, + queue: &wgpu::Queue, + _encoder: &mut wgpu::CommandEncoder, scale_factor: f32, transformation: Transformation, - layer: &Layer<'_>, - staging_belt: &mut wgpu::util::StagingBelt, + layers: &[Layer<'_>], + ) { + for layer in layers { + let bounds = (layer.bounds * scale_factor).snap(); + + if bounds.width < 1 || bounds.height < 1 { + continue; + } + + if !layer.quads.is_empty() { + self.quad_pipeline.prepare( + device, + queue, + &layer.quads, + transformation, + scale_factor, + ); + } + + if !layer.meshes.is_empty() { + let scaled = transformation + * Transformation::scale(scale_factor, scale_factor); + + self.triangle_pipeline.prepare( + device, + queue, + &layer.meshes, + scaled, + ); + } + + #[cfg(any(feature = "image", feature = "svg"))] + { + if !layer.images.is_empty() { + let scaled = transformation + * Transformation::scale(scale_factor, scale_factor); + + self.image_pipeline.prepare( + device, + queue, + _encoder, + &layer.images, + scaled, + scale_factor, + ); + } + } + } + } + + fn render( + &mut self, + device: &wgpu::Device, encoder: &mut wgpu::CommandEncoder, target: &wgpu::TextureView, + clear_color: Option<Color>, + scale_factor: f32, target_size: Size<u32>, + layers: &[Layer<'_>], ) { - let bounds = (layer.bounds * scale_factor).snap(); + use std::mem::ManuallyDrop; - if bounds.width < 1 || bounds.height < 1 { - return; - } + let mut quad_layer = 0; + let mut triangle_layer = 0; + #[cfg(any(feature = "image", feature = "svg"))] + let mut image_layer = 0; + let mut text_layer = 0; + + let mut render_pass = ManuallyDrop::new(encoder.begin_render_pass( + &wgpu::RenderPassDescriptor { + label: Some("iced_wgpu::quad render pass"), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: target, + resolve_target: None, + ops: wgpu::Operations { + load: match clear_color { + Some(background_color) => wgpu::LoadOp::Clear({ + let [r, g, b, a] = + background_color.into_linear(); + + wgpu::Color { + r: f64::from(r), + g: f64::from(g), + b: f64::from(b), + a: f64::from(a), + } + }), + None => wgpu::LoadOp::Load, + }, + store: true, + }, + })], + depth_stencil_attachment: None, + }, + )); - if !layer.quads.is_empty() { - self.quad_pipeline.draw( - device, - staging_belt, - encoder, - &layer.quads, - transformation, - scale_factor, - bounds, - target, - ); - } + for layer in layers { + let bounds = (layer.bounds * scale_factor).snap(); - if !layer.meshes.is_empty() { - let scaled = transformation - * Transformation::scale(scale_factor, scale_factor); - - self.triangle_pipeline.draw( - device, - staging_belt, - encoder, - target, - target_size, - scaled, - scale_factor, - &layer.meshes, - ); - } + if bounds.width < 1 || bounds.height < 1 { + return; + } - #[cfg(any(feature = "image", feature = "svg"))] - { - if !layer.images.is_empty() { - let scaled = transformation - * Transformation::scale(scale_factor, scale_factor); + if !layer.quads.is_empty() { + self.quad_pipeline + .render(quad_layer, bounds, &mut render_pass); - self.image_pipeline.draw( + quad_layer += 1; + } + + if !layer.meshes.is_empty() { + let _ = ManuallyDrop::into_inner(render_pass); + + self.triangle_pipeline.render( device, - staging_belt, encoder, - &layer.images, - scaled, - bounds, target, + triangle_layer, + target_size, + &layer.meshes, scale_factor, ); + + triangle_layer += 1; + + render_pass = ManuallyDrop::new(encoder.begin_render_pass( + &wgpu::RenderPassDescriptor { + label: Some("iced_wgpu::quad render pass"), + color_attachments: &[Some( + wgpu::RenderPassColorAttachment { + view: target, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Load, + store: true, + }, + }, + )], + depth_stencil_attachment: None, + }, + )); } - } - if !layer.text.is_empty() { - for text in layer.text.iter() { - // Target physical coordinates directly to avoid blurry text - let text = wgpu_glyph::Section { - // TODO: We `round` here to avoid rerasterizing text when - // its position changes slightly. This can make text feel a - // bit "jumpy". We may be able to do better once we improve - // our text rendering/caching pipeline. - screen_position: ( - (text.bounds.x * scale_factor).round(), - (text.bounds.y * scale_factor).round(), - ), - // TODO: Fix precision issues with some scale factors. - // - // The `ceil` here can cause some words to render on the - // same line when they should not. - // - // Ideally, `wgpu_glyph` should be able to compute layout - // using logical positions, and then apply the proper - // scaling when rendering. This would ensure that both - // measuring and rendering follow the same layout rules. - bounds: ( - (text.bounds.width * scale_factor).ceil(), - (text.bounds.height * scale_factor).ceil(), - ), - text: vec![wgpu_glyph::Text { - text: text.content, - scale: wgpu_glyph::ab_glyph::PxScale { - x: text.size * scale_factor, - y: text.size * scale_factor, - }, - font_id: self.text_pipeline.find_font(text.font), - extra: wgpu_glyph::Extra { - color: text.color, - z: 0.0, - }, - }], - layout: wgpu_glyph::Layout::default() - .h_align(match text.horizontal_alignment { - alignment::Horizontal::Left => { - wgpu_glyph::HorizontalAlign::Left - } - alignment::Horizontal::Center => { - wgpu_glyph::HorizontalAlign::Center - } - alignment::Horizontal::Right => { - wgpu_glyph::HorizontalAlign::Right - } - }) - .v_align(match text.vertical_alignment { - alignment::Vertical::Top => { - wgpu_glyph::VerticalAlign::Top - } - alignment::Vertical::Center => { - wgpu_glyph::VerticalAlign::Center - } - alignment::Vertical::Bottom => { - wgpu_glyph::VerticalAlign::Bottom - } - }), - }; - - self.text_pipeline.queue(text); + #[cfg(any(feature = "image", feature = "svg"))] + { + if !layer.images.is_empty() { + self.image_pipeline.render( + image_layer, + bounds, + &mut render_pass, + ); + + image_layer += 1; + } } - self.text_pipeline.draw_queued( - device, - staging_belt, - encoder, - target, - transformation, - wgpu_glyph::Region { - x: bounds.x, - y: bounds.y, - width: bounds.width, - height: bounds.height, - }, - ); + if !layer.text.is_empty() { + self.text_pipeline + .render(text_layer, bounds, &mut render_pass); + + text_layer += 1; + } } + + let _ = ManuallyDrop::into_inner(render_pass); } } @@ -261,9 +336,13 @@ impl iced_graphics::Backend for Backend { } impl backend::Text for Backend { - const ICON_FONT: Font = font::ICONS; - const CHECKMARK_ICON: char = font::CHECKMARK_ICON; - const ARROW_DOWN_ICON: char = font::ARROW_DOWN_ICON; + const ICON_FONT: Font = Font::with_name("Iced-Icons"); + const CHECKMARK_ICON: char = '\u{f00c}'; + const ARROW_DOWN_ICON: char = '\u{e800}'; + + fn default_font(&self) -> Font { + self.default_font + } fn default_size(&self) -> f32 { self.default_text_size @@ -273,45 +352,59 @@ impl backend::Text for Backend { &self, contents: &str, size: f32, + line_height: core::text::LineHeight, font: Font, bounds: Size, + shaping: core::text::Shaping, ) -> (f32, f32) { - self.text_pipeline.measure(contents, size, font, bounds) + self.text_pipeline.measure( + contents, + size, + line_height, + font, + bounds, + shaping, + ) } fn hit_test( &self, contents: &str, size: f32, + line_height: core::text::LineHeight, font: Font, bounds: Size, - point: iced_native::Point, + shaping: core::text::Shaping, + point: Point, nearest_only: bool, - ) -> Option<text::Hit> { + ) -> Option<core::text::Hit> { self.text_pipeline.hit_test( contents, size, + line_height, font, bounds, + shaping, point, nearest_only, ) } + + fn load_font(&mut self, font: Cow<'static, [u8]>) { + self.text_pipeline.load_font(font); + } } #[cfg(feature = "image")] impl backend::Image for Backend { - fn dimensions(&self, handle: &iced_native::image::Handle) -> Size<u32> { + fn dimensions(&self, handle: &core::image::Handle) -> Size<u32> { self.image_pipeline.dimensions(handle) } } #[cfg(feature = "svg")] impl backend::Svg for Backend { - fn viewport_dimensions( - &self, - handle: &iced_native::svg::Handle, - ) -> Size<u32> { + fn viewport_dimensions(&self, handle: &core::svg::Handle) -> Size<u32> { self.image_pipeline.viewport_dimensions(handle) } } diff --git a/wgpu/src/buffer.rs b/wgpu/src/buffer.rs index 7c092d0b..c210dd4e 100644 --- a/wgpu/src/buffer.rs +++ b/wgpu/src/buffer.rs @@ -1,3 +1,89 @@ //! Utilities for buffer operations. pub mod dynamic; pub mod r#static; + +use std::marker::PhantomData; +use std::ops::RangeBounds; + +#[derive(Debug)] +pub struct Buffer<T> { + label: &'static str, + size: u64, + usage: wgpu::BufferUsages, + raw: wgpu::Buffer, + type_: PhantomData<T>, +} + +impl<T: bytemuck::Pod> Buffer<T> { + pub fn new( + device: &wgpu::Device, + label: &'static str, + amount: usize, + usage: wgpu::BufferUsages, + ) -> Self { + let size = next_copy_size::<T>(amount); + + let raw = device.create_buffer(&wgpu::BufferDescriptor { + label: Some(label), + size, + usage, + mapped_at_creation: false, + }); + + Self { + label, + size, + usage, + raw, + type_: PhantomData, + } + } + + pub fn resize(&mut self, device: &wgpu::Device, new_count: usize) -> bool { + let new_size = (std::mem::size_of::<T>() * new_count) as u64; + + if self.size < new_size { + self.raw = device.create_buffer(&wgpu::BufferDescriptor { + label: Some(self.label), + size: new_size, + usage: self.usage, + mapped_at_creation: false, + }); + + self.size = new_size; + + true + } else { + false + } + } + + pub fn write( + &self, + queue: &wgpu::Queue, + offset_count: usize, + contents: &[T], + ) { + queue.write_buffer( + &self.raw, + (std::mem::size_of::<T>() * offset_count) as u64, + bytemuck::cast_slice(contents), + ); + } + + pub fn slice( + &self, + bounds: impl RangeBounds<wgpu::BufferAddress>, + ) -> wgpu::BufferSlice<'_> { + self.raw.slice(bounds) + } +} + +fn next_copy_size<T>(amount: usize) -> u64 { + let align_mask = wgpu::COPY_BUFFER_ALIGNMENT - 1; + + (((std::mem::size_of::<T>() * amount).next_power_of_two() as u64 + + align_mask) + & !align_mask) + .max(wgpu::COPY_BUFFER_ALIGNMENT) +} diff --git a/wgpu/src/buffer/dynamic.rs b/wgpu/src/buffer/dynamic.rs index 88289b98..43fc47ac 100644 --- a/wgpu/src/buffer/dynamic.rs +++ b/wgpu/src/buffer/dynamic.rs @@ -112,25 +112,8 @@ impl<T: ShaderType + WriteInto> Buffer<T> { } /// Write the contents of this dynamic buffer to the GPU via staging belt command. - pub fn write( - &mut self, - device: &wgpu::Device, - staging_belt: &mut wgpu::util::StagingBelt, - encoder: &mut wgpu::CommandEncoder, - ) { - let size = self.cpu.get_ref().len(); - - if let Some(buffer_size) = wgpu::BufferSize::new(size as u64) { - let mut buffer = staging_belt.write_buffer( - encoder, - &self.gpu, - 0, - buffer_size, - device, - ); - - buffer.copy_from_slice(self.cpu.get_ref()); - } + pub fn write(&mut self, queue: &wgpu::Queue) { + queue.write_buffer(&self.gpu, 0, self.cpu.get_ref()); } // Gets the aligned offset at the given index from the CPU buffer. @@ -184,7 +167,7 @@ impl Internal { } /// Returns bytearray of aligned CPU buffer. - pub(super) fn get_ref(&self) -> &Vec<u8> { + pub(super) fn get_ref(&self) -> &[u8] { match self { Internal::Uniform(buf) => buf.as_ref(), #[cfg(not(target_arch = "wasm32"))] diff --git a/wgpu/src/buffer/static.rs b/wgpu/src/buffer/static.rs index ef87422f..d8ae116e 100644 --- a/wgpu/src/buffer/static.rs +++ b/wgpu/src/buffer/static.rs @@ -2,8 +2,7 @@ use bytemuck::{Pod, Zeroable}; use std::marker::PhantomData; use std::mem; -//128 triangles/indices -const DEFAULT_STATIC_BUFFER_COUNT: wgpu::BufferAddress = 128; +const DEFAULT_COUNT: wgpu::BufferAddress = 128; /// A generic buffer struct useful for items which have no alignment requirements /// (e.g. Vertex, Index buffers) & no dynamic offsets. @@ -25,7 +24,7 @@ impl<T: Pod + Zeroable> Buffer<T> { label: &'static str, usages: wgpu::BufferUsages, ) -> Self { - let size = (mem::size_of::<T>() as u64) * DEFAULT_STATIC_BUFFER_COUNT; + let size = (mem::size_of::<T>() as u64) * DEFAULT_COUNT; Self { offsets: Vec::new(), @@ -57,9 +56,13 @@ impl<T: Pod + Zeroable> Buffer<T> { let size = (mem::size_of::<T>() * new_count) as u64; if self.size < size { + self.size = + (mem::size_of::<T>() * (new_count + new_count / 2)) as u64; + + self.gpu = + Self::gpu_buffer(device, self.label, self.size, self.usages); + self.offsets.clear(); - self.size = size; - self.gpu = Self::gpu_buffer(device, self.label, size, self.usages); true } else { false @@ -71,28 +74,15 @@ impl<T: Pod + Zeroable> Buffer<T> { /// Returns the size of the written bytes. pub fn write( &mut self, - device: &wgpu::Device, - staging_belt: &mut wgpu::util::StagingBelt, - encoder: &mut wgpu::CommandEncoder, + queue: &wgpu::Queue, offset: u64, content: &[T], ) -> u64 { let bytes = bytemuck::cast_slice(content); let bytes_size = bytes.len() as u64; - if let Some(buffer_size) = wgpu::BufferSize::new(bytes_size) { - let mut buffer = staging_belt.write_buffer( - encoder, - &self.gpu, - offset, - buffer_size, - device, - ); - - buffer.copy_from_slice(bytes); - - self.offsets.push(offset); - } + queue.write_buffer(&self.gpu, offset, bytes); + self.offsets.push(offset); bytes_size } diff --git a/graphics/src/widget/canvas/frame.rs b/wgpu/src/geometry.rs index d68548ae..7e17a7ad 100644 --- a/graphics/src/widget/canvas/frame.rs +++ b/wgpu/src/geometry.rs @@ -1,17 +1,16 @@ -use crate::gradient::Gradient; -use crate::triangle; -use crate::widget::canvas::{path, Fill, Geometry, Path, Stroke, Style, Text}; -use crate::Primitive; - -use iced_native::{Point, Rectangle, Size, Vector}; +//! Build and draw geometry. +use crate::core::{Gradient, Point, Rectangle, Size, Vector}; +use crate::graphics::geometry::fill::{self, Fill}; +use crate::graphics::geometry::{ + LineCap, LineDash, LineJoin, Path, Stroke, Style, Text, +}; +use crate::graphics::primitive::{self, Primitive}; use lyon::geom::euclid; use lyon::tessellation; use std::borrow::Cow; -/// The frame of a [`Canvas`]. -/// -/// [`Canvas`]: crate::widget::Canvas +/// A frame for drawing some geometry. #[allow(missing_debug_implementations)] pub struct Frame { size: Size, @@ -23,9 +22,9 @@ pub struct Frame { } enum Buffer { - Solid(tessellation::VertexBuffers<triangle::ColoredVertex2D, u32>), + Solid(tessellation::VertexBuffers<primitive::ColoredVertex2D, u32>), Gradient( - tessellation::VertexBuffers<triangle::Vertex2D, u32>, + tessellation::VertexBuffers<primitive::Vertex2D, u32>, Gradient, ), } @@ -196,8 +195,8 @@ impl Frame { .buffers .get_fill(&self.transforms.current.transform_style(style)); - let options = - tessellation::FillOptions::default().with_fill_rule(rule.into()); + let options = tessellation::FillOptions::default() + .with_fill_rule(into_fill_rule(rule)); if self.transforms.current.is_identity { self.fill_tessellator.tessellate_path( @@ -206,7 +205,7 @@ impl Frame { buffer.as_mut(), ) } else { - let path = path.transformed(&self.transforms.current.raw); + let path = path.transform(&self.transforms.current.raw); self.fill_tessellator.tessellate_path( path.raw(), @@ -241,8 +240,8 @@ impl Frame { lyon::math::Vector::new(size.width, size.height), ); - let options = - tessellation::FillOptions::default().with_fill_rule(rule.into()); + let options = tessellation::FillOptions::default() + .with_fill_rule(into_fill_rule(rule)); self.fill_tessellator .tessellate_rectangle( @@ -264,14 +263,14 @@ impl Frame { let mut options = tessellation::StrokeOptions::default(); options.line_width = stroke.width; - options.start_cap = stroke.line_cap.into(); - options.end_cap = stroke.line_cap.into(); - options.line_join = stroke.line_join.into(); + options.start_cap = into_line_cap(stroke.line_cap); + options.end_cap = into_line_cap(stroke.line_cap); + options.line_join = into_line_join(stroke.line_join); let path = if stroke.line_dash.segments.is_empty() { Cow::Borrowed(path) } else { - Cow::Owned(path::dashed(path, stroke.line_dash)) + Cow::Owned(dashed(path, stroke.line_dash)) }; if self.transforms.current.is_identity { @@ -281,7 +280,7 @@ impl Frame { buffer.as_mut(), ) } else { - let path = path.transformed(&self.transforms.current.raw); + let path = path.transform(&self.transforms.current.raw); self.stroke_tessellator.tessellate_path( path.raw(), @@ -331,9 +330,11 @@ impl Frame { }, color: text.color, size: text.size, + line_height: text.line_height, font: text.font, horizontal_alignment: text.horizontal_alignment, vertical_alignment: text.vertical_alignment, + shaping: text.shaping, }); } @@ -344,10 +345,20 @@ impl Frame { /// operations in different coordinate systems. #[inline] pub fn with_save(&mut self, f: impl FnOnce(&mut Frame)) { - self.transforms.previous.push(self.transforms.current); + self.push_transform(); f(self); + self.pop_transform(); + } + + /// Pushes the current transform in the transform stack. + pub fn push_transform(&mut self) { + self.transforms.previous.push(self.transforms.current); + } + + /// Pops a transform from the transform stack and sets it as the current transform. + pub fn pop_transform(&mut self) { self.transforms.current = self.transforms.previous.pop().unwrap(); } @@ -363,14 +374,21 @@ impl Frame { f(&mut frame); + let origin = Point::new(region.x, region.y); + + self.clip(frame, origin); + } + + /// Draws the clipped contents of the given [`Frame`] with origin at the given [`Point`]. + pub fn clip(&mut self, frame: Frame, at: Point) { + let size = frame.size(); let primitives = frame.into_primitives(); + let translation = Vector::new(at.x, at.y); let (text, meshes) = primitives .into_iter() .partition(|primitive| matches!(primitive, Primitive::Text { .. })); - let translation = Vector::new(region.x, region.y); - self.primitives.push(Primitive::Group { primitives: vec![ Primitive::Translate { @@ -380,7 +398,7 @@ impl Frame { Primitive::Translate { translation, content: Box::new(Primitive::Clip { - bounds: Rectangle::with_size(region.size()), + bounds: Rectangle::with_size(size), content: Box::new(Primitive::Group { primitives: text, }), @@ -423,11 +441,11 @@ impl Frame { self.transforms.current.is_identity = false; } - /// Produces the [`Geometry`] representing everything drawn on the [`Frame`]. - pub fn into_geometry(self) -> Geometry { - Geometry::from_primitive(Primitive::Group { + /// Produces the [`Primitive`] representing everything drawn on the [`Frame`]. + pub fn into_primitive(self) -> Primitive { + Primitive::Group { primitives: self.into_primitives(), - }) + } } fn into_primitives(mut self) -> Vec<Primitive> { @@ -436,7 +454,7 @@ impl Frame { Buffer::Solid(buffer) => { if !buffer.indices.is_empty() { self.primitives.push(Primitive::SolidMesh { - buffers: triangle::Mesh2D { + buffers: primitive::Mesh2D { vertices: buffer.vertices, indices: buffer.indices, }, @@ -447,7 +465,7 @@ impl Frame { Buffer::Gradient(buffer, gradient) => { if !buffer.indices.is_empty() { self.primitives.push(Primitive::GradientMesh { - buffers: triangle::Mesh2D { + buffers: primitive::Mesh2D { vertices: buffer.vertices, indices: buffer.indices, }, @@ -465,31 +483,31 @@ impl Frame { struct Vertex2DBuilder; -impl tessellation::FillVertexConstructor<triangle::Vertex2D> +impl tessellation::FillVertexConstructor<primitive::Vertex2D> for Vertex2DBuilder { fn new_vertex( &mut self, vertex: tessellation::FillVertex<'_>, - ) -> triangle::Vertex2D { + ) -> primitive::Vertex2D { let position = vertex.position(); - triangle::Vertex2D { + primitive::Vertex2D { position: [position.x, position.y], } } } -impl tessellation::StrokeVertexConstructor<triangle::Vertex2D> +impl tessellation::StrokeVertexConstructor<primitive::Vertex2D> for Vertex2DBuilder { fn new_vertex( &mut self, vertex: tessellation::StrokeVertex<'_, '_>, - ) -> triangle::Vertex2D { + ) -> primitive::Vertex2D { let position = vertex.position(); - triangle::Vertex2D { + primitive::Vertex2D { position: [position.x, position.y], } } @@ -497,34 +515,99 @@ impl tessellation::StrokeVertexConstructor<triangle::Vertex2D> struct TriangleVertex2DBuilder([f32; 4]); -impl tessellation::FillVertexConstructor<triangle::ColoredVertex2D> +impl tessellation::FillVertexConstructor<primitive::ColoredVertex2D> for TriangleVertex2DBuilder { fn new_vertex( &mut self, vertex: tessellation::FillVertex<'_>, - ) -> triangle::ColoredVertex2D { + ) -> primitive::ColoredVertex2D { let position = vertex.position(); - triangle::ColoredVertex2D { + primitive::ColoredVertex2D { position: [position.x, position.y], color: self.0, } } } -impl tessellation::StrokeVertexConstructor<triangle::ColoredVertex2D> +impl tessellation::StrokeVertexConstructor<primitive::ColoredVertex2D> for TriangleVertex2DBuilder { fn new_vertex( &mut self, vertex: tessellation::StrokeVertex<'_, '_>, - ) -> triangle::ColoredVertex2D { + ) -> primitive::ColoredVertex2D { let position = vertex.position(); - triangle::ColoredVertex2D { + primitive::ColoredVertex2D { position: [position.x, position.y], color: self.0, } } } + +fn into_line_join(line_join: LineJoin) -> lyon::tessellation::LineJoin { + match line_join { + LineJoin::Miter => lyon::tessellation::LineJoin::Miter, + LineJoin::Round => lyon::tessellation::LineJoin::Round, + LineJoin::Bevel => lyon::tessellation::LineJoin::Bevel, + } +} + +fn into_line_cap(line_cap: LineCap) -> lyon::tessellation::LineCap { + match line_cap { + LineCap::Butt => lyon::tessellation::LineCap::Butt, + LineCap::Square => lyon::tessellation::LineCap::Square, + LineCap::Round => lyon::tessellation::LineCap::Round, + } +} + +fn into_fill_rule(rule: fill::Rule) -> lyon::tessellation::FillRule { + match rule { + fill::Rule::NonZero => lyon::tessellation::FillRule::NonZero, + fill::Rule::EvenOdd => lyon::tessellation::FillRule::EvenOdd, + } +} + +pub(super) fn dashed(path: &Path, line_dash: LineDash<'_>) -> Path { + use lyon::algorithms::walk::{ + walk_along_path, RepeatedPattern, WalkerEvent, + }; + use lyon::path::iterator::PathIterator; + + Path::new(|builder| { + let segments_odd = (line_dash.segments.len() % 2 == 1) + .then(|| [line_dash.segments, line_dash.segments].concat()); + + let mut draw_line = false; + + walk_along_path( + path.raw().iter().flattened(0.01), + 0.0, + lyon::tessellation::StrokeOptions::DEFAULT_TOLERANCE, + &mut RepeatedPattern { + callback: |event: WalkerEvent<'_>| { + let point = Point { + x: event.position.x, + y: event.position.y, + }; + + if draw_line { + builder.line_to(point); + } else { + builder.move_to(point); + } + + draw_line = !draw_line; + + true + }, + index: line_dash.offset, + intervals: segments_odd + .as_deref() + .unwrap_or(line_dash.segments), + }, + ); + }) +} diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index 9f56c188..263bcfa2 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -1,16 +1,17 @@ mod atlas; #[cfg(feature = "image")] -use iced_graphics::image::raster; +mod raster; #[cfg(feature = "svg")] -use iced_graphics::image::vector; +mod vector; -use crate::Transformation; use atlas::Atlas; -use iced_graphics::layer; -use iced_native::{Rectangle, Size}; +use crate::core::{Rectangle, Size}; +use crate::graphics::Transformation; +use crate::layer; +use crate::Buffer; use std::cell::RefCell; use std::mem; @@ -18,10 +19,10 @@ use std::mem; use bytemuck::{Pod, Zeroable}; #[cfg(feature = "image")] -use iced_native::image; +use crate::core::image; #[cfg(feature = "svg")] -use iced_native::svg; +use crate::core::svg; #[cfg(feature = "tracing")] use tracing::info_span; @@ -29,20 +30,112 @@ use tracing::info_span; #[derive(Debug)] pub struct Pipeline { #[cfg(feature = "image")] - raster_cache: RefCell<raster::Cache<Atlas>>, + raster_cache: RefCell<raster::Cache>, #[cfg(feature = "svg")] - vector_cache: RefCell<vector::Cache<Atlas>>, + vector_cache: RefCell<vector::Cache>, pipeline: wgpu::RenderPipeline, - uniforms: wgpu::Buffer, vertices: wgpu::Buffer, indices: wgpu::Buffer, - instances: wgpu::Buffer, - constants: wgpu::BindGroup, + sampler: wgpu::Sampler, texture: wgpu::BindGroup, texture_version: usize, - texture_layout: wgpu::BindGroupLayout, texture_atlas: Atlas, + texture_layout: wgpu::BindGroupLayout, + constant_layout: wgpu::BindGroupLayout, + + layers: Vec<Layer>, + prepare_layer: usize, +} + +#[derive(Debug)] +struct Layer { + uniforms: wgpu::Buffer, + constants: wgpu::BindGroup, + instances: Buffer<Instance>, + instance_count: usize, +} + +impl Layer { + fn new( + device: &wgpu::Device, + constant_layout: &wgpu::BindGroupLayout, + sampler: &wgpu::Sampler, + ) -> Self { + let uniforms = device.create_buffer(&wgpu::BufferDescriptor { + label: Some("iced_wgpu::image uniforms buffer"), + size: mem::size_of::<Uniforms>() as u64, + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, + }); + + let constants = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("iced_wgpu::image constants bind group"), + layout: constant_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::Buffer( + wgpu::BufferBinding { + buffer: &uniforms, + offset: 0, + size: None, + }, + ), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(sampler), + }, + ], + }); + + let instances = Buffer::new( + device, + "iced_wgpu::image instance buffer", + Instance::INITIAL, + wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, + ); + + Self { + uniforms, + constants, + instances, + instance_count: 0, + } + } + + fn prepare( + &mut self, + device: &wgpu::Device, + queue: &wgpu::Queue, + instances: &[Instance], + transformation: Transformation, + ) { + queue.write_buffer( + &self.uniforms, + 0, + bytemuck::bytes_of(&Uniforms { + transform: transformation.into(), + }), + ); + + let _ = self.instances.resize(device, instances.len()); + self.instances.write(queue, 0, instances); + + self.instance_count = instances.len(); + } + + fn render<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) { + render_pass.set_bind_group(0, &self.constants, &[]); + render_pass.set_vertex_buffer(1, self.instances.slice(..)); + + render_pass.draw_indexed( + 0..QUAD_INDICES.len() as u32, + 0, + 0..self.instance_count as u32, + ); + } } impl Pipeline { @@ -86,35 +179,6 @@ impl Pipeline { ], }); - let uniforms_buffer = device.create_buffer(&wgpu::BufferDescriptor { - label: Some("iced_wgpu::image uniforms buffer"), - size: mem::size_of::<Uniforms>() as u64, - usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, - mapped_at_creation: false, - }); - - let constant_bind_group = - device.create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("iced_wgpu::image constants bind group"), - layout: &constant_layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::Buffer( - wgpu::BufferBinding { - buffer: &uniforms_buffer, - offset: 0, - size: None, - }, - ), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: wgpu::BindingResource::Sampler(&sampler), - }, - ], - }); - let texture_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: Some("iced_wgpu::image texture atlas layout"), @@ -225,13 +289,6 @@ impl Pipeline { usage: wgpu::BufferUsages::INDEX, }); - let instances = device.create_buffer(&wgpu::BufferDescriptor { - label: Some("iced_wgpu::image instance buffer"), - size: mem::size_of::<Instance>() as u64 * Instance::MAX as u64, - usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, - mapped_at_creation: false, - }); - let texture_atlas = Atlas::new(device); let texture = device.create_bind_group(&wgpu::BindGroupDescriptor { @@ -253,15 +310,17 @@ impl Pipeline { vector_cache: RefCell::new(vector::Cache::default()), pipeline, - uniforms: uniforms_buffer, vertices, indices, - instances, - constants: constant_bind_group, + sampler, texture, texture_version: texture_atlas.layer_count(), - texture_layout, texture_atlas, + texture_layout, + constant_layout, + + layers: Vec::new(), + prepare_layer: 0, } } @@ -281,18 +340,19 @@ impl Pipeline { svg.viewport_dimensions() } - pub fn draw( + pub fn prepare( &mut self, device: &wgpu::Device, - staging_belt: &mut wgpu::util::StagingBelt, + queue: &wgpu::Queue, encoder: &mut wgpu::CommandEncoder, images: &[layer::Image], transformation: Transformation, - bounds: Rectangle<u32>, - target: &wgpu::TextureView, _scale: f32, ) { #[cfg(feature = "tracing")] + let _ = info_span!("Wgpu::Image", "PREPARE").entered(); + + #[cfg(feature = "tracing")] let _ = info_span!("Wgpu::Image", "DRAW").entered(); let instances: &mut Vec<Instance> = &mut Vec::new(); @@ -308,8 +368,10 @@ impl Pipeline { #[cfg(feature = "image")] layer::Image::Raster { handle, bounds } => { if let Some(atlas_entry) = raster_cache.upload( + device, + queue, + encoder, handle, - &mut (device, encoder), &mut self.texture_atlas, ) { add_instances( @@ -332,11 +394,13 @@ impl Pipeline { let size = [bounds.width, bounds.height]; if let Some(atlas_entry) = vector_cache.upload( + device, + queue, + encoder, handle, *color, size, _scale, - &mut (device, encoder), &mut self.texture_atlas, ) { add_instances( @@ -376,68 +440,28 @@ impl Pipeline { self.texture_version = texture_version; } - { - let mut uniforms_buffer = staging_belt.write_buffer( - encoder, - &self.uniforms, - 0, - wgpu::BufferSize::new(mem::size_of::<Uniforms>() as u64) - .unwrap(), + if self.layers.len() <= self.prepare_layer { + self.layers.push(Layer::new( device, - ); - - uniforms_buffer.copy_from_slice(bytemuck::bytes_of(&Uniforms { - transform: transformation.into(), - })); + &self.constant_layout, + &self.sampler, + )); } - let mut i = 0; - let total = instances.len(); - - while i < total { - let end = (i + Instance::MAX).min(total); - let amount = end - i; - - let mut instances_buffer = staging_belt.write_buffer( - encoder, - &self.instances, - 0, - wgpu::BufferSize::new( - (amount * std::mem::size_of::<Instance>()) as u64, - ) - .unwrap(), - device, - ); + let layer = &mut self.layers[self.prepare_layer]; + layer.prepare(device, queue, instances, transformation); - instances_buffer.copy_from_slice(bytemuck::cast_slice( - &instances[i..i + amount], - )); - - let mut render_pass = - encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("iced_wgpu::image render pass"), - color_attachments: &[Some( - wgpu::RenderPassColorAttachment { - view: target, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Load, - store: true, - }, - }, - )], - depth_stencil_attachment: None, - }); + self.prepare_layer += 1; + } + pub fn render<'a>( + &'a self, + layer: usize, + bounds: Rectangle<u32>, + render_pass: &mut wgpu::RenderPass<'a>, + ) { + if let Some(layer) = self.layers.get(layer) { render_pass.set_pipeline(&self.pipeline); - render_pass.set_bind_group(0, &self.constants, &[]); - render_pass.set_bind_group(1, &self.texture, &[]); - render_pass.set_index_buffer( - self.indices.slice(..), - wgpu::IndexFormat::Uint16, - ); - render_pass.set_vertex_buffer(0, self.vertices.slice(..)); - render_pass.set_vertex_buffer(1, self.instances.slice(..)); render_pass.set_scissor_rect( bounds.x, @@ -446,30 +470,25 @@ impl Pipeline { bounds.height, ); - render_pass.draw_indexed( - 0..QUAD_INDICES.len() as u32, - 0, - 0..amount as u32, + render_pass.set_bind_group(1, &self.texture, &[]); + render_pass.set_index_buffer( + self.indices.slice(..), + wgpu::IndexFormat::Uint16, ); + render_pass.set_vertex_buffer(0, self.vertices.slice(..)); - i += Instance::MAX; + layer.render(render_pass); } } - pub fn trim_cache( - &mut self, - device: &wgpu::Device, - encoder: &mut wgpu::CommandEncoder, - ) { + pub fn end_frame(&mut self) { #[cfg(feature = "image")] - self.raster_cache - .borrow_mut() - .trim(&mut self.texture_atlas, &mut (device, encoder)); + self.raster_cache.borrow_mut().trim(&mut self.texture_atlas); #[cfg(feature = "svg")] - self.vector_cache - .borrow_mut() - .trim(&mut self.texture_atlas, &mut (device, encoder)); + self.vector_cache.borrow_mut().trim(&mut self.texture_atlas); + + self.prepare_layer = 0; } } @@ -507,7 +526,7 @@ struct Instance { } impl Instance { - pub const MAX: usize = 1_000; + pub const INITIAL: usize = 1_000; } #[repr(C)] diff --git a/wgpu/src/image/atlas.rs b/wgpu/src/image/atlas.rs index a0fdf146..366fe623 100644 --- a/wgpu/src/image/atlas.rs +++ b/wgpu/src/image/atlas.rs @@ -12,8 +12,7 @@ use allocator::Allocator; pub const SIZE: u32 = 2048; -use iced_graphics::image; -use iced_graphics::Size; +use crate::core::Size; #[derive(Debug)] pub struct Atlas { @@ -37,10 +36,10 @@ impl Atlas { sample_count: 1, dimension: wgpu::TextureDimension::D2, format: wgpu::TextureFormat::Rgba8UnormSrgb, - view_formats: &[], usage: wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::COPY_SRC | wgpu::TextureUsages::TEXTURE_BINDING, + view_formats: &[], }); let texture_view = texture.create_view(&wgpu::TextureViewDescriptor { @@ -63,6 +62,97 @@ impl Atlas { self.layers.len() } + pub fn upload( + &mut self, + device: &wgpu::Device, + queue: &wgpu::Queue, + encoder: &mut wgpu::CommandEncoder, + width: u32, + height: u32, + data: &[u8], + ) -> Option<Entry> { + let entry = { + let current_size = self.layers.len(); + let entry = self.allocate(width, height)?; + + // We grow the internal texture after allocating if necessary + let new_layers = self.layers.len() - current_size; + self.grow(new_layers, device, encoder); + + entry + }; + + log::info!("Allocated atlas entry: {:?}", entry); + + // It is a webgpu requirement that: + // BufferCopyView.layout.bytes_per_row % wgpu::COPY_BYTES_PER_ROW_ALIGNMENT == 0 + // So we calculate padded_width by rounding width up to the next + // multiple of wgpu::COPY_BYTES_PER_ROW_ALIGNMENT. + let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT; + let padding = (align - (4 * width) % align) % align; + let padded_width = (4 * width + padding) as usize; + let padded_data_size = padded_width * height as usize; + + let mut padded_data = vec![0; padded_data_size]; + + for row in 0..height as usize { + let offset = row * padded_width; + + padded_data[offset..offset + 4 * width as usize].copy_from_slice( + &data[row * 4 * width as usize..(row + 1) * 4 * width as usize], + ) + } + + match &entry { + Entry::Contiguous(allocation) => { + self.upload_allocation( + &padded_data, + width, + height, + padding, + 0, + allocation, + queue, + ); + } + Entry::Fragmented { fragments, .. } => { + for fragment in fragments { + let (x, y) = fragment.position; + let offset = (y * padded_width as u32 + 4 * x) as usize; + + self.upload_allocation( + &padded_data, + width, + height, + padding, + offset, + &fragment.allocation, + queue, + ); + } + } + } + + log::info!("Current atlas: {:?}", self); + + Some(entry) + } + + pub fn remove(&mut self, entry: &Entry) { + log::info!("Removing atlas entry: {:?}", entry); + + match entry { + Entry::Contiguous(allocation) => { + self.deallocate(allocation); + } + Entry::Fragmented { fragments, .. } => { + for fragment in fragments { + self.deallocate(&fragment.allocation); + } + } + } + } + fn allocate(&mut self, width: u32, height: u32) -> Option<Entry> { // Allocate one layer if texture fits perfectly if width == SIZE && height == SIZE { @@ -184,13 +274,13 @@ impl Atlas { fn upload_allocation( &mut self, - buffer: &wgpu::Buffer, + data: &[u8], image_width: u32, image_height: u32, padding: u32, offset: usize, allocation: &Allocation, - encoder: &mut wgpu::CommandEncoder, + queue: &wgpu::Queue, ) { let (x, y) = allocation.position(); let Size { width, height } = allocation.size(); @@ -202,15 +292,7 @@ impl Atlas { depth_or_array_layers: 1, }; - encoder.copy_buffer_to_texture( - wgpu::ImageCopyBuffer { - buffer, - layout: wgpu::ImageDataLayout { - offset: offset as u64, - bytes_per_row: Some(4 * image_width + padding), - rows_per_image: Some(image_height), - }, - }, + queue.write_texture( wgpu::ImageCopyTexture { texture: &self.texture, mip_level: 0, @@ -221,6 +303,12 @@ impl Atlas { }, aspect: wgpu::TextureAspect::default(), }, + data, + wgpu::ImageDataLayout { + offset: offset as u64, + bytes_per_row: Some(4 * image_width + padding), + rows_per_image: Some(image_height), + }, extent, ); } @@ -246,10 +334,10 @@ impl Atlas { sample_count: 1, dimension: wgpu::TextureDimension::D2, format: wgpu::TextureFormat::Rgba8UnormSrgb, - view_formats: &[], usage: wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::COPY_SRC | wgpu::TextureUsages::TEXTURE_BINDING, + view_formats: &[], }); let amount_to_copy = self.layers.len() - amount; @@ -298,100 +386,3 @@ impl Atlas { }); } } - -impl image::Storage for Atlas { - type Entry = Entry; - type State<'a> = (&'a wgpu::Device, &'a mut wgpu::CommandEncoder); - - fn upload( - &mut self, - width: u32, - height: u32, - data: &[u8], - (device, encoder): &mut Self::State<'_>, - ) -> Option<Self::Entry> { - use wgpu::util::DeviceExt; - - let entry = { - let current_size = self.layers.len(); - let entry = self.allocate(width, height)?; - - // We grow the internal texture after allocating if necessary - let new_layers = self.layers.len() - current_size; - self.grow(new_layers, device, encoder); - - entry - }; - - log::info!("Allocated atlas entry: {:?}", entry); - - // It is a webgpu requirement that: - // BufferCopyView.layout.bytes_per_row % wgpu::COPY_BYTES_PER_ROW_ALIGNMENT == 0 - // So we calculate padded_width by rounding width up to the next - // multiple of wgpu::COPY_BYTES_PER_ROW_ALIGNMENT. - let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT; - let padding = (align - (4 * width) % align) % align; - let padded_width = (4 * width + padding) as usize; - let padded_data_size = padded_width * height as usize; - - let mut padded_data = vec![0; padded_data_size]; - - for row in 0..height as usize { - let offset = row * padded_width; - - padded_data[offset..offset + 4 * width as usize].copy_from_slice( - &data[row * 4 * width as usize..(row + 1) * 4 * width as usize], - ) - } - - let buffer = - device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("iced_wgpu::image staging buffer"), - contents: &padded_data, - usage: wgpu::BufferUsages::COPY_SRC, - }); - - match &entry { - Entry::Contiguous(allocation) => { - self.upload_allocation( - &buffer, width, height, padding, 0, allocation, encoder, - ); - } - Entry::Fragmented { fragments, .. } => { - for fragment in fragments { - let (x, y) = fragment.position; - let offset = (y * padded_width as u32 + 4 * x) as usize; - - self.upload_allocation( - &buffer, - width, - height, - padding, - offset, - &fragment.allocation, - encoder, - ); - } - } - } - - log::info!("Current atlas: {:?}", self); - - Some(entry) - } - - fn remove(&mut self, entry: &Entry, _: &mut Self::State<'_>) { - log::info!("Removing atlas entry: {:?}", entry); - - match entry { - Entry::Contiguous(allocation) => { - self.deallocate(allocation); - } - Entry::Fragmented { fragments, .. } => { - for fragment in fragments { - self.deallocate(&fragment.allocation); - } - } - } - } -} diff --git a/wgpu/src/image/atlas/allocation.rs b/wgpu/src/image/atlas/allocation.rs index 43aba875..11289771 100644 --- a/wgpu/src/image/atlas/allocation.rs +++ b/wgpu/src/image/atlas/allocation.rs @@ -1,7 +1,6 @@ +use crate::core::Size; use crate::image::atlas::{self, allocator}; -use iced_graphics::Size; - #[derive(Debug)] pub enum Allocation { Partial { diff --git a/wgpu/src/image/atlas/allocator.rs b/wgpu/src/image/atlas/allocator.rs index 03effdcb..204a5c26 100644 --- a/wgpu/src/image/atlas/allocator.rs +++ b/wgpu/src/image/atlas/allocator.rs @@ -46,10 +46,10 @@ impl Region { (rectangle.min.x as u32, rectangle.min.y as u32) } - pub fn size(&self) -> iced_graphics::Size<u32> { + pub fn size(&self) -> crate::core::Size<u32> { let size = self.allocation.rectangle.size(); - iced_graphics::Size::new(size.width as u32, size.height as u32) + crate::core::Size::new(size.width as u32, size.height as u32) } } diff --git a/wgpu/src/image/atlas/entry.rs b/wgpu/src/image/atlas/entry.rs index 69c05a50..7e4c92a2 100644 --- a/wgpu/src/image/atlas/entry.rs +++ b/wgpu/src/image/atlas/entry.rs @@ -1,8 +1,6 @@ +use crate::core::Size; use crate::image::atlas; -use iced_graphics::image; -use iced_graphics::Size; - #[derive(Debug)] pub enum Entry { Contiguous(atlas::Allocation), @@ -12,8 +10,9 @@ pub enum Entry { }, } -impl image::storage::Entry for Entry { - fn size(&self) -> Size<u32> { +impl Entry { + #[cfg(feature = "image")] + pub fn size(&self) -> Size<u32> { match self { Entry::Contiguous(allocation) => allocation.size(), Entry::Fragmented { size, .. } => *size, diff --git a/wgpu/src/image/raster.rs b/wgpu/src/image/raster.rs new file mode 100644 index 00000000..9b38dce4 --- /dev/null +++ b/wgpu/src/image/raster.rs @@ -0,0 +1,121 @@ +use crate::core::image; +use crate::core::Size; +use crate::graphics; +use crate::graphics::image::image_rs; +use crate::image::atlas::{self, Atlas}; + +use std::collections::{HashMap, HashSet}; + +/// Entry in cache corresponding to an image handle +#[derive(Debug)] +pub enum Memory { + /// Image data on host + Host(image_rs::ImageBuffer<image_rs::Rgba<u8>, Vec<u8>>), + /// Storage entry + Device(atlas::Entry), + /// Image not found + NotFound, + /// Invalid image data + Invalid, +} + +impl Memory { + /// Width and height of image + pub fn dimensions(&self) -> Size<u32> { + match self { + Memory::Host(image) => { + let (width, height) = image.dimensions(); + + Size::new(width, height) + } + Memory::Device(entry) => entry.size(), + Memory::NotFound => Size::new(1, 1), + Memory::Invalid => Size::new(1, 1), + } + } +} + +/// Caches image raster data +#[derive(Debug, Default)] +pub struct Cache { + map: HashMap<u64, Memory>, + hits: HashSet<u64>, +} + +impl Cache { + /// Load image + pub fn load(&mut self, handle: &image::Handle) -> &mut Memory { + if self.contains(handle) { + return self.get(handle).unwrap(); + } + + let memory = match graphics::image::load(handle) { + Ok(image) => Memory::Host(image.to_rgba8()), + Err(image_rs::error::ImageError::IoError(_)) => Memory::NotFound, + Err(_) => Memory::Invalid, + }; + + self.insert(handle, memory); + self.get(handle).unwrap() + } + + /// Load image and upload raster data + pub fn upload( + &mut self, + device: &wgpu::Device, + queue: &wgpu::Queue, + encoder: &mut wgpu::CommandEncoder, + handle: &image::Handle, + atlas: &mut Atlas, + ) -> Option<&atlas::Entry> { + let memory = self.load(handle); + + if let Memory::Host(image) = memory { + let (width, height) = image.dimensions(); + + let entry = + atlas.upload(device, queue, encoder, width, height, image)?; + + *memory = Memory::Device(entry); + } + + if let Memory::Device(allocation) = memory { + Some(allocation) + } else { + None + } + } + + /// Trim cache misses from cache + pub fn trim(&mut self, atlas: &mut Atlas) { + let hits = &self.hits; + + self.map.retain(|k, memory| { + let retain = hits.contains(k); + + if !retain { + if let Memory::Device(entry) = memory { + atlas.remove(entry); + } + } + + retain + }); + + self.hits.clear(); + } + + fn get(&mut self, handle: &image::Handle) -> Option<&mut Memory> { + let _ = self.hits.insert(handle.id()); + + self.map.get_mut(&handle.id()) + } + + fn insert(&mut self, handle: &image::Handle, memory: Memory) { + let _ = self.map.insert(handle.id(), memory); + } + + fn contains(&self, handle: &image::Handle) -> bool { + self.map.contains_key(&handle.id()) + } +} diff --git a/graphics/src/image/vector.rs b/wgpu/src/image/vector.rs index c950ccd6..58bdf64a 100644 --- a/graphics/src/image/vector.rs +++ b/wgpu/src/image/vector.rs @@ -1,9 +1,6 @@ -//! Vector image loading and caching -use crate::image::Storage; -use crate::Color; - -use iced_native::svg; -use iced_native::Size; +use crate::core::svg; +use crate::core::{Color, Size}; +use crate::image::atlas::{self, Atlas}; use resvg::tiny_skia; use resvg::usvg; @@ -33,19 +30,21 @@ impl Svg { } /// Caches svg vector and raster data -#[derive(Debug)] -pub struct Cache<T: Storage> { +#[derive(Debug, Default)] +pub struct Cache { svgs: HashMap<u64, Svg>, - rasterized: HashMap<(u64, u32, u32, ColorFilter), T::Entry>, + rasterized: HashMap<(u64, u32, u32, ColorFilter), atlas::Entry>, svg_hits: HashSet<u64>, rasterized_hits: HashSet<(u64, u32, u32, ColorFilter)>, } type ColorFilter = Option<[u8; 4]>; -impl<T: Storage> Cache<T> { +impl Cache { /// Load svg pub fn load(&mut self, handle: &svg::Handle) -> &Svg { + use usvg::TreeParsing; + if self.svgs.contains_key(&handle.id()) { return self.svgs.get(&handle.id()).unwrap(); } @@ -74,13 +73,15 @@ impl<T: Storage> Cache<T> { /// Load svg and upload raster data pub fn upload( &mut self, + device: &wgpu::Device, + queue: &wgpu::Queue, + encoder: &mut wgpu::CommandEncoder, handle: &svg::Handle, color: Option<Color>, [width, height]: [f32; 2], scale: f32, - state: &mut T::State<'_>, - storage: &mut T, - ) -> Option<&T::Entry> { + atlas: &mut Atlas, + ) -> Option<&atlas::Entry> { let id = handle.id(); let (width, height) = ( @@ -117,9 +118,9 @@ impl<T: Storage> Cache<T> { resvg::render( tree, if width > height { - usvg::FitTo::Width(width) + resvg::FitTo::Width(width) } else { - usvg::FitTo::Height(height) + resvg::FitTo::Height(height) }, tiny_skia::Transform::default(), img.as_mut(), @@ -137,7 +138,9 @@ impl<T: Storage> Cache<T> { }); } - let allocation = storage.upload(width, height, &rgba, state)?; + let allocation = atlas + .upload(device, queue, encoder, width, height, &rgba)?; + log::debug!("allocating {} {}x{}", id, width, height); let _ = self.svg_hits.insert(id); @@ -151,7 +154,7 @@ impl<T: Storage> Cache<T> { } /// Load svg and upload raster data - pub fn trim(&mut self, storage: &mut T, state: &mut T::State<'_>) { + pub fn trim(&mut self, atlas: &mut Atlas) { let svg_hits = &self.svg_hits; let rasterized_hits = &self.rasterized_hits; @@ -160,7 +163,7 @@ impl<T: Storage> Cache<T> { let retain = rasterized_hits.contains(k); if !retain { - storage.remove(entry, state); + atlas.remove(entry); } retain @@ -170,17 +173,6 @@ impl<T: Storage> Cache<T> { } } -impl<T: Storage> Default for Cache<T> { - fn default() -> Self { - Self { - svgs: HashMap::new(), - rasterized: HashMap::new(), - svg_hits: HashSet::new(), - rasterized_hits: HashSet::new(), - } - } -} - impl std::fmt::Debug for Svg { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { diff --git a/graphics/src/layer.rs b/wgpu/src/layer.rs index 1d453caa..8af72b9d 100644 --- a/graphics/src/layer.rs +++ b/wgpu/src/layer.rs @@ -10,10 +10,10 @@ pub use mesh::Mesh; pub use quad::Quad; pub use text::Text; -use crate::alignment; -use crate::{ - Background, Font, Point, Primitive, Rectangle, Size, Vector, Viewport, -}; +use crate::core; +use crate::core::alignment; +use crate::core::{Background, Color, Font, Point, Rectangle, Size, Vector}; +use crate::graphics::{Primitive, Viewport}; /// A group of primitives that should be clipped together. #[derive(Debug)] @@ -60,18 +60,20 @@ impl<'a> Layer<'a> { Point::new(11.0, 11.0 + 25.0 * i as f32), Size::INFINITY, ), - color: [0.9, 0.9, 0.9, 1.0], + color: Color::new(0.9, 0.9, 0.9, 1.0), size: 20.0, - font: Font::Default, + line_height: core::text::LineHeight::default(), + font: Font::MONOSPACE, horizontal_alignment: alignment::Horizontal::Left, vertical_alignment: alignment::Vertical::Top, + shaping: core::text::Shaping::Basic, }; overlay.text.push(text); overlay.text.push(Text { bounds: text.bounds + Vector::new(-1.0, -1.0), - color: [0.0, 0.0, 0.0, 1.0], + color: Color::BLACK, ..text }); } @@ -109,26 +111,16 @@ impl<'a> Layer<'a> { current_layer: usize, ) { match primitive { - Primitive::None => {} - Primitive::Group { primitives } => { - // TODO: Inspect a bit and regroup (?) - for primitive in primitives { - Self::process_primitive( - layers, - translation, - primitive, - current_layer, - ) - } - } Primitive::Text { content, bounds, size, + line_height, color, font, horizontal_alignment, vertical_alignment, + shaping, } => { let layer = &mut layers[current_layer]; @@ -136,10 +128,12 @@ impl<'a> Layer<'a> { content, bounds: *bounds + translation, size: *size, - color: color.into_linear(), + line_height: *line_height, + color: *color, font: *font, horizontal_alignment: *horizontal_alignment, vertical_alignment: *vertical_alignment, + shaping: *shaping, }); } Primitive::Quad { @@ -166,6 +160,27 @@ impl<'a> Layer<'a> { border_color: border_color.into_linear(), }); } + Primitive::Image { handle, bounds } => { + let layer = &mut layers[current_layer]; + + layer.images.push(Image::Raster { + handle: handle.clone(), + bounds: *bounds + translation, + }); + } + Primitive::Svg { + handle, + color, + bounds, + } => { + let layer = &mut layers[current_layer]; + + layer.images.push(Image::Vector { + handle: handle.clone(), + color: *color, + bounds: *bounds + translation, + }); + } Primitive::SolidMesh { buffers, size } => { let layer = &mut layers[current_layer]; @@ -205,6 +220,17 @@ impl<'a> Layer<'a> { }); } } + Primitive::Group { primitives } => { + // TODO: Inspect a bit and regroup (?) + for primitive in primitives { + Self::process_primitive( + layers, + translation, + primitive, + current_layer, + ) + } + } Primitive::Clip { bounds, content } => { let layer = &mut layers[current_layer]; let translated_bounds = *bounds + translation; @@ -235,34 +261,20 @@ impl<'a> Layer<'a> { current_layer, ); } - Primitive::Cached { cache } => { + Primitive::Cache { content } => { Self::process_primitive( layers, translation, - cache, + content, current_layer, ); } - Primitive::Image { handle, bounds } => { - let layer = &mut layers[current_layer]; - - layer.images.push(Image::Raster { - handle: handle.clone(), - bounds: *bounds + translation, - }); - } - Primitive::Svg { - handle, - color, - bounds, - } => { - let layer = &mut layers[current_layer]; - - layer.images.push(Image::Vector { - handle: handle.clone(), - color: *color, - bounds: *bounds + translation, - }); + _ => { + // Not supported! + log::warn!( + "Unsupported primitive in `iced_wgpu`: {:?}", + primitive + ); } } } diff --git a/graphics/src/layer/image.rs b/wgpu/src/layer/image.rs index 3eff2397..0de589f8 100644 --- a/graphics/src/layer/image.rs +++ b/wgpu/src/layer/image.rs @@ -1,6 +1,6 @@ -use crate::{Color, Rectangle}; - -use iced_native::{image, svg}; +use crate::core::image; +use crate::core::svg; +use crate::core::{Color, Rectangle}; /// A raster or vector image. #[derive(Debug, Clone)] diff --git a/graphics/src/layer/mesh.rs b/wgpu/src/layer/mesh.rs index 7661c5c9..9dd14391 100644 --- a/graphics/src/layer/mesh.rs +++ b/wgpu/src/layer/mesh.rs @@ -1,6 +1,6 @@ //! A collection of triangle primitives. -use crate::triangle; -use crate::{Gradient, Point, Rectangle}; +use crate::core::{Gradient, Point, Rectangle}; +use crate::graphics::primitive; /// A mesh of triangles. #[derive(Debug, Clone, Copy)] @@ -11,7 +11,7 @@ pub enum Mesh<'a> { origin: Point, /// The vertex and index buffers of the [`Mesh`]. - buffers: &'a triangle::Mesh2D<triangle::ColoredVertex2D>, + buffers: &'a primitive::Mesh2D<primitive::ColoredVertex2D>, /// The clipping bounds of the [`Mesh`]. clip_bounds: Rectangle<f32>, @@ -22,7 +22,7 @@ pub enum Mesh<'a> { origin: Point, /// The vertex and index buffers of the [`Mesh`]. - buffers: &'a triangle::Mesh2D<triangle::Vertex2D>, + buffers: &'a primitive::Mesh2D<primitive::Vertex2D>, /// The clipping bounds of the [`Mesh`]. clip_bounds: Rectangle<f32>, diff --git a/graphics/src/layer/quad.rs b/wgpu/src/layer/quad.rs index 0d8bde9d..0d8bde9d 100644 --- a/graphics/src/layer/quad.rs +++ b/wgpu/src/layer/quad.rs diff --git a/graphics/src/layer/text.rs b/wgpu/src/layer/text.rs index 74f7a676..ba1bdca8 100644 --- a/graphics/src/layer/text.rs +++ b/wgpu/src/layer/text.rs @@ -1,4 +1,6 @@ -use crate::{alignment, Font, Rectangle}; +use crate::core::alignment; +use crate::core::text; +use crate::core::{Color, Font, Rectangle}; /// A paragraph of text. #[derive(Debug, Clone, Copy)] @@ -10,11 +12,14 @@ pub struct Text<'a> { pub bounds: Rectangle, /// The color of the [`Text`], in __linear RGB_. - pub color: [f32; 4], + pub color: Color, - /// The size of the [`Text`]. + /// The size of the [`Text`] in logical pixels. pub size: f32, + /// The line height of the [`Text`]. + pub line_height: text::LineHeight, + /// The font of the [`Text`]. pub font: Font, @@ -23,4 +28,7 @@ pub struct Text<'a> { /// The vertical alignment of the [`Text`]. pub vertical_alignment: alignment::Vertical, + + /// The shaping strategy of the text. + pub shaping: text::Shaping, } diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 969e3199..4a92c345 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -37,24 +37,29 @@ #![forbid(rust_2018_idioms)] #![allow(clippy::inherent_to_string, clippy::type_complexity)] #![cfg_attr(docsrs, feature(doc_cfg))] - +pub mod layer; pub mod settings; pub mod window; +#[cfg(feature = "geometry")] +pub mod geometry; + mod backend; mod buffer; mod quad; mod text; mod triangle; -pub use iced_graphics::{Antialiasing, Color, Error, Primitive, Viewport}; -pub use iced_native::Theme; +pub use iced_graphics as graphics; +pub use iced_graphics::core; + pub use wgpu; pub use backend::Backend; +pub use layer::Layer; pub use settings::Settings; -pub(crate) use iced_graphics::Transformation; +use buffer::Buffer; #[cfg(any(feature = "image", feature = "svg"))] mod image; @@ -63,5 +68,4 @@ mod image; /// /// [`wgpu`]: https://github.com/gfx-rs/wgpu-rs /// [`iced`]: https://github.com/iced-rs/iced -pub type Renderer<Theme = iced_native::Theme> = - iced_graphics::Renderer<Backend, Theme>; +pub type Renderer<Theme> = iced_graphics::Renderer<Backend, Theme>; diff --git a/wgpu/src/quad.rs b/wgpu/src/quad.rs index 1343181e..8fa7359e 100644 --- a/wgpu/src/quad.rs +++ b/wgpu/src/quad.rs @@ -1,6 +1,7 @@ -use crate::Transformation; -use iced_graphics::layer; -use iced_native::Rectangle; +use crate::core::Rectangle; +use crate::graphics::Transformation; +use crate::layer; +use crate::Buffer; use bytemuck::{Pod, Zeroable}; use std::mem; @@ -12,11 +13,11 @@ use tracing::info_span; #[derive(Debug)] pub struct Pipeline { pipeline: wgpu::RenderPipeline, - constants: wgpu::BindGroup, - constants_buffer: wgpu::Buffer, + constant_layout: wgpu::BindGroupLayout, vertices: wgpu::Buffer, indices: wgpu::Buffer, - instances: wgpu::Buffer, + layers: Vec<Layer>, + prepare_layer: usize, } impl Pipeline { @@ -38,22 +39,6 @@ impl Pipeline { }], }); - let constants_buffer = device.create_buffer(&wgpu::BufferDescriptor { - label: Some("iced_wgpu::quad uniforms buffer"), - size: mem::size_of::<Uniforms>() as wgpu::BufferAddress, - usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, - mapped_at_creation: false, - }); - - let constants = device.create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("iced_wgpu::quad uniforms bind group"), - layout: &constant_layout, - entries: &[wgpu::BindGroupEntry { - binding: 0, - resource: constants_buffer.as_entire_binding(), - }], - }); - let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: Some("iced_wgpu::quad pipeline layout"), @@ -148,118 +133,146 @@ impl Pipeline { usage: wgpu::BufferUsages::INDEX, }); - let instances = device.create_buffer(&wgpu::BufferDescriptor { - label: Some("iced_wgpu::quad instance buffer"), - size: mem::size_of::<layer::Quad>() as u64 * MAX_INSTANCES as u64, - usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, - mapped_at_creation: false, - }); - Pipeline { pipeline, - constants, - constants_buffer, + constant_layout, vertices, indices, - instances, + layers: Vec::new(), + prepare_layer: 0, } } - pub fn draw( + pub fn prepare( &mut self, device: &wgpu::Device, - staging_belt: &mut wgpu::util::StagingBelt, - encoder: &mut wgpu::CommandEncoder, + queue: &wgpu::Queue, instances: &[layer::Quad], transformation: Transformation, scale: f32, - bounds: Rectangle<u32>, - target: &wgpu::TextureView, ) { - #[cfg(feature = "tracing")] - let _ = info_span!("Wgpu::Quad", "DRAW").entered(); + if self.layers.len() <= self.prepare_layer { + self.layers.push(Layer::new(device, &self.constant_layout)); + } - let uniforms = Uniforms::new(transformation, scale); + let layer = &mut self.layers[self.prepare_layer]; + layer.prepare(device, queue, instances, transformation, scale); + + self.prepare_layer += 1; + } + + pub fn render<'a>( + &'a self, + layer: usize, + bounds: Rectangle<u32>, + render_pass: &mut wgpu::RenderPass<'a>, + ) { + if let Some(layer) = self.layers.get(layer) { + render_pass.set_pipeline(&self.pipeline); + + render_pass.set_scissor_rect( + bounds.x, + bounds.y, + bounds.width, + bounds.height, + ); - { - let mut constants_buffer = staging_belt.write_buffer( - encoder, - &self.constants_buffer, - 0, - wgpu::BufferSize::new(mem::size_of::<Uniforms>() as u64) - .unwrap(), - device, + render_pass.set_index_buffer( + self.indices.slice(..), + wgpu::IndexFormat::Uint16, ); + render_pass.set_vertex_buffer(0, self.vertices.slice(..)); - constants_buffer.copy_from_slice(bytemuck::bytes_of(&uniforms)); + layer.draw(render_pass); } + } - let mut i = 0; - let total = instances.len(); + pub fn end_frame(&mut self) { + self.prepare_layer = 0; + } +} - while i < total { - let end = (i + MAX_INSTANCES).min(total); - let amount = end - i; +#[derive(Debug)] +struct Layer { + constants: wgpu::BindGroup, + constants_buffer: wgpu::Buffer, + instances: Buffer<layer::Quad>, + instance_count: usize, +} - let instance_bytes = bytemuck::cast_slice(&instances[i..end]); +impl Layer { + pub fn new( + device: &wgpu::Device, + constant_layout: &wgpu::BindGroupLayout, + ) -> Self { + let constants_buffer = device.create_buffer(&wgpu::BufferDescriptor { + label: Some("iced_wgpu::quad uniforms buffer"), + size: mem::size_of::<Uniforms>() as wgpu::BufferAddress, + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, + }); - let mut instance_buffer = staging_belt.write_buffer( - encoder, - &self.instances, - 0, - wgpu::BufferSize::new(instance_bytes.len() as u64).unwrap(), - device, - ); + let constants = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("iced_wgpu::quad uniforms bind group"), + layout: constant_layout, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: constants_buffer.as_entire_binding(), + }], + }); - instance_buffer.copy_from_slice(instance_bytes); - - #[cfg(feature = "tracing")] - let _ = info_span!("Wgpu::Quad", "BEGIN_RENDER_PASS").enter(); - - { - let mut render_pass = - encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("iced_wgpu::quad render pass"), - color_attachments: &[Some( - wgpu::RenderPassColorAttachment { - view: target, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Load, - store: true, - }, - }, - )], - depth_stencil_attachment: None, - }); - - render_pass.set_pipeline(&self.pipeline); - render_pass.set_bind_group(0, &self.constants, &[]); - render_pass.set_index_buffer( - self.indices.slice(..), - wgpu::IndexFormat::Uint16, - ); - render_pass.set_vertex_buffer(0, self.vertices.slice(..)); - render_pass.set_vertex_buffer(1, self.instances.slice(..)); - - render_pass.set_scissor_rect( - bounds.x, - bounds.y, - bounds.width, - // TODO: Address anti-aliasing adjustments properly - bounds.height, - ); - - render_pass.draw_indexed( - 0..QUAD_INDICES.len() as u32, - 0, - 0..amount as u32, - ); - } - - i += MAX_INSTANCES; + let instances = Buffer::new( + device, + "iced_wgpu::quad instance buffer", + INITIAL_INSTANCES, + wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, + ); + + Self { + constants, + constants_buffer, + instances, + instance_count: 0, } } + + pub fn prepare( + &mut self, + device: &wgpu::Device, + queue: &wgpu::Queue, + instances: &[layer::Quad], + transformation: Transformation, + scale: f32, + ) { + #[cfg(feature = "tracing")] + let _ = info_span!("Wgpu::Quad", "PREPARE").entered(); + + let uniforms = Uniforms::new(transformation, scale); + + queue.write_buffer( + &self.constants_buffer, + 0, + bytemuck::bytes_of(&uniforms), + ); + + let _ = self.instances.resize(device, instances.len()); + self.instances.write(queue, 0, instances); + self.instance_count = instances.len(); + } + + pub fn draw<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) { + #[cfg(feature = "tracing")] + let _ = info_span!("Wgpu::Quad", "DRAW").entered(); + + render_pass.set_bind_group(0, &self.constants, &[]); + render_pass.set_vertex_buffer(1, self.instances.slice(..)); + + render_pass.draw_indexed( + 0..QUAD_INDICES.len() as u32, + 0, + 0..self.instance_count as u32, + ); + } } #[repr(C)] @@ -285,7 +298,7 @@ const QUAD_VERTS: [Vertex; 4] = [ }, ]; -const MAX_INSTANCES: usize = 100_000; +const INITIAL_INSTANCES: usize = 10_000; #[repr(C)] #[derive(Debug, Clone, Copy, Zeroable, Pod)] diff --git a/wgpu/src/settings.rs b/wgpu/src/settings.rs index 5ef79499..266a2c87 100644 --- a/wgpu/src/settings.rs +++ b/wgpu/src/settings.rs @@ -1,12 +1,11 @@ //! Configure a renderer. -use std::fmt; - -pub use crate::Antialiasing; +use crate::core::Font; +use crate::graphics::Antialiasing; /// The settings of a [`Backend`]. /// /// [`Backend`]: crate::Backend -#[derive(Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq)] pub struct Settings { /// The present mode of the [`Backend`]. /// @@ -16,42 +15,20 @@ pub struct Settings { /// The internal graphics backend to use. pub internal_backend: wgpu::Backends, - /// The bytes of the font that will be used by default. - /// - /// If `None` is provided, a default system font will be chosen. - pub default_font: Option<&'static [u8]>, + /// 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: f32, - /// If enabled, spread text workload in multiple threads when multiple cores - /// are available. - /// - /// By default, it is disabled. - pub text_multithreading: bool, - /// The antialiasing strategy that will be used for triangle primitives. /// /// By default, it is `None`. pub antialiasing: Option<Antialiasing>, } -impl fmt::Debug for Settings { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Settings") - .field("present_mode", &self.present_mode) - .field("internal_backend", &self.internal_backend) - // Instead of printing the font bytes, we simply show a `bool` indicating if using a default font or not. - .field("default_font", &self.default_font.is_some()) - .field("default_text_size", &self.default_text_size) - .field("text_multithreading", &self.text_multithreading) - .field("antialiasing", &self.antialiasing) - .finish() - } -} - impl Settings { /// Creates new [`Settings`] using environment configuration. /// @@ -69,7 +46,7 @@ impl Settings { /// - `primary` pub fn from_env() -> Self { Settings { - internal_backend: backend_from_env() + internal_backend: wgpu::util::backend_bits_from_env() .unwrap_or(wgpu::Backends::all()), ..Self::default() } @@ -81,25 +58,9 @@ impl Default for Settings { Settings { present_mode: wgpu::PresentMode::AutoVsync, internal_backend: wgpu::Backends::all(), - default_font: None, - default_text_size: 20.0, - text_multithreading: false, + default_font: Font::default(), + default_text_size: 16.0, antialiasing: None, } } } - -fn backend_from_env() -> Option<wgpu::Backends> { - std::env::var("WGPU_BACKEND").ok().map(|backend| { - match backend.to_lowercase().as_str() { - "vulkan" => wgpu::Backends::VULKAN, - "metal" => wgpu::Backends::METAL, - "dx12" => wgpu::Backends::DX12, - "dx11" => wgpu::Backends::DX11, - "gl" => wgpu::Backends::GL, - "webgpu" => wgpu::Backends::BROWSER_WEBGPU, - "primary" => wgpu::Backends::PRIMARY, - other => panic!("Unknown backend: {other}"), - } - }) -} diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index e17b84c1..714e0400 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -1,265 +1,439 @@ -use crate::Transformation; - -use iced_graphics::font; - -use std::{cell::RefCell, collections::HashMap}; -use wgpu_glyph::ab_glyph; - -pub use iced_native::text::Hit; - -#[derive(Debug)] +use crate::core::alignment; +use crate::core::font::{self, Font}; +use crate::core::text::{Hit, LineHeight, Shaping}; +use crate::core::{Pixels, Point, Rectangle, Size}; +use crate::layer::Text; + +use rustc_hash::{FxHashMap, FxHashSet}; +use std::borrow::Cow; +use std::cell::RefCell; +use std::collections::hash_map; +use std::hash::{BuildHasher, Hash, Hasher}; +use std::sync::Arc; + +#[allow(missing_debug_implementations)] pub struct Pipeline { - draw_brush: RefCell<wgpu_glyph::GlyphBrush<()>>, - draw_font_map: RefCell<HashMap<String, wgpu_glyph::FontId>>, - measure_brush: RefCell<glyph_brush::GlyphBrush<()>>, + font_system: RefCell<glyphon::FontSystem>, + renderers: Vec<glyphon::TextRenderer>, + atlas: glyphon::TextAtlas, + prepare_layer: usize, + measurement_cache: RefCell<Cache>, + render_cache: Cache, } impl Pipeline { pub fn new( device: &wgpu::Device, + queue: &wgpu::Queue, format: wgpu::TextureFormat, - default_font: Option<&[u8]>, - multithreading: bool, ) -> Self { - let default_font = default_font.map(|slice| slice.to_vec()); - - // TODO: Font customization - #[cfg(not(target_os = "ios"))] - #[cfg(feature = "default_system_font")] - let default_font = { - default_font.or_else(|| { - font::Source::new() - .load(&[font::Family::SansSerif, font::Family::Serif]) - .ok() - }) - }; + Pipeline { + font_system: RefCell::new(glyphon::FontSystem::new_with_fonts( + [glyphon::fontdb::Source::Binary(Arc::new( + include_bytes!("../fonts/Iced-Icons.ttf").as_slice(), + ))] + .into_iter(), + )), + renderers: Vec::new(), + atlas: glyphon::TextAtlas::new(device, queue, format), + prepare_layer: 0, + measurement_cache: RefCell::new(Cache::new()), + render_cache: Cache::new(), + } + } - let default_font = - default_font.unwrap_or_else(|| font::FALLBACK.to_vec()); + pub fn load_font(&mut self, bytes: Cow<'static, [u8]>) { + self.font_system.get_mut().db_mut().load_font_source( + glyphon::fontdb::Source::Binary(Arc::new(bytes.into_owned())), + ); + } - let font = ab_glyph::FontArc::try_from_vec(default_font) - .unwrap_or_else(|_| { - log::warn!( - "System font failed to load. Falling back to \ - embedded font..." + pub fn prepare( + &mut self, + device: &wgpu::Device, + queue: &wgpu::Queue, + sections: &[Text<'_>], + bounds: Rectangle, + scale_factor: f32, + target_size: Size<u32>, + ) -> bool { + if self.renderers.len() <= self.prepare_layer { + self.renderers.push(glyphon::TextRenderer::new( + &mut self.atlas, + device, + Default::default(), + None, + )); + } + + let font_system = self.font_system.get_mut(); + let renderer = &mut self.renderers[self.prepare_layer]; + + let keys: Vec<_> = sections + .iter() + .map(|section| { + let (key, _) = self.render_cache.allocate( + font_system, + Key { + content: section.content, + size: section.size * scale_factor, + line_height: f32::from( + section + .line_height + .to_absolute(Pixels(section.size)), + ) * scale_factor, + font: section.font, + bounds: Size { + width: (section.bounds.width * scale_factor).ceil(), + height: (section.bounds.height * scale_factor) + .ceil(), + }, + shaping: section.shaping, + }, ); - ab_glyph::FontArc::try_from_slice(font::FALLBACK) - .expect("Load fallback font") - }); + key + }) + .collect(); + + let bounds = bounds * scale_factor; + + let text_areas = + sections + .iter() + .zip(keys.iter()) + .filter_map(|(section, key)| { + let buffer = + self.render_cache.get(key).expect("Get cached buffer"); + + let (total_lines, max_width) = buffer + .layout_runs() + .enumerate() + .fold((0, 0.0), |(_, max), (i, buffer)| { + (i + 1, buffer.line_w.max(max)) + }); + + let total_height = + total_lines as f32 * buffer.metrics().line_height; + + let x = section.bounds.x * scale_factor; + let y = section.bounds.y * scale_factor; + + let left = match section.horizontal_alignment { + alignment::Horizontal::Left => x, + alignment::Horizontal::Center => x - max_width / 2.0, + alignment::Horizontal::Right => x - max_width, + }; + + let top = match section.vertical_alignment { + alignment::Vertical::Top => y, + alignment::Vertical::Center => y - total_height / 2.0, + alignment::Vertical::Bottom => y - total_height, + }; + + let section_bounds = Rectangle { + x: left, + y: top, + width: section.bounds.width * scale_factor, + height: section.bounds.height * scale_factor, + }; + + let clip_bounds = bounds.intersection(§ion_bounds)?; + + // TODO: Subpixel glyph positioning + let left = left.round() as i32; + let top = top.round() as i32; + + Some(glyphon::TextArea { + buffer, + left, + top, + bounds: glyphon::TextBounds { + left: clip_bounds.x as i32, + top: clip_bounds.y as i32, + right: (clip_bounds.x + clip_bounds.width) as i32, + bottom: (clip_bounds.y + clip_bounds.height) as i32, + }, + default_color: { + let [r, g, b, a] = section.color.into_linear(); + + glyphon::Color::rgba( + (r * 255.0) as u8, + (g * 255.0) as u8, + (b * 255.0) as u8, + (a * 255.0) as u8, + ) + }, + }) + }); + + let result = renderer.prepare( + device, + queue, + font_system, + &mut self.atlas, + glyphon::Resolution { + width: target_size.width, + height: target_size.height, + }, + text_areas, + &mut glyphon::SwashCache::new(), + ); - let draw_brush_builder = - wgpu_glyph::GlyphBrushBuilder::using_font(font.clone()) - .initial_cache_size((2048, 2048)) - .draw_cache_multithread(multithreading); + match result { + Ok(()) => { + self.prepare_layer += 1; - #[cfg(target_arch = "wasm32")] - let draw_brush_builder = draw_brush_builder.draw_cache_align_4x4(true); + true + } + Err(glyphon::PrepareError::AtlasFull(content_type)) => { + self.prepare_layer = 0; + + #[allow(clippy::needless_bool)] + if self.atlas.grow(device, content_type) { + false + } else { + // If the atlas cannot grow, then all bets are off. + // Instead of panicking, we will just pray that the result + // will be somewhat readable... + true + } + } + } + } - let draw_brush = draw_brush_builder.build(device, format); + pub fn render<'a>( + &'a self, + layer: usize, + bounds: Rectangle<u32>, + render_pass: &mut wgpu::RenderPass<'a>, + ) { + let renderer = &self.renderers[layer]; - let measure_brush = - glyph_brush::GlyphBrushBuilder::using_font(font).build(); + render_pass.set_scissor_rect( + bounds.x, + bounds.y, + bounds.width, + bounds.height, + ); - Pipeline { - draw_brush: RefCell::new(draw_brush), - draw_font_map: RefCell::new(HashMap::new()), - measure_brush: RefCell::new(measure_brush), - } + renderer + .render(&self.atlas, render_pass) + .expect("Render text"); } - pub fn queue(&mut self, section: wgpu_glyph::Section<'_>) { - self.draw_brush.borrow_mut().queue(section); - } + pub fn end_frame(&mut self) { + self.atlas.trim(); + self.render_cache.trim(); - pub fn draw_queued( - &mut self, - device: &wgpu::Device, - staging_belt: &mut wgpu::util::StagingBelt, - encoder: &mut wgpu::CommandEncoder, - target: &wgpu::TextureView, - transformation: Transformation, - region: wgpu_glyph::Region, - ) { - self.draw_brush - .borrow_mut() - .draw_queued_with_transform_and_scissoring( - device, - staging_belt, - encoder, - target, - transformation.into(), - region, - ) - .expect("Draw text"); + self.prepare_layer = 0; } pub fn measure( &self, content: &str, size: f32, - font: iced_native::Font, - bounds: iced_native::Size, + line_height: LineHeight, + font: Font, + bounds: Size, + shaping: Shaping, ) -> (f32, f32) { - use wgpu_glyph::GlyphCruncher; - - let wgpu_glyph::FontId(font_id) = self.find_font(font); - - let section = wgpu_glyph::Section { - bounds: (bounds.width, bounds.height), - text: vec![wgpu_glyph::Text { - text: content, - scale: size.into(), - font_id: wgpu_glyph::FontId(font_id), - extra: wgpu_glyph::Extra::default(), - }], - ..Default::default() - }; + let mut measurement_cache = self.measurement_cache.borrow_mut(); + + let line_height = f32::from(line_height.to_absolute(Pixels(size))); + + let (_, paragraph) = measurement_cache.allocate( + &mut self.font_system.borrow_mut(), + Key { + content, + size, + line_height, + font, + bounds, + shaping, + }, + ); - if let Some(bounds) = - self.measure_brush.borrow_mut().glyph_bounds(section) - { - (bounds.width().ceil(), bounds.height().ceil()) - } else { - (0.0, 0.0) - } + let (total_lines, max_width) = paragraph + .layout_runs() + .enumerate() + .fold((0, 0.0), |(_, max), (i, buffer)| { + (i + 1, buffer.line_w.max(max)) + }); + + (max_width, line_height * total_lines as f32) } pub fn hit_test( &self, content: &str, size: f32, - font: iced_native::Font, - bounds: iced_native::Size, - point: iced_native::Point, - nearest_only: bool, + line_height: LineHeight, + font: Font, + bounds: Size, + shaping: Shaping, + point: Point, + _nearest_only: bool, ) -> Option<Hit> { - use wgpu_glyph::GlyphCruncher; - - let wgpu_glyph::FontId(font_id) = self.find_font(font); - - let section = wgpu_glyph::Section { - bounds: (bounds.width, bounds.height), - text: vec![wgpu_glyph::Text { - text: content, - scale: size.into(), - font_id: wgpu_glyph::FontId(font_id), - extra: wgpu_glyph::Extra::default(), - }], - ..Default::default() - }; - - let mut mb = self.measure_brush.borrow_mut(); - - // The underlying type is FontArc, so clones are cheap. - use wgpu_glyph::ab_glyph::{Font, ScaleFont}; - let font = mb.fonts()[font_id].clone().into_scaled(size); - - // Implements an iterator over the glyph bounding boxes. - let bounds = mb.glyphs(section).map( - |wgpu_glyph::SectionGlyph { - byte_index, glyph, .. - }| { - ( - *byte_index, - iced_native::Rectangle::new( - iced_native::Point::new( - glyph.position.x - font.h_side_bearing(glyph.id), - glyph.position.y - font.ascent(), - ), - iced_native::Size::new( - font.h_advance(glyph.id), - font.ascent() - font.descent(), - ), - ), - ) + let mut measurement_cache = self.measurement_cache.borrow_mut(); + + let line_height = f32::from(line_height.to_absolute(Pixels(size))); + + let (_, paragraph) = measurement_cache.allocate( + &mut self.font_system.borrow_mut(), + Key { + content, + size, + line_height, + font, + bounds, + shaping, }, ); - // Implements computation of the character index based on the byte index - // within the input string. - let char_index = |byte_index| { - let mut b_count = 0; - for (i, utf8_len) in - content.chars().map(|c| c.len_utf8()).enumerate() - { - if byte_index < (b_count + utf8_len) { - return i; - } - b_count += utf8_len; - } + let cursor = paragraph.hit(point.x, point.y)?; - byte_index - }; + Some(Hit::CharOffset(cursor.index)) + } - if !nearest_only { - for (idx, bounds) in bounds.clone() { - if bounds.contains(point) { - return Some(Hit::CharOffset(char_index(idx))); - } - } - } + pub fn trim_measurement_cache(&mut self) { + self.measurement_cache.borrow_mut().trim(); + } +} - let nearest = bounds - .map(|(index, bounds)| (index, bounds.center())) - .min_by(|(_, center_a), (_, center_b)| { - center_a - .distance(point) - .partial_cmp(¢er_b.distance(point)) - .unwrap_or(std::cmp::Ordering::Greater) - }); +fn to_family(family: font::Family) -> glyphon::Family<'static> { + match family { + font::Family::Name(name) => glyphon::Family::Name(name), + font::Family::SansSerif => glyphon::Family::SansSerif, + font::Family::Serif => glyphon::Family::Serif, + font::Family::Cursive => glyphon::Family::Cursive, + font::Family::Fantasy => glyphon::Family::Fantasy, + font::Family::Monospace => glyphon::Family::Monospace, + } +} - nearest.map(|(idx, center)| { - Hit::NearestCharOffset(char_index(idx), point - center) - }) +fn to_weight(weight: font::Weight) -> glyphon::Weight { + match weight { + font::Weight::Thin => glyphon::Weight::THIN, + font::Weight::ExtraLight => glyphon::Weight::EXTRA_LIGHT, + font::Weight::Light => glyphon::Weight::LIGHT, + font::Weight::Normal => glyphon::Weight::NORMAL, + font::Weight::Medium => glyphon::Weight::MEDIUM, + font::Weight::Semibold => glyphon::Weight::SEMIBOLD, + font::Weight::Bold => glyphon::Weight::BOLD, + font::Weight::ExtraBold => glyphon::Weight::EXTRA_BOLD, + font::Weight::Black => glyphon::Weight::BLACK, } +} - pub fn trim_measurement_cache(&mut self) { - // TODO: We should probably use a `GlyphCalculator` for this. However, - // it uses a lifetimed `GlyphCalculatorGuard` with side-effects on drop. - // This makes stuff quite inconvenient. A manual method for trimming the - // cache would make our lives easier. - loop { - let action = self - .measure_brush - .borrow_mut() - .process_queued(|_, _| {}, |_| {}); - - match action { - Ok(_) => break, - Err(glyph_brush::BrushError::TextureTooSmall { suggested }) => { - let (width, height) = suggested; - - self.measure_brush - .borrow_mut() - .resize_texture(width, height); - } - } - } +fn to_stretch(stretch: font::Stretch) -> glyphon::Stretch { + match stretch { + font::Stretch::UltraCondensed => glyphon::Stretch::UltraCondensed, + font::Stretch::ExtraCondensed => glyphon::Stretch::ExtraCondensed, + font::Stretch::Condensed => glyphon::Stretch::Condensed, + font::Stretch::SemiCondensed => glyphon::Stretch::SemiCondensed, + font::Stretch::Normal => glyphon::Stretch::Normal, + font::Stretch::SemiExpanded => glyphon::Stretch::SemiExpanded, + font::Stretch::Expanded => glyphon::Stretch::Expanded, + font::Stretch::ExtraExpanded => glyphon::Stretch::ExtraExpanded, + font::Stretch::UltraExpanded => glyphon::Stretch::UltraExpanded, + } +} + +fn to_shaping(shaping: Shaping) -> glyphon::Shaping { + match shaping { + Shaping::Basic => glyphon::Shaping::Basic, + Shaping::Advanced => glyphon::Shaping::Advanced, } +} - pub fn find_font(&self, font: iced_native::Font) -> wgpu_glyph::FontId { - match font { - iced_native::Font::Default => wgpu_glyph::FontId(0), - iced_native::Font::External { name, bytes } => { - if let Some(font_id) = self.draw_font_map.borrow().get(name) { - return *font_id; - } +struct Cache { + entries: FxHashMap<KeyHash, glyphon::Buffer>, + recently_used: FxHashSet<KeyHash>, + hasher: HashBuilder, +} - let font = ab_glyph::FontArc::try_from_slice(bytes) - .expect("Load font"); +#[cfg(not(target_arch = "wasm32"))] +type HashBuilder = twox_hash::RandomXxHashBuilder64; - let _ = self.measure_brush.borrow_mut().add_font(font.clone()); +#[cfg(target_arch = "wasm32")] +type HashBuilder = std::hash::BuildHasherDefault<twox_hash::XxHash64>; - let font_id = self.draw_brush.borrow_mut().add_font(font); +impl Cache { + fn new() -> Self { + Self { + entries: FxHashMap::default(), + recently_used: FxHashSet::default(), + hasher: HashBuilder::default(), + } + } - let _ = self - .draw_font_map - .borrow_mut() - .insert(String::from(name), font_id); + fn get(&self, key: &KeyHash) -> Option<&glyphon::Buffer> { + self.entries.get(key) + } - font_id - } + fn allocate( + &mut self, + font_system: &mut glyphon::FontSystem, + key: Key<'_>, + ) -> (KeyHash, &mut glyphon::Buffer) { + let hash = { + let mut hasher = self.hasher.build_hasher(); + + key.content.hash(&mut hasher); + key.size.to_bits().hash(&mut hasher); + key.line_height.to_bits().hash(&mut hasher); + key.font.hash(&mut hasher); + key.bounds.width.to_bits().hash(&mut hasher); + key.bounds.height.to_bits().hash(&mut hasher); + key.shaping.hash(&mut hasher); + + hasher.finish() + }; + + if let hash_map::Entry::Vacant(entry) = self.entries.entry(hash) { + let metrics = glyphon::Metrics::new(key.size, key.line_height); + let mut buffer = glyphon::Buffer::new(font_system, metrics); + + buffer.set_size( + font_system, + key.bounds.width, + key.bounds.height.max(key.line_height), + ); + buffer.set_text( + font_system, + key.content, + glyphon::Attrs::new() + .family(to_family(key.font.family)) + .weight(to_weight(key.font.weight)) + .stretch(to_stretch(key.font.stretch)), + to_shaping(key.shaping), + ); + + let _ = entry.insert(buffer); } + + let _ = self.recently_used.insert(hash); + + (hash, self.entries.get_mut(&hash).unwrap()) + } + + fn trim(&mut self) { + self.entries + .retain(|key, _| self.recently_used.contains(key)); + + self.recently_used.clear(); } } + +#[derive(Debug, Clone, Copy)] +struct Key<'a> { + content: &'a str, + size: f32, + line_height: f32, + font: Font, + bounds: Size, + shaping: Shaping, +} + +type KeyHash = u64; diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index 162428f0..eb15a458 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -2,62 +2,68 @@ mod msaa; use crate::buffer::r#static::Buffer; -use crate::settings; -use crate::Transformation; +use crate::core::Size; +use crate::graphics::{Antialiasing, Transformation}; +use crate::layer::mesh::{self, Mesh}; + +#[cfg(not(target_arch = "wasm32"))] +use crate::core::Gradient; -use iced_graphics::layer::mesh::{self, Mesh}; -use iced_graphics::triangle::ColoredVertex2D; -use iced_graphics::Size; #[cfg(feature = "tracing")] use tracing::info_span; #[derive(Debug)] pub struct Pipeline { blit: Option<msaa::Blit>, - index_buffer: Buffer<u32>, - index_strides: Vec<u32>, solid: solid::Pipeline, /// Gradients are currently not supported on WASM targets due to their need of storage buffers. #[cfg(not(target_arch = "wasm32"))] gradient: gradient::Pipeline, + + layers: Vec<Layer>, + prepare_layer: usize, } -impl Pipeline { - pub fn new( +#[derive(Debug)] +struct Layer { + index_buffer: Buffer<u32>, + index_strides: Vec<u32>, + solid: solid::Layer, + + #[cfg(not(target_arch = "wasm32"))] + gradient: gradient::Layer, +} + +impl Layer { + fn new( device: &wgpu::Device, - format: wgpu::TextureFormat, - antialiasing: Option<settings::Antialiasing>, - ) -> Pipeline { - Pipeline { - blit: antialiasing.map(|a| msaa::Blit::new(device, format, a)), + solid: &solid::Pipeline, + #[cfg(not(target_arch = "wasm32"))] gradient: &gradient::Pipeline, + ) -> Self { + Self { index_buffer: Buffer::new( device, - "iced_wgpu::triangle vertex buffer", + "iced_wgpu::triangle index buffer", wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST, ), index_strides: Vec::new(), - solid: solid::Pipeline::new(device, format, antialiasing), + solid: solid::Layer::new(device, &solid.constants_layout), #[cfg(not(target_arch = "wasm32"))] - gradient: gradient::Pipeline::new(device, format, antialiasing), + gradient: gradient::Layer::new(device, &gradient.constants_layout), } } - pub fn draw( + fn prepare( &mut self, device: &wgpu::Device, - staging_belt: &mut wgpu::util::StagingBelt, - encoder: &mut wgpu::CommandEncoder, - target: &wgpu::TextureView, - target_size: Size<u32>, - transformation: Transformation, - scale_factor: f32, + queue: &wgpu::Queue, + solid: &solid::Pipeline, + #[cfg(not(target_arch = "wasm32"))] gradient: &gradient::Pipeline, meshes: &[Mesh<'_>], + transformation: Transformation, ) { - #[cfg(feature = "tracing")] - let _ = info_span!("Wgpu::Triangle", "DRAW").entered(); - // Count the total amount of vertices & indices we need to handle let count = mesh::attribute_count_of(meshes); @@ -75,6 +81,7 @@ impl Pipeline { .resize(device, count.gradient_vertices); // Prepare dynamic buffers & data store for writing + self.index_buffer.clear(); self.index_strides.clear(); self.solid.vertices.clear(); self.solid.uniforms.clear(); @@ -99,13 +106,8 @@ impl Pipeline { let transform = transformation * Transformation::translate(origin.x, origin.y); - let new_index_offset = self.index_buffer.write( - device, - staging_belt, - encoder, - index_offset, - indices, - ); + let new_index_offset = + self.index_buffer.write(queue, index_offset, indices); index_offset += new_index_offset; self.index_strides.push(indices.len() as u32); @@ -116,9 +118,7 @@ impl Pipeline { self.solid.uniforms.push(&solid::Uniforms::new(transform)); let written_bytes = self.solid.vertices.write( - device, - staging_belt, - encoder, + queue, solid_vertex_offset, &buffers.vertices, ); @@ -130,9 +130,7 @@ impl Pipeline { buffers, gradient, .. } => { let written_bytes = self.gradient.vertices.write( - device, - staging_belt, - encoder, + queue, gradient_vertex_offset, &buffers.vertices, ); @@ -140,7 +138,7 @@ impl Pipeline { gradient_vertex_offset += written_bytes; match gradient { - iced_graphics::Gradient::Linear(linear) => { + Gradient::Linear(linear) => { use glam::{IVec4, Vec4}; let start_offset = self.gradient.color_stop_offset; @@ -196,14 +194,14 @@ impl Pipeline { let uniforms_resized = self.solid.uniforms.resize(device); if uniforms_resized { - self.solid.bind_group = solid::Pipeline::bind_group( + self.solid.constants = solid::Layer::bind_group( device, self.solid.uniforms.raw(), - &self.solid.bind_group_layout, + &solid.constants_layout, ) } - self.solid.uniforms.write(device, staging_belt, encoder); + self.solid.uniforms.write(queue); } #[cfg(not(target_arch = "wasm32"))] @@ -218,22 +216,169 @@ impl Pipeline { let storage_resized = self.gradient.storage.resize(device); if uniforms_resized || storage_resized { - self.gradient.bind_group = gradient::Pipeline::bind_group( + self.gradient.constants = gradient::Layer::bind_group( device, self.gradient.uniforms.raw(), self.gradient.storage.raw(), - &self.gradient.bind_group_layout, + &gradient.constants_layout, ); } // Write to GPU - self.gradient.uniforms.write(device, staging_belt, encoder); - self.gradient.storage.write(device, staging_belt, encoder); + self.gradient.uniforms.write(queue); + self.gradient.storage.write(queue); // Cleanup self.gradient.color_stop_offset = 0; self.gradient.color_stops_pending_write.color_stops.clear(); } + } + + fn render<'a>( + &'a self, + solid: &'a solid::Pipeline, + #[cfg(not(target_arch = "wasm32"))] gradient: &'a gradient::Pipeline, + meshes: &[Mesh<'_>], + scale_factor: f32, + render_pass: &mut wgpu::RenderPass<'a>, + ) { + let mut num_solids = 0; + #[cfg(not(target_arch = "wasm32"))] + let mut num_gradients = 0; + let mut last_is_solid = None; + + for (index, mesh) in meshes.iter().enumerate() { + let clip_bounds = (mesh.clip_bounds() * scale_factor).snap(); + + render_pass.set_scissor_rect( + clip_bounds.x, + clip_bounds.y, + clip_bounds.width, + clip_bounds.height, + ); + + match mesh { + Mesh::Solid { .. } => { + if !last_is_solid.unwrap_or(false) { + render_pass.set_pipeline(&solid.pipeline); + + last_is_solid = Some(true); + } + + render_pass.set_bind_group( + 0, + &self.solid.constants, + &[self.solid.uniforms.offset_at_index(num_solids)], + ); + + render_pass.set_vertex_buffer( + 0, + self.solid.vertices.slice_from_index(num_solids), + ); + + num_solids += 1; + } + #[cfg(not(target_arch = "wasm32"))] + Mesh::Gradient { .. } => { + if last_is_solid.unwrap_or(true) { + render_pass.set_pipeline(&gradient.pipeline); + + last_is_solid = Some(false); + } + + render_pass.set_bind_group( + 0, + &self.gradient.constants, + &[self + .gradient + .uniforms + .offset_at_index(num_gradients)], + ); + + render_pass.set_vertex_buffer( + 0, + self.gradient.vertices.slice_from_index(num_gradients), + ); + + num_gradients += 1; + } + #[cfg(target_arch = "wasm32")] + Mesh::Gradient { .. } => {} + }; + + render_pass.set_index_buffer( + self.index_buffer.slice_from_index(index), + wgpu::IndexFormat::Uint32, + ); + + render_pass.draw_indexed(0..self.index_strides[index], 0, 0..1); + } + } +} + +impl Pipeline { + pub fn new( + device: &wgpu::Device, + format: wgpu::TextureFormat, + antialiasing: Option<Antialiasing>, + ) -> Pipeline { + Pipeline { + blit: antialiasing.map(|a| msaa::Blit::new(device, format, a)), + solid: solid::Pipeline::new(device, format, antialiasing), + + #[cfg(not(target_arch = "wasm32"))] + gradient: gradient::Pipeline::new(device, format, antialiasing), + + layers: Vec::new(), + prepare_layer: 0, + } + } + + pub fn prepare( + &mut self, + device: &wgpu::Device, + queue: &wgpu::Queue, + meshes: &[Mesh<'_>], + transformation: Transformation, + ) { + #[cfg(feature = "tracing")] + let _ = info_span!("Wgpu::Triangle", "PREPARE").entered(); + + if self.layers.len() <= self.prepare_layer { + self.layers.push(Layer::new( + device, + &self.solid, + #[cfg(not(target_arch = "wasm32"))] + &self.gradient, + )); + } + + let layer = &mut self.layers[self.prepare_layer]; + layer.prepare( + device, + queue, + &self.solid, + #[cfg(not(target_arch = "wasm32"))] + &self.gradient, + meshes, + transformation, + ); + + self.prepare_layer += 1; + } + + pub fn render( + &mut self, + device: &wgpu::Device, + encoder: &mut wgpu::CommandEncoder, + target: &wgpu::TextureView, + layer: usize, + target_size: Size<u32>, + meshes: &[Mesh<'_>], + scale_factor: f32, + ) { + #[cfg(feature = "tracing")] + let _ = info_span!("Wgpu::Triangle", "DRAW").entered(); // Configure render pass { @@ -268,87 +413,26 @@ impl Pipeline { depth_stencil_attachment: None, }); - let mut num_solids = 0; - #[cfg(not(target_arch = "wasm32"))] - let mut num_gradients = 0; - let mut last_is_solid = None; - - for (index, mesh) in meshes.iter().enumerate() { - let clip_bounds = (mesh.clip_bounds() * scale_factor).snap(); - - render_pass.set_scissor_rect( - clip_bounds.x, - clip_bounds.y, - clip_bounds.width, - clip_bounds.height, - ); - - match mesh { - Mesh::Solid { .. } => { - if !last_is_solid.unwrap_or(false) { - render_pass.set_pipeline(&self.solid.pipeline); - - last_is_solid = Some(true); - } + let layer = &mut self.layers[layer]; - render_pass.set_bind_group( - 0, - &self.solid.bind_group, - &[self.solid.uniforms.offset_at_index(num_solids)], - ); - - render_pass.set_vertex_buffer( - 0, - self.solid.vertices.slice_from_index(num_solids), - ); - - num_solids += 1; - } - #[cfg(not(target_arch = "wasm32"))] - Mesh::Gradient { .. } => { - if last_is_solid.unwrap_or(true) { - render_pass.set_pipeline(&self.gradient.pipeline); - - last_is_solid = Some(false); - } - - render_pass.set_bind_group( - 0, - &self.gradient.bind_group, - &[self - .gradient - .uniforms - .offset_at_index(num_gradients)], - ); - - render_pass.set_vertex_buffer( - 0, - self.gradient - .vertices - .slice_from_index(num_gradients), - ); - - num_gradients += 1; - } - #[cfg(target_arch = "wasm32")] - Mesh::Gradient { .. } => {} - }; - - render_pass.set_index_buffer( - self.index_buffer.slice_from_index(index), - wgpu::IndexFormat::Uint32, - ); - - render_pass.draw_indexed(0..self.index_strides[index], 0, 0..1); - } + layer.render( + &self.solid, + #[cfg(not(target_arch = "wasm32"))] + &self.gradient, + meshes, + scale_factor, + &mut render_pass, + ); } - self.index_buffer.clear(); - if let Some(blit) = &mut self.blit { blit.draw(encoder, target); } } + + pub fn end_frame(&mut self) { + self.prepare_layer = 0; + } } fn fragment_target( @@ -370,7 +454,7 @@ fn primitive_state() -> wgpu::PrimitiveState { } fn multisample_state( - antialiasing: Option<settings::Antialiasing>, + antialiasing: Option<Antialiasing>, ) -> wgpu::MultisampleState { wgpu::MultisampleState { count: antialiasing.map(|a| a.sample_count()).unwrap_or(1), @@ -382,18 +466,71 @@ fn multisample_state( mod solid { use crate::buffer::dynamic; use crate::buffer::r#static::Buffer; - use crate::settings; + use crate::graphics::primitive; + use crate::graphics::{Antialiasing, Transformation}; use crate::triangle; + use encase::ShaderType; - use iced_graphics::Transformation; #[derive(Debug)] pub struct Pipeline { pub pipeline: wgpu::RenderPipeline, - pub vertices: Buffer<triangle::ColoredVertex2D>, + pub constants_layout: wgpu::BindGroupLayout, + } + + #[derive(Debug)] + pub struct Layer { + pub vertices: Buffer<primitive::ColoredVertex2D>, pub uniforms: dynamic::Buffer<Uniforms>, - pub bind_group_layout: wgpu::BindGroupLayout, - pub bind_group: wgpu::BindGroup, + pub constants: wgpu::BindGroup, + } + + impl Layer { + pub fn new( + device: &wgpu::Device, + constants_layout: &wgpu::BindGroupLayout, + ) -> Self { + let vertices = Buffer::new( + device, + "iced_wgpu::triangle::solid vertex buffer", + wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, + ); + + let uniforms = dynamic::Buffer::uniform( + device, + "iced_wgpu::triangle::solid uniforms", + ); + + let constants = + Self::bind_group(device, uniforms.raw(), constants_layout); + + Self { + vertices, + uniforms, + constants, + } + } + + pub fn bind_group( + device: &wgpu::Device, + buffer: &wgpu::Buffer, + layout: &wgpu::BindGroupLayout, + ) -> wgpu::BindGroup { + device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("iced_wgpu::triangle::solid bind group"), + layout, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::Buffer( + wgpu::BufferBinding { + buffer, + offset: 0, + size: Some(Uniforms::min_size()), + }, + ), + }], + }) + } } #[derive(Debug, Clone, Copy, ShaderType)] @@ -414,20 +551,9 @@ mod solid { pub fn new( device: &wgpu::Device, format: wgpu::TextureFormat, - antialiasing: Option<settings::Antialiasing>, + antialiasing: Option<Antialiasing>, ) -> Self { - let vertices = Buffer::new( - device, - "iced_wgpu::triangle::solid vertex buffer", - wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, - ); - - let uniforms = dynamic::Buffer::uniform( - device, - "iced_wgpu::triangle::solid uniforms", - ); - - let bind_group_layout = device.create_bind_group_layout( + let constants_layout = device.create_bind_group_layout( &wgpu::BindGroupLayoutDescriptor { label: Some("iced_wgpu::triangle::solid bind group layout"), entries: &[wgpu::BindGroupLayoutEntry { @@ -443,13 +569,10 @@ mod solid { }, ); - let bind_group = - Self::bind_group(device, uniforms.raw(), &bind_group_layout); - let layout = device.create_pipeline_layout( &wgpu::PipelineLayoutDescriptor { label: Some("iced_wgpu::triangle::solid pipeline layout"), - bind_group_layouts: &[&bind_group_layout], + bind_group_layouts: &[&constants_layout], push_constant_ranges: &[], }, ); @@ -475,7 +598,7 @@ mod solid { entry_point: "vs_main", buffers: &[wgpu::VertexBufferLayout { array_stride: std::mem::size_of::< - triangle::ColoredVertex2D, + primitive::ColoredVertex2D, >() as u64, step_mode: wgpu::VertexStepMode::Vertex, @@ -501,33 +624,9 @@ mod solid { Self { pipeline, - vertices, - uniforms, - bind_group_layout, - bind_group, + constants_layout, } } - - pub fn bind_group( - device: &wgpu::Device, - buffer: &wgpu::Buffer, - layout: &wgpu::BindGroupLayout, - ) -> wgpu::BindGroup { - device.create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("iced_wgpu::triangle::solid bind group"), - layout, - entries: &[wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::Buffer( - wgpu::BufferBinding { - buffer, - offset: 0, - size: Some(Uniforms::min_size()), - }, - ), - }], - }) - } } } @@ -535,25 +634,100 @@ mod solid { mod gradient { use crate::buffer::dynamic; use crate::buffer::r#static::Buffer; - use crate::settings; + use crate::graphics::Antialiasing; use crate::triangle; use encase::ShaderType; use glam::{IVec4, Vec4}; - use iced_graphics::triangle::Vertex2D; + use iced_graphics::primitive; #[derive(Debug)] pub struct Pipeline { pub pipeline: wgpu::RenderPipeline, - pub vertices: Buffer<Vertex2D>, + pub constants_layout: wgpu::BindGroupLayout, + } + + #[derive(Debug)] + pub struct Layer { + pub vertices: Buffer<primitive::Vertex2D>, pub uniforms: dynamic::Buffer<Uniforms>, pub storage: dynamic::Buffer<Storage>, + pub constants: wgpu::BindGroup, pub color_stop_offset: i32, //Need to store these and then write them all at once //or else they will be padded to 256 and cause gaps in the storage buffer pub color_stops_pending_write: Storage, - pub bind_group_layout: wgpu::BindGroupLayout, - pub bind_group: wgpu::BindGroup, + } + + impl Layer { + pub fn new( + device: &wgpu::Device, + constants_layout: &wgpu::BindGroupLayout, + ) -> Self { + let vertices = Buffer::new( + device, + "iced_wgpu::triangle::gradient vertex buffer", + wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, + ); + + let uniforms = dynamic::Buffer::uniform( + device, + "iced_wgpu::triangle::gradient uniforms", + ); + + // Note: with a WASM target storage buffers are not supported. Will need to use UBOs & static + // sized array (eg like the 32-sized array on OpenGL side right now) to make gradients work + let storage = dynamic::Buffer::storage( + device, + "iced_wgpu::triangle::gradient storage", + ); + + let constants = Self::bind_group( + device, + uniforms.raw(), + storage.raw(), + constants_layout, + ); + + Self { + vertices, + uniforms, + storage, + constants, + color_stop_offset: 0, + color_stops_pending_write: Storage { + color_stops: vec![], + }, + } + } + + pub fn bind_group( + device: &wgpu::Device, + uniform_buffer: &wgpu::Buffer, + storage_buffer: &wgpu::Buffer, + layout: &wgpu::BindGroupLayout, + ) -> wgpu::BindGroup { + device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("iced_wgpu::triangle::gradient bind group"), + layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::Buffer( + wgpu::BufferBinding { + buffer: uniform_buffer, + offset: 0, + size: Some(Uniforms::min_size()), + }, + ), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: storage_buffer.as_entire_binding(), + }, + ], + }) + } } #[derive(Debug, ShaderType)] @@ -582,27 +756,9 @@ mod gradient { pub(super) fn new( device: &wgpu::Device, format: wgpu::TextureFormat, - antialiasing: Option<settings::Antialiasing>, + antialiasing: Option<Antialiasing>, ) -> Self { - let vertices = Buffer::new( - device, - "iced_wgpu::triangle::gradient vertex buffer", - wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, - ); - - let uniforms = dynamic::Buffer::uniform( - device, - "iced_wgpu::triangle::gradient uniforms", - ); - - //Note: with a WASM target storage buffers are not supported. Will need to use UBOs & static - // sized array (eg like the 32-sized array on OpenGL side right now) to make gradients work - let storage = dynamic::Buffer::storage( - device, - "iced_wgpu::triangle::gradient storage", - ); - - let bind_group_layout = device.create_bind_group_layout( + let constants_layout = device.create_bind_group_layout( &wgpu::BindGroupLayoutDescriptor { label: Some( "iced_wgpu::triangle::gradient bind group layout", @@ -634,19 +790,12 @@ mod gradient { }, ); - let bind_group = Pipeline::bind_group( - device, - uniforms.raw(), - storage.raw(), - &bind_group_layout, - ); - let layout = device.create_pipeline_layout( &wgpu::PipelineLayoutDescriptor { label: Some( "iced_wgpu::triangle::gradient pipeline layout", ), - bind_group_layouts: &[&bind_group_layout], + bind_group_layouts: &[&constants_layout], push_constant_ranges: &[], }, ); @@ -654,7 +803,7 @@ mod gradient { let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { label: Some( - "iced_wgpu triangle gradient create shader module", + "iced_wgpu::triangle::gradient create shader module", ), source: wgpu::ShaderSource::Wgsl( std::borrow::Cow::Borrowed(include_str!( @@ -663,75 +812,43 @@ mod gradient { ), }); - let pipeline = device.create_render_pipeline( - &wgpu::RenderPipelineDescriptor { - label: Some("iced_wgpu::triangle::gradient pipeline"), - layout: Some(&layout), - vertex: wgpu::VertexState { - module: &shader, - entry_point: "vs_main", - buffers: &[wgpu::VertexBufferLayout { - array_stride: std::mem::size_of::<Vertex2D>() - as u64, - step_mode: wgpu::VertexStepMode::Vertex, - attributes: &wgpu::vertex_attr_array!( - // Position - 0 => Float32x2, - ), - }], + let pipeline = + device.create_render_pipeline( + &wgpu::RenderPipelineDescriptor { + label: Some("iced_wgpu::triangle::gradient pipeline"), + layout: Some(&layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: "vs_main", + buffers: &[wgpu::VertexBufferLayout { + array_stride: std::mem::size_of::< + primitive::Vertex2D, + >( + ) + as u64, + step_mode: wgpu::VertexStepMode::Vertex, + attributes: &wgpu::vertex_attr_array!( + // Position + 0 => Float32x2, + ), + }], + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: "fs_main", + targets: &[triangle::fragment_target(format)], + }), + primitive: triangle::primitive_state(), + depth_stencil: None, + multisample: triangle::multisample_state(antialiasing), + multiview: None, }, - fragment: Some(wgpu::FragmentState { - module: &shader, - entry_point: "fs_main", - targets: &[triangle::fragment_target(format)], - }), - primitive: triangle::primitive_state(), - depth_stencil: None, - multisample: triangle::multisample_state(antialiasing), - multiview: None, - }, - ); + ); Self { pipeline, - vertices, - uniforms, - storage, - color_stop_offset: 0, - color_stops_pending_write: Storage { - color_stops: vec![], - }, - bind_group_layout, - bind_group, + constants_layout, } } - - pub fn bind_group( - device: &wgpu::Device, - uniform_buffer: &wgpu::Buffer, - storage_buffer: &wgpu::Buffer, - layout: &wgpu::BindGroupLayout, - ) -> wgpu::BindGroup { - device.create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("iced_wgpu::triangle::gradient bind group"), - layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::Buffer( - wgpu::BufferBinding { - buffer: uniform_buffer, - offset: 0, - size: Some(Uniforms::min_size()), - }, - ), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: storage_buffer.as_entire_binding(), - }, - ], - }) - } } } diff --git a/wgpu/src/triangle/msaa.rs b/wgpu/src/triangle/msaa.rs index d24f8e1a..4afbdb32 100644 --- a/wgpu/src/triangle/msaa.rs +++ b/wgpu/src/triangle/msaa.rs @@ -1,4 +1,4 @@ -use crate::settings; +use crate::graphics; #[derive(Debug)] pub struct Blit { @@ -14,7 +14,7 @@ impl Blit { pub fn new( device: &wgpu::Device, format: wgpu::TextureFormat, - antialiasing: settings::Antialiasing, + antialiasing: graphics::Antialiasing, ) -> Blit { let sampler = device.create_sampler(&wgpu::SamplerDescriptor { address_mode_u: wgpu::AddressMode::ClampToEdge, @@ -222,8 +222,8 @@ impl Targets { sample_count, dimension: wgpu::TextureDimension::D2, format, - view_formats: &[], usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + view_formats: &[], }); let resolve = device.create_texture(&wgpu::TextureDescriptor { @@ -233,9 +233,9 @@ impl Targets { sample_count: 1, dimension: wgpu::TextureDimension::D2, format, - view_formats: &[], usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING, + view_formats: &[], }); let attachment = diff --git a/wgpu/src/window.rs b/wgpu/src/window.rs index aac5fb9e..9545a14e 100644 --- a/wgpu/src/window.rs +++ b/wgpu/src/window.rs @@ -1,4 +1,5 @@ //! Display rendering results on windows. -mod compositor; +pub mod compositor; pub use compositor::Compositor; +pub use wgpu::Surface; diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index 53af19bf..500458e8 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -1,9 +1,12 @@ -use crate::{Backend, Color, Error, Renderer, Settings, Viewport}; +//! Connect a window with a renderer. +use crate::core::Color; +use crate::graphics; +use crate::graphics::compositor; +use crate::graphics::{Error, Primitive, Viewport}; +use crate::{Backend, Renderer, Settings}; use futures::stream::{self, StreamExt}; -use iced_graphics::compositor; -use iced_native::futures; use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle}; use std::marker::PhantomData; @@ -16,14 +19,11 @@ pub struct Compositor<Theme> { adapter: wgpu::Adapter, device: wgpu::Device, queue: wgpu::Queue, - staging_belt: wgpu::util::StagingBelt, format: wgpu::TextureFormat, theme: PhantomData<Theme>, } impl<Theme> Compositor<Theme> { - const CHUNK_SIZE: u64 = 10 * 1024; - /// Requests a new [`Compositor`] with the given [`Settings`]. /// /// Returns `None` if no compatible graphics adapter could be found. @@ -53,11 +53,12 @@ impl<Theme> Compositor<Theme> { let adapter = instance .request_adapter(&wgpu::RequestAdapterOptions { - power_preference: if settings.antialiasing.is_none() { - wgpu::PowerPreference::LowPower - } else { - wgpu::PowerPreference::HighPerformance - }, + power_preference: wgpu::util::power_preference_from_env() + .unwrap_or(if settings.antialiasing.is_none() { + wgpu::PowerPreference::LowPower + } else { + wgpu::PowerPreference::HighPerformance + }), compatible_surface: compatible_surface.as_ref(), force_fallback_adapter: false, }) @@ -112,15 +113,12 @@ impl<Theme> Compositor<Theme> { .next() .await?; - let staging_belt = wgpu::util::StagingBelt::new(Self::CHUNK_SIZE); - Some(Compositor { instance, settings, adapter, device, queue, - staging_belt, format, theme: PhantomData, }) @@ -128,11 +126,82 @@ impl<Theme> Compositor<Theme> { /// Creates a new rendering [`Backend`] for this [`Compositor`]. pub fn create_backend(&self) -> Backend { - Backend::new(&self.device, self.settings, self.format) + Backend::new(&self.device, &self.queue, self.settings, self.format) + } +} + +/// Creates a [`Compositor`] and its [`Backend`] for the given [`Settings`] and +/// window. +pub fn new<Theme, W: HasRawWindowHandle + HasRawDisplayHandle>( + settings: Settings, + compatible_window: Option<&W>, +) -> Result<(Compositor<Theme>, Backend), Error> { + let compositor = futures::executor::block_on(Compositor::request( + settings, + compatible_window, + )) + .ok_or(Error::GraphicsAdapterNotFound)?; + + let backend = compositor.create_backend(); + + Ok((compositor, backend)) +} + +/// Presents the given primitives with the given [`Compositor`] and [`Backend`]. +pub fn present<Theme, T: AsRef<str>>( + compositor: &mut Compositor<Theme>, + backend: &mut Backend, + surface: &mut wgpu::Surface, + primitives: &[Primitive], + viewport: &Viewport, + background_color: Color, + overlay: &[T], +) -> Result<(), compositor::SurfaceError> { + match surface.get_current_texture() { + Ok(frame) => { + let mut encoder = compositor.device.create_command_encoder( + &wgpu::CommandEncoderDescriptor { + label: Some("iced_wgpu encoder"), + }, + ); + + let view = &frame + .texture + .create_view(&wgpu::TextureViewDescriptor::default()); + + backend.present( + &compositor.device, + &compositor.queue, + &mut encoder, + Some(background_color), + view, + primitives, + viewport, + overlay, + ); + + // Submit work + let _submission = compositor.queue.submit(Some(encoder.finish())); + frame.present(); + + Ok(()) + } + Err(error) => match error { + wgpu::SurfaceError::Timeout => { + Err(compositor::SurfaceError::Timeout) + } + wgpu::SurfaceError::Outdated => { + Err(compositor::SurfaceError::Outdated) + } + wgpu::SurfaceError::Lost => Err(compositor::SurfaceError::Lost), + wgpu::SurfaceError::OutOfMemory => { + Err(compositor::SurfaceError::OutOfMemory) + } + }, } } -impl<Theme> iced_graphics::window::Compositor for Compositor<Theme> { +impl<Theme> graphics::Compositor for Compositor<Theme> { type Settings = Settings; type Renderer = Renderer<Theme>; type Surface = wgpu::Surface; @@ -141,13 +210,7 @@ impl<Theme> iced_graphics::window::Compositor for Compositor<Theme> { settings: Self::Settings, compatible_window: Option<&W>, ) -> Result<(Self, Self::Renderer), Error> { - let compositor = futures::executor::block_on(Self::request( - settings, - compatible_window, - )) - .ok_or(Error::GraphicsAdapterNotFound)?; - - let backend = compositor.create_backend(); + let (compositor, backend) = new(settings, compatible_window)?; Ok((compositor, Renderer::new(backend))) } @@ -155,13 +218,16 @@ impl<Theme> iced_graphics::window::Compositor for Compositor<Theme> { fn create_surface<W: HasRawWindowHandle + HasRawDisplayHandle>( &mut self, window: &W, + width: u32, + height: u32, ) -> wgpu::Surface { #[allow(unsafe_code)] - unsafe { - self.instance - .create_surface(window) - .expect("Create surface") - } + let mut surface = unsafe { self.instance.create_surface(window) } + .expect("Create surface"); + + self.configure_surface(&mut surface, width, height); + + surface } fn configure_surface( @@ -201,80 +267,16 @@ impl<Theme> iced_graphics::window::Compositor for Compositor<Theme> { background_color: Color, overlay: &[T], ) -> Result<(), compositor::SurfaceError> { - match surface.get_current_texture() { - Ok(frame) => { - let mut encoder = self.device.create_command_encoder( - &wgpu::CommandEncoderDescriptor { - label: Some("iced_wgpu encoder"), - }, - ); - - let view = &frame - .texture - .create_view(&wgpu::TextureViewDescriptor::default()); - - let _ = - encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some( - "iced_wgpu::window::Compositor render pass", - ), - color_attachments: &[Some( - wgpu::RenderPassColorAttachment { - view, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Clear({ - let [r, g, b, a] = - background_color.into_linear(); - - wgpu::Color { - r: f64::from(r), - g: f64::from(g), - b: f64::from(b), - a: f64::from(a), - } - }), - store: true, - }, - }, - )], - depth_stencil_attachment: None, - }); - - renderer.with_primitives(|backend, primitives| { - backend.present( - &self.device, - &mut self.staging_belt, - &mut encoder, - view, - primitives, - viewport, - overlay, - ); - }); - - // Submit work - self.staging_belt.finish(); - let _submission = self.queue.submit(Some(encoder.finish())); - frame.present(); - - // Recall staging buffers - self.staging_belt.recall(); - - Ok(()) - } - Err(error) => match error { - wgpu::SurfaceError::Timeout => { - Err(compositor::SurfaceError::Timeout) - } - wgpu::SurfaceError::Outdated => { - Err(compositor::SurfaceError::Outdated) - } - wgpu::SurfaceError::Lost => Err(compositor::SurfaceError::Lost), - wgpu::SurfaceError::OutOfMemory => { - Err(compositor::SurfaceError::OutOfMemory) - } - }, - } + renderer.with_primitives(|backend, primitives| { + present( + self, + backend, + surface, + primitives, + viewport, + background_color, + overlay, + ) + }) } } diff --git a/widget/Cargo.toml b/widget/Cargo.toml new file mode 100644 index 00000000..40e4db37 --- /dev/null +++ b/widget/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "iced_widget" +version = "0.1.0" +edition = "2021" + +[features] +lazy = ["ouroboros"] +image = ["iced_renderer/image"] +svg = ["iced_renderer/svg"] +canvas = ["iced_renderer/geometry"] +qr_code = ["canvas", "qrcode"] + +[dependencies] +unicode-segmentation = "1.6" +num-traits = "0.2" +thiserror = "1" + +[dependencies.iced_runtime] +version = "0.1" +path = "../runtime" + +[dependencies.iced_renderer] +version = "0.1" +path = "../renderer" + +[dependencies.iced_style] +version = "0.8" +path = "../style" + +[dependencies.ouroboros] +version = "0.13" +optional = true + +[dependencies.qrcode] +version = "0.12" +optional = true +default-features = false diff --git a/native/src/widget/button.rs b/widget/src/button.rs index 39387173..7eee69cb 100644 --- a/native/src/widget/button.rs +++ b/widget/src/button.rs @@ -1,15 +1,15 @@ //! Allow your users to perform actions by pressing a button. //! //! A [`Button`] has some local [`State`]. -use crate::event::{self, Event}; -use crate::layout; -use crate::mouse; -use crate::overlay; -use crate::renderer; -use crate::touch; -use crate::widget::tree::{self, Tree}; -use crate::widget::Operation; -use crate::{ +use crate::core::event::{self, Event}; +use crate::core::layout; +use crate::core::mouse; +use crate::core::overlay; +use crate::core::renderer; +use crate::core::touch; +use crate::core::widget::tree::{self, Tree}; +use crate::core::widget::Operation; +use crate::core::{ Background, Clipboard, Color, Element, Layout, Length, Padding, Point, Rectangle, Shell, Vector, Widget, }; @@ -18,9 +18,9 @@ pub use iced_style::button::{Appearance, StyleSheet}; /// A generic widget that produces a message when pressed. /// -/// ``` +/// ```no_run /// # type Button<'a, Message> = -/// # iced_native::widget::Button<'a, Message, iced_native::renderer::Null>; +/// # iced_widget::Button<'a, Message, iced_widget::renderer::Renderer<iced_widget::style::Theme>>; /// # /// #[derive(Clone)] /// enum Message { @@ -35,7 +35,7 @@ pub use iced_style::button::{Appearance, StyleSheet}; /// /// ``` /// # type Button<'a, Message> = -/// # iced_native::widget::Button<'a, Message, iced_native::renderer::Null>; +/// # iced_widget::Button<'a, Message, iced_widget::renderer::Renderer<iced_widget::style::Theme>>; /// # /// #[derive(Clone)] /// enum Message { @@ -51,9 +51,9 @@ pub use iced_style::button::{Appearance, StyleSheet}; /// } /// ``` #[allow(missing_debug_implementations)] -pub struct Button<'a, Message, Renderer> +pub struct Button<'a, Message, Renderer = crate::Renderer> where - Renderer: crate::Renderer, + Renderer: crate::core::Renderer, Renderer::Theme: StyleSheet, { content: Element<'a, Message, Renderer>, @@ -66,7 +66,7 @@ where impl<'a, Message, Renderer> Button<'a, Message, Renderer> where - Renderer: crate::Renderer, + Renderer: crate::core::Renderer, Renderer::Theme: StyleSheet, { /// Creates a new [`Button`] with the given content. @@ -121,7 +121,7 @@ impl<'a, Message, Renderer> Widget<Message, Renderer> for Button<'a, Message, Renderer> where Message: 'a + Clone, - Renderer: 'a + crate::Renderer, + Renderer: 'a + crate::core::Renderer, Renderer::Theme: StyleSheet, { fn tag(&self) -> tree::Tag { @@ -279,7 +279,7 @@ impl<'a, Message, Renderer> From<Button<'a, Message, Renderer>> for Element<'a, Message, Renderer> where Message: Clone + 'a, - Renderer: crate::Renderer + 'a, + Renderer: crate::core::Renderer + 'a, Renderer::Theme: StyleSheet, { fn from(button: Button<'a, Message, Renderer>) -> Self { @@ -355,7 +355,7 @@ pub fn update<'a, Message: Clone>( } /// Draws a [`Button`]. -pub fn draw<'a, Renderer: crate::Renderer>( +pub fn draw<'a, Renderer: crate::core::Renderer>( renderer: &mut Renderer, bounds: Rectangle, cursor_position: Point, diff --git a/graphics/src/widget/canvas.rs b/widget/src/canvas.rs index a8d050f5..171c4534 100644 --- a/graphics/src/widget/canvas.rs +++ b/widget/src/canvas.rs @@ -1,43 +1,24 @@ //! Draw 2D graphics for your users. -//! -//! A [`Canvas`] widget can be used to draw different kinds of 2D shapes in a -//! [`Frame`]. It can be used for animation, data visualization, game graphics, -//! and more! pub mod event; -pub mod fill; -pub mod path; -pub mod stroke; -mod cache; mod cursor; -mod frame; -mod geometry; mod program; -mod style; -mod text; -pub use crate::gradient::{self, Gradient}; -pub use cache::Cache; pub use cursor::Cursor; pub use event::Event; -pub use fill::{Fill, FillRule}; -pub use frame::Frame; -pub use geometry::Geometry; -pub use path::Path; pub use program::Program; -pub use stroke::{LineCap, LineDash, LineJoin, Stroke}; -pub use style::Style; -pub use text::Text; -use crate::{Backend, Primitive, Renderer}; +pub use crate::graphics::geometry::*; +pub use crate::renderer::geometry::*; -use iced_native::layout::{self, Layout}; -use iced_native::mouse; -use iced_native::renderer; -use iced_native::widget::tree::{self, Tree}; -use iced_native::{ - Clipboard, Element, Length, Point, Rectangle, Shell, Size, Vector, Widget, -}; +use crate::core; +use crate::core::layout::{self, Layout}; +use crate::core::mouse; +use crate::core::renderer; +use crate::core::widget::tree::{self, Tree}; +use crate::core::{Clipboard, Element, Shell, Widget}; +use crate::core::{Length, Point, Rectangle, Size, Vector}; +use crate::graphics::geometry; use std::marker::PhantomData; @@ -47,15 +28,11 @@ use std::marker::PhantomData; /// If you want to get a quick overview, here's how we can draw a simple circle: /// /// ```no_run -/// # mod iced { -/// # pub mod widget { -/// # pub use iced_graphics::widget::canvas; -/// # } -/// # pub use iced_native::{Color, Rectangle, Theme}; -/// # } -/// use iced::widget::canvas::{self, Canvas, Cursor, Fill, Frame, Geometry, Path, Program}; -/// use iced::{Color, Rectangle, Theme}; -/// +/// # use iced_widget::canvas::{self, Canvas, Cursor, Fill, Frame, Geometry, Path, Program}; +/// # use iced_widget::core::{Color, Rectangle}; +/// # use iced_widget::style::Theme; +/// # +/// # pub type Renderer = iced_widget::renderer::Renderer<Theme>; /// // First, we define the data we need for drawing /// #[derive(Debug)] /// struct Circle { @@ -66,9 +43,9 @@ use std::marker::PhantomData; /// impl Program<()> for Circle { /// type State = (); /// -/// fn draw(&self, _state: &(), _theme: &Theme, bounds: Rectangle, _cursor: Cursor) -> Vec<Geometry>{ +/// fn draw(&self, _state: &(), renderer: &Renderer, _theme: &Theme, bounds: Rectangle, _cursor: Cursor) -> Vec<Geometry>{ /// // We prepare a new `Frame` -/// let mut frame = Frame::new(bounds.size()); +/// let mut frame = Frame::new(renderer, bounds.size()); /// /// // We create a `Path` representing a simple circle /// let circle = Path::circle(frame.center(), self.radius); @@ -85,20 +62,22 @@ use std::marker::PhantomData; /// let canvas = Canvas::new(Circle { radius: 50.0 }); /// ``` #[derive(Debug)] -pub struct Canvas<Message, Theme, P> +pub struct Canvas<P, Message, Renderer = crate::Renderer> where - P: Program<Message, Theme>, + Renderer: geometry::Renderer, + P: Program<Message, Renderer>, { width: Length, height: Length, program: P, message_: PhantomData<Message>, - theme_: PhantomData<Theme>, + theme_: PhantomData<Renderer>, } -impl<Message, Theme, P> Canvas<Message, Theme, P> +impl<P, Message, Renderer> Canvas<P, Message, Renderer> where - P: Program<Message, Theme>, + Renderer: geometry::Renderer, + P: Program<Message, Renderer>, { const DEFAULT_SIZE: f32 = 100.0; @@ -126,10 +105,11 @@ where } } -impl<Message, P, B, T> Widget<Message, Renderer<B, T>> for Canvas<Message, T, P> +impl<P, Message, Renderer> Widget<Message, Renderer> + for Canvas<P, Message, Renderer> where - P: Program<Message, T>, - B: Backend, + Renderer: geometry::Renderer, + P: Program<Message, Renderer>, { fn tag(&self) -> tree::Tag { struct Tag<T>(T); @@ -150,7 +130,7 @@ where fn layout( &self, - _renderer: &Renderer<B, T>, + _renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { let limits = limits.width(self.width).height(self.height); @@ -162,23 +142,19 @@ where fn on_event( &mut self, tree: &mut Tree, - event: iced_native::Event, + event: core::Event, layout: Layout<'_>, cursor_position: Point, - _renderer: &Renderer<B, T>, + _renderer: &Renderer, _clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, ) -> event::Status { let bounds = layout.bounds(); let canvas_event = match event { - iced_native::Event::Mouse(mouse_event) => { - Some(Event::Mouse(mouse_event)) - } - iced_native::Event::Touch(touch_event) => { - Some(Event::Touch(touch_event)) - } - iced_native::Event::Keyboard(keyboard_event) => { + core::Event::Mouse(mouse_event) => Some(Event::Mouse(mouse_event)), + core::Event::Touch(touch_event) => Some(Event::Touch(touch_event)), + core::Event::Keyboard(keyboard_event) => { Some(Event::Keyboard(keyboard_event)) } _ => None, @@ -208,7 +184,7 @@ where layout: Layout<'_>, cursor_position: Point, _viewport: &Rectangle, - _renderer: &Renderer<B, T>, + _renderer: &Renderer, ) -> mouse::Interaction { let bounds = layout.bounds(); let cursor = Cursor::from_window_position(cursor_position); @@ -220,49 +196,43 @@ where fn draw( &self, tree: &Tree, - renderer: &mut Renderer<B, T>, - theme: &T, + renderer: &mut Renderer, + theme: &Renderer::Theme, _style: &renderer::Style, layout: Layout<'_>, cursor_position: Point, _viewport: &Rectangle, ) { - use iced_native::Renderer as _; - let bounds = layout.bounds(); if bounds.width < 1.0 || bounds.height < 1.0 { return; } - let translation = Vector::new(bounds.x, bounds.y); let cursor = Cursor::from_window_position(cursor_position); let state = tree.state.downcast_ref::<P::State>(); - renderer.with_translation(translation, |renderer| { - renderer.draw_primitive(Primitive::Group { - primitives: self - .program - .draw(state, theme, bounds, cursor) - .into_iter() - .map(Geometry::into_primitive) - .collect(), - }); - }); + renderer.with_translation( + Vector::new(bounds.x, bounds.y), + |renderer| { + renderer.draw( + self.program.draw(state, renderer, theme, bounds, cursor), + ); + }, + ); } } -impl<'a, Message, P, B, T> From<Canvas<Message, T, P>> - for Element<'a, Message, Renderer<B, T>> +impl<'a, P, Message, Renderer> From<Canvas<P, Message, Renderer>> + for Element<'a, Message, Renderer> where Message: 'a, - P: Program<Message, T> + 'a, - B: Backend, - T: 'a, + Renderer: 'a + geometry::Renderer, + P: Program<Message, Renderer> + 'a, { fn from( - canvas: Canvas<Message, T, P>, - ) -> Element<'a, Message, Renderer<B, T>> { + canvas: Canvas<P, Message, Renderer>, + ) -> Element<'a, Message, Renderer> { Element::new(canvas) } } diff --git a/graphics/src/widget/canvas/cursor.rs b/widget/src/canvas/cursor.rs index 9588d129..5a65e9a7 100644 --- a/graphics/src/widget/canvas/cursor.rs +++ b/widget/src/canvas/cursor.rs @@ -1,4 +1,4 @@ -use iced_native::{Point, Rectangle}; +use crate::core::{Point, Rectangle}; /// The mouse cursor state. #[derive(Debug, Clone, Copy, PartialEq)] diff --git a/graphics/src/widget/canvas/event.rs b/widget/src/canvas/event.rs index 7c733a4d..4508c184 100644 --- a/graphics/src/widget/canvas/event.rs +++ b/widget/src/canvas/event.rs @@ -1,9 +1,9 @@ //! Handle events of a canvas. -use iced_native::keyboard; -use iced_native::mouse; -use iced_native::touch; +use crate::core::keyboard; +use crate::core::mouse; +use crate::core::touch; -pub use iced_native::event::Status; +pub use crate::core::event::Status; /// A [`Canvas`] event. /// diff --git a/graphics/src/widget/canvas/program.rs b/widget/src/canvas/program.rs index 656dbfa6..ad0fbb83 100644 --- a/graphics/src/widget/canvas/program.rs +++ b/widget/src/canvas/program.rs @@ -1,7 +1,8 @@ -use crate::widget::canvas::event::{self, Event}; -use crate::widget::canvas::mouse; -use crate::widget::canvas::{Cursor, Geometry}; -use crate::Rectangle; +use crate::canvas::event::{self, Event}; +use crate::canvas::mouse; +use crate::canvas::Cursor; +use crate::core::Rectangle; +use crate::graphics::geometry::{self, Geometry}; /// The state and logic of a [`Canvas`]. /// @@ -9,7 +10,10 @@ use crate::Rectangle; /// application. /// /// [`Canvas`]: crate::widget::Canvas -pub trait Program<Message, Theme = iced_native::Theme> { +pub trait Program<Message, Renderer = crate::Renderer> +where + Renderer: geometry::Renderer, +{ /// The internal state mutated by the [`Program`]. type State: Default + 'static; @@ -44,7 +48,8 @@ pub trait Program<Message, Theme = iced_native::Theme> { fn draw( &self, state: &Self::State, - theme: &Theme, + renderer: &Renderer, + theme: &Renderer::Theme, bounds: Rectangle, cursor: Cursor, ) -> Vec<Geometry>; @@ -65,9 +70,10 @@ pub trait Program<Message, Theme = iced_native::Theme> { } } -impl<Message, Theme, T> Program<Message, Theme> for &T +impl<Message, Renderer, T> Program<Message, Renderer> for &T where - T: Program<Message, Theme>, + Renderer: geometry::Renderer, + T: Program<Message, Renderer>, { type State = T::State; @@ -84,11 +90,12 @@ where fn draw( &self, state: &Self::State, - theme: &Theme, + renderer: &Renderer, + theme: &Renderer::Theme, bounds: Rectangle, cursor: Cursor, ) -> Vec<Geometry> { - T::draw(self, state, theme, bounds, cursor) + T::draw(self, state, renderer, theme, bounds, cursor) } fn mouse_interaction( diff --git a/native/src/widget/checkbox.rs b/widget/src/checkbox.rs index ad05a8e7..7d43bb4a 100644 --- a/native/src/widget/checkbox.rs +++ b/widget/src/checkbox.rs @@ -1,16 +1,17 @@ //! Show toggle controls using checkboxes. -use crate::alignment; -use crate::event::{self, Event}; -use crate::layout; -use crate::mouse; -use crate::renderer; -use crate::text; -use crate::touch; -use crate::widget::{self, Row, Text, Tree}; -use crate::{ +use crate::core::alignment; +use crate::core::event::{self, Event}; +use crate::core::layout; +use crate::core::mouse; +use crate::core::renderer; +use crate::core::text; +use crate::core::touch; +use crate::core::widget::Tree; +use crate::core::{ Alignment, Clipboard, Element, Layout, Length, Pixels, Point, Rectangle, Shell, Widget, }; +use crate::{Row, Text}; pub use iced_style::checkbox::{Appearance, StyleSheet}; @@ -18,8 +19,9 @@ pub use iced_style::checkbox::{Appearance, StyleSheet}; /// /// # Example /// -/// ``` -/// # type Checkbox<'a, Message> = iced_native::widget::Checkbox<'a, Message, iced_native::renderer::Null>; +/// ```no_run +/// # type Checkbox<'a, Message> = +/// # iced_widget::Checkbox<'a, Message, iced_widget::renderer::Renderer<iced_widget::style::Theme>>; /// # /// pub enum Message { /// CheckboxToggled(bool), @@ -32,10 +34,10 @@ pub use iced_style::checkbox::{Appearance, StyleSheet}; /// ///  #[allow(missing_debug_implementations)] -pub struct Checkbox<'a, Message, Renderer> +pub struct Checkbox<'a, Message, Renderer = crate::Renderer> where Renderer: text::Renderer, - Renderer::Theme: StyleSheet + widget::text::StyleSheet, + Renderer::Theme: StyleSheet + crate::text::StyleSheet, { is_checked: bool, on_toggle: Box<dyn Fn(bool) -> Message + 'a>, @@ -44,7 +46,9 @@ where size: f32, spacing: f32, text_size: Option<f32>, - font: Renderer::Font, + text_line_height: text::LineHeight, + text_shaping: text::Shaping, + font: Option<Renderer::Font>, icon: Icon<Renderer::Font>, style: <Renderer::Theme as StyleSheet>::Style, } @@ -52,7 +56,7 @@ where impl<'a, Message, Renderer> Checkbox<'a, Message, Renderer> where Renderer: text::Renderer, - Renderer::Theme: StyleSheet + widget::text::StyleSheet, + Renderer::Theme: StyleSheet + crate::text::StyleSheet, { /// The default size of a [`Checkbox`]. const DEFAULT_SIZE: f32 = 20.0; @@ -80,11 +84,15 @@ where size: Self::DEFAULT_SIZE, spacing: Self::DEFAULT_SPACING, text_size: None, - font: Renderer::Font::default(), + text_line_height: text::LineHeight::default(), + text_shaping: text::Shaping::Basic, + font: None, icon: Icon { font: Renderer::ICON_FONT, code_point: Renderer::CHECKMARK_ICON, size: None, + line_height: text::LineHeight::default(), + shaping: text::Shaping::Basic, }, style: Default::default(), } @@ -114,11 +122,26 @@ where self } + /// Sets the text [`LineHeight`] of the [`Checkbox`]. + pub fn text_line_height( + mut self, + line_height: impl Into<text::LineHeight>, + ) -> Self { + self.text_line_height = line_height.into(); + self + } + + /// Sets the [`text::Shaping`] strategy of the [`Checkbox`]. + pub fn text_shaping(mut self, shaping: text::Shaping) -> Self { + self.text_shaping = shaping; + self + } + /// Sets the [`Font`] of the text of the [`Checkbox`]. /// /// [`Font`]: crate::text::Renderer::Font - pub fn font(mut self, font: Renderer::Font) -> Self { - self.font = font; + pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self { + self.font = Some(font.into()); self } @@ -142,7 +165,7 @@ impl<'a, Message, Renderer> Widget<Message, Renderer> for Checkbox<'a, Message, Renderer> where Renderer: text::Renderer, - Renderer::Theme: StyleSheet + widget::text::StyleSheet, + Renderer::Theme: StyleSheet + crate::text::StyleSheet, { fn width(&self) -> Length { self.width @@ -164,12 +187,14 @@ where .push(Row::new().width(self.size).height(self.size)) .push( Text::new(&self.label) - .font(self.font.clone()) + .font(self.font.unwrap_or_else(|| renderer.default_font())) .width(self.width) .size( self.text_size .unwrap_or_else(|| renderer.default_size()), - ), + ) + .line_height(self.text_line_height) + .shaping(self.text_shaping), ) .layout(renderer, limits) } @@ -255,14 +280,17 @@ where font, code_point, size, + line_height, + shaping, } = &self.icon; - let size = size.map(f32::from).unwrap_or(bounds.height * 0.7); + let size = size.unwrap_or(bounds.height * 0.7); if self.is_checked { renderer.fill_text(text::Text { content: &code_point.to_string(), - font: font.clone(), + font: *font, size, + line_height: *line_height, bounds: Rectangle { x: bounds.center_x(), y: bounds.center_y(), @@ -271,6 +299,7 @@ where color: custom_style.icon_color, horizontal_alignment: alignment::Horizontal::Center, vertical_alignment: alignment::Vertical::Center, + shaping: *shaping, }); } } @@ -278,18 +307,20 @@ where { let label_layout = children.next().unwrap(); - widget::text::draw( + crate::text::draw( renderer, style, label_layout, &self.label, self.text_size, - self.font.clone(), - widget::text::Appearance { + self.text_line_height, + self.font, + crate::text::Appearance { color: custom_style.text_color, }, alignment::Horizontal::Left, alignment::Vertical::Center, + self.text_shaping, ); } } @@ -300,7 +331,7 @@ impl<'a, Message, Renderer> From<Checkbox<'a, Message, Renderer>> where Message: 'a, Renderer: 'a + text::Renderer, - Renderer::Theme: StyleSheet + widget::text::StyleSheet, + Renderer::Theme: StyleSheet + crate::text::StyleSheet, { fn from( checkbox: Checkbox<'a, Message, Renderer>, @@ -318,4 +349,8 @@ pub struct Icon<Font> { pub code_point: char, /// Font size of the content. pub size: Option<f32>, + /// The line height of the icon. + pub line_height: text::LineHeight, + /// The shaping strategy of the icon. + pub shaping: text::Shaping, } diff --git a/native/src/widget/column.rs b/widget/src/column.rs index ebe579d5..8f363ec6 100644 --- a/native/src/widget/column.rs +++ b/widget/src/column.rs @@ -1,18 +1,18 @@ //! Distribute content vertically. -use crate::event::{self, Event}; -use crate::layout; -use crate::mouse; -use crate::overlay; -use crate::renderer; -use crate::widget::{Operation, Tree}; -use crate::{ +use crate::core::event::{self, Event}; +use crate::core::layout; +use crate::core::mouse; +use crate::core::overlay; +use crate::core::renderer; +use crate::core::widget::{Operation, Tree}; +use crate::core::{ Alignment, Clipboard, Element, Layout, Length, Padding, Pixels, Point, Rectangle, Shell, Widget, }; /// A container that distributes its contents vertically. #[allow(missing_debug_implementations)] -pub struct Column<'a, Message, Renderer> { +pub struct Column<'a, Message, Renderer = crate::Renderer> { spacing: f32, padding: Padding, width: Length, @@ -102,7 +102,7 @@ impl<'a, Message, Renderer> Default for Column<'a, Message, Renderer> { impl<'a, Message, Renderer> Widget<Message, Renderer> for Column<'a, Message, Renderer> where - Renderer: crate::Renderer, + Renderer: crate::core::Renderer, { fn children(&self) -> Vec<Tree> { self.children.iter().map(Tree::new).collect() @@ -256,7 +256,7 @@ impl<'a, Message, Renderer> From<Column<'a, Message, Renderer>> for Element<'a, Message, Renderer> where Message: 'a, - Renderer: crate::Renderer + 'a, + Renderer: crate::core::Renderer + 'a, { fn from(column: Column<'a, Message, Renderer>) -> Self { Self::new(column) diff --git a/native/src/widget/container.rs b/widget/src/container.rs index b77bf50d..9d932772 100644 --- a/native/src/widget/container.rs +++ b/widget/src/container.rs @@ -1,12 +1,12 @@ //! Decorate content and apply alignment. -use crate::alignment::{self, Alignment}; -use crate::event::{self, Event}; -use crate::layout; -use crate::mouse; -use crate::overlay; -use crate::renderer; -use crate::widget::{self, Operation, Tree}; -use crate::{ +use crate::core::alignment::{self, Alignment}; +use crate::core::event::{self, Event}; +use crate::core::layout; +use crate::core::mouse; +use crate::core::overlay; +use crate::core::renderer; +use crate::core::widget::{self, Operation, Tree}; +use crate::core::{ Background, Clipboard, Color, Element, Layout, Length, Padding, Pixels, Point, Rectangle, Shell, Widget, }; @@ -17,9 +17,9 @@ pub use iced_style::container::{Appearance, StyleSheet}; /// /// It is normally used for alignment purposes. #[allow(missing_debug_implementations)] -pub struct Container<'a, Message, Renderer> +pub struct Container<'a, Message, Renderer = crate::Renderer> where - Renderer: crate::Renderer, + Renderer: crate::core::Renderer, Renderer::Theme: StyleSheet, { id: Option<Id>, @@ -36,7 +36,7 @@ where impl<'a, Message, Renderer> Container<'a, Message, Renderer> where - Renderer: crate::Renderer, + Renderer: crate::core::Renderer, Renderer::Theme: StyleSheet, { /// Creates an empty [`Container`]. @@ -131,7 +131,7 @@ where impl<'a, Message, Renderer> Widget<Message, Renderer> for Container<'a, Message, Renderer> where - Renderer: crate::Renderer, + Renderer: crate::core::Renderer, Renderer::Theme: StyleSheet, { fn children(&self) -> Vec<Tree> { @@ -276,7 +276,7 @@ impl<'a, Message, Renderer> From<Container<'a, Message, Renderer>> for Element<'a, Message, Renderer> where Message: 'a, - Renderer: 'a + crate::Renderer, + Renderer: 'a + crate::core::Renderer, Renderer::Theme: StyleSheet, { fn from( @@ -326,7 +326,7 @@ pub fn draw_background<Renderer>( appearance: &Appearance, bounds: Rectangle, ) where - Renderer: crate::Renderer, + Renderer: crate::core::Renderer, { if appearance.background.is_some() || appearance.border_width > 0.0 { renderer.fill_quad( diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs new file mode 100644 index 00000000..336ac4ee --- /dev/null +++ b/widget/src/helpers.rs @@ -0,0 +1,371 @@ +//! Helper functions to create pure widgets. +use crate::button::{self, Button}; +use crate::checkbox::{self, Checkbox}; +use crate::container::{self, Container}; +use crate::core; +use crate::core::widget::operation; +use crate::core::{Element, Length, Pixels}; +use crate::overlay; +use crate::pick_list::{self, PickList}; +use crate::progress_bar::{self, ProgressBar}; +use crate::radio::{self, Radio}; +use crate::rule::{self, Rule}; +use crate::runtime::Command; +use crate::scrollable::{self, Scrollable}; +use crate::slider::{self, Slider}; +use crate::text::{self, Text}; +use crate::text_input::{self, TextInput}; +use crate::toggler::{self, Toggler}; +use crate::tooltip::{self, Tooltip}; +use crate::{Column, MouseArea, Row, Space, VerticalSlider}; + +use std::borrow::Cow; +use std::ops::RangeInclusive; + +/// Creates a [`Column`] with the given children. +/// +/// [`Column`]: widget::Column +#[macro_export] +macro_rules! column { + () => ( + $crate::Column::new() + ); + ($($x:expr),+ $(,)?) => ( + $crate::Column::with_children(vec![$($crate::core::Element::from($x)),+]) + ); +} + +/// Creates a [`Row`] with the given children. +/// +/// [`Row`]: widget::Row +#[macro_export] +macro_rules! row { + () => ( + $crate::Row::new() + ); + ($($x:expr),+ $(,)?) => ( + $crate::Row::with_children(vec![$($crate::core::Element::from($x)),+]) + ); +} + +/// Creates a new [`Container`] with the provided content. +/// +/// [`Container`]: widget::Container +pub fn container<'a, Message, Renderer>( + content: impl Into<Element<'a, Message, Renderer>>, +) -> Container<'a, Message, Renderer> +where + Renderer: core::Renderer, + Renderer::Theme: container::StyleSheet, +{ + Container::new(content) +} + +/// Creates a new [`Column`] with the given children. +/// +/// [`Column`]: widget::Column +pub fn column<Message, Renderer>( + children: Vec<Element<'_, Message, Renderer>>, +) -> Column<'_, Message, Renderer> { + Column::with_children(children) +} + +/// Creates a new [`Row`] with the given children. +/// +/// [`Row`]: widget::Row +pub fn row<Message, Renderer>( + children: Vec<Element<'_, Message, Renderer>>, +) -> Row<'_, Message, Renderer> { + Row::with_children(children) +} + +/// Creates a new [`Scrollable`] with the provided content. +/// +/// [`Scrollable`]: widget::Scrollable +pub fn scrollable<'a, Message, Renderer>( + content: impl Into<Element<'a, Message, Renderer>>, +) -> Scrollable<'a, Message, Renderer> +where + Renderer: core::Renderer, + Renderer::Theme: scrollable::StyleSheet, +{ + Scrollable::new(content) +} + +/// Creates a new [`Button`] with the provided content. +/// +/// [`Button`]: widget::Button +pub fn button<'a, Message, Renderer>( + content: impl Into<Element<'a, Message, Renderer>>, +) -> Button<'a, Message, Renderer> +where + Renderer: core::Renderer, + Renderer::Theme: button::StyleSheet, + <Renderer::Theme as button::StyleSheet>::Style: Default, +{ + Button::new(content) +} + +/// Creates a new [`Tooltip`] with the provided content, tooltip text, and [`tooltip::Position`]. +/// +/// [`Tooltip`]: widget::Tooltip +/// [`tooltip::Position`]: widget::tooltip::Position +pub fn tooltip<'a, Message, Renderer>( + content: impl Into<Element<'a, Message, Renderer>>, + tooltip: impl ToString, + position: tooltip::Position, +) -> crate::Tooltip<'a, Message, Renderer> +where + Renderer: core::text::Renderer, + Renderer::Theme: container::StyleSheet + text::StyleSheet, +{ + Tooltip::new(content, tooltip.to_string(), position) +} + +/// Creates a new [`Text`] widget with the provided content. +/// +/// [`Text`]: widget::Text +pub fn text<'a, Renderer>(text: impl ToString) -> Text<'a, Renderer> +where + Renderer: core::text::Renderer, + Renderer::Theme: text::StyleSheet, +{ + Text::new(text.to_string()) +} + +/// Creates a new [`Checkbox`]. +/// +/// [`Checkbox`]: widget::Checkbox +pub fn checkbox<'a, Message, Renderer>( + label: impl Into<String>, + is_checked: bool, + f: impl Fn(bool) -> Message + 'a, +) -> Checkbox<'a, Message, Renderer> +where + Renderer: core::text::Renderer, + Renderer::Theme: checkbox::StyleSheet + text::StyleSheet, +{ + Checkbox::new(label, is_checked, f) +} + +/// Creates a new [`Radio`]. +/// +/// [`Radio`]: widget::Radio +pub fn radio<Message, Renderer, V>( + label: impl Into<String>, + value: V, + selected: Option<V>, + on_click: impl FnOnce(V) -> Message, +) -> Radio<Message, Renderer> +where + Message: Clone, + Renderer: core::text::Renderer, + Renderer::Theme: radio::StyleSheet, + V: Copy + Eq, +{ + Radio::new(label, value, selected, on_click) +} + +/// Creates a new [`Toggler`]. +/// +/// [`Toggler`]: widget::Toggler +pub fn toggler<'a, Message, Renderer>( + label: impl Into<Option<String>>, + is_checked: bool, + f: impl Fn(bool) -> Message + 'a, +) -> Toggler<'a, Message, Renderer> +where + Renderer: core::text::Renderer, + Renderer::Theme: toggler::StyleSheet, +{ + Toggler::new(label, is_checked, f) +} + +/// Creates a new [`TextInput`]. +/// +/// [`TextInput`]: widget::TextInput +pub fn text_input<'a, Message, Renderer>( + placeholder: &str, + value: &str, +) -> TextInput<'a, Message, Renderer> +where + Message: Clone, + Renderer: core::text::Renderer, + Renderer::Theme: text_input::StyleSheet, +{ + TextInput::new(placeholder, value) +} + +/// Creates a new [`Slider`]. +/// +/// [`Slider`]: widget::Slider +pub fn slider<'a, T, Message, Renderer>( + range: std::ops::RangeInclusive<T>, + value: T, + on_change: impl Fn(T) -> Message + 'a, +) -> Slider<'a, T, Message, Renderer> +where + T: Copy + From<u8> + std::cmp::PartialOrd, + Message: Clone, + Renderer: core::Renderer, + Renderer::Theme: slider::StyleSheet, +{ + Slider::new(range, value, on_change) +} + +/// Creates a new [`VerticalSlider`]. +/// +/// [`VerticalSlider`]: widget::VerticalSlider +pub fn vertical_slider<'a, T, Message, Renderer>( + range: std::ops::RangeInclusive<T>, + value: T, + on_change: impl Fn(T) -> Message + 'a, +) -> VerticalSlider<'a, T, Message, Renderer> +where + T: Copy + From<u8> + std::cmp::PartialOrd, + Message: Clone, + Renderer: core::Renderer, + Renderer::Theme: slider::StyleSheet, +{ + VerticalSlider::new(range, value, on_change) +} + +/// Creates a new [`PickList`]. +/// +/// [`PickList`]: widget::PickList +pub fn pick_list<'a, Message, Renderer, T>( + options: impl Into<Cow<'a, [T]>>, + selected: Option<T>, + on_selected: impl Fn(T) -> Message + 'a, +) -> PickList<'a, T, Message, Renderer> +where + T: ToString + Eq + 'static, + [T]: ToOwned<Owned = Vec<T>>, + Renderer: core::text::Renderer, + Renderer::Theme: pick_list::StyleSheet + + scrollable::StyleSheet + + overlay::menu::StyleSheet + + container::StyleSheet, + <Renderer::Theme as overlay::menu::StyleSheet>::Style: + From<<Renderer::Theme as pick_list::StyleSheet>::Style>, +{ + PickList::new(options, selected, on_selected) +} + +/// Creates a new horizontal [`Space`] with the given [`Length`]. +/// +/// [`Space`]: widget::Space +pub fn horizontal_space(width: impl Into<Length>) -> Space { + Space::with_width(width) +} + +/// Creates a new vertical [`Space`] with the given [`Length`]. +/// +/// [`Space`]: widget::Space +pub fn vertical_space(height: impl Into<Length>) -> Space { + Space::with_height(height) +} + +/// Creates a horizontal [`Rule`] with the given height. +/// +/// [`Rule`]: widget::Rule +pub fn horizontal_rule<Renderer>(height: impl Into<Pixels>) -> Rule<Renderer> +where + Renderer: core::Renderer, + Renderer::Theme: rule::StyleSheet, +{ + Rule::horizontal(height) +} + +/// Creates a vertical [`Rule`] with the given width. +/// +/// [`Rule`]: widget::Rule +pub fn vertical_rule<Renderer>(width: impl Into<Pixels>) -> Rule<Renderer> +where + Renderer: core::Renderer, + Renderer::Theme: rule::StyleSheet, +{ + Rule::vertical(width) +} + +/// Creates a new [`ProgressBar`]. +/// +/// It expects: +/// * an inclusive range of possible values, and +/// * the current value of the [`ProgressBar`]. +/// +/// [`ProgressBar`]: widget::ProgressBar +pub fn progress_bar<Renderer>( + range: RangeInclusive<f32>, + value: f32, +) -> ProgressBar<Renderer> +where + Renderer: core::Renderer, + Renderer::Theme: progress_bar::StyleSheet, +{ + ProgressBar::new(range, value) +} + +/// Creates a new [`Image`]. +/// +/// [`Image`]: widget::Image +#[cfg(feature = "image")] +#[cfg_attr(docsrs, doc(cfg(feature = "image")))] +pub fn image<Handle>(handle: impl Into<Handle>) -> crate::Image<Handle> { + crate::Image::new(handle.into()) +} + +/// Creates a new [`Svg`] widget from the given [`Handle`]. +/// +/// [`Svg`]: widget::Svg +/// [`Handle`]: widget::svg::Handle +#[cfg(feature = "svg")] +#[cfg_attr(docsrs, doc(cfg(feature = "svg")))] +pub fn svg<Renderer>( + handle: impl Into<core::svg::Handle>, +) -> crate::Svg<Renderer> +where + Renderer: core::svg::Renderer, + Renderer::Theme: crate::svg::StyleSheet, +{ + crate::Svg::new(handle) +} + +/// Creates a new [`Canvas`]. +#[cfg(feature = "canvas")] +#[cfg_attr(docsrs, doc(cfg(feature = "canvas")))] +pub fn canvas<P, Message, Renderer>( + program: P, +) -> crate::Canvas<P, Message, Renderer> +where + Renderer: crate::graphics::geometry::Renderer, + P: crate::canvas::Program<Message, Renderer>, +{ + crate::Canvas::new(program) +} + +/// Focuses the previous focusable widget. +pub fn focus_previous<Message>() -> Command<Message> +where + Message: 'static, +{ + Command::widget(operation::focusable::focus_previous()) +} + +/// Focuses the next focusable widget. +pub fn focus_next<Message>() -> Command<Message> +where + Message: 'static, +{ + Command::widget(operation::focusable::focus_next()) +} + +/// A container intercepting mouse events. +pub fn mouse_area<'a, Message, Renderer>( + widget: impl Into<Element<'a, Message, Renderer>>, +) -> MouseArea<'a, Message, Renderer> +where + Renderer: core::Renderer, +{ + MouseArea::new(widget) +} diff --git a/native/src/widget/image.rs b/widget/src/image.rs index 73257a74..abcb6ef2 100644 --- a/native/src/widget/image.rs +++ b/widget/src/image.rs @@ -2,16 +2,18 @@ pub mod viewer; pub use viewer::Viewer; -use crate::image; -use crate::layout; -use crate::renderer; -use crate::widget::Tree; -use crate::{ +use crate::core::image; +use crate::core::layout; +use crate::core::renderer; +use crate::core::widget::Tree; +use crate::core::{ ContentFit, Element, Layout, Length, Point, Rectangle, Size, Vector, Widget, }; use std::hash::Hash; +pub use image::Handle; + /// Creates a new [`Viewer`] with the given image `Handle`. pub fn viewer<Handle>(handle: Handle) -> Viewer<Handle> { Viewer::new(handle) @@ -21,9 +23,8 @@ pub fn viewer<Handle>(handle: Handle) -> Viewer<Handle> { /// /// # Example /// -/// ``` -/// # use iced_native::widget::Image; -/// # use iced_native::image; +/// ```no_run +/// # use iced_widget::image::{self, Image}; /// # /// let image = Image::<image::Handle>::new("resources/ferris.png"); /// ``` diff --git a/native/src/widget/image/viewer.rs b/widget/src/image/viewer.rs index 1f8d5d7a..0d60d818 100644 --- a/native/src/widget/image/viewer.rs +++ b/widget/src/image/viewer.rs @@ -1,11 +1,11 @@ //! Zoom and pan on an image. -use crate::event::{self, Event}; -use crate::image; -use crate::layout; -use crate::mouse; -use crate::renderer; -use crate::widget::tree::{self, Tree}; -use crate::{ +use crate::core::event::{self, Event}; +use crate::core::image; +use crate::core::layout; +use crate::core::mouse; +use crate::core::renderer; +use crate::core::widget::tree::{self, Tree}; +use crate::core::{ Clipboard, Element, Layout, Length, Pixels, Point, Rectangle, Shell, Size, Vector, Widget, }; diff --git a/lazy/src/lazy.rs b/widget/src/lazy.rs index 5e909a49..0ad46865 100644 --- a/lazy/src/lazy.rs +++ b/widget/src/lazy.rs @@ -1,18 +1,32 @@ -use iced_native::event; -use iced_native::layout::{self, Layout}; -use iced_native::mouse; -use iced_native::overlay; -use iced_native::renderer; -use iced_native::widget::tree::{self, Tree}; -use iced_native::widget::{self, Widget}; -use iced_native::Element; -use iced_native::{Clipboard, Hasher, Length, Point, Rectangle, Shell, Size}; +#![allow(clippy::await_holding_refcell_ref, clippy::type_complexity)] +pub(crate) mod helpers; + +pub mod component; +pub mod responsive; + +pub use component::Component; +pub use responsive::Responsive; + +mod cache; + +use crate::core::event::{self, Event}; +use crate::core::layout::{self, Layout}; +use crate::core::mouse; +use crate::core::overlay; +use crate::core::renderer; +use crate::core::widget::tree::{self, Tree}; +use crate::core::widget::{self, Widget}; +use crate::core::Element; +use crate::core::{ + self, Clipboard, Hasher, Length, Point, Rectangle, Shell, Size, +}; use ouroboros::self_referencing; use std::cell::RefCell; use std::hash::{Hash, Hasher as H}; use std::rc::Rc; +/// A widget that only rebuilds its contents when necessary. #[allow(missing_debug_implementations)] pub struct Lazy<'a, Message, Renderer, Dependency, View> { dependency: Dependency, @@ -28,6 +42,8 @@ where Dependency: Hash + 'a, View: Into<Element<'static, Message, Renderer>>, { + /// Creates a new [`Lazy`] widget with the given data `Dependency` and a + /// closure that can turn this data into a widget tree. pub fn new( dependency: Dependency, view: impl Fn(&Dependency) -> View + 'a, @@ -41,7 +57,7 @@ where fn with_element<T>( &self, - f: impl FnOnce(&Element<Message, Renderer>) -> T, + f: impl FnOnce(&Element<'_, Message, Renderer>) -> T, ) -> T { f(self .element @@ -55,7 +71,7 @@ where fn with_element_mut<T>( &self, - f: impl FnOnce(&mut Element<Message, Renderer>) -> T, + f: impl FnOnce(&mut Element<'_, Message, Renderer>) -> T, ) -> T { f(self .element @@ -79,7 +95,7 @@ where View: Into<Element<'static, Message, Renderer>> + 'static, Dependency: Hash + 'a, Message: 'static, - Renderer: iced_native::Renderer + 'static, + Renderer: core::Renderer + 'static, { fn tag(&self) -> tree::Tag { struct Tag<T>(T); @@ -163,7 +179,7 @@ where fn on_event( &mut self, tree: &mut Tree, - event: iced_native::Event, + event: Event, layout: Layout<'_>, cursor_position: Point, renderer: &Renderer, @@ -304,7 +320,7 @@ impl<'a, Message, Renderer> Overlay<'a, Message, Renderer> { impl<'a, Message, Renderer> overlay::Overlay<Message, Renderer> for Overlay<'a, Message, Renderer> where - Renderer: iced_native::Renderer, + Renderer: core::Renderer, { fn layout( &self, @@ -353,7 +369,7 @@ where fn on_event( &mut self, - event: iced_native::Event, + event: Event, layout: Layout<'_>, cursor_position: Point, renderer: &Renderer, @@ -370,7 +386,7 @@ where shell, ) }) - .unwrap_or(iced_native::event::Status::Ignored) + .unwrap_or(event::Status::Ignored) } fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool { @@ -386,7 +402,7 @@ impl<'a, Message, Renderer, Dependency, View> for Element<'a, Message, Renderer> where View: Into<Element<'static, Message, Renderer>> + 'static, - Renderer: iced_native::Renderer + 'static, + Renderer: core::Renderer + 'static, Message: 'static, Dependency: Hash + 'a, { diff --git a/lazy/src/cache.rs b/widget/src/lazy/cache.rs index 5b4a39f6..e7b87614 100644 --- a/lazy/src/cache.rs +++ b/widget/src/lazy/cache.rs @@ -1,5 +1,5 @@ -use iced_native::overlay; -use iced_native::Element; +use crate::core::overlay; +use crate::core::Element; use ouroboros::self_referencing; diff --git a/lazy/src/component.rs b/widget/src/lazy/component.rs index 4a43223d..49ae68af 100644 --- a/lazy/src/component.rs +++ b/widget/src/lazy/component.rs @@ -1,13 +1,13 @@ //! Build and reuse custom widgets using The Elm Architecture. -use iced_native::event; -use iced_native::layout::{self, Layout}; -use iced_native::mouse; -use iced_native::overlay; -use iced_native::renderer; -use iced_native::widget; -use iced_native::widget::tree::{self, Tree}; -use iced_native::{ - Clipboard, Element, Length, Point, Rectangle, Shell, Size, Widget, +use crate::core::event; +use crate::core::layout::{self, Layout}; +use crate::core::mouse; +use crate::core::overlay; +use crate::core::renderer; +use crate::core::widget; +use crate::core::widget::tree::{self, Tree}; +use crate::core::{ + self, Clipboard, Element, Length, Point, Rectangle, Shell, Size, Widget, }; use ouroboros::self_referencing; @@ -70,7 +70,7 @@ where C: Component<Message, Renderer> + 'a, C::State: 'static, Message: 'a, - Renderer: iced_native::Renderer + 'a, + Renderer: core::Renderer + 'a, { Element::new(Instance { state: RefCell::new(Some( @@ -106,7 +106,7 @@ struct State<'a, Message: 'a, Renderer: 'a, Event: 'a, S: 'a> { impl<'a, Message, Renderer, Event, S> Instance<'a, Message, Renderer, Event, S> where S: Default + 'static, - Renderer: iced_native::Renderer, + Renderer: renderer::Renderer, { fn diff_self(&self) { self.with_element(|element| { @@ -216,7 +216,7 @@ impl<'a, Message, Renderer, Event, S> Widget<Message, Renderer> for Instance<'a, Message, Renderer, Event, S> where S: 'static + Default, - Renderer: iced_native::Renderer, + Renderer: core::Renderer, { fn tag(&self) -> tree::Tag { tree::Tag::of::<Tag<S>>() @@ -263,7 +263,7 @@ where fn on_event( &mut self, tree: &mut Tree, - event: iced_native::Event, + event: core::Event, layout: Layout<'_>, cursor_position: Point, renderer: &Renderer, @@ -546,7 +546,7 @@ impl<'a, 'b, Message, Renderer, Event, S> impl<'a, 'b, Message, Renderer, Event, S> overlay::Overlay<Message, Renderer> for OverlayInstance<'a, 'b, Message, Renderer, Event, S> where - Renderer: iced_native::Renderer, + Renderer: core::Renderer, S: 'static + Default, { fn layout( @@ -596,13 +596,13 @@ where fn on_event( &mut self, - event: iced_native::Event, + event: core::Event, layout: Layout<'_>, cursor_position: Point, renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, - ) -> iced_native::event::Status { + ) -> event::Status { let mut local_messages = Vec::new(); let mut local_shell = Shell::new(&mut local_messages); @@ -617,7 +617,7 @@ where &mut local_shell, ) }) - .unwrap_or(iced_native::event::Status::Ignored); + .unwrap_or(event::Status::Ignored); local_shell.revalidate_layout(|| shell.invalidate_layout()); diff --git a/lazy/src/lib.rs b/widget/src/lazy/helpers.rs index 41a28773..8ca9cb86 100644 --- a/lazy/src/lib.rs +++ b/widget/src/lazy/helpers.rs @@ -1,36 +1,11 @@ -#![doc( - html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg" -)] -#![deny( - missing_debug_implementations, - unused_results, - clippy::extra_unused_lifetimes, - clippy::from_over_into, - clippy::needless_borrow, - clippy::new_without_default, - clippy::useless_conversion -)] -#![forbid(unsafe_code)] -#![allow( - clippy::await_holding_refcell_ref, - clippy::inherent_to_string, - clippy::type_complexity -)] -#![cfg_attr(docsrs, feature(doc_cfg))] -mod lazy; +use crate::core::{self, Element, Size}; +use crate::lazy::component::{self, Component}; +use crate::lazy::{Lazy, Responsive}; -pub mod component; -pub mod responsive; - -pub use component::Component; -pub use lazy::Lazy; -pub use responsive::Responsive; - -mod cache; - -use iced_native::{Element, Size}; use std::hash::Hash; +/// Creates a new [`Lazy`] widget with the given data `Dependency` and a +/// closure that can turn this data into a widget tree. pub fn lazy<'a, Message, Renderer, Dependency, View>( dependency: Dependency, view: impl Fn(&Dependency) -> View + 'a, @@ -51,16 +26,22 @@ where C: Component<Message, Renderer> + 'a, C::State: 'static, Message: 'a, - Renderer: iced_native::Renderer + 'a, + Renderer: core::Renderer + 'a, { component::view(component) } +/// Creates a new [`Responsive`] widget with a closure that produces its +/// contents. +/// +/// The `view` closure will be provided with the current [`Size`] of +/// the [`Responsive`] widget and, therefore, can be used to build the +/// contents of the widget in a responsive way. pub fn responsive<'a, Message, Renderer>( f: impl Fn(Size) -> Element<'a, Message, Renderer> + 'a, ) -> Responsive<'a, Message, Renderer> where - Renderer: iced_native::Renderer, + Renderer: core::Renderer, { Responsive::new(f) } diff --git a/lazy/src/responsive.rs b/widget/src/lazy/responsive.rs index d87815f6..b41d978b 100644 --- a/lazy/src/responsive.rs +++ b/widget/src/lazy/responsive.rs @@ -1,13 +1,14 @@ -use iced_native::event; -use iced_native::layout::{self, Layout}; -use iced_native::mouse; -use iced_native::overlay; -use iced_native::renderer; -use iced_native::widget::tree::{self, Tree}; -use iced_native::widget::{self, horizontal_space}; -use iced_native::{ - Clipboard, Element, Length, Point, Rectangle, Shell, Size, Widget, +use crate::core::event::{self, Event}; +use crate::core::layout::{self, Layout}; +use crate::core::mouse; +use crate::core::overlay; +use crate::core::renderer; +use crate::core::widget; +use crate::core::widget::tree::{self, Tree}; +use crate::core::{ + self, Clipboard, Element, Length, Point, Rectangle, Shell, Size, Widget, }; +use crate::horizontal_space; use ouroboros::self_referencing; use std::cell::{RefCell, RefMut}; @@ -19,14 +20,14 @@ use std::ops::Deref; /// A [`Responsive`] widget will always try to fill all the available space of /// its parent. #[allow(missing_debug_implementations)] -pub struct Responsive<'a, Message, Renderer> { +pub struct Responsive<'a, Message, Renderer = crate::Renderer> { view: Box<dyn Fn(Size) -> Element<'a, Message, Renderer> + 'a>, content: RefCell<Content<'a, Message, Renderer>>, } impl<'a, Message, Renderer> Responsive<'a, Message, Renderer> where - Renderer: iced_native::Renderer, + Renderer: core::Renderer, { /// Creates a new [`Responsive`] widget with a closure that produces its /// contents. @@ -56,7 +57,7 @@ struct Content<'a, Message, Renderer> { impl<'a, Message, Renderer> Content<'a, Message, Renderer> where - Renderer: iced_native::Renderer, + Renderer: core::Renderer, { fn layout(&mut self, renderer: &Renderer) { if self.layout.is_none() { @@ -119,7 +120,7 @@ struct State { impl<'a, Message, Renderer> Widget<Message, Renderer> for Responsive<'a, Message, Renderer> where - Renderer: iced_native::Renderer, + Renderer: core::Renderer, { fn tag(&self) -> tree::Tag { tree::Tag::of::<State>() @@ -173,7 +174,7 @@ where fn on_event( &mut self, tree: &mut Tree, - event: iced_native::Event, + event: Event, layout: Layout<'_>, cursor_position: Point, renderer: &Renderer, @@ -287,7 +288,8 @@ where content: self.content.borrow_mut(), tree: state.tree.borrow_mut(), types: PhantomData, - overlay_builder: |content: &mut RefMut<Content<_, _>>, tree| { + overlay_builder: |content: &mut RefMut<'_, Content<'_, _, _>>, + tree| { content.update(tree, layout.bounds().size(), &self.view); content.layout(renderer); @@ -321,7 +323,7 @@ where impl<'a, Message, Renderer> From<Responsive<'a, Message, Renderer>> for Element<'a, Message, Renderer> where - Renderer: iced_native::Renderer + 'a, + Renderer: core::Renderer + 'a, Message: 'a, { fn from(responsive: Responsive<'a, Message, Renderer>) -> Self { @@ -359,7 +361,7 @@ impl<'a, 'b, Message, Renderer> Overlay<'a, 'b, Message, Renderer> { impl<'a, 'b, Message, Renderer> overlay::Overlay<Message, Renderer> for Overlay<'a, 'b, Message, Renderer> where - Renderer: iced_native::Renderer, + Renderer: core::Renderer, { fn layout( &self, @@ -408,7 +410,7 @@ where fn on_event( &mut self, - event: iced_native::Event, + event: Event, layout: Layout<'_>, cursor_position: Point, renderer: &Renderer, @@ -425,7 +427,7 @@ where shell, ) }) - .unwrap_or(iced_native::event::Status::Ignored) + .unwrap_or(event::Status::Ignored) } fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool { diff --git a/widget/src/lib.rs b/widget/src/lib.rs new file mode 100644 index 00000000..ab1ab95b --- /dev/null +++ b/widget/src/lib.rs @@ -0,0 +1,125 @@ +//! Use the built-in widgets or create your own. +#![doc( + html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg" +)] +#![deny( + missing_debug_implementations, + missing_docs, + unused_results, + clippy::extra_unused_lifetimes, + clippy::from_over_into, + clippy::needless_borrow, + clippy::new_without_default, + clippy::useless_conversion +)] +#![forbid(unsafe_code, rust_2018_idioms)] +#![allow(clippy::inherent_to_string, clippy::type_complexity)] +pub use iced_renderer as renderer; +pub use iced_renderer::graphics; +pub use iced_runtime as runtime; +pub use iced_runtime::core; +pub use iced_style as style; + +mod column; +mod mouse_area; +mod row; + +pub mod button; +pub mod checkbox; +pub mod container; +pub mod overlay; +pub mod pane_grid; +pub mod pick_list; +pub mod progress_bar; +pub mod radio; +pub mod rule; +pub mod scrollable; +pub mod slider; +pub mod space; +pub mod text; +pub mod text_input; +pub mod toggler; +pub mod tooltip; +pub mod vertical_slider; + +mod helpers; + +pub use helpers::*; + +#[cfg(feature = "lazy")] +mod lazy; + +#[cfg(feature = "lazy")] +pub use crate::lazy::{Component, Lazy, Responsive}; + +#[cfg(feature = "lazy")] +pub use crate::lazy::helpers::*; + +#[doc(no_inline)] +pub use button::Button; +#[doc(no_inline)] +pub use checkbox::Checkbox; +#[doc(no_inline)] +pub use column::Column; +#[doc(no_inline)] +pub use container::Container; +#[doc(no_inline)] +pub use mouse_area::MouseArea; +#[doc(no_inline)] +pub use pane_grid::PaneGrid; +#[doc(no_inline)] +pub use pick_list::PickList; +#[doc(no_inline)] +pub use progress_bar::ProgressBar; +#[doc(no_inline)] +pub use radio::Radio; +#[doc(no_inline)] +pub use row::Row; +#[doc(no_inline)] +pub use rule::Rule; +#[doc(no_inline)] +pub use scrollable::Scrollable; +#[doc(no_inline)] +pub use slider::Slider; +#[doc(no_inline)] +pub use space::Space; +#[doc(no_inline)] +pub use text::Text; +#[doc(no_inline)] +pub use text_input::TextInput; +#[doc(no_inline)] +pub use toggler::Toggler; +#[doc(no_inline)] +pub use tooltip::Tooltip; +#[doc(no_inline)] +pub use vertical_slider::VerticalSlider; + +#[cfg(feature = "svg")] +pub mod svg; + +#[cfg(feature = "svg")] +#[doc(no_inline)] +pub use svg::Svg; + +#[cfg(feature = "image")] +pub mod image; + +#[cfg(feature = "image")] +#[doc(no_inline)] +pub use image::Image; + +#[cfg(feature = "canvas")] +pub mod canvas; + +#[cfg(feature = "canvas")] +#[doc(no_inline)] +pub use canvas::Canvas; + +#[cfg(feature = "qr_code")] +pub mod qr_code; + +#[cfg(feature = "qr_code")] +#[doc(no_inline)] +pub use qr_code::QRCode; + +type Renderer<Theme = style::Theme> = renderer::Renderer<Theme>; diff --git a/native/src/widget/mouse_area.rs b/widget/src/mouse_area.rs index 69cfddbf..0232c494 100644 --- a/native/src/widget/mouse_area.rs +++ b/widget/src/mouse_area.rs @@ -1,13 +1,13 @@ //! A container for capturing mouse events. -use crate::event::{self, Event}; -use crate::layout; -use crate::mouse; -use crate::overlay; -use crate::renderer; -use crate::touch; -use crate::widget::{tree, Operation, Tree}; -use crate::{ +use crate::core::event::{self, Event}; +use crate::core::layout; +use crate::core::mouse; +use crate::core::overlay; +use crate::core::renderer; +use crate::core::touch; +use crate::core::widget::{tree, Operation, Tree}; +use crate::core::{ Clipboard, Element, Layout, Length, Point, Rectangle, Shell, Widget, }; @@ -91,7 +91,7 @@ impl<'a, Message, Renderer> MouseArea<'a, Message, Renderer> { impl<'a, Message, Renderer> Widget<Message, Renderer> for MouseArea<'a, Message, Renderer> where - Renderer: crate::Renderer, + Renderer: renderer::Renderer, Message: Clone, { fn tag(&self) -> tree::Tag { @@ -222,7 +222,7 @@ impl<'a, Message, Renderer> From<MouseArea<'a, Message, Renderer>> for Element<'a, Message, Renderer> where Message: 'a + Clone, - Renderer: 'a + crate::Renderer, + Renderer: 'a + renderer::Renderer, { fn from( area: MouseArea<'a, Message, Renderer>, diff --git a/graphics/src/overlay.rs b/widget/src/overlay.rs index bc0ed744..bc0ed744 100644 --- a/graphics/src/overlay.rs +++ b/widget/src/overlay.rs diff --git a/native/src/overlay/menu.rs b/widget/src/overlay/menu.rs index d93a3b56..0acc6f79 100644 --- a/native/src/overlay/menu.rs +++ b/widget/src/overlay/menu.rs @@ -1,25 +1,25 @@ //! Build and show dropdown menus. -use crate::alignment; -use crate::event::{self, Event}; -use crate::layout; -use crate::mouse; -use crate::overlay; -use crate::renderer; -use crate::text::{self, Text}; -use crate::touch; -use crate::widget::container::{self, Container}; -use crate::widget::scrollable::{self, Scrollable}; -use crate::widget::Tree; -use crate::{ - Clipboard, Color, Element, Layout, Length, Padding, Pixels, Point, - Rectangle, Shell, Size, Vector, Widget, +use crate::container::{self, Container}; +use crate::core::alignment; +use crate::core::event::{self, Event}; +use crate::core::layout::{self, Layout}; +use crate::core::mouse; +use crate::core::overlay; +use crate::core::renderer; +use crate::core::text::{self, Text}; +use crate::core::touch; +use crate::core::widget::Tree; +use crate::core::{ + Clipboard, Color, Length, Padding, Pixels, Point, Rectangle, Size, Vector, }; +use crate::core::{Element, Shell, Widget}; +use crate::scrollable::{self, Scrollable}; pub use iced_style::menu::{Appearance, StyleSheet}; /// A list of selectable options. #[allow(missing_debug_implementations)] -pub struct Menu<'a, T, Renderer> +pub struct Menu<'a, T, Renderer = crate::Renderer> where Renderer: text::Renderer, Renderer::Theme: StyleSheet, @@ -31,7 +31,9 @@ where width: f32, padding: Padding, text_size: Option<f32>, - font: Renderer::Font, + text_line_height: text::LineHeight, + text_shaping: text::Shaping, + font: Option<Renderer::Font>, style: <Renderer::Theme as StyleSheet>::Style, } @@ -58,7 +60,9 @@ where width: 0.0, padding: Padding::ZERO, text_size: None, - font: Default::default(), + text_line_height: text::LineHeight::default(), + text_shaping: text::Shaping::Basic, + font: None, style: Default::default(), } } @@ -81,9 +85,24 @@ where self } + /// Sets the text [`LineHeight`] of the [`Menu`]. + pub fn text_line_height( + mut self, + line_height: impl Into<text::LineHeight>, + ) -> Self { + self.text_line_height = line_height.into(); + self + } + + /// Sets the [`text::Shaping`] strategy of the [`Menu`]. + pub fn text_shaping(mut self, shaping: text::Shaping) -> Self { + self.text_shaping = shaping; + self + } + /// Sets the font of the [`Menu`]. - pub fn font(mut self, font: Renderer::Font) -> Self { - self.font = font; + pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self { + self.font = Some(font.into()); self } @@ -137,7 +156,7 @@ impl Default for State { struct Overlay<'a, Message, Renderer> where - Renderer: crate::Renderer, + Renderer: crate::core::Renderer, Renderer::Theme: StyleSheet + container::StyleSheet, { state: &'a mut Tree, @@ -168,6 +187,8 @@ where padding, font, text_size, + text_line_height, + text_shaping, style, } = menu; @@ -177,6 +198,8 @@ where last_selection, font, text_size, + text_line_height, + text_shaping, padding, style: style.clone(), })); @@ -193,7 +216,7 @@ where } } -impl<'a, Message, Renderer> crate::Overlay<Message, Renderer> +impl<'a, Message, Renderer> crate::core::Overlay<Message, Renderer> for Overlay<'a, Message, Renderer> where Renderer: text::Renderer, @@ -311,7 +334,9 @@ where last_selection: &'a mut Option<T>, padding: Padding, text_size: Option<f32>, - font: Renderer::Font, + text_line_height: text::LineHeight, + text_shaping: text::Shaping, + font: Option<Renderer::Font>, style: <Renderer::Theme as StyleSheet>::Style, } @@ -341,10 +366,13 @@ where let text_size = self.text_size.unwrap_or_else(|| renderer.default_size()); + let text_line_height = + self.text_line_height.to_absolute(Pixels(text_size)); + let size = { let intrinsic = Size::new( 0.0, - (text_size + self.padding.vertical()) + (f32::from(text_line_height) + self.padding.vertical()) * self.options.len() as f32, ); @@ -384,9 +412,12 @@ where .text_size .unwrap_or_else(|| renderer.default_size()); + let option_height = f32::from( + self.text_line_height.to_absolute(Pixels(text_size)), + ) + self.padding.vertical(); + *self.hovered_option = Some( - ((cursor_position.y - bounds.y) - / (text_size + self.padding.vertical())) + ((cursor_position.y - bounds.y) / option_height) as usize, ); } @@ -399,9 +430,12 @@ where .text_size .unwrap_or_else(|| renderer.default_size()); + let option_height = f32::from( + self.text_line_height.to_absolute(Pixels(text_size)), + ) + self.padding.vertical(); + *self.hovered_option = Some( - ((cursor_position.y - bounds.y) - / (text_size + self.padding.vertical())) + ((cursor_position.y - bounds.y) / option_height) as usize, ); @@ -450,12 +484,13 @@ where let text_size = self.text_size.unwrap_or_else(|| renderer.default_size()); - let option_height = (text_size + self.padding.vertical()) as usize; + let option_height = + f32::from(self.text_line_height.to_absolute(Pixels(text_size))) + + self.padding.vertical(); let offset = viewport.y - bounds.y; - let start = (offset / option_height as f32) as usize; - let end = - ((offset + viewport.height) / option_height as f32).ceil() as usize; + let start = (offset / option_height) as usize; + let end = ((offset + viewport.height) / option_height).ceil() as usize; let visible_options = &self.options[start..end.min(self.options.len())]; @@ -465,9 +500,9 @@ where let bounds = Rectangle { x: bounds.x, - y: bounds.y + (option_height * i) as f32, + y: bounds.y + (option_height * i as f32), width: bounds.width, - height: text_size + self.padding.vertical(), + height: option_height, }; if is_selected { @@ -495,7 +530,8 @@ where ..bounds }, size: text_size, - font: self.font.clone(), + line_height: self.text_line_height, + font: self.font.unwrap_or_else(|| renderer.default_font()), color: if is_selected { appearance.selected_text_color } else { @@ -503,6 +539,7 @@ where }, horizontal_alignment: alignment::Horizontal::Left, vertical_alignment: alignment::Vertical::Center, + shaping: self.text_shaping, }); } } diff --git a/native/src/widget/pane_grid.rs b/widget/src/pane_grid.rs index 06ece7f4..67145e8e 100644 --- a/native/src/widget/pane_grid.rs +++ b/widget/src/pane_grid.rs @@ -30,18 +30,18 @@ pub use split::Split; pub use state::State; pub use title_bar::TitleBar; -pub use iced_style::pane_grid::{Line, StyleSheet}; - -use crate::event::{self, Event}; -use crate::layout; -use crate::mouse; -use crate::overlay::{self, Group}; -use crate::renderer; -use crate::touch; -use crate::widget; -use crate::widget::container; -use crate::widget::tree::{self, Tree}; -use crate::{ +pub use crate::style::pane_grid::{Line, StyleSheet}; + +use crate::container; +use crate::core::event::{self, Event}; +use crate::core::layout; +use crate::core::mouse; +use crate::core::overlay::{self, Group}; +use crate::core::renderer; +use crate::core::touch; +use crate::core::widget; +use crate::core::widget::tree::{self, Tree}; +use crate::core::{ Clipboard, Color, Element, Layout, Length, Pixels, Point, Rectangle, Shell, Size, Vector, Widget, }; @@ -67,11 +67,11 @@ use crate::{ /// /// ## Example /// -/// ``` -/// # use iced_native::widget::{pane_grid, text}; +/// ```no_run +/// # use iced_widget::{pane_grid, text}; /// # /// # type PaneGrid<'a, Message> = -/// # iced_native::widget::PaneGrid<'a, Message, iced_native::renderer::Null>; +/// # iced_widget::PaneGrid<'a, Message, iced_widget::renderer::Renderer<iced_widget::style::Theme>>; /// # /// enum PaneState { /// SomePane, @@ -96,9 +96,9 @@ use crate::{ /// .on_resize(10, Message::PaneResized); /// ``` #[allow(missing_debug_implementations)] -pub struct PaneGrid<'a, Message, Renderer> +pub struct PaneGrid<'a, Message, Renderer = crate::Renderer> where - Renderer: crate::Renderer, + Renderer: crate::core::Renderer, Renderer::Theme: StyleSheet + container::StyleSheet, { contents: Contents<'a, Content<'a, Message, Renderer>>, @@ -113,7 +113,7 @@ where impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> where - Renderer: crate::Renderer, + Renderer: crate::core::Renderer, Renderer::Theme: StyleSheet + container::StyleSheet, { /// Creates a [`PaneGrid`] with the given [`State`] and view function. @@ -232,7 +232,7 @@ where impl<'a, Message, Renderer> Widget<Message, Renderer> for PaneGrid<'a, Message, Renderer> where - Renderer: crate::Renderer, + Renderer: crate::core::Renderer, Renderer::Theme: StyleSheet + container::StyleSheet, { fn tag(&self) -> tree::Tag { @@ -468,7 +468,7 @@ impl<'a, Message, Renderer> From<PaneGrid<'a, Message, Renderer>> for Element<'a, Message, Renderer> where Message: 'a, - Renderer: 'a + crate::Renderer, + Renderer: 'a + crate::core::Renderer, Renderer::Theme: StyleSheet + container::StyleSheet, { fn from( @@ -755,7 +755,7 @@ pub fn draw<Renderer, T>( &Rectangle, ), ) where - Renderer: crate::Renderer, + Renderer: crate::core::Renderer, Renderer::Theme: StyleSheet, { let picked_pane = action.picked_pane(); diff --git a/native/src/widget/pane_grid/axis.rs b/widget/src/pane_grid/axis.rs index 02bde064..a3049230 100644 --- a/native/src/widget/pane_grid/axis.rs +++ b/widget/src/pane_grid/axis.rs @@ -1,4 +1,4 @@ -use crate::Rectangle; +use crate::core::Rectangle; /// A fixed reference line for the measurement of coordinates. #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] diff --git a/native/src/widget/pane_grid/configuration.rs b/widget/src/pane_grid/configuration.rs index 7d68fb46..ddbc3bc2 100644 --- a/native/src/widget/pane_grid/configuration.rs +++ b/widget/src/pane_grid/configuration.rs @@ -1,4 +1,4 @@ -use crate::widget::pane_grid::Axis; +use crate::pane_grid::Axis; /// The arrangement of a [`PaneGrid`]. /// diff --git a/native/src/widget/pane_grid/content.rs b/widget/src/pane_grid/content.rs index c9b0df07..035ef05b 100644 --- a/native/src/widget/pane_grid/content.rs +++ b/widget/src/pane_grid/content.rs @@ -1,20 +1,20 @@ -use crate::event::{self, Event}; -use crate::layout; -use crate::mouse; -use crate::overlay; -use crate::renderer; -use crate::widget::container; -use crate::widget::pane_grid::{Draggable, TitleBar}; -use crate::widget::{self, Tree}; -use crate::{Clipboard, Element, Layout, Point, Rectangle, Shell, Size}; +use crate::container; +use crate::core::event::{self, Event}; +use crate::core::layout; +use crate::core::mouse; +use crate::core::overlay; +use crate::core::renderer; +use crate::core::widget::{self, Tree}; +use crate::core::{Clipboard, Element, Layout, Point, Rectangle, Shell, Size}; +use crate::pane_grid::{Draggable, TitleBar}; /// The content of a [`Pane`]. /// /// [`Pane`]: crate::widget::pane_grid::Pane #[allow(missing_debug_implementations)] -pub struct Content<'a, Message, Renderer> +pub struct Content<'a, Message, Renderer = crate::Renderer> where - Renderer: crate::Renderer, + Renderer: crate::core::Renderer, Renderer::Theme: container::StyleSheet, { title_bar: Option<TitleBar<'a, Message, Renderer>>, @@ -24,7 +24,7 @@ where impl<'a, Message, Renderer> Content<'a, Message, Renderer> where - Renderer: crate::Renderer, + Renderer: crate::core::Renderer, Renderer::Theme: container::StyleSheet, { /// Creates a new [`Content`] with the provided body. @@ -57,7 +57,7 @@ where impl<'a, Message, Renderer> Content<'a, Message, Renderer> where - Renderer: crate::Renderer, + Renderer: crate::core::Renderer, Renderer::Theme: container::StyleSheet, { pub(super) fn state(&self) -> Tree { @@ -342,7 +342,7 @@ where impl<'a, Message, Renderer> Draggable for &Content<'a, Message, Renderer> where - Renderer: crate::Renderer, + Renderer: crate::core::Renderer, Renderer::Theme: container::StyleSheet, { fn can_be_dragged_at( @@ -364,7 +364,7 @@ where impl<'a, T, Message, Renderer> From<T> for Content<'a, Message, Renderer> where T: Into<Element<'a, Message, Renderer>>, - Renderer: crate::Renderer, + Renderer: crate::core::Renderer, Renderer::Theme: container::StyleSheet, { fn from(element: T) -> Self { diff --git a/native/src/widget/pane_grid/direction.rs b/widget/src/pane_grid/direction.rs index b31a8737..b31a8737 100644 --- a/native/src/widget/pane_grid/direction.rs +++ b/widget/src/pane_grid/direction.rs diff --git a/native/src/widget/pane_grid/draggable.rs b/widget/src/pane_grid/draggable.rs index 6044871d..a9274dad 100644 --- a/native/src/widget/pane_grid/draggable.rs +++ b/widget/src/pane_grid/draggable.rs @@ -1,4 +1,4 @@ -use crate::{Layout, Point}; +use crate::core::{Layout, Point}; /// A pane that can be dragged. pub trait Draggable { diff --git a/native/src/widget/pane_grid/node.rs b/widget/src/pane_grid/node.rs index cc304b96..3976acd8 100644 --- a/native/src/widget/pane_grid/node.rs +++ b/widget/src/pane_grid/node.rs @@ -1,5 +1,5 @@ -use crate::widget::pane_grid::{Axis, Pane, Split}; -use crate::{Rectangle, Size}; +use crate::core::{Rectangle, Size}; +use crate::pane_grid::{Axis, Pane, Split}; use std::collections::BTreeMap; diff --git a/native/src/widget/pane_grid/pane.rs b/widget/src/pane_grid/pane.rs index d6fbab83..d6fbab83 100644 --- a/native/src/widget/pane_grid/pane.rs +++ b/widget/src/pane_grid/pane.rs diff --git a/native/src/widget/pane_grid/split.rs b/widget/src/pane_grid/split.rs index 8132272a..8132272a 100644 --- a/native/src/widget/pane_grid/split.rs +++ b/widget/src/pane_grid/split.rs diff --git a/native/src/widget/pane_grid/state.rs b/widget/src/pane_grid/state.rs index c4ae0a0e..a6e2ec7f 100644 --- a/native/src/widget/pane_grid/state.rs +++ b/widget/src/pane_grid/state.rs @@ -1,10 +1,8 @@ //! The state of a [`PaneGrid`]. //! //! [`PaneGrid`]: crate::widget::PaneGrid -use crate::widget::pane_grid::{ - Axis, Configuration, Direction, Node, Pane, Split, -}; -use crate::{Point, Size}; +use crate::core::{Point, Size}; +use crate::pane_grid::{Axis, Configuration, Direction, Node, Pane, Split}; use std::collections::HashMap; diff --git a/native/src/widget/pane_grid/title_bar.rs b/widget/src/pane_grid/title_bar.rs index 107078ef..2129937b 100644 --- a/native/src/widget/pane_grid/title_bar.rs +++ b/widget/src/pane_grid/title_bar.rs @@ -1,11 +1,11 @@ -use crate::event::{self, Event}; -use crate::layout; -use crate::mouse; -use crate::overlay; -use crate::renderer; -use crate::widget::container; -use crate::widget::{self, Tree}; -use crate::{ +use crate::container; +use crate::core::event::{self, Event}; +use crate::core::layout; +use crate::core::mouse; +use crate::core::overlay; +use crate::core::renderer; +use crate::core::widget::{self, Tree}; +use crate::core::{ Clipboard, Element, Layout, Padding, Point, Rectangle, Shell, Size, }; @@ -13,9 +13,9 @@ use crate::{ /// /// [`Pane`]: crate::widget::pane_grid::Pane #[allow(missing_debug_implementations)] -pub struct TitleBar<'a, Message, Renderer> +pub struct TitleBar<'a, Message, Renderer = crate::Renderer> where - Renderer: crate::Renderer, + Renderer: crate::core::Renderer, Renderer::Theme: container::StyleSheet, { content: Element<'a, Message, Renderer>, @@ -27,7 +27,7 @@ where impl<'a, Message, Renderer> TitleBar<'a, Message, Renderer> where - Renderer: crate::Renderer, + Renderer: crate::core::Renderer, Renderer::Theme: container::StyleSheet, { /// Creates a new [`TitleBar`] with the given content. @@ -84,7 +84,7 @@ where impl<'a, Message, Renderer> TitleBar<'a, Message, Renderer> where - Renderer: crate::Renderer, + Renderer: crate::core::Renderer, Renderer::Theme: container::StyleSheet, { pub(super) fn state(&self) -> Tree { diff --git a/native/src/widget/pick_list.rs b/widget/src/pick_list.rs index 17528db4..8c445dda 100644 --- a/native/src/widget/pick_list.rs +++ b/widget/src/pick_list.rs @@ -1,28 +1,29 @@ //! Display a dropdown list of selectable values. -use crate::alignment; -use crate::event::{self, Event}; -use crate::keyboard; -use crate::layout; -use crate::mouse; -use crate::overlay; -use crate::overlay::menu::{self, Menu}; -use crate::renderer; -use crate::text::{self, Text}; -use crate::touch; -use crate::widget::container; -use crate::widget::scrollable; -use crate::widget::tree::{self, Tree}; -use crate::{ +use crate::container; +use crate::core::alignment; +use crate::core::event::{self, Event}; +use crate::core::keyboard; +use crate::core::layout; +use crate::core::mouse; +use crate::core::overlay; +use crate::core::renderer; +use crate::core::text::{self, Text}; +use crate::core::touch; +use crate::core::widget::tree::{self, Tree}; +use crate::core::{ Clipboard, Element, Layout, Length, Padding, Pixels, Point, Rectangle, Shell, Size, Widget, }; +use crate::overlay::menu::{self, Menu}; +use crate::scrollable; + use std::borrow::Cow; -pub use iced_style::pick_list::{Appearance, StyleSheet}; +pub use crate::style::pick_list::{Appearance, StyleSheet}; /// A widget for selecting a single value from a list of options. #[allow(missing_debug_implementations)] -pub struct PickList<'a, T, Message, Renderer> +pub struct PickList<'a, T, Message, Renderer = crate::Renderer> where [T]: ToOwned<Owned = Vec<T>>, Renderer: text::Renderer, @@ -35,7 +36,9 @@ where width: Length, padding: Padding, text_size: Option<f32>, - font: Renderer::Font, + text_line_height: text::LineHeight, + text_shaping: text::Shaping, + font: Option<Renderer::Font>, handle: Handle<Renderer::Font>, style: <Renderer::Theme as StyleSheet>::Style, } @@ -70,7 +73,9 @@ where width: Length::Shrink, padding: Self::DEFAULT_PADDING, text_size: None, - font: Default::default(), + text_line_height: text::LineHeight::default(), + text_shaping: text::Shaping::Basic, + font: None, handle: Default::default(), style: Default::default(), } @@ -100,9 +105,24 @@ where self } + /// Sets the text [`LineHeight`] of the [`PickList`]. + pub fn text_line_height( + mut self, + line_height: impl Into<text::LineHeight>, + ) -> Self { + self.text_line_height = line_height.into(); + self + } + + /// Sets the [`text::Shaping`] strategy of the [`PickList`]. + pub fn text_shaping(mut self, shaping: text::Shaping) -> Self { + self.text_shaping = shaping; + self + } + /// Sets the font of the [`PickList`]. - pub fn font(mut self, font: Renderer::Font) -> Self { - self.font = font; + pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self { + self.font = Some(font.into()); self } @@ -163,7 +183,9 @@ where self.width, self.padding, self.text_size, - &self.font, + self.text_line_height, + self.text_shaping, + self.font, self.placeholder.as_deref(), &self.options, ) @@ -212,6 +234,7 @@ where cursor_position: Point, _viewport: &Rectangle, ) { + let font = self.font.unwrap_or_else(|| renderer.default_font()); draw( renderer, theme, @@ -219,7 +242,9 @@ where cursor_position, self.padding, self.text_size, - &self.font, + self.text_line_height, + self.text_shaping, + font, self.placeholder.as_deref(), self.selected.as_ref(), &self.handle, @@ -232,7 +257,7 @@ where &'b mut self, tree: &'b mut Tree, layout: Layout<'_>, - _renderer: &Renderer, + renderer: &Renderer, ) -> Option<overlay::Element<'b, Message, Renderer>> { let state = tree.state.downcast_mut::<State<T>>(); @@ -241,7 +266,8 @@ where state, self.padding, self.text_size, - self.font.clone(), + self.text_shaping, + self.font.unwrap_or_else(|| renderer.default_font()), &self.options, self.style.clone(), ) @@ -334,6 +360,10 @@ pub struct Icon<Font> { pub code_point: char, /// Font size of the content. pub size: Option<f32>, + /// Line height of the content. + pub line_height: text::LineHeight, + /// The shaping strategy of the icon. + pub shaping: text::Shaping, } /// Computes the layout of a [`PickList`]. @@ -343,7 +373,9 @@ pub fn layout<Renderer, T>( width: Length, padding: Padding, text_size: Option<f32>, - font: &Renderer::Font, + text_line_height: text::LineHeight, + text_shaping: text::Shaping, + font: Option<Renderer::Font>, placeholder: Option<&str>, options: &[T], ) -> layout::Node @@ -359,11 +391,11 @@ where let max_width = match width { Length::Shrink => { let measure = |label: &str| -> f32 { - let (width, _) = renderer.measure( + let width = renderer.measure_width( label, text_size, - font.clone(), - Size::new(f32::INFINITY, f32::INFINITY), + font.unwrap_or_else(|| renderer.default_font()), + text_shaping, ); width.round() @@ -383,8 +415,10 @@ where }; let size = { - let intrinsic = - Size::new(max_width + text_size + padding.left, text_size); + let intrinsic = Size::new( + max_width + text_size + padding.left, + f32::from(text_line_height.to_absolute(Pixels(text_size))), + ); limits.resolve(intrinsic).pad(padding) }; @@ -513,6 +547,7 @@ pub fn overlay<'a, T, Message, Renderer>( state: &'a mut State<T>, padding: Padding, text_size: Option<f32>, + text_shaping: text::Shaping, font: Renderer::Font, options: &'a [T], style: <Renderer::Theme as StyleSheet>::Style, @@ -540,6 +575,7 @@ where .width(bounds.width) .padding(padding) .font(font) + .text_shaping(text_shaping) .style(style); if let Some(text_size) = text_size { @@ -560,7 +596,9 @@ pub fn draw<'a, T, Renderer>( cursor_position: Point, padding: Padding, text_size: Option<f32>, - font: &Renderer::Font, + text_line_height: text::LineHeight, + text_shaping: text::Shaping, + font: Renderer::Font, placeholder: Option<&str>, selected: Option<&T>, handle: &Handle<Renderer::Font>, @@ -592,40 +630,60 @@ pub fn draw<'a, T, Renderer>( ); let handle = match handle { - Handle::Arrow { size } => { - Some((Renderer::ICON_FONT, Renderer::ARROW_DOWN_ICON, *size)) - } + Handle::Arrow { size } => Some(( + Renderer::ICON_FONT, + Renderer::ARROW_DOWN_ICON, + *size, + text::LineHeight::default(), + text::Shaping::Basic, + )), Handle::Static(Icon { font, code_point, size, - }) => Some((font.clone(), *code_point, *size)), + line_height, + shaping, + }) => Some((*font, *code_point, *size, *line_height, *shaping)), Handle::Dynamic { open, closed } => { if state().is_open { - Some((open.font.clone(), open.code_point, open.size)) + Some(( + open.font, + open.code_point, + open.size, + open.line_height, + open.shaping, + )) } else { - Some((closed.font.clone(), closed.code_point, closed.size)) + Some(( + closed.font, + closed.code_point, + closed.size, + closed.line_height, + closed.shaping, + )) } } Handle::None => None, }; - if let Some((font, code_point, size)) = handle { + if let Some((font, code_point, size, line_height, shaping)) = handle { let size = size.unwrap_or_else(|| renderer.default_size()); renderer.fill_text(Text { content: &code_point.to_string(), size, + line_height, font, color: style.handle_color, bounds: Rectangle { x: bounds.x + bounds.width - padding.horizontal(), - y: bounds.center_y() - size / 2.0, - height: size, + y: bounds.center_y(), + height: f32::from(line_height.to_absolute(Pixels(size))), ..bounds }, horizontal_alignment: alignment::Horizontal::Right, - vertical_alignment: alignment::Vertical::Top, + vertical_alignment: alignment::Vertical::Center, + shaping, }); } @@ -637,7 +695,8 @@ pub fn draw<'a, T, Renderer>( renderer.fill_text(Text { content: label, size: text_size, - font: font.clone(), + line_height: text_line_height, + font, color: if is_selected { style.text_color } else { @@ -645,12 +704,15 @@ pub fn draw<'a, T, Renderer>( }, bounds: Rectangle { x: bounds.x + padding.left, - y: bounds.center_y() - text_size / 2.0, + y: bounds.center_y(), width: bounds.width - padding.horizontal(), - height: text_size, + height: f32::from( + text_line_height.to_absolute(Pixels(text_size)), + ), }, horizontal_alignment: alignment::Horizontal::Left, - vertical_alignment: alignment::Vertical::Top, + vertical_alignment: alignment::Vertical::Center, + shaping: text_shaping, }); } } diff --git a/native/src/widget/progress_bar.rs b/widget/src/progress_bar.rs index dd46fa76..ef0d87d5 100644 --- a/native/src/widget/progress_bar.rs +++ b/widget/src/progress_bar.rs @@ -1,8 +1,10 @@ //! Provide progress feedback to your users. -use crate::layout; -use crate::renderer; -use crate::widget::Tree; -use crate::{Color, Element, Layout, Length, Point, Rectangle, Size, Widget}; +use crate::core::layout; +use crate::core::renderer; +use crate::core::widget::Tree; +use crate::core::{ + Color, Element, Layout, Length, Point, Rectangle, Size, Widget, +}; use std::ops::RangeInclusive; @@ -11,8 +13,10 @@ pub use iced_style::progress_bar::{Appearance, StyleSheet}; /// A bar that displays progress. /// /// # Example -/// ``` -/// # type ProgressBar = iced_native::widget::ProgressBar<iced_native::renderer::Null>; +/// ```no_run +/// # type ProgressBar = +/// # iced_widget::ProgressBar<iced_widget::renderer::Renderer<iced_widget::style::Theme>>; +/// # /// let value = 50.0; /// /// ProgressBar::new(0.0..=100.0, value); @@ -20,9 +24,9 @@ pub use iced_style::progress_bar::{Appearance, StyleSheet}; /// ///  #[allow(missing_debug_implementations)] -pub struct ProgressBar<Renderer> +pub struct ProgressBar<Renderer = crate::Renderer> where - Renderer: crate::Renderer, + Renderer: crate::core::Renderer, Renderer::Theme: StyleSheet, { range: RangeInclusive<f32>, @@ -34,7 +38,7 @@ where impl<Renderer> ProgressBar<Renderer> where - Renderer: crate::Renderer, + Renderer: crate::core::Renderer, Renderer::Theme: StyleSheet, { /// The default height of a [`ProgressBar`]. @@ -79,7 +83,7 @@ where impl<Message, Renderer> Widget<Message, Renderer> for ProgressBar<Renderer> where - Renderer: crate::Renderer, + Renderer: crate::core::Renderer, Renderer::Theme: StyleSheet, { fn width(&self) -> Length { @@ -157,7 +161,7 @@ impl<'a, Message, Renderer> From<ProgressBar<Renderer>> for Element<'a, Message, Renderer> where Message: 'a, - Renderer: 'a + crate::Renderer, + Renderer: 'a + crate::core::Renderer, Renderer::Theme: StyleSheet, { fn from( diff --git a/graphics/src/widget/qr_code.rs b/widget/src/qr_code.rs index 12ce5b1f..7709125f 100644 --- a/graphics/src/widget/qr_code.rs +++ b/widget/src/qr_code.rs @@ -1,13 +1,12 @@ //! Encode and display information in a QR code. -use crate::renderer::{self, Renderer}; -use crate::widget::canvas; -use crate::Backend; - -use iced_native::layout; -use iced_native::widget::Tree; -use iced_native::{ +use crate::canvas; +use crate::core::layout; +use crate::core::renderer::{self, Renderer as _}; +use crate::core::widget::Tree; +use crate::core::{ Color, Element, Layout, Length, Point, Rectangle, Size, Vector, Widget, }; +use crate::Renderer; use thiserror::Error; const DEFAULT_CELL_SIZE: u16 = 4; @@ -48,10 +47,7 @@ impl<'a> QRCode<'a> { } } -impl<'a, Message, B, T> Widget<Message, Renderer<B, T>> for QRCode<'a> -where - B: Backend, -{ +impl<'a, Message, Theme> Widget<Message, Renderer<Theme>> for QRCode<'a> { fn width(&self) -> Length { Length::Shrink } @@ -62,7 +58,7 @@ where fn layout( &self, - _renderer: &Renderer<B, T>, + _renderer: &Renderer<Theme>, _limits: &layout::Limits, ) -> layout::Node { let side_length = (self.state.width + 2 * QUIET_ZONE) as f32 @@ -74,63 +70,63 @@ where fn draw( &self, _state: &Tree, - renderer: &mut Renderer<B, T>, - _theme: &T, + renderer: &mut Renderer<Theme>, + _theme: &Theme, _style: &renderer::Style, layout: Layout<'_>, _cursor_position: Point, _viewport: &Rectangle, ) { - use iced_native::Renderer as _; - let bounds = layout.bounds(); let side_length = self.state.width + 2 * QUIET_ZONE; // Reuse cache if possible - let geometry = self.state.cache.draw(bounds.size(), |frame| { - // Scale units to cell size - frame.scale(f32::from(self.cell_size)); - - // Draw background - frame.fill_rectangle( - Point::ORIGIN, - Size::new(side_length as f32, side_length as f32), - self.light, - ); - - // Avoid drawing on the quiet zone - frame.translate(Vector::new(QUIET_ZONE as f32, QUIET_ZONE as f32)); - - // Draw contents - self.state - .contents - .iter() - .enumerate() - .filter(|(_, value)| **value == qrcode::Color::Dark) - .for_each(|(index, _)| { - let row = index / self.state.width; - let column = index % self.state.width; - - frame.fill_rectangle( - Point::new(column as f32, row as f32), - Size::UNIT, - self.dark, - ); - }); - }); + let geometry = + self.state.cache.draw(renderer, bounds.size(), |frame| { + // Scale units to cell size + frame.scale(f32::from(self.cell_size)); + + // Draw background + frame.fill_rectangle( + Point::ORIGIN, + Size::new(side_length as f32, side_length as f32), + self.light, + ); + + // Avoid drawing on the quiet zone + frame.translate(Vector::new( + QUIET_ZONE as f32, + QUIET_ZONE as f32, + )); + + // Draw contents + self.state + .contents + .iter() + .enumerate() + .filter(|(_, value)| **value == qrcode::Color::Dark) + .for_each(|(index, _)| { + let row = index / self.state.width; + let column = index % self.state.width; + + frame.fill_rectangle( + Point::new(column as f32, row as f32), + Size::UNIT, + self.dark, + ); + }); + }); let translation = Vector::new(bounds.x, bounds.y); renderer.with_translation(translation, |renderer| { - renderer.draw_primitive(geometry.into_primitive()); + renderer.draw_primitive(geometry.0); }); } } -impl<'a, Message, B, T> From<QRCode<'a>> - for Element<'a, Message, Renderer<B, T>> -where - B: Backend, +impl<'a, Message, Theme> From<QRCode<'a>> + for Element<'a, Message, Renderer<Theme>> { fn from(qr_code: QRCode<'a>) -> Self { Self::new(qr_code) diff --git a/native/src/widget/radio.rs b/widget/src/radio.rs index 3ca041bf..9dad1e22 100644 --- a/native/src/widget/radio.rs +++ b/widget/src/radio.rs @@ -1,27 +1,28 @@ //! Create choices using radio buttons. -use crate::alignment; -use crate::event::{self, Event}; -use crate::layout; -use crate::mouse; -use crate::renderer; -use crate::text; -use crate::touch; -use crate::widget::{self, Row, Text, Tree}; -use crate::{ +use crate::core::alignment; +use crate::core::event::{self, Event}; +use crate::core::layout; +use crate::core::mouse; +use crate::core::renderer; +use crate::core::text; +use crate::core::touch; +use crate::core::widget::Tree; +use crate::core::{ Alignment, Clipboard, Color, Element, Layout, Length, Pixels, Point, Rectangle, Shell, Widget, }; +use crate::{Row, Text}; pub use iced_style::radio::{Appearance, StyleSheet}; /// A circular button representing a choice. /// /// # Example -/// ``` +/// ```no_run /// # type Radio<Message> = -/// # iced_native::widget::Radio<Message, iced_native::renderer::Null>; +/// # iced_widget::Radio<Message, iced_widget::renderer::Renderer<iced_widget::style::Theme>>; /// # -/// # use iced_native::column; +/// # use iced_widget::column; /// #[derive(Debug, Clone, Copy, PartialEq, Eq)] /// pub enum Choice { /// A, @@ -68,7 +69,7 @@ pub use iced_style::radio::{Appearance, StyleSheet}; /// let content = column![a, b, c, all]; /// ``` #[allow(missing_debug_implementations)] -pub struct Radio<Message, Renderer> +pub struct Radio<Message, Renderer = crate::Renderer> where Renderer: text::Renderer, Renderer::Theme: StyleSheet, @@ -80,7 +81,9 @@ where size: f32, spacing: f32, text_size: Option<f32>, - font: Renderer::Font, + text_line_height: text::LineHeight, + text_shaping: text::Shaping, + font: Option<Renderer::Font>, style: <Renderer::Theme as StyleSheet>::Style, } @@ -122,7 +125,9 @@ where size: Self::DEFAULT_SIZE, spacing: Self::DEFAULT_SPACING, //15 text_size: None, - font: Default::default(), + text_line_height: text::LineHeight::default(), + text_shaping: text::Shaping::Basic, + font: None, style: Default::default(), } } @@ -151,9 +156,24 @@ where self } + /// Sets the text [`LineHeight`] of the [`Radio`] button. + pub fn text_line_height( + mut self, + line_height: impl Into<text::LineHeight>, + ) -> Self { + self.text_line_height = line_height.into(); + self + } + + /// Sets the [`text::Shaping`] strategy of the [`Radio`] button. + pub fn text_shaping(mut self, shaping: text::Shaping) -> Self { + self.text_shaping = shaping; + self + } + /// Sets the text font of the [`Radio`] button. - pub fn font(mut self, font: Renderer::Font) -> Self { - self.font = font; + pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self { + self.font = Some(font.into()); self } @@ -171,7 +191,7 @@ impl<Message, Renderer> Widget<Message, Renderer> for Radio<Message, Renderer> where Message: Clone, Renderer: text::Renderer, - Renderer::Theme: StyleSheet + widget::text::StyleSheet, + Renderer::Theme: StyleSheet + crate::text::StyleSheet, { fn width(&self) -> Length { self.width @@ -191,9 +211,16 @@ where .spacing(self.spacing) .align_items(Alignment::Center) .push(Row::new().width(self.size).height(self.size)) - .push(Text::new(&self.label).width(self.width).size( - self.text_size.unwrap_or_else(|| renderer.default_size()), - )) + .push( + Text::new(&self.label) + .width(self.width) + .size( + self.text_size + .unwrap_or_else(|| renderer.default_size()), + ) + .line_height(self.text_line_height) + .shaping(self.text_shaping), + ) .layout(renderer, limits) } @@ -296,18 +323,20 @@ where { let label_layout = children.next().unwrap(); - widget::text::draw( + crate::text::draw( renderer, style, label_layout, &self.label, self.text_size, - self.font.clone(), - widget::text::Appearance { + self.text_line_height, + self.font, + crate::text::Appearance { color: custom_style.text_color, }, alignment::Horizontal::Left, alignment::Vertical::Center, + self.text_shaping, ); } } @@ -318,7 +347,7 @@ impl<'a, Message, Renderer> From<Radio<Message, Renderer>> where Message: 'a + Clone, Renderer: 'a + text::Renderer, - Renderer::Theme: StyleSheet + widget::text::StyleSheet, + Renderer::Theme: StyleSheet + crate::text::StyleSheet, { fn from(radio: Radio<Message, Renderer>) -> Element<'a, Message, Renderer> { Element::new(radio) diff --git a/native/src/widget/row.rs b/widget/src/row.rs index 286c1c2d..3ce363f8 100644 --- a/native/src/widget/row.rs +++ b/widget/src/row.rs @@ -1,18 +1,18 @@ //! Distribute content horizontally. -use crate::event::{self, Event}; -use crate::layout::{self, Layout}; -use crate::mouse; -use crate::overlay; -use crate::renderer; -use crate::widget::{Operation, Tree}; -use crate::{ +use crate::core::event::{self, Event}; +use crate::core::layout::{self, Layout}; +use crate::core::mouse; +use crate::core::overlay; +use crate::core::renderer; +use crate::core::widget::{Operation, Tree}; +use crate::core::{ Alignment, Clipboard, Element, Length, Padding, Pixels, Point, Rectangle, Shell, Widget, }; /// A container that distributes its contents horizontally. #[allow(missing_debug_implementations)] -pub struct Row<'a, Message, Renderer> { +pub struct Row<'a, Message, Renderer = crate::Renderer> { spacing: f32, padding: Padding, width: Length, @@ -94,7 +94,7 @@ impl<'a, Message, Renderer> Default for Row<'a, Message, Renderer> { impl<'a, Message, Renderer> Widget<Message, Renderer> for Row<'a, Message, Renderer> where - Renderer: crate::Renderer, + Renderer: crate::core::Renderer, { fn children(&self) -> Vec<Tree> { self.children.iter().map(Tree::new).collect() @@ -245,7 +245,7 @@ impl<'a, Message, Renderer> From<Row<'a, Message, Renderer>> for Element<'a, Message, Renderer> where Message: 'a, - Renderer: crate::Renderer + 'a, + Renderer: crate::core::Renderer + 'a, { fn from(row: Row<'a, Message, Renderer>) -> Self { Self::new(row) diff --git a/native/src/widget/rule.rs b/widget/src/rule.rs index 1ab6a0d3..3749d7ce 100644 --- a/native/src/widget/rule.rs +++ b/widget/src/rule.rs @@ -1,18 +1,18 @@ //! Display a horizontal or vertical rule for dividing content. -use crate::layout; -use crate::renderer; -use crate::widget::Tree; -use crate::{ +use crate::core::layout; +use crate::core::renderer; +use crate::core::widget::Tree; +use crate::core::{ Color, Element, Layout, Length, Pixels, Point, Rectangle, Size, Widget, }; -pub use iced_style::rule::{Appearance, FillMode, StyleSheet}; +pub use crate::style::rule::{Appearance, FillMode, StyleSheet}; /// Display a horizontal or vertical rule for dividing content. #[allow(missing_debug_implementations)] -pub struct Rule<Renderer> +pub struct Rule<Renderer = crate::Renderer> where - Renderer: crate::Renderer, + Renderer: crate::core::Renderer, Renderer::Theme: StyleSheet, { width: Length, @@ -23,7 +23,7 @@ where impl<Renderer> Rule<Renderer> where - Renderer: crate::Renderer, + Renderer: crate::core::Renderer, Renderer::Theme: StyleSheet, { /// Creates a horizontal [`Rule`] with the given height. @@ -58,7 +58,7 @@ where impl<Message, Renderer> Widget<Message, Renderer> for Rule<Renderer> where - Renderer: crate::Renderer, + Renderer: crate::core::Renderer, Renderer::Theme: StyleSheet, { fn width(&self) -> Length { @@ -138,7 +138,7 @@ impl<'a, Message, Renderer> From<Rule<Renderer>> for Element<'a, Message, Renderer> where Message: 'a, - Renderer: 'a + crate::Renderer, + Renderer: 'a + crate::core::Renderer, Renderer::Theme: StyleSheet, { fn from(rule: Rule<Renderer>) -> Element<'a, Message, Renderer> { diff --git a/native/src/widget/scrollable.rs b/widget/src/scrollable.rs index e35a4f96..fd51a6a8 100644 --- a/native/src/widget/scrollable.rs +++ b/widget/src/scrollable.rs @@ -1,35 +1,29 @@ //! Navigate an endless amount of content with a scrollbar. -use crate::event::{self, Event}; -use crate::keyboard; -use crate::layout; -use crate::mouse; -use crate::overlay; -use crate::renderer; -use crate::touch; -use crate::widget; -use crate::widget::operation::{self, Operation}; -use crate::widget::tree::{self, Tree}; -use crate::{ - Background, Clipboard, Color, Command, Element, Layout, Length, Pixels, - Point, Rectangle, Shell, Size, Vector, Widget, +use crate::core::event::{self, Event}; +use crate::core::keyboard; +use crate::core::layout; +use crate::core::mouse; +use crate::core::overlay; +use crate::core::renderer; +use crate::core::touch; +use crate::core::widget; +use crate::core::widget::operation::{self, Operation}; +use crate::core::widget::tree::{self, Tree}; +use crate::core::{ + Background, Clipboard, Color, Element, Layout, Length, Pixels, Point, + Rectangle, Shell, Size, Vector, Widget, }; +use crate::runtime::Command; -pub use iced_style::scrollable::StyleSheet; +pub use crate::style::scrollable::{Scrollbar, Scroller, StyleSheet}; pub use operation::scrollable::{AbsoluteOffset, RelativeOffset}; -pub mod style { - //! The styles of a [`Scrollable`]. - //! - //! [`Scrollable`]: crate::widget::Scrollable - pub use iced_style::scrollable::{Scrollbar, Scroller}; -} - /// A widget that can vertically display an infinite amount of content with a /// scrollbar. #[allow(missing_debug_implementations)] -pub struct Scrollable<'a, Message, Renderer> +pub struct Scrollable<'a, Message, Renderer = crate::Renderer> where - Renderer: crate::Renderer, + Renderer: crate::core::Renderer, Renderer::Theme: StyleSheet, { id: Option<Id>, @@ -44,7 +38,7 @@ where impl<'a, Message, Renderer> Scrollable<'a, Message, Renderer> where - Renderer: crate::Renderer, + Renderer: crate::core::Renderer, Renderer::Theme: StyleSheet, { /// Creates a new [`Scrollable`]. @@ -157,7 +151,7 @@ impl Properties { impl<'a, Message, Renderer> Widget<Message, Renderer> for Scrollable<'a, Message, Renderer> where - Renderer: crate::Renderer, + Renderer: crate::core::Renderer, Renderer::Theme: StyleSheet, { fn tag(&self) -> tree::Tag { @@ -349,7 +343,7 @@ impl<'a, Message, Renderer> From<Scrollable<'a, Message, Renderer>> for Element<'a, Message, Renderer> where Message: 'a, - Renderer: 'a + crate::Renderer, + Renderer: 'a + crate::core::Renderer, Renderer::Theme: StyleSheet, { fn from( @@ -768,7 +762,7 @@ pub fn draw<Renderer>( style: &<Renderer::Theme as StyleSheet>::Style, draw_content: impl FnOnce(&mut Renderer, Layout<'_>, Point, &Rectangle), ) where - Renderer: crate::Renderer, + Renderer: crate::core::Renderer, Renderer::Theme: StyleSheet, { let bounds = layout.bounds(); @@ -814,8 +808,8 @@ pub fn draw<Renderer>( let draw_scrollbar = |renderer: &mut Renderer, - style: style::Scrollbar, - scrollbar: &Scrollbar| { + style: Scrollbar, + scrollbar: &internals::Scrollbar| { //track if style.background.is_some() || (style.border_color != Color::TRANSPARENT @@ -1139,8 +1133,8 @@ impl State { #[derive(Debug)] /// State of both [`Scrollbar`]s. struct Scrollbars { - y: Option<Scrollbar>, - x: Option<Scrollbar>, + y: Option<internals::Scrollbar>, + x: Option<internals::Scrollbar>, } impl Scrollbars { @@ -1210,10 +1204,10 @@ impl Scrollbars { height: scroller_height, }; - Some(Scrollbar { + Some(internals::Scrollbar { total_bounds: total_scrollbar_bounds, bounds: scrollbar_bounds, - scroller: Scroller { + scroller: internals::Scroller { bounds: scroller_bounds, }, }) @@ -1270,10 +1264,10 @@ impl Scrollbars { height: scroller_width, }; - Some(Scrollbar { + Some(internals::Scrollbar { total_bounds: total_scrollbar_bounds, bounds: scrollbar_bounds, - scroller: Scroller { + scroller: internals::Scroller { bounds: scroller_bounds, }, }) @@ -1335,64 +1329,68 @@ impl Scrollbars { } } -/// The scrollbar of a [`Scrollable`]. -#[derive(Debug, Copy, Clone)] -struct Scrollbar { - /// The total bounds of the [`Scrollbar`], including the scrollbar, the scroller, - /// and the scrollbar margin. - total_bounds: Rectangle, +pub(super) mod internals { + use crate::core::{Point, Rectangle}; - /// The bounds of just the [`Scrollbar`]. - bounds: Rectangle, + /// The scrollbar of a [`Scrollable`]. + #[derive(Debug, Copy, Clone)] + pub struct Scrollbar { + /// The total bounds of the [`Scrollbar`], including the scrollbar, the scroller, + /// and the scrollbar margin. + pub total_bounds: Rectangle, - /// The state of this scrollbar's [`Scroller`]. - scroller: Scroller, -} + /// The bounds of just the [`Scrollbar`]. + pub bounds: Rectangle, -impl Scrollbar { - /// Returns whether the mouse is over the scrollbar or not. - fn is_mouse_over(&self, cursor_position: Point) -> bool { - self.total_bounds.contains(cursor_position) + /// The state of this scrollbar's [`Scroller`]. + pub scroller: Scroller, } - /// Returns the y-axis scrolled percentage from the cursor position. - fn scroll_percentage_y( - &self, - grabbed_at: f32, - cursor_position: Point, - ) -> f32 { - if cursor_position.x < 0.0 && cursor_position.y < 0.0 { - // cursor position is unavailable! Set to either end or beginning of scrollbar depending - // on where the thumb currently is in the track - (self.scroller.bounds.y / self.total_bounds.height).round() - } else { - (cursor_position.y - - self.bounds.y - - self.scroller.bounds.height * grabbed_at) - / (self.bounds.height - self.scroller.bounds.height) + impl Scrollbar { + /// Returns whether the mouse is over the scrollbar or not. + pub fn is_mouse_over(&self, cursor_position: Point) -> bool { + self.total_bounds.contains(cursor_position) } - } - /// Returns the x-axis scrolled percentage from the cursor position. - fn scroll_percentage_x( - &self, - grabbed_at: f32, - cursor_position: Point, - ) -> f32 { - if cursor_position.x < 0.0 && cursor_position.y < 0.0 { - (self.scroller.bounds.x / self.total_bounds.width).round() - } else { - (cursor_position.x - - self.bounds.x - - self.scroller.bounds.width * grabbed_at) - / (self.bounds.width - self.scroller.bounds.width) + /// Returns the y-axis scrolled percentage from the cursor position. + pub fn scroll_percentage_y( + &self, + grabbed_at: f32, + cursor_position: Point, + ) -> f32 { + if cursor_position.x < 0.0 && cursor_position.y < 0.0 { + // cursor position is unavailable! Set to either end or beginning of scrollbar depending + // on where the thumb currently is in the track + (self.scroller.bounds.y / self.total_bounds.height).round() + } else { + (cursor_position.y + - self.bounds.y + - self.scroller.bounds.height * grabbed_at) + / (self.bounds.height - self.scroller.bounds.height) + } + } + + /// Returns the x-axis scrolled percentage from the cursor position. + pub fn scroll_percentage_x( + &self, + grabbed_at: f32, + cursor_position: Point, + ) -> f32 { + if cursor_position.x < 0.0 && cursor_position.y < 0.0 { + (self.scroller.bounds.x / self.total_bounds.width).round() + } else { + (cursor_position.x + - self.bounds.x + - self.scroller.bounds.width * grabbed_at) + / (self.bounds.width - self.scroller.bounds.width) + } } } -} -/// The handle of a [`Scrollbar`]. -#[derive(Debug, Clone, Copy)] -struct Scroller { - /// The bounds of the [`Scroller`]. - bounds: Rectangle, + /// The handle of a [`Scrollbar`]. + #[derive(Debug, Clone, Copy)] + pub struct Scroller { + /// The bounds of the [`Scroller`]. + pub bounds: Rectangle, + } } diff --git a/native/src/widget/slider.rs b/widget/src/slider.rs index cfb0a8fe..18a49665 100644 --- a/native/src/widget/slider.rs +++ b/widget/src/slider.rs @@ -1,13 +1,13 @@ //! Display an interactive selector of a single value from a range of values. //! //! A [`Slider`] has some local [`State`]. -use crate::event::{self, Event}; -use crate::layout; -use crate::mouse; -use crate::renderer; -use crate::touch; -use crate::widget::tree::{self, Tree}; -use crate::{ +use crate::core::event::{self, Event}; +use crate::core::layout; +use crate::core::mouse; +use crate::core::renderer; +use crate::core::touch; +use crate::core::widget::tree::{self, Tree}; +use crate::core::{ Clipboard, Color, Element, Layout, Length, Pixels, Point, Rectangle, Shell, Size, Widget, }; @@ -27,11 +27,9 @@ pub use iced_style::slider::{ /// to 1 unit. /// /// # Example -/// ``` -/// # use iced_native::widget::slider; -/// # use iced_native::renderer::Null; -/// # -/// # type Slider<'a, T, Message> = slider::Slider<'a, T, Message, Null>; +/// ```no_run +/// # type Slider<'a, T, Message> = +/// # iced_widget::Slider<'a, Message, T, iced_widget::renderer::Renderer<iced_widget::style::Theme>>; /// # /// #[derive(Clone)] /// pub enum Message { @@ -45,9 +43,9 @@ pub use iced_style::slider::{ /// ///  #[allow(missing_debug_implementations)] -pub struct Slider<'a, T, Message, Renderer> +pub struct Slider<'a, T, Message, Renderer = crate::Renderer> where - Renderer: crate::Renderer, + Renderer: crate::core::Renderer, Renderer::Theme: StyleSheet, { range: RangeInclusive<T>, @@ -64,7 +62,7 @@ impl<'a, T, Message, Renderer> Slider<'a, T, Message, Renderer> where T: Copy + From<u8> + std::cmp::PartialOrd, Message: Clone, - Renderer: crate::Renderer, + Renderer: crate::core::Renderer, Renderer::Theme: StyleSheet, { /// The default height of a [`Slider`]. @@ -150,7 +148,7 @@ impl<'a, T, Message, Renderer> Widget<Message, Renderer> where T: Copy + Into<f64> + num_traits::FromPrimitive, Message: Clone, - Renderer: crate::Renderer, + Renderer: crate::core::Renderer, Renderer::Theme: StyleSheet, { fn tag(&self) -> tree::Tag { @@ -247,7 +245,7 @@ impl<'a, T, Message, Renderer> From<Slider<'a, T, Message, Renderer>> where T: 'a + Copy + Into<f64> + num_traits::FromPrimitive, Message: 'a + Clone, - Renderer: 'a + crate::Renderer, + Renderer: 'a + crate::core::Renderer, Renderer::Theme: StyleSheet, { fn from( @@ -356,7 +354,7 @@ pub fn draw<T, R>( style: &<R::Theme as StyleSheet>::Style, ) where T: Into<f64> + Copy, - R: crate::Renderer, + R: crate::core::Renderer, R::Theme: StyleSheet, { let bounds = layout.bounds(); diff --git a/native/src/widget/space.rs b/widget/src/space.rs index a6fc977e..e1e09d5a 100644 --- a/native/src/widget/space.rs +++ b/widget/src/space.rs @@ -1,8 +1,9 @@ //! Distribute content vertically. -use crate::layout; -use crate::renderer; -use crate::widget::Tree; -use crate::{Element, Layout, Length, Point, Rectangle, Size, Widget}; +use crate::core; +use crate::core::layout; +use crate::core::renderer; +use crate::core::widget::Tree; +use crate::core::{Element, Layout, Length, Point, Rectangle, Size, Widget}; /// An amount of empty space. /// @@ -41,7 +42,7 @@ impl Space { impl<Message, Renderer> Widget<Message, Renderer> for Space where - Renderer: crate::Renderer, + Renderer: core::Renderer, { fn width(&self) -> Length { self.width @@ -76,7 +77,7 @@ where impl<'a, Message, Renderer> From<Space> for Element<'a, Message, Renderer> where - Renderer: crate::Renderer, + Renderer: core::Renderer, Message: 'a, { fn from(space: Space) -> Element<'a, Message, Renderer> { diff --git a/native/src/widget/svg.rs b/widget/src/svg.rs index f5ed0a6c..89017fcf 100644 --- a/native/src/widget/svg.rs +++ b/widget/src/svg.rs @@ -1,15 +1,15 @@ //! Display vector graphics in your application. -use crate::layout; -use crate::renderer; -use crate::svg; -use crate::widget::Tree; -use crate::{ +use crate::core::layout; +use crate::core::renderer; +use crate::core::svg; +use crate::core::widget::Tree; +use crate::core::{ ContentFit, Element, Layout, Length, Point, Rectangle, Size, Vector, Widget, }; use std::path::PathBuf; -pub use iced_style::svg::{Appearance, StyleSheet}; +pub use crate::style::svg::{Appearance, StyleSheet}; pub use svg::Handle; /// A vector graphics image. @@ -19,7 +19,7 @@ pub use svg::Handle; /// [`Svg`] images can have a considerable rendering cost when resized, /// specially when they are complex. #[allow(missing_debug_implementations)] -pub struct Svg<Renderer> +pub struct Svg<Renderer = crate::Renderer> where Renderer: svg::Renderer, Renderer::Theme: StyleSheet, diff --git a/widget/src/text.rs b/widget/src/text.rs new file mode 100644 index 00000000..ce4f44bd --- /dev/null +++ b/widget/src/text.rs @@ -0,0 +1,6 @@ +//! Draw and interact with text. +pub use crate::core::widget::text::*; + +/// A paragraph. +pub type Text<'a, Renderer = crate::Renderer> = + crate::core::widget::Text<'a, Renderer>; diff --git a/native/src/widget/text_input.rs b/widget/src/text_input.rs index 6113cf94..bbc07dac 100644 --- a/native/src/widget/text_input.rs +++ b/widget/src/text_input.rs @@ -11,31 +11,34 @@ pub use value::Value; use editor::Editor; -use crate::alignment; -use crate::event::{self, Event}; -use crate::keyboard; -use crate::layout; -use crate::mouse::{self, click}; -use crate::renderer; -use crate::text::{self, Text}; -use crate::time::{Duration, Instant}; -use crate::touch; -use crate::widget; -use crate::widget::operation::{self, Operation}; -use crate::widget::tree::{self, Tree}; -use crate::window; -use crate::{ - Clipboard, Color, Command, Element, Layout, Length, Padding, Pixels, Point, +use crate::core::alignment; +use crate::core::event::{self, Event}; +use crate::core::keyboard; +use crate::core::layout; +use crate::core::mouse::{self, click}; +use crate::core::renderer; +use crate::core::text::{self, Text}; +use crate::core::time::{Duration, Instant}; +use crate::core::touch; +use crate::core::widget; +use crate::core::widget::operation::{self, Operation}; +use crate::core::widget::tree::{self, Tree}; +use crate::core::window; +use crate::core::{ + Clipboard, Color, Element, Layout, Length, Padding, Pixels, Point, Rectangle, Shell, Size, Vector, Widget, }; +use crate::runtime::Command; pub use iced_style::text_input::{Appearance, StyleSheet}; /// A field that can be filled with text. /// /// # Example -/// ``` -/// # pub type TextInput<'a, Message> = iced_native::widget::TextInput<'a, Message, iced_native::renderer::Null>; +/// ```no_run +/// # pub type TextInput<'a, Message> = +/// # iced_widget::TextInput<'a, Message, iced_widget::renderer::Renderer<iced_widget::style::Theme>>; +/// # /// #[derive(Debug, Clone)] /// enum Message { /// TextInputChanged(String), @@ -52,7 +55,7 @@ pub use iced_style::text_input::{Appearance, StyleSheet}; /// ``` ///  #[allow(missing_debug_implementations)] -pub struct TextInput<'a, Message, Renderer> +pub struct TextInput<'a, Message, Renderer = crate::Renderer> where Renderer: text::Renderer, Renderer::Theme: StyleSheet, @@ -61,10 +64,11 @@ where placeholder: String, value: Value, is_secure: bool, - font: Renderer::Font, + font: Option<Renderer::Font>, width: Length, padding: Padding, size: Option<f32>, + line_height: text::LineHeight, on_input: Option<Box<dyn Fn(String) -> Message + 'a>>, on_paste: Option<Box<dyn Fn(String) -> Message + 'a>>, on_submit: Option<Message>, @@ -89,10 +93,11 @@ where placeholder: String::from(placeholder), value: Value::new(value), is_secure: false, - font: Default::default(), + font: None, width: Length::Fill, padding: Padding::new(5.0), size: None, + line_height: text::LineHeight::default(), on_input: None, on_paste: None, on_submit: None, @@ -146,7 +151,7 @@ where /// /// [`Font`]: text::Renderer::Font pub fn font(mut self, font: Renderer::Font) -> Self { - self.font = font; + self.font = Some(font); self } @@ -174,6 +179,15 @@ where self } + /// Sets the [`LineHeight`] of the [`TextInput`]. + pub fn line_height( + mut self, + line_height: impl Into<text::LineHeight>, + ) -> Self { + self.line_height = line_height.into(); + self + } + /// Sets the style of the [`TextInput`]. pub fn style( mut self, @@ -205,7 +219,8 @@ where value.unwrap_or(&self.value), &self.placeholder, self.size, - &self.font, + self.line_height, + self.font, self.on_input.is_none(), self.is_secure, self.icon.as_ref(), @@ -260,6 +275,7 @@ where self.width, self.padding, self.size, + self.line_height, self.icon.as_ref(), ) } @@ -296,7 +312,8 @@ where shell, &mut self.value, self.size, - &self.font, + self.line_height, + self.font, self.is_secure, self.on_input.as_deref(), self.on_paste.as_deref(), @@ -324,7 +341,8 @@ where &self.value, &self.placeholder, self.size, - &self.font, + self.line_height, + self.font, self.on_input.is_none(), self.is_secure, self.icon.as_ref(), @@ -444,15 +462,18 @@ pub fn layout<Renderer>( width: Length, padding: Padding, size: Option<f32>, + line_height: text::LineHeight, icon: Option<&Icon<Renderer::Font>>, ) -> layout::Node where Renderer: text::Renderer, { let text_size = size.unwrap_or_else(|| renderer.default_size()); - let padding = padding.fit(Size::ZERO, limits.max()); - let limits = limits.width(width).pad(padding).height(text_size); + let limits = limits + .width(width) + .pad(padding) + .height(line_height.to_absolute(Pixels(text_size))); let text_bounds = limits.resolve(Size::ZERO); @@ -460,7 +481,8 @@ where let icon_width = renderer.measure_width( &icon.code_point.to_string(), icon.size.unwrap_or_else(|| renderer.default_size()), - icon.font.clone(), + icon.font, + text::Shaping::Advanced, ); let mut text_node = layout::Node::new( @@ -512,7 +534,8 @@ pub fn update<'a, Message, Renderer>( shell: &mut Shell<'_, Message>, value: &mut Value, size: Option<f32>, - font: &Renderer::Font, + line_height: text::LineHeight, + font: Option<Renderer::Font>, is_secure: bool, on_input: Option<&dyn Fn(String) -> Message>, on_paste: Option<&dyn Fn(String) -> Message>, @@ -562,8 +585,9 @@ where find_cursor_position( renderer, text_layout.bounds(), - font.clone(), + font, size, + line_height, &value, state, target, @@ -590,8 +614,9 @@ where let position = find_cursor_position( renderer, text_layout.bounds(), - font.clone(), + font, size, + line_height, value, state, target, @@ -639,8 +664,9 @@ where let position = find_cursor_position( renderer, text_layout.bounds(), - font.clone(), + font, size, + line_height, &value, state, target, @@ -923,7 +949,8 @@ pub fn draw<Renderer>( value: &Value, placeholder: &str, size: Option<f32>, - font: &Renderer::Font, + line_height: text::LineHeight, + font: Option<Renderer::Font>, is_disabled: bool, is_secure: bool, icon: Option<&Icon<Renderer::Font>>, @@ -968,7 +995,8 @@ pub fn draw<Renderer>( renderer.fill_text(Text { content: &icon.code_point.to_string(), size: icon.size.unwrap_or_else(|| renderer.default_size()), - font: icon.font.clone(), + line_height: text::LineHeight::default(), + font: icon.font, color: appearance.icon_color, bounds: Rectangle { y: text_bounds.center_y(), @@ -976,10 +1004,12 @@ pub fn draw<Renderer>( }, horizontal_alignment: alignment::Horizontal::Left, vertical_alignment: alignment::Vertical::Center, + shaping: text::Shaping::Advanced, }); } let text = value.to_string(); + let font = font.unwrap_or_else(|| renderer.default_font()); let size = size.unwrap_or_else(|| renderer.default_size()); let (cursor, offset) = if let Some(focus) = &state.is_focused { @@ -992,7 +1022,7 @@ pub fn draw<Renderer>( value, size, position, - font.clone(), + font, ); let is_cursor_visible = ((focus.now - focus.updated_at) @@ -1033,7 +1063,7 @@ pub fn draw<Renderer>( value, size, left, - font.clone(), + font, ); let (right_position, right_offset) = @@ -1043,7 +1073,7 @@ pub fn draw<Renderer>( value, size, right, - font.clone(), + font, ); let width = right_position - left_position; @@ -1078,12 +1108,15 @@ pub fn draw<Renderer>( let text_width = renderer.measure_width( if text.is_empty() { placeholder } else { &text }, size, - font.clone(), + font, + text::Shaping::Advanced, ); let render = |renderer: &mut Renderer| { if let Some((cursor, color)) = cursor { renderer.fill_quad(cursor, color); + } else { + renderer.with_translation(Vector::ZERO, |_| {}); } renderer.fill_text(Text { @@ -1095,15 +1128,17 @@ pub fn draw<Renderer>( } else { theme.value_color(style) }, - font: font.clone(), + font, bounds: Rectangle { y: text_bounds.center_y(), width: f32::INFINITY, ..text_bounds }, size, + line_height, horizontal_alignment: alignment::Horizontal::Left, vertical_alignment: alignment::Vertical::Center, + shaping: text::Shaping::Advanced, }); }; @@ -1250,7 +1285,7 @@ impl operation::TextInput for State { } mod platform { - use crate::keyboard; + use crate::core::keyboard; pub fn is_jump_modifier_pressed(modifiers: keyboard::Modifiers) -> bool { if cfg!(target_os = "macos") { @@ -1308,8 +1343,12 @@ where { let text_before_cursor = value.until(cursor_index).to_string(); - let text_value_width = - renderer.measure_width(&text_before_cursor, size, font); + let text_value_width = renderer.measure_width( + &text_before_cursor, + size, + font, + text::Shaping::Advanced, + ); let offset = ((text_value_width + 5.0) - text_bounds.width).max(0.0); @@ -1321,8 +1360,9 @@ where fn find_cursor_position<Renderer>( renderer: &Renderer, text_bounds: Rectangle, - font: Renderer::Font, + font: Option<Renderer::Font>, size: Option<f32>, + line_height: text::LineHeight, value: &Value, state: &State, x: f32, @@ -1330,21 +1370,32 @@ fn find_cursor_position<Renderer>( where Renderer: text::Renderer, { + let font = font.unwrap_or_else(|| renderer.default_font()); let size = size.unwrap_or_else(|| renderer.default_size()); - let offset = - offset(renderer, text_bounds, font.clone(), size, value, state); + let offset = offset(renderer, text_bounds, font, size, value, state); + let value = value.to_string(); - renderer + let char_offset = renderer .hit_test( - &value.to_string(), + &value, size, + line_height, font, Size::INFINITY, + text::Shaping::Advanced, Point::new(x + offset, text_bounds.height / 2.0), true, ) - .map(text::Hit::cursor) + .map(text::Hit::cursor)?; + + Some( + unicode_segmentation::UnicodeSegmentation::graphemes( + &value[..char_offset], + true, + ) + .count(), + ) } const CURSOR_BLINK_INTERVAL_MILLIS: u128 = 500; diff --git a/native/src/widget/text_input/cursor.rs b/widget/src/text_input/cursor.rs index 4f3b159b..9680dfd7 100644 --- a/native/src/widget/text_input/cursor.rs +++ b/widget/src/text_input/cursor.rs @@ -1,5 +1,5 @@ //! Track the cursor of a text input. -use crate::widget::text_input::Value; +use crate::text_input::Value; /// The cursor of a text input. #[derive(Debug, Copy, Clone)] diff --git a/native/src/widget/text_input/editor.rs b/widget/src/text_input/editor.rs index d53fa8d9..f1fd641f 100644 --- a/native/src/widget/text_input/editor.rs +++ b/widget/src/text_input/editor.rs @@ -1,4 +1,4 @@ -use crate::widget::text_input::{Cursor, Value}; +use crate::text_input::{Cursor, Value}; pub struct Editor<'a> { value: &'a mut Value, diff --git a/native/src/widget/text_input/value.rs b/widget/src/text_input/value.rs index cf4da562..cf4da562 100644 --- a/native/src/widget/text_input/value.rs +++ b/widget/src/text_input/value.rs diff --git a/native/src/widget/toggler.rs b/widget/src/toggler.rs index a434af65..b1ba65c9 100644 --- a/native/src/widget/toggler.rs +++ b/widget/src/toggler.rs @@ -1,24 +1,26 @@ //! Show toggle controls using togglers. -use crate::alignment; -use crate::event; -use crate::layout; -use crate::mouse; -use crate::renderer; -use crate::text; -use crate::widget::{self, Row, Text, Tree}; -use crate::{ +use crate::core::alignment; +use crate::core::event; +use crate::core::layout; +use crate::core::mouse; +use crate::core::renderer; +use crate::core::text; +use crate::core::widget::Tree; +use crate::core::{ Alignment, Clipboard, Element, Event, Layout, Length, Pixels, Point, Rectangle, Shell, Widget, }; +use crate::{Row, Text}; -pub use iced_style::toggler::{Appearance, StyleSheet}; +pub use crate::style::toggler::{Appearance, StyleSheet}; /// A toggler widget. /// /// # Example /// -/// ``` -/// # type Toggler<'a, Message> = iced_native::widget::Toggler<'a, Message, iced_native::renderer::Null>; +/// ```no_run +/// # type Toggler<'a, Message> = +/// # iced_widget::Toggler<'a, Message, iced_widget::renderer::Renderer<iced_widget::style::Theme>>; /// # /// pub enum Message { /// TogglerToggled(bool), @@ -29,7 +31,7 @@ pub use iced_style::toggler::{Appearance, StyleSheet}; /// Toggler::new(String::from("Toggle me!"), is_toggled, |b| Message::TogglerToggled(b)); /// ``` #[allow(missing_debug_implementations)] -pub struct Toggler<'a, Message, Renderer> +pub struct Toggler<'a, Message, Renderer = crate::Renderer> where Renderer: text::Renderer, Renderer::Theme: StyleSheet, @@ -40,9 +42,11 @@ where width: Length, size: f32, text_size: Option<f32>, + text_line_height: text::LineHeight, text_alignment: alignment::Horizontal, + text_shaping: text::Shaping, spacing: f32, - font: Renderer::Font, + font: Option<Renderer::Font>, style: <Renderer::Theme as StyleSheet>::Style, } @@ -77,9 +81,11 @@ where width: Length::Fill, size: Self::DEFAULT_SIZE, text_size: None, + text_line_height: text::LineHeight::default(), text_alignment: alignment::Horizontal::Left, + text_shaping: text::Shaping::Basic, spacing: 0.0, - font: Renderer::Font::default(), + font: None, style: Default::default(), } } @@ -102,12 +108,27 @@ where self } + /// Sets the text [`LineHeight`] of the [`Toggler`]. + pub fn text_line_height( + mut self, + line_height: impl Into<text::LineHeight>, + ) -> Self { + self.text_line_height = line_height.into(); + self + } + /// Sets the horizontal alignment of the text of the [`Toggler`] pub fn text_alignment(mut self, alignment: alignment::Horizontal) -> Self { self.text_alignment = alignment; self } + /// Sets the [`text::Shaping`] strategy of the [`Toggler`]. + pub fn text_shaping(mut self, shaping: text::Shaping) -> Self { + self.text_shaping = shaping; + self + } + /// Sets the spacing between the [`Toggler`] and the text. pub fn spacing(mut self, spacing: impl Into<Pixels>) -> Self { self.spacing = spacing.into().0; @@ -117,8 +138,8 @@ where /// Sets the [`Font`] of the text of the [`Toggler`] /// /// [`Font`]: crate::text::Renderer::Font - pub fn font(mut self, font: Renderer::Font) -> Self { - self.font = font; + pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self { + self.font = Some(font.into()); self } @@ -136,7 +157,7 @@ impl<'a, Message, Renderer> Widget<Message, Renderer> for Toggler<'a, Message, Renderer> where Renderer: text::Renderer, - Renderer::Theme: StyleSheet + widget::text::StyleSheet, + Renderer::Theme: StyleSheet + crate::text::StyleSheet, { fn width(&self) -> Length { self.width @@ -160,12 +181,14 @@ where row = row.push( Text::new(label) .horizontal_alignment(self.text_alignment) - .font(self.font.clone()) + .font(self.font.unwrap_or_else(|| renderer.default_font())) .width(self.width) .size( self.text_size .unwrap_or_else(|| renderer.default_size()), - ), + ) + .line_height(self.text_line_height) + .shaping(self.text_shaping), ); } @@ -237,16 +260,18 @@ where if let Some(label) = &self.label { let label_layout = children.next().unwrap(); - crate::widget::text::draw( + crate::text::draw( renderer, style, label_layout, label, self.text_size, - self.font.clone(), + self.text_line_height, + self.font, Default::default(), self.text_alignment, alignment::Vertical::Center, + self.text_shaping, ); } @@ -314,7 +339,7 @@ impl<'a, Message, Renderer> From<Toggler<'a, Message, Renderer>> where Message: 'a, Renderer: 'a + text::Renderer, - Renderer::Theme: StyleSheet + widget::text::StyleSheet, + Renderer::Theme: StyleSheet + crate::text::StyleSheet, { fn from( toggler: Toggler<'a, Message, Renderer>, diff --git a/native/src/widget/tooltip.rs b/widget/src/tooltip.rs index 2a24c055..084650d1 100644 --- a/native/src/widget/tooltip.rs +++ b/widget/src/tooltip.rs @@ -1,26 +1,27 @@ //! Display a widget over another. -use crate::event; -use crate::layout; -use crate::mouse; -use crate::renderer; -use crate::text; -use crate::widget; -use crate::widget::container; -use crate::widget::overlay; -use crate::widget::{Text, Tree}; -use crate::{ - Clipboard, Element, Event, Layout, Length, Padding, Pixels, Point, - Rectangle, Shell, Size, Vector, Widget, +use crate::container; +use crate::core; +use crate::core::event::{self, Event}; +use crate::core::layout::{self, Layout}; +use crate::core::mouse; +use crate::core::overlay; +use crate::core::renderer; +use crate::core::text; +use crate::core::widget::Tree; +use crate::core::{ + Clipboard, Element, Length, Padding, Pixels, Point, Rectangle, Shell, Size, + Vector, Widget, }; +use crate::Text; use std::borrow::Cow; /// An element to display a widget over another. #[allow(missing_debug_implementations)] -pub struct Tooltip<'a, Message, Renderer: text::Renderer> +pub struct Tooltip<'a, Message, Renderer = crate::Renderer> where Renderer: text::Renderer, - Renderer::Theme: container::StyleSheet + widget::text::StyleSheet, + Renderer::Theme: container::StyleSheet + crate::text::StyleSheet, { content: Element<'a, Message, Renderer>, tooltip: Text<'a, Renderer>, @@ -34,7 +35,7 @@ where impl<'a, Message, Renderer> Tooltip<'a, Message, Renderer> where Renderer: text::Renderer, - Renderer::Theme: container::StyleSheet + widget::text::StyleSheet, + Renderer::Theme: container::StyleSheet + crate::text::StyleSheet, { /// The default padding of a [`Tooltip`] drawn by this renderer. const DEFAULT_PADDING: f32 = 5.0; @@ -104,7 +105,7 @@ impl<'a, Message, Renderer> Widget<Message, Renderer> for Tooltip<'a, Message, Renderer> where Renderer: text::Renderer, - Renderer::Theme: container::StyleSheet + widget::text::StyleSheet, + Renderer::Theme: container::StyleSheet + crate::text::StyleSheet, { fn children(&self) -> Vec<Tree> { vec![Tree::new(&self.content)] @@ -239,7 +240,7 @@ impl<'a, Message, Renderer> From<Tooltip<'a, Message, Renderer>> where Message: 'a, Renderer: 'a + text::Renderer, - Renderer::Theme: container::StyleSheet + widget::text::StyleSheet, + Renderer::Theme: container::StyleSheet + crate::text::StyleSheet, { fn from( tooltip: Tooltip<'a, Message, Renderer>, @@ -285,7 +286,7 @@ pub fn draw<Renderer>( &Rectangle, ), ) where - Renderer: crate::Renderer, + Renderer: core::Renderer, Renderer::Theme: container::StyleSheet, { use container::StyleSheet; diff --git a/native/src/widget/vertical_slider.rs b/widget/src/vertical_slider.rs index cb2de480..2635611d 100644 --- a/native/src/widget/vertical_slider.rs +++ b/widget/src/vertical_slider.rs @@ -3,13 +3,18 @@ //! A [`VerticalSlider`] has some local [`State`]. use std::ops::RangeInclusive; -pub use iced_style::slider::{Appearance, Handle, HandleShape, StyleSheet}; - -use crate::event::{self, Event}; -use crate::widget::tree::{self, Tree}; -use crate::{ - layout, mouse, renderer, touch, Clipboard, Color, Element, Layout, Length, - Pixels, Point, Rectangle, Shell, Size, Widget, +pub use crate::style::slider::{Appearance, Handle, HandleShape, StyleSheet}; + +use crate::core; +use crate::core::event::{self, Event}; +use crate::core::layout::{self, Layout}; +use crate::core::mouse; +use crate::core::renderer; +use crate::core::touch; +use crate::core::widget::tree::{self, Tree}; +use crate::core::{ + Clipboard, Color, Element, Length, Pixels, Point, Rectangle, Shell, Size, + Widget, }; /// An vertical bar and a handle that selects a single value from a range of @@ -21,11 +26,9 @@ use crate::{ /// to 1 unit. /// /// # Example -/// ``` -/// # use iced_native::widget::vertical_slider; -/// # use iced_native::renderer::Null; -/// # -/// # type VerticalSlider<'a, T, Message> = vertical_slider::VerticalSlider<'a, T, Message, Null>; +/// ```no_run +/// # type VerticalSlider<'a, T, Message> = +/// # iced_widget::VerticalSlider<'a, T, Message, iced_widget::renderer::Renderer<iced_widget::style::Theme>>; /// # /// #[derive(Clone)] /// pub enum Message { @@ -37,9 +40,9 @@ use crate::{ /// VerticalSlider::new(0.0..=100.0, value, Message::SliderChanged); /// ``` #[allow(missing_debug_implementations)] -pub struct VerticalSlider<'a, T, Message, Renderer> +pub struct VerticalSlider<'a, T, Message, Renderer = crate::Renderer> where - Renderer: crate::Renderer, + Renderer: core::Renderer, Renderer::Theme: StyleSheet, { range: RangeInclusive<T>, @@ -56,7 +59,7 @@ impl<'a, T, Message, Renderer> VerticalSlider<'a, T, Message, Renderer> where T: Copy + From<u8> + std::cmp::PartialOrd, Message: Clone, - Renderer: crate::Renderer, + Renderer: core::Renderer, Renderer::Theme: StyleSheet, { /// The default width of a [`VerticalSlider`]. @@ -142,7 +145,7 @@ impl<'a, T, Message, Renderer> Widget<Message, Renderer> where T: Copy + Into<f64> + num_traits::FromPrimitive, Message: Clone, - Renderer: crate::Renderer, + Renderer: core::Renderer, Renderer::Theme: StyleSheet, { fn tag(&self) -> tree::Tag { @@ -239,7 +242,7 @@ impl<'a, T, Message, Renderer> From<VerticalSlider<'a, T, Message, Renderer>> where T: 'a + Copy + Into<f64> + num_traits::FromPrimitive, Message: 'a + Clone, - Renderer: 'a + crate::Renderer, + Renderer: 'a + core::Renderer, Renderer::Theme: StyleSheet, { fn from( @@ -349,7 +352,7 @@ pub fn draw<T, R>( style: &<R::Theme as StyleSheet>::Style, ) where T: Into<f64> + Copy, - R: crate::Renderer, + R: core::Renderer, R::Theme: StyleSheet, { let bounds = layout.bounds(); diff --git a/winit/Cargo.toml b/winit/Cargo.toml index 8788b667..58e13b3e 100644 --- a/winit/Cargo.toml +++ b/winit/Cargo.toml @@ -14,7 +14,7 @@ categories = ["gui"] default = ["x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"] trace = ["tracing", "tracing-core", "tracing-subscriber"] chrome-trace = ["trace", "tracing-chrome"] -debug = ["iced_native/debug"] +debug = ["iced_runtime/debug"] system = ["sysinfo"] application = [] x11 = ["winit/x11"] @@ -33,17 +33,17 @@ git = "https://github.com/iced-rs/winit.git" rev = "940457522e9fb9f5dac228b0ecfafe0138b4048c" default-features = false -[dependencies.iced_native] -version = "0.10" -path = "../native" +[dependencies.iced_runtime] +version = "0.1" +path = "../runtime" [dependencies.iced_graphics] version = "0.8" path = "../graphics" -[dependencies.iced_futures] -version = "0.6" -path = "../futures" +[dependencies.iced_style] +version = "0.8" +path = "../style" [dependencies.tracing] version = "0.1.37" diff --git a/winit/src/application.rs b/winit/src/application.rs index dd345785..3d7c6e5d 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -5,25 +5,25 @@ mod state; pub use state::State; -use crate::clipboard::{self, Clipboard}; use crate::conversion; -use crate::mouse; -use crate::renderer; -use crate::widget::operation; -use crate::{ - Command, Debug, Error, Event, Executor, Proxy, Runtime, Settings, Size, - Subscription, -}; - -use iced_futures::futures; -use iced_futures::futures::channel::mpsc; -use iced_graphics::compositor; -use iced_graphics::window; -use iced_native::program::Program; -use iced_native::time::Instant; -use iced_native::user_interface::{self, UserInterface}; - -pub use iced_native::application::{Appearance, StyleSheet}; +use crate::core; +use crate::core::mouse; +use crate::core::renderer; +use crate::core::time::Instant; +use crate::core::widget::operation; +use crate::core::window; +use crate::core::{Event, Size}; +use crate::futures::futures; +use crate::futures::{Executor, Runtime, Subscription}; +use crate::graphics::compositor::{self, Compositor}; +use crate::runtime::clipboard; +use crate::runtime::program::Program; +use crate::runtime::user_interface::{self, UserInterface}; +use crate::runtime::{Command, Debug}; +use crate::style::application::{Appearance, StyleSheet}; +use crate::{Clipboard, Error, Proxy, Settings}; + +use futures::channel::mpsc; use std::mem::ManuallyDrop; @@ -45,7 +45,7 @@ use tracing::{info_span, instrument::Instrument}; /// can be toggled by pressing `F12`. pub trait Application: Program where - <Self::Renderer as crate::Renderer>::Theme: StyleSheet, + <Self::Renderer as core::Renderer>::Theme: StyleSheet, { /// The data needed to initialize your [`Application`]. type Flags; @@ -67,12 +67,12 @@ where fn title(&self) -> String; /// Returns the current `Theme` of the [`Application`]. - fn theme(&self) -> <Self::Renderer as crate::Renderer>::Theme; + fn theme(&self) -> <Self::Renderer as core::Renderer>::Theme; /// Returns the `Style` variation of the `Theme`. fn style( &self, - ) -> <<Self::Renderer as crate::Renderer>::Theme as StyleSheet>::Style { + ) -> <<Self::Renderer as core::Renderer>::Theme as StyleSheet>::Style { Default::default() } @@ -112,8 +112,8 @@ pub fn run<A, E, C>( where A: Application + 'static, E: Executor + 'static, - C: window::Compositor<Renderer = A::Renderer> + 'static, - <A::Renderer as crate::Renderer>::Theme: StyleSheet, + C: Compositor<Renderer = A::Renderer> + 'static, + <A::Renderer as core::Renderer>::Theme: StyleSheet, { use futures::task; use futures::Future; @@ -282,28 +282,25 @@ async fn run_instance<A, E, C>( ) where A: Application + 'static, E: Executor + 'static, - C: window::Compositor<Renderer = A::Renderer> + 'static, - <A::Renderer as crate::Renderer>::Theme: StyleSheet, + C: Compositor<Renderer = A::Renderer> + 'static, + <A::Renderer as core::Renderer>::Theme: StyleSheet, { - use iced_futures::futures::stream::StreamExt; + use futures::stream::StreamExt; use winit::event; use winit::event_loop::ControlFlow; - let mut clipboard = Clipboard::connect(&window); - let mut cache = user_interface::Cache::default(); - let mut surface = compositor.create_surface(&window); - let mut should_exit = false; - let mut state = State::new(&application, &window); let mut viewport_version = state.viewport_version(); - let physical_size = state.physical_size(); - compositor.configure_surface( - &mut surface, + let mut clipboard = Clipboard::connect(&window); + let mut cache = user_interface::Cache::default(); + let mut surface = compositor.create_surface( + &window, physical_size.width, physical_size.height, ); + let mut should_exit = false; if should_be_visible { window.set_visible(true); @@ -323,7 +320,7 @@ async fn run_instance<A, E, C>( &window, || compositor.fetch_information(), ); - runtime.track(application.subscription()); + runtime.track(application.subscription().into_recipes()); let mut user_interface = ManuallyDrop::new(build_user_interface( &application, @@ -367,8 +364,10 @@ async fn run_instance<A, E, C>( debug.event_processing_finished(); - for event in events.drain(..).zip(statuses.into_iter()) { - runtime.broadcast(event); + for (event, status) in + events.drain(..).zip(statuses.into_iter()) + { + runtime.broadcast(event, status); } if !messages.is_empty() @@ -418,7 +417,7 @@ async fn run_instance<A, E, C>( // Then, we can use the `interface_state` here to decide if a redraw // is needed right away, or simply wait until a specific time. let redraw_event = Event::Window( - crate::window::Event::RedrawRequested(Instant::now()), + window::Event::RedrawRequested(Instant::now()), ); let (interface_state, _) = user_interface.update( @@ -449,17 +448,14 @@ async fn run_instance<A, E, C>( } window.request_redraw(); - runtime - .broadcast((redraw_event, crate::event::Status::Ignored)); + runtime.broadcast(redraw_event, core::event::Status::Ignored); let _ = control_sender.start_send(match interface_state { user_interface::State::Updated { redraw_request: Some(redraw_request), } => match redraw_request { - crate::window::RedrawRequest::NextFrame => { - ControlFlow::Poll - } - crate::window::RedrawRequest::At(at) => { + window::RedrawRequest::NextFrame => ControlFlow::Poll, + window::RedrawRequest::At(at) => { ControlFlow::WaitUntil(at) } }, @@ -471,9 +467,9 @@ async fn run_instance<A, E, C>( event::Event::PlatformSpecific(event::PlatformSpecific::MacOS( event::MacOS::ReceivedUrl(url), )) => { - use iced_native::event; + use crate::core::event; - events.push(iced_native::Event::PlatformSpecific( + events.push(Event::PlatformSpecific( event::PlatformSpecific::MacOS(event::MacOS::ReceivedUrl( url, )), @@ -622,7 +618,7 @@ pub fn build_user_interface<'a, A: Application>( debug: &mut Debug, ) -> UserInterface<'a, A::Message, A::Renderer> where - <A::Renderer as crate::Renderer>::Theme: StyleSheet, + <A::Renderer as core::Renderer>::Theme: StyleSheet, { #[cfg(feature = "trace")] let view_span = info_span!("Application", "VIEW").entered(); @@ -663,7 +659,7 @@ pub fn update<A: Application, E: Executor>( window: &winit::window::Window, graphics_info: impl FnOnce() -> compositor::Information + Copy, ) where - <A::Renderer as crate::Renderer>::Theme: StyleSheet, + <A::Renderer as core::Renderer>::Theme: StyleSheet, { for message in messages.drain(..) { #[cfg(feature = "trace")] @@ -695,7 +691,7 @@ pub fn update<A: Application, E: Executor>( } let subscription = application.subscription(); - runtime.track(subscription); + runtime.track(subscription.into_recipes()); } /// Runs the actions of a [`Command`]. @@ -715,11 +711,11 @@ pub fn run_command<A, E>( ) where A: Application, E: Executor, - <A::Renderer as crate::Renderer>::Theme: StyleSheet, + <A::Renderer as core::Renderer>::Theme: StyleSheet, { - use iced_native::command; - use iced_native::system; - use iced_native::window; + use crate::runtime::command; + use crate::runtime::system; + use crate::runtime::window; for action in command.actions() { match action { @@ -777,7 +773,7 @@ pub fn run_command<A, E>( let mode = if window.is_visible().unwrap_or(true) { conversion::mode(window.fullscreen()) } else { - window::Mode::Hidden + core::window::Mode::Hidden }; proxy @@ -829,7 +825,7 @@ pub fn run_command<A, E>( }, command::Action::Widget(action) => { let mut current_cache = std::mem::take(cache); - let mut current_operation = Some(action.into_operation()); + let mut current_operation = Some(action); let mut user_interface = build_user_interface( application, @@ -858,6 +854,16 @@ pub fn run_command<A, E>( current_cache = user_interface.into_cache(); *cache = current_cache; } + command::Action::LoadFont { bytes, tagger } => { + use crate::core::text::Renderer; + + // TODO: Error handling (?) + renderer.load_font(bytes); + + proxy + .send_event(tagger(Ok(()))) + .expect("Send message to event loop"); + } } } } diff --git a/winit/src/application/state.rs b/winit/src/application/state.rs index 8d6a1df1..c37ccca6 100644 --- a/winit/src/application/state.rs +++ b/winit/src/application/state.rs @@ -1,6 +1,10 @@ use crate::application::{self, StyleSheet as _}; use crate::conversion; -use crate::{Application, Color, Debug, Point, Size, Viewport}; +use crate::core; +use crate::core::{Color, Point, Size}; +use crate::graphics::Viewport; +use crate::runtime::Debug; +use crate::Application; use std::marker::PhantomData; use winit::event::{Touch, WindowEvent}; @@ -10,7 +14,7 @@ use winit::window::Window; #[allow(missing_debug_implementations)] pub struct State<A: Application> where - <A::Renderer as crate::Renderer>::Theme: application::StyleSheet, + <A::Renderer as core::Renderer>::Theme: application::StyleSheet, { title: String, scale_factor: f64, @@ -18,14 +22,14 @@ where viewport_version: usize, cursor_position: winit::dpi::PhysicalPosition<f64>, modifiers: winit::event::ModifiersState, - theme: <A::Renderer as crate::Renderer>::Theme, + theme: <A::Renderer as core::Renderer>::Theme, appearance: application::Appearance, application: PhantomData<A>, } impl<A: Application> State<A> where - <A::Renderer as crate::Renderer>::Theme: application::StyleSheet, + <A::Renderer as core::Renderer>::Theme: application::StyleSheet, { /// Creates a new [`State`] for the provided [`Application`] and window. pub fn new(application: &A, window: &Window) -> Self { @@ -98,7 +102,7 @@ where } /// Returns the current theme of the [`State`]. - pub fn theme(&self) -> &<A::Renderer as crate::Renderer>::Theme { + pub fn theme(&self) -> &<A::Renderer as core::Renderer>::Theme { &self.theme } diff --git a/winit/src/clipboard.rs b/winit/src/clipboard.rs index c1fd8813..7271441d 100644 --- a/winit/src/clipboard.rs +++ b/winit/src/clipboard.rs @@ -1,7 +1,4 @@ //! Access the clipboard. -pub use iced_native::clipboard::Action; - -use crate::command::{self, Command}; /// A buffer for short-term storage and transfer within and between /// applications. @@ -56,7 +53,7 @@ impl Clipboard { } } -impl iced_native::Clipboard for Clipboard { +impl crate::core::Clipboard for Clipboard { fn read(&self) -> Option<String> { self.read() } @@ -65,15 +62,3 @@ impl iced_native::Clipboard for Clipboard { self.write(contents) } } - -/// Read the current contents of the clipboard. -pub fn read<Message>( - f: impl Fn(Option<String>) -> Message + 'static, -) -> Command<Message> { - Command::single(command::Action::Clipboard(Action::Read(Box::new(f)))) -} - -/// Write the given contents to the clipboard. -pub fn write<Message>(contents: String) -> Command<Message> { - Command::single(command::Action::Clipboard(Action::Write(contents))) -} diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs index e416c073..a9262184 100644 --- a/winit/src/conversion.rs +++ b/winit/src/conversion.rs @@ -2,11 +2,12 @@ //! //! [`winit`]: https://github.com/rust-windowing/winit //! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.9/native -use crate::keyboard; -use crate::mouse; -use crate::touch; -use crate::window; -use crate::{Event, Point, Position}; +use crate::core::keyboard; +use crate::core::mouse; +use crate::core::touch; +use crate::core::window; +use crate::core::{Event, Point}; +use crate::Position; /// Converts a winit window event into an iced event. pub fn window_event( diff --git a/winit/src/error.rs b/winit/src/error.rs index eaeafd51..7687fb17 100644 --- a/winit/src/error.rs +++ b/winit/src/error.rs @@ -1,4 +1,5 @@ -use iced_futures::futures; +use crate::futures::futures; +use crate::graphics; /// An error that occurred while running an application. #[derive(Debug, thiserror::Error)] @@ -13,10 +14,10 @@ pub enum Error { /// The application graphics context could not be created. #[error("the application graphics context could not be created")] - GraphicsCreationFailed(iced_graphics::Error), + GraphicsCreationFailed(graphics::Error), } -impl From<iced_graphics::Error> for Error { +impl From<graphics::Error> for Error { fn from(error: iced_graphics::Error) -> Error { Error::GraphicsCreationFailed(error) } diff --git a/winit/src/lib.rs b/winit/src/lib.rs index 6b6c6045..9f6bcebb 100644 --- a/winit/src/lib.rs +++ b/winit/src/lib.rs @@ -30,9 +30,11 @@ #![forbid(rust_2018_idioms, unsafe_code)] #![allow(clippy::inherent_to_string, clippy::type_complexity)] #![cfg_attr(docsrs, feature(doc_cfg))] - -#[doc(no_inline)] -pub use iced_native::*; +pub use iced_graphics as graphics; +pub use iced_runtime as runtime; +pub use iced_runtime::core; +pub use iced_runtime::futures; +pub use iced_style as style; pub use winit; #[cfg(feature = "application")] @@ -40,7 +42,6 @@ pub mod application; pub mod clipboard; pub mod conversion; pub mod settings; -pub mod window; #[cfg(feature = "system")] pub mod system; diff --git a/winit/src/proxy.rs b/winit/src/proxy.rs index 7b9074d7..1d6c48bb 100644 --- a/winit/src/proxy.rs +++ b/winit/src/proxy.rs @@ -1,4 +1,4 @@ -use iced_native::futures::{ +use crate::futures::futures::{ channel::mpsc, task::{Context, Poll}, Sink, diff --git a/winit/src/settings.rs b/winit/src/settings.rs index 6658773d..be0ab329 100644 --- a/winit/src/settings.rs +++ b/winit/src/settings.rs @@ -22,6 +22,7 @@ mod platform; pub use platform::PlatformSpecific; use crate::conversion; +use crate::core::window::Icon; use crate::Position; use winit::monitor::MonitorHandle; @@ -51,14 +52,6 @@ pub struct Settings<Flags> { /// /// [`Application`]: crate::Application pub exit_on_close_request: bool, - - /// Whether the [`Application`] should try to build the context - /// using OpenGL ES first then OpenGL. - /// - /// NOTE: Only works for the `glow` backend. - /// - /// [`Application`]: crate::Application - pub try_opengles_first: bool, } /// The window settings of an application. @@ -92,7 +85,7 @@ pub struct Window { pub always_on_top: bool, /// The window icon, which is also usually used in the taskbar - pub icon: Option<crate::window::Icon>, + pub icon: Option<Icon>, /// Platform specific settings. pub platform_specific: platform::PlatformSpecific, diff --git a/winit/src/system.rs b/winit/src/system.rs index 8d8b018c..145a4d92 100644 --- a/winit/src/system.rs +++ b/winit/src/system.rs @@ -1,8 +1,7 @@ //! Access the native system. -use crate::command::{self, Command}; -pub use iced_native::system::*; - -use iced_graphics::compositor; +use crate::graphics::compositor; +use crate::runtime::command::{self, Command}; +use crate::runtime::system::{Action, Information}; /// Query for available system information. pub fn fetch_information<Message>( |