diff options
Diffstat (limited to '')
-rw-r--r-- | .github/workflows/test.yml | 2 | ||||
-rw-r--r-- | Cargo.toml | 40 | ||||
-rw-r--r-- | core/Cargo.toml | 4 | ||||
-rw-r--r-- | core/src/alignment.rs | 3 | ||||
-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/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) | 0 | ||||
-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) | 67 | ||||
-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) | 6 | ||||
-rw-r--r-- | core/src/lib.rs | 26 | ||||
-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/padding.rs | 10 | ||||
-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) | 4 | ||||
-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) | 0 | ||||
-rw-r--r-- | core/src/text.rs (renamed from native/src/text.rs) | 0 | ||||
-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) | 87 | ||||
-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) | 54 | ||||
-rw-r--r-- | core/src/widget/tree.rs (renamed from native/src/widget/tree.rs) | 0 | ||||
-rw-r--r-- | core/src/window.rs | 10 | ||||
-rw-r--r-- | core/src/window/event.rs (renamed from native/src/window/event.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/arc/src/main.rs | 7 | ||||
-rw-r--r-- | examples/bezier_tool/src/main.rs | 23 | ||||
-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 | 27 | ||||
-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/Cargo.toml | 4 | ||||
-rw-r--r-- | examples/integration/src/controls.rs | 10 | ||||
-rw-r--r-- | examples/integration/src/main.rs | 91 | ||||
-rw-r--r-- | examples/integration/src/scene.rs | 2 | ||||
-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/scrollable/src/main.rs | 1 | ||||
-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/src/main.rs | 21 | ||||
-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-- | examples/websocket/src/main.rs | 4 | ||||
-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 | 316 | ||||
-rw-r--r-- | futures/src/subscription/tracker.rs | 51 | ||||
-rw-r--r-- | graphics/Cargo.toml | 57 | ||||
-rw-r--r-- | graphics/src/backend.rs | 8 | ||||
-rw-r--r-- | graphics/src/compositor.rs (renamed from graphics/src/window/compositor.rs) | 8 | ||||
-rw-r--r-- | graphics/src/geometry.rs | 36 | ||||
-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) | 52 | ||||
-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) | 4 | ||||
-rw-r--r-- | graphics/src/image.rs | 98 | ||||
-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.rs | 2 | ||||
-rw-r--r-- | graphics/src/overlay/menu.rs | 3 | ||||
-rw-r--r-- | graphics/src/primitive.rs | 134 | ||||
-rw-r--r-- | graphics/src/renderer.rs | 68 | ||||
-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/src/mouse.rs | 6 | ||||
-rw-r--r-- | native/src/runtime.rs | 18 | ||||
-rw-r--r-- | native/src/subscription.rs | 248 | ||||
-rw-r--r-- | native/src/widget/action.rs | 154 | ||||
-rw-r--r-- | native/src/widget/helpers.rs | 317 | ||||
-rw-r--r-- | native/src/widget/operation.rs | 112 | ||||
-rw-r--r-- | native/src/window.rs | 30 | ||||
-rw-r--r-- | renderer/Cargo.toml | 30 | ||||
-rw-r--r-- | renderer/src/backend.rs | 98 | ||||
-rw-r--r-- | renderer/src/compositor.rs | 185 | ||||
-rw-r--r-- | renderer/src/geometry.rs | 179 | ||||
-rw-r--r-- | renderer/src/geometry/cache.rs (renamed from graphics/src/widget/canvas/cache.rs) | 59 | ||||
-rw-r--r-- | renderer/src/lib.rs | 22 | ||||
-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) | 10 | ||||
-rw-r--r-- | runtime/README.md (renamed from native/README.md) | 20 | ||||
-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) | 8 | ||||
-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 (renamed from native/src/font.rs) | 0 | ||||
-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) | 6 | ||||
-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) | 82 | ||||
-rw-r--r-- | runtime/src/window.rs (renamed from winit/src/window.rs) | 70 | ||||
-rw-r--r-- | runtime/src/window/action.rs (renamed from native/src/window/action.rs) | 4 | ||||
-rw-r--r-- | src/advanced.rs | 14 | ||||
-rw-r--r-- | src/application.rs | 14 | ||||
-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 | 140 | ||||
-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/touch.rs | 2 | ||||
-rw-r--r-- | src/widget.rs | 238 | ||||
-rw-r--r-- | src/window.rs | 2 | ||||
-rw-r--r-- | style/src/lib.rs | 3 | ||||
-rw-r--r-- | style/src/text.rs | 2 | ||||
-rw-r--r-- | style/src/theme.rs | 2 | ||||
-rw-r--r-- | tiny_skia/Cargo.toml | 40 | ||||
-rw-r--r-- | tiny_skia/src/backend.rs | 548 | ||||
-rw-r--r-- | tiny_skia/src/geometry.rs | 287 | ||||
-rw-r--r-- | tiny_skia/src/lib.rs | 26 | ||||
-rw-r--r-- | tiny_skia/src/primitive.rs | 82 | ||||
-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 | 431 | ||||
-rw-r--r-- | tiny_skia/src/vector.rs | 180 | ||||
-rw-r--r-- | tiny_skia/src/window.rs | 3 | ||||
-rw-r--r-- | tiny_skia/src/window/compositor.rs | 130 | ||||
-rw-r--r-- | wgpu/Cargo.toml | 43 | ||||
-rw-r--r-- | wgpu/src/backend.rs | 23 | ||||
-rw-r--r-- | wgpu/src/geometry.rs (renamed from graphics/src/widget/canvas/frame.rs) | 158 | ||||
-rw-r--r-- | wgpu/src/image.rs | 42 | ||||
-rw-r--r-- | wgpu/src/image/atlas.rs | 192 | ||||
-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) | 61 | ||||
-rw-r--r-- | wgpu/src/layer.rs (renamed from graphics/src/layer.rs) | 78 | ||||
-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) | 3 | ||||
-rw-r--r-- | wgpu/src/lib.rs | 21 | ||||
-rw-r--r-- | wgpu/src/quad.rs | 7 | ||||
-rw-r--r-- | wgpu/src/settings.rs | 5 | ||||
-rw-r--r-- | wgpu/src/text.rs | 15 | ||||
-rw-r--r-- | wgpu/src/triangle.rs | 91 | ||||
-rw-r--r-- | wgpu/src/triangle/msaa.rs | 4 | ||||
-rw-r--r-- | wgpu/src/window.rs | 3 | ||||
-rw-r--r-- | wgpu/src/window/compositor.rs | 155 | ||||
-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) | 31 | ||||
-rw-r--r-- | widget/src/checkbox.rs (renamed from native/src/widget/checkbox.rs) | 38 | ||||
-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 | 362 | ||||
-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) | 47 | ||||
-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) | 32 | ||||
-rw-r--r-- | widget/src/lazy/helpers.rs (renamed from lazy/src/lib.rs) | 37 | ||||
-rw-r--r-- | widget/src/lazy/responsive.rs (renamed from lazy/src/responsive.rs) | 40 | ||||
-rw-r--r-- | widget/src/lib.rs | 122 | ||||
-rw-r--r-- | widget/src/overlay.rs | 1 | ||||
-rw-r--r-- | widget/src/overlay/menu.rs (renamed from native/src/overlay/menu.rs) | 34 | ||||
-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) | 33 | ||||
-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) | 33 | ||||
-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 | 4 | ||||
-rw-r--r-- | widget/src/text_input.rs (renamed from native/src/widget/text_input.rs) | 41 | ||||
-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) | 32 | ||||
-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 | 110 | ||||
-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/system.rs | 11 |
261 files changed, 5931 insertions, 3959 deletions
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 390e7bf3..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 + - name: Check compilation of `integration` example run: cargo build --package integration --target wasm32-unknown-unknown @@ -12,14 +12,21 @@ keywords = ["gui", "ui", "graphics", "interface", "widgets"] categories = ["gui"] [features] +default = ["wgpu", "tiny-skia"] +# Enable the `wgpu` GPU-accelerated renderer backend +wgpu = ["iced_renderer/wgpu"] +# Enable the `tiny-skia` software renderer backend +tiny-skia = ["iced_renderer/tiny-skia"] # Enables the `Image` widget -image = ["iced_wgpu/image", "image_rs"] +image = ["iced_widget/image", "image_rs"] # Enables the `Svg` widget -svg = ["iced_wgpu/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"] +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 @@ -32,11 +39,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_wgpu/tracing", -] +# Enables the advanced module +advanced = [] [badges] maintenance = { status = "actively-developed" } @@ -46,10 +50,12 @@ members = [ "core", "futures", "graphics", - "lazy", - "native", + "runtime", + "renderer", "style", + "tiny_skia", "wgpu", + "widget", "winit", "examples/*", ] @@ -57,22 +63,16 @@ members = [ [dependencies] iced_core = { version = "0.8", path = "core" } iced_futures = { version = "0.6", path = "futures" } -iced_native = { version = "0.9", path = "native" } -iced_graphics = { version = "0.7", path = "graphics" } +iced_renderer = { version = "0.1", path = "renderer" } +iced_widget = { version = "0.1", path = "widget" } iced_winit = { version = "0.8", path = "winit", features = ["application"] } -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.9", path = "wgpu" } - -[target.'cfg(target_arch = "wasm32")'.dependencies] -iced_wgpu = { version = "0.9", path = "wgpu", features = ["webgl"] } - [package.metadata.docs.rs] rustdoc-args = ["--cfg", "docsrs"] features = ["image", "svg", "canvas", "qr_code"] diff --git a/core/Cargo.toml b/core/Cargo.toml index 43865e4d..dac31828 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "iced_core" -version = "0.8.0" +version = "0.8.1" authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] edition = "2021" description = "The essential concepts of Iced" @@ -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/alignment.rs b/core/src/alignment.rs index 73f41d3f..51b7fca9 100644 --- a/core/src/alignment.rs +++ b/core/src/alignment.rs @@ -11,9 +11,6 @@ pub enum Alignment { /// Align at the end of the axis. End, - - /// Fill the entire axis. - Fill, } impl From<Horizontal> for Alignment { 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/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..70fbade0 100644 --- a/native/src/image.rs +++ b/core/src/image.rs 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 5d70c2fc..8b967849 100644 --- a/native/src/layout/flex.rs +++ b/core/src/layout/flex.rs @@ -81,32 +81,6 @@ where let mut nodes: Vec<Node> = Vec::with_capacity(items.len()); nodes.resize(items.len(), Node::default()); - if align_items == Alignment::Fill { - let mut fill_cross = axis.cross(limits.min()); - - items.iter().for_each(|child| { - let cross_fill_factor = match axis { - Axis::Horizontal => child.as_widget().height(), - Axis::Vertical => child.as_widget().width(), - } - .fill_factor(); - - if cross_fill_factor == 0 { - let (max_width, max_height) = axis.pack(available, max_cross); - - let child_limits = - Limits::new(Size::ZERO, Size::new(max_width, max_height)); - - let layout = child.as_widget().layout(renderer, &child_limits); - let size = layout.size(); - - fill_cross = fill_cross.max(axis.cross(size)); - } - }); - - cross = fill_cross; - } - for (i, child) in items.iter().enumerate() { let fill_factor = match axis { Axis::Horizontal => child.as_widget().width(), @@ -115,31 +89,16 @@ where .fill_factor(); if fill_factor == 0 { - let (min_width, min_height) = if align_items == Alignment::Fill { - axis.pack(0.0, cross) - } else { - axis.pack(0.0, 0.0) - }; + let (max_width, max_height) = axis.pack(available, max_cross); - let (max_width, max_height) = if align_items == Alignment::Fill { - axis.pack(available, cross) - } else { - axis.pack(available, max_cross) - }; - - let child_limits = Limits::new( - Size::new(min_width, min_height), - Size::new(max_width, max_height), - ); + let child_limits = + Limits::new(Size::ZERO, Size::new(max_width, max_height)); let layout = child.as_widget().layout(renderer, &child_limits); let size = layout.size(); available -= axis.main(size); - - if align_items != Alignment::Fill { - cross = cross.max(axis.cross(size)); - } + cross = cross.max(axis.cross(size)); nodes[i] = layout; } else { @@ -164,17 +123,10 @@ where max_main }; - let (min_width, min_height) = if align_items == Alignment::Fill { - axis.pack(min_main, cross) - } else { - axis.pack(min_main, axis.cross(limits.min())) - }; + let (min_width, min_height) = + axis.pack(min_main, axis.cross(limits.min())); - let (max_width, max_height) = if align_items == Alignment::Fill { - axis.pack(max_main, cross) - } else { - axis.pack(max_main, max_cross) - }; + let (max_width, max_height) = axis.pack(max_main, max_cross); let child_limits = Limits::new( Size::new(min_width, min_height), @@ -182,10 +134,7 @@ where ); let layout = child.as_widget().layout(renderer, &child_limits); - - if align_items != Alignment::Fill { - cross = cross.max(axis.cross(layout.size())); - } + cross = cross.max(axis.cross(layout.size())); nodes[i] = layout; } 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 e0c7dcb2..2b44a7d5 100644 --- a/native/src/layout/node.rs +++ b/core/src/layout/node.rs @@ -56,9 +56,6 @@ impl Node { Alignment::End => { self.bounds.x += space.width - self.bounds.width; } - Alignment::Fill => { - self.bounds.width = space.width; - } } match vertical_alignment { @@ -69,9 +66,6 @@ impl Node { Alignment::End => { self.bounds.y += space.height - self.bounds.height; } - Alignment::Fill => { - self.bounds.height = space.height; - } } } diff --git a/core/src/lib.rs b/core/src/lib.rs index d7314851..5bdcee6a 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 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/padding.rs b/core/src/padding.rs index 752b2b86..0b1bba13 100644 --- a/core/src/padding.rs +++ b/core/src/padding.rs @@ -77,12 +77,14 @@ impl Padding { /// Fits the [`Padding`] between the provided `inner` and `outer` [`Size`]. pub fn fit(self, inner: Size, outer: Size) -> Self { let available = (outer - inner).max(Size::ZERO); + let new_top = self.top.min(available.height); + let new_left = self.left.min(available.width); Padding { - top: self.top.min(available.height / 2.0), - right: self.right.min(available.width / 2.0), - bottom: self.bottom.min(available.height / 2.0), - left: self.left.min(available.width / 2.0), + top: new_top, + bottom: self.bottom.min(available.height - new_top), + left: new_left, + right: self.right.min(available.width - new_left), } } } 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 150ee786..d93338ae 100644 --- a/native/src/renderer/null.rs +++ b/core/src/renderer/null.rs @@ -1,6 +1,6 @@ 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; @@ -18,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)) {} 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..9b98877a 100644 --- a/native/src/svg.rs +++ b/core/src/svg.rs diff --git a/native/src/text.rs b/core/src/text.rs index 4c72abc3..4c72abc3 100644 --- a/native/src/text.rs +++ b/core/src/text.rs 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 2b3ca7be..70e2c2d9 100644 --- a/native/src/widget.rs +++ b/core/src/widget.rs @@ -1,98 +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; +//! 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 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 3b20631f..3b20631f 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 aede754a..485bb542 100644 --- a/native/src/widget/text.rs +++ b/core/src/widget/text.rs @@ -4,27 +4,13 @@ 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}; - /// 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 @@ -211,7 +197,7 @@ pub fn draw<Renderer>( alignment::Vertical::Bottom => bounds.y + bounds.height, }; - renderer.fill_text(crate::text::Text { + renderer.fill_text(crate::Text { content, size: size.unwrap_or_else(|| renderer.default_size()), bounds: Rectangle { x, y, ..bounds }, @@ -252,12 +238,40 @@ where } } +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 + 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/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..d829a4b4 --- /dev/null +++ b/core/src/window.rs @@ -0,0 +1,10 @@ +//! Build window-based GUI applications. +mod event; +mod mode; +mod redraw_request; +mod user_attention; + +pub use event::Event; +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/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/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/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 c407bb06..09e5e4a2 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; @@ -127,7 +120,8 @@ mod numeric_input { .horizontal_alignment(alignment::Horizontal::Center) .vertical_alignment(alignment::Vertical::Center), ) - .width(50) + .width(40) + .height(40) .on_press(on_press) }; @@ -145,23 +139,18 @@ mod numeric_input { .padding(10), button("+", Event::IncrementPressed), ] - .align_items(Alignment::Fill) + .align_items(Alignment::Center) .spacing(10) .into() } } - 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 39dd843f..5ff951b3 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/Cargo.toml b/examples/integration/Cargo.toml index 200306aa..f6863cd3 100644 --- a/examples/integration/Cargo.toml +++ b/examples/integration/Cargo.toml @@ -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", "tiny-skia"] } env_logger = "0.8" [target.'cfg(target_arch = "wasm32")'.dependencies] diff --git a/examples/integration/src/controls.rs b/examples/integration/src/controls.rs index 533cb6e2..5849f730 100644 --- a/examples/integration/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/src/main.rs b/examples/integration/src/main.rs index a7a90ced..9707eda7 100644 --- a/examples/integration/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, @@ -73,43 +76,45 @@ pub fn main() { let instance = wgpu::Instance::new(backend); 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(); - - ( - surface - .get_supported_formats(&adapter) - .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("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(); + + ( + surface + .get_supported_formats(&adapter) + .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, @@ -188,7 +193,7 @@ pub fn main() { viewport.scale_factor(), ), &mut renderer, - &iced_wgpu::Theme::Dark, + &Theme::Dark, &renderer::Style { text_color: Color::WHITE }, &mut clipboard, &mut debug, diff --git a/examples/integration/src/scene.rs b/examples/integration/src/scene.rs index 3e41fbda..90c7efbf 100644 --- a/examples/integration/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/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 6512106f..e1cdaefe 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 54555684..214ec97e 100644 --- a/examples/modal/src/main.rs +++ b/examples/modal/src/main.rs @@ -178,12 +178,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> { @@ -218,14 +221,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]); } @@ -247,7 +253,7 @@ mod modal { fn on_event( &mut self, - state: &mut Tree, + state: &mut widget::Tree, event: Event, layout: Layout<'_>, cursor_position: Point, @@ -268,9 +274,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, @@ -289,7 +295,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>> { @@ -306,7 +312,7 @@ mod modal { fn mouse_interaction( &self, - state: &Tree, + state: &widget::Tree, layout: Layout<'_>, cursor_position: Point, viewport: &Rectangle, @@ -323,7 +329,7 @@ mod modal { fn operate( &self, - state: &mut Tree, + state: &mut widget::Tree, layout: Layout<'_>, renderer: &Renderer, operation: &mut dyn widget::Operation<Message>, @@ -339,7 +345,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>, } @@ -347,7 +353,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( @@ -469,7 +475,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/scrollable/src/main.rs b/examples/scrollable/src/main.rs index 7c858961..a3ade54f 100644 --- a/examples/scrollable/src/main.rs +++ b/examples/scrollable/src/main.rs @@ -254,7 +254,6 @@ impl Application for ScrollableDemo { scroll_to_beginning_button(), vertical_space(40), ] - .align_items(Alignment::Fill) .spacing(40), horizontal_space(1200), text("Horizontal - End!"), 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 e74b3ee6..78fb9de1 100644 --- a/examples/toast/src/main.rs +++ b/examples/toast/src/main.rs @@ -176,17 +176,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; @@ -324,13 +330,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> { @@ -584,7 +590,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, @@ -613,7 +619,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/src/main.rs b/examples/todos/src/main.rs index 5df4e968..1cc18eca 100644 --- a/examples/todos/src/main.rs +++ b/examples/todos/src/main.rs @@ -1,7 +1,7 @@ use iced::alignment::{self, Alignment}; use iced::event::{self, Event}; use iced::font::{self, Font}; -use iced::keyboard; +use iced::keyboard::{self, KeyCode, Modifiers}; use iced::subscription; use iced::theme::{self, Theme}; use iced::widget::{ @@ -52,6 +52,7 @@ enum Message { FilterChanged(Filter), TaskMessage(usize, TaskMessage), TabPressed { shift: bool }, + ToggleFullscreen(window::Mode), } impl Application for Todos { @@ -162,6 +163,9 @@ impl Application for Todos { widget::focus_next() } } + Message::ToggleFullscreen(mode) => { + window::change_mode(mode) + } _ => Command::none(), }; @@ -272,6 +276,21 @@ impl Application for Todos { ) => Some(Message::TabPressed { shift: modifiers.shift(), }), + ( + Event::Keyboard(keyboard::Event::KeyPressed { + key_code, + modifiers: Modifiers::SHIFT, + }), + event::Status::Ignored, + ) => match key_code { + KeyCode::Up => { + Some(Message::ToggleFullscreen(window::Mode::Fullscreen)) + } + KeyCode::Down => { + Some(Message::ToggleFullscreen(window::Mode::Windowed)) + } + _ => None, + }, _ => None, }) } 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 e74768a6..122c20db 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/examples/websocket/src/main.rs b/examples/websocket/src/main.rs index ccd9c815..e617b8ce 100644 --- a/examples/websocket/src/main.rs +++ b/examples/websocket/src/main.rs @@ -146,7 +146,9 @@ impl Application for WebSocket { } } - row![input, button].spacing(10).align_items(Alignment::Fill) + row![input, button] + .spacing(10) + .align_items(Alignment::Center) }; column![message_log, new_message_input] diff --git a/futures/Cargo.toml b/futures/Cargo.toml index e4d355ee..411e7c2a 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.8" +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 fe53fd7e..876f29c2 100644 --- a/futures/src/subscription.rs +++ b/futures/src/subscription.rs @@ -3,7 +3,18 @@ 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 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 +27,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 +43,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 +53,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 +64,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 +82,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 +99,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 +129,7 @@ impl<I, O, H> std::fmt::Debug for Subscription<I, O, H> { /// [examples]: https://github.com/iced-rs/iced/tree/0.8/examples /// [`download_progress`]: https://github.com/iced-rs/iced/tree/0.8/examples/download_progress /// [`stopwatch`]: https://github.com/iced-rs/iced/tree/0.8/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 +141,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 +176,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 +212,222 @@ 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`]. +/// +/// # 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; +/// +/// use futures::channel::mpsc; +/// +/// 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::unfold(std::any::TypeId::of::<SomeWorker>(), State::Starting, |state| async move { +/// match state { +/// State::Starting => { +/// // Create channel +/// let (sender, receiver) = mpsc::channel(100); +/// +/// (Some(Event::Ready(sender)), State::Ready(receiver)) +/// } +/// State::Ready(mut receiver) => { +/// use 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 return a message to tell the +/// // `Application` the work is done +/// (Some(Event::WorkFinished), State::Ready(receiver)) +/// } +/// } +/// } +/// } +/// }) +/// } +/// ``` +/// +/// Check out the [`websocket`] example, which showcases this pattern to maintain a WebSocket +/// connection open. +/// +/// [`websocket`]: https://github.com/iced-rs/iced/tree/0.8/examples/websocket +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 = (Option<Message>, T)> + MaybeSend + 'static, + Message: 'static + MaybeSend, +{ + use futures::future::{self, FutureExt}; + use futures::stream::StreamExt; + + run_with_id( + id, + futures::stream::unfold(initial, move |state| f(state).map(Some)) + .filter_map(future::ready), + ) +} + +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/graphics/Cargo.toml b/graphics/Cargo.toml index 19d6af0c..a08c6707 100644 --- a/graphics/Cargo.toml +++ b/graphics/Cargo.toml @@ -11,23 +11,9 @@ keywords = ["gui", "ui", "graphics", "interface", "widgets"] categories = ["gui"] [features] -svg = ["resvg", "usvg", "tiny-skia"] -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"] +geometry = ["lyon_path"] opengl = [] -image_rs = ["kamadak-exif"] +image = ["dep:image", "kamadak-exif"] [dependencies] glam = "0.21.3" @@ -40,45 +26,26 @@ bitflags = "1.2" version = "1.4" features = ["derive"] -[dependencies.iced_native] -version = "0.9" -path = "../native" +[dependencies.iced_core] +version = "0.8" +path = "../core" -[dependencies.iced_style] -version = "0.7" -path = "../style" - -[dependencies.lyon] -version = "1.0" -optional = true - -[dependencies.qrcode] -version = "0.12" +[dependencies.tiny-skia] +version = "0.8" optional = true -default-features = false -[dependencies.image_rs] +[dependencies.image] version = "0.24" -package = "image" -default-features = false -optional = true - -[dependencies.resvg] -version = "0.18" -optional = true - -[dependencies.usvg] -version = "0.18" -optional = true - -[dependencies.tiny-skia] -version = "0.6" 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/src/backend.rs b/graphics/src/backend.rs index 8658cffe..dd2888ab 100644 --- a/graphics/src/backend.rs +++ b/graphics/src/backend.rs @@ -1,8 +1,8 @@ //! 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; 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/geometry.rs b/graphics/src/geometry.rs new file mode 100644 index 00000000..8db1594a --- /dev/null +++ b/graphics/src/geometry.rs @@ -0,0 +1,36 @@ +//! 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 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 iced_core::gradient::{self, Gradient}; + +use crate::Primitive; + +#[derive(Debug, Clone)] +pub struct Geometry(pub Primitive); + +impl From<Geometry> for Primitive { + fn from(geometry: Geometry) -> Self { + geometry.0 + } +} + +pub trait Renderer: iced_core::Renderer { + type Geometry; + + fn draw(&mut self, geometry: Vec<Self::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..c3127bdf 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 { @@ -56,54 +54,14 @@ impl Path { } #[inline] - pub(crate) fn raw(&self) -> &lyon::path::Path { + pub fn raw(&self) -> &lyon_path::Path { &self.raw } #[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 4c19251d..2d760a6c 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)] @@ -77,16 +77,6 @@ impl Default for LineCap { } } -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)] @@ -105,16 +95,6 @@ impl Default for LineJoin { } } -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 8c0b2dfb..06e0b4d0 100644 --- a/graphics/src/widget/canvas/text.rs +++ b/graphics/src/geometry/text.rs @@ -1,5 +1,5 @@ -use crate::alignment; -use crate::{Color, Font, Point}; +use iced_core::alignment; +use iced_core::{Color, Font, Point}; /// A bunch of text that can be drawn to a canvas #[derive(Debug, Clone)] diff --git a/graphics/src/image.rs b/graphics/src/image.rs index 04f4ff9d..2f634252 100644 --- a/graphics/src/image.rs +++ b/graphics/src/image.rs @@ -1,10 +1,94 @@ -//! 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; +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 41bef2c3..0c50db52 100644 --- a/graphics/src/lib.rs +++ b/graphics/src/lib.rs @@ -9,7 +9,7 @@ )] #![deny( missing_debug_implementations, - missing_docs, + //missing_docs, unsafe_code, unused_results, clippy::extra_unused_lifetimes, @@ -23,32 +23,30 @@ #![cfg_attr(docsrs, feature(doc_cfg))] mod antialiasing; mod error; -mod primitive; mod transformation; mod viewport; pub mod backend; -pub mod gradient; -pub mod image; -pub mod layer; -pub mod overlay; +pub mod compositor; +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.rs b/graphics/src/overlay.rs deleted file mode 100644 index bc0ed744..00000000 --- a/graphics/src/overlay.rs +++ /dev/null @@ -1,2 +0,0 @@ -//! Display interactive elements on top of other widgets. -pub mod menu; 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 5a163a2f..195b62da 100644 --- a/graphics/src/primitive.rs +++ b/graphics/src/primitive.rs @@ -1,23 +1,15 @@ -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; +use iced_core::alignment; +use iced_core::image; +use iced_core::svg; +use iced_core::{Background, Color, Font, Gradient, Rectangle, Size, Vector}; +use bytemuck::{Pod, Zeroable}; use std::sync::Arc; /// A rendering primitive. #[derive(Debug, Clone)] +#[non_exhaustive] pub enum Primitive { - /// An empty primitive - None, - /// A group of primitives - Group { - /// The primitives of the group - primitives: Vec<Primitive>, - }, /// A text primitive Text { /// The contents of the text @@ -66,27 +58,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. /// @@ -98,7 +75,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. /// @@ -108,18 +85,103 @@ pub enum Primitive { /// The [`Gradient`] to apply to the mesh. gradient: Gradient, }, + #[cfg(feature = "tiny-skia")] + Fill { + path: tiny_skia::Path, + paint: tiny_skia::Paint<'static>, + rule: tiny_skia::FillRule, + transform: tiny_skia::Transform, + }, + #[cfg(feature = "tiny-skia")] + Stroke { + path: tiny_skia::Path, + paint: tiny_skia::Paint<'static>, + stroke: tiny_skia::Stroke, + 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 Default for Primitive { - fn default() -> Primitive { - Primitive::None +impl Primitive { + pub fn group(primitives: Vec<Self>) -> Self { + Self::Group { primitives } + } + + pub fn clip(self, bounds: Rectangle) -> Self { + Self::Clip { + bounds, + content: Box::new(self), + } + } + + pub fn translate(self, translation: Vector) -> Self { + Self::Translate { + translation, + content: Box::new(self), + } + } +} + +/// 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], +} + +impl From<()> for Primitive { + fn from(_: ()) -> Self { + Self::Group { primitives: vec![] } } } diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs index b052c094..7bc462ef 100644 --- a/graphics/src/renderer.rs +++ b/graphics/src/renderer.rs @@ -1,14 +1,15 @@ //! 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}; +use crate::Primitive; -pub use iced_native::renderer::Style; +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; @@ -43,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, { @@ -67,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( @@ -87,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( @@ -196,7 +189,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 }) } } @@ -214,10 +207,23 @@ 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, +{ + type Geometry = crate::Geometry; + + fn draw(&mut self, layers: Vec<Self::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 c739b312..00000000 --- a/lazy/Cargo.toml +++ /dev/null @@ -1,18 +0,0 @@ -[package] -name = "iced_lazy" -version = "0.5.0" -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.9" -path = "../native" 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 16e78e82..00000000 --- a/native/src/subscription.rs +++ /dev/null @@ -1,248 +0,0 @@ -//! Listen to external events in your application. -use crate::event::{self, Event}; -use crate::window; -use crate::Hasher; - -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`]. -/// -/// # 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; -/// -/// 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::unfold(std::any::TypeId::of::<SomeWorker>(), State::Starting, |state| async move { -/// match state { -/// State::Starting => { -/// // Create channel -/// let (sender, receiver) = mpsc::channel(100); -/// -/// (Some(Event::Ready(sender)), State::Ready(receiver)) -/// } -/// State::Ready(mut 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 return a message to tell the -/// // `Application` the work is done -/// (Some(Event::WorkFinished), State::Ready(receiver)) -/// } -/// } -/// } -/// } -/// }) -/// } -/// ``` -/// -/// Check out the [`websocket`] example, which showcases this pattern to maintain a WebSocket -/// connection open. -/// -/// [`websocket`]: https://github.com/iced-rs/iced/tree/0.8/examples/websocket -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 = (Option<Message>, T)> + MaybeSend + 'static, - Message: 'static + MaybeSend, -{ - use futures::future::{self, FutureExt}; - use futures::stream::StreamExt; - - run_with_id( - id, - futures::stream::unfold(initial, move |state| f(state).map(Some)) - .filter_map(future::ready), - ) -} - -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/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 d13eca75..00000000 --- a/native/src/widget/helpers.rs +++ /dev/null @@ -1,317 +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(value, label, 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, - on_change: impl Fn(String) -> Message + 'a, -) -> widget::TextInput<'a, Message, Renderer> -where - Message: Clone, - Renderer: crate::text::Renderer, - Renderer::Theme: widget::text_input::StyleSheet, -{ - widget::TextInput::new(placeholder, value, on_change) -} - -/// 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) -} 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 a5cdc8ce..00000000 --- a/native/src/window.rs +++ /dev/null @@ -1,30 +0,0 @@ -//! Build window-based GUI applications. -mod action; -mod event; -mod mode; -mod redraw_request; -mod user_attention; - -pub use action::Action; -pub use event::Event; -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..629c11ba --- /dev/null +++ b/renderer/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "iced_renderer" +version = "0.1.0" +edition = "2021" + +[features] +wgpu = ["iced_wgpu"] +tiny-skia = ["iced_tiny_skia"] +image = ["iced_wgpu/image", "iced_tiny_skia/image"] +svg = ["iced_wgpu/svg", "iced_tiny_skia/svg"] +geometry = ["iced_graphics/geometry", "iced_wgpu?/geometry", "iced_tiny_skia?/geometry"] +tracing = ["iced_wgpu/tracing"] + +[dependencies] +raw-window-handle = "0.5" +thiserror = "1" + +[dependencies.iced_graphics] +version = "0.7" +path = "../graphics" + +[dependencies.iced_wgpu] +version = "0.9" +path = "../wgpu" +optional = true + +[dependencies.iced_tiny_skia] +version = "0.1" +path = "../tiny_skia" +optional = true diff --git a/renderer/src/backend.rs b/renderer/src/backend.rs new file mode 100644 index 00000000..e77b708b --- /dev/null +++ b/renderer/src/backend.rs @@ -0,0 +1,98 @@ +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 { + #[cfg(feature = "wgpu")] + Wgpu(iced_wgpu::Backend), + #[cfg(feature = "tiny-skia")] + TinySkia(iced_tiny_skia::Backend), +} + +macro_rules! delegate { + ($backend:expr, $name:ident, $body:expr) => { + match $backend { + #[cfg(feature = "wgpu")] + Self::Wgpu($name) => $body, + #[cfg(feature = "tiny-skia")] + Self::TinySkia($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::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, + font: Font, + bounds: Size, + ) -> (f32, f32) { + delegate!(self, backend, backend.measure(contents, size, font, bounds)) + } + + fn hit_test( + &self, + contents: &str, + size: f32, + font: Font, + bounds: Size, + position: Point, + nearest_only: bool, + ) -> Option<text::Hit> { + delegate!( + self, + backend, + backend.hit_test( + contents, + size, + font, + bounds, + 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..218e7e33 --- /dev/null +++ b/renderer/src/compositor.rs @@ -0,0 +1,185 @@ +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}; + +pub enum Compositor<Theme> { + #[cfg(feature = "wgpu")] + Wgpu(iced_wgpu::window::Compositor<Theme>), + #[cfg(feature = "tiny-skia")] + TinySkia(iced_tiny_skia::window::Compositor<Theme>), +} + +pub enum Surface { + #[cfg(feature = "wgpu")] + Wgpu(iced_wgpu::window::Surface), + #[cfg(feature = "tiny-skia")] + TinySkia(iced_tiny_skia::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> { + #[cfg(feature = "wgpu")] + let new_wgpu = |settings: Self::Settings, compatible_window| { + 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(( + Self::Wgpu(compositor), + Renderer::new(crate::Backend::Wgpu(backend)), + )) + }; + + #[cfg(feature = "tiny-skia")] + let new_tiny_skia = |settings: Self::Settings, _compatible_window| { + 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(( + Self::TinySkia(compositor), + Renderer::new(crate::Backend::TinySkia(backend)), + )) + }; + + let fail = |_, _| Err(Error::GraphicsAdapterNotFound); + + let candidates = &[ + #[cfg(feature = "wgpu")] + new_wgpu, + #[cfg(feature = "tiny-skia")] + new_tiny_skia, + fail, + ]; + + let mut error = Error::GraphicsAdapterNotFound; + + for candidate in candidates { + match candidate(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 { + #[cfg(feature = "wgpu")] + Self::Wgpu(compositor) => { + Surface::Wgpu(compositor.create_surface(window, width, height)) + } + #[cfg(feature = "tiny-skia")] + Self::TinySkia(compositor) => Surface::TinySkia( + compositor.create_surface(window, width, height), + ), + } + } + + fn configure_surface( + &mut self, + surface: &mut Surface, + width: u32, + height: u32, + ) { + match (self, surface) { + #[cfg(feature = "wgpu")] + (Self::Wgpu(compositor), Surface::Wgpu(surface)) => { + compositor.configure_surface(surface, width, height); + } + #[cfg(feature = "tiny-skia")] + (Self::TinySkia(compositor), Surface::TinySkia(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 { + #[cfg(feature = "wgpu")] + Self::Wgpu(compositor) => compositor.fetch_information(), + #[cfg(feature = "tiny-skia")] + Self::TinySkia(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) { + #[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, + ), + #[cfg(feature = "tiny-skia")] + ( + Self::TinySkia(compositor), + crate::Backend::TinySkia(backend), + Surface::TinySkia(surface), + ) => iced_tiny_skia::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." + ), + } + }) + } +} diff --git a/renderer/src/geometry.rs b/renderer/src/geometry.rs new file mode 100644 index 00000000..21ef2c06 --- /dev/null +++ b/renderer/src/geometry.rs @@ -0,0 +1,179 @@ +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 { + #[cfg(feature = "wgpu")] + Wgpu(iced_wgpu::geometry::Frame), + #[cfg(feature = "tiny-skia")] + TinySkia(iced_tiny_skia::geometry::Frame), +} + +macro_rules! delegate { + ($frame:expr, $name:ident, $body:expr) => { + match $frame { + #[cfg(feature = "wgpu")] + Self::Wgpu($name) => $body, + #[cfg(feature = "tiny-skia")] + Self::TinySkia($name) => $body, + } + }; +} + +impl Frame { + pub fn new<Theme>(renderer: &crate::Renderer<Theme>, size: Size) -> Self { + match renderer.backend() { + #[cfg(feature = "wgpu")] + Backend::Wgpu(_) => { + Frame::Wgpu(iced_wgpu::geometry::Frame::new(size)) + } + #[cfg(feature = "tiny-skia")] + Backend::TinySkia(_) => { + Frame::TinySkia(iced_tiny_skia::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 { + #[cfg(feature = "wgpu")] + Self::Wgpu(_) => { + Self::Wgpu(iced_wgpu::geometry::Frame::new(region.size())) + } + #[cfg(feature = "tiny-skia")] + Self::TinySkia(_) => Self::TinySkia( + iced_tiny_skia::geometry::Frame::new(region.size()), + ), + }; + + f(&mut frame); + + let translation = Vector::new(region.x, region.y); + + match (self, frame) { + #[cfg(feature = "wgpu")] + (Self::Wgpu(target), Self::Wgpu(frame)) => { + target.clip(frame, translation); + } + #[cfg(feature = "tiny-skia")] + (Self::TinySkia(target), Self::TinySkia(frame)) => { + target.clip(frame, translation); + } + #[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 52217bbb..2a3534d0 100644 --- a/graphics/src/widget/canvas/cache.rs +++ b/renderer/src/geometry/cache.rs @@ -1,22 +1,11 @@ -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}; +use std::cell::RefCell; +use std::sync::Arc; -enum State { - Empty, - Filled { - bounds: Size, - primitive: Arc<Primitive>, - }, -} - -impl Default for State { - fn default() -> Self { - State::Empty - } -} /// A simple cache that stores generated [`Geometry`] to avoid recomputation. /// /// A [`Cache`] will not redraw its geometry unless the dimensions of its layer @@ -26,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 { @@ -49,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 { @@ -62,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 { @@ -82,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..ba737b11 --- /dev/null +++ b/renderer/src/lib.rs @@ -0,0 +1,22 @@ +#[cfg(not(any(feature = "wgpu", feature = "tiny-skia")))] +compile_error!("No backend selected. Enable at least one backend feature: `wgpu` or `tiny-skia`."); + +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..d32c87d3 --- /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::SansSerif, + 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 3f92783e..2d3e8db3 100644 --- a/native/Cargo.toml +++ b/runtime/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "iced_native" +name = "iced_runtime" version = "0.9.1" authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] edition = "2021" @@ -11,9 +11,7 @@ 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] version = "0.8" @@ -23,7 +21,3 @@ path = "../core" version = "0.6" path = "../futures" features = ["thread-pool"] - -[dependencies.iced_style] -version = "0.7" -path = "../style" diff --git a/native/README.md b/runtime/README.md index 996daa76..497fd145 100644 --- a/native/README.md +++ b/runtime/README.md @@ -1,22 +1,10 @@ -# `iced_native` +# `iced_runtime` [][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> +`iced_runtime` takes [`iced_core`] and builds a native runtime on top of it. [documentation]: https://docs.rs/iced_native [`iced_core`]: ../core @@ -25,10 +13,10 @@ To achieve this, it introduces a bunch of reusable interfaces: [`raw-window-handle`]: https://github.com/rust-windowing/raw-window-handle ## Installation -Add `iced_native` as a dependency in your `Cargo.toml`: +Add `iced_runtime` as a dependency in your `Cargo.toml`: ```toml -iced_native = "0.9" +iced_runtime = "0.9" ``` __Iced moves fast and the `master` branch can contain breaking changes!__ If 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 d1589c05..6c74f0ef 100644 --- a/native/src/command/action.rs +++ b/runtime/src/command/action.rs @@ -1,7 +1,7 @@ use crate::clipboard; +use crate::core::widget; use crate::font; use crate::system; -use crate::widget; use crate::window; use iced_futures::MaybeSend; @@ -28,7 +28,7 @@ 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 { @@ -59,7 +59,9 @@ 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))), 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/native/src/font.rs b/runtime/src/font.rs index 15359694..15359694 100644 --- a/native/src/font.rs +++ b/runtime/src/font.rs 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 27b6fc0d..aa45e57a 100644 --- a/native/src/lib.rs +++ b/runtime/src/lib.rs @@ -33,7 +33,7 @@ )] #![deny( missing_debug_implementations, - missing_docs, + //missing_docs, unused_results, clippy::extra_unused_lifetimes, clippy::from_over_into, @@ -42,33 +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 font; -pub mod image; 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")] @@ -78,33 +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, 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 font::Font; -pub use hasher::Hasher; -pub use layout::Layout; -pub use overlay::Overlay; 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 25cab332..44585cc5 100644 --- a/native/src/program.rs +++ b/runtime/src/program.rs @@ -1,6 +1,8 @@ //! Build interactive programs using The Elm Architecture. -use crate::text; -use crate::{Command, Element, Renderer}; +use crate::Command; + +use iced_core::text; +use iced_core::{Element, Renderer}; mod state; 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 68ccda55..2c76fd8a 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`]. /// @@ -34,8 +32,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 +45,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 +118,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 +347,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 +373,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 +395,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 961562bd..236064f7 100644 --- a/winit/src/window.rs +++ b/runtime/src/window.rs @@ -1,68 +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, Mode, UserAttention}; +use crate::futures::subscription::{self, Subscription}; -pub use window::{frames, Event, 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 @@ -74,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 @@ -86,21 +96,17 @@ 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)))) } diff --git a/native/src/window/action.rs b/runtime/src/window/action.rs index ce36d129..c1dbd84f 100644 --- a/native/src/window/action.rs +++ b/runtime/src/window/action.rs @@ -1,6 +1,6 @@ -use crate::window::{Mode, UserAttention}; +use crate::core::window::{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..7afba85c --- /dev/null +++ b/src/advanced.rs @@ -0,0 +1,14 @@ +//! Leverage advanced concepts like custom widgets. +pub use crate::core::image; +pub use crate::core::layout::{self, Layout}; +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, Shell}; + +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 9a1c1855..c9ddf840 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. /// @@ -198,24 +198,24 @@ pub trait Application: Sized { default_font: settings.default_font, default_text_size: settings.default_text_size, 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, { @@ -231,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,51 +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; -use iced_wgpu as renderer; -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, 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; -pub use iced_native::theme; -pub use runtime::event; -pub use runtime::font; -pub use runtime::subscription; + /// 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}; +} + +pub mod mouse { + //! Listen and react to mouse events. + pub use crate::core::mouse::{Button, Event, Interaction, ScrollDelta}; +} + +pub mod subscription { + //! Listen to external events in your application. + pub use iced_futures::subscription::{ + events, events_with, run, run_with_id, unfold, Subscription, + }; +} + +#[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 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 font::Font; -pub use renderer::Renderer; -pub use result::Result; 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, 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/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 e2b0537e..00000000 --- a/src/widget.rs +++ /dev/null @@ -1,238 +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 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.8/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::{ - snap_to, style::Scrollbar, style::Scroller, Id, Properties, - RelativeOffset, StyleSheet, - }; - - /// 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, Id, 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 2018053f..824915b2 100644 --- a/src/window.rs +++ b/src/window.rs @@ -8,5 +8,5 @@ pub use icon::Icon; pub use position::Position; pub use settings::Settings; -#[cfg(not(target_arch = "wasm32"))] +pub use crate::core::window::*; pub use crate::runtime::window::*; 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 index f31c2306..d5a16bef 100644 --- a/style/src/text.rs +++ b/style/src/text.rs @@ -1,5 +1,5 @@ //! Change the appearance of text. -use iced_core::Color; +use crate::core::Color; /// The style sheet of some text. pub trait StyleSheet { diff --git a/style/src/theme.rs b/style/src/theme.rs index 4ba4facf..b507d096 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..69197589 --- /dev/null +++ b/tiny_skia/Cargo.toml @@ -0,0 +1,40 @@ +[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.8" +bytemuck = "1" +rustc-hash = "1.1" +ouroboros = "0.15" +kurbo = "0.9" + +[dependencies.iced_graphics] +version = "0.7" +path = "../graphics" +features = ["tiny-skia"] + +[dependencies.cosmic-text] +features = ["std", "swash"] +git = "https://github.com/hecrj/cosmic-text" +rev = "81080c1b9498933b43c1889601a7ea6a3d16e161" + +[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.29" +optional = true diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs new file mode 100644 index 00000000..ba063f4e --- /dev/null +++ b/tiny_skia/src/backend.rs @@ -0,0 +1,548 @@ +use crate::core::alignment; +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::ClipMask, + primitives: &[Primitive], + viewport: &Viewport, + background_color: Color, + overlay: &[T], + ) { + pixels.fill(into_color(background_color)); + + let scale_factor = viewport.scale_factor() as f32; + + for primitive in primitives { + self.draw_primitive( + primitive, + pixels, + clip_mask, + None, + scale_factor, + Vector::ZERO, + ); + } + + for (i, text) in overlay.iter().enumerate() { + const OVERLAY_TEXT_SIZE: f32 = 20.0; + + self.draw_primitive( + &Primitive::Text { + content: text.as_ref().to_owned(), + size: OVERLAY_TEXT_SIZE, + bounds: Rectangle { + x: 10.0, + y: 10.0 + i as f32 * OVERLAY_TEXT_SIZE * 1.2, + width: f32::INFINITY, + height: f32::INFINITY, + }, + color: Color::BLACK, + font: Font::Monospace, + horizontal_alignment: alignment::Horizontal::Left, + vertical_alignment: alignment::Vertical::Top, + }, + pixels, + clip_mask, + None, + scale_factor, + Vector::ZERO, + ); + } + + 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::ClipMask, + clip_bounds: Option<Rectangle>, + scale_factor: f32, + translation: Vector, + ) { + match primitive { + Primitive::Quad { + bounds, + background, + border_radius, + border_width, + border_color, + } => { + let transform = tiny_skia::Transform::from_translate( + translation.x, + translation.y, + ) + .post_scale(scale_factor, scale_factor); + + let path = rounded_rectangle(*bounds, *border_radius); + let clip_mask = clip_bounds.map(|_| clip_mask as &_); + + 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, + font, + horizontal_alignment, + vertical_alignment, + } => { + self.text_pipeline.draw( + content, + (*bounds + translation) * scale_factor, + *color, + *size * scale_factor, + *font, + *horizontal_alignment, + *vertical_alignment, + pixels, + clip_bounds.map(|_| clip_mask as &_), + ); + } + #[cfg(feature = "image")] + Primitive::Image { handle, bounds } => { + 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_bounds.map(|_| clip_mask as &_), + ); + } + #[cfg(feature = "svg")] + Primitive::Svg { + handle, + bounds, + color, + } => { + self.vector_pipeline.draw( + handle, + *color, + (*bounds + translation) * scale_factor, + pixels, + clip_bounds.map(|_| clip_mask as &_), + ); + } + Primitive::Fill { + path, + paint, + rule, + transform, + } => { + pixels.fill_path( + path, + paint, + *rule, + transform + .post_translate(translation.x, translation.y) + .post_scale(scale_factor, scale_factor), + clip_bounds.map(|_| clip_mask as &_), + ); + } + Primitive::Stroke { + path, + paint, + stroke, + transform, + } => { + pixels.stroke_path( + path, + paint, + stroke, + transform + .post_translate(translation.x, translation.y) + .post_scale(scale_factor, scale_factor), + clip_bounds.map(|_| clip_mask as &_), + ); + } + 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; + + adjust_clip_mask(clip_mask, pixels, bounds); + + self.draw_primitive( + content, + pixels, + clip_mask, + Some(bounds), + scale_factor, + translation, + ); + + if let Some(bounds) = clip_bounds { + adjust_clip_mask(clip_mask, pixels, bounds); + } else { + clip_mask.clear(); + } + } + 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 it (?) + } + _ => { + // Not supported! + } + } + } +} + +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_right > 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::ClipMask, + pixels: &tiny_skia::PixmapMut<'_>, + bounds: Rectangle, +) { + 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 + .set_path( + pixels.width(), + pixels.height(), + &path, + tiny_skia::FillRule::EvenOdd, + true, + ) + .expect("Set path of clipping area"); +} + +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::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, + 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: Point, + nearest_only: bool, + ) -> Option<text::Hit> { + self.text_pipeline.hit_test( + contents, + size, + font, + bounds, + 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..c66621dd --- /dev/null +++ b/tiny_skia/src/geometry.rs @@ -0,0 +1,287 @@ +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 path = convert_path(path); + 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 path = convert_path(&Path::rectangle(top_left, size)); + 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 path = convert_path(path); + 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, + font: text.font, + horizontal_alignment: text.horizontal_alignment, + vertical_alignment: text.vertical_alignment, + }); + } + + 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, _translation: Vector) {} + + 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) -> 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(); + } + } + } + } + + builder + .finish() + .expect("Convert lyon path to tiny_skia path") +} + +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/primitive.rs b/tiny_skia/src/primitive.rs new file mode 100644 index 00000000..22daaedc --- /dev/null +++ b/tiny_skia/src/primitive.rs @@ -0,0 +1,82 @@ +use crate::{Rectangle, Vector}; + +use std::sync::Arc; + +#[derive(Debug, Clone)] +pub enum Primitive { + /// 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 { + /// The cached primitive + cache: Arc<Primitive>, + }, + /// A basic primitive. + Basic(iced_graphics::Primitive), +} + +impl iced_graphics::backend::Primitive for Primitive { + fn translate(self, translation: Vector) -> Self { + Self::Translate { + translation, + content: Box::new(self), + } + } + + fn clip(self, bounds: Rectangle) -> Self { + Self::Clip { + bounds, + content: Box::new(self), + } + } +} + +#[derive(Debug, Clone, Default)] +pub struct Recording(pub(crate) Vec<Primitive>); + +impl iced_graphics::backend::Recording for Recording { + type Primitive = Primitive; + + fn push(&mut self, primitive: Primitive) { + self.0.push(primitive); + } + + fn push_basic(&mut self, basic: iced_graphics::Primitive) { + self.0.push(Primitive::Basic(basic)); + } + + fn group(self) -> Self::Primitive { + Primitive::Group { primitives: self.0 } + } + + fn clear(&mut self) { + self.0.clear(); + } +} + +impl Recording { + pub fn primitives(&self) -> &[Primitive] { + &self.0 + } +} diff --git a/tiny_skia/src/raster.rs b/tiny_skia/src/raster.rs new file mode 100644 index 00000000..2fd73f8c --- /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::ClipMask>, + ) { + 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..9e4be0c4 --- /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::SansSerif, + 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..f2935efa --- /dev/null +++ b/tiny_skia/src/text.rs @@ -0,0 +1,431 @@ +use crate::core::alignment; +use crate::core::text::Hit; +use crate::core::{Color, Font, 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 { + system: Option<System>, + glyph_cache: GlyphCache, +} + +#[ouroboros::self_referencing] +struct System { + fonts: cosmic_text::FontSystem, + + #[borrows(fonts)] + #[not_covariant] + measurement_cache: RefCell<Cache<'this>>, + + #[borrows(fonts)] + #[not_covariant] + render_cache: Cache<'this>, +} + +impl Pipeline { + pub fn new() -> Self { + Pipeline { + system: Some( + SystemBuilder { + fonts: cosmic_text::FontSystem::new_with_fonts( + [cosmic_text::fontdb::Source::Binary(Arc::new( + include_bytes!("../../wgpu/fonts/Iced-Icons.ttf") + .as_slice(), + ))] + .into_iter(), + ), + measurement_cache_builder: |_| RefCell::new(Cache::new()), + render_cache_builder: |_| Cache::new(), + } + .build(), + ), + glyph_cache: GlyphCache::new(), + } + } + + pub fn load_font(&mut self, bytes: Cow<'static, [u8]>) { + let heads = self.system.take().unwrap().into_heads(); + + let (locale, mut db) = heads.fonts.into_locale_and_db(); + + db.load_font_source(cosmic_text::fontdb::Source::Binary(Arc::new( + bytes.into_owned(), + ))); + + self.system = Some( + SystemBuilder { + fonts: cosmic_text::FontSystem::new_with_locale_and_db( + locale, db, + ), + measurement_cache_builder: |_| RefCell::new(Cache::new()), + render_cache_builder: |_| Cache::new(), + } + .build(), + ); + } + + pub fn draw( + &mut self, + content: &str, + bounds: Rectangle, + color: Color, + size: f32, + font: Font, + horizontal_alignment: alignment::Horizontal, + vertical_alignment: alignment::Vertical, + pixels: &mut tiny_skia::PixmapMut<'_>, + clip_mask: Option<&tiny_skia::ClipMask>, + ) { + self.system.as_mut().unwrap().with_mut(|fields| { + let key = Key { + bounds: { + let size = bounds.size(); + + // TODO: Reuse buffers from layouting + Size::new(size.width.ceil(), size.height.ceil()) + }, + content, + font, + size, + }; + + let (_, buffer) = fields.render_cache.allocate(fields.fonts, 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 * size * 1.2; + + 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, + }; + + let mut swash = cosmic_text::SwashCache::new(fields.fonts); + + for run in buffer.layout_runs() { + for glyph in run.glyphs { + if let Some((buffer, placement)) = self + .glyph_cache + .allocate(glyph.cache_key, color, &mut swash) + { + let pixmap = tiny_skia::PixmapRef::from_bytes( + buffer, + placement.width, + placement.height, + ) + .expect("Create glyph pixel map"); + + pixels.draw_pixmap( + x as i32 + glyph.x_int + placement.left, + y as i32 - 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.system + .as_mut() + .unwrap() + .with_render_cache_mut(|cache| cache.trim()); + + self.glyph_cache.trim(); + } + + pub fn measure( + &self, + content: &str, + size: f32, + font: Font, + bounds: Size, + ) -> (f32, f32) { + self.system.as_ref().unwrap().with(|fields| { + let mut measurement_cache = fields.measurement_cache.borrow_mut(); + + let (_, paragraph) = measurement_cache.allocate( + fields.fonts, + Key { + content, + size, + font, + bounds, + }, + ); + + 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, size * 1.2 * total_lines as f32) + }) + } + + pub fn hit_test( + &self, + content: &str, + size: f32, + font: Font, + bounds: Size, + point: Point, + _nearest_only: bool, + ) -> Option<Hit> { + self.system.as_ref().unwrap().with(|fields| { + let mut measurement_cache = fields.measurement_cache.borrow_mut(); + + let (_, paragraph) = measurement_cache.allocate( + fields.fonts, + Key { + content, + size, + font, + bounds, + }, + ); + + let cursor = paragraph.hit(point.x, point.y)?; + + Some(Hit::CharOffset(cursor.index)) + }) + } + + pub fn trim_measurement_cache(&mut self) { + self.system + .as_mut() + .unwrap() + .with_measurement_cache_mut(|cache| cache.borrow_mut().trim()); + } +} + +fn to_family(font: Font) -> cosmic_text::Family<'static> { + match font { + Font::Name(name) => cosmic_text::Family::Name(name), + Font::SansSerif => cosmic_text::Family::SansSerif, + Font::Serif => cosmic_text::Family::Serif, + Font::Cursive => cosmic_text::Family::Cursive, + Font::Fantasy => cosmic_text::Family::Fantasy, + Font::Monospace => cosmic_text::Family::Monospace, + } +} + +#[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 { + fn new() -> Self { + GlyphCache::default() + } + + fn allocate( + &mut self, + cache_key: cosmic_text::CacheKey, + color: Color, + 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(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 > 300 { + self.entries + .retain(|key, _| self.recently_used.contains(key)); + + self.recently_used.clear(); + + self.trim_count = 0; + } else { + self.trim_count += 1; + } + } +} + +struct Cache<'a> { + entries: FxHashMap<KeyHash, cosmic_text::Buffer<'a>>, + 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<'a> Cache<'a> { + 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, + fonts: &'a cosmic_text::FontSystem, + key: Key<'_>, + ) -> (KeyHash, &mut cosmic_text::Buffer<'a>) { + let hash = { + let mut hasher = self.hasher.build_hasher(); + + key.content.hash(&mut hasher); + key.size.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); + + 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(fonts, metrics); + + buffer.set_size( + key.bounds.width, + key.bounds.height.max(key.size * 1.2), + ); + buffer.set_text( + key.content, + cosmic_text::Attrs::new() + .family(to_family(key.font)) + .monospaced(matches!(key.font, Font::Monospace)), + ); + + 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, + font: Font, + bounds: Size, +} + +type KeyHash = u64; diff --git a/tiny_skia/src/vector.rs b/tiny_skia/src/vector.rs new file mode 100644 index 00000000..8509b761 --- /dev/null +++ b/tiny_skia/src/vector.rs @@ -0,0 +1,180 @@ +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::ClipMask>, + ) { + 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> { + 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 { + usvg::FitTo::Width(size.width) + } else { + usvg::FitTo::Height(size.height) + }, + tiny_skia::Transform::default(), + image.as_mut(), + )?; + + if let Some([r, g, b, a]) = key.color { + // TODO: Blend alpha + let color = tiny_skia::ColorU8::from_rgba(b, g, r, a) + .premultiply() + .get() + & 0x00FFFFFF; + + // Apply color filter + for pixel in + bytemuck::cast_slice_mut::<u8, u32>(image.data_mut()) + { + *pixel = *pixel & 0xFF000000 | color; + } + } 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..cea1cabf --- /dev/null +++ b/tiny_skia/src/window/compositor.rs @@ -0,0 +1,130 @@ +use crate::core::Color; +use crate::graphics::compositor::{self, Information, SurfaceError}; +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> { + clip_mask: tiny_skia::ClipMask, + _theme: PhantomData<Theme>, +} + +pub struct Surface { + window: softbuffer::GraphicsContext, + buffer: Vec<u32>, +} + +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], + } + } + + fn configure_surface( + &mut self, + surface: &mut Surface, + width: u32, + height: u32, + ) { + surface.buffer.resize((width * height) as usize, 0); + } + + 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( + self, + backend, + surface, + primitives, + viewport, + background_color, + overlay, + ) + }) + } +} + +pub fn new<Theme>(settings: Settings) -> (Compositor<Theme>, Backend) { + // TOD + ( + Compositor { + clip_mask: tiny_skia::ClipMask::new(), + _theme: PhantomData, + }, + Backend::new(settings), + ) +} + +pub fn present<Theme, T: AsRef<str>>( + compositor: &mut Compositor<Theme>, + backend: &mut Backend, + surface: &mut Surface, + primitives: &[Primitive], + viewport: &Viewport, + background_color: Color, + overlay: &[T], +) -> Result<(), compositor::SurfaceError> { + let physical_size = viewport.physical_size(); + + backend.draw( + &mut tiny_skia::PixmapMut::from_bytes( + bytemuck::cast_slice_mut(&mut surface.buffer), + physical_size.width, + physical_size.height, + ) + .expect("Create pixel map"), + &mut compositor.clip_mask, + primitives, + viewport, + 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 9e80a76d..6a313d4f 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -8,23 +8,9 @@ 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"] -spirv = ["wgpu/spirv"] -webgl = ["wgpu/webgl"] +svg = ["resvg"] [dependencies] wgpu = "0.14" @@ -37,6 +23,9 @@ once_cell = "1.0" rustc-hash = "1.1" ouroboros = "0.15" +[target.'cfg(target_arch = "wasm32")'.dependencies] +wgpu = { version = "0.14", features = ["webgl"] } + [dependencies.twox-hash] version = "1.6" default-features = false @@ -49,10 +38,6 @@ features = ["std"] version = "1.9" features = ["derive"] -[dependencies.iced_native] -version = "0.9" -path = "../native" - [dependencies.iced_graphics] version = "0.7" path = "../graphics" @@ -60,11 +45,7 @@ path = "../graphics" [dependencies.glyphon] version = "0.2" git = "https://github.com/hecrj/glyphon.git" -rev = "810bc979f9005e2bd343b72b980e57e46174283f" - -[dependencies.tracing] -version = "0.1.6" -optional = true +rev = "edd23695ad53db5f89d455c3c130172fd107d6a2" [dependencies.encase] version = "0.3.0" @@ -73,6 +54,18 @@ features = ["glam"] [dependencies.glam] version = "0.21.3" +[dependencies.lyon] +version = "1.0" +optional = true + +[dependencies.resvg] +version = "0.29" +optional = true + +[dependencies.tracing] +version = "0.1.6" +optional = true + [package.metadata.docs.rs] rustdoc-args = ["--cfg", "docsrs"] all-features = true diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index e650d9a5..88c58554 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -1,11 +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::layer::Layer; -use iced_graphics::{Color, Font, Primitive, Size, Viewport}; +use crate::{Layer, Settings}; #[cfg(feature = "tracing")] use tracing::info_span; @@ -119,7 +119,7 @@ impl Backend { self.triangle_pipeline.end_frame(); #[cfg(any(feature = "image", feature = "svg"))] - self.image_pipeline.end_frame(device, queue, encoder); + self.image_pipeline.end_frame(); } fn prepare_text( @@ -364,9 +364,9 @@ impl backend::Text for Backend { size: f32, font: Font, bounds: Size, - point: iced_native::Point, + point: Point, nearest_only: bool, - ) -> Option<text::Hit> { + ) -> Option<core::text::Hit> { self.text_pipeline.hit_test( contents, size, @@ -384,17 +384,14 @@ impl backend::Text for Backend { #[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/graphics/src/widget/canvas/frame.rs b/wgpu/src/geometry.rs index d68548ae..59ec31fe 100644 --- a/graphics/src/widget/canvas/frame.rs +++ b/wgpu/src/geometry.rs @@ -1,9 +1,9 @@ -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}; +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; @@ -23,9 +23,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 +196,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 +206,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 +241,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 +264,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 +281,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(), @@ -344,10 +344,18 @@ 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(); + } + + pub fn push_transform(&mut self) { + self.transforms.previous.push(self.transforms.current); + } + + pub fn pop_transform(&mut self) { self.transforms.current = self.transforms.previous.pop().unwrap(); } @@ -363,14 +371,19 @@ impl Frame { f(&mut frame); + let translation = Vector::new(region.x, region.y); + + self.clip(frame, translation); + } + + pub fn clip(&mut self, frame: Frame, translation: Vector) { + let size = frame.size(); let primitives = frame.into_primitives(); 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 +393,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 +436,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 +449,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 +460,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 +478,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 +510,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 db05d2ff..4163e338 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::{Buffer, 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,9 +30,9 @@ 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, vertices: wgpu::Buffer, @@ -367,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, queue, encoder), &mut self.texture_atlas, ) { add_instances( @@ -391,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, queue, encoder), &mut self.texture_atlas, ) { add_instances( @@ -476,21 +481,12 @@ impl Pipeline { } } - pub fn end_frame( - &mut self, - device: &wgpu::Device, - queue: &wgpu::Queue, - 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, queue, 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, queue, encoder)); + self.vector_cache.borrow_mut().trim(&mut self.texture_atlas); self.prepare_layer = 0; } diff --git a/wgpu/src/image/atlas.rs b/wgpu/src/image/atlas.rs index 7df67abd..c00b8cef 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; use std::num::NonZeroU32; @@ -64,6 +63,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 { @@ -296,101 +386,3 @@ impl Atlas { }); } } - -impl image::Storage for Atlas { - type Entry = Entry; - type State<'a> = ( - &'a wgpu::Device, - &'a wgpu::Queue, - &'a mut wgpu::CommandEncoder, - ); - - fn upload( - &mut self, - width: u32, - height: u32, - data: &[u8], - (device, queue, encoder): &mut Self::State<'_>, - ) -> Option<Self::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) - } - - 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 82d77aff..3624e46b 100644 --- a/graphics/src/image/vector.rs +++ b/wgpu/src/image/vector.rs @@ -1,10 +1,9 @@ -//! 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; use std::collections::{HashMap, HashSet}; use std::fs; @@ -21,7 +20,7 @@ impl Svg { pub fn viewport_dimensions(&self) -> Size<u32> { match self { Svg::Loaded(tree) => { - let size = tree.svg_node().size; + let size = tree.size; Size::new(size.width() as u32, size.height() as u32) } @@ -31,17 +30,17 @@ 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 { if self.svgs.contains_key(&handle.id()) { @@ -51,20 +50,14 @@ impl<T: Storage> Cache<T> { let svg = match handle.data() { svg::Data::Path(path) => { let tree = fs::read_to_string(path).ok().and_then(|contents| { - usvg::Tree::from_str( - &contents, - &usvg::Options::default().to_ref(), - ) - .ok() + usvg::Tree::from_str(&contents, &usvg::Options::default()) + .ok() }); tree.map(Svg::Loaded).unwrap_or(Svg::NotFound) } svg::Data::Bytes(bytes) => { - match usvg::Tree::from_data( - bytes, - &usvg::Options::default().to_ref(), - ) { + match usvg::Tree::from_data(bytes, &usvg::Options::default()) { Ok(tree) => Svg::Loaded(tree), Err(_) => Svg::NotFound, } @@ -78,13 +71,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) = ( @@ -125,6 +120,7 @@ impl<T: Storage> Cache<T> { } else { usvg::FitTo::Height(height) }, + tiny_skia::Transform::default(), img.as_mut(), )?; @@ -140,7 +136,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); @@ -154,7 +152,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; @@ -163,7 +161,7 @@ impl<T: Storage> Cache<T> { let retain = rasterized_hits.contains(k); if !retain { - storage.remove(entry, state); + atlas.remove(entry); } retain @@ -173,17 +171,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 f6eb2fdd..cb9d5e2f 100644 --- a/graphics/src/layer.rs +++ b/wgpu/src/layer.rs @@ -10,11 +10,9 @@ pub use mesh::Mesh; pub use quad::Quad; pub use text::Text; -use crate::alignment; -use crate::{ - Background, Color, Font, Point, Primitive, Rectangle, Size, Vector, - Viewport, -}; +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)] @@ -110,18 +108,6 @@ 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, @@ -167,6 +153,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]; @@ -206,6 +213,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; @@ -236,34 +254,16 @@ 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, - }); + _ => { + // Unsupported! } } } 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 38d62616..fdbdaafb 100644 --- a/graphics/src/layer/text.rs +++ b/wgpu/src/layer/text.rs @@ -1,4 +1,5 @@ -use crate::{alignment, Color, Font, Rectangle}; +use crate::core::alignment; +use crate::core::{Color, Font, Rectangle}; /// A paragraph of text. #[derive(Debug, Clone, Copy)] diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 9da40572..473f3621 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -25,7 +25,7 @@ )] #![deny( missing_debug_implementations, - missing_docs, + //missing_docs, unsafe_code, unused_results, clippy::extra_unused_lifetimes, @@ -37,27 +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, Font, 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; -use crate::buffer::Buffer; -use iced_graphics::Transformation; +use buffer::Buffer; #[cfg(any(feature = "image", feature = "svg"))] mod image; @@ -66,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 246cc5e1..b55216d7 100644 --- a/wgpu/src/quad.rs +++ b/wgpu/src/quad.rs @@ -1,6 +1,7 @@ -use crate::{Buffer, 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; diff --git a/wgpu/src/settings.rs b/wgpu/src/settings.rs index bd9cf473..7c0750ef 100644 --- a/wgpu/src/settings.rs +++ b/wgpu/src/settings.rs @@ -1,7 +1,6 @@ //! Configure a renderer. -pub use crate::Antialiasing; - -use crate::Font; +use crate::core::Font; +use crate::graphics::Antialiasing; /// The settings of a [`Backend`]. /// diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 3839b31f..e99844e6 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -1,8 +1,7 @@ -pub use iced_native::text::Hit; - -use iced_graphics::layer::Text; -use iced_native::alignment; -use iced_native::{Font, Rectangle, Size}; +use crate::core::alignment; +use crate::core::text::Hit; +use crate::core::{Font, Point, Rectangle, Size}; +use crate::layer::Text; use rustc_hash::{FxHashMap, FxHashSet}; use std::borrow::Cow; @@ -275,9 +274,9 @@ impl Pipeline { &self, content: &str, size: f32, - font: iced_native::Font, - bounds: iced_native::Size, - point: iced_native::Point, + font: Font, + bounds: Size, + point: Point, _nearest_only: bool, ) -> Option<Hit> { self.system.as_ref().unwrap().with(|fields| { diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index 4b4fa16d..9fa521d7 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -2,12 +2,10 @@ mod msaa; use crate::buffer::r#static::Buffer; -use crate::settings; -use crate::Transformation; +use crate::core::{Gradient, Size}; +use crate::graphics::{Antialiasing, Transformation}; +use crate::layer::mesh::{self, Mesh}; -use iced_graphics::layer::mesh::{self, Mesh}; -use iced_graphics::triangle::ColoredVertex2D; -use iced_graphics::Size; #[cfg(feature = "tracing")] use tracing::info_span; @@ -137,7 +135,7 @@ impl Layer { 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; @@ -319,7 +317,7 @@ impl Pipeline { pub fn new( device: &wgpu::Device, format: wgpu::TextureFormat, - antialiasing: Option<settings::Antialiasing>, + antialiasing: Option<Antialiasing>, ) -> Pipeline { Pipeline { blit: antialiasing.map(|a| msaa::Blit::new(device, format, a)), @@ -453,7 +451,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), @@ -465,10 +463,11 @@ 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 { @@ -478,7 +477,7 @@ mod solid { #[derive(Debug)] pub struct Layer { - pub vertices: Buffer<triangle::ColoredVertex2D>, + pub vertices: Buffer<primitive::ColoredVertex2D>, pub uniforms: dynamic::Buffer<Uniforms>, pub constants: wgpu::BindGroup, } @@ -549,7 +548,7 @@ mod solid { pub fn new( device: &wgpu::Device, format: wgpu::TextureFormat, - antialiasing: Option<settings::Antialiasing>, + antialiasing: Option<Antialiasing>, ) -> Self { let constants_layout = device.create_bind_group_layout( &wgpu::BindGroupLayoutDescriptor { @@ -596,7 +595,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, @@ -632,12 +631,12 @@ 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 { @@ -647,7 +646,7 @@ mod gradient { #[derive(Debug)] pub struct Layer { - pub vertices: Buffer<Vertex2D>, + pub vertices: Buffer<primitive::Vertex2D>, pub uniforms: dynamic::Buffer<Uniforms>, pub storage: dynamic::Buffer<Storage>, pub constants: wgpu::BindGroup, @@ -754,7 +753,7 @@ mod gradient { pub(super) fn new( device: &wgpu::Device, format: wgpu::TextureFormat, - antialiasing: Option<settings::Antialiasing>, + antialiasing: Option<Antialiasing>, ) -> Self { let constants_layout = device.create_bind_group_layout( &wgpu::BindGroupLayoutDescriptor { @@ -810,34 +809,38 @@ 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, diff --git a/wgpu/src/triangle/msaa.rs b/wgpu/src/triangle/msaa.rs index a3016ff8..7144125c 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, 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 365cb603..a67ac3c0 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; @@ -112,7 +115,78 @@ impl<Theme> Compositor<Theme> { } } -impl<Theme> iced_graphics::window::Compositor for Compositor<Theme> { +/// 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> graphics::Compositor for Compositor<Theme> { type Settings = Settings; type Renderer = Renderer<Theme>; type Surface = wgpu::Surface; @@ -121,13 +195,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))) } @@ -135,11 +203,15 @@ 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) - } + let mut surface = unsafe { self.instance.create_surface(window) }; + + self.configure_surface(&mut surface, width, height); + + surface } fn configure_surface( @@ -178,49 +250,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()); - - renderer.with_primitives(|backend, primitives| { - backend.present( - &self.device, - &self.queue, - &mut encoder, - Some(background_color), - view, - primitives, - viewport, - overlay, - ); - }); - - // Submit work - let _submission = self.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) - } - }, - } + 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..4c23f3e8 --- /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.9" +path = "../runtime" + +[dependencies.iced_renderer] +version = "0.1" +path = "../renderer" + +[dependencies.iced_style] +version = "0.7" +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..efb33c56 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; /// 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,10 +48,11 @@ 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>; + ) -> Vec<Renderer::Geometry>; /// Returns the current mouse interaction of the [`Program`]. /// @@ -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) + ) -> Vec<Renderer::Geometry> { + 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 cd8b9c6b..d1f886c6 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}; @@ -29,8 +30,9 @@ pub struct Icon<Font> { /// /// # 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), @@ -43,10 +45,10 @@ pub struct Icon<Font> { /// ///  #[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>, @@ -63,7 +65,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; @@ -153,7 +155,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 @@ -289,14 +291,14 @@ where { let label_layout = children.next().unwrap(); - widget::text::draw( + crate::text::draw( renderer, style, label_layout, &self.label, self.text_size, self.font, - widget::text::Appearance { + crate::text::Appearance { color: custom_style.text_color, }, alignment::Horizontal::Left, @@ -311,7 +313,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>, 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..a43e7248 --- /dev/null +++ b/widget/src/helpers.rs @@ -0,0 +1,362 @@ +//! 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, 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(value, label, 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, + on_change: impl Fn(String) -> Message + 'a, +) -> TextInput<'a, Message, Renderer> +where + Message: Clone, + Renderer: core::text::Renderer, + Renderer::Theme: text_input::StyleSheet, +{ + TextInput::new(placeholder, value, on_change) +} + +/// 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()) +} 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..b08ed8cb 100644 --- a/lazy/src/lazy.rs +++ b/widget/src/lazy.rs @@ -1,12 +1,25 @@ -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; @@ -41,7 +54,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 +68,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 +92,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 +176,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 +317,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 +366,7 @@ where fn on_event( &mut self, - event: iced_native::Event, + event: Event, layout: Layout<'_>, cursor_position: Point, renderer: &Renderer, @@ -370,7 +383,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 +399,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 b23da9f7..0b8070af 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; @@ -67,7 +67,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( @@ -159,7 +159,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 { struct Tag<T>(T); @@ -203,7 +203,7 @@ where fn on_event( &mut self, tree: &mut Tree, - event: iced_native::Event, + event: core::Event, layout: Layout<'_>, cursor_position: Point, renderer: &Renderer, @@ -447,7 +447,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( @@ -497,13 +497,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); @@ -518,7 +518,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..be60bb78 100644 --- a/lazy/src/lib.rs +++ b/widget/src/lazy/helpers.rs @@ -1,34 +1,7 @@ -#![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; pub fn lazy<'a, Message, Renderer, Dependency, View>( @@ -51,7 +24,7 @@ where C: Component<Message, Renderer> + 'a, C::State: 'static, Message: 'a, - Renderer: iced_native::Renderer + 'a, + Renderer: core::Renderer + 'a, { component::view(component) } @@ -60,7 +33,7 @@ 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 57c07de1..7b2fc37c 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 update( &mut self, @@ -114,7 +115,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>() @@ -168,7 +169,7 @@ where fn on_event( &mut self, tree: &mut Tree, - event: iced_native::Event, + event: Event, layout: Layout<'_>, cursor_position: Point, renderer: &Renderer, @@ -271,7 +272,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, renderer, @@ -309,7 +311,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 { @@ -347,7 +349,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, @@ -396,7 +398,7 @@ where fn on_event( &mut self, - event: iced_native::Event, + event: Event, layout: Layout<'_>, cursor_position: Point, renderer: &Renderer, @@ -413,7 +415,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..a3e7c8bc --- /dev/null +++ b/widget/src/lib.rs @@ -0,0 +1,122 @@ +//! 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 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 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/widget/src/overlay.rs b/widget/src/overlay.rs new file mode 100644 index 00000000..b9a0e3e0 --- /dev/null +++ b/widget/src/overlay.rs @@ -0,0 +1 @@ +pub mod menu; diff --git a/native/src/overlay/menu.rs b/widget/src/overlay/menu.rs index bd58a122..c322c8ba 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, @@ -137,7 +137,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, @@ -193,7 +193,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, diff --git a/native/src/widget/pane_grid.rs b/widget/src/pane_grid.rs index bcb17ebd..257c0144 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 8ff82f3b..cd23cdd2 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, 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 5f60eaef..c2b6b017 100644 --- a/native/src/widget/radio.rs +++ b/widget/src/radio.rs @@ -1,25 +1,26 @@ //! 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>>; /// # /// #[derive(Debug, Clone, Copy, PartialEq, Eq)] /// pub enum Choice { @@ -41,7 +42,7 @@ pub use iced_style::radio::{Appearance, StyleSheet}; /// ///  #[allow(missing_debug_implementations)] -pub struct Radio<Message, Renderer> +pub struct Radio<Message, Renderer = crate::Renderer> where Renderer: text::Renderer, Renderer::Theme: StyleSheet, @@ -144,7 +145,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 @@ -269,14 +270,14 @@ where { let label_layout = children.next().unwrap(); - widget::text::draw( + crate::text::draw( renderer, style, label_layout, &self.label, self.text_size, self.font, - widget::text::Appearance { + crate::text::Appearance { color: custom_style.text_color, }, alignment::Horizontal::Left, @@ -291,7 +292,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 c1df8c39..5a7481f7 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::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>, @@ -43,7 +37,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`]. @@ -153,7 +147,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 { @@ -345,7 +339,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( @@ -763,7 +757,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(); @@ -809,8 +803,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 @@ -1068,8 +1062,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 { @@ -1139,10 +1133,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, }, }) @@ -1199,10 +1193,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, }, }) @@ -1264,64 +1258,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 d3715b1c..e1153d2d 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::{ Background, Clipboard, Color, Element, Layout, Length, Pixels, Point, Rectangle, Shell, Size, Widget, }; @@ -25,11 +25,9 @@ pub use iced_style::slider::{Appearance, Handle, HandleShape, StyleSheet}; /// 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 { @@ -43,9 +41,9 @@ pub use iced_style::slider::{Appearance, Handle, HandleShape, StyleSheet}; /// ///  #[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>, @@ -62,7 +60,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`]. @@ -148,7 +146,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 { @@ -245,7 +243,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( @@ -354,7 +352,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..04c31edc --- /dev/null +++ b/widget/src/text.rs @@ -0,0 +1,4 @@ +pub use crate::core::widget::text::*; + +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 65a9bd3b..d066109a 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, @@ -1109,7 +1112,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") { 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 d9c80ebe..713a9c30 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, @@ -136,7 +138,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 @@ -237,7 +239,7 @@ where if let Some(label) = &self.label { let label_layout = children.next().unwrap(); - crate::widget::text::draw( + crate::text::draw( renderer, style, label_layout, @@ -314,7 +316,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 f1687e38..62dc997f 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, Background, 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::{ + Background, 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 60e464c6..1bf63804 100644 --- a/winit/Cargo.toml +++ b/winit/Cargo.toml @@ -13,7 +13,7 @@ categories = ["gui"] [features] trace = ["tracing", "tracing-core", "tracing-subscriber"] chrome-trace = ["trace", "tracing-chrome"] -debug = ["iced_native/debug"] +debug = ["iced_runtime/debug"] system = ["sysinfo"] application = [] @@ -27,17 +27,17 @@ version = "0.27" git = "https://github.com/iced-rs/winit.git" rev = "940457522e9fb9f5dac228b0ecfafe0138b4048c" -[dependencies.iced_native] +[dependencies.iced_runtime] version = "0.9" -path = "../native" +path = "../runtime" [dependencies.iced_graphics] version = "0.7" path = "../graphics" -[dependencies.iced_futures] -version = "0.6" -path = "../futures" +[dependencies.iced_style] +version = "0.7" +path = "../style" [dependencies.tracing] version = "0.1.37" @@ -65,5 +65,5 @@ version = "0.3" features = ["Document", "Window"] [dependencies.sysinfo] -version = "0.23" +version = "0.28" optional = true diff --git a/winit/src/application.rs b/winit/src/application.rs index 889becad..48f94235 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; @@ -278,28 +278,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); @@ -319,7 +316,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, @@ -363,8 +360,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() @@ -414,7 +413,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( @@ -445,17 +444,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) } }, @@ -467,9 +463,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, )), @@ -618,7 +614,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(); @@ -659,7 +655,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")] @@ -691,7 +687,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`]. @@ -711,11 +707,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 { @@ -762,7 +758,7 @@ pub fn run_command<A, E>( window::Action::ChangeMode(mode) => { window.set_visible(conversion::visible(mode)); window.set_fullscreen(conversion::fullscreen( - window.primary_monitor(), + window.current_monitor(), mode, )); } @@ -770,7 +766,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 @@ -822,7 +818,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, @@ -852,7 +848,7 @@ pub fn run_command<A, E>( *cache = current_cache; } command::Action::LoadFont { bytes, tagger } => { - use crate::text::Renderer; + use crate::core::text::Renderer; // TODO: Error handling (?) renderer.load_font(bytes); 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 1b2ead36..0759ad9d 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.8/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 3a33e174..5cde510a 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/system.rs b/winit/src/system.rs index 619086b8..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>( @@ -16,11 +15,11 @@ pub fn fetch_information<Message>( pub(crate) fn information( graphics_info: compositor::Information, ) -> Information { - use sysinfo::{ProcessExt, ProcessorExt, System, SystemExt}; + use sysinfo::{CpuExt, ProcessExt, System, SystemExt}; let mut system = System::new_all(); system.refresh_all(); - let cpu = system.global_processor_info(); + let cpu = system.global_cpu_info(); let memory_used = sysinfo::get_current_pid() .and_then(|pid| system.process(pid).ok_or("Process not found")) |